"use client"; import { useState, useReducer, useEffect, useCallback, useRef } from "react"; import { getTimeTrackingWorkers, getTimeTrackingTasks, createTimeWorker, resetTimeWorkerPin, updateTimeWorker, deleteTimeWorker, createTimeTask, updateTimeTask, deleteTimeTask, getTimeTrackingSettings, updateTimeTrackingSettings, type TimeWorker, type TimeTask, type TimeTrackingSettings, } from "@/actions/time-tracking"; import AdminToggle from "./design-system/AdminToggle"; import { AdminInput, AdminTextInput, AdminSelect } from "./design-system/AdminFormElements"; import AdminButton from "./design-system/AdminButton"; const DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; // ────────────────────────────────────────────────────────────────────────── // State (useReducer) // ────────────────────────────────────────────────────────────────────────── type State = { workers: TimeWorker[]; tasks: TimeTask[]; loadedSettings: TimeTrackingSettings | null; loading: boolean; // Settings form payPeriodStartDay: number; payPeriodLength: number; dailyOvertimeThreshold: number; weeklyOvertimeThreshold: number; notificationEmails: string[]; notificationSmsNumbers: string[]; enableDailyAlerts: boolean; enableWeeklyAlerts: boolean; newEmail: string; newSms: string; settingsSaving: boolean; settingsSaved: boolean; settingsError: string | null; // Worker modal showWorkerModal: boolean; editingWorker: TimeWorker | null; workerName: string; workerRole: string; workerLang: string; workerActive: boolean; resetPinResult: string | null; workerError: string | null; submitting: boolean; // Task modal showTaskModal: boolean; editingTask: TimeTask | null; taskName: string; taskNameEs: string; taskUnit: string; taskActive: boolean; taskSortOrder: number; taskError: string | null; }; type Action = | { type: "SET_WORKERS"; value: TimeWorker[] } | { type: "SET_TASKS"; value: TimeTask[] } | { type: "SET_LOADED_SETTINGS"; value: TimeTrackingSettings | null } | { type: "SET_LOADING"; value: boolean } // Settings form | { type: "SET_PAY_PERIOD_START_DAY"; value: number } | { type: "SET_PAY_PERIOD_LENGTH"; value: number } | { type: "SET_DAILY_OVERTIME_THRESHOLD"; value: number } | { type: "SET_WEEKLY_OVERTIME_THRESHOLD"; value: number } | { type: "SET_NOTIFICATION_EMAILS"; value: string[] } | { type: "ADD_EMAIL"; value: string } | { type: "REMOVE_EMAIL"; value: string } | { type: "SET_NOTIFICATION_SMS"; value: string[] } | { type: "ADD_SMS"; value: string } | { type: "REMOVE_SMS"; value: string } | { type: "SET_ENABLE_DAILY_ALERTS"; value: boolean } | { type: "SET_ENABLE_WEEKLY_ALERTS"; value: boolean } | { type: "SET_NEW_EMAIL"; value: string } | { type: "SET_NEW_SMS"; value: string } | { type: "SET_SETTINGS_SAVING"; value: boolean } | { type: "SET_SETTINGS_SAVED"; value: boolean } | { type: "SET_SETTINGS_ERROR"; value: string | null } // Worker modal | { type: "OPEN_ADD_WORKER" } | { type: "OPEN_EDIT_WORKER"; worker: TimeWorker } | { type: "CLOSE_WORKER_MODAL" } | { type: "SET_WORKER_NAME"; value: string } | { type: "SET_WORKER_ROLE"; value: string } | { type: "SET_WORKER_LANG"; value: string } | { type: "SET_WORKER_ACTIVE"; value: boolean } | { type: "SET_RESET_PIN_RESULT"; value: string | null } | { type: "SET_WORKER_ERROR"; value: string | null } | { type: "SET_SUBMITTING"; value: boolean } // Task modal | { type: "OPEN_ADD_TASK" } | { type: "OPEN_EDIT_TASK"; task: TimeTask } | { type: "CLOSE_TASK_MODAL" } | { type: "SET_TASK_NAME"; value: string } | { type: "SET_TASK_NAME_ES"; value: string } | { type: "SET_TASK_UNIT"; value: string } | { type: "SET_TASK_ACTIVE"; value: boolean } | { type: "SET_TASK_SORT_ORDER"; value: number } | { type: "SET_TASK_ERROR"; value: string | null }; const initialState: State = { workers: [], tasks: [], loadedSettings: null, loading: true, payPeriodStartDay: 0, payPeriodLength: 7, dailyOvertimeThreshold: 12, weeklyOvertimeThreshold: 56, notificationEmails: [], notificationSmsNumbers: [], enableDailyAlerts: true, enableWeeklyAlerts: true, newEmail: "", newSms: "", settingsSaving: false, settingsSaved: false, settingsError: null, showWorkerModal: false, editingWorker: null, workerName: "", workerRole: "worker", workerLang: "en", workerActive: true, resetPinResult: null, workerError: null, submitting: false, showTaskModal: false, editingTask: null, taskName: "", taskNameEs: "", taskUnit: "hours", taskActive: true, taskSortOrder: 0, taskError: null, }; function reducer(state: State, action: Action): State { switch (action.type) { case "SET_WORKERS": return { ...state, workers: action.value }; case "SET_TASKS": return { ...state, tasks: action.value }; case "SET_LOADED_SETTINGS": // Hydrate form state from the freshly-loaded settings object if (!action.value) { return { ...state, loadedSettings: null }; } return { ...state, loadedSettings: action.value, payPeriodStartDay: action.value.pay_period_start_day, payPeriodLength: action.value.pay_period_length_days, dailyOvertimeThreshold: action.value.daily_overtime_threshold, weeklyOvertimeThreshold: action.value.weekly_overtime_threshold, notificationEmails: action.value.notification_emails ?? [], notificationSmsNumbers: action.value.notification_sms_numbers ?? [], enableDailyAlerts: action.value.enable_daily_alerts ?? true, enableWeeklyAlerts: action.value.enable_weekly_alerts ?? true, }; case "SET_LOADING": return { ...state, loading: action.value }; case "SET_PAY_PERIOD_START_DAY": return { ...state, payPeriodStartDay: action.value }; case "SET_PAY_PERIOD_LENGTH": return { ...state, payPeriodLength: action.value }; case "SET_DAILY_OVERTIME_THRESHOLD": return { ...state, dailyOvertimeThreshold: action.value }; case "SET_WEEKLY_OVERTIME_THRESHOLD": return { ...state, weeklyOvertimeThreshold: action.value }; case "SET_NOTIFICATION_EMAILS": return { ...state, notificationEmails: action.value }; case "ADD_EMAIL": if (!action.value || state.notificationEmails.includes(action.value)) { return { ...state, newEmail: "" }; } return { ...state, notificationEmails: [...state.notificationEmails, action.value], newEmail: "", }; case "REMOVE_EMAIL": return { ...state, notificationEmails: state.notificationEmails.filter((e) => e !== action.value), }; case "SET_NOTIFICATION_SMS": return { ...state, notificationSmsNumbers: action.value }; case "ADD_SMS": if (!action.value || state.notificationSmsNumbers.includes(action.value)) { return { ...state, newSms: "" }; } return { ...state, notificationSmsNumbers: [...state.notificationSmsNumbers, action.value], newSms: "", }; case "REMOVE_SMS": return { ...state, notificationSmsNumbers: state.notificationSmsNumbers.filter((n) => n !== action.value), }; case "SET_ENABLE_DAILY_ALERTS": return { ...state, enableDailyAlerts: action.value }; case "SET_ENABLE_WEEKLY_ALERTS": return { ...state, enableWeeklyAlerts: action.value }; case "SET_NEW_EMAIL": return { ...state, newEmail: action.value }; case "SET_NEW_SMS": return { ...state, newSms: action.value }; case "SET_SETTINGS_SAVING": return { ...state, settingsSaving: action.value }; case "SET_SETTINGS_SAVED": return { ...state, settingsSaved: action.value }; case "SET_SETTINGS_ERROR": return { ...state, settingsError: action.value }; case "OPEN_ADD_WORKER": return { ...state, editingWorker: null, workerName: "", workerRole: "worker", workerLang: "en", workerActive: true, workerError: null, resetPinResult: null, showWorkerModal: true, }; case "OPEN_EDIT_WORKER": return { ...state, editingWorker: action.worker, workerName: action.worker.name, workerRole: action.worker.role, workerLang: action.worker.lang, workerActive: action.worker.active, workerError: null, resetPinResult: null, showWorkerModal: true, }; case "CLOSE_WORKER_MODAL": return { ...state, showWorkerModal: false }; case "SET_WORKER_NAME": return { ...state, workerName: action.value }; case "SET_WORKER_ROLE": return { ...state, workerRole: action.value }; case "SET_WORKER_LANG": return { ...state, workerLang: action.value }; case "SET_WORKER_ACTIVE": return { ...state, workerActive: action.value }; case "SET_RESET_PIN_RESULT": return { ...state, resetPinResult: action.value }; case "SET_WORKER_ERROR": return { ...state, workerError: action.value }; case "SET_SUBMITTING": return { ...state, submitting: action.value }; case "OPEN_ADD_TASK": return { ...state, editingTask: null, taskName: "", taskNameEs: "", taskUnit: "hours", taskActive: true, taskSortOrder: 0, taskError: null, showTaskModal: true, }; case "OPEN_EDIT_TASK": return { ...state, editingTask: action.task, taskName: action.task.name, taskNameEs: action.task.name_es ?? "", taskUnit: action.task.unit, taskActive: action.task.active, taskSortOrder: action.task.sort_order, taskError: null, showTaskModal: true, }; case "CLOSE_TASK_MODAL": return { ...state, showTaskModal: false }; case "SET_TASK_NAME": return { ...state, taskName: action.value }; case "SET_TASK_NAME_ES": return { ...state, taskNameEs: action.value }; case "SET_TASK_UNIT": return { ...state, taskUnit: action.value }; case "SET_TASK_ACTIVE": return { ...state, taskActive: action.value }; case "SET_TASK_SORT_ORDER": return { ...state, taskSortOrder: action.value }; case "SET_TASK_ERROR": return { ...state, taskError: action.value }; } } // ── Accordion wrapper (kept untouched) ───────────────────────────────────── type AccordionProps = { title: string; id?: string; description?: string; defaultOpen?: boolean; accentColor?: "emerald" | "violet" | "amber" | "stone"; children: React.ReactNode; }; function Accordion({ title, id, description, defaultOpen = false, accentColor = "stone", children }: AccordionProps) { const [open, setOpen] = useState(defaultOpen); const accent = { emerald: "bg-emerald-50 text-emerald-700 border-emerald-200", violet: "bg-violet-50 text-violet-700 border-violet-200", amber: "bg-amber-50 text-amber-700 border-amber-200", stone: "bg-stone-50 text-stone-700 border-stone-200", }[accentColor]; const borderColor = open ? "border-stone-300" : "border-stone-200"; return (
{children}
); } // ────────────────────────────────────────────────────────────────────────── // Main component // ────────────────────────────────────────────────────────────────────────── type Props = { brandId: string; workersOnly?: boolean; tasksOnly?: boolean; }; export default function SettingsSections({ brandId, workersOnly, tasksOnly }: Props) { const [state, dispatch] = useReducer(reducer, initialState); // Refs for values that don't need to trigger re-renders const overtimeMultiplierRef = useRef(1.5); const overtimeNotificationsRef = useRef(true); const dailyAlertThresholdRef = useRef(80); const weeklyAlertThresholdRef = useRef(80); const sendEndOfPeriodSummaryRef = useRef(true); const brandNameRef = useRef("Farm"); // Sync the non-rendered ref values when fresh settings arrive. The // form-state portion of the reducer is hydrated atomically by the // SET_LOADED_SETTINGS action above, so there's no stale-frame window // for fields the user sees — these refs only hold values used at // save time and don't need to drive a re-render. useEffect(() => { if (state.loadedSettings) { overtimeMultiplierRef.current = state.loadedSettings.overtime_multiplier; overtimeNotificationsRef.current = state.loadedSettings.overtime_notifications; dailyAlertThresholdRef.current = Math.round((state.loadedSettings.daily_alert_threshold ?? 0.80) * 100); weeklyAlertThresholdRef.current = Math.round((state.loadedSettings.weekly_alert_threshold ?? 0.80) * 100); sendEndOfPeriodSummaryRef.current = state.loadedSettings.send_end_of_period_summary ?? true; brandNameRef.current = state.loadedSettings.brand_name ?? "Farm"; } }, [state.loadedSettings]); const load = useCallback(async () => { dispatch({ type: "SET_LOADING", value: true }); const [w, t, s] = await Promise.all([ getTimeTrackingWorkers(brandId), getTimeTrackingTasks(brandId, false), getTimeTrackingSettings(brandId), ]); dispatch({ type: "SET_WORKERS", value: w }); dispatch({ type: "SET_TASKS", value: t }); dispatch({ type: "SET_LOADED_SETTINGS", value: s }); dispatch({ type: "SET_LOADING", value: false }); }, [brandId]); useEffect(() => { void load(); }, [load]); const handleSaveNotifications = async () => { dispatch({ type: "SET_SETTINGS_SAVING", value: true }); dispatch({ type: "SET_SETTINGS_ERROR", value: null }); dispatch({ type: "SET_SETTINGS_SAVED", value: false }); const result = await updateTimeTrackingSettings(brandId, { pay_period_start_day: state.payPeriodStartDay, pay_period_length_days: state.payPeriodLength, daily_overtime_threshold: state.dailyOvertimeThreshold, weekly_overtime_threshold: state.weeklyOvertimeThreshold, overtime_multiplier: overtimeMultiplierRef.current, overtime_notifications: overtimeNotificationsRef.current, notification_emails: state.notificationEmails, notification_sms_numbers: state.notificationSmsNumbers, enable_daily_alerts: state.enableDailyAlerts, enable_weekly_alerts: state.enableWeeklyAlerts, daily_alert_threshold: dailyAlertThresholdRef.current / 100, weekly_alert_threshold: weeklyAlertThresholdRef.current / 100, send_end_of_period_summary: sendEndOfPeriodSummaryRef.current, brand_name: brandNameRef.current, }); dispatch({ type: "SET_SETTINGS_SAVING", value: false }); if (!result.success) { dispatch({ type: "SET_SETTINGS_ERROR", value: result.error ?? "Failed to save" }); return; } dispatch({ type: "SET_SETTINGS_SAVED", value: true }); setTimeout(() => dispatch({ type: "SET_SETTINGS_SAVED", value: false }), 3000); load(); }; const handleSaveWorker = async () => { if (!state.workerName.trim()) { dispatch({ type: "SET_WORKER_ERROR", value: "Name is required" }); return; } dispatch({ type: "SET_SUBMITTING", value: true }); dispatch({ type: "SET_WORKER_ERROR", value: null }); if (state.editingWorker) { const result = await updateTimeWorker( state.editingWorker.id, state.workerName.trim(), state.workerRole, state.workerLang, state.workerActive ); if (!result.success) { dispatch({ type: "SET_WORKER_ERROR", value: result.error ?? "Failed" }); dispatch({ type: "SET_SUBMITTING", value: false }); return; } } else { const result = await createTimeWorker( brandId, state.workerName.trim(), state.workerRole, state.workerLang ); if (!result.success) { dispatch({ type: "SET_WORKER_ERROR", value: result.error ?? "Failed" }); dispatch({ type: "SET_SUBMITTING", value: false }); return; } } dispatch({ type: "SET_SUBMITTING", value: false }); dispatch({ type: "CLOSE_WORKER_MODAL" }); load(); }; const handleResetPin = async (workerId: string) => { const result = await resetTimeWorkerPin(workerId); if (result.success && result.pin) { dispatch({ type: "SET_RESET_PIN_RESULT", value: result.pin }); } else { dispatch({ type: "SET_WORKER_ERROR", value: result.error ?? "Failed to reset PIN" }); } }; const handleDeleteWorker = async (workerId: string) => { if (!confirm("Delete this worker? This cannot be undone.")) return; const result = await deleteTimeWorker(workerId); if (!result.success) { dispatch({ type: "SET_WORKER_ERROR", value: result.error ?? "Failed" }); return; } load(); }; const handleSaveTask = async () => { if (!state.taskName.trim()) { dispatch({ type: "SET_TASK_ERROR", value: "Name is required" }); return; } dispatch({ type: "SET_SUBMITTING", value: true }); dispatch({ type: "SET_TASK_ERROR", value: null }); if (state.editingTask) { const result = await updateTimeTask( state.editingTask.id, state.taskName.trim(), state.taskNameEs, state.taskUnit, state.taskActive, state.taskSortOrder ); if (!result.success) { dispatch({ type: "SET_TASK_ERROR", value: result.error ?? "Failed" }); dispatch({ type: "SET_SUBMITTING", value: false }); return; } } else { const result = await createTimeTask( brandId, state.taskName.trim(), state.taskNameEs || null, state.taskUnit, state.taskSortOrder ); if (!result.success) { dispatch({ type: "SET_TASK_ERROR", value: result.error ?? "Failed" }); dispatch({ type: "SET_SUBMITTING", value: false }); return; } } dispatch({ type: "SET_SUBMITTING", value: false }); dispatch({ type: "CLOSE_TASK_MODAL" }); load(); }; const handleDeleteTask = async (taskId: string) => { if (!confirm("Delete this task?")) return; const result = await deleteTimeTask(taskId); if (!result.success) { dispatch({ type: "SET_TASK_ERROR", value: result.error ?? "Failed" }); return; } load(); }; if (state.loading) { return ; } return (
{!workersOnly && !tasksOnly && ( dispatch({ type: "SET_PAY_PERIOD_START_DAY", value: v })} onSetPayPeriodLength={(v) => dispatch({ type: "SET_PAY_PERIOD_LENGTH", value: v })} onSetDailyOvertimeThreshold={(v) => dispatch({ type: "SET_DAILY_OVERTIME_THRESHOLD", value: v })} onSetWeeklyOvertimeThreshold={(v) => dispatch({ type: "SET_WEEKLY_OVERTIME_THRESHOLD", value: v })} onSetEnableDailyAlerts={(v) => dispatch({ type: "SET_ENABLE_DAILY_ALERTS", value: v })} onSetEnableWeeklyAlerts={(v) => dispatch({ type: "SET_ENABLE_WEEKLY_ALERTS", value: v })} onSetNewEmail={(v) => dispatch({ type: "SET_NEW_EMAIL", value: v })} onSetNewSms={(v) => dispatch({ type: "SET_NEW_SMS", value: v })} onAddEmail={() => dispatch({ type: "ADD_EMAIL", value: state.newEmail.trim() })} onRemoveEmail={(e) => dispatch({ type: "REMOVE_EMAIL", value: e })} onAddSms={() => dispatch({ type: "ADD_SMS", value: state.newSms.replace(/[^0-9+]/g, "") }) } onRemoveSms={(n) => dispatch({ type: "REMOVE_SMS", value: n })} onSave={handleSaveNotifications} /> )} {(workersOnly || (!workersOnly && !tasksOnly)) && ( dispatch({ type: "OPEN_ADD_WORKER" })} onEdit={(w) => dispatch({ type: "OPEN_EDIT_WORKER", worker: w })} onResetPin={handleResetPin} onDelete={handleDeleteWorker} /> )} {(tasksOnly || (!workersOnly && !tasksOnly)) && ( dispatch({ type: "OPEN_ADD_TASK" })} onEdit={(t) => dispatch({ type: "OPEN_EDIT_TASK", task: t })} onDelete={handleDeleteTask} /> )} {state.showWorkerModal && ( dispatch({ type: "SET_WORKER_NAME", value: v })} onSetWorkerRole={(v) => dispatch({ type: "SET_WORKER_ROLE", value: v })} onSetWorkerLang={(v) => dispatch({ type: "SET_WORKER_LANG", value: v })} onSetWorkerActive={(v) => dispatch({ type: "SET_WORKER_ACTIVE", value: v })} onResetPin={() => state.editingWorker && handleResetPin(state.editingWorker.id)} onClose={() => dispatch({ type: "CLOSE_WORKER_MODAL" })} onSave={handleSaveWorker} /> )} {state.showTaskModal && ( dispatch({ type: "SET_TASK_NAME", value: v })} onSetTaskNameEs={(v) => dispatch({ type: "SET_TASK_NAME_ES", value: v })} onSetTaskUnit={(v) => dispatch({ type: "SET_TASK_UNIT", value: v })} onSetTaskActive={(v) => dispatch({ type: "SET_TASK_ACTIVE", value: v })} onSetTaskSortOrder={(v) => dispatch({ type: "SET_TASK_SORT_ORDER", value: v })} onClose={() => dispatch({ type: "CLOSE_TASK_MODAL" })} onSave={handleSaveTask} /> )}
); } // ── LoadingState subcomponent ────────────────────────────────────────────── function LoadingState() { return (
Loading settings...
); } // ── GeneralSettingsAccordion subcomponent ────────────────────────────────── function GeneralSettingsAccordion({ payPeriodStartDay, payPeriodLength, dailyOvertimeThreshold, weeklyOvertimeThreshold, enableDailyAlerts, enableWeeklyAlerts, notificationEmails, notificationSmsNumbers, newEmail, newSms, settingsSaving, settingsSaved, settingsError, onSetPayPeriodStartDay, onSetPayPeriodLength, onSetDailyOvertimeThreshold, onSetWeeklyOvertimeThreshold, onSetEnableDailyAlerts, onSetEnableWeeklyAlerts, onSetNewEmail, onSetNewSms, onAddEmail, onRemoveEmail, onAddSms, onRemoveSms, onSave, }: { payPeriodStartDay: number; payPeriodLength: number; dailyOvertimeThreshold: number; weeklyOvertimeThreshold: number; enableDailyAlerts: boolean; enableWeeklyAlerts: boolean; notificationEmails: string[]; notificationSmsNumbers: string[]; newEmail: string; newSms: string; settingsSaving: boolean; settingsSaved: boolean; settingsError: string | null; onSetPayPeriodStartDay: (v: number) => void; onSetPayPeriodLength: (v: number) => void; onSetDailyOvertimeThreshold: (v: number) => void; onSetWeeklyOvertimeThreshold: (v: number) => void; onSetEnableDailyAlerts: (v: boolean) => void; onSetEnableWeeklyAlerts: (v: boolean) => void; onSetNewEmail: (v: string) => void; onSetNewSms: (v: string) => void; onAddEmail: () => void; onRemoveEmail: (e: string) => void; onAddSms: () => void; onRemoveSms: (n: string) => void; onSave: () => void; }) { return (
{/* Pay Period & Overtime */}

Pay Period & Overtime

onSetPayPeriodLength(Number(e.target.value))} className="flex-1 px-4 py-3 rounded-xl border border-stone-200 bg-white text-stone-900 focus:outline-none focus:border-emerald-500 transition-colors" /> days
onSetDailyOvertimeThreshold(Number(e.target.value))} className="flex-1 px-4 py-3 rounded-xl border border-stone-200 bg-white text-stone-900 focus:outline-none focus:border-emerald-500 transition-colors" /> hrs
onSetWeeklyOvertimeThreshold(Number(e.target.value))} className="flex-1 px-4 py-3 rounded-xl border border-stone-200 bg-white text-stone-900 focus:outline-none focus:border-emerald-500 transition-colors" /> hrs
{/* Colorado notice */}

Colorado Overtime Law

Colorado requires daily overtime (1.5×) after 12 hours in a workday, or weekly overtime after 40 hours in a workweek.

{/* Alert Settings */}

Alert Settings

Daily overtime alerts

Notify when worker hits daily limit

Weekly overtime alerts

Notify when worker hits weekly limit

{/* Notification Recipients */}

Notification Recipients

onSetNewEmail(e.target.value)} onKeyDown={e => e.key === "Enter" && (onAddEmail(), e.preventDefault())} className="flex-1 px-4 py-3 rounded-xl border border-stone-200 bg-white text-stone-900 placeholder:text-stone-400 focus:outline-none focus:border-emerald-500" placeholder="manager@farm.com" />
{notificationEmails.map(e => ( {e} ))}
onSetNewSms(e.target.value)} onKeyDown={e => e.key === "Enter" && (onAddSms(), e.preventDefault())} className="flex-1 px-4 py-3 rounded-xl border border-stone-200 bg-white text-stone-900 placeholder:text-stone-400 focus:outline-none focus:border-emerald-500" placeholder="+1234567890" />
{notificationSmsNumbers.map(n => ( {n} ))}
{settingsError && (
{settingsError}
)} {settingsSaved && (
Settings saved successfully!
)}
{settingsSaving ? ( Saving... ) : ( <> Save Settings )}
); } // ── WorkersAccordion subcomponent ────────────────────────────────────────── function WorkersAccordion({ workers, defaultOpen, onAdd, onEdit, onResetPin, onDelete, }: { workers: TimeWorker[]; defaultOpen?: boolean; onAdd: () => void; onEdit: (w: TimeWorker) => void; onResetPin: (id: string) => void; onDelete: (id: string) => void; }) { return (

Manage time tracking workers and PIN codes.

{workers.length === 0 ? ( ) : workers.map(w => ( ))}
Name Role Lang Status Last Used Actions
No workers yet — add one to get started.
{w.name} {w.role} {w.lang} {w.active ? "Active" : "Inactive"} {w.last_used_at ? new Date(w.last_used_at).toLocaleDateString() : "—"}
); } // ── TasksAccordion subcomponent ──────────────────────────────────────────── function TasksAccordion({ tasks, defaultOpen, onAdd, onEdit, onDelete, }: { tasks: TimeTask[]; defaultOpen?: boolean; onAdd: () => void; onEdit: (t: TimeTask) => void; onDelete: (id: string) => void; }) { return (

Define tasks workers can clock into.

{tasks.length === 0 ? ( ) : tasks.map(t => ( ))}
Name (EN) Name (ES) Unit Sort Status Actions
No tasks yet — add one to get started.
{t.name} {t.name_es ?? "—"} {t.unit} {t.sort_order} {t.active ? "Active" : "Inactive"}
); } // ── WorkerModal subcomponent ─────────────────────────────────────────────── function WorkerModal({ editingWorker, workerName, workerRole, workerLang, workerActive, resetPinResult, workerError, submitting, onSetWorkerName, onSetWorkerRole, onSetWorkerLang, onSetWorkerActive, onResetPin, onClose, onSave, }: { editingWorker: TimeWorker | null; workerName: string; workerRole: string; workerLang: string; workerActive: boolean; resetPinResult: string | null; workerError: string | null; submitting: boolean; onSetWorkerName: (v: string) => void; onSetWorkerRole: (v: string) => void; onSetWorkerLang: (v: string) => void; onSetWorkerActive: (v: boolean) => void; onResetPin: () => void; onClose: () => void; onSave: () => void; }) { return (

{editingWorker ? "Edit Worker" : "Add Worker"}

{workerError &&
{workerError}
} {resetPinResult && (

New PIN:

{resetPinResult}

Show this to the worker — it will not be shown again.

)}
onSetWorkerName(e.target.value)} className="w-full px-4 py-3 rounded-xl border border-stone-200 bg-white text-stone-900 placeholder:text-stone-400 focus:outline-none focus:border-emerald-500 transition-colors" placeholder="Worker name" />
onSetWorkerActive(e.target.checked)} className="w-4 h-4 rounded border-stone-300 bg-white text-emerald-600" />
{editingWorker && (
)}
); } // ── TaskModal subcomponent ───────────────────────────────────────────────── function TaskModal({ editingTask, taskName, taskNameEs, taskUnit, taskActive, taskSortOrder, taskError, submitting, onSetTaskName, onSetTaskNameEs, onSetTaskUnit, onSetTaskActive, onSetTaskSortOrder, onClose, onSave, }: { editingTask: TimeTask | null; taskName: string; taskNameEs: string; taskUnit: string; taskActive: boolean; taskSortOrder: number; taskError: string | null; submitting: boolean; onSetTaskName: (v: string) => void; onSetTaskNameEs: (v: string) => void; onSetTaskUnit: (v: string) => void; onSetTaskActive: (v: boolean) => void; onSetTaskSortOrder: (v: number) => void; onClose: () => void; onSave: () => void; }) { return (

{editingTask ? "Edit Task" : "Add Task"}

{taskError &&
{taskError}
}
onSetTaskName(e.target.value)} className="w-full px-4 py-3 rounded-xl border border-stone-200 bg-white text-stone-900 placeholder:text-stone-400 focus:outline-none focus:border-emerald-500 transition-colors" placeholder="e.g. Harvesting" />
onSetTaskNameEs(e.target.value)} className="w-full px-4 py-3 rounded-xl border border-stone-200 bg-white text-stone-900 placeholder:text-stone-400 focus:outline-none focus:border-emerald-500 transition-colors" placeholder="e.g. Cosecha" />
onSetTaskSortOrder(parseInt(e.target.value) || 0)} className="w-full px-4 py-3 rounded-xl border border-stone-200 bg-white text-stone-900 focus:outline-none focus:border-emerald-500 transition-colors" />
onSetTaskActive(e.target.checked)} className="w-4 h-4 rounded border-stone-300 bg-white text-emerald-600" />
); }