diff --git a/app/api/chauffeurs/[id]/route.ts b/app/api/chauffeurs/[id]/route.ts new file mode 100644 index 0000000..8d2bc82 --- /dev/null +++ b/app/api/chauffeurs/[id]/route.ts @@ -0,0 +1,103 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { prisma } from '@/lib/prisma'; +import { getCurrentUser } from '@/lib/auth'; + +// GET - Obtenir un chauffeur par ID +export async function GET( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const user = await getCurrentUser(); + if (!user) { + return NextResponse.json({ error: 'Non autorisé' }, { status: 401 }); + } + + const chauffeur = await prisma.chauffeur.findUnique({ + where: { id: params.id }, + }); + + if (!chauffeur) { + return NextResponse.json( + { error: 'Chauffeur non trouvé' }, + { status: 404 } + ); + } + + return NextResponse.json(chauffeur); + } catch (error) { + console.error('Erreur lors de la récupération du chauffeur:', error); + return NextResponse.json( + { error: 'Erreur serveur' }, + { status: 500 } + ); + } +} + +// PUT - Mettre à jour un chauffeur +export async function PUT( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const user = await getCurrentUser(); + if (!user) { + return NextResponse.json({ error: 'Non autorisé' }, { status: 401 }); + } + + const body = await request.json(); + const { nom, prenom, dateNaissance, telephone, email, adresse, heuresContrat, dateDebutContrat, dateFinContrat, status } = body; + + const updateData: any = {}; + if (nom) updateData.nom = nom; + if (prenom) updateData.prenom = prenom; + if (dateNaissance) updateData.dateNaissance = new Date(dateNaissance); + if (telephone) updateData.telephone = telephone; + if (email) updateData.email = email; + if (adresse) updateData.adresse = adresse; + if (heuresContrat !== undefined) updateData.heuresContrat = heuresContrat; + if (dateDebutContrat) updateData.dateDebutContrat = new Date(dateDebutContrat); + if (dateFinContrat !== undefined) { + updateData.dateFinContrat = dateFinContrat ? new Date(dateFinContrat) : null; + } + if (status) updateData.status = status; + + const chauffeur = await prisma.chauffeur.update({ + where: { id: params.id }, + data: updateData, + }); + + return NextResponse.json(chauffeur); + } catch (error) { + console.error('Erreur lors de la mise à jour du chauffeur:', error); + return NextResponse.json( + { error: 'Erreur serveur' }, + { status: 500 } + ); + } +} + +// DELETE - Supprimer un chauffeur +export async function DELETE( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const user = await getCurrentUser(); + if (!user) { + return NextResponse.json({ error: 'Non autorisé' }, { status: 401 }); + } + + await prisma.chauffeur.delete({ + where: { id: params.id }, + }); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error('Erreur lors de la suppression du chauffeur:', error); + return NextResponse.json( + { error: 'Erreur serveur' }, + { status: 500 } + ); + } +} diff --git a/app/api/chauffeurs/route.ts b/app/api/chauffeurs/route.ts new file mode 100644 index 0000000..39eea51 --- /dev/null +++ b/app/api/chauffeurs/route.ts @@ -0,0 +1,89 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { prisma } from '@/lib/prisma'; +import { getCurrentUser } from '@/lib/auth'; + +// GET - Liste tous les chauffeurs avec recherche +export async function GET(request: NextRequest) { + try { + const user = await getCurrentUser(); + if (!user) { + return NextResponse.json({ error: 'Non autorisé' }, { status: 401 }); + } + + const searchParams = request.nextUrl.searchParams; + const search = searchParams.get('search') || ''; + + // Récupérer tous les chauffeurs (SQLite ne supporte pas bien les recherches complexes) + const allChauffeurs = await prisma.chauffeur.findMany({ + orderBy: { createdAt: 'desc' }, + }); + + // Filtrer en JavaScript pour la recherche insensible à la casse + const chauffeurs = search + ? allChauffeurs.filter( + (ch) => { + const searchLower = search.toLowerCase(); + return ( + ch.nom.toLowerCase().includes(searchLower) || + ch.prenom.toLowerCase().includes(searchLower) || + ch.email.toLowerCase().includes(searchLower) || + ch.telephone.includes(search) || + ch.adresse.toLowerCase().includes(searchLower) + ); + } + ) + : allChauffeurs; + + return NextResponse.json(chauffeurs); + } catch (error) { + console.error('Erreur lors de la récupération des chauffeurs:', error); + return NextResponse.json( + { error: 'Erreur serveur' }, + { status: 500 } + ); + } +} + +// POST - Créer un nouveau chauffeur +export async function POST(request: NextRequest) { + try { + const user = await getCurrentUser(); + if (!user) { + return NextResponse.json({ error: 'Non autorisé' }, { status: 401 }); + } + + const body = await request.json(); + const { nom, prenom, dateNaissance, telephone, email, adresse, heuresContrat, dateDebutContrat, dateFinContrat } = body; + + if (!nom || !prenom || !dateNaissance || !telephone || !email || !adresse || !heuresContrat || !dateDebutContrat) { + return NextResponse.json( + { error: 'Tous les champs obligatoires sont requis' }, + { status: 400 } + ); + } + + const chauffeur = await prisma.chauffeur.create({ + data: { + nom, + prenom, + dateNaissance: new Date(dateNaissance), + telephone, + email, + adresse, + heuresContrat: heuresContrat || 35, + dateDebutContrat: new Date(dateDebutContrat), + dateFinContrat: dateFinContrat ? new Date(dateFinContrat) : null, + heuresRestantes: heuresContrat || 35, // Initialiser avec le nombre d'heures du contrat + status: 'Disponible', // Par défaut + }, + }); + + return NextResponse.json(chauffeur, { status: 201 }); + } catch (error) { + console.error('Erreur lors de la création du chauffeur:', error); + return NextResponse.json( + { error: 'Erreur serveur' }, + { status: 500 } + ); + } +} diff --git a/app/api/univers-pro/[id]/route.ts b/app/api/univers-pro/[id]/route.ts new file mode 100644 index 0000000..9719ebc --- /dev/null +++ b/app/api/univers-pro/[id]/route.ts @@ -0,0 +1,97 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { prisma } from '@/lib/prisma'; +import { getCurrentUser } from '@/lib/auth'; + +// GET - Obtenir un contact par ID +export async function GET( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const user = await getCurrentUser(); + if (!user) { + return NextResponse.json({ error: 'Non autorisé' }, { status: 401 }); + } + + const contact = await prisma.universPro.findUnique({ + where: { id: params.id }, + }); + + if (!contact) { + return NextResponse.json( + { error: 'Contact non trouvé' }, + { status: 404 } + ); + } + + return NextResponse.json(contact); + } catch (error) { + console.error('Erreur lors de la récupération du contact:', error); + return NextResponse.json( + { error: 'Erreur serveur' }, + { status: 500 } + ); + } +} + +// PUT - Mettre à jour un contact +export async function PUT( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const user = await getCurrentUser(); + if (!user) { + return NextResponse.json({ error: 'Non autorisé' }, { status: 401 }); + } + + const body = await request.json(); + const { nom, prenom, telephone, email, adresse, nomEntreprise } = body; + + const updateData: any = {}; + if (nom) updateData.nom = nom; + if (prenom) updateData.prenom = prenom; + if (telephone) updateData.telephone = telephone; + if (email) updateData.email = email; + if (adresse) updateData.adresse = adresse; + if (nomEntreprise) updateData.nomEntreprise = nomEntreprise; + + const contact = await prisma.universPro.update({ + where: { id: params.id }, + data: updateData, + }); + + return NextResponse.json(contact); + } catch (error) { + console.error('Erreur lors de la mise à jour du contact:', error); + return NextResponse.json( + { error: 'Erreur serveur' }, + { status: 500 } + ); + } +} + +// DELETE - Supprimer un contact +export async function DELETE( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const user = await getCurrentUser(); + if (!user) { + return NextResponse.json({ error: 'Non autorisé' }, { status: 401 }); + } + + await prisma.universPro.delete({ + where: { id: params.id }, + }); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error('Erreur lors de la suppression du contact:', error); + return NextResponse.json( + { error: 'Erreur serveur' }, + { status: 500 } + ); + } +} diff --git a/app/api/univers-pro/route.ts b/app/api/univers-pro/route.ts new file mode 100644 index 0000000..5d6cbfd --- /dev/null +++ b/app/api/univers-pro/route.ts @@ -0,0 +1,85 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { prisma } from '@/lib/prisma'; +import { getCurrentUser } from '@/lib/auth'; + +// GET - Liste tous les contacts univers pro avec recherche +export async function GET(request: NextRequest) { + try { + const user = await getCurrentUser(); + if (!user) { + return NextResponse.json({ error: 'Non autorisé' }, { status: 401 }); + } + + const searchParams = request.nextUrl.searchParams; + const search = searchParams.get('search') || ''; + + // Récupérer tous les contacts (SQLite ne supporte pas bien les recherches complexes) + const allContacts = await prisma.universPro.findMany({ + orderBy: { createdAt: 'desc' }, + }); + + // Filtrer en JavaScript pour la recherche insensible à la casse + const contacts = search + ? allContacts.filter( + (contact) => { + const searchLower = search.toLowerCase(); + return ( + contact.nom.toLowerCase().includes(searchLower) || + contact.prenom.toLowerCase().includes(searchLower) || + contact.email.toLowerCase().includes(searchLower) || + contact.telephone.includes(search) || + contact.adresse.toLowerCase().includes(searchLower) || + contact.nomEntreprise.toLowerCase().includes(searchLower) + ); + } + ) + : allContacts; + + return NextResponse.json(contacts); + } catch (error) { + console.error('Erreur lors de la récupération des contacts:', error); + return NextResponse.json( + { error: 'Erreur serveur' }, + { status: 500 } + ); + } +} + +// POST - Créer un nouveau contact +export async function POST(request: NextRequest) { + try { + const user = await getCurrentUser(); + if (!user) { + return NextResponse.json({ error: 'Non autorisé' }, { status: 401 }); + } + + const body = await request.json(); + const { nom, prenom, telephone, email, adresse, nomEntreprise } = body; + + if (!nom || !prenom || !telephone || !email || !adresse || !nomEntreprise) { + return NextResponse.json( + { error: 'Tous les champs sont requis' }, + { status: 400 } + ); + } + + const contact = await prisma.universPro.create({ + data: { + nom, + prenom, + telephone, + email, + adresse, + nomEntreprise, + }, + }); + + return NextResponse.json(contact, { status: 201 }); + } catch (error) { + console.error('Erreur lors de la création du contact:', error); + return NextResponse.json( + { error: 'Erreur serveur' }, + { status: 500 } + ); + } +} diff --git a/app/dashboard/chauffeurs/page.tsx b/app/dashboard/chauffeurs/page.tsx new file mode 100644 index 0000000..8ede46f --- /dev/null +++ b/app/dashboard/chauffeurs/page.tsx @@ -0,0 +1,27 @@ +import { redirect } from 'next/navigation'; +import { getCurrentUser } from '@/lib/auth'; +import DashboardLayout from '@/components/DashboardLayout'; +import ChauffeursTable from '@/components/ChauffeursTable'; + +export default async function ChauffeursPage() { + const user = await getCurrentUser(); + + if (!user) { + redirect('/login'); + } + + return ( + +
+

+ Chauffeurs +

+

+ Base de données des chauffeurs +

+ + +
+
+ ); +} diff --git a/app/dashboard/univers-pro/page.tsx b/app/dashboard/univers-pro/page.tsx new file mode 100644 index 0000000..c4023f1 --- /dev/null +++ b/app/dashboard/univers-pro/page.tsx @@ -0,0 +1,27 @@ +import { redirect } from 'next/navigation'; +import { getCurrentUser } from '@/lib/auth'; +import DashboardLayout from '@/components/DashboardLayout'; +import UniversProTable from '@/components/UniversProTable'; + +export default async function UniversProPage() { + const user = await getCurrentUser(); + + if (!user) { + redirect('/login'); + } + + return ( + +
+

+ Univers Pro +

+

+ Base de données des contacts professionnels +

+ + +
+
+ ); +} diff --git a/app/globals.css b/app/globals.css index c9726fe..3b8de5b 100644 --- a/app/globals.css +++ b/app/globals.css @@ -25,3 +25,31 @@ body { text-wrap: balance; } } + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-fadeIn { + animation: fadeIn 0.2s ease-out; +} + +.animate-slideUp { + animation: slideUp 0.3s ease-out; +} diff --git a/components/ChauffeurForm.tsx b/components/ChauffeurForm.tsx new file mode 100644 index 0000000..afb7532 --- /dev/null +++ b/components/ChauffeurForm.tsx @@ -0,0 +1,360 @@ +'use client'; + +import { useState, useEffect } from 'react'; + +interface Chauffeur { + id: string; + nom: string; + prenom: string; + dateNaissance: string; + telephone: string; + email: string; + adresse: string; + heuresContrat: number; + dateDebutContrat: string; + dateFinContrat: string | null; + heuresRestantes?: number; + status?: string; +} + +interface ChauffeurFormProps { + chauffeur: Chauffeur | null; + onClose: () => void; +} + +export default function ChauffeurForm({ chauffeur, onClose }: ChauffeurFormProps) { + const [loading, setLoading] = useState(false); + const [formData, setFormData] = useState({ + nom: '', + prenom: '', + dateNaissance: '', + telephone: '', + email: '', + adresse: '', + heuresContrat: 35, + dateDebutContrat: new Date().toISOString().split('T')[0], // Date du jour par défaut + dateFinContrat: '', + status: 'Disponible', + }); + + useEffect(() => { + if (chauffeur) { + const dateNaissance = new Date(chauffeur.dateNaissance); + const dateDebut = new Date(chauffeur.dateDebutContrat); + const dateFin = chauffeur.dateFinContrat ? new Date(chauffeur.dateFinContrat) : null; + + setFormData({ + nom: chauffeur.nom, + prenom: chauffeur.prenom, + dateNaissance: dateNaissance.toISOString().split('T')[0], + telephone: chauffeur.telephone, + email: chauffeur.email, + adresse: chauffeur.adresse, + heuresContrat: chauffeur.heuresContrat, + dateDebutContrat: dateDebut.toISOString().split('T')[0], + dateFinContrat: dateFin ? dateFin.toISOString().split('T')[0] : '', + status: chauffeur.status || 'Disponible', + }); + } + }, [chauffeur]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + + try { + const url = chauffeur ? `/api/chauffeurs/${chauffeur.id}` : '/api/chauffeurs'; + const method = chauffeur ? 'PUT' : 'POST'; + + const payload = { + ...formData, + dateFinContrat: formData.dateFinContrat || null, // Convertir chaîne vide en null + }; + + const response = await fetch(url, { + method, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + + if (response.ok) { + onClose(); + } else { + const error = await response.json(); + alert(error.error || 'Une erreur est survenue'); + } + } catch (error) { + console.error('Erreur:', error); + alert('Une erreur est survenue'); + } finally { + setLoading(false); + } + }; + + return ( +
+
+
+
+

+ {chauffeur ? 'Modifier le chauffeur' : 'Nouveau chauffeur'} +

+

+ {chauffeur ? 'Modifiez les informations du chauffeur ci-dessous.' : 'Remplissez les informations pour créer un nouveau chauffeur.'} +

+
+ +
+ +
+
+
+ +
+
+ + + +
+ setFormData({ ...formData, nom: e.target.value })} + className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-lblue focus:border-transparent" + /> +
+
+ +
+ +
+
+ + + +
+ setFormData({ ...formData, prenom: e.target.value })} + className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-lblue focus:border-transparent" + /> +
+
+ +
+ +
+
+ + + +
+ setFormData({ ...formData, dateNaissance: e.target.value })} + className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-lblue focus:border-transparent" + /> +
+
+ +
+ +
+
+ + + +
+ setFormData({ ...formData, telephone: e.target.value })} + className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-lblue focus:border-transparent" + /> +
+
+ +
+ +
+
+ + + +
+ setFormData({ ...formData, email: e.target.value })} + className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-lblue focus:border-transparent" + /> +
+
+ +
+ +
+
+ + + + +
+ setFormData({ ...formData, adresse: e.target.value })} + className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-lblue focus:border-transparent" + /> +
+
+ +
+ +
+
+ + + +
+ setFormData({ ...formData, heuresContrat: parseInt(e.target.value) || 0 })} + className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-lblue focus:border-transparent" + placeholder="35" + /> +
+
+ +
+ +
+
+ + + +
+ setFormData({ ...formData, dateDebutContrat: e.target.value })} + className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-lblue focus:border-transparent" + /> +
+
+ +
+ +
+
+ + + +
+ setFormData({ ...formData, dateFinContrat: e.target.value })} + className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-lblue focus:border-transparent" + /> +
+
+ +
+ +
+
+ + + +
+ +
+ + + +
+
+
+
+ +
+ + +
+
+
+
+ ); +} diff --git a/components/ChauffeursTable.tsx b/components/ChauffeursTable.tsx new file mode 100644 index 0000000..846fc18 --- /dev/null +++ b/components/ChauffeursTable.tsx @@ -0,0 +1,500 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import ChauffeurForm from './ChauffeurForm'; + +interface Chauffeur { + id: string; + nom: string; + prenom: string; + dateNaissance: string; + telephone: string; + email: string; + adresse: string; + heuresContrat: number; + dateDebutContrat: string; + dateFinContrat: string | null; + heuresRestantes?: number; + status?: string; +} + +export default function ChauffeursTable() { + const [chauffeurs, setChauffeurs] = useState([]); + const [search, setSearch] = useState(''); + const [loading, setLoading] = useState(true); + const [showForm, setShowForm] = useState(false); + const [editingChauffeur, setEditingChauffeur] = useState(null); + const [viewingChauffeur, setViewingChauffeur] = useState(null); + + const fetchChauffeurs = async (searchTerm: string = '') => { + setLoading(true); + try { + const url = searchTerm + ? `/api/chauffeurs?search=${encodeURIComponent(searchTerm)}` + : '/api/chauffeurs'; + const response = await fetch(url); + if (response.ok) { + const data = await response.json(); + setChauffeurs(data); + } + } catch (error) { + console.error('Erreur lors du chargement des chauffeurs:', error); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + const timeoutId = setTimeout(() => { + fetchChauffeurs(search); + }, 300); // Debounce de 300ms pour la recherche + + return () => clearTimeout(timeoutId); + }, [search]); + + useEffect(() => { + fetchChauffeurs(); + }, []); + + const handleDelete = async (id: string) => { + if (!confirm('Êtes-vous sûr de vouloir supprimer ce chauffeur ?')) { + return; + } + + try { + const response = await fetch(`/api/chauffeurs/${id}`, { + method: 'DELETE', + }); + + if (response.ok) { + fetchChauffeurs(search); + } else { + alert('Erreur lors de la suppression'); + } + } catch (error) { + console.error('Erreur lors de la suppression:', error); + alert('Erreur lors de la suppression'); + } + }; + + const handleEdit = (chauffeur: Chauffeur) => { + setEditingChauffeur(chauffeur); + setShowForm(true); + }; + + const handleView = async (id: string) => { + try { + const response = await fetch(`/api/chauffeurs/${id}`); + if (response.ok) { + const data = await response.json(); + setViewingChauffeur(data); + } + } catch (error) { + console.error('Erreur lors de la récupération:', error); + } + }; + + + const handleFormClose = () => { + setShowForm(false); + setEditingChauffeur(null); + fetchChauffeurs(search); + }; + + const getInitials = (nom: string, prenom: string) => { + return `${prenom.charAt(0)}${nom.charAt(0)}`.toUpperCase(); + }; + + const getStatusColor = (status: string) => { + switch (status) { + case 'Disponible': + return 'bg-lgreen text-white'; + case 'Vacances': + return 'bg-lblue text-white'; + case 'Arrêt Maladie': + return 'bg-lorange text-white'; + default: + return 'bg-gray-200 text-gray-700'; + } + }; + + const formatDate = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric' }); + }; + + const getProgressPercentage = (restantes: number, total: number) => { + return ((total - restantes) / total) * 100; + }; + + return ( + <> + {/* Barre de recherche et actions */} +
+
+ {/* Barre de recherche */} +
+
+
+ + + +
+ setSearch(e.target.value)} + className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-lblue focus:border-transparent" + /> +
+
+ + {/* Boutons d'action */} +
+ + + +
+
+
+ + {/* Tableau */} +
+ {loading ? ( +
Chargement...
+ ) : chauffeurs.length === 0 ? ( +
Aucun chauffeur trouvé
+ ) : ( +
+ + + + + + + + + + + + + {chauffeurs.map((chauffeur) => ( + + + + + + + + + ))} + +
NOMCONTACTADRESSENOMBRES D'HEURESSTATUSACTIONS
+
+
+ {getInitials(chauffeur.nom, chauffeur.prenom)} +
+
+
+ {chauffeur.prenom} {chauffeur.nom} +
+
+ Né le {formatDate(chauffeur.dateNaissance)} +
+
+
+
+
{chauffeur.telephone}
+
{chauffeur.email}
+
+
{chauffeur.adresse}
+
+
+
+ + {chauffeur.heuresRestantes || chauffeur.heuresContrat}h restantes sur {chauffeur.heuresContrat}h + +
+
+
+
+
+
+ {chauffeur.status && ( + + {chauffeur.status} + + )} + +
+ + + +
+
+
+ )} +
+ + {/* Modal formulaire */} + {showForm && ( + + )} + + {/* Modal vue détaillée */} + {viewingChauffeur && ( +
+
+ {/* Header sobre */} +
+
+
+
+ {getInitials(viewingChauffeur.nom, viewingChauffeur.prenom)} +
+
+

+ {viewingChauffeur.prenom} {viewingChauffeur.nom} +

+

+ Informations détaillées du chauffeur +

+
+
+ +
+
+ + {/* Contenu scrollable */} +
+ {/* Section Informations personnelles */} +
+

+ Informations personnelles +

+
+
+
+ Date de naissance +
+
+ + + + {formatDate(viewingChauffeur.dateNaissance)} +
+
+ +
+
+ Téléphone +
+ +
+ +
+
+ Email +
+ +
+ +
+
+ Adresse +
+
+ + + + + {viewingChauffeur.adresse} +
+
+
+
+ + {/* Section Contrat */} +
+

+ Informations contractuelles +

+
+
+
+ Contrat d'heure +
+
+ + + + {viewingChauffeur.heuresContrat}h +
+
+ +
+
+ Date de début +
+
+ + + + {formatDate(viewingChauffeur.dateDebutContrat)} +
+
+ + {viewingChauffeur.dateFinContrat && ( +
+
+ Date de fin +
+
+ + + + {formatDate(viewingChauffeur.dateFinContrat)} +
+
+ )} + + {viewingChauffeur.heuresRestantes !== undefined && ( +
+
+ Heures restantes +
+
+
+ + {viewingChauffeur.heuresRestantes}h / {viewingChauffeur.heuresContrat}h + +
+
+
+
+
+
+ )} + + {viewingChauffeur.status && ( +
+
+ Status +
+
+ + {viewingChauffeur.status} + +
+
+ )} +
+
+
+ + {/* Footer sobre */} +
+
+ + +
+
+
+
+ )} + + ); +} diff --git a/components/UniversProForm.tsx b/components/UniversProForm.tsx new file mode 100644 index 0000000..c2eeb20 --- /dev/null +++ b/components/UniversProForm.tsx @@ -0,0 +1,246 @@ +'use client'; + +import { useState, useEffect } from 'react'; + +interface UniversPro { + id: string; + nom: string; + prenom: string; + telephone: string; + email: string; + adresse: string; + nomEntreprise: string; +} + +interface UniversProFormProps { + contact: UniversPro | null; + onClose: () => void; +} + +export default function UniversProForm({ contact, onClose }: UniversProFormProps) { + const [loading, setLoading] = useState(false); + const [formData, setFormData] = useState({ + nom: '', + prenom: '', + telephone: '', + email: '', + adresse: '', + nomEntreprise: '', + }); + + useEffect(() => { + if (contact) { + setFormData({ + nom: contact.nom, + prenom: contact.prenom, + telephone: contact.telephone, + email: contact.email, + adresse: contact.adresse, + nomEntreprise: contact.nomEntreprise, + }); + } + }, [contact]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + + try { + const url = contact ? `/api/univers-pro/${contact.id}` : '/api/univers-pro'; + const method = contact ? 'PUT' : 'POST'; + + const response = await fetch(url, { + method, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(formData), + }); + + if (response.ok) { + onClose(); + } else { + const error = await response.json(); + alert(error.error || 'Une erreur est survenue'); + } + } catch (error) { + console.error('Erreur:', error); + alert('Une erreur est survenue'); + } finally { + setLoading(false); + } + }; + + return ( +
+
+
+
+

+ {contact ? 'Modifier le contact' : 'Nouveau contact'} +

+

+ {contact ? 'Modifiez les informations du contact ci-dessous.' : 'Remplissez les informations pour créer un nouveau contact professionnel.'} +

+
+ +
+ +
+
+
+ +
+
+ + + +
+ setFormData({ ...formData, nom: e.target.value })} + className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-lblue focus:border-transparent" + /> +
+
+ +
+ +
+
+ + + +
+ setFormData({ ...formData, prenom: e.target.value })} + className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-lblue focus:border-transparent" + /> +
+
+ +
+ +
+
+ + + +
+ setFormData({ ...formData, telephone: e.target.value })} + className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-lblue focus:border-transparent" + /> +
+
+ +
+ +
+
+ + + +
+ setFormData({ ...formData, email: e.target.value })} + className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-lblue focus:border-transparent" + /> +
+
+ +
+ +
+
+ + + +
+ setFormData({ ...formData, nomEntreprise: e.target.value })} + className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-lblue focus:border-transparent" + /> +
+
+
+ +
+ +
+
+ + + + +
+ setFormData({ ...formData, adresse: e.target.value })} + className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-lblue focus:border-transparent" + /> +
+
+ +
+ + +
+
+
+
+ ); +} diff --git a/components/UniversProTable.tsx b/components/UniversProTable.tsx new file mode 100644 index 0000000..a7a38fa --- /dev/null +++ b/components/UniversProTable.tsx @@ -0,0 +1,363 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import UniversProForm from './UniversProForm'; + +interface UniversPro { + id: string; + nom: string; + prenom: string; + telephone: string; + email: string; + adresse: string; + nomEntreprise: string; +} + +export default function UniversProTable() { + const [contacts, setContacts] = useState([]); + const [search, setSearch] = useState(''); + const [loading, setLoading] = useState(true); + const [showForm, setShowForm] = useState(false); + const [editingContact, setEditingContact] = useState(null); + const [viewingContact, setViewingContact] = useState(null); + + const fetchContacts = async (searchTerm: string = '') => { + setLoading(true); + try { + const url = searchTerm + ? `/api/univers-pro?search=${encodeURIComponent(searchTerm)}` + : '/api/univers-pro'; + const response = await fetch(url); + if (response.ok) { + const data = await response.json(); + setContacts(data); + } + } catch (error) { + console.error('Erreur lors du chargement des contacts:', error); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + const timeoutId = setTimeout(() => { + fetchContacts(search); + }, 300); + + return () => clearTimeout(timeoutId); + }, [search]); + + useEffect(() => { + fetchContacts(); + }, []); + + const handleDelete = async (id: string) => { + if (!confirm('Êtes-vous sûr de vouloir supprimer ce contact ?')) { + return; + } + + try { + const response = await fetch(`/api/univers-pro/${id}`, { + method: 'DELETE', + }); + + if (response.ok) { + fetchContacts(search); + } else { + alert('Erreur lors de la suppression'); + } + } catch (error) { + console.error('Erreur lors de la suppression:', error); + alert('Erreur lors de la suppression'); + } + }; + + const handleEdit = (contact: UniversPro) => { + setEditingContact(contact); + setShowForm(true); + }; + + const handleView = async (id: string) => { + try { + const response = await fetch(`/api/univers-pro/${id}`); + if (response.ok) { + const data = await response.json(); + setViewingContact(data); + } + } catch (error) { + console.error('Erreur lors de la récupération:', error); + } + }; + + const handleFormClose = () => { + setShowForm(false); + setEditingContact(null); + fetchContacts(search); + }; + + const getInitials = (nom: string, prenom: string) => { + return `${prenom.charAt(0)}${nom.charAt(0)}`.toUpperCase(); + }; + + return ( + <> + {/* Barre de recherche et actions */} +
+
+ {/* Barre de recherche */} +
+
+
+ + + +
+ setSearch(e.target.value)} + className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-lblue focus:border-transparent" + /> +
+
+ + {/* Boutons d'action */} +
+ + + +
+
+
+ + {/* Tableau */} +
+ {loading ? ( +
Chargement...
+ ) : contacts.length === 0 ? ( +
Aucun contact trouvé
+ ) : ( +
+ + + + + + + + + + + + {contacts.map((contact) => ( + + + + + + + + ))} + +
NOMCONTACTADRESSEENTREPRISEACTIONS
+
+
+ {getInitials(contact.nom, contact.prenom)} +
+
+
+ {contact.prenom} {contact.nom} +
+
+
+
+
{contact.telephone}
+
{contact.email}
+
+
{contact.adresse}
+
+
{contact.nomEntreprise}
+
+
+ + + +
+
+
+ )} +
+ + {/* Modal formulaire */} + {showForm && ( + + )} + + {/* Modal vue détaillée */} + {viewingContact && ( +
+
+ {/* Header */} +
+
+
+
+ {getInitials(viewingContact.nom, viewingContact.prenom)} +
+
+

+ {viewingContact.prenom} {viewingContact.nom} +

+

+ Informations détaillées du contact +

+
+
+ +
+
+ + {/* Contenu scrollable */} +
+
+
+
+ Téléphone +
+ +
+ +
+
+ Email +
+ +
+ +
+
+ Adresse +
+
+ + + + + {viewingContact.adresse} +
+
+ +
+
+ Entreprise +
+
+ + + + {viewingContact.nomEntreprise} +
+
+
+
+ + {/* Footer */} +
+
+ + +
+
+
+
+ )} + + ); +} diff --git a/prisma/dev.db b/prisma/dev.db index 755df23..341367b 100644 Binary files a/prisma/dev.db and b/prisma/dev.db differ diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f8e1656..010e2ca 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -18,3 +18,32 @@ model User { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } + +model Chauffeur { + id String @id @default(cuid()) + nom String + prenom String + dateNaissance DateTime + telephone String + email String + adresse String + heuresContrat Int @default(35) // Nombre d'heures dans le contrat (ex: 35h) + dateDebutContrat DateTime // Date de début du contrat + dateFinContrat DateTime? // Date de fin du contrat (modifiable à tout moment, peut être null) + heuresRestantes Int @default(35) // Heures restantes (calculé/géré séparément) + status String @default("Disponible") // Disponible, Vacances, Arrêt Maladie + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model UniversPro { + id String @id @default(cuid()) + nom String + prenom String + telephone String + email String + adresse String // Adresse de résidence + nomEntreprise String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +}