"use client"; import React, { useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import SidebarLayout from "@/components/SidebarLayout"; import { fetchApi } from "@/app/utils/api"; import { Check, Loader2, CheckCircle2, AlertCircle, Sliders, Info, Fingerprint, Clock, Volume2 } from "lucide-react"; type SettingsTab = "biometrics" | "shift" | "voice" | "advanced"; export default function SettingsPage() { const queryClient = useQueryClient(); const [activeTab, setActiveTab] = useState("biometrics"); const [updatingKey, setUpdatingKey] = useState(null); const [successKey, setSuccessKey] = useState(null); const [editValues, setEditValues] = useState>({}); const { data: settings, isLoading } = useQuery({ queryKey: ["settings"], queryFn: async () => { const data = await fetchApi("/settings/"); const valMap: Record = {}; data.forEach((s: any) => { valMap[s.key] = s.value; }); setEditValues(valMap); return data; } }); const saveMutation = useMutation({ mutationFn: ({ key, value }: { key: string; value: string }) => fetchApi(`/settings/${key}`, { method: "PUT", body: JSON.stringify({ value }) }), onSuccess: (data: any) => { queryClient.invalidateQueries({ queryKey: ["settings"] }); setSuccessKey(data.key); setUpdatingKey(null); setTimeout(() => setSuccessKey(null), 3000); }, onError: (err: any, vars) => { setUpdatingKey(null); alert(err.message || `Failed to save ${vars.key}`); } }); const handleSave = (key: string) => { setUpdatingKey(key); saveMutation.mutate({ key, value: editValues[key] }); }; // Group settings by keys for our tabs const getTabForSettings = (key: string): SettingsTab => { if (key.includes("FACE") || key.includes("LIVENESS")) return "biometrics"; if (key.includes("SHIFT") || key.includes("CHECK") || key.includes("GRACE")) return "shift"; if (key.includes("VOICE") || key.includes("GREETING")) return "voice"; return "advanced"; }; const formatSettingKey = (key: string) => { return key .split("_") .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(" "); }; const filteredSettings = settings?.filter((s: any) => getTabForSettings(s.key) === activeTab) || []; const booleanSettings = filteredSettings.filter((s: any) => { const val = editValues[s.key] || ""; return s.key === "VOICE_GREETING_ENABLED" || s.key === "SYSTEM_MAINTENANCE_MODE" || val === "true" || val === "false"; }); const standardSettings = filteredSettings.filter((s: any) => { const val = editValues[s.key] || ""; return !(s.key === "VOICE_GREETING_ENABLED" || s.key === "SYSTEM_MAINTENANCE_MODE" || val === "true" || val === "false"); }); const isAutosaveKey = (key: string) => { return key === "VOICE_GREETING_ENABLED" || key === "SYSTEM_MAINTENANCE_MODE"; }; const renderSettingControl = (setting: any, isUpdating: boolean) => { const key = setting.key; const val = editValues[key] || ""; // Toggle for Booleans (with Auto-save) if (key === "VOICE_GREETING_ENABLED" || key === "SYSTEM_MAINTENANCE_MODE" || val === "true" || val === "false") { const isChecked = val === "true"; const nextVal = isChecked ? "false" : "true"; return ( ); } // Range Slider for Thresholds if (key.includes("THRESHOLD")) { const numVal = parseFloat(val) || 0; return (
setEditValues(prev => ({ ...prev, [key]: e.target.value }))} className="w-full h-1 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-slate-900" /> {numVal.toFixed(2)}
); } // Time inputs for check-in/out if (key.includes("START") || key.includes("END")) { return ( setEditValues(prev => ({ ...prev, [key]: e.target.value }))} className="input-field h-9 text-[12px] w-28 bg-white border-slate-200 rounded-xl px-2.5 focus:border-slate-800 transition-all text-center" /> ); } // Number input for grace period, image limits, or time counters if (key.includes("MINUTES") || key.includes("PERIOD") || key.includes("IMAGES") || key.includes("SECONDS") || key.includes("MAX_")) { const unit = key.includes("MINUTES") ? "min" : key.includes("SECONDS") ? "sec" : key.includes("IMAGES") ? "imgs" : ""; return (
setEditValues(prev => ({ ...prev, [key]: e.target.value }))} className="input-field h-9 text-[12px] w-20 bg-white border-slate-200 rounded-xl px-2.5 focus:border-slate-800 transition-all text-center" /> {unit && {unit}}
); } // Default Fallback return ( setEditValues(prev => ({ ...prev, [key]: e.target.value }))} className="input-field h-9 text-[12.5px] w-36 bg-white border-slate-200 rounded-xl px-3 focus:border-slate-800 transition-all text-center" /> ); }; const getTabIcon = (tab: SettingsTab) => { switch(tab) { case "biometrics": return ; case "shift": return ; case "voice": return ; case "advanced": return ; } }; const getTabLabel = (tab: SettingsTab) => { switch(tab) { case "biometrics": return "Biometrics & AI"; case "shift": return "Shift & Grace Hours"; case "voice": return "Voice Alerts"; case "advanced": return "Advanced Configurations"; } }; const getTabDesc = (tab: SettingsTab) => { switch(tab) { case "biometrics": return "Configure facial matching similarity and liveness parameters"; case "shift": return "Manage daily check-in start, grace window, and shifts"; case "voice": return "Enable or disable text-to-speech audio feedback at terminals"; case "advanced": return "System variables and advanced parameters"; } }; return (
{/* Header */}

System Configuration

Optimize biometric accuracy rates, manage shift schedule rules, and toggle voice assistant audio.

{/* Outer container */}
{/* Left Navigation: Vertical tabs */}
{(["biometrics", "shift", "voice", "advanced"] as SettingsTab[]).map(tab => { const isActive = activeTab === tab; return ( ); })}
{/* Right Panels */}
{/* Setting List Card */}
{/* Card Header - Beautifully light without background */}

{getTabLabel(activeTab)}

{getTabDesc(activeTab)}

{isLoading ? (
{Array.from({ length: 3 }).map((_, i) => (
))}
) : filteredSettings.length === 0 ? (
No configuration parameters found under this category.
) : (
{/* Grid layout for Boolean Settings (Modern Feature Cards) */} {booleanSettings.length > 0 && (
{booleanSettings.map((setting: any) => { const key = setting.key; const val = editValues[key] || ""; const isChecked = val === "true"; const nextVal = isChecked ? "false" : "true"; const isUpdating = updatingKey === key; const isSuccess = successKey === key; const getSettingIcon = (k: string) => { if (k.includes("VOICE") || k.includes("GREETING")) return ; if (k.includes("MAINTENANCE")) return ; return ; }; return (
{ if (!isUpdating) { setEditValues(prev => ({ ...prev, [key]: nextVal })); setUpdatingKey(key); saveMutation.mutate({ key, value: nextVal }); } }} className={`group p-5 rounded-2xl border transition-all duration-300 cursor-pointer flex flex-col justify-between h-42 select-none ${ isChecked ? "bg-slate-900 border-slate-900 text-white shadow-md shadow-slate-900/10" : "bg-white border-slate-200 text-slate-800 hover:border-slate-350 hover:shadow-sm" } ${isUpdating ? "opacity-60 pointer-events-none" : ""}`} >
{getSettingIcon(key)}
{isUpdating ? ( ) : isSuccess ? ( ) : (
)}

{formatSettingKey(key)}

{setting.description || "System configuration parameter"}

{isChecked ? "ACTIVE" : "DISABLED"}
); })}
)} {/* Standard Settings list */} {standardSettings.length > 0 && (
{standardSettings.map((setting: any) => { const isUpdating = updatingKey === setting.key; const isSuccess = successKey === setting.key; const isDirty = (() => { const currentVal = editValues[setting.key]; const originalVal = setting.value; if (currentVal === undefined || originalVal === undefined) return false; if (setting.key.includes("THRESHOLD")) { return parseFloat(currentVal) !== parseFloat(originalVal); } return currentVal !== originalVal; })(); return (

{formatSettingKey(setting.key)}

{setting.description || "System configuration parameter"}

{renderSettingControl(setting, isUpdating)}
); })}
)}
)}
{/* Info alerts */} {activeTab === "biometrics" && (
Tip: Lower face threshold values increase matching speed but may cause false matches. Keep liveness between 0.70 - 0.85 for optimal anti-spoofing.
)} {activeTab === "shift" && (
Note: Check-ins past the start time plus the grace window will automatically be flagged as Late.
)}
); }