841 lines
33 KiB
TypeScript
841 lines
33 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState, useEffect, useRef } from 'react';
|
|
import { DataTable } from 'primereact/datatable';
|
|
import { Column } from 'primereact/column';
|
|
import { Button } from 'primereact/button';
|
|
import { InputText } from 'primereact/inputtext';
|
|
import { Card } from 'primereact/card';
|
|
import { Dialog } from 'primereact/dialog';
|
|
import { Toast } from 'primereact/toast';
|
|
import { Toolbar } from 'primereact/toolbar';
|
|
import { Tag } from 'primereact/tag';
|
|
import { InputNumber } from 'primereact/inputnumber';
|
|
import { Dropdown } from 'primereact/dropdown';
|
|
import { Calendar } from 'primereact/calendar';
|
|
import { InputTextarea } from 'primereact/inputtextarea';
|
|
import { confirmDialog } from 'primereact/confirmdialog';
|
|
import { ConfirmDialog } from 'primereact/confirmdialog';
|
|
|
|
interface OrderItem {
|
|
id: string;
|
|
reference: string;
|
|
nom: string;
|
|
quantiteCommandee: number;
|
|
quantiteRecue: number;
|
|
prixUnitaire: number;
|
|
total: number;
|
|
unite: string;
|
|
}
|
|
|
|
interface Order {
|
|
id: string;
|
|
numero: string;
|
|
fournisseur: string;
|
|
dateCommande: Date;
|
|
dateLivraison?: Date;
|
|
statut: 'BROUILLON' | 'ENVOYEE' | 'CONFIRMEE' | 'PARTIELLE' | 'LIVREE' | 'ANNULEE';
|
|
priorite: 'NORMALE' | 'URGENTE' | 'CRITIQUE';
|
|
montantTotal: number;
|
|
items: OrderItem[];
|
|
commentaires: string;
|
|
adresseLivraison: string;
|
|
responsable: string;
|
|
}
|
|
|
|
const CommandesPage = () => {
|
|
const [orders, setOrders] = useState<Order[]>([]);
|
|
const [selectedOrders, setSelectedOrders] = useState<Order[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [globalFilter, setGlobalFilter] = useState('');
|
|
const [orderDialog, setOrderDialog] = useState(false);
|
|
const [itemDialog, setItemDialog] = useState(false);
|
|
const [deleteOrderDialog, setDeleteOrderDialog] = useState(false);
|
|
const [order, setOrder] = useState<Order>({
|
|
id: '',
|
|
numero: '',
|
|
fournisseur: '',
|
|
dateCommande: new Date(),
|
|
statut: 'BROUILLON',
|
|
priorite: 'NORMALE',
|
|
montantTotal: 0,
|
|
items: [],
|
|
commentaires: '',
|
|
adresseLivraison: '',
|
|
responsable: ''
|
|
});
|
|
const [currentItem, setCurrentItem] = useState<OrderItem>({
|
|
id: '',
|
|
reference: '',
|
|
nom: '',
|
|
quantiteCommandee: 0,
|
|
quantiteRecue: 0,
|
|
prixUnitaire: 0,
|
|
total: 0,
|
|
unite: 'unité'
|
|
});
|
|
const [submitted, setSubmitted] = useState(false);
|
|
const toast = useRef<Toast>(null);
|
|
const dt = useRef<DataTable<Order[]>>(null);
|
|
|
|
const fournisseurs = [
|
|
{ label: 'Lafarge', value: 'lafarge' },
|
|
{ label: 'Bosch', value: 'bosch' },
|
|
{ label: 'Protecta', value: 'protecta' },
|
|
{ label: 'Leroy Merlin', value: 'leroy-merlin' },
|
|
{ label: 'Castorama', value: 'castorama' }
|
|
];
|
|
|
|
const priorites = [
|
|
{ label: 'Normale', value: 'NORMALE' },
|
|
{ label: 'Urgente', value: 'URGENTE' },
|
|
{ label: 'Critique', value: 'CRITIQUE' }
|
|
];
|
|
|
|
const statuts = [
|
|
{ label: 'Brouillon', value: 'BROUILLON' },
|
|
{ label: 'Envoyée', value: 'ENVOYEE' },
|
|
{ label: 'Confirmée', value: 'CONFIRMEE' },
|
|
{ label: 'Partielle', value: 'PARTIELLE' },
|
|
{ label: 'Livrée', value: 'LIVREE' },
|
|
{ label: 'Annulée', value: 'ANNULEE' }
|
|
];
|
|
|
|
const unites = [
|
|
{ label: 'Unité', value: 'unité' },
|
|
{ label: 'Mètre', value: 'm' },
|
|
{ label: 'Mètre carré', value: 'm²' },
|
|
{ label: 'Kilogramme', value: 'kg' },
|
|
{ label: 'Litre', value: 'l' },
|
|
{ label: 'Sac', value: 'sac' },
|
|
{ label: 'Palette', value: 'palette' }
|
|
];
|
|
|
|
useEffect(() => {
|
|
loadOrders();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
// Recalculer le total de la commande quand les items changent
|
|
const total = order.items.reduce((sum, item) => sum + item.total, 0);
|
|
setOrder(prev => ({ ...prev, montantTotal: total }));
|
|
}, [order.items]);
|
|
|
|
const loadOrders = async () => {
|
|
try {
|
|
setLoading(true);
|
|
|
|
// Données mockées
|
|
const mockOrders: Order[] = [
|
|
{
|
|
id: '1',
|
|
numero: 'CMD-2024-001',
|
|
fournisseur: 'lafarge',
|
|
dateCommande: new Date('2024-01-15'),
|
|
dateLivraison: new Date('2024-01-22'),
|
|
statut: 'LIVREE',
|
|
priorite: 'NORMALE',
|
|
montantTotal: 1275.00,
|
|
items: [
|
|
{
|
|
id: '1',
|
|
reference: 'CIM-001',
|
|
nom: 'Ciment Portland',
|
|
quantiteCommandee: 150,
|
|
quantiteRecue: 150,
|
|
prixUnitaire: 8.50,
|
|
total: 1275.00,
|
|
unite: 'sac'
|
|
}
|
|
],
|
|
commentaires: 'Livraison urgente pour chantier Dupont',
|
|
adresseLivraison: '123 Rue du Chantier, 75001 Paris',
|
|
responsable: 'Jean Martin'
|
|
},
|
|
{
|
|
id: '2',
|
|
numero: 'CMD-2024-002',
|
|
fournisseur: 'bosch',
|
|
dateCommande: new Date('2024-01-20'),
|
|
statut: 'CONFIRMEE',
|
|
priorite: 'URGENTE',
|
|
montantTotal: 600.00,
|
|
items: [
|
|
{
|
|
id: '2',
|
|
reference: 'OUT-002',
|
|
nom: 'Perceuse électrique',
|
|
quantiteCommandee: 5,
|
|
quantiteRecue: 0,
|
|
prixUnitaire: 120.00,
|
|
total: 600.00,
|
|
unite: 'unité'
|
|
}
|
|
],
|
|
commentaires: 'Renouvellement outillage équipe',
|
|
adresseLivraison: 'Entrepôt A, Zone Industrielle',
|
|
responsable: 'Marie Dubois'
|
|
},
|
|
{
|
|
id: '3',
|
|
numero: 'CMD-2024-003',
|
|
fournisseur: 'protecta',
|
|
dateCommande: new Date(),
|
|
statut: 'BROUILLON',
|
|
priorite: 'CRITIQUE',
|
|
montantTotal: 500.00,
|
|
items: [
|
|
{
|
|
id: '3',
|
|
reference: 'SEC-003',
|
|
nom: 'Casque de sécurité',
|
|
quantiteCommandee: 20,
|
|
quantiteRecue: 0,
|
|
prixUnitaire: 25.00,
|
|
total: 500.00,
|
|
unite: 'unité'
|
|
}
|
|
],
|
|
commentaires: 'Commande urgente EPI',
|
|
adresseLivraison: 'Bureau central',
|
|
responsable: 'Pierre Durand'
|
|
}
|
|
];
|
|
|
|
setOrders(mockOrders);
|
|
} catch (error) {
|
|
console.error('Erreur lors du chargement:', error);
|
|
toast.current?.show({
|
|
severity: 'error',
|
|
summary: 'Erreur',
|
|
detail: 'Impossible de charger les commandes',
|
|
life: 3000
|
|
});
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const openNew = () => {
|
|
setOrder({
|
|
id: '',
|
|
numero: '',
|
|
fournisseur: '',
|
|
dateCommande: new Date(),
|
|
statut: 'BROUILLON',
|
|
priorite: 'NORMALE',
|
|
montantTotal: 0,
|
|
items: [],
|
|
commentaires: '',
|
|
adresseLivraison: '',
|
|
responsable: ''
|
|
});
|
|
setSubmitted(false);
|
|
setOrderDialog(true);
|
|
};
|
|
|
|
const hideDialog = () => {
|
|
setSubmitted(false);
|
|
setOrderDialog(false);
|
|
};
|
|
|
|
const hideItemDialog = () => {
|
|
setSubmitted(false);
|
|
setItemDialog(false);
|
|
};
|
|
|
|
const hideDeleteOrderDialog = () => {
|
|
setDeleteOrderDialog(false);
|
|
};
|
|
|
|
const saveOrder = () => {
|
|
setSubmitted(true);
|
|
|
|
if (order.fournisseur && order.responsable && order.items.length > 0) {
|
|
let updatedOrders = [...orders];
|
|
|
|
if (order.id) {
|
|
// Mise à jour
|
|
const index = orders.findIndex(o => o.id === order.id);
|
|
updatedOrders[index] = order;
|
|
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Succès',
|
|
detail: 'Commande mise à jour',
|
|
life: 3000
|
|
});
|
|
} else {
|
|
// Création
|
|
const newOrder = {
|
|
...order,
|
|
id: Date.now().toString(),
|
|
numero: `CMD-${new Date().getFullYear()}-${String(orders.length + 1).padStart(3, '0')}`
|
|
};
|
|
updatedOrders.push(newOrder);
|
|
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Succès',
|
|
detail: 'Commande créée',
|
|
life: 3000
|
|
});
|
|
}
|
|
|
|
setOrders(updatedOrders);
|
|
setOrderDialog(false);
|
|
}
|
|
};
|
|
|
|
const editOrder = (order: Order) => {
|
|
setOrder({ ...order });
|
|
setOrderDialog(true);
|
|
};
|
|
|
|
const confirmDeleteOrder = (order: Order) => {
|
|
setOrder(order);
|
|
setDeleteOrderDialog(true);
|
|
};
|
|
|
|
const deleteOrder = () => {
|
|
const updatedOrders = orders.filter(o => o.id !== order.id);
|
|
setOrders(updatedOrders);
|
|
setDeleteOrderDialog(false);
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Succès',
|
|
detail: 'Commande supprimée',
|
|
life: 3000
|
|
});
|
|
};
|
|
|
|
const sendOrder = (order: Order) => {
|
|
const updatedOrders = orders.map(o =>
|
|
o.id === order.id
|
|
? { ...o, statut: 'ENVOYEE' as const }
|
|
: o
|
|
);
|
|
setOrders(updatedOrders);
|
|
|
|
toast.current?.show({
|
|
severity: 'info',
|
|
summary: 'Commande envoyée',
|
|
detail: `Commande ${order.numero} envoyée au fournisseur`,
|
|
life: 3000
|
|
});
|
|
};
|
|
|
|
const receiveOrder = (order: Order) => {
|
|
const updatedOrders = orders.map(o =>
|
|
o.id === order.id
|
|
? {
|
|
...o,
|
|
statut: 'LIVREE' as const,
|
|
dateLivraison: new Date(),
|
|
items: o.items.map(item => ({ ...item, quantiteRecue: item.quantiteCommandee }))
|
|
}
|
|
: o
|
|
);
|
|
setOrders(updatedOrders);
|
|
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Commande réceptionnée',
|
|
detail: `Commande ${order.numero} réceptionnée`,
|
|
life: 3000
|
|
});
|
|
};
|
|
|
|
const addItem = () => {
|
|
setCurrentItem({
|
|
id: '',
|
|
reference: '',
|
|
nom: '',
|
|
quantiteCommandee: 0,
|
|
quantiteRecue: 0,
|
|
prixUnitaire: 0,
|
|
total: 0,
|
|
unite: 'unité'
|
|
});
|
|
setSubmitted(false);
|
|
setItemDialog(true);
|
|
};
|
|
|
|
const saveItem = () => {
|
|
setSubmitted(true);
|
|
|
|
if (currentItem.nom && currentItem.quantiteCommandee > 0 && currentItem.prixUnitaire > 0) {
|
|
const item = {
|
|
...currentItem,
|
|
id: currentItem.id || Date.now().toString(),
|
|
total: currentItem.quantiteCommandee * currentItem.prixUnitaire
|
|
};
|
|
|
|
let updatedItems = [...order.items];
|
|
|
|
if (currentItem.id) {
|
|
// Mise à jour
|
|
const index = order.items.findIndex(i => i.id === currentItem.id);
|
|
updatedItems[index] = item;
|
|
} else {
|
|
// Ajout
|
|
updatedItems.push(item);
|
|
}
|
|
|
|
setOrder({ ...order, items: updatedItems });
|
|
setItemDialog(false);
|
|
}
|
|
};
|
|
|
|
const editItem = (item: OrderItem) => {
|
|
setCurrentItem({ ...item });
|
|
setItemDialog(true);
|
|
};
|
|
|
|
const deleteItem = (itemId: string) => {
|
|
const updatedItems = order.items.filter(i => i.id !== itemId);
|
|
setOrder({ ...order, items: updatedItems });
|
|
};
|
|
|
|
const exportCSV = () => {
|
|
dt.current?.exportCSV();
|
|
};
|
|
|
|
const leftToolbarTemplate = () => {
|
|
return (
|
|
<div className="flex flex-wrap gap-2">
|
|
<Button
|
|
label="Nouvelle Commande"
|
|
icon="pi pi-plus"
|
|
severity="success"
|
|
onClick={openNew}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const rightToolbarTemplate = () => {
|
|
return (
|
|
<Button
|
|
label="Exporter"
|
|
icon="pi pi-upload"
|
|
severity="help"
|
|
onClick={exportCSV}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const actionBodyTemplate = (rowData: Order) => {
|
|
return (
|
|
<div className="flex gap-2">
|
|
<Button
|
|
icon="pi pi-pencil"
|
|
rounded
|
|
severity="success"
|
|
onClick={() => editOrder(rowData)}
|
|
tooltip="Modifier"
|
|
/>
|
|
{rowData.statut === 'BROUILLON' && (
|
|
<Button
|
|
icon="pi pi-send"
|
|
rounded
|
|
severity="info"
|
|
onClick={() => sendOrder(rowData)}
|
|
tooltip="Envoyer"
|
|
/>
|
|
)}
|
|
{(rowData.statut === 'CONFIRMEE' || rowData.statut === 'PARTIELLE') && (
|
|
<Button
|
|
icon="pi pi-check"
|
|
rounded
|
|
severity="success"
|
|
onClick={() => receiveOrder(rowData)}
|
|
tooltip="Réceptionner"
|
|
/>
|
|
)}
|
|
<Button
|
|
icon="pi pi-trash"
|
|
rounded
|
|
severity="warning"
|
|
onClick={() => confirmDeleteOrder(rowData)}
|
|
tooltip="Supprimer"
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const statusBodyTemplate = (rowData: Order) => {
|
|
let severity: "success" | "warning" | "danger" | "info" = 'info';
|
|
let label = rowData.statut;
|
|
|
|
switch (rowData.statut) {
|
|
case 'BROUILLON':
|
|
severity = 'info';
|
|
label = 'Brouillon';
|
|
break;
|
|
case 'ENVOYEE':
|
|
severity = 'warning';
|
|
label = 'Envoyée';
|
|
break;
|
|
case 'CONFIRMEE':
|
|
severity = 'success';
|
|
label = 'Confirmée';
|
|
break;
|
|
case 'PARTIELLE':
|
|
severity = 'warning';
|
|
label = 'Partielle';
|
|
break;
|
|
case 'LIVREE':
|
|
severity = 'success';
|
|
label = 'Livrée';
|
|
break;
|
|
case 'ANNULEE':
|
|
severity = 'danger';
|
|
label = 'Annulée';
|
|
break;
|
|
}
|
|
|
|
return <Tag value={label} severity={severity} />;
|
|
};
|
|
|
|
const prioriteBodyTemplate = (rowData: Order) => {
|
|
let severity: "success" | "warning" | "danger" | "info" = 'info';
|
|
let label = rowData.priorite;
|
|
|
|
switch (rowData.priorite) {
|
|
case 'NORMALE':
|
|
severity = 'info';
|
|
label = 'Normale';
|
|
break;
|
|
case 'URGENTE':
|
|
severity = 'warning';
|
|
label = 'Urgente';
|
|
break;
|
|
case 'CRITIQUE':
|
|
severity = 'danger';
|
|
label = 'Critique';
|
|
break;
|
|
}
|
|
|
|
return <Tag value={label} severity={severity} />;
|
|
};
|
|
|
|
const fournisseurBodyTemplate = (rowData: Order) => {
|
|
const fournisseur = fournisseurs.find(f => f.value === rowData.fournisseur);
|
|
return fournisseur ? fournisseur.label : rowData.fournisseur;
|
|
};
|
|
|
|
const montantBodyTemplate = (rowData: Order) => {
|
|
return new Intl.NumberFormat('fr-FR', {
|
|
style: 'currency',
|
|
currency: 'EUR'
|
|
}).format(rowData.montantTotal);
|
|
};
|
|
|
|
const header = (
|
|
<div className="flex flex-column md:flex-row md:justify-content-between md:align-items-center">
|
|
<h5 className="m-0">Gestion des Commandes</h5>
|
|
<span className="block mt-2 md:mt-0 p-input-icon-left">
|
|
<i className="pi pi-search" />
|
|
<InputText
|
|
type="search"
|
|
placeholder="Rechercher..."
|
|
onInput={(e) => setGlobalFilter(e.currentTarget.value)}
|
|
/>
|
|
</span>
|
|
</div>
|
|
);
|
|
|
|
const orderDialogFooter = (
|
|
<div className="flex justify-content-end gap-2">
|
|
<Button label="Annuler" icon="pi pi-times" text onClick={hideDialog} />
|
|
<Button label="Sauvegarder" icon="pi pi-check" onClick={saveOrder} />
|
|
</div>
|
|
);
|
|
|
|
const itemDialogFooter = (
|
|
<div className="flex justify-content-end gap-2">
|
|
<Button label="Annuler" icon="pi pi-times" text onClick={hideItemDialog} />
|
|
<Button label="Ajouter" icon="pi pi-check" onClick={saveItem} />
|
|
</div>
|
|
);
|
|
|
|
const deleteOrderDialogFooter = (
|
|
<div className="flex justify-content-end gap-2">
|
|
<Button label="Non" icon="pi pi-times" text onClick={hideDeleteOrderDialog} />
|
|
<Button label="Oui" icon="pi pi-check" onClick={deleteOrder} />
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<div className="grid">
|
|
<div className="col-12">
|
|
<Card>
|
|
<Toast ref={toast} />
|
|
<ConfirmDialog />
|
|
<Toolbar className="mb-4" left={leftToolbarTemplate} right={rightToolbarTemplate} />
|
|
|
|
<DataTable
|
|
ref={dt}
|
|
value={orders}
|
|
selection={selectedOrders}
|
|
onSelectionChange={(e) => setSelectedOrders(e.value)}
|
|
dataKey="id"
|
|
paginator
|
|
rows={10}
|
|
rowsPerPageOptions={[5, 10, 25]}
|
|
className="datatable-responsive"
|
|
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
|
|
currentPageReportTemplate="Affichage de {first} à {last} sur {totalRecords} commandes"
|
|
globalFilter={globalFilter}
|
|
emptyMessage="Aucune commande trouvée."
|
|
header={header}
|
|
responsiveLayout="scroll"
|
|
loading={loading}
|
|
>
|
|
<Column selectionMode="multiple" headerStyle={{ width: '4rem' }} />
|
|
<Column field="numero" header="Numéro" sortable />
|
|
<Column field="fournisseur" header="Fournisseur" body={fournisseurBodyTemplate} sortable />
|
|
<Column field="dateCommande" header="Date commande" body={(rowData) => rowData.dateCommande.toLocaleDateString()} sortable />
|
|
<Column field="dateLivraison" header="Date livraison" body={(rowData) => rowData.dateLivraison?.toLocaleDateString() || '-'} sortable />
|
|
<Column field="statut" header="Statut" body={statusBodyTemplate} sortable />
|
|
<Column field="priorite" header="Priorité" body={prioriteBodyTemplate} sortable />
|
|
<Column field="montantTotal" header="Montant" body={montantBodyTemplate} sortable />
|
|
<Column field="responsable" header="Responsable" sortable />
|
|
<Column body={actionBodyTemplate} headerStyle={{ minWidth: '12rem' }} />
|
|
</DataTable>
|
|
|
|
{/* Dialog commande */}
|
|
<Dialog
|
|
visible={orderDialog}
|
|
style={{ width: '80vw' }}
|
|
header="Détails de la commande"
|
|
modal
|
|
className="p-fluid"
|
|
footer={orderDialogFooter}
|
|
onHide={hideDialog}
|
|
>
|
|
<div className="formgrid grid">
|
|
<div className="field col-12 md:col-6">
|
|
<label htmlFor="fournisseur">Fournisseur *</label>
|
|
<Dropdown
|
|
id="fournisseur"
|
|
value={order.fournisseur}
|
|
options={fournisseurs}
|
|
onChange={(e) => setOrder({...order, fournisseur: e.value})}
|
|
placeholder="Sélectionnez un fournisseur"
|
|
required
|
|
className={submitted && !order.fournisseur ? 'p-invalid' : ''}
|
|
/>
|
|
{submitted && !order.fournisseur && <small className="p-invalid">Le fournisseur est requis.</small>}
|
|
</div>
|
|
|
|
<div className="field col-12 md:col-6">
|
|
<label htmlFor="responsable">Responsable *</label>
|
|
<InputText
|
|
id="responsable"
|
|
value={order.responsable}
|
|
onChange={(e) => setOrder({...order, responsable: e.target.value})}
|
|
required
|
|
className={submitted && !order.responsable ? 'p-invalid' : ''}
|
|
/>
|
|
{submitted && !order.responsable && <small className="p-invalid">Le responsable est requis.</small>}
|
|
</div>
|
|
|
|
<div className="field col-12 md:col-4">
|
|
<label htmlFor="dateCommande">Date commande</label>
|
|
<Calendar
|
|
id="dateCommande"
|
|
value={order.dateCommande}
|
|
onChange={(e) => setOrder({...order, dateCommande: e.value?.toISOString() || new Date().toISOString()})}
|
|
showIcon
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12 md:col-4">
|
|
<label htmlFor="priorite">Priorité</label>
|
|
<Dropdown
|
|
id="priorite"
|
|
value={order.priorite}
|
|
options={priorites}
|
|
onChange={(e) => setOrder({...order, priorite: e.value})}
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12 md:col-4">
|
|
<label htmlFor="statut">Statut</label>
|
|
<Dropdown
|
|
id="statut"
|
|
value={order.statut}
|
|
options={statuts}
|
|
onChange={(e) => setOrder({...order, statut: e.value})}
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12">
|
|
<label htmlFor="adresseLivraison">Adresse de livraison</label>
|
|
<InputTextarea
|
|
id="adresseLivraison"
|
|
value={order.adresseLivraison}
|
|
onChange={(e) => setOrder({...order, adresseLivraison: e.target.value})}
|
|
rows={2}
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12">
|
|
<label htmlFor="commentaires">Commentaires</label>
|
|
<InputTextarea
|
|
id="commentaires"
|
|
value={order.commentaires}
|
|
onChange={(e) => setOrder({...order, commentaires: e.target.value})}
|
|
rows={3}
|
|
/>
|
|
</div>
|
|
|
|
{/* Articles commandés */}
|
|
<div className="field col-12">
|
|
<div className="flex justify-content-between align-items-center mb-3">
|
|
<h6>Articles commandés</h6>
|
|
<Button
|
|
label="Ajouter Article"
|
|
icon="pi pi-plus"
|
|
size="small"
|
|
onClick={addItem}
|
|
/>
|
|
</div>
|
|
|
|
<DataTable
|
|
value={order.items}
|
|
responsiveLayout="scroll"
|
|
emptyMessage="Aucun article ajouté"
|
|
>
|
|
<Column field="reference" header="Référence" />
|
|
<Column field="nom" header="Nom" />
|
|
<Column field="quantiteCommandee" header="Qté" />
|
|
<Column field="unite" header="Unité" />
|
|
<Column field="prixUnitaire" header="Prix unitaire" body={(rowData) => `${rowData.prixUnitaire.toFixed(2)}€`} />
|
|
<Column field="total" header="Total" body={(rowData) => `${rowData.total.toFixed(2)}€`} />
|
|
<Column body={(rowData) => (
|
|
<div className="flex gap-2">
|
|
<Button
|
|
icon="pi pi-pencil"
|
|
size="small"
|
|
onClick={() => editItem(rowData)}
|
|
/>
|
|
<Button
|
|
icon="pi pi-trash"
|
|
size="small"
|
|
severity="danger"
|
|
onClick={() => deleteItem(rowData.id)}
|
|
/>
|
|
</div>
|
|
)} />
|
|
</DataTable>
|
|
|
|
<div className="text-right mt-3">
|
|
<strong>Total commande: {order.montantTotal.toFixed(2)}€</strong>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Dialog>
|
|
|
|
{/* Dialog article */}
|
|
<Dialog
|
|
visible={itemDialog}
|
|
style={{ width: '450px' }}
|
|
header="Article de commande"
|
|
modal
|
|
className="p-fluid"
|
|
footer={itemDialogFooter}
|
|
onHide={hideItemDialog}
|
|
>
|
|
<div className="formgrid grid">
|
|
<div className="field col-12">
|
|
<label htmlFor="itemReference">Référence</label>
|
|
<InputText
|
|
id="itemReference"
|
|
value={currentItem.reference}
|
|
onChange={(e) => setCurrentItem({...currentItem, reference: e.target.value})}
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12">
|
|
<label htmlFor="itemNom">Nom *</label>
|
|
<InputText
|
|
id="itemNom"
|
|
value={currentItem.nom}
|
|
onChange={(e) => setCurrentItem({...currentItem, nom: e.target.value})}
|
|
required
|
|
className={submitted && !currentItem.nom ? 'p-invalid' : ''}
|
|
/>
|
|
{submitted && !currentItem.nom && <small className="p-invalid">Le nom est requis.</small>}
|
|
</div>
|
|
|
|
<div className="field col-12 md:col-6">
|
|
<label htmlFor="itemQuantite">Quantité *</label>
|
|
<InputNumber
|
|
id="itemQuantite"
|
|
value={currentItem.quantiteCommandee}
|
|
onValueChange={(e) => setCurrentItem({...currentItem, quantiteCommandee: e.value || 0})}
|
|
min={1}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12 md:col-6">
|
|
<label htmlFor="itemUnite">Unité</label>
|
|
<Dropdown
|
|
id="itemUnite"
|
|
value={currentItem.unite}
|
|
options={unites}
|
|
onChange={(e) => setCurrentItem({...currentItem, unite: e.value})}
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12">
|
|
<label htmlFor="itemPrix">Prix unitaire (€) *</label>
|
|
<InputNumber
|
|
id="itemPrix"
|
|
value={currentItem.prixUnitaire}
|
|
onValueChange={(e) => setCurrentItem({...currentItem, prixUnitaire: e.value || 0})}
|
|
mode="currency"
|
|
currency="EUR"
|
|
locale="fr-FR"
|
|
min={0}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12">
|
|
<label>Total: {(currentItem.quantiteCommandee * currentItem.prixUnitaire).toFixed(2)}€</label>
|
|
</div>
|
|
</div>
|
|
</Dialog>
|
|
|
|
{/* Dialog suppression */}
|
|
<Dialog
|
|
visible={deleteOrderDialog}
|
|
style={{ width: '450px' }}
|
|
header="Confirmer"
|
|
modal
|
|
footer={deleteOrderDialogFooter}
|
|
onHide={hideDeleteOrderDialog}
|
|
>
|
|
<div className="flex align-items-center justify-content-center">
|
|
<i className="pi pi-exclamation-triangle mr-3" style={{ fontSize: '2rem' }} />
|
|
{order && (
|
|
<span>
|
|
Êtes-vous sûr de vouloir supprimer la commande <b>{order.numero}</b> ?
|
|
</span>
|
|
)}
|
|
</div>
|
|
</Dialog>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default CommandesPage;
|
|
|