Initial commit
This commit is contained in:
676
app/(main)/stock/inventaire/page.tsx
Normal file
676
app/(main)/stock/inventaire/page.tsx
Normal file
@@ -0,0 +1,676 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { Card } from 'primereact/card';
|
||||
import { DataTable } from 'primereact/datatable';
|
||||
import { Column } from 'primereact/column';
|
||||
import { Button } from 'primereact/button';
|
||||
import { Tag } from 'primereact/tag';
|
||||
import { Dialog } from 'primereact/dialog';
|
||||
import { InputText } from 'primereact/inputtext';
|
||||
import { InputNumber } from 'primereact/inputnumber';
|
||||
import { Toast } from 'primereact/toast';
|
||||
import { Divider } from 'primereact/divider';
|
||||
import { Badge } from 'primereact/badge';
|
||||
import { ProgressBar } from 'primereact/progressbar';
|
||||
import { Dropdown } from 'primereact/dropdown';
|
||||
import { Calendar } from 'primereact/calendar';
|
||||
import { InputTextarea } from 'primereact/inputtextarea';
|
||||
import { Knob } from 'primereact/knob';
|
||||
import { Toolbar } from 'primereact/toolbar';
|
||||
import { Checkbox } from 'primereact/checkbox';
|
||||
import { Chart } from 'primereact/chart';
|
||||
import {
|
||||
ActionButtonGroup,
|
||||
ViewButton,
|
||||
EditButton,
|
||||
DeleteButton,
|
||||
ActionButton
|
||||
} from '../../../../components/ui/ActionButton';
|
||||
|
||||
// Types pour l'inventaire
|
||||
interface InventoryItem {
|
||||
id: string;
|
||||
reference: string;
|
||||
nom: string;
|
||||
categorie: string;
|
||||
unite: string;
|
||||
stockTheorique: number;
|
||||
stockReel: number;
|
||||
ecart: number;
|
||||
valeurStock: number;
|
||||
emplacement: string;
|
||||
dateInventaire: Date;
|
||||
auditeur: string;
|
||||
statut: 'OK' | 'ECART' | 'MANQUANT' | 'EXCEDENT';
|
||||
}
|
||||
|
||||
interface InventorySession {
|
||||
id: string;
|
||||
nom: string;
|
||||
dateDebut: Date;
|
||||
dateFin?: Date;
|
||||
statut: 'PLANIFIE' | 'EN_COURS' | 'TERMINE';
|
||||
emplacements: string[];
|
||||
categories: string[];
|
||||
auditeur: string;
|
||||
commentaires: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Page Inventaire Matériel BTP Express
|
||||
* Gestion complète stocks, maintenances et alertes matériel
|
||||
*/
|
||||
const InventairePage = () => {
|
||||
const [inventoryItems, setInventoryItems] = useState<InventoryItem[]>([]);
|
||||
const [sessions, setSessions] = useState<InventorySession[]>([]);
|
||||
const [selectedItems, setSelectedItems] = useState<InventoryItem[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [inventaireDialog, setInventaireDialog] = useState(false);
|
||||
const [maintenanceDialog, setMaintenanceDialog] = useState(false);
|
||||
const [alertesDialog, setAlertesDialog] = useState(false);
|
||||
const [selectedMateriel, setSelectedMateriel] = useState<any>(null);
|
||||
const [nouvelleQuantite, setNouvelleQuantite] = useState<number>(0);
|
||||
const [commentaireInventaire, setCommentaireInventaire] = useState('');
|
||||
const [dateMaintenance, setDateMaintenance] = useState<Date | null>(null);
|
||||
const [typeMaintenance, setTypeMaintenance] = useState('');
|
||||
const [descriptionMaintenance, setDescriptionMaintenance] = useState('');
|
||||
const [alertes, setAlertes] = useState<any[]>([]);
|
||||
const [metriques, setMetriques] = useState<any>({});
|
||||
const [globalFilter, setGlobalFilter] = useState('');
|
||||
const [sessionDialog, setSessionDialog] = useState(false);
|
||||
const [inventoryDialog, setInventoryDialog] = useState(false);
|
||||
const [selectedSession, setSelectedSession] = useState<InventorySession | null>(null);
|
||||
const [showStats, setShowStats] = useState(false);
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const toast = useRef<Toast>(null);
|
||||
const dt = useRef<DataTable<InventoryItem[]>>(null);
|
||||
|
||||
const [newSession, setNewSession] = useState<InventorySession>({
|
||||
id: '',
|
||||
nom: '',
|
||||
dateDebut: new Date(),
|
||||
statut: 'PLANIFIE',
|
||||
emplacements: [],
|
||||
categories: [],
|
||||
auditeur: '',
|
||||
commentaires: ''
|
||||
});
|
||||
|
||||
const emplacements = [
|
||||
{ label: 'Entrepôt A', value: 'entrepot-a' },
|
||||
{ label: 'Entrepôt B', value: 'entrepot-b' },
|
||||
{ label: 'Magasin', value: 'magasin' },
|
||||
{ label: 'Chantier Mobile', value: 'chantier-mobile' },
|
||||
{ label: 'Bureau', value: 'bureau' }
|
||||
];
|
||||
|
||||
const categories = [
|
||||
{ label: 'Matériaux', value: 'materiaux' },
|
||||
{ label: 'Outillage', value: 'outillage' },
|
||||
{ label: 'Équipement', value: 'equipement' },
|
||||
{ label: 'Consommables', value: 'consommables' },
|
||||
{ label: 'Sécurité', value: 'securite' }
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
loadInventoryData();
|
||||
}, []);
|
||||
|
||||
const loadInventoryData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Données mockées
|
||||
const mockInventoryItems: InventoryItem[] = [
|
||||
{
|
||||
id: '1',
|
||||
reference: 'CIM-001',
|
||||
nom: 'Ciment Portland',
|
||||
categorie: 'materiaux',
|
||||
unite: 'sac',
|
||||
stockTheorique: 150,
|
||||
stockReel: 148,
|
||||
ecart: -2,
|
||||
valeurStock: 1258.00,
|
||||
emplacement: 'entrepot-a',
|
||||
dateInventaire: new Date(),
|
||||
auditeur: 'Jean Dupont',
|
||||
statut: 'ECART'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
reference: 'OUT-002',
|
||||
nom: 'Perceuse électrique',
|
||||
categorie: 'outillage',
|
||||
unite: 'unité',
|
||||
stockTheorique: 5,
|
||||
stockReel: 5,
|
||||
ecart: 0,
|
||||
valeurStock: 600.00,
|
||||
emplacement: 'magasin',
|
||||
dateInventaire: new Date(),
|
||||
auditeur: 'Marie Martin',
|
||||
statut: 'OK'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
reference: 'SEC-003',
|
||||
nom: 'Casque de sécurité',
|
||||
categorie: 'securite',
|
||||
unite: 'unité',
|
||||
stockTheorique: 8,
|
||||
stockReel: 10,
|
||||
ecart: 2,
|
||||
valeurStock: 250.00,
|
||||
emplacement: 'bureau',
|
||||
dateInventaire: new Date(),
|
||||
auditeur: 'Pierre Durand',
|
||||
statut: 'EXCEDENT'
|
||||
}
|
||||
];
|
||||
|
||||
const mockSessions: InventorySession[] = [
|
||||
{
|
||||
id: '1',
|
||||
nom: 'Inventaire Mensuel Janvier 2024',
|
||||
dateDebut: new Date('2024-01-15'),
|
||||
dateFin: new Date('2024-01-17'),
|
||||
statut: 'TERMINE',
|
||||
emplacements: ['entrepot-a', 'magasin'],
|
||||
categories: ['materiaux', 'outillage'],
|
||||
auditeur: 'Équipe Inventaire',
|
||||
commentaires: 'Inventaire mensuel standard'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
nom: 'Inventaire Sécurité',
|
||||
dateDebut: new Date(),
|
||||
statut: 'EN_COURS',
|
||||
emplacements: ['bureau'],
|
||||
categories: ['securite'],
|
||||
auditeur: 'Pierre Durand',
|
||||
commentaires: 'Contrôle EPI obligatoire'
|
||||
}
|
||||
];
|
||||
|
||||
setInventoryItems(mockInventoryItems);
|
||||
setSessions(mockSessions);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du chargement:', error);
|
||||
toast.current?.show({
|
||||
severity: 'error',
|
||||
summary: 'Erreur',
|
||||
detail: 'Impossible de charger les données d\'inventaire',
|
||||
life: 3000
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const openNewSession = () => {
|
||||
setNewSession({
|
||||
id: '',
|
||||
nom: '',
|
||||
dateDebut: new Date(),
|
||||
statut: 'PLANIFIE',
|
||||
emplacements: [],
|
||||
categories: [],
|
||||
auditeur: '',
|
||||
commentaires: ''
|
||||
});
|
||||
setSubmitted(false);
|
||||
setSessionDialog(true);
|
||||
};
|
||||
|
||||
const saveSession = () => {
|
||||
setSubmitted(true);
|
||||
|
||||
if (newSession.nom.trim() && newSession.auditeur.trim()) {
|
||||
const session = {
|
||||
...newSession,
|
||||
id: Date.now().toString()
|
||||
};
|
||||
|
||||
setSessions([...sessions, session]);
|
||||
setSessionDialog(false);
|
||||
|
||||
toast.current?.show({
|
||||
severity: 'success',
|
||||
summary: 'Succès',
|
||||
detail: 'Session d\'inventaire créée',
|
||||
life: 3000
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const startInventory = (session: InventorySession) => {
|
||||
const updatedSessions = sessions.map(s =>
|
||||
s.id === session.id
|
||||
? { ...s, statut: 'EN_COURS' as const }
|
||||
: s
|
||||
);
|
||||
setSessions(updatedSessions);
|
||||
|
||||
toast.current?.show({
|
||||
severity: 'info',
|
||||
summary: 'Inventaire démarré',
|
||||
detail: `Session "${session.nom}" en cours`,
|
||||
life: 3000
|
||||
});
|
||||
};
|
||||
|
||||
const finishInventory = (session: InventorySession) => {
|
||||
const updatedSessions = sessions.map(s =>
|
||||
s.id === session.id
|
||||
? { ...s, statut: 'TERMINE' as const, dateFin: new Date() }
|
||||
: s
|
||||
);
|
||||
setSessions(updatedSessions);
|
||||
|
||||
toast.current?.show({
|
||||
severity: 'success',
|
||||
summary: 'Inventaire terminé',
|
||||
detail: `Session "${session.nom}" terminée`,
|
||||
life: 3000
|
||||
});
|
||||
};
|
||||
|
||||
const exportCSV = () => {
|
||||
dt.current?.exportCSV();
|
||||
};
|
||||
|
||||
const generateReport = () => {
|
||||
const totalItems = inventoryItems.length;
|
||||
const itemsWithIssues = inventoryItems.filter(item => item.statut !== 'OK').length;
|
||||
const totalValue = inventoryItems.reduce((sum, item) => sum + item.valeurStock, 0);
|
||||
|
||||
toast.current?.show({
|
||||
severity: 'info',
|
||||
summary: 'Rapport généré',
|
||||
detail: `${totalItems} articles, ${itemsWithIssues} écarts, Valeur: ${totalValue.toFixed(2)}€`,
|
||||
life: 5000
|
||||
});
|
||||
setShowStats(true);
|
||||
};
|
||||
|
||||
const leftToolbarTemplate = () => {
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
label="Nouvelle Session"
|
||||
icon="pi pi-plus"
|
||||
severity="success"
|
||||
onClick={openNewSession}
|
||||
/>
|
||||
<Button
|
||||
label="Rapport"
|
||||
icon="pi pi-chart-bar"
|
||||
severity="info"
|
||||
onClick={generateReport}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const rightToolbarTemplate = () => {
|
||||
return (
|
||||
<Button
|
||||
label="Exporter"
|
||||
icon="pi pi-upload"
|
||||
severity="help"
|
||||
onClick={exportCSV}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const statusBodyTemplate = (rowData: InventoryItem) => {
|
||||
let severity: "success" | "warning" | "danger" | "info" = 'success';
|
||||
let label = rowData.statut;
|
||||
|
||||
switch (rowData.statut) {
|
||||
case 'OK':
|
||||
severity = 'success';
|
||||
label = 'OK';
|
||||
break;
|
||||
case 'ECART':
|
||||
severity = 'warning';
|
||||
label = 'Écart';
|
||||
break;
|
||||
case 'MANQUANT':
|
||||
severity = 'danger';
|
||||
label = 'Manquant';
|
||||
break;
|
||||
case 'EXCEDENT':
|
||||
severity = 'info';
|
||||
label = 'Excédent';
|
||||
break;
|
||||
}
|
||||
|
||||
return <Tag value={label} severity={severity} />;
|
||||
};
|
||||
|
||||
const ecartBodyTemplate = (rowData: InventoryItem) => {
|
||||
const ecart = rowData.ecart;
|
||||
const className = ecart === 0 ? '' : ecart > 0 ? 'text-green-600' : 'text-red-600';
|
||||
return <span className={className}>{ecart > 0 ? '+' : ''}{ecart}</span>;
|
||||
};
|
||||
|
||||
const valeurBodyTemplate = (rowData: InventoryItem) => {
|
||||
return new Intl.NumberFormat('fr-FR', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
}).format(rowData.valeurStock);
|
||||
};
|
||||
|
||||
const categorieBodyTemplate = (rowData: InventoryItem) => {
|
||||
const categorie = categories.find(c => c.value === rowData.categorie);
|
||||
return categorie ? categorie.label : rowData.categorie;
|
||||
};
|
||||
|
||||
const emplacementBodyTemplate = (rowData: InventoryItem) => {
|
||||
const emplacement = emplacements.find(e => e.value === rowData.emplacement);
|
||||
return emplacement ? emplacement.label : rowData.emplacement;
|
||||
};
|
||||
|
||||
const sessionActionTemplate = (rowData: InventorySession) => {
|
||||
return (
|
||||
<ActionButtonGroup>
|
||||
{rowData.statut === 'PLANIFIE' && (
|
||||
<ActionButton
|
||||
icon="pi pi-play"
|
||||
color="success"
|
||||
tooltip="Démarrer"
|
||||
onClick={() => startInventory(rowData)}
|
||||
/>
|
||||
)}
|
||||
{rowData.statut === 'EN_COURS' && (
|
||||
<ActionButton
|
||||
icon="pi pi-check"
|
||||
color="info"
|
||||
tooltip="Terminer"
|
||||
onClick={() => finishInventory(rowData)}
|
||||
/>
|
||||
)}
|
||||
<ViewButton
|
||||
tooltip="Voir détails"
|
||||
onClick={() => setSelectedSession(rowData)}
|
||||
/>
|
||||
</ActionButtonGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const sessionStatusTemplate = (rowData: InventorySession) => {
|
||||
let severity: "success" | "warning" | "danger" | "info" = 'info';
|
||||
let label = rowData.statut;
|
||||
|
||||
switch (rowData.statut) {
|
||||
case 'PLANIFIE':
|
||||
severity = 'info';
|
||||
label = 'Planifié';
|
||||
break;
|
||||
case 'EN_COURS':
|
||||
severity = 'warning';
|
||||
label = 'En cours';
|
||||
break;
|
||||
case 'TERMINE':
|
||||
severity = 'success';
|
||||
label = 'Terminé';
|
||||
break;
|
||||
}
|
||||
|
||||
return <Tag value={label} severity={severity} />;
|
||||
};
|
||||
|
||||
const header = (
|
||||
<div className="flex flex-column md:flex-row md:justify-content-between md:align-items-center">
|
||||
<h5 className="m-0">Inventaire du Stock</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 sessionDialogFooter = (
|
||||
<div className="flex justify-content-end gap-2">
|
||||
<Button label="Annuler" icon="pi pi-times" text onClick={() => setSessionDialog(false)} />
|
||||
<Button label="Créer" icon="pi pi-check" onClick={saveSession} />
|
||||
</div>
|
||||
);
|
||||
|
||||
// Statistiques pour le graphique
|
||||
const getStatsData = () => {
|
||||
const statsOK = inventoryItems.filter(item => item.statut === 'OK').length;
|
||||
const statsEcart = inventoryItems.filter(item => item.statut === 'ECART').length;
|
||||
const statsManquant = inventoryItems.filter(item => item.statut === 'MANQUANT').length;
|
||||
const statsExcedent = inventoryItems.filter(item => item.statut === 'EXCEDENT').length;
|
||||
|
||||
return {
|
||||
labels: ['OK', 'Écart', 'Manquant', 'Excédent'],
|
||||
datasets: [
|
||||
{
|
||||
data: [statsOK, statsEcart, statsManquant, statsExcedent],
|
||||
backgroundColor: ['#10B981', '#F59E0B', '#EF4444', '#3B82F6'],
|
||||
hoverBackgroundColor: ['#059669', '#D97706', '#DC2626', '#2563EB']
|
||||
}
|
||||
]
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid">
|
||||
<div className="col-12">
|
||||
<Card>
|
||||
<Toast ref={toast} />
|
||||
|
||||
{/* Sessions d'inventaire */}
|
||||
<div className="mb-4">
|
||||
<h6>Sessions d'inventaire</h6>
|
||||
<DataTable
|
||||
value={sessions}
|
||||
responsiveLayout="scroll"
|
||||
className="mb-4"
|
||||
>
|
||||
<Column field="nom" header="Nom de la session" />
|
||||
<Column field="dateDebut" header="Date début" body={(rowData) => rowData.dateDebut.toLocaleDateString()} />
|
||||
<Column field="dateFin" header="Date fin" body={(rowData) => rowData.dateFin?.toLocaleDateString() || '-'} />
|
||||
<Column field="statut" header="Statut" body={sessionStatusTemplate} />
|
||||
<Column field="auditeur" header="Auditeur" />
|
||||
<Column body={sessionActionTemplate} headerStyle={{ minWidth: '8rem' }} />
|
||||
</DataTable>
|
||||
</div>
|
||||
|
||||
<Toolbar className="mb-4" left={leftToolbarTemplate} right={rightToolbarTemplate} />
|
||||
|
||||
{showStats && (
|
||||
<Card className="mb-4" title="Statistiques d'inventaire">
|
||||
<div className="grid">
|
||||
<div className="col-12 md:col-6">
|
||||
<Chart type="doughnut" data={getStatsData()} />
|
||||
</div>
|
||||
<div className="col-12 md:col-6">
|
||||
<div className="grid">
|
||||
<div className="col-6">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-semibold text-green-600">
|
||||
{inventoryItems.filter(item => item.statut === 'OK').length}
|
||||
</div>
|
||||
<div className="text-sm">Articles OK</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-6">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-semibold text-orange-600">
|
||||
{inventoryItems.filter(item => item.statut !== 'OK').length}
|
||||
</div>
|
||||
<div className="text-sm">Avec écarts</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-6">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-semibold text-blue-600">
|
||||
{inventoryItems.reduce((sum, item) => sum + item.valeurStock, 0).toFixed(0)}€
|
||||
</div>
|
||||
<div className="text-sm">Valeur totale</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-6">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-semibold text-purple-600">
|
||||
{Math.round((inventoryItems.filter(item => item.statut === 'OK').length / inventoryItems.length) * 100)}%
|
||||
</div>
|
||||
<div className="text-sm">Conformité</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<DataTable
|
||||
ref={dt}
|
||||
value={inventoryItems}
|
||||
selection={selectedItems}
|
||||
onSelectionChange={(e) => setSelectedItems(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} articles"
|
||||
globalFilter={globalFilter}
|
||||
emptyMessage="Aucun article trouvé."
|
||||
header={header}
|
||||
responsiveLayout="scroll"
|
||||
loading={loading}
|
||||
>
|
||||
<Column selectionMode="multiple" headerStyle={{ width: '4rem' }} />
|
||||
<Column field="reference" header="Référence" sortable />
|
||||
<Column field="nom" header="Nom" sortable />
|
||||
<Column field="categorie" header="Catégorie" body={categorieBodyTemplate} sortable />
|
||||
<Column field="stockTheorique" header="Stock théorique" sortable />
|
||||
<Column field="stockReel" header="Stock réel" sortable />
|
||||
<Column field="ecart" header="Écart" body={ecartBodyTemplate} sortable />
|
||||
<Column field="valeurStock" header="Valeur" body={valeurBodyTemplate} sortable />
|
||||
<Column field="emplacement" header="Emplacement" body={emplacementBodyTemplate} sortable />
|
||||
<Column field="statut" header="Statut" body={statusBodyTemplate} sortable />
|
||||
<Column field="auditeur" header="Auditeur" sortable />
|
||||
</DataTable>
|
||||
|
||||
{/* Dialog pour nouvelle session */}
|
||||
<Dialog
|
||||
visible={sessionDialog}
|
||||
style={{ width: '600px' }}
|
||||
header="Nouvelle session d'inventaire"
|
||||
modal
|
||||
className="p-fluid"
|
||||
footer={sessionDialogFooter}
|
||||
onHide={() => setSessionDialog(false)}
|
||||
>
|
||||
<div className="formgrid grid">
|
||||
<div className="field col-12">
|
||||
<label htmlFor="nom">Nom de la session</label>
|
||||
<InputText
|
||||
id="nom"
|
||||
value={newSession.nom}
|
||||
onChange={(e) => setNewSession({...newSession, nom: e.target.value})}
|
||||
required
|
||||
className={submitted && !newSession.nom ? 'p-invalid' : ''}
|
||||
/>
|
||||
{submitted && !newSession.nom && <small className="p-invalid">Le nom est requis.</small>}
|
||||
</div>
|
||||
|
||||
<div className="field col-12 md:col-6">
|
||||
<label htmlFor="dateDebut">Date de début</label>
|
||||
<Calendar
|
||||
id="dateDebut"
|
||||
value={newSession.dateDebut}
|
||||
onChange={(e) => setNewSession({...newSession, dateDebut: e.value || new Date()})}
|
||||
showIcon
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field col-12 md:col-6">
|
||||
<label htmlFor="auditeur">Auditeur</label>
|
||||
<InputText
|
||||
id="auditeur"
|
||||
value={newSession.auditeur}
|
||||
onChange={(e) => setNewSession({...newSession, auditeur: e.target.value})}
|
||||
required
|
||||
className={submitted && !newSession.auditeur ? 'p-invalid' : ''}
|
||||
/>
|
||||
{submitted && !newSession.auditeur && <small className="p-invalid">L'auditeur est requis.</small>}
|
||||
</div>
|
||||
|
||||
<div className="field col-12">
|
||||
<label>Emplacements à inventorier</label>
|
||||
<div className="grid">
|
||||
{emplacements.map((emplacement) => (
|
||||
<div key={emplacement.value} className="col-6">
|
||||
<Checkbox
|
||||
inputId={emplacement.value}
|
||||
value={emplacement.value}
|
||||
onChange={(e) => {
|
||||
const selected = [...newSession.emplacements];
|
||||
if (e.checked) {
|
||||
selected.push(e.value);
|
||||
} else {
|
||||
const index = selected.indexOf(e.value);
|
||||
if (index > -1) {
|
||||
selected.splice(index, 1);
|
||||
}
|
||||
}
|
||||
setNewSession({...newSession, emplacements: selected});
|
||||
}}
|
||||
checked={newSession.emplacements.includes(emplacement.value)}
|
||||
/>
|
||||
<label htmlFor={emplacement.value} className="ml-2">{emplacement.label}</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="field col-12">
|
||||
<label>Catégories à inventorier</label>
|
||||
<div className="grid">
|
||||
{categories.map((categorie) => (
|
||||
<div key={categorie.value} className="col-6">
|
||||
<Checkbox
|
||||
inputId={categorie.value}
|
||||
value={categorie.value}
|
||||
onChange={(e) => {
|
||||
const selected = [...newSession.categories];
|
||||
if (e.checked) {
|
||||
selected.push(e.value);
|
||||
} else {
|
||||
const index = selected.indexOf(e.value);
|
||||
if (index > -1) {
|
||||
selected.splice(index, 1);
|
||||
}
|
||||
}
|
||||
setNewSession({...newSession, categories: selected});
|
||||
}}
|
||||
checked={newSession.categories.includes(categorie.value)}
|
||||
/>
|
||||
<label htmlFor={categorie.value} className="ml-2">{categorie.label}</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InventairePage;
|
||||
Reference in New Issue
Block a user