/* global React, Card, StatusBadge, StatusGlyph, STATUS_META, Ic, Button, Input, Breadcrumb, VS, LoadingState, ErrorState, statusFromAlarm, fmtInt */
function MeasurementsScreen({ onOpenSession }) {
const [sites, setSites] = React.useState(null);
const [siteId, setSiteId] = React.useState(() => {
const saved = localStorage.getItem('vs_selected_site');
return saved ? parseInt(saved, 10) : null;
});
const [machines, setMachines] = React.useState(null);
const [loadingMachines, setLoadingMachines] = React.useState(false);
const [query, setQuery] = React.useState('');
const [selectedMachine, setSelectedMachine] = React.useState(null);
const [sessions, setSessions] = React.useState(null);
const [loadingSessions, setLoadingSessions] = React.useState(false);
const [err, setErr] = React.useState('');
const [logoBust, setLogoBust] = React.useState(0);
const fileInputRef = React.useRef(null);
const role = VS.getRole();
const isAdmin = role === 'admin';
React.useEffect(() => {
VS.sites().then(s => {
setSites(s);
const existing = siteId && s.find(x => x.id === siteId);
if (!existing) {
const firstNonEmpty = s.find(x => (x.measurements_count || 0) > 0);
const fallback = firstNonEmpty || s[0];
if (fallback) setSiteId(fallback.id);
}
}).catch(e => setErr(e.message));
}, []);
React.useEffect(() => {
if (!siteId) return;
localStorage.setItem('vs_selected_site', String(siteId));
setMachines(null);
setSelectedMachine(null);
setSessions(null);
setLoadingMachines(true);
VS.siteMachines(siteId)
.then(m => setMachines(m))
.catch(e => setErr(e.message))
.finally(() => setLoadingMachines(false));
}, [siteId]);
async function selectMachine(m) {
setSelectedMachine(m);
setSessions(null);
setLoadingSessions(true);
try {
const s = await VS.sessions(m.id, 50);
setSessions(s);
} catch (e) { setErr(e.message); }
finally { setLoadingSessions(false); }
}
async function uploadLogo(e) {
const file = e.target.files && e.target.files[0];
if (!file || !siteId) return;
try {
await VS.uploadSiteLogo(siteId, file);
setLogoBust(x => x + 1);
} catch (err) { setErr(err.message); }
e.target.value = '';
}
async function removeLogo() {
if (!siteId) return;
try {
await VS.deleteSiteLogo(siteId);
setLogoBust(x => x + 1);
} catch (err) { setErr(err.message); }
}
const site = sites && sites.find(s => s.id === siteId);
const filteredMachines = machines
? machines.filter(m => !query
|| m.name.toLowerCase().includes(query.toLowerCase())
|| (m.hall || '').toLowerCase().includes(query.toLowerCase()))
: [];
if (err && !sites) return ;
if (!sites) return ;
return (
{err && (
{err}
)}
{!selectedMachine && }
{selectedMachine && (
selectMachine(selectedMachine)}
/>
)}
);
}
function SiteHeader({ site, sites, onChange, logoBust, isAdmin, fileInputRef, onUpload, onRemove }) {
const [logoErr, setLogoErr] = React.useState(false);
React.useEffect(() => { setLogoErr(false); }, [site && site.id, logoBust]);
return (
{site && !logoErr ? (
)
setLogoErr(true)}
style={{ width: '100%', height: '100%', objectFit: 'contain', padding: 6 }}
/>
) : (
{site ? site.name.slice(0, 3).toUpperCase() : '—'}
)}
{isAdmin && site && (
}
onClick={() => fileInputRef.current && fileInputRef.current.click()}
>
Logo
{!logoErr && (
)}
)}
);
}
function GlassDropdown({ site, sites, onChange }) {
const [open, setOpen] = React.useState(false);
const [q, setQ] = React.useState('');
const wrapRef = React.useRef(null);
React.useEffect(() => {
function onDoc(e) {
if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
}
document.addEventListener('mousedown', onDoc);
return () => document.removeEventListener('mousedown', onDoc);
}, []);
React.useEffect(() => {
if (!open) setQ('');
}, [open]);
const list = sites.filter(s => !q || s.name.toLowerCase().includes(q.toLowerCase()));
return (
{open && (
}
placeholder="Szukaj zakładu…"
value={q}
onChange={(e) => setQ(e.target.value)}
autoFocus
/>
{list.length === 0 && (
Brak zakładów.
)}
{list.map(s => {
const active = site && site.id === s.id;
const hasData = (s.measurements_count || 0) > 0;
return (
{ onChange(s.id); setOpen(false); }}
style={{
display: 'flex', alignItems: 'center', gap: 10,
padding: '9px 10px',
borderRadius: 'var(--r-sm)',
cursor: 'pointer',
background: active ? 'var(--surface)' : 'transparent',
border: active ? '1px solid var(--border-strong)' : '1px solid transparent',
}}
onMouseEnter={(e) => { if (!active) e.currentTarget.style.background = 'var(--bg-sunken)'; }}
onMouseLeave={(e) => { if (!active) e.currentTarget.style.background = 'transparent'; }}
>
{s.name}
{s.measurements_count || 0}
);
})}
)}
);
}
function MachineList({ machines, selectedId, onSelect }) {
const groups = React.useMemo(() => {
const m = new Map();
for (const item of machines) {
const key = item.hall || '—';
if (!m.has(key)) m.set(key, []);
m.get(key).push(item);
}
return Array.from(m.entries());
}, [machines]);
return (
{groups.map(([hall, items]) => (
{hall} · {items.length}
{items.map((m) => {
const active = m.id === selectedId;
const st = statusFromAlarm(m.max_alarm);
return (
onSelect(m)}
style={{
display: 'flex', alignItems: 'center', gap: 10,
padding: '10px 14px',
cursor: 'pointer',
background: active ? 'var(--surface)' : 'transparent',
borderLeft: active ? '2px solid var(--fg)' : '2px solid transparent',
borderBottom: '1px solid var(--hairline)',
}}
onMouseEnter={(e) => { if (!active) e.currentTarget.style.background = 'var(--bg-sunken)'; }}
onMouseLeave={(e) => { if (!active) e.currentTarget.style.background = 'transparent'; }}
>
{m.name}
{m.sessions} {m.sessions === 1 ? 'sesja' : 'sesji'}
{m.last_dtg && <> · ostatnia: {m.last_dtg}>}
);
})}
))}
);
}
function MachineDetail({ machine, siteName, sessions, loading, onOpenSession, onRefresh }) {
return (
<>
{machine.name}
ID {machine.id}
{machine.hall && hala {machine.hall}}
sesji {machine.sessions}
} onClick={onRefresh}>Odśwież
{loading &&
}
{!loading && sessions && sessions.length === 0 && (
Brak sesji
Ta maszyna nie ma jeszcze wgranych pomiarów.
)}
{!loading && sessions && sessions.length > 0 && (
Sesje pomiarowe
{sessions.length} sesji
Status
Data · godzina
Punkty
Urządzenie
Akcja
{sessions.map((s, i) => {
const st = statusFromAlarm(s.max_alarm);
const dateStr = s.session_date
? `${s.session_date.slice(0,4)}-${s.session_date.slice(4,6)}-${s.session_date.slice(6,8)}`
: s.date_display;
return (
onOpenSession && onOpenSession({ machine_id: machine.id, machine_name: machine.name, session_date: s.session_date })}
style={{
display: 'grid', gridTemplateColumns: '110px 1fr 100px 160px 90px',
padding: '12px 16px', borderBottom: i < sessions.length - 1 ? '1px solid var(--hairline)' : 'none',
alignItems: 'center', fontSize: 12.5, cursor: 'pointer',
}}
onMouseEnter={(e) => e.currentTarget.style.background = 'var(--bg-sunken)'}
onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}
>
{dateStr}
{s.time_start && · {s.time_start}}
{s.point_count || 0}
{s.device_id || '—'}
}>Otwórz
);
})}
)}
>
);
}
function EmptyPicker() {
return (
Wybierz maszynę
Kliknij maszynę w liście po lewej żeby zobaczyć jej sesje pomiarowe.
);
}
Object.assign(window, { MeasurementsScreen });