'use client'; import { useState, useEffect, useRef } from 'react'; import Link from 'next/link'; import { ArrowLeft, ClipboardCheck, MapPin, CheckCircle2, Loader2, AlertTriangle } from 'lucide-react'; import { useTranslations } from 'next-intl'; import { trpc } from '@/lib/trpc/client'; import type { RouterOutputs } from '@/lib/trpc/server'; type Defect = RouterOutputs['qualityDefect']['forMyStation'][number]; type DefectStatus = 'OPEN' | 'ACKNOWLEDGED' | 'CORRECTED'; type TFn = (key: string, values?: Record) => string; function timeAgo(date: Date | string, t: TFn): string { const diffMs = Date.now() - new Date(date).getTime(); const mins = Math.floor(diffMs / 60_000); if (mins < 1) return t('now'); if (mins < 60) return t('minutesAgo', { mins }); const hours = Math.floor(mins / 60); if (hours < 24) return t('hoursAgo', { hours }); return t('daysAgo', { days: Math.floor(hours / 24) }); } function playBeep() { try { const ctx = new AudioContext(); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.type = 'sine'; osc.frequency.value = 880; gain.gain.setValueAtTime(0.2, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.4); osc.start(ctx.currentTime); osc.stop(ctx.currentTime + 0.4); } catch { // AudioContext may be blocked before user interaction — ignore. } } const STATUS_CLASS: Record = { OPEN: 'bg-orange-100 text-orange-700', ACKNOWLEDGED: 'bg-blue-100 text-blue-700', CORRECTED: 'bg-green-100 text-green-700', }; function Thumbnail({ photoKey, alt }: { photoKey: string | null; alt: string }) { const { data } = trpc.storage.signPhotoDownload.useQuery( { photoKey: photoKey! }, { enabled: !!photoKey, staleTime: 50_000 }, ); if (!photoKey) return null; if (!data?.url) { return
; } return ( // eslint-disable-next-line @next/next/no-img-element {alt} ); } function CorrectDialog({ onConfirm, onCancel, note, onNoteChange, busy, t, tc, }: { onConfirm: () => void; onCancel: () => void; note: string; onNoteChange: (v: string) => void; busy: boolean; t: ReturnType>; tc: ReturnType>; }) { return (

{t('correctDialogTitle')}