feat(employee-management): add scope filtering and management for employees
CI / build (push) Has been skipped
CI / deploy (push) Successful in 53s

- Introduced a new `Scopes` field in the `EmployeeRecord` interface to categorize employee scopes.
- Implemented scope filtering in the employee listing, allowing users to filter by various scopes.
- Enhanced employee detail and edit views to manage employee scopes with checkboxes.
- Updated API endpoints to support scope data during employee creation and updates.
- Adjusted UI components to display employee scopes in lists and detail views.
This commit is contained in:
eewing
2026-02-20 09:28:08 -06:00
parent 7c1d089035
commit a5d3ac3a84
5 changed files with 194 additions and 14 deletions
+72 -3
View File
@@ -115,7 +115,7 @@
}
}
async function patchField(field: keyof EmployeeRecord, value: string) {
async function patchField(field: keyof EmployeeRecord, value: string | string[]) {
if (!employee?.id) return;
patchSaving = true;
patchError = null;
@@ -125,8 +125,15 @@
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ [field]: value })
});
if (!res.ok) throw new Error('Update failed');
const json = await res.json();
const json = await res.json().catch(() => ({}));
if (!res.ok) {
const msg = (json as { error?: string }).error ?? 'Update failed';
const data = (json as { data?: unknown }).data;
patchError = data && typeof data === 'object' && data !== null
? `${msg}: ${JSON.stringify(data)}`
: msg;
return;
}
employee = (json.employee ?? employee) as EmployeeRecord;
} catch (e) {
patchError = e instanceof Error ? e.message : 'Update failed';
@@ -135,6 +142,37 @@
}
}
const SCOPE_OPTIONS = [
'General',
'OFFICE',
'IT',
'Framing',
'Insulation',
'Hanging',
'Scarp & Paper',
'Taping',
'Sand & Mask',
'WT & Cleanup',
'Grid & Tile'
];
function employeeScopes(): string[] {
const raw = employee?.Scopes;
if (Array.isArray(raw)) return raw;
if (typeof raw === 'string') return raw ? [raw] : [];
return [];
}
function toggleScope(scope: string) {
if (!employee) return;
const current = employeeScopes();
const next = current.includes(scope)
? current.filter((s) => s !== scope)
: [...current, scope];
employee = { ...employee, Scopes: next };
patchField('Scopes', next);
}
async function confirmDelete() {
deleteSaving = true;
deleteError = null;
@@ -434,6 +472,37 @@
<dd>{employee.Voxer_ID || '—'}</dd>
{/if}
</div>
<div class="flex flex-col gap-1">
<Label class="text-muted-foreground text-sm">Scopes</Label>
{#if editMode}
<div class="border-input bg-background flex min-h-[80px] flex-wrap gap-2 rounded-md border px-3 py-2">
{#each SCOPE_OPTIONS as scope}
<label class="flex cursor-pointer items-center gap-2 text-sm">
<input
type="checkbox"
class="border-input size-4 rounded"
checked={employeeScopes().includes(scope)}
onchange={() => toggleScope(scope)}
disabled={patchSaving}
/>
{scope}
</label>
{/each}
</div>
{:else}
<dd>
{#if employeeScopes().length > 0}
<div class="flex flex-wrap gap-1">
{#each employeeScopes() as scope}
<Badge variant="secondary">{scope}</Badge>
{/each}
</div>
{:else}
{/if}
</dd>
{/if}
</div>
</dl>
<!-- Notes section -->