2026-01-21 18:13:35 +01:00
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import { useState, useEffect } from 'react';
|
|
|
|
|
import { useNotification } from './NotificationProvider';
|
|
|
|
|
import ConfirmModal from './ConfirmModal';
|
2026-02-16 14:43:02 +01:00
|
|
|
import { getParticipationRef } from '@/lib/participation-ref';
|
2026-01-21 18:13:35 +01:00
|
|
|
|
|
|
|
|
interface Trajet {
|
|
|
|
|
id: string;
|
|
|
|
|
date: string;
|
|
|
|
|
adresseDepart: string;
|
|
|
|
|
adresseArrivee: string;
|
|
|
|
|
commentaire?: string | null;
|
|
|
|
|
statut: string;
|
|
|
|
|
archived: boolean;
|
2026-02-16 14:43:02 +01:00
|
|
|
participations?: { id: string }[];
|
2026-01-21 18:13:35 +01:00
|
|
|
adherent: {
|
|
|
|
|
id: string;
|
|
|
|
|
nom: string;
|
|
|
|
|
prenom: string;
|
|
|
|
|
telephone: string;
|
|
|
|
|
email: string;
|
|
|
|
|
};
|
|
|
|
|
chauffeur?: {
|
|
|
|
|
id: string;
|
|
|
|
|
nom: string;
|
|
|
|
|
prenom: string;
|
|
|
|
|
telephone: string;
|
|
|
|
|
} | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function ArchivesTrajets() {
|
|
|
|
|
const { showNotification } = useNotification();
|
|
|
|
|
const [trajets, setTrajets] = useState<Trajet[]>([]);
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
|
|
|
const [showRestoreConfirm, setShowRestoreConfirm] = useState(false);
|
|
|
|
|
const [trajetToRestore, setTrajetToRestore] = useState<string | null>(null);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
fetchTrajets();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const fetchTrajets = async () => {
|
|
|
|
|
setLoading(true);
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch('/api/trajets/archives');
|
|
|
|
|
if (response.ok) {
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
setTrajets(data);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Erreur lors du chargement des archives:', error);
|
|
|
|
|
showNotification('error', 'Erreur lors du chargement des archives');
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleRestoreClick = (trajetId: string) => {
|
|
|
|
|
setTrajetToRestore(trajetId);
|
|
|
|
|
setShowRestoreConfirm(true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleRestore = async () => {
|
|
|
|
|
if (!trajetToRestore) return;
|
|
|
|
|
|
|
|
|
|
setShowRestoreConfirm(false);
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`/api/trajets/${trajetToRestore}/archive`, {
|
|
|
|
|
method: 'DELETE',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (response.ok) {
|
|
|
|
|
showNotification('success', 'Trajet restauré avec succès');
|
|
|
|
|
fetchTrajets();
|
|
|
|
|
} else {
|
|
|
|
|
const error = await response.json();
|
|
|
|
|
showNotification('error', error.error || 'Erreur lors de la restauration du trajet');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Erreur lors de la restauration:', error);
|
|
|
|
|
showNotification('error', 'Erreur lors de la restauration du trajet');
|
|
|
|
|
} finally {
|
|
|
|
|
setTrajetToRestore(null);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const formatDate = (dateString: string) => {
|
|
|
|
|
const date = new Date(dateString);
|
|
|
|
|
return date.toLocaleDateString('fr-FR', {
|
|
|
|
|
day: '2-digit',
|
|
|
|
|
month: '2-digit',
|
|
|
|
|
year: 'numeric',
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const formatTime = (dateString: string) => {
|
|
|
|
|
const date = new Date(dateString);
|
|
|
|
|
return date.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' });
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getStatutColor = (statut: string) => {
|
|
|
|
|
switch (statut) {
|
|
|
|
|
case 'Validé':
|
|
|
|
|
return 'bg-purple-100 text-purple-700 border-purple-200';
|
|
|
|
|
case 'Terminé':
|
|
|
|
|
return 'bg-green-100 text-green-700 border-green-200';
|
|
|
|
|
case 'En cours':
|
|
|
|
|
return 'bg-blue-100 text-blue-700 border-blue-200';
|
|
|
|
|
case 'Annulé':
|
|
|
|
|
return 'bg-red-100 text-red-700 border-red-200';
|
|
|
|
|
default:
|
|
|
|
|
return 'bg-gray-100 text-gray-700 border-gray-200';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getInitials = (nom: string, prenom: string) => {
|
|
|
|
|
return `${prenom.charAt(0)}${nom.charAt(0)}`.toUpperCase();
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-16 14:43:02 +01:00
|
|
|
const participationRef = (t: Trajet) => {
|
|
|
|
|
const p = t.participations?.[0];
|
|
|
|
|
return p ? getParticipationRef(p.id) : null;
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-21 18:13:35 +01:00
|
|
|
const filteredTrajets = trajets.filter((trajet) => {
|
2026-02-16 14:43:02 +01:00
|
|
|
const ref = participationRef(trajet);
|
2026-01-21 18:13:35 +01:00
|
|
|
const searchLower = searchTerm.toLowerCase();
|
|
|
|
|
return (
|
|
|
|
|
trajet.adherent.nom.toLowerCase().includes(searchLower) ||
|
|
|
|
|
trajet.adherent.prenom.toLowerCase().includes(searchLower) ||
|
|
|
|
|
trajet.adresseDepart.toLowerCase().includes(searchLower) ||
|
|
|
|
|
trajet.adresseArrivee.toLowerCase().includes(searchLower) ||
|
2026-02-16 14:43:02 +01:00
|
|
|
(trajet.chauffeur && `${trajet.chauffeur.prenom} ${trajet.chauffeur.nom}`.toLowerCase().includes(searchLower)) ||
|
|
|
|
|
(ref && ref.toLowerCase().includes(searchLower))
|
2026-01-21 18:13:35 +01:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (loading) {
|
|
|
|
|
return (
|
2026-02-08 15:27:44 +01:00
|
|
|
<div className="bg-white rounded-lg shadow-sm p-4 md:p-8">
|
|
|
|
|
<div className="text-center text-sm md:text-base text-gray-500">Chargement des archives...</div>
|
2026-01-21 18:13:35 +01:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="bg-white rounded-lg shadow-sm">
|
|
|
|
|
{/* Barre de recherche */}
|
2026-02-08 15:27:44 +01:00
|
|
|
<div className="p-3 md:p-4 lg:p-6 border-b border-gray-200">
|
|
|
|
|
<div className="flex items-center gap-2 md:gap-4">
|
2026-01-21 18:13:35 +01:00
|
|
|
<div className="flex-1 relative">
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
2026-02-16 14:43:02 +01:00
|
|
|
placeholder="Rechercher (adhérent, adresse, référence PART-…)..."
|
2026-01-21 18:13:35 +01:00
|
|
|
value={searchTerm}
|
|
|
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
2026-02-08 15:27:44 +01:00
|
|
|
className="w-full px-3 md:px-4 py-2 md:py-2.5 pl-9 md:pl-10 text-sm md:text-base border border-gray-300 rounded-lg focus:ring-2 focus:ring-lblue focus:border-transparent"
|
2026-01-21 18:13:35 +01:00
|
|
|
/>
|
|
|
|
|
<svg
|
2026-02-08 15:27:44 +01:00
|
|
|
className="w-4 h-4 md:w-5 md:h-5 text-gray-400 absolute left-2.5 md:left-3 top-1/2 transform -translate-y-1/2"
|
2026-01-21 18:13:35 +01:00
|
|
|
fill="none"
|
|
|
|
|
stroke="currentColor"
|
|
|
|
|
viewBox="0 0 24 24"
|
|
|
|
|
>
|
|
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Liste des trajets archivés */}
|
2026-02-08 15:27:44 +01:00
|
|
|
<div className="p-3 md:p-4 lg:p-6">
|
2026-01-21 18:13:35 +01:00
|
|
|
{filteredTrajets.length === 0 ? (
|
2026-02-08 15:27:44 +01:00
|
|
|
<div className="text-center py-8 md:py-12 text-sm md:text-base text-gray-500">
|
2026-01-21 18:13:35 +01:00
|
|
|
{searchTerm ? 'Aucun trajet trouvé' : 'Aucun trajet archivé'}
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
2026-02-08 15:27:44 +01:00
|
|
|
<div className="space-y-3 md:space-y-4">
|
2026-01-21 18:13:35 +01:00
|
|
|
{filteredTrajets.map((trajet) => (
|
|
|
|
|
<div
|
|
|
|
|
key={trajet.id}
|
2026-02-08 15:27:44 +01:00
|
|
|
className="border border-gray-200 rounded-lg p-3 md:p-4 hover:shadow-md transition-shadow"
|
2026-01-21 18:13:35 +01:00
|
|
|
>
|
2026-02-08 15:27:44 +01:00
|
|
|
<div className="flex flex-col md:flex-row md:items-start md:justify-between gap-3 md:gap-4">
|
2026-01-21 18:13:35 +01:00
|
|
|
<div className="flex-1">
|
2026-02-08 15:27:44 +01:00
|
|
|
<div className="flex items-center gap-2 md:gap-3 mb-2 md:mb-3 flex-wrap">
|
|
|
|
|
<div className="w-8 h-8 md:w-10 md:h-10 rounded-full bg-lgreen flex items-center justify-center text-white font-semibold text-xs md:text-sm flex-shrink-0">
|
2026-01-21 18:13:35 +01:00
|
|
|
{getInitials(trajet.adherent.nom, trajet.adherent.prenom)}
|
|
|
|
|
</div>
|
2026-02-08 15:27:44 +01:00
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
<div className="font-semibold text-sm md:text-base text-gray-900 truncate">
|
2026-01-21 18:13:35 +01:00
|
|
|
{trajet.adherent.prenom} {trajet.adherent.nom}
|
|
|
|
|
</div>
|
2026-02-08 15:27:44 +01:00
|
|
|
<div className="text-xs md:text-sm text-gray-500">
|
2026-01-21 18:13:35 +01:00
|
|
|
{formatDate(trajet.date)} à {formatTime(trajet.date)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-02-16 14:43:02 +01:00
|
|
|
{participationRef(trajet) && (
|
|
|
|
|
<span className="px-1.5 py-0.5 text-xs font-mono font-medium rounded bg-lblue/10 text-lblue flex-shrink-0" title="Référence de prescription">
|
|
|
|
|
{participationRef(trajet)}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
2026-02-08 15:27:44 +01:00
|
|
|
<span className={`px-2 py-1 text-xs font-medium rounded border ${getStatutColor(trajet.statut)} flex-shrink-0`}>
|
2026-01-21 18:13:35 +01:00
|
|
|
{trajet.statut}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-02-08 15:27:44 +01:00
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 md:gap-3 mb-2 md:mb-3">
|
2026-01-21 18:13:35 +01:00
|
|
|
<div className="flex items-start gap-2">
|
2026-02-08 15:27:44 +01:00
|
|
|
<div className="w-5 h-5 md:w-6 md:h-6 rounded-full bg-lgreen flex items-center justify-center text-white text-xs font-bold mt-0.5 flex-shrink-0">
|
2026-01-21 18:13:35 +01:00
|
|
|
A
|
|
|
|
|
</div>
|
2026-02-08 15:27:44 +01:00
|
|
|
<div className="min-w-0 flex-1">
|
2026-01-21 18:13:35 +01:00
|
|
|
<div className="text-xs text-gray-500">Départ</div>
|
2026-02-08 15:27:44 +01:00
|
|
|
<div className="text-xs md:text-sm text-gray-900 break-words">{trajet.adresseDepart}</div>
|
2026-01-21 18:13:35 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-start gap-2">
|
2026-02-08 15:27:44 +01:00
|
|
|
<div className="w-5 h-5 md:w-6 md:h-6 rounded-full bg-lblue flex items-center justify-center text-white text-xs font-bold mt-0.5 flex-shrink-0">
|
2026-01-21 18:13:35 +01:00
|
|
|
B
|
|
|
|
|
</div>
|
2026-02-08 15:27:44 +01:00
|
|
|
<div className="min-w-0 flex-1">
|
2026-01-21 18:13:35 +01:00
|
|
|
<div className="text-xs text-gray-500">Arrivée</div>
|
2026-02-08 15:27:44 +01:00
|
|
|
<div className="text-xs md:text-sm text-gray-900 break-words">{trajet.adresseArrivee}</div>
|
2026-01-21 18:13:35 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{trajet.chauffeur && (
|
2026-02-08 15:27:44 +01:00
|
|
|
<div className="flex items-center gap-2 text-xs md:text-sm text-gray-600">
|
|
|
|
|
<svg className="w-3.5 h-3.5 md:w-4 md:h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
2026-01-21 18:13:35 +01:00
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 17a2 2 0 11-4 0 2 2 0 014 0zM19 17a2 2 0 11-4 0 2 2 0 014 0z" />
|
|
|
|
|
</svg>
|
2026-02-08 15:27:44 +01:00
|
|
|
<span className="truncate">Chauffeur: {trajet.chauffeur.prenom} {trajet.chauffeur.nom}</span>
|
2026-01-21 18:13:35 +01:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => handleRestoreClick(trajet.id)}
|
2026-02-08 15:27:44 +01:00
|
|
|
className="md:ml-4 px-3 md:px-4 py-2 text-xs md:text-sm font-medium text-lgreen hover:text-dgreen transition-colors flex items-center justify-center gap-1.5 md:gap-2 w-full md:w-auto"
|
2026-01-21 18:13:35 +01:00
|
|
|
>
|
2026-02-08 15:27:44 +01:00
|
|
|
<svg className="w-3.5 h-3.5 md:w-4 md:h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
2026-01-21 18:13:35 +01:00
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
|
|
|
</svg>
|
|
|
|
|
Restaurer
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<ConfirmModal
|
|
|
|
|
isOpen={showRestoreConfirm}
|
|
|
|
|
title="Restaurer le trajet"
|
|
|
|
|
message="Êtes-vous sûr de vouloir restaurer ce trajet ? Il sera à nouveau visible dans le calendrier."
|
|
|
|
|
confirmText="Restaurer"
|
|
|
|
|
cancelText="Annuler"
|
|
|
|
|
confirmColor="primary"
|
|
|
|
|
onConfirm={handleRestore}
|
|
|
|
|
onCancel={() => {
|
|
|
|
|
setShowRestoreConfirm(false);
|
|
|
|
|
setTrajetToRestore(null);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|