diff --git a/src/app/clock/clock-view.tsx b/src/app/clock/clock-view.tsx index f3f8abd..1ff8c53 100644 --- a/src/app/clock/clock-view.tsx +++ b/src/app/clock/clock-view.tsx @@ -1,6 +1,7 @@ "use client"; import * as React from "react"; +import { Truck, User } from "lucide-react"; import { cn } from "@/lib/utils"; import { PersonScanner } from "./person-scanner"; @@ -9,6 +10,7 @@ type Tab = "person" | "fleet"; export function ClockView() { const [tab, setTab] = React.useState("person"); const [now, setNow] = React.useState(() => new Date()); + const [scanCount, setScanCount] = React.useState(0); React.useEffect(() => { const id = setInterval(() => setNow(new Date()), 30_000); @@ -19,19 +21,41 @@ export function ClockView() {
MEGA Clock - - {now.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })} - +
+ + {scanCount} scans + + + {now.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })} + +
- {tab === "person" ? : } + {tab === "person" ? ( + setScanCount((c) => c + 1)} /> + ) : ( + + )}
- setTab("person")} /> - - setTab("fleet")} /> + setTab("person")} + /> + + setTab("fleet")} + />
); @@ -39,10 +63,12 @@ export function ClockView() { function TabButton({ label, + icon: Icon, active, onClick, }: { label: string; + icon: React.ComponentType<{ className?: string }>; active: boolean; onClick: () => void; }) { @@ -51,10 +77,11 @@ function TabButton({ type="button" onClick={onClick} className={cn( - "flex-1 py-4 text-center text-sm font-semibold", + "flex flex-1 flex-col items-center justify-center gap-1 py-6 text-center text-base font-semibold", active ? "text-primary" : "text-muted-foreground", )} > + {label} ); diff --git a/src/app/clock/person-scanner.tsx b/src/app/clock/person-scanner.tsx index 8fa715e..42e951c 100644 --- a/src/app/clock/person-scanner.tsx +++ b/src/app/clock/person-scanner.tsx @@ -12,7 +12,7 @@ type ScanResult = | { kind: "out"; employeeName: string; sessionHours: number; dailyTotalHours: number } | { kind: "error"; message: string }; -export function PersonScanner() { +export function PersonScanner({ onScan }: { onScan?: () => void }) { const videoRef = React.useRef(null); const canvasRef = React.useRef(null); const lastCodeRef = React.useRef<{ value: string; at: number } | null>(null); @@ -42,6 +42,8 @@ export function PersonScanner() { } if (responseBody.direction === "in") { setResult({ kind: "in", employeeName: responseBody.employeeName }); + navigator.vibrate?.(150); + onScan?.(); } else { setResult({ kind: "out", @@ -49,21 +51,40 @@ export function PersonScanner() { sessionHours: responseBody.sessionHours, dailyTotalHours: responseBody.dailyTotalHours, }); + navigator.vibrate?.([100, 80, 100]); + onScan?.(); } }) .catch(() => { setResult({ kind: "error", message: "Couldn't reach the server — try again" }); + navigator.vibrate?.(300); }) .finally(() => { busyRef.current = false; setTimeout(() => setResult(null), BANNER_MS); }); - }, []); + }, [onScan]); React.useEffect(() => { let stream: MediaStream | null = null; let rafId = 0; let cancelled = false; + let wakeLock: WakeLockSentinel | null = null; + + // A kiosk phone mounted on a wall must not let its screen lock mid-shift. + // The OS can revoke the lock when the tab is backgrounded (app-switch, + // screen-off from a hardware button), so it's re-requested on return. + async function requestWakeLock() { + try { + wakeLock = (await navigator.wakeLock?.request("screen")) ?? null; + } catch { + // Unsupported or denied — not fatal, the screen may just dim on its own. + } + } + + function handleVisibilityChange() { + if (document.visibilityState === "visible") requestWakeLock(); + } async function tick() { if (cancelled) return; @@ -119,14 +140,18 @@ export function PersonScanner() { if (window.BarcodeDetector) { detectorRef.current = new window.BarcodeDetector({ formats: ["qr_code"] }); } + await requestWakeLock(); tick(); } + document.addEventListener("visibilitychange", handleVisibilityChange); start(); return () => { cancelled = true; cancelAnimationFrame(rafId); + document.removeEventListener("visibilitychange", handleVisibilityChange); + wakeLock?.release().catch(() => {}); stream?.getTracks().forEach((t) => t.stop()); }; }, [handleDecoded]);