137 lines
4.3 KiB
TypeScript
Executable File
137 lines
4.3 KiB
TypeScript
Executable File
/**
|
|
* Composant widget d'alertes (factures en retard, devis expirant)
|
|
*/
|
|
|
|
import React from 'react';
|
|
import { Card } from 'primereact/card';
|
|
import { Message } from 'primereact/message';
|
|
import { Button } from 'primereact/button';
|
|
import { Skeleton } from 'primereact/skeleton';
|
|
import { FactureEnRetard, DevisEnAttente } from '../../types/btp';
|
|
|
|
interface AlertsWidgetProps {
|
|
facturesEnRetard: FactureEnRetard[];
|
|
devisEnAttente: DevisEnAttente[];
|
|
loading?: boolean;
|
|
onViewFacture?: (id: string) => void;
|
|
onViewDevis?: (id: string) => void;
|
|
}
|
|
|
|
const AlertsWidget: React.FC<AlertsWidgetProps> = ({
|
|
facturesEnRetard,
|
|
devisEnAttente,
|
|
loading = false,
|
|
onViewFacture,
|
|
onViewDevis
|
|
}) => {
|
|
const getAlertSeverity = (jours: number) => {
|
|
if (jours <= 3) return 'warn';
|
|
if (jours <= 7) return 'info';
|
|
return 'error';
|
|
};
|
|
|
|
const header = (
|
|
<div className="flex align-items-center">
|
|
<i className="pi pi-exclamation-triangle text-orange-500 mr-2" />
|
|
<h5 className="m-0">Alertes</h5>
|
|
</div>
|
|
);
|
|
|
|
if (loading) {
|
|
return (
|
|
<Card>
|
|
<div className="flex align-items-center mb-3">
|
|
<Skeleton width="1.5rem" height="1.5rem" className="mr-2" />
|
|
<Skeleton width="4rem" height="1.5rem" />
|
|
</div>
|
|
<div className="space-y-2">
|
|
{[...Array(3)].map((_, i) => (
|
|
<div key={i} className="p-3 border-1 border-200 border-round">
|
|
<Skeleton width="100%" height="1rem" className="mb-2" />
|
|
<Skeleton width="60%" height="0.8rem" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
const hasAlerts = facturesEnRetard.length > 0 || devisEnAttente.length > 0;
|
|
|
|
return (
|
|
<Card>
|
|
<div className="flex align-items-center mb-3">
|
|
<i className="pi pi-exclamation-triangle text-orange-500 mr-2" />
|
|
<h5 className="m-0">Alertes</h5>
|
|
</div>
|
|
|
|
{!hasAlerts ? (
|
|
<Message
|
|
severity="success"
|
|
text="Aucune alerte pour le moment"
|
|
className="w-full"
|
|
/>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{/* Factures en retard */}
|
|
{facturesEnRetard.map((facture) => (
|
|
<div
|
|
key={facture.id}
|
|
className="flex align-items-center justify-content-between p-3 border-1 border-red-200 border-round bg-red-50"
|
|
>
|
|
<div className="flex-1">
|
|
<div className="font-medium text-900 mb-1">
|
|
Facture {facture.numero} en retard
|
|
</div>
|
|
<div className="text-500 text-sm">
|
|
{facture.client} • {facture.montantTTC.toLocaleString()} € •
|
|
{facture.joursRetard} jour{facture.joursRetard > 1 ? 's' : ''} de retard
|
|
</div>
|
|
</div>
|
|
{onViewFacture && (
|
|
<Button
|
|
icon="pi pi-eye"
|
|
className="p-button-text p-button-sm"
|
|
onClick={() => onViewFacture(facture.id)}
|
|
tooltip="Voir la facture"
|
|
/>
|
|
)}
|
|
</div>
|
|
))}
|
|
|
|
{/* Devis expirant bientôt */}
|
|
{devisEnAttente.map((devis) => (
|
|
<div
|
|
key={devis.id}
|
|
className={`flex align-items-center justify-content-between p-3 border-1 border-round ${
|
|
devis.joursRestants <= 3
|
|
? 'border-red-200 bg-red-50'
|
|
: 'border-orange-200 bg-orange-50'
|
|
}`}
|
|
>
|
|
<div className="flex-1">
|
|
<div className="font-medium text-900 mb-1">
|
|
Devis {devis.numero} expire bientôt
|
|
</div>
|
|
<div className="text-500 text-sm">
|
|
{devis.client} • {devis.montantTTC.toLocaleString()} € •
|
|
{devis.joursRestants} jour{devis.joursRestants > 1 ? 's' : ''} restant{devis.joursRestants > 1 ? 's' : ''}
|
|
</div>
|
|
</div>
|
|
{onViewDevis && (
|
|
<Button
|
|
icon="pi pi-eye"
|
|
className="p-button-text p-button-sm"
|
|
onClick={() => onViewDevis(devis.id)}
|
|
tooltip="Voir le devis"
|
|
/>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</Card>
|
|
);
|
|
};
|
|
|
|
export default AlertsWidget; |