270 lines
7.9 KiB
React
270 lines
7.9 KiB
React
import { useState, useEffect, useCallback } from 'react';
|
||
import { useNavigate } from 'react-router-dom';
|
||
import { useAuth } from '../context/AuthContext';
|
||
import {
|
||
fetchFormexMaterials,
|
||
fetchFormexCollections,
|
||
deleteFormexMaterial
|
||
} from '../api/client';
|
||
import MaterialModal from '../components/MaterialModal';
|
||
import styles from './Materials.module.css';
|
||
|
||
export default function Materials() {
|
||
const { isAdmin } = useAuth();
|
||
const navigate = useNavigate();
|
||
|
||
// Data state
|
||
const [materials, setMaterials] = useState([]);
|
||
const [collections, setCollections] = useState([]);
|
||
const [pagination, setPagination] = useState({ page: 1, limit: 25, total: 0, totalPages: 0 });
|
||
const [loading, setLoading] = useState(true);
|
||
const [error, setError] = useState('');
|
||
|
||
// Filter state
|
||
const [search, setSearch] = useState('');
|
||
const [collection, setCollection] = useState('');
|
||
const [searchInput, setSearchInput] = useState('');
|
||
|
||
// Modal state
|
||
const [modalOpen, setModalOpen] = useState(false);
|
||
const [editingMaterial, setEditingMaterial] = useState(null);
|
||
|
||
// Delete confirmation
|
||
const [deletingId, setDeletingId] = useState(null);
|
||
|
||
const loadMaterials = useCallback(async (page = 1) => {
|
||
setLoading(true);
|
||
setError('');
|
||
try {
|
||
const result = await fetchFormexMaterials({ search, collection, page, limit: 25 });
|
||
setMaterials(result.materials);
|
||
setPagination(result.pagination);
|
||
} catch (err) {
|
||
setError(err.message);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, [search, collection]);
|
||
|
||
useEffect(() => {
|
||
if (!isAdmin) {
|
||
navigate('/login?next=/materiaux', { replace: true });
|
||
return;
|
||
}
|
||
// Load collections for filter dropdown
|
||
fetchFormexCollections()
|
||
.then(setCollections)
|
||
.catch(() => {});
|
||
// Load materials
|
||
loadMaterials(1);
|
||
}, [isAdmin, navigate, loadMaterials]);
|
||
|
||
const handleSearch = (e) => {
|
||
e.preventDefault();
|
||
setSearch(searchInput);
|
||
};
|
||
|
||
const handleCollectionChange = (e) => {
|
||
setCollection(e.target.value);
|
||
};
|
||
|
||
// Reload when filters change
|
||
useEffect(() => {
|
||
loadMaterials(1);
|
||
}, [search, collection, loadMaterials]);
|
||
|
||
const handlePageChange = (newPage) => {
|
||
loadMaterials(newPage);
|
||
};
|
||
|
||
const handleCreate = () => {
|
||
setEditingMaterial(null);
|
||
setModalOpen(true);
|
||
};
|
||
|
||
const handleEdit = (material) => {
|
||
setEditingMaterial(material);
|
||
setModalOpen(true);
|
||
};
|
||
|
||
const handleModalClose = () => {
|
||
setModalOpen(false);
|
||
setEditingMaterial(null);
|
||
};
|
||
|
||
const handleModalSave = () => {
|
||
setModalOpen(false);
|
||
setEditingMaterial(null);
|
||
loadMaterials(pagination.page);
|
||
};
|
||
|
||
const handleDeleteClick = (id) => {
|
||
setDeletingId(id);
|
||
};
|
||
|
||
const handleDeleteConfirm = async () => {
|
||
if (!deletingId) return;
|
||
try {
|
||
await deleteFormexMaterial(deletingId);
|
||
setDeletingId(null);
|
||
loadMaterials(pagination.page);
|
||
} catch (err) {
|
||
setError(err.message);
|
||
setDeletingId(null);
|
||
}
|
||
};
|
||
|
||
const handleDeleteCancel = () => {
|
||
setDeletingId(null);
|
||
};
|
||
|
||
if (!isAdmin) return null;
|
||
|
||
return (
|
||
<div className={styles.page}>
|
||
<div className={styles.header}>
|
||
<h1>Matériaux Formex</h1>
|
||
<button type="button" onClick={handleCreate} className={styles.createBtn}>
|
||
+ Nouveau matériau
|
||
</button>
|
||
</div>
|
||
|
||
{/* Filters */}
|
||
<div className={styles.filters}>
|
||
<form onSubmit={handleSearch} className={styles.searchForm}>
|
||
<input
|
||
type="text"
|
||
placeholder="Rechercher par nom ou code…"
|
||
value={searchInput}
|
||
onChange={(e) => setSearchInput(e.target.value)}
|
||
className={styles.searchInput}
|
||
/>
|
||
<button type="submit" className={styles.searchBtn}>Rechercher</button>
|
||
</form>
|
||
<select
|
||
value={collection}
|
||
onChange={handleCollectionChange}
|
||
className={styles.collectionSelect}
|
||
>
|
||
<option value="">Toutes les collections</option>
|
||
{collections.map((c) => (
|
||
<option key={c} value={c}>{c}</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
|
||
{error && <p className={styles.error}>{error}</p>}
|
||
|
||
{loading ? (
|
||
<p className={styles.muted}>Chargement…</p>
|
||
) : (
|
||
<>
|
||
<div className={styles.tableWrap}>
|
||
<table className={styles.table}>
|
||
<thead>
|
||
<tr>
|
||
<th>Code</th>
|
||
<th>Nom</th>
|
||
<th>Collection</th>
|
||
<th>Dimensions (mm)</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{materials.length === 0 ? (
|
||
<tr>
|
||
<td colSpan={5} className={styles.empty}>
|
||
Aucun matériau trouvé.
|
||
</td>
|
||
</tr>
|
||
) : (
|
||
materials.map((m) => (
|
||
<tr key={m.id}>
|
||
<td className={styles.code}>{m.formex_code}</td>
|
||
<td>{m.name}</td>
|
||
<td>{m.collection}</td>
|
||
<td>
|
||
{m.width_mm && m.height_mm && m.thickness_mm
|
||
? `${m.width_mm} × ${m.height_mm} × ${m.thickness_mm}`
|
||
: '—'}
|
||
</td>
|
||
<td className={styles.actions}>
|
||
<button
|
||
type="button"
|
||
onClick={() => handleEdit(m)}
|
||
className={styles.editBtn}
|
||
>
|
||
Modifier
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={() => handleDeleteClick(m.id)}
|
||
className={styles.deleteBtn}
|
||
>
|
||
Supprimer
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
))
|
||
)}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
{/* Pagination */}
|
||
{pagination.totalPages > 1 && (
|
||
<div className={styles.pagination}>
|
||
<button
|
||
type="button"
|
||
onClick={() => handlePageChange(pagination.page - 1)}
|
||
disabled={pagination.page <= 1}
|
||
className={styles.pageBtn}
|
||
>
|
||
Précédent
|
||
</button>
|
||
<span className={styles.pageInfo}>
|
||
Page {pagination.page} sur {pagination.totalPages} ({pagination.total} résultats)
|
||
</span>
|
||
<button
|
||
type="button"
|
||
onClick={() => handlePageChange(pagination.page + 1)}
|
||
disabled={pagination.page >= pagination.totalPages}
|
||
className={styles.pageBtn}
|
||
>
|
||
Suivant
|
||
</button>
|
||
</div>
|
||
)}
|
||
</>
|
||
)}
|
||
|
||
{/* Delete confirmation dialog */}
|
||
{deletingId && (
|
||
<div className={styles.overlay}>
|
||
<div className={styles.dialog}>
|
||
<p>Voulez-vous vraiment supprimer ce matériau ?</p>
|
||
<div className={styles.dialogActions}>
|
||
<button type="button" onClick={handleDeleteCancel} className={styles.cancelBtn}>
|
||
Annuler
|
||
</button>
|
||
<button type="button" onClick={handleDeleteConfirm} className={styles.confirmDeleteBtn}>
|
||
Supprimer
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Create/Edit Modal */}
|
||
{modalOpen && (
|
||
<MaterialModal
|
||
material={editingMaterial}
|
||
collections={collections}
|
||
onClose={handleModalClose}
|
||
onSave={handleModalSave}
|
||
/>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|