Initial commit
This commit is contained in:
773
app/(main)/stock/page.tsx
Normal file
773
app/(main)/stock/page.tsx
Normal file
@@ -0,0 +1,773 @@
|
||||
'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;
|
||||
Reference in New Issue
Block a user