MY QUALITY - First iteration

This commit is contained in:
2026-06-11 15:43:35 +01:00
parent 4f8996712e
commit 1fdb9536fa
31 changed files with 1965 additions and 98 deletions
+23 -27
View File
@@ -3,7 +3,7 @@
import { useState, useRef } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { ArrowLeft, Camera, X } from 'lucide-react';
import { ArrowLeft, Camera, X, MapPin } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { trpc } from '@/lib/trpc/client';
import { db } from '@/lib/queue/db';
@@ -48,17 +48,17 @@ export default function NewRequestPage() {
const router = useRouter();
const fileRef = useRef<HTMLInputElement>(null);
const [workstationId, setWorkstationId] = useState('');
const [description, setDescription] = useState('');
const [photoBlob, setPhotoBlob] = useState<Blob | null>(null);
const [photoPreview, setPhotoPreview] = useState<string | null>(null);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const { data: workstations = [], isLoading: wsLoading } = trpc.workstation.list.useQuery(
undefined,
{ staleTime: 60 * 60 * 1000 },
);
// Workstation is no longer chosen per-request: it comes from the operator's
// active badge-in session. It still travels in the queued payload so offline
// submissions remain self-contained even if the operator later changes posto.
const { data: session, isLoading: sessionLoading } = trpc.operatorSession.current.useQuery();
const workstationId = session?.workstationId ?? '';
async function handlePhotoChange(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0];
@@ -121,28 +121,24 @@ export default function NewRequestPage() {
</header>
<form onSubmit={handleSubmit} className="flex flex-1 flex-col gap-6 p-4">
{/* Workstation */}
{/* Workstation — read-only, from the active session */}
<div className="flex flex-col gap-1.5">
<label htmlFor="workstation" className="text-sm font-medium">
{t('workstationLabel')} <span className="text-destructive">{t('workstationRequired')}</span>
</label>
<select
id="workstation"
value={workstationId}
onChange={(e) => setWorkstationId(e.target.value)}
required
disabled={wsLoading}
className="w-full rounded-lg border border-border bg-card px-3 py-2.5 text-sm disabled:opacity-50"
>
<option value="">
{wsLoading ? t('workstationLoading') : t('workstationPlaceholder')}
</option>
{workstations.map((ws) => (
<option key={ws.id} value={ws.id}>
{ws.code} {ws.name} · {ws.area}
</option>
))}
</select>
<span className="text-sm font-medium">{t('workstationLabel')}</span>
{sessionLoading ? (
<p className="text-sm text-muted-foreground">{t('workstationLoading')}</p>
) : session ? (
<div className="flex items-center gap-2 rounded-lg border border-border bg-muted/40 px-3 py-2.5 text-sm">
<MapPin className="h-4 w-4 shrink-0 text-primary" />
<span className="font-medium">
{session.workstation.code} {session.workstation.name}
<span className="text-xs text-muted-foreground"> · {session.workstation.area}</span>
</span>
</div>
) : (
<p className="rounded-lg bg-destructive/10 px-3 py-2 text-sm text-destructive">
{t('noSession')}
</p>
)}
</div>
{/* Photo */}