/* global React, Card, Button, Input, Ic, Breadcrumb, VS, LoadingState, ErrorState, GlassDropdown */ // ============================================================ // LIST SCREEN // ============================================================ function ZaleceniaRemontoweScreen() { const [activeId, setActiveId] = React.useState(null); const [list, setList] = React.useState(null); const [facilities, setFacilities] = React.useState([]); const [err, setErr] = React.useState(''); const [busy, setBusy] = React.useState(false); const [facilityFilter, setFacilityFilter] = React.useState(''); const load = React.useCallback(() => { setList(null); setErr(''); VS.zaleceniaProtocols(facilityFilter || undefined) .then((r) => setList(r || [])) .catch((e) => setErr(e.message)); }, [facilityFilter]); React.useEffect(() => { VS.zaleceniaFacilities() .then((r) => setFacilities(r || [])) .catch(() => setFacilities([])); }, []); React.useEffect(() => { load(); }, [load]); async function createNew() { setBusy(true); try { const r = await VS.createZaleceniaProtocol(); setActiveId(r.id); } catch (e) { setErr(e.message); } finally { setBusy(false); } } async function removeRow(id) { if (!confirm('Usunąć ten protokół?')) return; try { await VS.deleteZaleceniaProtocol(id); load(); } catch (e) { alert('Błąd: ' + e.message); } } if (activeId) { return ( { setActiveId(null); load(); }} /> ); } return (
Raporty

Zalecenia remontowe

Protokoły zaleceń remontowych dla urządzeń zakładów BC-1/2/3 i innych.
{/* Filter */}
Zakład setFacilityFilter(v || '')} options={[ { value: '', label: 'Wszystkie zakłady' }, ...(facilities || []).map((f) => ({ value: f.id, label: `${f.code} — ${f.name}`, })), ]} placeholder="Wszystkie zakłady" searchPlaceholder="Szukaj zakładu…" emptyLabel="Brak zakładów." minWidth={280} />
{err && } {!err && !list && } {!err && list && list.length === 0 && (
Brak protokołów
Utwórz nowy protokół aby zacząć.
)} {!err && list && list.length > 0 && (
Zakład
Tytuł
Ważny do
Ost. edycja
Autor
Akcje
{list.map((p, i) => (
setActiveId(p.id)} onMouseEnter={(e) => e.currentTarget.style.background = 'var(--bg-sunken)'} onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'} >
{p.facility_code || '—'}
{p.title || — bez tytułu —}
{p.date_until || '—'}
{p.updated_at ? p.updated_at.slice(0, 16).replace('T', ' ') : '—'}
{p.created_by || '—'}
e.stopPropagation()}>
))}
)}
); } // ============================================================ // EDITOR // ============================================================ function ZaleceniaEditor({ protocolId, facilities, onBack }) { const [protocol, setProtocol] = React.useState(null); const [err, setErr] = React.useState(''); const [users, setUsers] = React.useState(null); // null = loading, [] = none const [dirty, setDirty] = React.useState(false); const [busy, setBusy] = React.useState(false); const [msg, setMsg] = React.useState(''); // bust counters per device idx — centralny map, inkrementowany po uploadzie/usunięciu // żeby odświeżył się bez cache const [trendBustMap, setTrendBustMap] = React.useState({}); // Normalizuje odpowiedź protokołu do lokalnego state. const adjustProtocol = React.useCallback((p) => ({ ...p, devices: Array.isArray(p.devices) ? p.devices : [], }), []); const refreshProtocol = React.useCallback(async () => { const p = await VS.zaleceniaProtocol(protocolId); setProtocol(adjustProtocol(p)); }, [protocolId, adjustProtocol]); React.useEffect(() => { VS.zaleceniaProtocol(protocolId) .then((p) => setProtocol(adjustProtocol(p))) .catch((e) => setErr(e.message)); // Try admin-wide user list; fallback to self-only stamp. VS.userStamps() .then((list) => setUsers(list || [])) .catch(() => { VS.myStamp() .then((me) => setUsers([{ user_id: me.user_id, username: me.username, name: me.name, cert_number: me.cert_number, has_image: me.has_image, }])) .catch(() => setUsers([])); }); }, [protocolId]); function patch(p) { setProtocol((prev) => ({ ...prev, ...p })); setDirty(true); } function updateDevice(idx, field, value) { setProtocol((prev) => { const devices = prev.devices.map((d, i) => (i === idx ? { ...d, [field]: value } : d)); return { ...prev, devices }; }); setDirty(true); } function updateDeviceUwaga(idx, uIdx, value) { setProtocol((prev) => { const devices = prev.devices.map((d, i) => { if (i !== idx) return d; const uwagi = Array.isArray(d.uwagi) ? [...d.uwagi] : []; uwagi[uIdx] = value; return { ...d, uwagi }; }); return { ...prev, devices }; }); setDirty(true); } function removeDeviceUwaga(idx, uIdx) { setProtocol((prev) => { const devices = prev.devices.map((d, i) => { if (i !== idx) return d; const uwagi = (Array.isArray(d.uwagi) ? d.uwagi : []).filter((_, j) => j !== uIdx); return { ...d, uwagi }; }); return { ...prev, devices }; }); setDirty(true); } function addDeviceUwaga(idx) { setProtocol((prev) => { const devices = prev.devices.map((d, i) => { if (i !== idx) return d; const uwagi = Array.isArray(d.uwagi) ? [...d.uwagi, ''] : ['']; return { ...d, uwagi }; }); return { ...prev, devices }; }); setDirty(true); } function addDevice() { setProtocol((prev) => ({ ...prev, devices: [...(prev.devices || []), { code: '', name: '', ocena: '', uwagi: [], trend_note: '' }], })); setDirty(true); } function removeDevice(idx) { // TODO: jeśli device.has_trend_image — warto wywołać backendowe usunięcie obrazka, // żeby nie zostawić osieroconego pliku. Dodatkowo: obecnie indeksy są stabilne, // więc upload wiąże obrazek do pozycji na liście — jeśli kiedyś dojdzie drag/drop // lub zmiana kolejności, trzeba re-indeksować po stronie backendu. const dev = (protocol?.devices || [])[idx]; if (dev && dev.has_trend_image) { VS.deleteZaleceniaTrend(protocolId, idx).catch(() => { /* best effort */ }); } setProtocol((prev) => ({ ...prev, devices: prev.devices.filter((_, i) => i !== idx) })); setDirty(true); } async function onTrendUpload(idx, file) { try { await VS.uploadZaleceniaTrend(protocolId, idx, file); setTrendBustMap((m) => ({ ...m, [idx]: Date.now() })); // Optymistyczny merge — tylko flaga has_trend_image, bez refreshu, // żeby nie nadpisać niezapisanych zmian w polach urządzeń. setProtocol((prev) => ({ ...prev, devices: prev.devices.map((d, i) => i === idx ? { ...d, has_trend_image: true } : d ), })); } catch (e) { alert('Błąd uploadu: ' + e.message); } } async function onTrendDelete(idx) { if (!confirm('Usunąć obrazek trendu?')) return; try { await VS.deleteZaleceniaTrend(protocolId, idx); setTrendBustMap((m) => ({ ...m, [idx]: Date.now() })); setProtocol((prev) => ({ ...prev, devices: prev.devices.map((d, i) => i === idx ? { ...d, has_trend_image: false } : d ), })); } catch (e) { alert('Błąd usuwania: ' + e.message); } } async function save() { if (!protocol) return; setBusy(true); setMsg(''); try { const payload = { facility_id: protocol.facility_id, facility_code: protocol.facility_code || '', title: protocol.title || '', date_until: protocol.date_until || null, devices: (protocol.devices || []).map((d) => ({ code: d.code || '', name: d.name || '', ocena: d.ocena || '', uwagi: (Array.isArray(d.uwagi) ? d.uwagi : []).filter((u) => u != null), trend_note: d.trend_note || '', })), opracowal_user_id: protocol.opracowal_user_id, opracowal_date: protocol.opracowal_date || null, }; await VS.saveZaleceniaProtocol(protocolId, payload); setDirty(false); setMsg('Zapisano.'); setTimeout(() => setMsg(''), 2500); } catch (e) { setMsg('Błąd: ' + e.message); } finally { setBusy(false); } } async function removeProtocol() { if (!confirm('Usunąć ten protokół? Operacja nieodwracalna.')) return; try { await VS.deleteZaleceniaProtocol(protocolId); onBack(); } catch (e) { alert('Błąd: ' + e.message); } } async function downloadPdf() { if (dirty) await save(); try { await VS.openZaleceniaPdf(protocolId); } catch (e) { alert('Błąd pobierania PDF: ' + e.message); } } if (err) return ; if (!protocol) return ; const facilityCodes = (facilities || []).map((f) => f.code); const selectedFacility = (facilities || []).find((f) => f.code === protocol.facility_code); return (

Zalecenia remontowe #{protocolId}

{dirty ? ( ● Niezapisane zmiany ) : ( Wszystkie zmiany zapisane )} {msg && ( {msg} )}
{/* Metadane */}
Metadane