correta associazione macchine-stabilimento
All checks were successful
Deploy to VPS / build (push) Successful in 39s
Deploy to VPS / deploy (push) Successful in 28s

This commit is contained in:
AV77web 2026-05-30 00:15:39 +02:00
parent 42ad52e344
commit 819cee2bec
3 changed files with 97 additions and 98 deletions

View file

@ -2,11 +2,12 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import type { ArticoloAssegnatoMacchina } from "@/src/types/artMacchineParti"; import type { ArticoloAssegnatoMacchina } from "@/src/types/artMacchineParti";
import type { Stabilimento, Magazzino } from "@/src/types/stabilimento"; import type { Magazzino } from "@/src/types/stabilimento";
interface ArticoliMacchinaModalProps { interface ArticoliMacchinaModalProps {
macchinaId: number; macchinaId: number;
macchinaNome: string; macchinaNome: string;
macchinaStabilimentoId: number | null;
articoli: ArticoloAssegnatoMacchina[]; articoli: ArticoloAssegnatoMacchina[];
onClose: () => void; onClose: () => void;
onArticoloRimosso: () => void; onArticoloRimosso: () => void;
@ -22,6 +23,7 @@ interface RimuoviModalState {
export default function ArticoliMacchinaModal({ export default function ArticoliMacchinaModal({
macchinaId, macchinaId,
macchinaNome, macchinaNome,
macchinaStabilimentoId,
articoli, articoli,
onClose, onClose,
onArticoloRimosso, onArticoloRimosso,
@ -29,33 +31,28 @@ export default function ArticoliMacchinaModal({
const [rimuoviModal, setRimuoviModal] = useState<RimuoviModalState | null>(null); const [rimuoviModal, setRimuoviModal] = useState<RimuoviModalState | null>(null);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [stabilimenti, setStabilimenti] = useState<Stabilimento[]>([]);
const [magazzini, setMagazzini] = useState<Magazzino[]>([]); const [magazzini, setMagazzini] = useState<Magazzino[]>([]);
const [selectedStabilimento, setSelectedStabilimento] = useState<number | null>(null);
useEffect(() => { useEffect(() => {
async function fetchData() { async function fetchMagazzini() {
if (!macchinaStabilimentoId) return;
try { try {
const [stabRes, magRes] = await Promise.all([ const res = await fetch("/api/magazzini");
fetch("/api/stabilimenti"), if (res.ok) {
fetch("/api/magazzini"), const data = await res.json();
]); // Filtra solo i magazzini dello stabilimento della macchina
if (stabRes.ok && magRes.ok) { const filtered = data.magazzini.filter(
const stabData = await stabRes.json(); (m: Magazzino) => m.id_stabilimento === macchinaStabilimentoId
const magData = await magRes.json(); );
setStabilimenti(stabData.stabilimenti); setMagazzini(filtered);
setMagazzini(magData.magazzini);
} }
} catch (err) { } catch (err) {
console.error("Errore caricamento stabilimenti/magazzini:", err); console.error("Errore caricamento magazzini:", err);
} }
} }
fetchData(); fetchMagazzini();
}, []); }, [macchinaStabilimentoId]);
const filteredMagazzini = selectedStabilimento
? magazzini.filter((m) => m.id_stabilimento === selectedStabilimento)
: magazzini;
const handleRimuoviClick = (articolo: ArticoloAssegnatoMacchina) => { const handleRimuoviClick = (articolo: ArticoloAssegnatoMacchina) => {
setRimuoviModal({ setRimuoviModal({
@ -64,7 +61,6 @@ export default function ArticoliMacchinaModal({
opzione: "carico", opzione: "carico",
idMagazzino: null, idMagazzino: null,
}); });
setSelectedStabilimento(null);
setError(null); setError(null);
}; };
@ -255,54 +251,39 @@ export default function ArticoliMacchinaModal({
<div className="space-y-3 p-4 rounded-lg bg-teal-900/20 border border-teal-700/30"> <div className="space-y-3 p-4 rounded-lg bg-teal-900/20 border border-teal-700/30">
<p className="text-sm font-medium text-teal-300">Seleziona il magazzino di destinazione</p> <p className="text-sm font-medium text-teal-300">Seleziona il magazzino di destinazione</p>
<div> {magazzini.length === 0 ? (
<label className="block text-xs text-zinc-400 mb-1">Stabilimento</label>
<select
value={selectedStabilimento || ""}
onChange={(e) => {
const stabId = e.target.value ? parseInt(e.target.value) : null;
setSelectedStabilimento(stabId);
setRimuoviModal({ ...rimuoviModal, idMagazzino: null });
}}
className="w-full px-3 py-2 bg-zinc-800 border border-zinc-700 rounded-lg text-white text-sm focus:outline-none focus:ring-2 focus:ring-teal-500"
>
<option value="">Seleziona stabilimento...</option>
{stabilimenti.map((stab) => (
<option key={stab.id} value={stab.id}>
{stab.descrizione}
</option>
))}
</select>
</div>
<div>
<label className="block text-xs text-zinc-400 mb-1">Magazzino</label>
<select
value={rimuoviModal.idMagazzino || ""}
onChange={(e) =>
setRimuoviModal({
...rimuoviModal,
idMagazzino: e.target.value ? parseInt(e.target.value) : null,
})
}
disabled={!selectedStabilimento}
className="w-full px-3 py-2 bg-zinc-800 border border-zinc-700 rounded-lg text-white text-sm focus:outline-none focus:ring-2 focus:ring-teal-500 disabled:opacity-50"
>
<option value="">
{selectedStabilimento ? "Seleziona magazzino..." : "Prima seleziona uno stabilimento"}
</option>
{filteredMagazzini.map((mag) => (
<option key={mag.id} value={mag.id}>
{mag.descrizione}
</option>
))}
</select>
</div>
{!rimuoviModal.idMagazzino && (
<p className="text-xs text-yellow-400"> <p className="text-xs text-yellow-400">
Devi selezionare un magazzino per confermare il rientro Nessun magazzino disponibile per lo stabilimento di questa macchina
</p> </p>
) : (
<>
<div>
<label className="block text-xs text-zinc-400 mb-1">Magazzino</label>
<select
value={rimuoviModal.idMagazzino || ""}
onChange={(e) =>
setRimuoviModal({
...rimuoviModal,
idMagazzino: e.target.value ? parseInt(e.target.value) : null,
})
}
className="w-full px-3 py-2 bg-zinc-800 border border-zinc-700 rounded-lg text-white text-sm focus:outline-none focus:ring-2 focus:ring-teal-500"
>
<option value="">Seleziona magazzino...</option>
{magazzini.map((mag) => (
<option key={mag.id} value={mag.id}>
{mag.descrizione}
</option>
))}
</select>
</div>
{!rimuoviModal.idMagazzino && (
<p className="text-xs text-yellow-400">
Devi selezionare un magazzino per confermare il rientro
</p>
)}
</>
)} )}
</div> </div>
)} )}

View file

@ -51,6 +51,7 @@ export default function MacchineTree() {
const [showArticlesModal, setShowArticlesModal] = useState<{ const [showArticlesModal, setShowArticlesModal] = useState<{
macchinaId: number; macchinaId: number;
macchinaNome: string; macchinaNome: string;
macchinaStabilimentoId: number | null;
articoli: ArticoloAssegnatoMacchina[]; articoli: ArticoloAssegnatoMacchina[];
} | null>(null); } | null>(null);
@ -243,6 +244,7 @@ export default function MacchineTree() {
setShowArticlesModal({ setShowArticlesModal({
macchinaId: nodeId, macchinaId: nodeId,
macchinaNome: macchina.nome, macchinaNome: macchina.nome,
macchinaStabilimentoId: macchina.id_stabilimento,
articoli: allArticoli, articoli: allArticoli,
}); });
}; };
@ -301,14 +303,34 @@ export default function MacchineTree() {
}; };
const filteredNodes = useMemo(() => { const filteredNodes = useMemo(() => {
if (!selectedStabilimento) return nodes; if (!selectedStabilimento) return [];
return nodes.filter((n) => n.id_stabilimento === selectedStabilimento.id); return nodes.filter((n) => n.id_stabilimento === selectedStabilimento.id);
}, [nodes, selectedStabilimento]); }, [nodes, selectedStabilimento]);
const rootNodes = filteredNodes.filter((n) => n.id_padre === null); const rootNodes = filteredNodes.filter((n) => n.id_padre === null);
const filteredCount = filteredNodes.length; const filteredCount = filteredNodes.length;
const totalCount = nodes.length;
// Se non c'è stabilimento selezionato, mostra messaggio
if (!selectedStabilimento) {
return (
<div className="flex flex-col h-full">
<div className="mb-4">
<h2 className="text-lg font-semibold text-white">Macchine</h2>
<p className="text-xs text-zinc-500">Seleziona uno stabilimento per visualizzare le macchine</p>
</div>
<div className="flex-1 flex flex-col items-center justify-center text-center p-8 border-2 border-dashed border-zinc-800 rounded-lg">
<svg className="w-12 h-12 text-zinc-700 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
<p className="text-zinc-500 text-sm mb-2">Nessuno stabilimento selezionato</p>
<p className="text-zinc-600 text-xs">
Seleziona uno stabilimento dal menu in alto per visualizzare e gestire le macchine
</p>
</div>
</div>
);
}
return ( return (
<div className="flex flex-col h-full"> <div className="flex flex-col h-full">
@ -317,10 +339,7 @@ export default function MacchineTree() {
<div> <div>
<h2 className="text-lg font-semibold text-white">Macchine</h2> <h2 className="text-lg font-semibold text-white">Macchine</h2>
<p className="text-xs text-zinc-500"> <p className="text-xs text-zinc-500">
{selectedStabilimento {filteredCount} elementi in {selectedStabilimento.descrizione}
? `${filteredCount} di ${totalCount} elementi`
: `${totalCount} elementi nell'albero`
}
</p> </p>
</div> </div>
<button <button
@ -335,13 +354,6 @@ export default function MacchineTree() {
</button> </button>
</div> </div>
{/* Info selezione corrente */}
{selectedStabilimento && (
<div className="mb-3 px-3 py-2 rounded-lg bg-blue-900/20 border border-blue-700/30 text-xs text-blue-300">
Filtro attivo: {selectedStabilimento.descrizione}
</div>
)}
{/* Indicatore clonazione */} {/* Indicatore clonazione */}
{isCloning && ( {isCloning && (
<div className="mb-3 px-3 py-2 rounded-lg bg-blue-900/30 border border-blue-700/50 flex items-center gap-2"> <div className="mb-3 px-3 py-2 rounded-lg bg-blue-900/30 border border-blue-700/50 flex items-center gap-2">
@ -391,16 +403,10 @@ export default function MacchineTree() {
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
</svg> </svg>
<p className="text-zinc-500 text-sm mb-2"> <p className="text-zinc-500 text-sm mb-2">
{selectedStabilimento Nessuna macchina in &quot;{selectedStabilimento.descrizione}&quot;
? `Nessuna macchina in "${selectedStabilimento.descrizione}"`
: "Nessuna macchina presente"
}
</p> </p>
<p className="text-zinc-600 text-xs mb-4"> <p className="text-zinc-600 text-xs mb-4">
{selectedStabilimento Clicca sul pulsante + per aggiungere la prima macchina
? "Seleziona un altro stabilimento o aggiungi una nuova macchina"
: "Clicca sul pulsante + per aggiungere la prima macchina"
}
</p> </p>
<button <button
onClick={handleAddMachine} onClick={handleAddMachine}
@ -483,6 +489,7 @@ export default function MacchineTree() {
<ArticoliMacchinaModal <ArticoliMacchinaModal
macchinaId={showArticlesModal.macchinaId} macchinaId={showArticlesModal.macchinaId}
macchinaNome={showArticlesModal.macchinaNome} macchinaNome={showArticlesModal.macchinaNome}
macchinaStabilimentoId={showArticlesModal.macchinaStabilimentoId}
articoli={showArticlesModal.articoli} articoli={showArticlesModal.articoli}
onClose={() => setShowArticlesModal(null)} onClose={() => setShowArticlesModal(null)}
onArticoloRimosso={() => { onArticoloRimosso={() => {

View file

@ -143,15 +143,9 @@ export async function rimuoviArticoloDaMacchina(
); );
const articolo = articoloRows[0]; const articolo = articoloRows[0];
const [magazzinoRows] = await db.execute<(RowDataPacket & { descrizione: string })[]>(
"SELECT descrizione FROM magazzini WHERE id = :idMagazzino LIMIT 1",
{ idMagazzino }
);
const magazzinoNome = magazzinoRows[0]?.descrizione || "";
await db.execute( await db.execute(
`INSERT INTO movimenti (id_articolo, articolo_codice, articolo_descrizione, quantita, tipo_movimento, causale, utente, id_magazzino, nome_magazzino) `INSERT INTO movimenti (id_articolo, articolo_codice, articolo_descrizione, quantita, tipo_movimento, causale, utente, id_magazzino_destinazione)
VALUES (:id_articolo, :articolo_codice, :articolo_descrizione, :quantita, 1, :causale, :utente, :id_magazzino, :nome_magazzino)`, VALUES (:id_articolo, :articolo_codice, :articolo_descrizione, :quantita, 1, :causale, :utente, :id_magazzino_destinazione)`,
{ {
id_articolo: idart, id_articolo: idart,
articolo_codice: articolo?.codice || "", articolo_codice: articolo?.codice || "",
@ -159,10 +153,27 @@ export async function rimuoviArticoloDaMacchina(
quantita, quantita,
causale: "Rientro da macchina", causale: "Rientro da macchina",
utente: user, utente: user,
id_magazzino: idMagazzino, id_magazzino_destinazione: idMagazzino,
nome_magazzino: magazzinoNome,
} }
); );
// Aggiorna la giacenza nel magazzino
const [existingGiacenza] = await db.execute<(RowDataPacket & { id: number; quantita: number })[]>(
"SELECT id, quantita FROM giacenze WHERE articolo_id = :articoloId AND id_magazzino = :magazzinoId",
{ articoloId: idart, magazzinoId: idMagazzino }
);
if (existingGiacenza.length > 0) {
await db.execute(
"UPDATE giacenze SET quantita = quantita + :quantita WHERE articolo_id = :articoloId AND id_magazzino = :magazzinoId",
{ quantita, articoloId: idart, magazzinoId: idMagazzino }
);
} else {
await db.execute(
"INSERT INTO giacenze (articolo_id, id_magazzino, quantita) VALUES (:articoloId, :magazzinoId, :quantita)",
{ articoloId: idart, magazzinoId: idMagazzino, quantita }
);
}
} }
return { success: true, message: opzione === "carico" ? "Articolo rientrato in magazzino" : "Articolo rimosso" }; return { success: true, message: opzione === "carico" ? "Articolo rientrato in magazzino" : "Articolo rimosso" };