Files
clearity/src/routes/dashboard/clients/+page.svelte
T

121 lines
4.0 KiB
Svelte

<script lang="ts">
import { createListSearch } from '$lib/list-search.svelte';
import ListSearch from '$lib/components/list-search.svelte';
import CreateClientDialog from './create-client-dialog.svelte';
import ClientsTable from './clients-table.svelte';
import EditClientDialog from './edit-client-dialog.svelte';
import ArchiveClientDialog from './archive-client-dialog.svelte';
import { toast } from 'svelte-sonner';
import { superForm } from 'sveltekit-superforms';
import { zod4Client } from 'sveltekit-superforms/adapters';
import { archiveSchema } from '$lib/schemas/shared.schema';
import { formatValue, recordName as getRecordName } from '$lib/record-utils';
import { handleFormToast } from '$lib/form-feedback';
import { clientEditSchema, clientOnboardingCreateSchema } from '$lib/schemas/clients.schema';
import type { PageData } from './$types';
type RecordRow = PageData['records'][number];
let { data }: { data: PageData } = $props();
const listSearch = createListSearch(() => data.records);
const listData = $derived({ ...data, records: listSearch.filtered });
let createOpen = $state(false);
let createStep = $state(0);
let editingId = $state<string | null>(null);
let archivingId = $state<string | null>(null);
const editingRecord = $derived(data.records.find((record) => record.id === editingId));
const archivingRecord = $derived(data.records.find((record) => record.id === archivingId));
// svelte-ignore state_referenced_locally
const createForm = superForm(data.createForm, {
validators: zod4Client(clientOnboardingCreateSchema),
onUpdated: ({ form }) =>
handleFormToast(form, 'clients-create', () => {
createOpen = false;
createStep = 0;
}),
onError: ({ result }) => toast.error(result.error.message, { id: 'clients-create' })
});
// svelte-ignore state_referenced_locally
const editForm = superForm(data.editForm, {
validators: zod4Client(clientEditSchema),
resetForm: false,
onUpdated: ({ form }) => handleFormToast(form, 'clients-edit', () => (editingId = null)),
onError: ({ result }) => toast.error(result.error.message, { id: 'clients-edit' })
});
// svelte-ignore state_referenced_locally
const archiveForm = superForm(data.archiveForm, {
validators: zod4Client(archiveSchema),
resetForm: false,
onUpdated: ({ form }) => handleFormToast(form, 'clients-archive', () => (archivingId = null)),
onError: ({ result }) => toast.error(result.error.message, { id: 'clients-archive' })
});
const { form: createData, enhance: enhanceCreate } = createForm;
const { form: editData, enhance: enhanceEdit } = editForm;
const { form: archiveData, enhance: enhanceArchive } = archiveForm;
function openEdit(record: RecordRow) {
editForm.reset({ data: record.formValues as never });
editingId = record.id;
}
function openArchive(record: RecordRow) {
archiveForm.reset({ data: { id: record.id } });
archivingId = record.id;
}
function recordName(record: Partial<RecordRow> | undefined) {
return getRecordName(record, 'this client');
}
</script>
<svelte:head>
<title>Clients | Clearity</title>
</svelte:head>
<div class="flex flex-col gap-6">
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div>
<h1 class="text-2xl font-semibold tracking-tight">Clients</h1>
<p class="text-sm text-muted-foreground">
Manage businesses, sports organisations, and individual clients.
</p>
</div>
<CreateClientDialog
bind:open={createOpen}
bind:createStep
{createForm}
{createData}
{enhanceCreate}
/>
</div>
<ListSearch
bind:value={listSearch.params.q}
placeholder="Search clients..."
totalCount={data.records.length}
resultCount={listSearch.filtered.length}
/>
<ClientsTable data={listData} {openEdit} {openArchive} {formatValue} {recordName} />
</div>
<EditClientDialog
{editingRecord}
{editForm}
{editData}
{enhanceEdit}
onClose={() => (editingId = null)}
/>
<ArchiveClientDialog
{archivingRecord}
{archiveData}
{enhanceArchive}
{recordName}
onClose={() => (archivingId = null)}
/>