Admin AI Tools page: unified design system styling, provider selector with OpenAI turd emoji, free-text model input with exact ID warning
This commit is contained in:
@@ -75,9 +75,11 @@ function Accordion({ title, id, description, defaultOpen = false, accentColor =
|
||||
|
||||
type Props = {
|
||||
brandId: string;
|
||||
workersOnly?: boolean;
|
||||
tasksOnly?: boolean;
|
||||
};
|
||||
|
||||
export default function SettingsSections({ brandId }: Props) {
|
||||
export default function SettingsSections({ brandId, workersOnly, tasksOnly }: Props) {
|
||||
const [workers, setWorkers] = useState<TimeWorker[]>([]);
|
||||
const [tasks, setTasks] = useState<TimeTask[]>([]);
|
||||
const [settings, setSettings] = useState<TimeTrackingSettings | null>(null);
|
||||
@@ -292,261 +294,268 @@ export default function SettingsSections({ brandId }: Props) {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{(!workersOnly && !tasksOnly) && (
|
||||
<>
|
||||
{/* ACCORDION 1: General Settings */}
|
||||
<Accordion
|
||||
id="general"
|
||||
title="General Settings"
|
||||
description="Pay period, overtime, alerts"
|
||||
defaultOpen={true}
|
||||
accentColor="stone"
|
||||
>
|
||||
<div className="space-y-5">
|
||||
{/* Pay Period & Overtime */}
|
||||
<div className="bg-stone-50 border border-stone-200 rounded-xl p-5">
|
||||
<h3 className="text-sm font-semibold text-stone-800 mb-4">Pay Period & Overtime</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2">Work week starts on</label>
|
||||
<select value={payPeriodStartDay} onChange={e => setPayPeriodStartDay(Number(e.target.value))}
|
||||
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">
|
||||
{DAYS.map((d, i) => <option key={i} value={i}>{d}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2">Pay period length</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input type="number" min={1} max={31} value={payPeriodLength}
|
||||
onChange={e => setPayPeriodLength(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" />
|
||||
<span className="text-sm text-stone-500">days</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2">Daily overtime threshold</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input type="number" min={1} max={24} value={dailyOvertimeThreshold}
|
||||
onChange={e => setDailyOvertimeThreshold(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" />
|
||||
<span className="text-sm text-stone-500">hrs</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2">Weekly overtime threshold</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input type="number" min={1} max={80} value={weeklyOvertimeThreshold}
|
||||
onChange={e => setWeeklyOvertimeThreshold(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" />
|
||||
<span className="text-sm text-stone-500">hrs</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ACCORDION 1: General Settings */}
|
||||
<Accordion
|
||||
id="general"
|
||||
title="General Settings"
|
||||
description="Pay period, overtime, alerts"
|
||||
defaultOpen={true}
|
||||
accentColor="stone"
|
||||
>
|
||||
<div className="space-y-5">
|
||||
{/* Pay Period & Overtime */}
|
||||
<div className="bg-stone-50 border border-stone-200 rounded-xl p-5">
|
||||
<h3 className="text-sm font-semibold text-stone-800 mb-4">Pay Period & Overtime</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2">Work week starts on</label>
|
||||
<select value={payPeriodStartDay} onChange={e => setPayPeriodStartDay(Number(e.target.value))}
|
||||
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">
|
||||
{DAYS.map((d, i) => <option key={i} value={i}>{d}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2">Pay period length</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input type="number" min={1} max={31} value={payPeriodLength}
|
||||
onChange={e => setPayPeriodLength(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" />
|
||||
<span className="text-sm text-stone-500">days</span>
|
||||
{/* Colorado notice */}
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="text-amber-600 text-base mt-0.5">⚠️</span>
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-amber-800">Colorado Overtime Law</p>
|
||||
<p className="text-xs text-amber-700 mt-1">Colorado requires daily overtime (1.5×) after 12 hours in a workday, or weekly overtime after 40 hours in a workweek.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2">Daily overtime threshold</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input type="number" min={1} max={24} value={dailyOvertimeThreshold}
|
||||
onChange={e => setDailyOvertimeThreshold(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" />
|
||||
<span className="text-sm text-stone-500">hrs</span>
|
||||
|
||||
{/* Alert Settings */}
|
||||
<div className="bg-stone-50 border border-stone-200 rounded-xl p-5">
|
||||
<h3 className="text-sm font-semibold text-stone-800 mb-4">Alert Settings</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-stone-700 font-medium">Daily overtime alerts</p>
|
||||
<p className="text-xs text-stone-500">Notify when worker hits daily limit</p>
|
||||
</div>
|
||||
<button onClick={() => setEnableDailyAlerts(!enableDailyAlerts)}
|
||||
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${enableDailyAlerts ? "bg-emerald-600" : "bg-stone-300"}`}>
|
||||
<span className={`inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform ${enableDailyAlerts ? "translate-x-4" : "translate-x-1"}`} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-stone-700 font-medium">Weekly overtime alerts</p>
|
||||
<p className="text-xs text-stone-500">Notify when worker hits weekly limit</p>
|
||||
</div>
|
||||
<button onClick={() => setEnableWeeklyAlerts(!enableWeeklyAlerts)}
|
||||
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${enableWeeklyAlerts ? "bg-emerald-600" : "bg-stone-300"}`}>
|
||||
<span className={`inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform ${enableWeeklyAlerts ? "translate-x-4" : "translate-x-1"}`} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2">Weekly overtime threshold</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input type="number" min={1} max={80} value={weeklyOvertimeThreshold}
|
||||
onChange={e => setWeeklyOvertimeThreshold(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" />
|
||||
<span className="text-sm text-stone-500">hrs</span>
|
||||
|
||||
{/* Notification Recipients */}
|
||||
<div className="bg-stone-50 border border-stone-200 rounded-xl p-5">
|
||||
<h3 className="text-sm font-semibold text-stone-800 mb-4">Notification Recipients</h3>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2">Email addresses</label>
|
||||
<div className="flex gap-2">
|
||||
<input value={newEmail}
|
||||
onChange={e => setNewEmail(e.target.value)}
|
||||
onKeyDown={e => e.key === "Enter" && (addEmail(), 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" />
|
||||
<button onClick={addEmail} className="px-5 py-3 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white font-semibold text-sm">Add</button>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{notificationEmails.map(e => (
|
||||
<span key={e} className="inline-flex items-center gap-1.5 bg-stone-100 text-stone-700 text-xs px-2.5 py-1 rounded-lg">
|
||||
{e}
|
||||
<button onClick={() => removeEmail(e)} className="text-stone-400 hover:text-red-500 ml-1">×</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2">SMS numbers</label>
|
||||
<div className="flex gap-2">
|
||||
<input value={newSms}
|
||||
onChange={e => setNewSms(e.target.value)}
|
||||
onKeyDown={e => e.key === "Enter" && (addSms(), 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" />
|
||||
<button onClick={addSms} className="px-5 py-3 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white font-semibold text-sm">Add</button>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{notificationSmsNumbers.map(n => (
|
||||
<span key={n} className="inline-flex items-center gap-1.5 bg-stone-100 text-stone-700 text-xs px-2.5 py-1 rounded-lg">
|
||||
{n}
|
||||
<button onClick={() => removeSms(n)} className="text-stone-400 hover:text-red-500 ml-1">×</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{settingsError && (
|
||||
<div className="bg-red-50 border border-red-200 rounded-xl py-3 px-4 text-red-700 text-sm">{settingsError}</div>
|
||||
)}
|
||||
{settingsSaved && (
|
||||
<div className="bg-emerald-50 border border-emerald-200 rounded-xl py-3 px-4 text-emerald-700 text-sm">Settings saved.</div>
|
||||
)}
|
||||
<button onClick={handleSaveNotifications} disabled={settingsSaving}
|
||||
className="px-6 py-3 rounded-xl bg-stone-900 hover:bg-stone-800 disabled:opacity-40 font-semibold text-sm text-white transition-all">
|
||||
{settingsSaving ? "Saving..." : "Save Settings"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Colorado notice */}
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="text-amber-600 text-base mt-0.5">⚠️</span>
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-amber-800">Colorado Overtime Law</p>
|
||||
<p className="text-xs text-amber-700 mt-1">Colorado requires daily overtime (1.5×) after 12 hours in a workday, or weekly overtime after 40 hours in a workweek.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Alert Settings */}
|
||||
<div className="bg-stone-50 border border-stone-200 rounded-xl p-5">
|
||||
<h3 className="text-sm font-semibold text-stone-800 mb-4">Alert Settings</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-stone-700 font-medium">Daily overtime alerts</p>
|
||||
<p className="text-xs text-stone-500">Notify when worker hits daily limit</p>
|
||||
</div>
|
||||
<button onClick={() => setEnableDailyAlerts(!enableDailyAlerts)}
|
||||
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${enableDailyAlerts ? "bg-emerald-600" : "bg-stone-300"}`}>
|
||||
<span className={`inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform ${enableDailyAlerts ? "translate-x-4" : "translate-x-1"}`} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-stone-700 font-medium">Weekly overtime alerts</p>
|
||||
<p className="text-xs text-stone-500">Notify when worker hits weekly limit</p>
|
||||
</div>
|
||||
<button onClick={() => setEnableWeeklyAlerts(!enableWeeklyAlerts)}
|
||||
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${enableWeeklyAlerts ? "bg-emerald-600" : "bg-stone-300"}`}>
|
||||
<span className={`inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform ${enableWeeklyAlerts ? "translate-x-4" : "translate-x-1"}`} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Notification Recipients */}
|
||||
<div className="bg-stone-50 border border-stone-200 rounded-xl p-5">
|
||||
<h3 className="text-sm font-semibold text-stone-800 mb-4">Notification Recipients</h3>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2">Email addresses</label>
|
||||
<div className="flex gap-2">
|
||||
<input value={newEmail}
|
||||
onChange={e => setNewEmail(e.target.value)}
|
||||
onKeyDown={e => e.key === "Enter" && (addEmail(), 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" />
|
||||
<button onClick={addEmail} className="px-5 py-3 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white font-semibold text-sm">Add</button>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{notificationEmails.map(e => (
|
||||
<span key={e} className="inline-flex items-center gap-1.5 bg-stone-100 text-stone-700 text-xs px-2.5 py-1 rounded-lg">
|
||||
{e}
|
||||
<button onClick={() => removeEmail(e)} className="text-stone-400 hover:text-red-500 ml-1">×</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2">SMS numbers</label>
|
||||
<div className="flex gap-2">
|
||||
<input value={newSms}
|
||||
onChange={e => setNewSms(e.target.value)}
|
||||
onKeyDown={e => e.key === "Enter" && (addSms(), 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" />
|
||||
<button onClick={addSms} className="px-5 py-3 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white font-semibold text-sm">Add</button>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{notificationSmsNumbers.map(n => (
|
||||
<span key={n} className="inline-flex items-center gap-1.5 bg-stone-100 text-stone-700 text-xs px-2.5 py-1 rounded-lg">
|
||||
{n}
|
||||
<button onClick={() => removeSms(n)} className="text-stone-400 hover:text-red-500 ml-1">×</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{settingsError && (
|
||||
<div className="bg-red-50 border border-red-200 rounded-xl py-3 px-4 text-red-700 text-sm">{settingsError}</div>
|
||||
)}
|
||||
{settingsSaved && (
|
||||
<div className="bg-emerald-50 border border-emerald-200 rounded-xl py-3 px-4 text-emerald-700 text-sm">Settings saved.</div>
|
||||
)}
|
||||
<button onClick={handleSaveNotifications} disabled={settingsSaving}
|
||||
className="px-6 py-3 rounded-xl bg-stone-900 hover:bg-stone-800 disabled:opacity-40 font-semibold text-sm text-white transition-all">
|
||||
{settingsSaving ? "Saving..." : "Save Settings"}
|
||||
</button>
|
||||
</div>
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* ACCORDION 2: Workers & PINs */}
|
||||
<Accordion
|
||||
id="workers"
|
||||
title="Workers & PINs"
|
||||
description={`${workers.length} worker${workers.length !== 1 ? "s" : ""}`}
|
||||
defaultOpen={true}
|
||||
accentColor="emerald"
|
||||
>
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-xs text-stone-500">Manage time tracking workers and PIN codes.</p>
|
||||
<button onClick={openAddWorker}
|
||||
className="text-xs bg-emerald-600 hover:bg-emerald-500 text-white px-3 py-1.5 rounded-lg font-semibold transition-all">+ Add Worker</button>
|
||||
</div>
|
||||
<div className="bg-white border border-stone-200 rounded-xl overflow-hidden">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="text-xs text-stone-500 uppercase tracking-widest border-b border-stone-100">
|
||||
<th className="text-left px-4 py-3 font-medium">Name</th>
|
||||
<th className="text-left px-4 py-3 font-medium">Role</th>
|
||||
<th className="text-left px-4 py-3 font-medium hidden sm:table-cell">Lang</th>
|
||||
<th className="text-left px-4 py-3 font-medium">Status</th>
|
||||
<th className="text-left px-4 py-3 font-medium hidden md:table-cell">Last Used</th>
|
||||
<th className="text-right px-4 py-3 font-medium">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{workers.length === 0 ? (
|
||||
<tr><td colSpan={6} className="px-4 py-10 text-center text-stone-400 text-sm">No workers yet — add one to get started.</td></tr>
|
||||
) : workers.map(w => (
|
||||
<tr key={w.id} className="border-t border-stone-100 hover:bg-stone-50 transition-colors">
|
||||
<td className="px-4 py-3.5 text-stone-900 font-medium">{w.name}</td>
|
||||
<td className="px-4 py-3.5 text-stone-500 capitalize text-xs">{w.role}</td>
|
||||
<td className="px-4 py-3.5 text-stone-400 uppercase text-xs font-mono hidden sm:table-cell">{w.lang}</td>
|
||||
<td className="px-4 py-3.5">
|
||||
<span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-semibold ${w.active ? "bg-emerald-100 text-emerald-700" : "bg-stone-100 text-stone-500"}`}>
|
||||
{w.active ? "Active" : "Inactive"}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3.5 text-stone-400 text-xs hidden md:table-cell">{w.last_used_at ? new Date(w.last_used_at).toLocaleDateString() : "—"}</td>
|
||||
<td className="px-4 py-3.5 text-right">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<button onClick={() => openEditWorker(w)} className="text-xs text-stone-500 hover:text-stone-900 px-2 py-1 rounded-lg hover:bg-stone-100 transition-all">Edit</button>
|
||||
<button onClick={() => handleResetPin(w.id)} className="text-xs text-amber-600 hover:text-amber-800 px-2 py-1 rounded-lg hover:bg-amber-50 transition-all">Reset PIN</button>
|
||||
<button onClick={() => handleDeleteWorker(w.id)} className="text-xs text-red-500 hover:text-red-700 px-2 py-1 rounded-lg hover:bg-red-50 transition-all">Delete</button>
|
||||
</div>
|
||||
</td>
|
||||
{(workersOnly || (!workersOnly && !tasksOnly)) && (
|
||||
<Accordion
|
||||
id="workers"
|
||||
title="Workers & PINs"
|
||||
description={`${workers.length} worker${workers.length !== 1 ? "s" : ""}`}
|
||||
defaultOpen={workersOnly}
|
||||
accentColor="emerald"
|
||||
>
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-xs text-stone-500">Manage time tracking workers and PIN codes.</p>
|
||||
<button onClick={openAddWorker}
|
||||
className="text-xs bg-emerald-600 hover:bg-emerald-500 text-white px-3 py-1.5 rounded-lg font-semibold transition-all">+ Add Worker</button>
|
||||
</div>
|
||||
<div className="bg-white border border-stone-200 rounded-xl overflow-hidden">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="text-xs text-stone-500 uppercase tracking-widest border-b border-stone-100">
|
||||
<th className="text-left px-4 py-3 font-medium">Name</th>
|
||||
<th className="text-left px-4 py-3 font-medium">Role</th>
|
||||
<th className="text-left px-4 py-3 font-medium hidden sm:table-cell">Lang</th>
|
||||
<th className="text-left px-4 py-3 font-medium">Status</th>
|
||||
<th className="text-left px-4 py-3 font-medium hidden md:table-cell">Last Used</th>
|
||||
<th className="text-right px-4 py-3 font-medium">Actions</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{workers.length === 0 ? (
|
||||
<tr><td colSpan={6} className="px-4 py-10 text-center text-stone-400 text-sm">No workers yet — add one to get started.</td></tr>
|
||||
) : workers.map(w => (
|
||||
<tr key={w.id} className="border-t border-stone-100 hover:bg-stone-50 transition-colors">
|
||||
<td className="px-4 py-3.5 text-stone-900 font-medium">{w.name}</td>
|
||||
<td className="px-4 py-3.5 text-stone-500 capitalize text-xs">{w.role}</td>
|
||||
<td className="px-4 py-3.5 text-stone-400 uppercase text-xs font-mono hidden sm:table-cell">{w.lang}</td>
|
||||
<td className="px-4 py-3.5">
|
||||
<span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-semibold ${w.active ? "bg-emerald-100 text-emerald-700" : "bg-stone-100 text-stone-500"}`}>
|
||||
{w.active ? "Active" : "Inactive"}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3.5 text-stone-400 text-xs hidden md:table-cell">{w.last_used_at ? new Date(w.last_used_at).toLocaleDateString() : "—"}</td>
|
||||
<td className="px-4 py-3.5 text-right">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<button onClick={() => openEditWorker(w)} className="text-xs text-stone-500 hover:text-stone-900 px-2 py-1 rounded-lg hover:bg-stone-100 transition-all">Edit</button>
|
||||
<button onClick={() => handleResetPin(w.id)} className="text-xs text-amber-600 hover:text-amber-800 px-2 py-1 rounded-lg hover:bg-amber-50 transition-all">Reset PIN</button>
|
||||
<button onClick={() => handleDeleteWorker(w.id)} className="text-xs text-red-500 hover:text-red-700 px-2 py-1 rounded-lg hover:bg-red-50 transition-all">Delete</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
)}
|
||||
|
||||
{/* ACCORDION 3: Tasks */}
|
||||
<Accordion
|
||||
id="tasks"
|
||||
title="Tasks"
|
||||
description={`${tasks.length} task${tasks.length !== 1 ? "s" : ""}`}
|
||||
defaultOpen={false}
|
||||
accentColor="amber"
|
||||
>
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-xs text-stone-500">Define tasks workers can clock into.</p>
|
||||
<button onClick={openAddTask}
|
||||
className="text-xs bg-amber-500 hover:bg-amber-600 text-white px-3 py-1.5 rounded-lg font-semibold transition-all">+ Add Task</button>
|
||||
</div>
|
||||
<div className="bg-white border border-stone-200 rounded-xl overflow-hidden">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="text-xs text-stone-500 uppercase tracking-widest border-b border-stone-100">
|
||||
<th className="text-left px-4 py-3 font-medium">Name (EN)</th>
|
||||
<th className="text-left px-4 py-3 font-medium hidden sm:table-cell">Name (ES)</th>
|
||||
<th className="text-left px-4 py-3 font-medium">Unit</th>
|
||||
<th className="text-left px-4 py-3 font-medium hidden md:table-cell">Sort</th>
|
||||
<th className="text-left px-4 py-3 font-medium">Status</th>
|
||||
<th className="text-right px-4 py-3 font-medium">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tasks.length === 0 ? (
|
||||
<tr><td colSpan={6} className="px-4 py-10 text-center text-stone-400 text-sm">No tasks yet — add one to get started.</td></tr>
|
||||
) : tasks.map(t => (
|
||||
<tr key={t.id} className="border-t border-stone-100 hover:bg-stone-50 transition-colors">
|
||||
<td className="px-4 py-3.5 text-stone-900 font-medium">{t.name}</td>
|
||||
<td className="px-4 py-3.5 text-stone-500 text-xs hidden sm:table-cell">{t.name_es ?? "—"}</td>
|
||||
<td className="px-4 py-3.5 text-stone-400 text-xs font-mono">{t.unit}</td>
|
||||
<td className="px-4 py-3.5 text-stone-400 text-xs hidden md:table-cell">{t.sort_order}</td>
|
||||
<td className="px-4 py-3.5">
|
||||
<span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-semibold ${t.active ? "bg-emerald-100 text-emerald-700" : "bg-stone-100 text-stone-500"}`}>
|
||||
{t.active ? "Active" : "Inactive"}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3.5 text-right">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<button onClick={() => openEditTask(t)} className="text-xs text-stone-500 hover:text-stone-900 px-2 py-1 rounded-lg hover:bg-stone-100 transition-all">Edit</button>
|
||||
<button onClick={() => handleDeleteTask(t.id)} className="text-xs text-red-500 hover:text-red-700 px-2 py-1 rounded-lg hover:bg-red-50 transition-all">Delete</button>
|
||||
</div>
|
||||
</td>
|
||||
{(tasksOnly || (!workersOnly && !tasksOnly)) && (
|
||||
<Accordion
|
||||
id="tasks"
|
||||
title="Tasks"
|
||||
description={`${tasks.length} task${tasks.length !== 1 ? "s" : ""}`}
|
||||
defaultOpen={tasksOnly}
|
||||
accentColor="amber"
|
||||
>
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-xs text-stone-500">Define tasks workers can clock into.</p>
|
||||
<button onClick={openAddTask}
|
||||
className="text-xs bg-amber-500 hover:bg-amber-600 text-white px-3 py-1.5 rounded-lg font-semibold transition-all">+ Add Task</button>
|
||||
</div>
|
||||
<div className="bg-white border border-stone-200 rounded-xl overflow-hidden">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="text-xs text-stone-500 uppercase tracking-widest border-b border-stone-100">
|
||||
<th className="text-left px-4 py-3 font-medium">Name (EN)</th>
|
||||
<th className="text-left px-4 py-3 font-medium hidden sm:table-cell">Name (ES)</th>
|
||||
<th className="text-left px-4 py-3 font-medium">Unit</th>
|
||||
<th className="text-left px-4 py-3 font-medium hidden md:table-cell">Sort</th>
|
||||
<th className="text-left px-4 py-3 font-medium">Status</th>
|
||||
<th className="text-right px-4 py-3 font-medium">Actions</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tasks.length === 0 ? (
|
||||
<tr><td colSpan={6} className="px-4 py-10 text-center text-stone-400 text-sm">No tasks yet — add one to get started.</td></tr>
|
||||
) : tasks.map(t => (
|
||||
<tr key={t.id} className="border-t border-stone-100 hover:bg-stone-50 transition-colors">
|
||||
<td className="px-4 py-3.5 text-stone-900 font-medium">{t.name}</td>
|
||||
<td className="px-4 py-3.5 text-stone-500 text-xs hidden sm:table-cell">{t.name_es ?? "—"}</td>
|
||||
<td className="px-4 py-3.5 text-stone-400 text-xs font-mono">{t.unit}</td>
|
||||
<td className="px-4 py-3.5 text-stone-400 text-xs hidden md:table-cell">{t.sort_order}</td>
|
||||
<td className="px-4 py-3.5">
|
||||
<span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-semibold ${t.active ? "bg-emerald-100 text-emerald-700" : "bg-stone-100 text-stone-500"}`}>
|
||||
{t.active ? "Active" : "Inactive"}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3.5 text-right">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<button onClick={() => openEditTask(t)} className="text-xs text-stone-500 hover:text-stone-900 px-2 py-1 rounded-lg hover:bg-stone-100 transition-all">Edit</button>
|
||||
<button onClick={() => handleDeleteTask(t.id)} className="text-xs text-red-500 hover:text-red-700 px-2 py-1 rounded-lg hover:bg-red-50 transition-all">Delete</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
)}
|
||||
|
||||
{/* Worker Modal */}
|
||||
{showWorkerModal && (
|
||||
|
||||
Reference in New Issue
Block a user