'use client'; import React, { useState, useEffect, useRef } from 'react'; import { DataTable } from 'primereact/datatable'; import { Column } from 'primereact/column'; import { Button } from 'primereact/button'; import { InputText } from 'primereact/inputtext'; import { Card } from 'primereact/card'; import { Toast } from 'primereact/toast'; import { Toolbar } from 'primereact/toolbar'; import { Tag } from 'primereact/tag'; import { Dialog } from 'primereact/dialog'; import { Calendar } from 'primereact/calendar'; import { InputTextarea } from 'primereact/inputtextarea'; import { InputNumber } from 'primereact/inputnumber'; import { Chip } from 'primereact/chip'; import { devisService } from '../../../../services/api'; import { formatDate, formatCurrency } from '../../../../utils/formatters'; import type { Devis } from '../../../../types/btp'; import devisActionsService from '../../../../services/devisActionsService'; const DevisExpiresPage = () => { const [devis, setDevis] = useState([]); const [loading, setLoading] = useState(true); const [globalFilter, setGlobalFilter] = useState(''); const [selectedDevis, setSelectedDevis] = useState([]); const [actionDialog, setActionDialog] = useState(false); const [selectedDevisItem, setSelectedDevisItem] = useState(null); const [actionType, setActionType] = useState<'renew' | 'archive' | 'follow_up'>('renew'); const [renewData, setRenewData] = useState({ newValidityDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), adjustments: '', newPrice: 0 }); const [followUpNotes, setFollowUpNotes] = useState(''); const toast = useRef(null); const dt = useRef>(null); useEffect(() => { loadDevis(); }, []); const loadDevis = async () => { try { setLoading(true); const data = await devisService.getAll(); // Filtrer les devis expirés ou marqués comme expirés const devisExpires = data.filter(devis => { const today = new Date(); const validityDate = new Date(devis.dateValidite); return devis.statut === 'EXPIRE' || (devis.statut === 'ENVOYE' && validityDate < today); }); setDevis(devisExpires); } catch (error) { console.error('Erreur lors du chargement des devis:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Impossible de charger les devis expirés', life: 3000 }); } finally { setLoading(false); } }; const getDaysExpired = (dateValidite: string | Date) => { const today = new Date(); const validityDate = new Date(dateValidite); const diffTime = today.getTime() - validityDate.getTime(); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); return Math.max(0, diffDays); }; const getExpiryCategory = (dateValidite: string | Date) => { const days = getDaysExpired(dateValidite); if (days <= 7) return { category: 'RÉCEMMENT', color: 'orange', severity: 'warning' as const }; if (days <= 30) return { category: 'ANCIEN', color: 'red', severity: 'danger' as const }; return { category: 'TRÈS ANCIEN', color: 'darkred', severity: 'danger' as const }; }; const calculateLostValue = () => { return devis.reduce((sum, d) => sum + (d.montantTTC || 0), 0); }; const renewDevis = (devisItem: Devis) => { setSelectedDevisItem(devisItem); setActionType('renew'); setRenewData({ newValidityDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), adjustments: '', newPrice: devisItem.montantTTC || 0 }); setActionDialog(true); }; const archiveDevis = (devisItem: Devis) => { setSelectedDevisItem(devisItem); setActionType('archive'); setActionDialog(true); }; const followUpDevis = (devisItem: Devis) => { setSelectedDevisItem(devisItem); setActionType('follow_up'); setFollowUpNotes(''); setActionDialog(true); }; const handleAction = async () => { if (!selectedDevisItem) return; try { let message = ''; switch (actionType) { case 'renew': await devisActionsService.renewDevis({ devisId: selectedDevisItem.id, nouveaueDateValidite: renewData.newValidityDate.toISOString(), modifications: renewData.modifications }); message = 'Devis renouvelé avec succès'; break; case 'archive': await devisActionsService.archiveDevis({ devisId: selectedDevisItem.id, motif: followUpNotes }); setDevis(prev => prev.filter(d => d.id !== selectedDevisItem.id)); message = 'Devis archivé avec succès'; break; case 'follow_up': await devisActionsService.followUpClient({ devisId: selectedDevisItem.id, clientId: selectedDevisItem.client.id, type: 'email', message: followUpNotes }); message = 'Suivi client effectué avec succès'; break; } setActionDialog(false); toast.current?.show({ severity: 'success', summary: 'Succès', detail: `${message} (simulation)`, life: 3000 }); } catch (error) { console.error('Erreur lors de l\'action:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Impossible d\'effectuer l\'action', life: 3000 }); } }; const exportCSV = () => { dt.current?.exportCSV(); }; const bulkArchive = async () => { if (selectedDevis.length === 0) { toast.current?.show({ severity: 'warn', summary: 'Attention', detail: 'Veuillez sélectionner au moins un devis', life: 3000 }); return; } // Simulation d'archivage en lot console.log('Archivage en lot de', selectedDevis.length, 'devis'); setDevis(prev => prev.filter(d => !selectedDevis.includes(d))); setSelectedDevis([]); toast.current?.show({ severity: 'success', summary: 'Succès', detail: `${selectedDevis.length} devis archivé(s) (simulation)`, life: 3000 }); }; const generateLossAnalysis = () => { const totalLoss = calculateLostValue(); const recentlyExpired = devis.filter(d => getDaysExpired(d.dateValidite) <= 7); const oldExpired = devis.filter(d => getDaysExpired(d.dateValidite) > 30); const report = ` === ANALYSE DES DEVIS EXPIRÉS === Date du rapport: ${new Date().toLocaleDateString('fr-FR')} STATISTIQUES GÉNÉRALES: - Nombre total de devis expirés: ${devis.length} - Valeur totale perdue: ${formatCurrency(totalLoss)} - Valeur moyenne par devis: ${formatCurrency(totalLoss / (devis.length || 1))} RÉPARTITION PAR ANCIENNETÉ: - Récemment expirés (≤7 jours): ${recentlyExpired.length} devis, ${formatCurrency(recentlyExpired.reduce((sum, d) => sum + (d.montantTTC || 0), 0))} - Anciens (>30 jours): ${oldExpired.length} devis, ${formatCurrency(oldExpired.reduce((sum, d) => sum + (d.montantTTC || 0), 0))} DEVIS À FORT POTENTIEL (>5000€): ${devis.filter(d => (d.montantTTC || 0) > 5000).map(d => ` - ${d.numero} - ${d.objet} Client: ${d.client ? `${d.client.prenom} ${d.client.nom}` : 'N/A'} Montant: ${formatCurrency(d.montantTTC || 0)} Expiré depuis: ${getDaysExpired(d.dateValidite)} jours `).join('')} ANALYSE PAR CLIENT: ${getClientAnalysis()} RECOMMANDATIONS: - Relancer les clients pour les devis récemment expirés - Proposer des devis renouvelés avec ajustements - Analyser les causes d'expiration pour améliorer le taux de conversion - Archiver les devis très anciens après relance finale `; const blob = new Blob([report], { type: 'text/plain;charset=utf-8;' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = `analyse_devis_expires_${new Date().toISOString().split('T')[0]}.txt`; link.click(); toast.current?.show({ severity: 'success', summary: 'Analyse générée', detail: 'Le rapport d\'analyse a été téléchargé', life: 3000 }); }; const getClientAnalysis = () => { const clientStats = {}; devis.forEach(d => { if (d.client) { const clientKey = `${d.client.prenom} ${d.client.nom}`; if (!clientStats[clientKey]) { clientStats[clientKey] = { count: 0, value: 0 }; } clientStats[clientKey].count++; clientStats[clientKey].value += d.montantTTC || 0; } }); return Object.entries(clientStats) .sort((a: [string, any], b: [string, any]) => b[1].value - a[1].value) .slice(0, 5) .map(([client, data]: [string, any]) => `- ${client}: ${data.count} devis, ${formatCurrency(data.value)}`) .join('\n'); }; const leftToolbarTemplate = () => { return (
Devis expirés ({devis.length})
); }; const rightToolbarTemplate = () => { return (