Initial commit

This commit is contained in:
2026-05-30 14:33:11 +01:00
commit a8c372177f
156 changed files with 38163 additions and 0 deletions

View File

@@ -0,0 +1,996 @@
'use client'
import { useState, useEffect } from 'react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
import { Badge } from '@/components/ui/badge'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Separator } from '@/components/ui/separator'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import {
ShoppingCart,
Search,
Plus,
Minus,
Trash2,
User,
CreditCard,
DollarSign,
FileText,
X,
CheckCircle,
AlertCircle
} from 'lucide-react'
import { toast } from '@/hooks/use-toast'
// Types
interface Produit {
id: string
reference: string
designation: string
categorie: string
prixVenteTTC: number
tva: number
stock: number
marque?: string
typeMonture?: string
}
interface Client {
id: string
nom: string
prenom: string
email?: string
telephone: string
}
interface CartItem {
produit: Produit
quantite: number
prixUnitaireHT: number
prixUnitaireTTC: number
remise: number
montantHT: number
montantTTC: number
}
interface Paiement {
mode: 'ESPECES' | 'CARTE' | 'CHEQUE' | 'VIREMENT' | 'BON_CAISSE'
montant: number
reference?: string
}
interface Vente {
id: string
numero: string
date: string
statut: 'INITIEE' | 'PAYEE' | 'ANNULEE' | 'REMBOURSEE'
montantHT: number
montantTVA: number
montantTTC: number
remise: number
client?: Client
}
// Helper functions
const calculateHTFromTTC = (ttc: number, tvaRate: number) => {
return ttc / (1 + tvaRate / 100)
}
const calculateTTCFromHT = (ht: number, tvaRate: number) => {
return ht * (1 + tvaRate / 100)
}
export default function POSModule() {
// State
const [products, setProducts] = useState<Produit[]>([])
const [filteredProducts, setFilteredProducts] = useState<Produit[]>([])
const [clients, setClients] = useState<Client[]>([])
const [cart, setCart] = useState<CartItem[]>([])
const [selectedClient, setSelectedClient] = useState<Client | null>(null)
const [searchQuery, setSearchQuery] = useState('')
const [globalDiscount, setGlobalDiscount] = useState(0)
const [payments, setPayments] = useState<Paiement[]>([])
const [currentPayment, setCurrentPayment] = useState<Paiement>({
mode: 'ESPECES',
montant: 0
})
const [showCustomerDialog, setShowCustomerDialog] = useState(false)
const [showPaymentDialog, setShowPaymentDialog] = useState(false)
const [showInvoiceDialog, setShowInvoiceDialog] = useState(false)
const [currentSale, setCurrentSale] = useState<Vente | null>(null)
const [newClient, setNewClient] = useState({
nom: '',
prenom: '',
email: '',
telephone: ''
})
const [activeTab, setActiveTab] = useState<'pos' | 'history'>('pos')
const [salesHistory, setSalesHistory] = useState<Vente[]>([])
// Calculations
const cartTotals = cart.reduce((acc, item) => ({
ht: acc.ht + item.montantHT,
ttc: acc.ttc + item.montantTTC
}), { ht: 0, ttc: 0 })
const totalTVA = cartTotals.ttc - cartTotals.ht
const globalDiscountAmount = (cartTotals.ttc * globalDiscount) / 100
const finalTTC = cartTotals.ttc - globalDiscountAmount
const totalPaid = payments.reduce((sum, p) => sum + p.montant, 0)
const remainingToPay = finalTTC - totalPaid
// Load functions
const loadProducts = async () => {
try {
const response = await fetch('/api/pos/products')
if (response.ok) {
const data = await response.json()
setProducts(data)
setFilteredProducts(data)
}
} catch (error) {
console.error('Error loading products:', error)
toast({
title: 'Erreur',
description: 'Impossible de charger les produits',
variant: 'destructive'
})
}
}
const loadClients = async () => {
try {
const response = await fetch('/api/pos/clients')
if (response.ok) {
const data = await response.json()
setClients(data)
}
} catch (error) {
console.error('Error loading clients:', error)
}
}
const loadSalesHistory = async () => {
try {
const response = await fetch('/api/pos/sales')
if (response.ok) {
const data = await response.json()
setSalesHistory(data)
}
} catch (error) {
console.error('Error loading sales history:', error)
}
}
// Seed sample data
const seedSampleData = async () => {
try {
const response = await fetch('/api/pos/seed', {
method: 'POST'
})
if (response.ok) {
toast({
title: 'Données ajoutées',
description: 'Les données de test ont été créées avec succès'
})
loadProducts()
loadClients()
}
} catch (error) {
console.error('Error seeding data:', error)
toast({
title: 'Erreur',
description: 'Impossible de créer les données de test',
variant: 'destructive'
})
}
}
// Load products on mount
useEffect(() => {
loadProducts()
loadClients()
loadSalesHistory()
}, [])
// Search products
useEffect(() => {
if (!searchQuery.trim()) {
setFilteredProducts(products)
return
}
const query = searchQuery.toLowerCase()
const filtered = products.filter(p =>
p.reference.toLowerCase().includes(query) ||
p.designation.toLowerCase().includes(query) ||
p.marque?.toLowerCase().includes(query) ||
p.categorie.toLowerCase().includes(query)
)
setFilteredProducts(filtered)
}, [searchQuery, products])
// Add to cart
const addToCart = (product: Produit) => {
if (product.stock <= 0) {
toast({
title: 'Stock épuisé',
description: 'Ce produit n\'est plus en stock',
variant: 'destructive'
})
return
}
setCart(prevCart => {
const existingItem = prevCart.find(item => item.produit.id === product.id)
const maxQty = existingItem ? existingItem.quantite + 1 : 1
if (maxQty > product.stock) {
toast({
title: 'Stock insuffisant',
description: `Seulement ${product.stock} unités disponibles`,
variant: 'destructive'
})
return prevCart
}
if (existingItem) {
const updatedItem = {
...existingItem,
quantite: maxQty,
montantHT: existingItem.prixUnitaireHT * maxQty,
montantTTC: existingItem.prixUnitaireTTC * maxQty
}
return prevCart.map(item =>
item.produit.id === product.id ? updatedItem : item
)
}
const prixHT = calculateHTFromTTC(product.prixVenteTTC, product.tva)
const newItem: CartItem = {
produit: product,
quantite: 1,
prixUnitaireHT: prixHT,
prixUnitaireTTC: product.prixVenteTTC,
remise: 0,
montantHT: prixHT,
montantTTC: product.prixVenteTTC
}
return [...prevCart, newItem]
})
}
// Update cart item quantity
const updateQuantity = (productId: string, newQuantity: number) => {
if (newQuantity < 1) return
const item = cart.find(i => i.produit.id === productId)
if (!item) return
if (newQuantity > item.produit.stock) {
toast({
title: 'Stock insuffisant',
description: `Seulement ${item.produit.stock} unités disponibles`,
variant: 'destructive'
})
return
}
setCart(prevCart =>
prevCart.map(item => {
if (item.produit.id === productId) {
return {
...item,
quantite: newQuantity,
montantHT: item.prixUnitaireHT * newQuantity,
montantTTC: item.prixUnitaireTTC * newQuantity
}
}
return item
})
)
}
// Remove from cart
const removeFromCart = (productId: string) => {
setCart(prevCart => prevCart.filter(item => item.produit.id !== productId))
}
// Clear cart
const clearCart = () => {
setCart([])
setSelectedClient(null)
setGlobalDiscount(0)
setPayments([])
setCurrentPayment({ mode: 'ESPECES', montant: 0 })
}
// Create new client
const handleCreateClient = async () => {
if (!newClient.nom || !newClient.prenom || !newClient.telephone) {
toast({
title: 'Champs requis',
description: 'Veuillez remplir tous les champs obligatoires',
variant: 'destructive'
})
return
}
try {
const response = await fetch('/api/pos/clients', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newClient)
})
if (response.ok) {
const createdClient = await response.json()
setSelectedClient(createdClient)
setClients([...clients, createdClient])
setNewClient({ nom: '', prenom: '', email: '', telephone: '' })
setShowCustomerDialog(false)
toast({
title: 'Client créé',
description: 'Le client a été créé avec succès'
})
}
} catch (error) {
console.error('Error creating client:', error)
toast({
title: 'Erreur',
description: 'Impossible de créer le client',
variant: 'destructive'
})
}
}
// Add payment
const addPayment = () => {
if (currentPayment.montant <= 0) {
toast({
title: 'Montant invalide',
description: 'Veuillez entrer un montant valide',
variant: 'destructive'
})
return
}
if (totalPaid + currentPayment.montant > finalTTC) {
toast({
title: 'Montant excessif',
description: 'Le paiement dépasse le montant dû',
variant: 'destructive'
})
return
}
setPayments([...payments, currentPayment])
setCurrentPayment({ mode: 'ESPECES', montant: 0 })
}
// Remove payment
const removePayment = (index: number) => {
setPayments(payments.filter((_, i) => i !== index))
}
// Complete sale
const completeSale = async () => {
if (cart.length === 0) {
toast({
title: 'Panier vide',
description: 'Veuillez ajouter des produits au panier',
variant: 'destructive'
})
return
}
if (remainingToPay > 0.01) {
toast({
title: 'Paiement incomplet',
description: `Il reste ${remainingToPay.toFixed(2)} € à payer`,
variant: 'destructive'
})
return
}
try {
const response = await fetch('/api/pos/sales', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
clientId: selectedClient?.id || null,
lignes: cart.map(item => ({
produitId: item.produit.id,
quantite: item.quantite,
prixUnitaireHT: item.prixUnitaireHT,
prixUnitaireTTC: item.prixUnitaireTTC,
remise: item.remise,
montantHT: item.montantHT,
montantTTC: item.montantTTC
})),
paiements: payments,
remise: globalDiscount,
montantHT: cartTotals.ht - (globalDiscountAmount / (1 + 0.2)),
montantTVA: totalTVA,
montantTTC: finalTTC,
notes: ''
})
})
if (response.ok) {
const sale = await response.json()
setCurrentSale(sale)
setShowInvoiceDialog(true)
loadSalesHistory()
loadProducts()
clearCart()
toast({
title: 'Vente enregistrée',
description: `Vente ${sale.numero} complétée avec succès`
})
}
} catch (error) {
console.error('Error completing sale:', error)
toast({
title: 'Erreur',
description: 'Impossible d\'enregistrer la vente',
variant: 'destructive'
})
}
}
// Get status badge
const getStatusBadge = (statut: string) => {
const variants: Record<string, { color: string; label: string }> = {
'INITIEE': { color: 'bg-yellow-500', label: 'Initiée' },
'PAYEE': { color: 'bg-green-500', label: 'Payée' },
'ANNULEE': { color: 'bg-red-500', label: 'Annulée' },
'REMBOURSEE': { color: 'bg-blue-500', label: 'Remboursée' }
}
const status = variants[statut] || variants['INITIEE']
return <Badge className={status.color}>{status.label}</Badge>
}
return (
<div className="space-y-6">
<Tabs value={activeTab} onValueChange={(v) => setActiveTab(v as 'pos' | 'history')}>
<TabsList className="grid w-full max-w-md grid-cols-2">
<TabsTrigger value="pos">Point de Vente</TabsTrigger>
<TabsTrigger value="history">Historique</TabsTrigger>
</TabsList>
<TabsContent value="pos" className="space-y-6">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Left column: Products */}
<div className="lg:col-span-1 space-y-4">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Search className="h-5 w-5" />
Recherche Produits
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<Input
placeholder="Référence, désignation, marque..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full"
/>
<ScrollArea className="h-[600px] pr-4">
<div className="space-y-2">
{filteredProducts.map(product => (
<Card
key={product.id}
className={`cursor-pointer transition-all hover:shadow-md hover:border-primary ${
product.stock <= 0 ? 'opacity-50' : ''
}`}
onClick={() => product.stock > 0 && addToCart(product)}
>
<CardContent className="p-4">
<div className="flex justify-between items-start">
<div className="flex-1">
<p className="text-sm font-medium">{product.designation}</p>
<p className="text-xs text-gray-500">{product.reference}</p>
{product.marque && (
<p className="text-xs text-gray-400">{product.marque}</p>
)}
<Badge variant="outline" className="mt-2 text-xs">
{product.categorie}
</Badge>
</div>
<div className="text-right">
<p className="font-bold text-primary">
{product.prixVenteTTC.toFixed(2)}
</p>
<p className="text-xs text-gray-500">
Stock: {product.stock}
</p>
</div>
</div>
</CardContent>
</Card>
))}
{filteredProducts.length === 0 && (
<div className="text-center py-8 space-y-4">
<p className="text-gray-500">
{products.length === 0
? 'Aucun produit dans la base de données'
: 'Aucun produit trouvé'}
</p>
{products.length === 0 && (
<Button onClick={seedSampleData} variant="outline">
<Plus className="h-4 w-4 mr-2" />
Ajouter des données de test
</Button>
)}
</div>
)}
</div>
</ScrollArea>
</CardContent>
</Card>
</div>
{/* Middle & Right columns: Cart and Checkout */}
<div className="lg:col-span-2 space-y-4">
{/* Customer Selection */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<User className="h-5 w-5" />
Client
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex gap-4 items-center">
<Select
value={selectedClient?.id || ''}
onValueChange={(value) => {
const client = clients.find(c => c.id === value)
setSelectedClient(client || null)
}}
>
<SelectTrigger className="flex-1">
<SelectValue placeholder="Sélectionner un client" />
</SelectTrigger>
<SelectContent>
{clients.map(client => (
<SelectItem key={client.id} value={client.id}>
{client.prenom} {client.nom} - {client.telephone}
</SelectItem>
))}
</SelectContent>
</Select>
<Dialog open={showCustomerDialog} onOpenChange={setShowCustomerDialog}>
<DialogTrigger asChild>
<Button variant="outline">
<Plus className="h-4 w-4 mr-2" />
Nouveau Client
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Créer un nouveau client</DialogTitle>
<DialogDescription>
Remplissez les informations du client
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<Label>Nom *</Label>
<Input
value={newClient.nom}
onChange={(e) => setNewClient({ ...newClient, nom: e.target.value })}
/>
</div>
<div>
<Label>Prénom *</Label>
<Input
value={newClient.prenom}
onChange={(e) => setNewClient({ ...newClient, prenom: e.target.value })}
/>
</div>
</div>
<div>
<Label>Téléphone *</Label>
<Input
value={newClient.telephone}
onChange={(e) => setNewClient({ ...newClient, telephone: e.target.value })}
/>
</div>
<div>
<Label>Email</Label>
<Input
type="email"
value={newClient.email}
onChange={(e) => setNewClient({ ...newClient, email: e.target.value })}
/>
</div>
<Button onClick={handleCreateClient} className="w-full">
Créer le client
</Button>
</div>
</DialogContent>
</Dialog>
{selectedClient && (
<Button variant="ghost" onClick={() => setSelectedClient(null)}>
<X className="h-4 w-4" />
</Button>
)}
</div>
{selectedClient && (
<div className="mt-2 text-sm text-gray-600">
<span className="font-medium">{selectedClient.prenom} {selectedClient.nom}</span>
{selectedClient.email && <span> - {selectedClient.email}</span>}
</div>
)}
</CardContent>
</Card>
{/* Shopping Cart */}
<Card>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<div className="flex items-center gap-2">
<ShoppingCart className="h-5 w-5" />
Panier ({cart.length} articles)
</div>
{cart.length > 0 && (
<Button variant="ghost" size="sm" onClick={clearCart}>
<Trash2 className="h-4 w-4" />
</Button>
)}
</CardTitle>
</CardHeader>
<CardContent>
{cart.length === 0 ? (
<div className="text-center py-8 text-gray-500">
Le panier est vide
</div>
) : (
<>
<div className="max-h-[300px] overflow-y-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>Produit</TableHead>
<TableHead className="text-center">Qté</TableHead>
<TableHead className="text-right">Prix Unit.</TableHead>
<TableHead className="text-right">Total TTC</TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{cart.map(item => (
<TableRow key={item.produit.id}>
<TableCell>
<div>
<p className="font-medium text-sm">{item.produit.designation}</p>
<p className="text-xs text-gray-500">{item.produit.reference}</p>
</div>
</TableCell>
<TableCell>
<div className="flex items-center justify-center gap-1">
<Button
variant="outline"
size="icon"
className="h-6 w-6"
onClick={() => updateQuantity(item.produit.id, item.quantite - 1)}
>
<Minus className="h-3 w-3" />
</Button>
<span className="w-8 text-center text-sm">{item.quantite}</span>
<Button
variant="outline"
size="icon"
className="h-6 w-6"
onClick={() => updateQuantity(item.produit.id, item.quantite + 1)}
>
<Plus className="h-3 w-3" />
</Button>
</div>
</TableCell>
<TableCell className="text-right text-sm">
{item.prixUnitaireTTC.toFixed(2)}
</TableCell>
<TableCell className="text-right font-medium">
{item.montantTTC.toFixed(2)}
</TableCell>
<TableCell>
<Button
variant="ghost"
size="icon"
onClick={() => removeFromCart(item.produit.id)}
>
<Trash2 className="h-4 w-4 text-red-500" />
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<Separator className="my-4" />
{/* Totals */}
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span>Total HT:</span>
<span>{cartTotals.ht.toFixed(2)} </span>
</div>
<div className="flex justify-between text-sm">
<span>TVA:</span>
<span>{totalTVA.toFixed(2)} </span>
</div>
<div className="flex justify-between items-center gap-2">
<span className="text-sm">Remise globale:</span>
<div className="flex items-center gap-2">
<Input
type="number"
min="0"
max="100"
value={globalDiscount}
onChange={(e) => setGlobalDiscount(Number(e.target.value))}
className="w-20 h-8 text-right"
/>
<span className="text-sm">%</span>
</div>
<span className="text-sm font-medium text-red-500">
-{globalDiscountAmount.toFixed(2)}
</span>
</div>
<Separator />
<div className="flex justify-between text-lg font-bold">
<span>Total TTC:</span>
<span className="text-primary">{finalTTC.toFixed(2)} </span>
</div>
</div>
</>
)}
</CardContent>
</Card>
{/* Payment */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<CreditCard className="h-5 w-5" />
Paiement
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Mode de paiement</Label>
<Select
value={currentPayment.mode}
onValueChange={(value: any) =>
setCurrentPayment({ ...currentPayment, mode: value })
}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="ESPECES">Espèces</SelectItem>
<SelectItem value="CARTE">Carte bancaire</SelectItem>
<SelectItem value="CHEQUE">Chèque</SelectItem>
<SelectItem value="VIREMENT">Virement</SelectItem>
<SelectItem value="BON_CAISSE">Bon de caisse</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label>Montant</Label>
<div className="flex gap-2">
<Input
type="number"
min="0"
step="0.01"
value={currentPayment.montant || ''}
onChange={(e) => setCurrentPayment({
...currentPayment,
montant: Number(e.target.value)
})}
placeholder="0.00"
/>
<Button onClick={addPayment} disabled={cart.length === 0}>
<Plus className="h-4 w-4" />
</Button>
</div>
</div>
</div>
{/* Payments list */}
{payments.length > 0 && (
<div className="space-y-2">
<Label>Paiements enregistrés</Label>
<div className="space-y-2 max-h-[150px] overflow-y-auto">
{payments.map((payment, index) => (
<div key={index} className="flex justify-between items-center p-2 bg-gray-50 rounded">
<div className="flex items-center gap-2">
{payment.mode === 'ESPECES' && <DollarSign className="h-4 w-4 text-green-500" />}
{payment.mode === 'CARTE' && <CreditCard className="h-4 w-4 text-blue-500" />}
{payment.mode === 'CHEQUE' && <FileText className="h-4 w-4 text-purple-500" />}
{payment.mode === 'VIREMENT' && <FileText className="h-4 w-4 text-orange-500" />}
{payment.mode === 'BON_CAISSE' && <FileText className="h-4 w-4 text-cyan-500" />}
<span className="text-sm capitalize">
{payment.mode.toLowerCase().replace('_', ' ')}
</span>
</div>
<div className="flex items-center gap-2">
<span className="font-medium">{payment.montant.toFixed(2)} </span>
<Button
variant="ghost"
size="icon"
className="h-6 w-6"
onClick={() => removePayment(index)}
>
<X className="h-3 w-3" />
</Button>
</div>
</div>
))}
</div>
</div>
)}
<Separator />
<div className="flex justify-between items-center text-lg">
<span>Reste à payer:</span>
<span className={`font-bold ${remainingToPay <= 0.01 ? 'text-green-600' : 'text-red-600'}`}>
{Math.max(0, remainingToPay).toFixed(2)}
</span>
</div>
<Button
onClick={completeSale}
disabled={cart.length === 0 || remainingToPay > 0.01}
className="w-full"
size="lg"
>
<CheckCircle className="h-5 w-5 mr-2" />
Valider la vente
</Button>
</CardContent>
</Card>
</div>
</div>
</TabsContent>
{/* Sales History */}
<TabsContent value="history">
<Card>
<CardHeader>
<CardTitle>Historique des ventes</CardTitle>
<CardDescription>Les dernières ventes enregistrées</CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>N° Vente</TableHead>
<TableHead>Date</TableHead>
<TableHead>Client</TableHead>
<TableHead>Statut</TableHead>
<TableHead className="text-right">Montant TTC</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{salesHistory.map((sale) => (
<TableRow key={sale.id}>
<TableCell className="font-medium">{sale.numero}</TableCell>
<TableCell>
{new Date(sale.date).toLocaleDateString('fr-FR')}
</TableCell>
<TableCell>
{sale.client
? `${sale.client.prenom} ${sale.client.nom}`
: 'Client anonyme'
}
</TableCell>
<TableCell>{getStatusBadge(sale.statut)}</TableCell>
<TableCell className="text-right font-bold">
{sale.montantTTC.toFixed(2)}
</TableCell>
</TableRow>
))}
{salesHistory.length === 0 && (
<TableRow>
<TableCell colSpan={5} className="text-center py-8 text-gray-500">
Aucune vente enregistrée
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</CardContent>
</Card>
</TabsContent>
</Tabs>
{/* Invoice Dialog */}
<Dialog open={showInvoiceDialog} onOpenChange={setShowInvoiceDialog}>
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<FileText className="h-5 w-5" />
Facture {currentSale?.numero}
</DialogTitle>
<DialogDescription>
Vente complétée avec succès
</DialogDescription>
</DialogHeader>
{currentSale && (
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<p className="font-semibold mb-1">OptiqueStock</p>
<p className="text-gray-600">Système de Gestion d'Optique</p>
</div>
<div className="text-right">
<p className="font-semibold">Facture N° {currentSale.numero}</p>
<p className="text-gray-600">
{new Date(currentSale.date).toLocaleDateString('fr-FR')} à{' '}
{new Date(currentSale.date).toLocaleTimeString('fr-FR')}
</p>
</div>
</div>
{currentSale.client && (
<div className="bg-gray-50 p-4 rounded">
<p className="font-semibold mb-1">Client</p>
<p>{currentSale.client.prenom} {currentSale.client.nom}</p>
<p className="text-sm text-gray-600">{currentSale.client.telephone}</p>
</div>
)}
<Separator />
<div className="space-y-2">
<div className="flex justify-between">
<span>Total HT:</span>
<span>{currentSale.montantHT.toFixed(2)} </span>
</div>
<div className="flex justify-between">
<span>TVA:</span>
<span>{currentSale.montantTVA.toFixed(2)} </span>
</div>
{currentSale.remise > 0 && (
<div className="flex justify-between text-red-600">
<span>Remise:</span>
<span>-{currentSale.remise.toFixed(2)} </span>
</div>
)}
<Separator />
<div className="flex justify-between text-lg font-bold">
<span>Total TTC:</span>
<span>{currentSale.montantTTC.toFixed(2)} </span>
</div>
</div>
<div className="flex justify-center pt-4">
{getStatusBadge(currentSale.statut)}
</div>
<Button onClick={() => setShowInvoiceDialog(false)} className="w-full">
Fermer
</Button>
</div>
)}
</DialogContent>
</Dialog>
</div>
)
}