'use client'; import { useEffect, useRef, useState } from 'react'; import { useRouter } from 'next/navigation'; import { AlertTriangle, CheckCircle2, Play, Square, X } from 'lucide-react'; import { BadgeCamera } from './BadgeCamera'; import { SearchPicker } from './SearchPicker'; import { clockTime } from '@/lib/clock-display'; import { assetFromQr } from '@/lib/stock-out'; import { formatMinutes, JOB_TYPES, minutesBetween, type JobType } from '@/lib/job-cards'; type Asset = { id: string; code: string; name: string }; type Mechanic = { id: string; name: string; code: string }; type OpenJob = { id: string; asset: string; mechanic: string; startedAt: string }; const LAST_MECHANIC = 'mp:lastMechanic'; /** * Scan a vehicle's QR. No open job on it → START one (pick who is working). An open job → * FINISH it: odometer, kind of work and a note. The clock runs from the first scan. */ export function JobScanner({ assets, mechanics, open }: { assets: Asset[]; mechanics: Mechanic[]; open: OpenJob[] }) { const router = useRouter(); const [asset, setAsset] = useState(null); const [mechanicId, setMechanicId] = useState(''); const [odometer, setOdometer] = useState(''); const [noMeter, setNoMeter] = useState(false); const [jobType, setJobType] = useState(''); const [note, setNote] = useState(''); const [busy, setBusy] = useState(false); const [message, setMessage] = useState<{ ok: boolean; text: string } | null>(null); const [unknownScan, setUnknownScan] = useState(''); const [now, setNow] = useState(() => new Date().toISOString()); const lastScan = useRef({ value: '', until: 0 }); useEffect(() => { const t = setInterval(() => setNow(new Date().toISOString()), 30000); return () => clearInterval(t); }, []); useEffect(() => { try { const m = localStorage.getItem(LAST_MECHANIC); if (m) setMechanicId(m); } catch { /* private mode */ } }, []); const job = asset ? open.find(j => j.asset === asset.id) ?? null : null; const assetById = (id: string) => assets.find(a => a.id === id); function choose(next: Asset | null) { setAsset(next); setOdometer(''); setNoMeter(false); setJobType(''); setNote(''); setUnknownScan(''); } function onDecode(raw: string) { if (asset || busy) return; if (lastScan.current.value === raw && Date.now() < lastScan.current.until) return; lastScan.current = { value: raw, until: Date.now() + 4000 }; const found = assetFromQr(raw, assets); if (found) { setMessage(null); choose(found); navigator.vibrate?.(120); } else setUnknownScan(raw.slice(0, 60)); } async function call(url: string, body: object) { setBusy(true); setMessage(null); try { const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.error || 'Could not save.'); return data; } catch (e) { setMessage({ ok: false, text: e instanceof Error ? e.message : 'Could not save.' }); return null; } finally { setBusy(false); } } async function start(e: React.FormEvent) { e.preventDefault(); if (!asset || !mechanicId) return; const data = await call('/api/clock/job-start', { asset_id: asset.id, mechanic_id: mechanicId }); if (!data) return; try { localStorage.setItem(LAST_MECHANIC, mechanicId); } catch { /* ignore */ } setMessage({ ok: true, text: `Job started on ${data.asset} for ${data.mechanic} at ${clockTime(data.at)}.` }); choose(null); router.refresh(); navigator.vibrate?.(120); } async function finish(e: React.FormEvent) { e.preventDefault(); if (!job || !jobType) return; const data = await call('/api/clock/job-finish', { job_id: job.id, job_type: jobType, note, odometer, no_meter: noMeter }); if (!data) return; setMessage({ ok: true, text: `Job on ${data.asset} finished — ${formatMinutes(data.minutes)} on the job.` }); choose(null); router.refresh(); navigator.vibrate?.(120); } return

MEGA PLANT

Job cards

{message &&
{message.ok ? : }

{message.text}

} {!asset ? <>
{unknownScan &&

That QR is not a vehicle in the plant register ({unknownScan}).

}
{ const found = assetById(id); if (found) { setMessage(null); choose(found); } }} options={assets.map(a => ({ id: a.id, label: a.code, sub: a.name }))}/>
: !job ?

{asset.code} · {asset.name}

No job open — this starts one, clock running from now.

({ id: m.id, label: m.name, sub: m.code }))}/> :

{asset.code} · {asset.name}

{job.mechanic} · started {clockTime(job.startedAt.replace(' ', 'T'))} · {formatMinutes(minutesBetween(job.startedAt, now))} so far

setOdometer(e.target.value)} placeholder="Current reading"/>

What kind of work was it?

{JOB_TYPES.map(t => )}