Files
btpxpress-frontend/app/(main)/stock/sorties/page.tsx

1027 lines
41 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 { AutoComplete } from 'primereact/autocomplete';
import { confirmDialog } from 'primereact/confirmdialog';
import { ConfirmDialog } from 'primereact/confirmdialog';
import { Chart } from 'primereact/chart';
import {
ActionButtonGroup,
ViewButton,
EditButton,
DeleteButton,
ActionButton
} from '../../../../components/ui/ActionButton';
interface StockItem {
id: string;
reference: string;
nom: string;
stockDisponible: number;
unite: string;
emplacement: string;
}
interface MaterialExit {
id: string;
numero: string;
dateCreation: Date;
dateSortie: Date;
chantier: string;
responsable: string;
statut: 'BROUILLON' | 'VALIDEE' | 'SORTIE' | 'RETOURNEE';
motif: string;
articles: ExitItem[];
commentaires: string;
signature?: string;
dateRetourPrevue?: Date;
dateRetourEffective?: Date;
}
interface ExitItem {
id: string;
articleId: string;
reference: string;
nom: string;
quantiteSortie: number;
quantiteRetournee: number;
unite: string;
etat: 'BON' | 'ABIME' | 'PERDU';
commentaire: string;
}
const SortiesPage = () => {
const [exits, setExits] = useState<MaterialExit[]>([]);
const [stockItems, setStockItems] = useState<StockItem[]>([]);
const [selectedExits, setSelectedExits] = useState<MaterialExit[]>([]);
const [loading, setLoading] = useState(true);
const [globalFilter, setGlobalFilter] = useState('');
const [exitDialog, setExitDialog] = useState(false);
const [itemDialog, setItemDialog] = useState(false);
const [returnDialog, setReturnDialog] = useState(false);
const [deleteExitDialog, setDeleteExitDialog] = useState(false);
const [exit, setExit] = useState<MaterialExit>({
id: '',
numero: '',
dateCreation: new Date(),
dateSortie: new Date(),
chantier: '',
responsable: '',
statut: 'BROUILLON',
motif: '',
articles: [],
commentaires: ''
});
const [currentItem, setCurrentItem] = useState<ExitItem>({
id: '',
articleId: '',
reference: '',
nom: '',
quantiteSortie: 0,
quantiteRetournee: 0,
unite: '',
etat: 'BON',
commentaire: ''
});
const [filteredStockItems, setFilteredStockItems] = useState<StockItem[]>([]);
const [submitted, setSubmitted] = useState(false);
const toast = useRef<Toast>(null);
const dt = useRef<DataTable<MaterialExit[]>>(null);
const chantiers = [
{ label: 'Chantier Dupont - Paris 15ème', value: 'chantier-dupont' },
{ label: 'Rénovation Mairie - Lyon', value: 'chantier-mairie-lyon' },
{ label: 'Construction Villa - Marseille', value: 'chantier-villa-marseille' },
{ label: 'Bureau Central - Maintenance', value: 'bureau-maintenance' }
];
const responsables = [
{ label: 'Jean Martin', value: 'jean.martin' },
{ label: 'Marie Dubois', value: 'marie.dubois' },
{ label: 'Pierre Durand', value: 'pierre.durand' },
{ label: 'Sophie Bernard', value: 'sophie.bernard' }
];
const motifs = [
{ label: 'Utilisation chantier', value: 'utilisation-chantier' },
{ label: 'Prêt temporaire', value: 'pret-temporaire' },
{ label: 'Maintenance', value: 'maintenance' },
{ label: 'Formation', value: 'formation' },
{ label: 'Démonstration', value: 'demonstration' }
];
const statuts = [
{ label: 'Brouillon', value: 'BROUILLON' },
{ label: 'Validée', value: 'VALIDEE' },
{ label: 'Sortie', value: 'SORTIE' },
{ label: 'Retournée', value: 'RETOURNEE' }
];
const etats = [
{ label: 'Bon état', value: 'BON' },
{ label: 'Abîmé', value: 'ABIME' },
{ label: 'Perdu', value: 'PERDU' }
];
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
setLoading(true);
// Données mockées stock
const mockStockItems: StockItem[] = [
{
id: '1',
reference: 'CIM-001',
nom: 'Ciment Portland',
stockDisponible: 150,
unite: 'sac',
emplacement: 'entrepot-a'
},
{
id: '2',
reference: 'OUT-002',
nom: 'Perceuse électrique',
stockDisponible: 5,
unite: 'unité',
emplacement: 'magasin'
},
{
id: '3',
reference: 'SEC-003',
nom: 'Casque de sécurité',
stockDisponible: 8,
unite: 'unité',
emplacement: 'bureau'
},
{
id: '4',
reference: 'OUT-004',
nom: 'Visseuse sans fil',
stockDisponible: 3,
unite: 'unité',
emplacement: 'magasin'
},
{
id: '5',
reference: 'MAT-005',
nom: 'Échafaudage mobile',
stockDisponible: 2,
unite: 'unité',
emplacement: 'entrepot-b'
}
];
// Données mockées sorties
const mockExits: MaterialExit[] = [
{
id: '1',
numero: 'SOR-2024-001',
dateCreation: new Date('2024-01-15'),
dateSortie: new Date('2024-01-16'),
chantier: 'chantier-dupont',
responsable: 'jean.martin',
statut: 'SORTIE',
motif: 'utilisation-chantier',
articles: [
{
id: '1',
articleId: '2',
reference: 'OUT-002',
nom: 'Perceuse électrique',
quantiteSortie: 2,
quantiteRetournee: 0,
unite: 'unité',
etat: 'BON',
commentaire: ''
}
],
commentaires: 'Matériel pour travaux de perçage',
dateRetourPrevue: new Date('2024-01-30')
},
{
id: '2',
numero: 'SOR-2024-002',
dateCreation: new Date('2024-01-20'),
dateSortie: new Date('2024-01-20'),
chantier: 'chantier-mairie-lyon',
responsable: 'marie.dubois',
statut: 'RETOURNEE',
motif: 'utilisation-chantier',
articles: [
{
id: '2',
articleId: '3',
reference: 'SEC-003',
nom: 'Casque de sécurité',
quantiteSortie: 5,
quantiteRetournee: 4,
unite: 'unité',
etat: 'BON',
commentaire: '1 casque perdu'
}
],
commentaires: 'EPI pour équipe de 5 personnes',
dateRetourPrevue: new Date('2024-01-25'),
dateRetourEffective: new Date('2024-01-24')
},
{
id: '3',
numero: 'SOR-2024-003',
dateCreation: new Date(),
dateSortie: new Date(),
chantier: 'chantier-villa-marseille',
responsable: 'pierre.durand',
statut: 'BROUILLON',
motif: 'utilisation-chantier',
articles: [
{
id: '3',
articleId: '5',
reference: 'MAT-005',
nom: 'Échafaudage mobile',
quantiteSortie: 1,
quantiteRetournee: 0,
unite: 'unité',
etat: 'BON',
commentaire: ''
}
],
commentaires: 'Échafaudage pour travaux en hauteur',
dateRetourPrevue: new Date('2024-02-15')
}
];
setStockItems(mockStockItems);
setExits(mockExits);
} catch (error) {
console.error('Erreur lors du chargement:', error);
toast.current?.show({
severity: 'error',
summary: 'Erreur',
detail: 'Impossible de charger les données',
life: 3000
});
} finally {
setLoading(false);
}
};
const openNew = () => {
setExit({
id: '',
numero: '',
dateCreation: new Date(),
dateSortie: new Date(),
chantier: '',
responsable: '',
statut: 'BROUILLON',
motif: '',
articles: [],
commentaires: ''
});
setSubmitted(false);
setExitDialog(true);
};
const hideDialog = () => {
setSubmitted(false);
setExitDialog(false);
};
const hideItemDialog = () => {
setSubmitted(false);
setItemDialog(false);
};
const hideReturnDialog = () => {
setReturnDialog(false);
};
const hideDeleteExitDialog = () => {
setDeleteExitDialog(false);
};
const saveExit = () => {
setSubmitted(true);
if (exit.chantier && exit.responsable && exit.motif && exit.articles.length > 0) {
let updatedExits = [...exits];
if (exit.id) {
// Mise à jour
const index = exits.findIndex(e => e.id === exit.id);
updatedExits[index] = exit;
toast.current?.show({
severity: 'success',
summary: 'Succès',
detail: 'Sortie mise à jour',
life: 3000
});
} else {
// Création
const newExit = {
...exit,
id: Date.now().toString(),
numero: `SOR-${new Date().getFullYear()}-${String(exits.length + 1).padStart(3, '0')}`
};
updatedExits.push(newExit);
toast.current?.show({
severity: 'success',
summary: 'Succès',
detail: 'Sortie créée',
life: 3000
});
}
setExits(updatedExits);
setExitDialog(false);
}
};
const editExit = (exit: MaterialExit) => {
setExit({ ...exit });
setExitDialog(true);
};
const confirmDeleteExit = (exit: MaterialExit) => {
setExit(exit);
setDeleteExitDialog(true);
};
const deleteExit = () => {
const updatedExits = exits.filter(e => e.id !== exit.id);
setExits(updatedExits);
setDeleteExitDialog(false);
toast.current?.show({
severity: 'success',
summary: 'Succès',
detail: 'Sortie supprimée',
life: 3000
});
};
const validateExit = (exit: MaterialExit) => {
const updatedExits = exits.map(e =>
e.id === exit.id
? { ...e, statut: 'VALIDEE' as const }
: e
);
setExits(updatedExits);
toast.current?.show({
severity: 'info',
summary: 'Sortie validée',
detail: `Sortie ${exit.numero} validée`,
life: 3000
});
};
const processExit = (exit: MaterialExit) => {
const updatedExits = exits.map(e =>
e.id === exit.id
? { ...e, statut: 'SORTIE' as const, dateSortie: new Date() }
: e
);
setExits(updatedExits);
toast.current?.show({
severity: 'success',
summary: 'Sortie effectuée',
detail: `Matériel sorti pour ${exit.numero}`,
life: 3000
});
};
const openReturn = (exit: MaterialExit) => {
setExit({ ...exit });
setReturnDialog(true);
};
const processReturn = () => {
const updatedExits = exits.map(e =>
e.id === exit.id
? { ...e, statut: 'RETOURNEE' as const, dateRetourEffective: new Date() }
: e
);
setExits(updatedExits);
setReturnDialog(false);
toast.current?.show({
severity: 'success',
summary: 'Retour traité',
detail: `Matériel retourné pour ${exit.numero}`,
life: 3000
});
};
const addItem = () => {
setCurrentItem({
id: '',
articleId: '',
reference: '',
nom: '',
quantiteSortie: 0,
quantiteRetournee: 0,
unite: '',
etat: 'BON',
commentaire: ''
});
setSubmitted(false);
setItemDialog(true);
};
const saveItem = () => {
setSubmitted(true);
if (currentItem.articleId && currentItem.quantiteSortie > 0) {
const item = {
...currentItem,
id: currentItem.id || Date.now().toString()
};
let updatedItems = [...exit.articles];
if (currentItem.id) {
// Mise à jour
const index = exit.articles.findIndex(i => i.id === currentItem.id);
updatedItems[index] = item;
} else {
// Ajout
updatedItems.push(item);
}
setExit({ ...exit, articles: updatedItems });
setItemDialog(false);
}
};
const editItem = (item: ExitItem) => {
setCurrentItem({ ...item });
setItemDialog(true);
};
const deleteItem = (itemId: string) => {
const updatedItems = exit.articles.filter(i => i.id !== itemId);
setExit({ ...exit, articles: updatedItems });
};
const searchStockItems = (event: any) => {
const query = event.query.toLowerCase();
const filtered = stockItems.filter(item =>
item.nom.toLowerCase().includes(query) ||
item.reference.toLowerCase().includes(query)
);
setFilteredStockItems(filtered);
};
const onStockItemSelect = (item: StockItem) => {
setCurrentItem({
...currentItem,
articleId: item.id,
reference: item.reference,
nom: item.nom,
unite: item.unite
});
};
const exportCSV = () => {
dt.current?.exportCSV();
};
const leftToolbarTemplate = () => {
return (
<div className="flex flex-wrap gap-2">
<Button
label="Nouvelle Sortie"
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: MaterialExit) => {
return (
<ActionButtonGroup>
<EditButton
onClick={() => editExit(rowData)}
tooltip="Modifier"
/>
{rowData.statut === 'BROUILLON' && (
<ActionButton
icon="pi pi-check"
color="info"
onClick={() => validateExit(rowData)}
tooltip="Valider"
/>
)}
{rowData.statut === 'VALIDEE' && (
<ActionButton
icon="pi pi-sign-out"
color="warning"
onClick={() => processExit(rowData)}
tooltip="Effectuer sortie"
/>
)}
{rowData.statut === 'SORTIE' && (
<ActionButton
icon="pi pi-sign-in"
color="help"
onClick={() => openReturn(rowData)}
tooltip="Retour"
/>
)}
<DeleteButton
onClick={() => confirmDeleteExit(rowData)}
tooltip="Supprimer"
/>
</ActionButtonGroup>
);
};
const statusBodyTemplate = (rowData: MaterialExit) => {
let severity: "success" | "warning" | "danger" | "info" = 'info';
let label = rowData.statut;
switch (rowData.statut) {
case 'BROUILLON':
severity = 'info';
label = 'Brouillon';
break;
case 'VALIDEE':
severity = 'warning';
label = 'Validée';
break;
case 'SORTIE':
severity = 'danger';
label = 'Sortie';
break;
case 'RETOURNEE':
severity = 'success';
label = 'Retournée';
break;
}
return <Tag value={label} severity={severity} />;
};
const chantierBodyTemplate = (rowData: MaterialExit) => {
const chantier = chantiers.find(c => c.value === rowData.chantier);
return chantier ? chantier.label : rowData.chantier;
};
const responsableBodyTemplate = (rowData: MaterialExit) => {
const responsable = responsables.find(r => r.value === rowData.responsable);
return responsable ? responsable.label : rowData.responsable;
};
const motifBodyTemplate = (rowData: MaterialExit) => {
const motif = motifs.find(m => m.value === rowData.motif);
return motif ? motif.label : rowData.motif;
};
const articlesBodyTemplate = (rowData: MaterialExit) => {
return `${rowData.articles.length} article(s)`;
};
const header = (
<div className="flex flex-column md:flex-row md:justify-content-between md:align-items-center">
<h5 className="m-0">Sorties de Matériel</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 exitDialogFooter = (
<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={saveExit} />
</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 returnDialogFooter = (
<div className="flex justify-content-end gap-2">
<Button label="Annuler" icon="pi pi-times" text onClick={hideReturnDialog} />
<Button label="Traiter Retour" icon="pi pi-check" onClick={processReturn} />
</div>
);
const deleteExitDialogFooter = (
<div className="flex justify-content-end gap-2">
<Button label="Non" icon="pi pi-times" text onClick={hideDeleteExitDialog} />
<Button label="Oui" icon="pi pi-check" onClick={deleteExit} />
</div>
);
// Statistiques pour le graphique
const getExitStats = () => {
const brouillon = exits.filter(e => e.statut === 'BROUILLON').length;
const validee = exits.filter(e => e.statut === 'VALIDEE').length;
const sortie = exits.filter(e => e.statut === 'SORTIE').length;
const retournee = exits.filter(e => e.statut === 'RETOURNEE').length;
return {
labels: ['Brouillon', 'Validée', 'Sortie', 'Retournée'],
datasets: [
{
data: [brouillon, validee, sortie, retournee],
backgroundColor: ['#6366F1', '#F59E0B', '#EF4444', '#10B981'],
hoverBackgroundColor: ['#4F46E5', '#D97706', '#DC2626', '#059669']
}
]
};
};
return (
<div className="grid">
<div className="col-12">
<Card>
<Toast ref={toast} />
<ConfirmDialog />
{/* Statistiques */}
<div className="grid mb-4">
<div className="col-12 md:col-3">
<Card className="text-center">
<div className="text-2xl font-semibold text-blue-600">
{exits.length}
</div>
<div className="text-sm">Total Sorties</div>
</Card>
</div>
<div className="col-12 md:col-3">
<Card className="text-center">
<div className="text-2xl font-semibold text-red-600">
{exits.filter(e => e.statut === 'SORTIE').length}
</div>
<div className="text-sm">En cours</div>
</Card>
</div>
<div className="col-12 md:col-3">
<Card className="text-center">
<div className="text-2xl font-semibold text-green-600">
{exits.filter(e => e.statut === 'RETOURNEE').length}
</div>
<div className="text-sm">Retournées</div>
</Card>
</div>
<div className="col-12 md:col-3">
<Card className="text-center">
<div className="text-2xl font-semibold text-orange-600">
{exits.filter(e => e.dateRetourPrevue && e.dateRetourPrevue < new Date() && e.statut === 'SORTIE').length}
</div>
<div className="text-sm">En retard</div>
</Card>
</div>
</div>
<Toolbar className="mb-4" left={leftToolbarTemplate} right={rightToolbarTemplate} />
<DataTable
ref={dt}
value={exits}
selection={selectedExits}
onSelectionChange={(e) => setSelectedExits(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} sorties"
globalFilter={globalFilter}
emptyMessage="Aucune sortie trouvée."
header={header}
responsiveLayout="scroll"
loading={loading}
>
<Column selectionMode="multiple" headerStyle={{ width: '4rem' }} />
<Column field="numero" header="Numéro" sortable />
<Column field="dateCreation" header="Date création" body={(rowData) => rowData.dateCreation.toLocaleDateString()} sortable />
<Column field="chantier" header="Chantier" body={chantierBodyTemplate} sortable />
<Column field="responsable" header="Responsable" body={responsableBodyTemplate} sortable />
<Column field="motif" header="Motif" body={motifBodyTemplate} sortable />
<Column field="articles" header="Articles" body={articlesBodyTemplate} />
<Column field="dateRetourPrevue" header="Retour prévu" body={(rowData) => rowData.dateRetourPrevue?.toLocaleDateString() || '-'} sortable />
<Column field="statut" header="Statut" body={statusBodyTemplate} sortable />
<Column body={actionBodyTemplate} headerStyle={{ minWidth: '12rem' }} />
</DataTable>
{/* Dialog sortie */}
<Dialog
visible={exitDialog}
style={{ width: '80vw' }}
header="Détails de la sortie"
modal
className="p-fluid"
footer={exitDialogFooter}
onHide={hideDialog}
>
<div className="formgrid grid">
<div className="field col-12 md:col-6">
<label htmlFor="chantier">Chantier *</label>
<Dropdown
id="chantier"
value={exit.chantier}
options={chantiers}
onChange={(e) => setExit({...exit, chantier: e.value})}
placeholder="Sélectionnez un chantier"
required
className={submitted && !exit.chantier ? 'p-invalid' : ''}
/>
{submitted && !exit.chantier && <small className="p-invalid">Le chantier est requis.</small>}
</div>
<div className="field col-12 md:col-6">
<label htmlFor="responsable">Responsable *</label>
<Dropdown
id="responsable"
value={exit.responsable}
options={responsables}
onChange={(e) => setExit({...exit, responsable: e.value})}
placeholder="Sélectionnez un responsable"
required
className={submitted && !exit.responsable ? 'p-invalid' : ''}
/>
{submitted && !exit.responsable && <small className="p-invalid">Le responsable est requis.</small>}
</div>
<div className="field col-12 md:col-4">
<label htmlFor="dateSortie">Date de sortie</label>
<Calendar
id="dateSortie"
value={exit.dateSortie}
onChange={(e) => setExit({...exit, dateSortie: e.value || new Date()})}
showIcon
/>
</div>
<div className="field col-12 md:col-4">
<label htmlFor="motif">Motif *</label>
<Dropdown
id="motif"
value={exit.motif}
options={motifs}
onChange={(e) => setExit({...exit, motif: e.value})}
placeholder="Sélectionnez un motif"
required
className={submitted && !exit.motif ? 'p-invalid' : ''}
/>
{submitted && !exit.motif && <small className="p-invalid">Le motif est requis.</small>}
</div>
<div className="field col-12 md:col-4">
<label htmlFor="dateRetourPrevue">Date retour prévue</label>
<Calendar
id="dateRetourPrevue"
value={exit.dateRetourPrevue}
onChange={(e) => setExit({...exit, dateRetourPrevue: e.value || undefined})}
showIcon
/>
</div>
<div className="field col-12">
<label htmlFor="commentaires">Commentaires</label>
<InputTextarea
id="commentaires"
value={exit.commentaires}
onChange={(e) => setExit({...exit, commentaires: e.target.value})}
rows={3}
/>
</div>
{/* Articles */}
<div className="field col-12">
<div className="flex justify-content-between align-items-center mb-3">
<h6>Articles à sortir</h6>
<Button
label="Ajouter Article"
icon="pi pi-plus"
size="small"
onClick={addItem}
/>
</div>
<DataTable
value={exit.articles}
responsiveLayout="scroll"
emptyMessage="Aucun article ajouté"
>
<Column field="reference" header="Référence" />
<Column field="nom" header="Nom" />
<Column field="quantiteSortie" header="Qté sortie" />
<Column field="unite" header="Unité" />
<Column field="etat" header="État" />
<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>
</div>
</Dialog>
{/* Dialog article */}
<Dialog
visible={itemDialog}
style={{ width: '450px' }}
header="Article de sortie"
modal
className="p-fluid"
footer={itemDialogFooter}
onHide={hideItemDialog}
>
<div className="formgrid grid">
<div className="field col-12">
<label htmlFor="article">Article *</label>
<AutoComplete
id="article"
value={currentItem.nom || ''}
suggestions={filteredStockItems}
completeMethod={searchStockItems}
field="nom"
placeholder="Rechercher un article..."
itemTemplate={(item) => `${item.reference} - ${item.nom} (Stock: ${item.stockDisponible})`}
onSelect={(e) => onStockItemSelect(e.value)}
onChange={(e) => setCurrentItem({...currentItem, nom: e.value})}
required
className={submitted && !currentItem.articleId ? 'p-invalid' : ''}
/>
{submitted && !currentItem.articleId && <small className="p-invalid">L'article est requis.</small>}
</div>
<div className="field col-12 md:col-6">
<label htmlFor="quantiteSortie">Quantité sortie *</label>
<InputNumber
id="quantiteSortie"
value={currentItem.quantiteSortie}
onValueChange={(e) => setCurrentItem({...currentItem, quantiteSortie: e.value || 0})}
min={1}
required
/>
</div>
<div className="field col-12 md:col-6">
<label htmlFor="etatSortie">État</label>
<Dropdown
id="etatSortie"
value={currentItem.etat}
options={etats}
onChange={(e) => setCurrentItem({...currentItem, etat: e.value})}
/>
</div>
<div className="field col-12">
<label htmlFor="commentaireItem">Commentaire</label>
<InputTextarea
id="commentaireItem"
value={currentItem.commentaire}
onChange={(e) => setCurrentItem({...currentItem, commentaire: e.target.value})}
rows={2}
/>
</div>
</div>
</Dialog>
{/* Dialog retour */}
<Dialog
visible={returnDialog}
style={{ width: '600px' }}
header="Retour de matériel"
modal
className="p-fluid"
footer={returnDialogFooter}
onHide={hideReturnDialog}
>
<div className="formgrid grid">
<div className="field col-12">
<h6>Sortie: {exit.numero}</h6>
<p>Chantier: {chantierBodyTemplate(exit)}</p>
<p>Responsable: {responsableBodyTemplate(exit)}</p>
</div>
<div className="field col-12">
<h6>Articles à retourner</h6>
<DataTable
value={exit.articles}
responsiveLayout="scroll"
>
<Column field="reference" header="Référence" />
<Column field="nom" header="Nom" />
<Column field="quantiteSortie" header="Qté sortie" />
<Column field="quantiteRetournee" header="Qté retournée" body={(rowData) => (
<InputNumber
value={rowData.quantiteRetournee}
onValueChange={(e) => {
const updatedArticles = exit.articles.map(article =>
article.id === rowData.id
? { ...article, quantiteRetournee: e.value || 0 }
: article
);
setExit({ ...exit, articles: updatedArticles });
}}
min={0}
max={rowData.quantiteSortie}
/>
)} />
<Column field="etat" header="État" body={(rowData) => (
<Dropdown
value={rowData.etat}
options={etats}
onChange={(e) => {
const updatedArticles = exit.articles.map(article =>
article.id === rowData.id
? { ...article, etat: e.value }
: article
);
setExit({ ...exit, articles: updatedArticles });
}}
/>
)} />
</DataTable>
</div>
</div>
</Dialog>
{/* Dialog suppression */}
<Dialog
visible={deleteExitDialog}
style={{ width: '450px' }}
header="Confirmer"
modal
footer={deleteExitDialogFooter}
onHide={hideDeleteExitDialog}
>
<div className="flex align-items-center justify-content-center">
<i className="pi pi-exclamation-triangle mr-3" style={{ fontSize: '2rem' }} />
{exit && (
<span>
Êtes-vous sûr de vouloir supprimer la sortie <b>{exit.numero}</b> ?
</span>
)}
</div>
</Dialog>
</Card>
</div>
</div>
);
};
export default SortiesPage;