'use client'; import React, { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { Button, Card, Input, Select, Textarea } from '../../../../shared/components'; import { useToast } from '@hykocx/zen/toast'; import { parseUTCDate, formatDateForDisplay, formatDateForInput, getTodayUTC } from '../../../../shared/lib/dates.js'; /** * Recurrence Create Page Component * Page for creating a new invoice recurrence */ const RecurrenceCreatePage = ({ user }) => { const router = useRouter(); const toast = useToast(); const [clients, setClients] = useState([]); const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [formData, setFormData] = useState({ client_id: '', frequency_type: 'months', frequency_value: 1, recurrence_day: 1, first_reminder_days: 30, first_occurrence_date: '', use_custom_first_date: false, notes: '', items: [{ name: '', description: '', quantity: 1, unit_price: 0 }] }); const [errors, setErrors] = useState({}); const [selectedItemIds, setSelectedItemIds] = useState(['custom']); const frequencyTypeOptions = [ { value: 'days', label: "jours" }, { value: 'months', label: "mois" }, { value: 'years', label: "ans" } ]; const reminderOptions = [ { value: 30, label: '30 jours' }, { value: 14, label: '14 jours' }, { value: 7, label: '7 jours' }, { value: 3, label: '3 jours' } ]; // Generate day options (1-28) const dayOptions = Array.from({ length: 28 }, (_, i) => ({ value: i + 1, label: `Jour ${i + 1}` })); // Generate frequency value options (1-12 for months/years, 1-90 for days) const getFrequencyValueOptions = () => { const maxValue = formData.frequency_type === 'days' ? 90 : 12; return Array.from({ length: maxValue }, (_, i) => ({ value: i + 1, label: i + 1 })); }; useEffect(() => { loadData(); }, []); const loadData = async () => { try { setLoading(true); // Load clients const clientsResponse = await fetch('/zen/api/admin/clients?limit=1000', { credentials: 'include' }); const clientsData = await clientsResponse.json(); if (clientsData.success) { setClients(clientsData.clients || []); } else { toast.error("Échec du chargement des clients"); } // Load items (active only) const itemsResponse = await fetch('/zen/api/admin/items?limit=1000&is_active=true', { credentials: 'include' }); const itemsData = await itemsResponse.json(); if (itemsData.success) { setItems(itemsData.items || []); } } catch (error) { console.error('Error loading data:', error); toast.error("Échec du chargement des données"); } finally { setLoading(false); } }; // Generate possible first occurrence dates based on recurrence settings const getFirstOccurrenceOptions = () => { if (!formData.frequency_type || !formData.recurrence_day) { return []; } if (formData.frequency_type === 'days') { return []; // Not applicable for days-based recurrence } const targetDay = parseInt(formData.recurrence_day); const today = getTodayUTC(); const options = []; // Generate 24 months (2 years) of options for (let i = 0; i < 24; i++) { const date = new Date(today); date.setUTCDate(targetDay); date.setUTCMonth(today.getUTCMonth() + i); const dateStr = formatDateForInput(date); const label = formatDateForDisplay(date, 'fr-FR', { year: 'numeric', month: 'long', day: 'numeric' }); options.push({ value: dateStr, label }); } return options; }; // Calculate first due date for preview const calculateFirstDueDate = () => { if (!formData.frequency_type || !formData.frequency_value || !formData.recurrence_day) { return null; } // If using custom first date, use it if (formData.use_custom_first_date && formData.first_occurrence_date) { return parseUTCDate(formData.first_occurrence_date); } const today = getTodayUTC(); if (formData.frequency_type === 'days') { const dueDate = new Date(today); dueDate.setUTCDate(dueDate.getUTCDate() + parseInt(formData.frequency_value)); return dueDate; } // For months/years, find the next occurrence of recurrence_day const targetDay = parseInt(formData.recurrence_day); const currentDay = today.getUTCDate(); let firstDueDate = new Date(today); firstDueDate.setUTCDate(targetDay); // If the target day has already passed this month, move to next period if (currentDay >= targetDay) { if (formData.frequency_type === 'months') { firstDueDate.setUTCMonth(firstDueDate.getUTCMonth() + 1); } else if (formData.frequency_type === 'years') { firstDueDate.setUTCFullYear(firstDueDate.getUTCFullYear() + 1); } } return firstDueDate; }; // Calculate invoice creation date for preview const calculateCreationDate = () => { const dueDate = calculateFirstDueDate(); if (!dueDate) return null; const creationDate = new Date(dueDate); creationDate.setUTCDate(creationDate.getUTCDate() - parseInt(formData.first_reminder_days)); return creationDate; }; const formatDate = (date) => { if (!date) return ''; return formatDateForDisplay(date, 'fr-FR', { year: 'numeric', month: 'long', day: 'numeric' }); }; const handleInputChange = (field, value) => { setFormData(prev => ({ ...prev, [field]: value })); if (errors[field]) { setErrors(prev => ({ ...prev, [field]: null })); } }; const handleItemsChange = (newItems) => { setFormData(prev => ({ ...prev, items: newItems })); }; const handleItemChange = (index, field, value) => { const newItems = [...formData.items]; newItems[index] = { ...newItems[index], [field]: value }; setFormData(prev => ({ ...prev, items: newItems })); if (errors[`item_${index}_${field}`]) { setErrors(prev => ({ ...prev, [`item_${index}_${field}`]: null })); } }; const handleSelectItem = (index, itemId) => { const newSelectedItemIds = [...selectedItemIds]; newSelectedItemIds[index] = itemId; setSelectedItemIds(newSelectedItemIds); if (itemId === '' || itemId === 'custom') { handleItemChange(index, 'name', ''); handleItemChange(index, 'description', ''); handleItemChange(index, 'unit_price', 0); return; } const selectedItem = items.find(item => item.id === parseInt(itemId)); if (selectedItem) { let fullItemName = ''; if (selectedItem.parent_category_title) { fullItemName = `${selectedItem.parent_category_title} - ${selectedItem.category_title} - ${selectedItem.name}`; } else if (selectedItem.category_title) { fullItemName = `${selectedItem.category_title} - ${selectedItem.name}`; } else { fullItemName = selectedItem.name; } const newItems = [...formData.items]; newItems[index] = { ...newItems[index], name: fullItemName, description: selectedItem.description || '', unit_price: selectedItem.unit_price }; setFormData(prev => ({ ...prev, items: newItems })); } }; const addItem = () => { setFormData(prev => ({ ...prev, items: [...prev.items, { name: '', description: '', quantity: 1, unit_price: 0 }] })); setSelectedItemIds(prev => [...prev, 'custom']); }; const removeItem = (index) => { if (formData.items.length > 1) { setFormData(prev => ({ ...prev, items: prev.items.filter((_, i) => i !== index) })); setSelectedItemIds(prev => prev.filter((_, i) => i !== index)); } }; const calculateItemTotal = (item) => { return parseFloat(item.quantity || 0) * parseFloat(item.unit_price || 0); }; const calculateTotals = () => { const subtotal = formData.items.reduce((sum, item) => { return sum + calculateItemTotal(item); }, 0); return { subtotal: parseFloat(subtotal.toFixed(2)), total: parseFloat(subtotal.toFixed(2)) }; }; const validateForm = () => { const newErrors = {}; if (!formData.client_id) { newErrors.client_id = 'Le client est requis'; } if (!formData.frequency_type) { newErrors.frequency_type = 'Le type de fréquence est requis'; } if (!formData.frequency_value || formData.frequency_value < 1) { newErrors.frequency_value = 'La valeur de fréquence doit être au moins 1'; } if (!formData.recurrence_day || formData.recurrence_day < 1 || formData.recurrence_day > 28) { newErrors.recurrence_day = 'Le jour de récurrence doit être entre 1 et 28'; } // Validate custom first occurrence date if enabled if (formData.use_custom_first_date && !formData.first_occurrence_date) { newErrors.first_occurrence_date = 'La date de première occurrence est requise lorsque la date personnalisée est activée'; } // Validate items formData.items.forEach((item, index) => { if (!item.name.trim()) { newErrors[`item_${index}_name`] = 'Le nom de l\'article est requis'; } if (!item.quantity || item.quantity <= 0) { newErrors[`item_${index}_quantity`] = 'La quantité doit être supérieure à 0'; } if (item.unit_price < 0) { newErrors[`item_${index}_unit_price`] = 'Le prix unitaire ne peut pas être négatif'; } }); setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = async (e) => { e.preventDefault(); if (!validateForm()) { return; } try { setSaving(true); // Prepare data - remove use_custom_first_date flag and clear first_occurrence_date if not using custom const submitData = { ...formData }; if (!submitData.use_custom_first_date) { submitData.first_occurrence_date = null; } delete submitData.use_custom_first_date; const response = await fetch('/zen/api/admin/recurrences', { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify({ recurrence: submitData }), }); const data = await response.json(); if (data.success) { toast.success("Récurrence créée avec succès"); router.push('/admin/invoice/recurrences'); } else { toast.error(data.error || 'Échec de la création de la récurrence'); } } catch (error) { console.error('Error creating recurrence:', error); toast.error("Échec de la création de la récurrence"); } finally { setSaving(false); } }; const firstDueDate = calculateFirstDueDate(); const creationDate = calculateCreationDate(); const totals = calculateTotals(); return (
{/* Header */}

Créer une récurrence

Configurer la génération automatique de factures

{/* Form */}
{/* Recurrence Information */}

Informations de la récurrence

handleInputChange('frequency_type', value)} options={frequencyTypeOptions} error={errors.frequency_type} /> handleInputChange('recurrence_day', value)} options={dayOptions} error={errors.recurrence_day} description="Jour du mois pour créer la facture (limité à 1-28 pour cohérence)" /> )} handleInputChange('use_custom_first_date', e.target.checked)} className="w-4 h-4 rounded border-neutral-300 dark:border-neutral-600 bg-neutral-100 dark:bg-neutral-800 text-blue-500 focus:ring-blue-500 focus:ring-offset-neutral-900" />
{formData.use_custom_first_date && ( handleSelectItem(index, value)} options={[ { value: 'custom', label: 'Custom' }, ...items.map(availableItem => { let categoryDisplay = ''; if (availableItem.parent_category_title) { categoryDisplay = `${availableItem.parent_category_title} - ${availableItem.category_title} - `; } else if (availableItem.category_title) { categoryDisplay = `${availableItem.category_title} - `; } return { value: availableItem.id, label: `${categoryDisplay}${availableItem.name} - $${parseFloat(availableItem.unit_price).toFixed(2)}` }; }) ]} disabled={loading} /> handleItemChange(index, 'name', value)} placeholder="Enter item name..." error={errors[`item_${index}_name`]} />
handleItemChange(index, 'quantity', value)} error={errors[`item_${index}_quantity`]} /> handleItemChange(index, 'unit_price', value)} error={errors[`item_${index}_unit_price`]} />