magricambi/src/components/ArticlesList.tsx

149 lines
5.3 KiB
TypeScript
Raw Normal View History

"use client";
import { useState, useEffect, useCallback } from "react";
import type { Article, ArticleFamily } from "@/src/types/article";
import type { Stabilimento } from "@/src/types/stabilimento";
import ArticleCard from "./ArticleCard";
import ArticleModal from "./ArticleModal";
interface ArticlesListProps {
stabilimento: Stabilimento;
}
export default function ArticlesList({ stabilimento }: ArticlesListProps) {
const [articles, setArticles] = useState<Article[]>([]);
const [families, setFamilies] = useState<ArticleFamily[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [search, setSearch] = useState("");
const [showModal, setShowModal] = useState(false);
const [editingArticle, setEditingArticle] = useState<Article | null>(null);
const fetchArticles = useCallback(async () => {
setLoading(true);
setError(null);
try {
const params = new URLSearchParams();
params.set("stabilimentoId", stabilimento.id.toString());
if (search) params.set("search", search);
const res = await fetch(`/api/articoli?${params}`);
if (!res.ok) throw new Error("Errore nel caricamento");
const data = await res.json();
setArticles(data.articles);
setFamilies(data.families);
} catch (err) {
setError(err instanceof Error ? err.message : "Errore sconosciuto");
} finally {
setLoading(false);
}
}, [stabilimento.id, search]);
useEffect(() => {
fetchArticles();
}, [fetchArticles]);
const handleEdit = (article: Article) => {
setEditingArticle(article);
setShowModal(true);
};
const handleDelete = (deleted: Article) => {
setArticles((prev) => prev.filter((a) => a.id !== deleted.id));
};
const handleSave = (saved: Article) => {
if (editingArticle) {
setArticles((prev) => prev.map((a) => (a.id === saved.id ? saved : a)));
} else {
setArticles((prev) => [saved, ...prev]);
}
setShowModal(false);
setEditingArticle(null);
};
const sottoScorta = articles.filter((a) => a.quantita < a.quantita_minima).length;
return (
<div className="flex flex-col h-full">
{/* Header */}
<div className="flex items-center justify-between gap-4 mb-4">
<div>
<h2 className="text-lg font-semibold text-white">Articoli</h2>
<p className="text-xs text-zinc-500">
{articles.length} articoli {sottoScorta > 0 && <span className="text-red-400">({sottoScorta} sotto scorta)</span>}
</p>
</div>
<button
onClick={() => { setEditingArticle(null); setShowModal(true); }}
className="p-2 rounded-lg bg-blue-600 text-white hover:bg-blue-500 transition-colors"
title="Nuovo articolo"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
</button>
</div>
{/* Ricerca */}
<div className="relative mb-4">
<svg className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-zinc-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Cerca articoli..."
className="w-full pl-9 pr-3 py-2 text-sm rounded-lg bg-zinc-900 border border-zinc-800 text-white placeholder-zinc-500 focus:outline-none focus:border-blue-500"
/>
</div>
{/* Lista articoli */}
<div className="flex-1 overflow-y-auto space-y-2 pr-1">
{loading ? (
<div className="flex items-center justify-center py-8 text-zinc-500 text-sm">
<svg className="w-5 h-5 animate-spin mr-2" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg>
Caricamento...
</div>
) : error ? (
<div className="text-center py-8">
<p className="text-red-400 text-sm mb-2">{error}</p>
<button onClick={fetchArticles} className="text-xs text-blue-400 hover:underline">Riprova</button>
</div>
) : articles.length === 0 ? (
<div className="text-center py-8 text-zinc-500 text-sm">
{search ? "Nessun articolo trovato" : "Nessun articolo in questo stabilimento"}
</div>
) : (
articles.map((article) => (
<ArticleCard
key={article.id}
article={article}
onEdit={handleEdit}
onDelete={handleDelete}
/>
))
)}
</div>
{/* Modal */}
{showModal && (
<ArticleModal
article={editingArticle}
families={families}
stabilimentoId={stabilimento.id}
onClose={() => { setShowModal(false); setEditingArticle(null); }}
onSave={handleSave}
/>
)}
</div>
);
}