import { useCollection, useIdentity } from '@deplixo/sdk';
import { Icon } from './components/icons.jsx';
import { TIPI_LAVORO, STATI, statusClass, formatDate, formatPrice, initials, MESI_SHORT } from './components/constants.js';
import WorkOrderDialog from './components/WorkOrderDialog.jsx';
import CustomerDialog from './components/CustomerDialog.jsx';
function App() {
const { user } = useIdentity();
const { items: allLavori, add: addLavoro, update: updateLavoro, remove: removeLavoro } = useCollection('lavori');
const { items: allClienti, add: addCliente, update: updateCliente } = useCollection('clienti');
const lavori = (allLavori || []).filter((x) => x.author?.id === user?.id);
const clienti = (allClienti || []).filter((x) => x.author?.id === user?.id);
const [view, setView] = useState('dashboard'); // dashboard | lavori | clienti | detail
const [selectedId, setSelectedId] = useState(null);
const [editingWO, setEditingWO] = useState(null);
const [editingClient, setEditingClient] = useState(null);
const [toast, setToast] = useState('');
// filters for lavori list
const [filterStato, setFilterStato] = useState('TUTTI');
const [filterTipo, setFilterTipo] = useState('TUTTI');
const [search, setSearch] = useState('');
const woDialogRef = useRef(null);
const custDialogRef = useRef(null);
const showToast = (msg) => {
setToast(msg);
setTimeout(() => setToast(''), 2200);
};
const openNewWO = () => {
setEditingWO(null);
setTimeout(() => woDialogRef.current?.showModal(), 0);
};
const openEditWO = (l) => {
setEditingWO({ id: l.id, ...(l.value || {}) });
setTimeout(() => woDialogRef.current?.showModal(), 0);
};
const closeWODialog = () => {
woDialogRef.current?.close();
setEditingWO(null);
};
const openNewClient = () => {
setEditingClient(null);
setTimeout(() => custDialogRef.current?.showModal(), 0);
};
const openEditClient = (c) => {
setEditingClient({ id: c.id, ...(c.value || {}) });
setTimeout(() => custDialogRef.current?.showModal(), 0);
};
const closeCustDialog = () => {
custDialogRef.current?.close();
setEditingClient(null);
};
// Save work order — also upserts a customer entry
const saveWO = async (data) => {
// upsert customer
const key = `${(data.nome||'').toLowerCase().trim()}|${(data.cognome||'').toLowerCase().trim()}|${(data.azienda||'').toLowerCase().trim()}`;
const existing = clienti.find((c) => {
const v = c.value || {};
const k = `${(v.nome||'').toLowerCase().trim()}|${(v.cognome||'').toLowerCase().trim()}|${(v.azienda||'').toLowerCase().trim()}`;
return k === key && key !== '||';
});
if (!existing && (data.nome || data.cognome || data.azienda)) {
await addCliente({
nome: data.nome, cognome: data.cognome, azienda: data.azienda,
telefono: data.telefono, email: data.email, indirizzo: '', note: '',
});
}
if (data.id) {
const { id, ...payload } = data;
await updateLavoro(id, payload);
showToast('Lavoro aggiornato');
} else {
await addLavoro(data);
showToast('Lavoro creato');
}
closeWODialog();
};
const saveClient = async (data) => {
if (data.id) {
const { id, ...payload } = data;
await updateCliente(id, payload);
showToast('Cliente aggiornato');
} else {
await addCliente(data);
showToast('Cliente creato');
}
closeCustDialog();
};
const deleteWO = async (id) => {
await removeLavoro(id);
showToast('Lavoro eliminato');
setView('lavori');
setSelectedId(null);
};
const changeStato = async (l, s) => {
await updateLavoro(l.id, { ...(l.value || {}), stato: s });
showToast(`Stato: ${s}`);
};
// stats
const inCorso = lavori.filter((l) => l.value?.stato === 'IN CORSO').length;
const daFare = lavori.filter((l) => l.value?.stato === 'DA FARE').length;
const completati = lavori.filter((l) => l.value?.stato === 'COMPLETATO' || l.value?.stato === 'CONSEGNATO').length;
const inAttesa = lavori.filter((l) => l.value?.stato === 'IN ATTESA').length;
const upcoming = lavori
.filter((l) => l.value?.dataPrevista && !['COMPLETATO', 'CONSEGNATO'].includes(l.value?.stato))
.sort((a, b) => new Date(a.value.dataPrevista) - new Date(b.value.dataPrevista))
.slice(0, 5);
// filtered work list
const filtered = lavori
.filter((l) => filterStato === 'TUTTI' ? true : l.value?.stato === filterStato)
.filter((l) => filterTipo === 'TUTTI' ? true : l.value?.tipoLavoro === filterTipo)
.filter((l) => {
if (!search.trim()) return true;
const s = search.toLowerCase();
const v = l.value || {};
return [v.nome, v.cognome, v.azienda, v.targa, v.marca, v.modello, v.veicolo]
.filter(Boolean).some((f) => f.toLowerCase().includes(s));
})
.sort((a, b) => {
const da = a.value?.dataPrevista || a.createdAt;
const db = b.value?.dataPrevista || b.createdAt;
return new Date(db) - new Date(da);
});
const selected = lavori.find((l) => l.id === selectedId);
const goToDetail = (id) => {
setSelectedId(id);
setView('detail');
};
const customerLavori = (nome, cognome, azienda) => {
const key = `${(nome||'').toLowerCase().trim()}|${(cognome||'').toLowerCase().trim()}|${(azienda||'').toLowerCase().trim()}`;
return lavori.filter((l) => {
const v = l.value || {};
const k = `${(v.nome||'').toLowerCase().trim()}|${(v.cognome||'').toLowerCase().trim()}|${(v.azienda||'').toLowerCase().trim()}`;
return k === key;
});
};
return (
LH
LavoriHub
{user?.name || 'Ospite'}
{view === 'dashboard' && (
)}
{view === 'lavori' && (
)}
{view === 'clienti' && (
)}
{view === 'detail' && selected && (
setView('lavori')}
onEdit={() => openEditWO(selected)}
onDelete={() => deleteWO(selected.id)}
onChangeStato={(s) => changeStato(selected, s)}
/>
)}
{view === 'detail' && !selected && (
Lavoro non trovato
)}
{toast && {toast}
}
);
}
/* ------------ Dashboard ------------ */
function DashboardView({ inCorso, daFare, completati, inAttesa, upcoming, onOpenLavoro, onNewLavoro }) {
return (
Dashboard
Prossimi lavori
{upcoming.length === 0 ? (
Nessun lavoro programmato
Crea un nuovo lavoro per iniziare
) : (
{upcoming.map((l) => {
const v = l.value || {};
const d = v.dataPrevista ? new Date(v.dataPrevista) : null;
return (
);
})}
)}
);
}
/* ------------ Lavori list ------------ */
function LavoriView({ lavori, totalCount, filterStato, setFilterStato, filterTipo, setFilterTipo, search, setSearch, onOpen, onNew }) {
return (
Lavori
setSearch(e.target.value)}
data-testid="lavori-search-input"
aria-label="Cerca lavori"
/>
{['TUTTI', ...STATI].map((s) => (
))}
{['TUTTI', ...TIPI_LAVORO].map((t) => (
))}
{lavori.length === 0 ? (
{totalCount === 0 ? 'Nessun lavoro' : 'Nessun risultato'}
{totalCount === 0 ? 'Tocca + per crearne uno' : 'Prova a modificare i filtri'}
) : (
{lavori.map((l) => {
const v = l.value || {};
return (
);
})}
)}
);
}
/* ------------ Clienti ------------ */
function ClientiView({ clienti, onNew, onEdit, customerLavori, onOpenLavoro }) {
const [expandedId, setExpandedId] = useState(null);
const [q, setQ] = useState('');
const filtered = clienti.filter((c) => {
if (!q.trim()) return true;
const v = c.value || {};
const s = q.toLowerCase();
return [v.nome, v.cognome, v.azienda, v.telefono, v.email].filter(Boolean).some((f) => f.toLowerCase().includes(s));
});
return (
Clienti
setQ(e.target.value)}
data-testid="clienti-search-input"
aria-label="Cerca clienti"
/>
{filtered.length === 0 ? (
{clienti.length === 0 ? 'Nessun cliente' : 'Nessun risultato'}
{clienti.length === 0 ? 'Tocca + per aggiungere un cliente' : ''}
) : (
{filtered.map((c) => {
const v = c.value || {};
const woList = customerLavori(v.nome, v.cognome, v.azienda);
const isOpen = expandedId === c.id;
return (
{isOpen && (
{v.telefono &&
Telefono{v.telefono}
}
{v.email &&
Email{v.email}
}
{v.indirizzo &&
Indirizzo{v.indirizzo}
}
{v.azienda &&
Azienda{v.azienda}
}
{v.note && (
<>
Note
{v.note}
>
)}
Lavori ({woList.length})
{woList.length === 0 ? (
Nessun lavoro registrato
) : (
{woList.map((l) => {
const lv = l.value || {};
return (
);
})}
)}
)}
);
})}
)}
);
}
/* ------------ Detail ------------ */
function DetailView({ lavoro, onBack, onEdit, onDelete, onChangeStato }) {
const v = lavoro.value || {};
const [confirmDel, setConfirmDel] = useState(false);
const margine = (v.prezzo && v.costoMateriale)
? (Number(v.prezzo) - Number(v.costoMateriale))
: null;
return (
<>
{[v.nome, v.cognome].filter(Boolean).join(' ') || v.azienda || 'Lavoro'}
{v.tipoLavoro || 'SERVIZIO'}
{v.stato || 'DA FARE'}
Cambia stato
{STATI.map((s) => (
))}
Cliente
Nome{[v.nome, v.cognome].filter(Boolean).join(' ') || '—'}
{v.azienda &&
Azienda{v.azienda}
}
{v.telefono && (
)}
{v.email && (
)}
Veicolo
{v.veicolo &&
Tipo{v.veicolo}
}
Marca{v.marca || '—'}
Modello{v.modello || '—'}
Targa{v.targa || '—'}
Date
Data prevista{formatDate(v.dataPrevista)}
Data consegna{formatDate(v.dataConsegna)}
Prezzo & Costi
Prezzo{formatPrice(v.prezzo)}
Costo materiale{formatPrice(v.costoMateriale)}
{margine !== null && (
Margine
= 0 ? 'var(--success)' : 'var(--danger)' }}>{formatPrice(margine)}
)}
{v.note && (
)}
{(v.foto || []).length > 0 && (
)}
{(v.allegati || []).length > 0 && (
Allegati ({v.allegati.length})
{v.allegati.map((a) => (
))}
)}
{!confirmDel ? (
) : (
)}
>
);
}
ReactDOM.createRoot(document.getElementById('root')).render();