Files
OpticZ/src/components/atelier/AtelierModule.tsx
2026-05-30 14:33:11 +01:00

966 lines
37 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Separator } from '@/components/ui/separator'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import {
Wrench,
Clock,
Play,
CheckCircle2,
Bell,
User,
Package,
Eye,
Calendar,
FileText,
AlertCircle,
CheckCheck
} from 'lucide-react'
import { toast } from '@/hooks/use-toast'
// Types
interface Produit {
id: string
reference: string
designation: string
categorie: string
marque?: string
typeMonture?: string
typeVerre?: string
materiau?: string
couleur?: string
}
interface Client {
id: string
nom: string
prenom: string
email?: string
telephone: string
}
interface Patient {
id: string
odSphere?: number
odCylindre?: number
odAxe?: number
ogSphere?: number
ogCylindre?: number
ogAxe?: number
addition?: number
pd?: number
hauteur?: number
}
interface LigneVente {
id: string
produit: Produit
quantite: number
montantHT: number
montantTTC: number
}
interface WorkOrder {
id: string
numero: string
date: string
statutAtelier: 'EN_ATTENTE' | 'EN_COURS' | 'TERMINE' | 'PRET' | 'RETIRE'
montantTTC: number
client?: Client
lignes: LigneVente[]
patients?: Patient[]
dateAtelier?: string
dateRetrait?: string
notes?: string
}
type StatusFilter = 'ALL' | 'EN_ATTENTE' | 'EN_COURS' | 'TERMINE' | 'PRET' | 'RETIRE'
export default function AtelierModule() {
const [workOrders, setWorkOrders] = useState<WorkOrder[]>([])
const [selectedOrder, setSelectedOrder] = useState<WorkOrder | null>(null)
const [statusFilter, setStatusFilter] = useState<StatusFilter>('ALL')
const [loading, setLoading] = useState(false)
const [showDetailDialog, setShowDetailDialog] = useState(false)
const [showNotifyDialog, setShowNotifyDialog] = useState(false)
const [activeTab, setActiveTab] = useState<'orders' | 'ready' | 'history'>('orders')
// Load work orders
const loadWorkOrders = async () => {
setLoading(true)
try {
const response = await fetch('/api/atelier/orders?XTransformPort=3000')
if (response.ok) {
const data = await response.json()
setWorkOrders(data)
} else {
toast({
title: 'Erreur',
description: 'Impossible de charger les commandes atelier',
variant: 'destructive'
})
}
} catch (error) {
console.error('Error loading work orders:', error)
toast({
title: 'Erreur',
description: 'Impossible de charger les commandes atelier',
variant: 'destructive'
})
} finally {
setLoading(false)
}
}
// Update order status
const updateOrderStatus = async (orderId: string, newStatus: string) => {
console.log('Frontend: updateOrderStatus called')
console.log('Frontend: orderId =', orderId)
console.log('Frontend: newStatus =', newStatus)
try {
const response = await fetch(`/api/atelier/orders/${orderId}?XTransformPort=3000`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ statutAtelier: newStatus })
})
console.log('Frontend: response.ok =', response.ok)
console.log('Frontend: response.status =', response.status)
if (response.ok) {
toast({
title: 'Statut mis à jour',
description: 'Le statut de la commande a été mis à jour'
})
loadWorkOrders()
if (selectedOrder?.id === orderId) {
setSelectedOrder({ ...selectedOrder, statutAtelier: newStatus as any })
}
} else {
const errorData = await response.json()
console.log('Frontend: errorData =', errorData)
toast({
title: 'Erreur',
description: errorData.error || 'Impossible de mettre à jour le statut',
variant: 'destructive'
})
}
} catch (error) {
console.error('Error updating order status:', error)
toast({
title: 'Erreur',
description: 'Impossible de mettre à jour le statut',
variant: 'destructive'
})
}
}
// View order details
const viewOrderDetails = async (order: WorkOrder) => {
setSelectedOrder(order)
setShowDetailDialog(true)
}
// Mark as ready for pickup
const markAsReady = async () => {
if (!selectedOrder) return
try {
const response = await fetch(`/api/atelier/orders/${selectedOrder.id}?XTransformPort=3000`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ statutAtelier: 'PRET' })
})
if (response.ok) {
toast({
title: 'Commande prête',
description: 'La commande est marquée comme prête pour le retrait'
})
loadWorkOrders()
setShowNotifyDialog(false)
setShowDetailDialog(false)
} else {
toast({
title: 'Erreur',
description: 'Impossible de marquer la commande comme prête',
variant: 'destructive'
})
}
} catch (error) {
console.error('Error marking order as ready:', error)
toast({
title: 'Erreur',
description: 'Impossible de marquer la commande comme prête',
variant: 'destructive'
})
}
}
// Confirm order pickup (ready → retrieved)
const confirmRetrait = async (orderId: string) => {
if (!confirm('Confirmer que le client a récupéré ses lunettes ?')) {
return
}
try {
const response = await fetch(`/api/atelier/orders/${orderId}?XTransformPort=3000`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ statutAtelier: 'RETIRE' })
})
if (response.ok) {
toast({
title: 'Retrait confirmé',
description: 'La commande a été marquée comme retirée'
})
loadWorkOrders()
} else {
toast({
title: 'Erreur',
description: 'Impossible de confirmer le retrait',
variant: 'destructive'
})
}
} catch (error) {
console.error('Error confirming pickup:', error)
toast({
title: 'Erreur',
description: 'Impossible de confirmer le retrait',
variant: 'destructive'
})
}
}
// Seed sample data
const seedSampleData = async () => {
try {
const response = await fetch('/api/atelier/seed?XTransformPort=3000', {
method: 'POST'
})
if (response.ok) {
toast({
title: 'Données ajoutées',
description: 'Les données de test ont été créées avec succès'
})
loadWorkOrders()
} else {
const error = await response.json()
toast({
title: 'Erreur',
description: error.error || 'Impossible de créer les données de test',
variant: 'destructive'
})
}
} catch (error) {
console.error('Error seeding data:', error)
toast({
title: 'Erreur',
description: 'Impossible de créer les données de test',
variant: 'destructive'
})
}
}
useEffect(() => {
loadWorkOrders()
}, [])
// Filter orders
const filteredOrders = workOrders.filter(order => {
if (statusFilter === 'ALL') return true
return order.statutAtelier === statusFilter
})
const readyOrders = workOrders.filter(order => order.statutAtelier === 'PRET')
// Get status badge
const getStatusBadge = (statut: string) => {
const variants: Record<string, { color: string; label: string; icon: React.ReactNode }> = {
'EN_ATTENTE': {
color: 'bg-gray-500',
label: 'En attente',
icon: <Clock className="h-3 w-3 mr-1" />
},
'EN_COURS': {
color: 'bg-blue-500',
label: 'En cours',
icon: <Play className="h-3 w-3 mr-1" />
},
'TERMINE': {
color: 'bg-purple-500',
label: 'Terminé',
icon: <CheckCircle2 className="h-3 w-3 mr-1" />
},
'PRET': {
color: 'bg-green-500',
label: 'Prêt',
icon: <Bell className="h-3 w-3 mr-1" />
},
'RETIRE': {
color: 'bg-orange-500',
label: 'Retiré',
icon: <CheckCheck className="h-3 w-3 mr-1" />
}
}
const status = variants[statut] || variants['EN_ATTENTE']
return (
<Badge className={status.color}>
{status.icon}
{status.label}
</Badge>
)
}
// Format vision data
const formatVisionData = (patient?: Patient) => {
if (!patient) return null
return {
od: `Sph: ${patient.odSphere || '-'} | Cyl: ${patient.odCylindre || '-'} | Axe: ${patient.odAxe || '-'}`,
og: `Sph: ${patient.ogSphere || '-'} | Cyl: ${patient.ogCylindre || '-'} | Axe: ${patient.ogAxe || '-'}`,
addition: patient.addition ? `Add: ${patient.addition}` : null,
pd: patient.pd ? `PD: ${patient.pd}mm` : null,
hauteur: patient.hauteur ? `Haut: ${patient.hauteur}mm` : null
}
}
return (
<div className="space-y-6">
{/* Statistics Cards */}
<div className="grid grid-cols-1 md:grid-cols-5 gap-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">En attente</CardTitle>
<Clock className="h-4 w-4 text-gray-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{workOrders.filter(o => o.statutAtelier === 'EN_ATTENTE').length}
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">En cours</CardTitle>
<Play className="h-4 w-4 text-blue-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{workOrders.filter(o => o.statutAtelier === 'EN_COURS').length}
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">Terminé</CardTitle>
<CheckCircle2 className="h-4 w-4 text-purple-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{workOrders.filter(o => o.statutAtelier === 'TERMINE').length}
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">Prêt à retirer</CardTitle>
<Bell className="h-4 w-4 text-green-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-green-600">
{workOrders.filter(o => o.statutAtelier === 'PRET').length}
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">Retiré</CardTitle>
<CheckCheck className="h-4 w-4 text-orange-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-orange-600">
{workOrders.filter(o => o.statutAtelier === 'RETIRE').length}
</div>
</CardContent>
</Card>
</div>
{/* Main Content */}
<Tabs value={activeTab} onValueChange={(v) => setActiveTab(v as 'orders' | 'ready')}>
<TabsList className="grid w-full max-w-lg grid-cols-3">
<TabsTrigger value="orders">Commandes</TabsTrigger>
<TabsTrigger value="ready">
Prêtes
{readyOrders.length > 0 && (
<Badge className="ml-2 bg-green-500">{readyOrders.length}</Badge>
)}
</TabsTrigger>
<TabsTrigger value="history">
Historique
{workOrders.filter(o => o.statutAtelier === 'RETIRE').length > 0 && (
<Badge className="ml-2 bg-orange-500">{workOrders.filter(o => o.statutAtelier === 'RETIRE').length}</Badge>
)}
</TabsTrigger>
</TabsList>
<TabsContent value="orders" className="space-y-4">
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle className="flex items-center gap-2">
<Wrench className="h-5 w-5" />
Commandes de Montage
</CardTitle>
<CardDescription>
Gérez les commandes de montage de lunettes
</CardDescription>
</div>
<div className="flex gap-2">
<Button
variant={statusFilter === 'ALL' ? 'default' : 'outline'}
size="sm"
onClick={() => setStatusFilter('ALL')}
>
Toutes
</Button>
<Button
variant={statusFilter === 'EN_ATTENTE' ? 'default' : 'outline'}
size="sm"
onClick={() => setStatusFilter('EN_ATTENTE')}
>
En attente
</Button>
<Button
variant={statusFilter === 'EN_COURS' ? 'default' : 'outline'}
size="sm"
onClick={() => setStatusFilter('EN_COURS')}
>
En cours
</Button>
<Button
variant={statusFilter === 'TERMINE' ? 'default' : 'outline'}
size="sm"
onClick={() => setStatusFilter('TERMINE')}
>
Terminé
</Button>
<Button
variant={statusFilter === 'RETIRE' ? 'default' : 'outline'}
size="sm"
onClick={() => setStatusFilter('RETIRE')}
>
Retiré
</Button>
</div>
</div>
</CardHeader>
<CardContent>
{loading ? (
<div className="text-center py-8 text-gray-500">
Chargement...
</div>
) : filteredOrders.length === 0 ? (
<div className="text-center py-8 space-y-4">
<AlertCircle className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<p className="text-gray-500">
{statusFilter === 'ALL'
? 'Aucune commande de montage en cours'
: statusFilter === 'RETIRE'
? 'Aucune commande retirée'
: `Aucune commande avec le statut "${statusFilter === 'EN_ATTENTE' ? 'En attente' : statusFilter === 'EN_COURS' ? 'En cours' : statusFilter === 'TERMINE' ? 'Terminé' : 'Prêt'}"`
}
</p>
{statusFilter === 'ALL' && workOrders.length === 0 && (
<Button onClick={seedSampleData} variant="outline">
<Wrench className="h-4 w-4 mr-2" />
Ajouter des données de test
</Button>
)}
</div>
) : (
<ScrollArea className="max-h-[600px]">
<Table>
<TableHeader>
<TableRow>
<TableHead>N° Commande</TableHead>
<TableHead>Date</TableHead>
<TableHead>Client</TableHead>
<TableHead>Produits</TableHead>
<TableHead>Statut</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredOrders.map((order) => (
<TableRow key={order.id}>
<TableCell className="font-medium">{order.numero}</TableCell>
<TableCell>
{new Date(order.date).toLocaleDateString('fr-FR')}
</TableCell>
<TableCell>
{order.client
? `${order.client.prenom} ${order.client.nom}`
: 'Client anonyme'
}
</TableCell>
<TableCell>
<div className="flex flex-col gap-1">
{order.lignes.slice(0, 2).map((ligne) => (
<span key={ligne.id} className="text-xs">
{ligne.produit.designation}
</span>
))}
{order.lignes.length > 2 && (
<span className="text-xs text-gray-500">
+{order.lignes.length - 2} autre(s)
</span>
)}
</div>
</TableCell>
<TableCell>{getStatusBadge(order.statutAtelier)}</TableCell>
<TableCell className="text-right">
<Button
variant="outline"
size="sm"
onClick={() => viewOrderDetails(order)}
>
Voir détails
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</ScrollArea>
)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="ready">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Bell className="h-5 w-5 text-green-500" />
Commandes prêtes pour retrait
</CardTitle>
<CardDescription>
Les commandes terminées et prêtes à être remises aux clients
</CardDescription>
</CardHeader>
<CardContent>
{readyOrders.length === 0 ? (
<div className="text-center py-8">
<CheckCircle2 className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<p className="text-gray-500">
Aucune commande prête pour le retrait
</p>
</div>
) : (
<ScrollArea className="max-h-[600px]">
<Table>
<TableHeader>
<TableRow>
<TableHead>N° Commande</TableHead>
<TableHead>Date</TableHead>
<TableHead>Client</TableHead>
<TableHead>Téléphone</TableHead>
<TableHead>Prêt depuis</TableHead>
<TableHead className="text-right">Montant TTC</TableHead>
<TableHead className="text-center">Action</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{readyOrders.map((order) => (
<TableRow key={order.id}>
<TableCell className="font-medium">{order.numero}</TableCell>
<TableCell>
{new Date(order.date).toLocaleDateString('fr-FR')}
</TableCell>
<TableCell>
{order.client
? `${order.client.prenom} ${order.client.nom}`
: 'Client anonyme'
}
</TableCell>
<TableCell>
{order.client?.telephone || '-'}
</TableCell>
<TableCell>
{order.dateAtelier
? new Date(order.dateAtelier).toLocaleDateString('fr-FR')
: new Date(order.date).toLocaleDateString('fr-FR')
}
</TableCell>
<TableCell className="text-right font-bold">
{order.montantTTC.toFixed(2)}
</TableCell>
<TableCell className="text-center">
<Button
variant="outline"
size="sm"
onClick={() => confirmRetrait(order.id)}
className="text-green-600 hover:text-green-700 hover:bg-green-50"
>
<CheckCheck className="h-4 w-4 mr-2" />
Confirmer retrait
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</ScrollArea>
)}
</CardContent>
</Card>
</TabsContent>
{/* History Tab */}
<TabsContent value="history" className="space-y-4">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<CheckCheck className="h-5 w-5 text-orange-500" />
Historique des retraits
</CardTitle>
<CardDescription>
Les commandes qui ont é retirées par les clients
</CardDescription>
</CardHeader>
<CardContent>
{workOrders.filter(o => o.statutAtelier === 'RETIRE').length === 0 ? (
<div className="text-center py-8">
<CheckCheck className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<p className="text-gray-500">
Aucune commande retirée
</p>
</div>
) : (
<ScrollArea className="max-h-[600px]">
<Table>
<TableHeader>
<TableRow>
<TableHead>N° Commande</TableHead>
<TableHead>Date de vente</TableHead>
<TableHead>Client</TableHead>
<TableHead>Téléphone</TableHead>
<TableHead>Prêt le</TableHead>
<TableHead>Retiré le</TableHead>
<TableHead className="text-right">Montant TTC</TableHead>
<TableHead className="text-center">Action</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{workOrders.filter(o => o.statutAtelier === 'RETIRE').map((order) => (
<TableRow key={order.id}>
<TableCell className="font-medium">{order.numero}</TableCell>
<TableCell>
{new Date(order.date).toLocaleDateString('fr-FR')}
</TableCell>
<TableCell>
{order.client
? `${order.client.prenom} ${order.client.nom}`
: 'Client anonyme'
}
</TableCell>
<TableCell>
{order.client?.telephone || '-'}
</TableCell>
<TableCell>
{order.dateAtelier
? new Date(order.dateAtelier).toLocaleDateString('fr-FR')
: '-'
}
</TableCell>
<TableCell className="text-green-600 font-medium">
{order.dateRetrait
? new Date(order.dateRetrait).toLocaleDateString('fr-FR')
: '-'
}
</TableCell>
<TableCell className="text-right font-bold">
{order.montantTTC.toFixed(2)}
</TableCell>
<TableCell className="text-center">
<Button
variant="outline"
size="sm"
onClick={() => viewOrderDetails(order)}
>
Voir détails
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</ScrollArea>
)}
</CardContent>
</Card>
</TabsContent>
</Tabs>
{/* Order Detail Dialog */}
<Dialog open={showDetailDialog} onOpenChange={setShowDetailDialog}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Package className="h-5 w-5" />
Détails de la commande {selectedOrder?.numero}
</DialogTitle>
<DialogDescription>
Gérez le montage et le statut de cette commande
</DialogDescription>
</DialogHeader>
{selectedOrder && (
<div className="space-y-6">
{/* Status and Actions */}
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-500">Statut actuel</p>
{getStatusBadge(selectedOrder.statutAtelier)}
</div>
<div className="flex gap-2">
{selectedOrder.statutAtelier === 'EN_ATTENTE' && (
<Button
onClick={() => updateOrderStatus(selectedOrder.id, 'EN_COURS')}
className="bg-blue-500 hover:bg-blue-600"
>
<Play className="h-4 w-4 mr-2" />
Commencer le montage
</Button>
)}
{selectedOrder.statutAtelier === 'EN_COURS' && (
<Button
onClick={() => updateOrderStatus(selectedOrder.id, 'TERMINE')}
className="bg-purple-500 hover:bg-purple-600"
>
<CheckCircle2 className="h-4 w-4 mr-2" />
Marquer comme terminé
</Button>
)}
{selectedOrder.statutAtelier === 'TERMINE' && (
<Button
onClick={() => {
setSelectedOrder(selectedOrder)
setShowNotifyDialog(true)
}}
className="bg-green-500 hover:bg-green-600"
>
<Bell className="h-4 w-4 mr-2" />
Marquer comme prêt
</Button>
)}
</div>
</div>
<Separator />
{/* Client Information */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<User className="h-4 w-4 text-gray-500" />
<h3 className="font-semibold">Client</h3>
</div>
{selectedOrder.client ? (
<div className="bg-gray-50 p-4 rounded-lg">
<p className="font-medium">{selectedOrder.client.prenom} {selectedOrder.client.nom}</p>
<p className="text-sm text-gray-600">{selectedOrder.client.telephone}</p>
{selectedOrder.client.email && (
<p className="text-sm text-gray-600">{selectedOrder.client.email}</p>
)}
</div>
) : (
<p className="text-sm text-gray-500">Client anonyme</p>
)}
</div>
{/* Vision Measurements */}
{selectedOrder.patients && selectedOrder.patients.length > 0 && (
<div className="space-y-2">
<div className="flex items-center gap-2">
<Eye className="h-4 w-4 text-gray-500" />
<h3 className="font-semibold">Mesures de vision</h3>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{selectedOrder.patients.map((patient) => {
const vision = formatVisionData(patient)
if (!vision) return null
return (
<Card key={patient.id}>
<CardContent className="pt-4 space-y-2">
<div className="text-sm">
<p className="font-medium text-blue-600">Œil Droit (OD)</p>
<p className="text-xs text-gray-600">{vision.od}</p>
</div>
<div className="text-sm">
<p className="font-medium text-green-600">Œil Gauche (OG)</p>
<p className="text-xs text-gray-600">{vision.og}</p>
</div>
{(vision.addition || vision.pd || vision.hauteur) && (
<div className="text-xs text-gray-600">
{[vision.addition, vision.pd, vision.hauteur].filter(Boolean).join(' | ')}
</div>
)}
</CardContent>
</Card>
)
})}
</div>
</div>
)}
{/* Products */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<Package className="h-4 w-4 text-gray-500" />
<h3 className="font-semibold">Produits à monter</h3>
</div>
<div className="border rounded-lg">
<Table>
<TableHeader>
<TableRow>
<TableHead>Référence</TableHead>
<TableHead>Désignation</TableHead>
<TableHead>Catégorie</TableHead>
<TableHead className="text-right">Quantité</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{selectedOrder.lignes.map((ligne) => (
<TableRow key={ligne.id}>
<TableCell className="font-medium">{ligne.produit.reference}</TableCell>
<TableCell>{ligne.produit.designation}</TableCell>
<TableCell>
<Badge variant="outline">{ligne.produit.categorie}</Badge>
</TableCell>
<TableCell className="text-right">{ligne.quantite}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
{/* Dates */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<Calendar className="h-4 w-4 text-gray-500" />
<h3 className="font-semibold">Dates</h3>
</div>
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<p className="text-gray-500">Date de vente:</p>
<p className="font-medium">
{new Date(selectedOrder.date).toLocaleDateString('fr-FR')} à{' '}
{new Date(selectedOrder.date).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })}
</p>
</div>
<div>
<p className="text-gray-500">Dernière mise à jour atelier:</p>
<p className="font-medium">
{selectedOrder.dateAtelier
? `${new Date(selectedOrder.dateAtelier).toLocaleDateString('fr-FR')} à ${new Date(selectedOrder.dateAtelier).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })}`
: '-'
}
</p>
</div>
</div>
{selectedOrder.statutAtelier === 'RETIRE' && selectedOrder.dateRetrait && (
<div className="mt-2 pt-2 border-t">
<div className="text-sm">
<p className="text-gray-500">Date de retrait:</p>
<p className="font-medium text-orange-600">
{new Date(selectedOrder.dateRetrait).toLocaleDateString('fr-FR')} à{' '}
{new Date(selectedOrder.dateRetrait).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })}
</p>
</div>
</div>
)}
</div>
{selectedOrder.notes && (
<>
<Separator />
<div className="space-y-2">
<div className="flex items-center gap-2">
<FileText className="h-4 w-4 text-gray-500" />
<h3 className="font-semibold">Notes</h3>
</div>
<p className="text-sm text-gray-600 bg-gray-50 p-3 rounded-lg">
{selectedOrder.notes}
</p>
</div>
</>
)}
<Separator />
<div className="flex justify-between items-center">
<div className="text-lg font-bold">
Total TTC: {selectedOrder.montantTTC.toFixed(2)}
</div>
<Button variant="outline" onClick={() => setShowDetailDialog(false)}>
Fermer
</Button>
</div>
</div>
)}
</DialogContent>
</Dialog>
{/* Notify Client Dialog */}
<Dialog open={showNotifyDialog} onOpenChange={setShowNotifyDialog}>
<DialogContent>
<DialogHeader>
<DialogTitle className="flex items-center gap-2 text-green-600">
<Bell className="h-5 w-5" />
Notifier le client
</DialogTitle>
<DialogDescription>
Confirmer que cette commande est prête pour le retrait
</DialogDescription>
</DialogHeader>
{selectedOrder && (
<div className="space-y-4">
<div className="bg-green-50 border border-green-200 rounded-lg p-4">
<p className="font-medium mb-2">La commande {selectedOrder.numero} est prête!</p>
<p className="text-sm text-gray-600">
{selectedOrder.client
? `Le client ${selectedOrder.client.prenom} ${selectedOrder.client.nom} pourra être notifié que ses lunettes sont prêtes.`
: 'La commande est marquée comme prête pour retrait.'
}
</p>
</div>
<div className="flex gap-2 justify-end">
<Button variant="outline" onClick={() => setShowNotifyDialog(false)}>
Annuler
</Button>
<Button onClick={markAsReady} className="bg-green-500 hover:bg-green-600">
<CheckCircle2 className="h-4 w-4 mr-2" />
Confirmer et marquer comme prêt
</Button>
</div>
</div>
)}
</DialogContent>
</Dialog>
</div>
)
}