Commit 1: Create individual form components per route, remove generic AdminForm dependency

- Add per-route form components: UserRoleForm, UserForm,
  ProclamationForm (with category cascade), CategoryForm,
  FreqForm, LevelForm
- Update all admin +page.svelte files to use AdminTable + AdminModal
  + their specific form component instead of AdminPage
- Update profiles page to inline fields instead of using AdminForm
- Add custom Proclamation server action with category fallback logic
  (categoryId falls back to parentCategoryId when no sub-category)
- Remove cascade/were/fieldLabels logic from generic crud.ts
- Remove outdated cascade/where tests from crud.spec.ts
- Remove fieldLabels from AdminModelConfig type
This commit is contained in:
NeilClack
2026-07-07 21:01:02 -05:00
parent 2073248576
commit 22b6adee3f
17 changed files with 1440 additions and 90 deletions
@@ -3,14 +3,13 @@
import type { ActionResult } from '$lib/svelte-admin/types';
import AdminTable from '$lib/components/admin/AdminTable.svelte';
import AdminModal from '$lib/components/admin/AdminModal.svelte';
import AdminForm from '$lib/components/admin/AdminForm.svelte';
import Toast from '$lib/components/admin/Toast.svelte';
import { enhance } from '$app/forms';
import { getModelConfig } from '$lib/svelte-admin/registry';
let { data, form }: PageProps = $props();
let config = $derived(getModelConfig('profiles')!);
const config = getModelConfig('profiles')!;
let canDelete = $derived((data.adminUser?.roleId ?? 4) <= 2);
let modalOpen = $state(false);
@@ -135,18 +134,110 @@
{canDelete}
recordId={editingRecord?.id as string | undefined}
>
<AdminForm
fields={config.formFields}
fieldTypes={config.fieldTypes}
relationOptions={config.relationOptions}
relationOptionsData={data.relationOptionsData}
record={editingRecord ?? {}}
readonlyFields={config.readonlyFields ?? []}
action="?/upsert"
submitLabel={editingRecord ? 'Update' : 'Create'}
{fieldErrors}
{formError}
/>
<form method="POST" action="?/upsert" use:enhance>
{#if editingRecord?.id}
<input type="hidden" name="id" value={String(editingRecord.id)} />
{/if}
{#if formError}
<div class="mb-4 border border-red-300 bg-red-50 px-4 py-2 text-sm text-red-700">
{formError}
</div>
{/if}
<div class="flex flex-col gap-4">
<div>
<label
for="field-userId"
class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300"
>
User
</label>
<select
id="field-userId"
name="userId"
value={String(editingRecord?.userId ?? '')}
class="w-full border px-3 py-1.5 text-sm {fieldErrors.userId
? 'border-red-400'
: 'border-gray-300 dark:border-gray-600'} dark:bg-gray-800 dark:text-gray-100"
>
<option value="">-- Select User --</option>
{#each data.relationOptionsData.userId ?? [] as opt (String(opt.value))}
<option value={opt.value}>{opt.label}</option>
{/each}
</select>
{#if fieldErrors.userId}
<p class="mt-1 text-xs text-red-600">{fieldErrors.userId}</p>
{/if}
</div>
<div>
<label
for="field-firstName"
class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300"
>
First Name
</label>
<input
type="text"
id="field-firstName"
name="firstName"
value={String(editingRecord?.firstName ?? '')}
class="w-full border px-3 py-1.5 text-sm {fieldErrors.firstName
? 'border-red-400'
: 'border-gray-300 dark:border-gray-600'} dark:bg-gray-800 dark:text-gray-100"
/>
{#if fieldErrors.firstName}
<p class="mt-1 text-xs text-red-600">{fieldErrors.firstName}</p>
{/if}
</div>
<div>
<label
for="field-lastName"
class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300"
>
Last Name
</label>
<input
type="text"
id="field-lastName"
name="lastName"
value={String(editingRecord?.lastName ?? '')}
class="w-full border px-3 py-1.5 text-sm {fieldErrors.lastName
? 'border-red-400'
: 'border-gray-300 dark:border-gray-600'} dark:bg-gray-800 dark:text-gray-100"
/>
{#if fieldErrors.lastName}
<p class="mt-1 text-xs text-red-600">{fieldErrors.lastName}</p>
{/if}
</div>
<div>
<label
for="field-phoneNumber"
class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300"
>
Phone Number
</label>
<input
type="text"
id="field-phoneNumber"
name="phoneNumber"
value={String(editingRecord?.phoneNumber ?? '')}
class="w-full border px-3 py-1.5 text-sm {fieldErrors.phoneNumber
? 'border-red-400'
: 'border-gray-300 dark:border-gray-600'} dark:bg-gray-800 dark:text-gray-100"
/>
{#if fieldErrors.phoneNumber}
<p class="mt-1 text-xs text-red-600">{fieldErrors.phoneNumber}</p>
{/if}
</div>
<div class="flex justify-end gap-2 pt-2">
<button
type="submit"
class="border border-gray-300 bg-gray-100 px-4 py-1.5 text-sm font-medium hover:bg-gray-200 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
>
{editingRecord ? 'Update' : 'Create'}
</button>
</div>
</div>
</form>
{#if editingRecord}
{@const user = editingRecord.user as Record<string, unknown> | undefined}