From be228bca5c0e7f4f46a48eafaed1956f2edb3c08 Mon Sep 17 00:00:00 2001 From: AV77web Date: Thu, 28 May 2026 22:21:52 +0200 Subject: [PATCH] correzione ArticleCard, creazione MacchineTree --- src/app/(main)/articoli/page.tsx | 67 ++++++++++---- src/app/(main)/page.tsx | 114 ++++++++++++------------ src/components/ArticleCard.tsx | 134 +++++++--------------------- src/components/ArticlesList.tsx | 148 +++++++++++++++++++++++++++++++ src/components/MacchineTree.tsx | 40 +++++++++ 5 files changed, 326 insertions(+), 177 deletions(-) create mode 100644 src/components/ArticlesList.tsx create mode 100644 src/components/MacchineTree.tsx diff --git a/src/app/(main)/articoli/page.tsx b/src/app/(main)/articoli/page.tsx index 3c28e03..7f91365 100644 --- a/src/app/(main)/articoli/page.tsx +++ b/src/app/(main)/articoli/page.tsx @@ -1,21 +1,56 @@ -export default function ArticoliPage() { - return ( -
-
-
-

- Articoli -

-

- Catalogo ricambi e componenti -

-
+"use client"; -
-

- Contenuto della pagina articoli in arrivo... -

+import { useState } from "react"; +import type { Stabilimento } from "@/src/types/stabilimento"; +import StabilimentoSelector from "@/src/components/StabilimentoSelector"; +import ArticlesGrid from "@/src/components/ArticlesGrid"; + +export default function ArticoliPage() { + const [selectedStabilimento, setSelectedStabilimento] = useState(null); + + return ( +
+
+ {/* Header */} +
+

Articoli

+

Catalogo ricambi e componenti

+ + {/* Selezione stabilimento */} +
+
+

Stabilimento:

+ {selectedStabilimento && ( + + )} +
+ {selectedStabilimento ? ( +

{selectedStabilimento.descrizione}

+ ) : ( + + )} +
+ + {/* Contenuto */} + {selectedStabilimento ? ( + + ) : ( +
+ + + +

Seleziona uno stabilimento per visualizzare gli articoli

+
+ )}
); diff --git a/src/app/(main)/page.tsx b/src/app/(main)/page.tsx index 7d9c203..b7b7ece 100644 --- a/src/app/(main)/page.tsx +++ b/src/app/(main)/page.tsx @@ -3,72 +3,70 @@ import { useState } from "react"; import type { Stabilimento } from "@/src/types/stabilimento"; import StabilimentoSelector from "@/src/components/StabilimentoSelector"; -import ArticlesGrid from "@/src/components/ArticlesGrid"; +import ArticlesList from "@/src/components/ArticlesList"; +import MacchineTree from "@/src/components/MacchineTree"; export default function Home() { const [selectedStabilimento, setSelectedStabilimento] = useState(null); return ( -
-
- {/* Header */} -
-

- MagRicambi -

-

- Gestione magazzino ricambi -

-
- - {/* Selezione stabilimento */} -
-

- Seleziona stabilimento -

- -
- - {/* Contenuto principale */} - {selectedStabilimento ? ( -
-
-

- Articoli - {selectedStabilimento.descrizione} -

- +
+
+ {/* Header + Selezione stabilimento */} +
+
+
+

MagRicambi

+

Gestione magazzino ricambi

+
+ {selectedStabilimento && ( +
+ Stabilimento: + {selectedStabilimento.descrizione} + +
+ )} +
+ + {!selectedStabilimento && ( +
+

Seleziona uno stabilimento per iniziare:

+ +
+ )} +
+ + {/* Contenuto principale: split 50/50 */} + {selectedStabilimento ? ( +
+ {/* Colonna sinistra: Articoli */} +
+ +
+ + {/* Colonna destra: Macchine */} +
+
-
) : ( -
- - - -

- Nessuno stabilimento selezionato -

-

- Seleziona uno stabilimento dalla lista sopra per visualizzare e gestire gli articoli del magazzino. -

+
+
+ + + +

Seleziona uno stabilimento per visualizzare articoli e macchine

+
)}
diff --git a/src/components/ArticleCard.tsx b/src/components/ArticleCard.tsx index 126b3c9..fa5b15f 100644 --- a/src/components/ArticleCard.tsx +++ b/src/components/ArticleCard.tsx @@ -10,30 +10,21 @@ interface ArticleCardProps { } export default function ArticleCard({ article, onEdit, onDelete }: ArticleCardProps) { - const [showActions, setShowActions] = useState(false); const [deleting, setDeleting] = useState(false); const isSottoScorta = article.quantita < article.quantita_minima; const disponibile = article.quantita - article.assegnata_macchine; const handleDelete = async () => { - if (!confirm(`Sei sicuro di voler eliminare l'articolo "${article.descrizione}"?`)) { - return; - } + if (!confirm(`Eliminare "${article.descrizione}"?`)) return; setDeleting(true); try { - const res = await fetch(`/api/articoli/${article.id}`, { - method: "DELETE", - }); - - if (!res.ok) { - throw new Error("Errore durante l'eliminazione"); - } - + const res = await fetch(`/api/articoli/${article.id}`, { method: "DELETE" }); + if (!res.ok) throw new Error("Errore durante l'eliminazione"); onDelete(article); } catch (error) { - alert(error instanceof Error ? error.message : "Errore durante l'eliminazione"); + alert(error instanceof Error ? error.message : "Errore"); } finally { setDeleting(false); } @@ -42,98 +33,44 @@ export default function ArticleCard({ article, onEdit, onDelete }: ArticleCardPr return (
setShowActions(true)} - onMouseLeave={() => setShowActions(false)} > - {/* Header */} -
-
-
-

- {article.descrizione} -

-

{article.codice}

-
- - {/* Badge famiglia */} + {/* Indicatore sotto scorta */} + {isSottoScorta && ( +
+ )} + + {/* Info articolo */} +
+
+ + {article.descrizione} + {article.famiglia && ( - + {article.famiglia} )}
-
- - {/* Body */} -
- {/* Quantità */} -
-
-

Quantità

-

- {article.quantita} - {isSottoScorta && ( - - (min: {article.quantita_minima}) - - )} -

-
-
-

Disponibile

-

- {disponibile} -

-
+
+ {article.codice} + + Qta: {article.quantita} + {isSottoScorta && (min {article.quantita_minima})} + + + Disp: {disponibile} +
- - {/* Assegnate a macchine */} - {article.assegnata_macchine > 0 && ( -
- - - - - {article.assegnata_macchine} assegnate a macchine - -
- )} - - {/* Prezzo */} - {article.prezzo > 0 && ( -
- Prezzo - - €{article.prezzo.toFixed(2)} - -
- )} - - {/* Barcode */} - {article.barcode && ( -
- - - - {article.barcode} -
- )}
- {/* Action buttons (hover) */} -
+ {/* Azioni */} +
- - {/* Sotto scorta indicator */} - {isSottoScorta && ( -
- - SOTTO SCORTA - -
- )}
); } diff --git a/src/components/ArticlesList.tsx b/src/components/ArticlesList.tsx new file mode 100644 index 0000000..9d769c1 --- /dev/null +++ b/src/components/ArticlesList.tsx @@ -0,0 +1,148 @@ +"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([]); + const [families, setFamilies] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const [search, setSearch] = useState(""); + const [showModal, setShowModal] = useState(false); + const [editingArticle, setEditingArticle] = useState
(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 ( +
+ {/* Header */} +
+
+

Articoli

+

+ {articles.length} articoli {sottoScorta > 0 && ({sottoScorta} sotto scorta)} +

+
+ +
+ + {/* Ricerca */} +
+ + + + 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" + /> +
+ + {/* Lista articoli */} +
+ {loading ? ( +
+ + + + + Caricamento... +
+ ) : error ? ( +
+

{error}

+ +
+ ) : articles.length === 0 ? ( +
+ {search ? "Nessun articolo trovato" : "Nessun articolo in questo stabilimento"} +
+ ) : ( + articles.map((article) => ( + + )) + )} +
+ + {/* Modal */} + {showModal && ( + { setShowModal(false); setEditingArticle(null); }} + onSave={handleSave} + /> + )} +
+ ); +} diff --git a/src/components/MacchineTree.tsx b/src/components/MacchineTree.tsx new file mode 100644 index 0000000..a5018f9 --- /dev/null +++ b/src/components/MacchineTree.tsx @@ -0,0 +1,40 @@ +"use client"; + +import type { Stabilimento } from "@/src/types/stabilimento"; + +interface MacchineTreeProps { + stabilimento: Stabilimento; +} + +export default function MacchineTree({ stabilimento }: MacchineTreeProps) { + return ( +
+ {/* Header */} +
+
+

Macchine

+

Albero macchine e parti

+
+ +
+ + {/* Placeholder contenuto */} +
+ + + +

Albero macchine

+

+ Trascina gli articoli qui per assegnarli alle macchine +

+
+
+ ); +}