"use client"; /** * Manual time entry — admin form to add a manual log to a timesheet. * Captures start, end (optional), break, task, optional GPS, and a * mandatory reason (required for payroll / labor-law compliance). */ import React, { useState, useTransition } from "react"; import { addManualTimeEntry } from "@/actions/time-tracking/entries"; function captureGps(): Promise<{ lat: number; lng: number; accuracy_m?: number } | null> { return new Promise((resolve) => { if (typeof navigator === "undefined" || !navigator.geolocation) { resolve(null); return; } navigator.geolocation.getCurrentPosition( (pos) => resolve({ lat: pos.coords.latitude, lng: pos.coords.longitude, accuracy_m: pos.coords.accuracy, }), () => resolve(null), { enableHighAccuracy: true, timeout: 5000 }, ); }); } export default function ManualEntryForm({ brandId, workerId, periodStart, periodEnd, onClose, onCreated, }: { brandId: string; workerId: string; periodStart: string; periodEnd: string; onClose: () => void; onCreated: (entryId: string) => void; }) { const [date, setDate] = useState(periodStart); const [clockIn, setClockIn] = useState("08:00"); const [clockOut, setClockOut] = useState("17:00"); const [hasClockOut, setHasClockOut] = useState(true); const [breakMin, setBreakMin] = useState(30); const [taskName, setTaskName] = useState("Field work"); const [notes, setNotes] = useState(""); const [reason, setReason] = useState(""); const [gps, setGps] = useState<{ lat: number; lng: number; accuracy_m?: number } | null>(null); const [error, setError] = useState(null); const [pending, startTransition] = useTransition(); function toIso(d: string, t: string): string { return new Date(`${d}T${t}:00`).toISOString(); } function handleCaptureGps() { setError(null); void captureGps().then((g) => { if (!g) { setError("Couldn't get GPS — that's OK, you can save without it."); } else { setGps(g); } }); } function handleSubmit() { setError(null); if (!reason || reason.length < 3) { setError("A manual-entry reason is required (min 3 chars)."); return; } startTransition(async () => { const r = await addManualTimeEntry({ brandId, workerId, taskName, clockIn: toIso(date, clockIn), clockOut: hasClockOut ? toIso(date, clockOut) : null, lunchBreakMinutes: breakMin, notes: notes || undefined, manualReason: reason, gps: gps ?? undefined, }); if (!r.success) { setError(r.error ?? "Save failed"); return; } onCreated(r.entry_id!); }); } return (

Add manual entry

setDate(e.target.value)} className="rounded-md border border-zinc-300 px-3 py-2 text-sm" /> setTaskName(e.target.value)} className="rounded-md border border-zinc-300 px-3 py-2 text-sm" /> setClockIn(e.target.value)} className="rounded-md border border-zinc-300 px-3 py-2 text-sm" />
setClockOut(e.target.value)} className="rounded-md border border-zinc-300 px-3 py-2 text-sm disabled:bg-zinc-100" />
setBreakMin(Number(e.target.value))} className="rounded-md border border-zinc-300 px-3 py-2 text-sm" />
{gps && ( {gps.lat.toFixed(4)}, {gps.lng.toFixed(4)} {gps.accuracy_m != null && ` ±${Math.round(gps.accuracy_m)}m`} )}
setReason(e.target.value)} placeholder="Forgot to clock in — was at the barn from 7am" className="w-full rounded-md border border-zinc-300 px-3 py-2 text-sm" />