"use client"; /* eslint-disable react-hooks/set-state-in-effect */ import { useState, useEffect, useCallback } from "react"; import { getTimeTrackingWorkers, getTimeTrackingTasks, createTimeWorker, resetTimeWorkerPin, updateTimeWorker, deleteTimeWorker, createTimeTask, updateTimeTask, deleteTimeTask, getTimeTrackingSettings, updateTimeTrackingSettings, getTimeTrackingNotificationLog, type TimeWorker, type TimeTask, type TimeTrackingSettings, type NotificationLogEntry, } from "@/actions/time-tracking"; import { AdminButton } from "@/components/admin/design-system"; // One-color outline icons const Icons = { clock: (className: string) => ( ), dollarSign: (className: string) => ( ), clipboard: (className: string) => ( ), chart: (className: string) => ( ), check: (className: string) => ( ), alert: (className: string) => ( ), }; type Tab = "dashboard" | "settings"; const DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; interface TimeTrackingSettingsClientProps { brandId: string; } export default function TimeTrackingSettingsClient({ brandId }: TimeTrackingSettingsClientProps) { const [tab, setTab] = useState("dashboard"); useEffect(() => { const params = new URLSearchParams(window.location.search); const t = params.get("tab"); if (t === "settings" || t === "export") setTab(t as Tab); }, []); const [workers, setWorkers] = useState([]); const [tasks, setTasks] = useState([]); const [settings, setSettings] = useState(null); const [notificationLog, setNotificationLog] = useState([]); const [loading, setLoading] = useState(true); const [payPeriodStartDay, setPayPeriodStartDay] = useState(0); const [payPeriodLength, setPayPeriodLength] = useState(7); const [dailyOvertimeThreshold, setDailyOvertimeThreshold] = useState(12); const [weeklyOvertimeThreshold, setWeeklyOvertimeThreshold] = useState(56); const [overtimeMultiplier, setOvertimeMultiplier] = useState(1.5); const [overtimeNotifications, setOvertimeNotifications] = useState(true); const [settingsSaving, setSettingsSaving] = useState(false); const [settingsSaved, setSettingsSaved] = useState(false); const [settingsError, setSettingsError] = useState(null); const [notificationEmails, setNotificationEmails] = useState([]); const [notificationSmsNumbers, setNotificationSmsNumbers] = useState([]); const [enableDailyAlerts, setEnableDailyAlerts] = useState(true); const [enableWeeklyAlerts, setEnableWeeklyAlerts] = useState(true); const [dailyAlertThreshold, setDailyAlertThreshold] = useState(80); const [weeklyAlertThreshold, setWeeklyAlertThreshold] = useState(80); const [sendEndOfPeriodSummary, setSendEndOfPeriodSummary] = useState(true); const [brandName, setBrandName] = useState("Farm"); const [newEmail, setNewEmail] = useState(""); const [newSms, setNewSms] = useState(""); const [showWorkerModal, setShowWorkerModal] = useState(false); const [editingWorker, setEditingWorker] = useState(null); const [workerName, setWorkerName] = useState(""); const [workerRole, setWorkerRole] = useState("worker"); const [workerLang, setWorkerLang] = useState("en"); const [workerActive, setWorkerActive] = useState(true); const [resetPinResult, setResetPinResult] = useState(null); const [workerError, setWorkerError] = useState(null); const [submitting, setSubmitting] = useState(false); const [showTaskModal, setShowTaskModal] = useState(false); const [editingTask, setEditingTask] = useState(null); const [taskName, setTaskName] = useState(""); const [taskNameEs, setTaskNameEs] = useState(""); const [taskUnit, setTaskUnit] = useState("hours"); const [taskActive, setTaskActive] = useState(true); const [taskSortOrder, setTaskSortOrder] = useState(0); const [taskError, setTaskError] = useState(null); const [exportStart, setExportStart] = useState(""); const [exportEnd, setExportEnd] = useState(""); const [exportBrand, setExportBrand] = useState<"all" | "tuxedo" | "ird">("all"); const [includeOvertime, setIncludeOvertime] = useState(true); const [includeNotes, setIncludeNotes] = useState(true); const load = useCallback(async () => { setLoading(true); const [w, t, s] = await Promise.all([ getTimeTrackingWorkers(brandId), getTimeTrackingTasks(brandId, false), getTimeTrackingSettings(brandId), ]); setWorkers(w); setTasks(t); if (s) { setSettings(s); setPayPeriodStartDay(s.pay_period_start_day); setPayPeriodLength(s.pay_period_length_days); setDailyOvertimeThreshold(s.daily_overtime_threshold); setWeeklyOvertimeThreshold(s.weekly_overtime_threshold); setOvertimeMultiplier(s.overtime_multiplier); setOvertimeNotifications(s.overtime_notifications); setNotificationEmails(s.notification_emails ?? []); setNotificationSmsNumbers(s.notification_sms_numbers ?? []); setEnableDailyAlerts(s.enable_daily_alerts ?? true); setEnableWeeklyAlerts(s.enable_weekly_alerts ?? true); setDailyAlertThreshold(Math.round((s.daily_alert_threshold ?? 0.80) * 100)); setWeeklyAlertThreshold(Math.round((s.weekly_alert_threshold ?? 0.80) * 100)); setSendEndOfPeriodSummary(s.send_end_of_period_summary ?? true); setBrandName(s.brand_name ?? "Farm"); } const log = await getTimeTrackingNotificationLog(brandId, 50); setNotificationLog(log); setLoading(false); }, [brandId]); useEffect(() => { load(); }, [load]); const handleSaveNotifications = async () => { setSettingsSaving(true); setSettingsError(null); setSettingsSaved(false); const result = await updateTimeTrackingSettings(brandId, { pay_period_start_day: payPeriodStartDay, pay_period_length_days: payPeriodLength, daily_overtime_threshold: dailyOvertimeThreshold, weekly_overtime_threshold: weeklyOvertimeThreshold, overtime_multiplier: overtimeMultiplier, overtime_notifications: overtimeNotifications, notification_emails: notificationEmails, notification_sms_numbers: notificationSmsNumbers, enable_daily_alerts: enableDailyAlerts, enable_weekly_alerts: enableWeeklyAlerts, daily_alert_threshold: dailyAlertThreshold / 100, weekly_alert_threshold: weeklyAlertThreshold / 100, send_end_of_period_summary: sendEndOfPeriodSummary, brand_name: brandName, }); setSettingsSaving(false); if (!result.success) { setSettingsError(result.error ?? "Failed to save"); return; } setSettingsSaved(true); setTimeout(() => setSettingsSaved(false), 3000); load(); }; const addEmail = () => { const email = newEmail.trim(); if (email && !notificationEmails.includes(email)) { setNotificationEmails([...notificationEmails, email]); } setNewEmail(""); }; const removeEmail = (email: string) => setNotificationEmails(notificationEmails.filter(e => e !== email)); const addSms = () => { const num = newSms.replace(/[^0-9+]/g, ""); if (num && !notificationSmsNumbers.includes(num)) { setNotificationSmsNumbers([...notificationSmsNumbers, num]); } setNewSms(""); }; const removeSms = (num: string) => setNotificationSmsNumbers(notificationSmsNumbers.filter(n => n !== num)); function buildExportUrl(type: "quickbooks" | "payroll" | "detailed" | "summary") { const params = new URLSearchParams({ type, start: exportStart, end: exportEnd, brand: exportBrand, overtime: String(includeOvertime), notes: String(includeNotes), }); return `/api/time-tracking/export?${params}`; } function triggerDownload(url: string) { const a = document.createElement("a"); a.href = url; a.download = ""; document.body.appendChild(a); a.click(); document.body.removeChild(a); } const openAddWorker = () => { setEditingWorker(null); setWorkerName(""); setWorkerRole("worker"); setWorkerLang("en"); setWorkerActive(true); setWorkerError(null); setResetPinResult(null); setShowWorkerModal(true); }; const openEditWorker = (w: TimeWorker) => { setEditingWorker(w); setWorkerName(w.name); setWorkerRole(w.role); setWorkerLang(w.lang); setWorkerActive(w.active); setWorkerError(null); setResetPinResult(null); setShowWorkerModal(true); }; const handleSaveWorker = async () => { if (!workerName.trim()) { setWorkerError("Name is required"); return; } setSubmitting(true); setWorkerError(null); if (editingWorker) { const result = await updateTimeWorker(editingWorker.id, workerName.trim(), workerRole, workerLang, workerActive); if (!result.success) { setWorkerError(result.error ?? "Failed"); setSubmitting(false); return; } } else { const result = await createTimeWorker(brandId, workerName.trim(), workerRole, workerLang); if (!result.success) { setWorkerError(result.error ?? "Failed"); setSubmitting(false); return; } } setSubmitting(false); setShowWorkerModal(false); load(); }; const handleResetPin = async (workerId: string) => { const result = await resetTimeWorkerPin(workerId); if (result.success && result.pin) setResetPinResult(result.pin); else setWorkerError(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) { setWorkerError(result.error ?? "Failed"); return; } load(); }; const openAddTask = () => { setEditingTask(null); setTaskName(""); setTaskNameEs(""); setTaskUnit("hours"); setTaskActive(true); setTaskSortOrder(0); setTaskError(null); setShowTaskModal(true); }; const openEditTask = (t: TimeTask) => { setEditingTask(t); setTaskName(t.name); setTaskNameEs(t.name_es ?? ""); setTaskUnit(t.unit); setTaskActive(t.active); setTaskSortOrder(t.sort_order); setTaskError(null); setShowTaskModal(true); }; const handleSaveTask = async () => { if (!taskName.trim()) { setTaskError("Name is required"); return; } setSubmitting(true); setTaskError(null); if (editingTask) { const result = await updateTimeTask(editingTask.id, taskName.trim(), taskNameEs, taskUnit, taskActive, taskSortOrder); if (!result.success) { setTaskError(result.error ?? "Failed"); setSubmitting(false); return; } } else { const result = await createTimeTask(brandId, taskName.trim(), taskNameEs || null, taskUnit, taskSortOrder); if (!result.success) { setTaskError(result.error ?? "Failed"); setSubmitting(false); return; } } setSubmitting(false); setShowTaskModal(false); load(); }; const handleDeleteTask = async (taskId: string) => { if (!confirm("Delete this task?")) return; const result = await deleteTimeTask(taskId); if (!result.success) { setTaskError(result.error ?? "Failed"); return; } load(); }; const triggerLabel: Record = { daily_approaching: { en: "Daily OT Approaching", color: "bg-amber-100 text-amber-700" }, daily_reached: { en: "Daily OT Reached", color: "bg-red-100 text-red-700" }, weekly_approaching: { en: "Weekly OT Approaching", color: "bg-amber-100 text-amber-700" }, weekly_reached: { en: "Weekly OT Reached", color: "bg-red-100 text-red-700" }, end_of_period_summary:{ en: "Period Summary", color: "bg-green-100 text-green-700" }, }; if (loading) return
Loading...
; return (
{/* Dashboard tab */} {tab === "dashboard" && (
{/* Stat row */}

{workers.length}

Workers

{tasks.length}

Tasks

{notificationLog.filter(n => n.email_sent || n.sms_sent).length}

Alerts Sent

{/* Quick export section */}

Export Data

From: setExportStart(e.target.value)} className="px-3 py-1.5 rounded-lg border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] focus:outline-none focus:border-[var(--admin-accent)]" /> To: setExportEnd(e.target.value)} className="px-3 py-1.5 rounded-lg border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] focus:outline-none focus:border-[var(--admin-accent)]" />
{/* Notification log */}

Recent Alerts

{notificationLog.length === 0 ? ( ) : notificationLog.slice(0, 20).map(entry => { const meta = triggerLabel[entry.trigger_type] ?? { en: entry.trigger_type, color: "bg-stone-100 text-stone-500" }; return ( ); })}
Time Worker Alert Email SMS
No alerts sent yet
{new Date(entry.created_at).toLocaleString()} {entry.worker_name ?? "—"} {meta.en} {Icons.check("h-4 w-4")} {entry.sms_sent ? Icons.check("h-4 w-4 text-[var(--admin-accent)]") : "—"}
)} {/* Settings tab */} {tab === "settings" && (
{/* Workers section */}

Workers & PINs

+ Add Worker
{workers.length === 0 ? ( ) : workers.map(w => ( ))}
Name Role Lang Status Last Used Actions
No workers yet
{w.name} {w.role} {w.lang} {w.active ? "Active" : "Inactive"} {w.last_used_at ? new Date(w.last_used_at).toLocaleDateString() : "—"}
openEditWorker(w)}>Edit handleResetPin(w.id)}>Reset PIN handleDeleteWorker(w.id)}>Delete
{/* Tasks section */}

Tasks

+ Add Task
{tasks.length === 0 ? ( ) : tasks.map(t => ( ))}
Name Name (ES) Unit Sort Status Actions
No tasks yet
{t.name} {t.name_es ?? "—"} {t.unit} {t.sort_order} {t.active ? "Active" : "Inactive"}
openEditTask(t)}>Edit handleDeleteTask(t.id)}>Delete
{/* Pay Period & Overtime section */}

Pay Period & Overtime

setPayPeriodLength(Number(e.target.value))} className="flex-1 px-4 py-3 rounded-xl border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] focus:outline-none focus:border-[var(--admin-accent)] transition-colors" /> days
setDailyOvertimeThreshold(Number(e.target.value))} className="flex-1 px-4 py-3 rounded-xl border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] focus:outline-none focus:border-[var(--admin-accent)] transition-colors" /> hrs
setWeeklyOvertimeThreshold(Number(e.target.value))} className="flex-1 px-4 py-3 rounded-xl border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] focus:outline-none focus:border-[var(--admin-accent)] transition-colors" /> hrs
{/* Colorado notice */}
{Icons.alert("h-5 w-5")}

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

Send notification when worker hits daily threshold

Weekly overtime alerts

Send notification when worker hits weekly threshold

Show overtime warning on employee screen

Display alert when worker approaches thresholds

{/* Notification Recipients */}

Notification Recipients

setNewEmail(e.target.value)} onKeyDown={e => e.key === "Enter" && (addEmail(), e.preventDefault())} className="flex-1 px-4 py-3 rounded-xl border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] placeholder:text-[var(--admin-text-muted)] focus:outline-none focus:border-[var(--admin-accent)] transition-colors" placeholder="manager@farm.com" /> Add
{notificationEmails.map(e => ( {e} ))}
setNewSms(e.target.value)} onKeyDown={e => e.key === "Enter" && (addSms(), e.preventDefault())} className="flex-1 px-4 py-3 rounded-xl border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] placeholder:text-[var(--admin-text-muted)] focus:outline-none focus:border-[var(--admin-accent)] transition-colors" placeholder="+1234567890" /> Add
{notificationSmsNumbers.map(n => ( {n} ))}
{settingsError && (
{settingsError}
)} {settingsSaved && (
Settings saved successfully.
)} Save Settings
)} {/* Worker Modal */} {showWorkerModal && (

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

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

New PIN:

{resetPinResult}

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

)}
setWorkerName(e.target.value)} className="w-full px-4 py-3 rounded-xl border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] placeholder:text-[var(--admin-text-muted)] focus:outline-none focus:border-[var(--admin-accent)] transition-colors" placeholder="Worker name" />
setWorkerActive(e.target.checked)} className="w-4 h-4 rounded border-[var(--admin-border)] bg-white text-[var(--admin-accent)]" />
{editingWorker && (
handleResetPin(editingWorker.id)}> Reset PIN for this worker
)}
setShowWorkerModal(false)} fullWidth> Cancel {submitting ? "..." : editingWorker ? "Save" : "Add Worker"}
)} {/* Task Modal */} {showTaskModal && (

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

{taskError &&
{taskError}
}
setTaskName(e.target.value)} className="w-full px-4 py-3 rounded-xl border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] placeholder:text-[var(--admin-text-muted)] focus:outline-none focus:border-[var(--admin-accent)] transition-colors" placeholder="e.g. Harvesting" />
setTaskNameEs(e.target.value)} className="w-full px-4 py-3 rounded-xl border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] placeholder:text-[var(--admin-text-muted)] focus:outline-none focus:border-[var(--admin-accent)] transition-colors" placeholder="e.g. Cosecha" />
setTaskSortOrder(parseInt(e.target.value) || 0)} className="w-full px-4 py-3 rounded-xl border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] focus:outline-none focus:border-[var(--admin-accent)] transition-colors" />
setTaskActive(e.target.checked)} className="w-4 h-4 rounded border-[var(--admin-border)] bg-white text-[var(--admin-accent)]" />
setShowTaskModal(false)} fullWidth> Cancel {submitting ? "..." : editingTask ? "Save" : "Add Task"}
)}
); }