773 lines
30 KiB
TypeScript
773 lines
30 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 { InputTextarea } from 'primereact/inputtextarea';
|
|
import { confirmDialog } from 'primereact/confirmdialog';
|
|
import { ConfirmDialog } from 'primereact/confirmdialog';
|
|
|
|
interface StockItem {
|
|
id: string;
|
|
nom: string;
|
|
reference: string;
|
|
description: string;
|
|
categorie: string;
|
|
unite: string;
|
|
quantiteStock: number;
|
|
seuilAlerte: number;
|
|
prixUnitaire: number;
|
|
fournisseur: string;
|
|
emplacement: string;
|
|
dateAjout: Date;
|
|
dateModification: Date;
|
|
actif: boolean;
|
|
}
|
|
|
|
interface StockMovement {
|
|
id: string;
|
|
articleId: string;
|
|
type: 'ENTREE' | 'SORTIE';
|
|
quantite: number;
|
|
motif: string;
|
|
chantier?: string;
|
|
utilisateur: string;
|
|
date: Date;
|
|
}
|
|
|
|
const StockPage = () => {
|
|
const [stockItems, setStockItems] = useState<StockItem[]>([]);
|
|
const [selectedItems, setSelectedItems] = useState<StockItem[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [globalFilter, setGlobalFilter] = useState('');
|
|
const [itemDialog, setItemDialog] = useState(false);
|
|
const [movementDialog, setMovementDialog] = useState(false);
|
|
const [deleteItemDialog, setDeleteItemDialog] = useState(false);
|
|
const [item, setItem] = useState<StockItem>({
|
|
id: '',
|
|
nom: '',
|
|
reference: '',
|
|
description: '',
|
|
categorie: '',
|
|
unite: 'unité',
|
|
quantiteStock: 0,
|
|
seuilAlerte: 10,
|
|
prixUnitaire: 0,
|
|
fournisseur: '',
|
|
emplacement: '',
|
|
dateAjout: new Date(),
|
|
dateModification: new Date(),
|
|
actif: true
|
|
});
|
|
const [movement, setMovement] = useState<StockMovement>({
|
|
id: '',
|
|
articleId: '',
|
|
type: 'ENTREE',
|
|
quantite: 0,
|
|
motif: '',
|
|
chantier: '',
|
|
utilisateur: 'Admin',
|
|
date: new Date()
|
|
});
|
|
const [submitted, setSubmitted] = useState(false);
|
|
const toast = useRef<Toast>(null);
|
|
const dt = useRef<DataTable<StockItem[]>>(null);
|
|
|
|
const categories = [
|
|
{ label: 'Matériaux', value: 'materiaux' },
|
|
{ label: 'Outillage', value: 'outillage' },
|
|
{ label: 'Équipement', value: 'equipement' },
|
|
{ label: 'Consommables', value: 'consommables' },
|
|
{ label: 'Sécurité', value: 'securite' }
|
|
];
|
|
|
|
const unites = [
|
|
{ label: 'Unité', value: 'unité' },
|
|
{ label: 'Mètre', value: 'm' },
|
|
{ label: 'Mètre carré', value: 'm²' },
|
|
{ label: 'Mètre cube', value: 'm³' },
|
|
{ label: 'Kilogramme', value: 'kg' },
|
|
{ label: 'Tonne', value: 't' },
|
|
{ label: 'Litre', value: 'l' },
|
|
{ label: 'Boîte', value: 'boîte' },
|
|
{ label: 'Sac', value: 'sac' },
|
|
{ label: 'Palette', value: 'palette' }
|
|
];
|
|
|
|
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 typesMovement = [
|
|
{ label: 'Entrée', value: 'ENTREE' },
|
|
{ label: 'Sortie', value: 'SORTIE' }
|
|
];
|
|
|
|
useEffect(() => {
|
|
// Simuler le chargement des données
|
|
loadStockItems();
|
|
}, []);
|
|
|
|
const loadStockItems = async () => {
|
|
try {
|
|
setLoading(true);
|
|
// Simulation de données
|
|
const mockData: StockItem[] = [
|
|
{
|
|
id: '1',
|
|
nom: 'Ciment Portland',
|
|
reference: 'CIM-001',
|
|
description: 'Ciment Portland 32.5 pour béton',
|
|
categorie: 'materiaux',
|
|
unite: 'sac',
|
|
quantiteStock: 150,
|
|
seuilAlerte: 20,
|
|
prixUnitaire: 8.50,
|
|
fournisseur: 'Lafarge',
|
|
emplacement: 'entrepot-a',
|
|
dateAjout: new Date('2024-01-15'),
|
|
dateModification: new Date('2024-01-15'),
|
|
actif: true
|
|
},
|
|
{
|
|
id: '2',
|
|
nom: 'Perceuse électrique',
|
|
reference: 'OUT-002',
|
|
description: 'Perceuse électrique 850W',
|
|
categorie: 'outillage',
|
|
unite: 'unité',
|
|
quantiteStock: 5,
|
|
seuilAlerte: 2,
|
|
prixUnitaire: 120.00,
|
|
fournisseur: 'Bosch',
|
|
emplacement: 'magasin',
|
|
dateAjout: new Date('2024-02-01'),
|
|
dateModification: new Date('2024-02-01'),
|
|
actif: true
|
|
},
|
|
{
|
|
id: '3',
|
|
nom: 'Casque de sécurité',
|
|
reference: 'SEC-003',
|
|
description: 'Casque de sécurité blanc',
|
|
categorie: 'securite',
|
|
unite: 'unité',
|
|
quantiteStock: 8,
|
|
seuilAlerte: 5,
|
|
prixUnitaire: 25.00,
|
|
fournisseur: 'Protecta',
|
|
emplacement: 'bureau',
|
|
dateAjout: new Date('2024-03-01'),
|
|
dateModification: new Date('2024-03-01'),
|
|
actif: true
|
|
}
|
|
];
|
|
|
|
setStockItems(mockData);
|
|
} catch (error) {
|
|
console.error('Erreur lors du chargement du stock:', error);
|
|
toast.current?.show({
|
|
severity: 'error',
|
|
summary: 'Erreur',
|
|
detail: 'Impossible de charger les articles',
|
|
life: 3000
|
|
});
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const openNew = () => {
|
|
setItem({
|
|
id: '',
|
|
nom: '',
|
|
reference: '',
|
|
description: '',
|
|
categorie: '',
|
|
unite: 'unité',
|
|
quantiteStock: 0,
|
|
seuilAlerte: 10,
|
|
prixUnitaire: 0,
|
|
fournisseur: '',
|
|
emplacement: '',
|
|
dateAjout: new Date(),
|
|
dateModification: new Date(),
|
|
actif: true
|
|
});
|
|
setSubmitted(false);
|
|
setItemDialog(true);
|
|
};
|
|
|
|
const openMovement = (item: StockItem) => {
|
|
setMovement({
|
|
id: '',
|
|
articleId: item.id,
|
|
type: 'ENTREE',
|
|
quantite: 0,
|
|
motif: '',
|
|
chantier: '',
|
|
utilisateur: 'Admin',
|
|
date: new Date()
|
|
});
|
|
setSubmitted(false);
|
|
setMovementDialog(true);
|
|
};
|
|
|
|
const hideDialog = () => {
|
|
setSubmitted(false);
|
|
setItemDialog(false);
|
|
};
|
|
|
|
const hideMovementDialog = () => {
|
|
setSubmitted(false);
|
|
setMovementDialog(false);
|
|
};
|
|
|
|
const hideDeleteItemDialog = () => {
|
|
setDeleteItemDialog(false);
|
|
};
|
|
|
|
const saveItem = () => {
|
|
setSubmitted(true);
|
|
|
|
if (item.nom.trim() && item.reference.trim() && item.categorie) {
|
|
let updatedItems = [...stockItems];
|
|
|
|
if (item.id) {
|
|
// Mise à jour
|
|
const index = stockItems.findIndex(i => i.id === item.id);
|
|
updatedItems[index] = { ...item, dateModification: new Date() };
|
|
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Succès',
|
|
detail: 'Article mis à jour',
|
|
life: 3000
|
|
});
|
|
} else {
|
|
// Création
|
|
const newItem = {
|
|
...item,
|
|
id: Date.now().toString(),
|
|
dateAjout: new Date(),
|
|
dateModification: new Date()
|
|
};
|
|
updatedItems.push(newItem);
|
|
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Succès',
|
|
detail: 'Article créé',
|
|
life: 3000
|
|
});
|
|
}
|
|
|
|
setStockItems(updatedItems);
|
|
setItemDialog(false);
|
|
setItem({
|
|
id: '',
|
|
nom: '',
|
|
reference: '',
|
|
description: '',
|
|
categorie: '',
|
|
unite: 'unité',
|
|
quantiteStock: 0,
|
|
seuilAlerte: 10,
|
|
prixUnitaire: 0,
|
|
fournisseur: '',
|
|
emplacement: '',
|
|
dateAjout: new Date(),
|
|
dateModification: new Date(),
|
|
actif: true
|
|
});
|
|
}
|
|
};
|
|
|
|
const saveMovement = () => {
|
|
setSubmitted(true);
|
|
|
|
if (movement.quantite > 0 && movement.motif.trim()) {
|
|
const targetItem = stockItems.find(i => i.id === movement.articleId);
|
|
if (targetItem) {
|
|
const newQuantity = movement.type === 'ENTREE'
|
|
? targetItem.quantiteStock + movement.quantite
|
|
: targetItem.quantiteStock - movement.quantite;
|
|
|
|
if (newQuantity < 0) {
|
|
toast.current?.show({
|
|
severity: 'error',
|
|
summary: 'Erreur',
|
|
detail: 'Stock insuffisant',
|
|
life: 3000
|
|
});
|
|
return;
|
|
}
|
|
|
|
const updatedItems = stockItems.map(item =>
|
|
item.id === movement.articleId
|
|
? { ...item, quantiteStock: newQuantity, dateModification: new Date() }
|
|
: item
|
|
);
|
|
|
|
setStockItems(updatedItems);
|
|
setMovementDialog(false);
|
|
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Succès',
|
|
detail: `Mouvement de stock ${movement.type === 'ENTREE' ? 'entrée' : 'sortie'} enregistré`,
|
|
life: 3000
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
const editItem = (item: StockItem) => {
|
|
setItem({ ...item });
|
|
setItemDialog(true);
|
|
};
|
|
|
|
const confirmDeleteItem = (item: StockItem) => {
|
|
setItem(item);
|
|
setDeleteItemDialog(true);
|
|
};
|
|
|
|
const deleteItem = () => {
|
|
const updatedItems = stockItems.filter(i => i.id !== item.id);
|
|
setStockItems(updatedItems);
|
|
setDeleteItemDialog(false);
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Succès',
|
|
detail: 'Article supprimé',
|
|
life: 3000
|
|
});
|
|
};
|
|
|
|
const exportCSV = () => {
|
|
dt.current?.exportCSV();
|
|
};
|
|
|
|
const onInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, name: string) => {
|
|
const val = (e.target && e.target.value) || '';
|
|
let _item = { ...item };
|
|
(_item as any)[name] = val;
|
|
setItem(_item);
|
|
};
|
|
|
|
const onNumberChange = (e: any, name: string) => {
|
|
let _item = { ...item };
|
|
(_item as any)[name] = e.value;
|
|
setItem(_item);
|
|
};
|
|
|
|
const onDropdownChange = (e: any, name: string) => {
|
|
let _item = { ...item };
|
|
(_item as any)[name] = e.value;
|
|
setItem(_item);
|
|
};
|
|
|
|
const onMovementInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, name: string) => {
|
|
const val = (e.target && e.target.value) || '';
|
|
let _movement = { ...movement };
|
|
(_movement as any)[name] = val;
|
|
setMovement(_movement);
|
|
};
|
|
|
|
const onMovementNumberChange = (e: any, name: string) => {
|
|
let _movement = { ...movement };
|
|
(_movement as any)[name] = e.value;
|
|
setMovement(_movement);
|
|
};
|
|
|
|
const onMovementDropdownChange = (e: any, name: string) => {
|
|
let _movement = { ...movement };
|
|
(_movement as any)[name] = e.value;
|
|
setMovement(_movement);
|
|
};
|
|
|
|
const leftToolbarTemplate = () => {
|
|
return (
|
|
<div className="flex flex-wrap gap-2">
|
|
<Button
|
|
label="Nouveau"
|
|
icon="pi pi-plus"
|
|
severity="success"
|
|
onClick={openNew}
|
|
/>
|
|
<Button
|
|
label="Alerte Stock"
|
|
icon="pi pi-exclamation-triangle"
|
|
severity="warning"
|
|
onClick={() => {
|
|
const alertes = stockItems.filter(item => item.quantiteStock <= item.seuilAlerte);
|
|
if (alertes.length > 0) {
|
|
toast.current?.show({
|
|
severity: 'warn',
|
|
summary: 'Alertes Stock',
|
|
detail: `${alertes.length} article(s) en rupture ou sous le seuil`,
|
|
life: 5000
|
|
});
|
|
} else {
|
|
toast.current?.show({
|
|
severity: 'info',
|
|
summary: 'Stock',
|
|
detail: 'Aucune alerte stock',
|
|
life: 3000
|
|
});
|
|
}
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const rightToolbarTemplate = () => {
|
|
return (
|
|
<Button
|
|
label="Exporter"
|
|
icon="pi pi-upload"
|
|
severity="help"
|
|
onClick={exportCSV}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const actionBodyTemplate = (rowData: StockItem) => {
|
|
return (
|
|
<div className="flex gap-2">
|
|
<Button
|
|
icon="pi pi-pencil"
|
|
rounded
|
|
severity="success"
|
|
onClick={() => editItem(rowData)}
|
|
/>
|
|
<Button
|
|
icon="pi pi-arrows-h"
|
|
rounded
|
|
severity="info"
|
|
onClick={() => openMovement(rowData)}
|
|
/>
|
|
<Button
|
|
icon="pi pi-trash"
|
|
rounded
|
|
severity="warning"
|
|
onClick={() => confirmDeleteItem(rowData)}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const stockStatusBodyTemplate = (rowData: StockItem) => {
|
|
if (rowData.quantiteStock === 0) {
|
|
return <Tag value="Rupture" severity="danger" />;
|
|
} else if (rowData.quantiteStock <= rowData.seuilAlerte) {
|
|
return <Tag value="Alerte" severity="warning" />;
|
|
} else {
|
|
return <Tag value="Disponible" severity="success" />;
|
|
}
|
|
};
|
|
|
|
const categorieBodyTemplate = (rowData: StockItem) => {
|
|
const categorie = categories.find(c => c.value === rowData.categorie);
|
|
return categorie ? categorie.label : rowData.categorie;
|
|
};
|
|
|
|
const emplacementBodyTemplate = (rowData: StockItem) => {
|
|
const emplacement = emplacements.find(e => e.value === rowData.emplacement);
|
|
return emplacement ? emplacement.label : rowData.emplacement;
|
|
};
|
|
|
|
const prixBodyTemplate = (rowData: StockItem) => {
|
|
return new Intl.NumberFormat('fr-FR', {
|
|
style: 'currency',
|
|
currency: 'EUR'
|
|
}).format(rowData.prixUnitaire);
|
|
};
|
|
|
|
const header = (
|
|
<div className="flex flex-column md:flex-row md:justify-content-between md:align-items-center">
|
|
<h5 className="m-0">Gestion 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 itemDialogFooter = (
|
|
<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={saveItem} />
|
|
</div>
|
|
);
|
|
|
|
const movementDialogFooter = (
|
|
<div className="flex justify-content-end gap-2">
|
|
<Button label="Annuler" icon="pi pi-times" text onClick={hideMovementDialog} />
|
|
<Button label="Enregistrer" icon="pi pi-check" onClick={saveMovement} />
|
|
</div>
|
|
);
|
|
|
|
const deleteItemDialogFooter = (
|
|
<div className="flex justify-content-end gap-2">
|
|
<Button label="Non" icon="pi pi-times" text onClick={hideDeleteItemDialog} />
|
|
<Button label="Oui" icon="pi pi-check" onClick={deleteItem} />
|
|
</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={stockItems}
|
|
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 headerStyle={{ minWidth: '10rem' }} />
|
|
<Column field="nom" header="Nom" sortable headerStyle={{ minWidth: '12rem' }} />
|
|
<Column field="categorie" header="Catégorie" body={categorieBodyTemplate} sortable headerStyle={{ minWidth: '10rem' }} />
|
|
<Column field="quantiteStock" header="Stock" sortable headerStyle={{ minWidth: '8rem' }} />
|
|
<Column field="unite" header="Unité" sortable headerStyle={{ minWidth: '8rem' }} />
|
|
<Column field="status" header="Statut" body={stockStatusBodyTemplate} headerStyle={{ minWidth: '10rem' }} />
|
|
<Column field="prixUnitaire" header="Prix" body={prixBodyTemplate} sortable headerStyle={{ minWidth: '10rem' }} />
|
|
<Column field="emplacement" header="Emplacement" body={emplacementBodyTemplate} sortable headerStyle={{ minWidth: '10rem' }} />
|
|
<Column body={actionBodyTemplate} headerStyle={{ minWidth: '12rem' }} />
|
|
</DataTable>
|
|
|
|
<Dialog
|
|
visible={itemDialog}
|
|
style={{ width: '600px' }}
|
|
header="Détails de l'article"
|
|
modal
|
|
className="p-fluid"
|
|
footer={itemDialogFooter}
|
|
onHide={hideDialog}
|
|
>
|
|
<div className="formgrid grid">
|
|
<div className="field col-12 md:col-6">
|
|
<label htmlFor="nom">Nom</label>
|
|
<InputText
|
|
id="nom"
|
|
value={item.nom}
|
|
onChange={(e) => onInputChange(e, 'nom')}
|
|
required
|
|
className={submitted && !item.nom ? 'p-invalid' : ''}
|
|
/>
|
|
{submitted && !item.nom && <small className="p-invalid">Le nom est requis.</small>}
|
|
</div>
|
|
|
|
<div className="field col-12 md:col-6">
|
|
<label htmlFor="reference">Référence</label>
|
|
<InputText
|
|
id="reference"
|
|
value={item.reference}
|
|
onChange={(e) => onInputChange(e, 'reference')}
|
|
required
|
|
className={submitted && !item.reference ? 'p-invalid' : ''}
|
|
/>
|
|
{submitted && !item.reference && <small className="p-invalid">La référence est requise.</small>}
|
|
</div>
|
|
|
|
<div className="field col-12">
|
|
<label htmlFor="description">Description</label>
|
|
<InputTextarea
|
|
id="description"
|
|
value={item.description}
|
|
onChange={(e) => onInputChange(e, 'description')}
|
|
rows={3}
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12 md:col-6">
|
|
<label htmlFor="categorie">Catégorie</label>
|
|
<Dropdown
|
|
id="categorie"
|
|
value={item.categorie}
|
|
options={categories}
|
|
onChange={(e) => onDropdownChange(e, 'categorie')}
|
|
placeholder="Sélectionnez une catégorie"
|
|
required
|
|
className={submitted && !item.categorie ? 'p-invalid' : ''}
|
|
/>
|
|
{submitted && !item.categorie && <small className="p-invalid">La catégorie est requise.</small>}
|
|
</div>
|
|
|
|
<div className="field col-12 md:col-6">
|
|
<label htmlFor="unite">Unité</label>
|
|
<Dropdown
|
|
id="unite"
|
|
value={item.unite}
|
|
options={unites}
|
|
onChange={(e) => onDropdownChange(e, 'unite')}
|
|
placeholder="Sélectionnez une unité"
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12 md:col-4">
|
|
<label htmlFor="quantiteStock">Stock actuel</label>
|
|
<InputNumber
|
|
id="quantiteStock"
|
|
value={item.quantiteStock}
|
|
onValueChange={(e) => onNumberChange(e, 'quantiteStock')}
|
|
min={0}
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12 md:col-4">
|
|
<label htmlFor="seuilAlerte">Seuil d'alerte</label>
|
|
<InputNumber
|
|
id="seuilAlerte"
|
|
value={item.seuilAlerte}
|
|
onValueChange={(e) => onNumberChange(e, 'seuilAlerte')}
|
|
min={0}
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12 md:col-4">
|
|
<label htmlFor="prixUnitaire">Prix unitaire (€)</label>
|
|
<InputNumber
|
|
id="prixUnitaire"
|
|
value={item.prixUnitaire}
|
|
onValueChange={(e) => onNumberChange(e, 'prixUnitaire')}
|
|
mode="currency"
|
|
currency="EUR"
|
|
locale="fr-FR"
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12 md:col-6">
|
|
<label htmlFor="fournisseur">Fournisseur</label>
|
|
<InputText
|
|
id="fournisseur"
|
|
value={item.fournisseur}
|
|
onChange={(e) => onInputChange(e, 'fournisseur')}
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12 md:col-6">
|
|
<label htmlFor="emplacement">Emplacement</label>
|
|
<Dropdown
|
|
id="emplacement"
|
|
value={item.emplacement}
|
|
options={emplacements}
|
|
onChange={(e) => onDropdownChange(e, 'emplacement')}
|
|
placeholder="Sélectionnez un emplacement"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</Dialog>
|
|
|
|
<Dialog
|
|
visible={movementDialog}
|
|
style={{ width: '450px' }}
|
|
header="Mouvement de stock"
|
|
modal
|
|
className="p-fluid"
|
|
footer={movementDialogFooter}
|
|
onHide={hideMovementDialog}
|
|
>
|
|
<div className="formgrid grid">
|
|
<div className="field col-12">
|
|
<label htmlFor="type">Type de mouvement</label>
|
|
<Dropdown
|
|
id="type"
|
|
value={movement.type}
|
|
options={typesMovement}
|
|
onChange={(e) => onMovementDropdownChange(e, 'type')}
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12">
|
|
<label htmlFor="quantite">Quantité</label>
|
|
<InputNumber
|
|
id="quantite"
|
|
value={movement.quantite}
|
|
onValueChange={(e) => onMovementNumberChange(e, 'quantite')}
|
|
min={1}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12">
|
|
<label htmlFor="motif">Motif</label>
|
|
<InputTextarea
|
|
id="motif"
|
|
value={movement.motif}
|
|
onChange={(e) => onMovementInputChange(e, 'motif')}
|
|
rows={3}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12">
|
|
<label htmlFor="chantier">Chantier (optionnel)</label>
|
|
<InputText
|
|
id="chantier"
|
|
value={movement.chantier}
|
|
onChange={(e) => onMovementInputChange(e, 'chantier')}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</Dialog>
|
|
|
|
<Dialog
|
|
visible={deleteItemDialog}
|
|
style={{ width: '450px' }}
|
|
header="Confirmer"
|
|
modal
|
|
footer={deleteItemDialogFooter}
|
|
onHide={hideDeleteItemDialog}
|
|
>
|
|
<div className="flex align-items-center justify-content-center">
|
|
<i className="pi pi-exclamation-triangle mr-3" style={{ fontSize: '2rem' }} />
|
|
{item && (
|
|
<span>
|
|
Êtes-vous sûr de vouloir supprimer l'article <b>{item.nom}</b> ?
|
|
</span>
|
|
)}
|
|
</div>
|
|
</Dialog>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default StockPage; |