Refactor admin to shared generic CRUD; restructure proclamation categories/groups and APIs

- Replace per-model admin forms with the shared generic AdminTable/AdminModal CRUD
- Add ProclamationGroup model and admin + API v2 routes (replaces subscription levels)
- Drop subscription levels and flatten proclamation categories
- Update v2 serializers and seed data accordingly
This commit is contained in:
NeilClack
2026-08-27 12:03:40 -05:00
parent b618d8cb93
commit 1d11de8cec
50 changed files with 1168 additions and 2000 deletions
@@ -1,11 +1,10 @@
<script lang="ts">
import type { PageProps } from './$types';
import type { ActionResult } from '$lib/svelte-admin/types';
import { enhance } from '$app/forms';
import AdminTable from '$lib/components/admin/AdminTable.svelte';
import AdminModal from '$lib/components/admin/AdminModal.svelte';
import Toast from '$lib/components/admin/Toast.svelte';
import { getModelConfig } from '$lib/svelte-admin/registry';
import UserRoleForm from './UserRoleForm.svelte';
let { data, form }: PageProps = $props();
@@ -15,16 +14,17 @@
let modalOpen = $state(false);
let editingRecord = $state<Record<string, unknown> | null>(null);
let toastMessage = $state('');
let toastType = $state<'success' | 'error'>('success');
let fieldErrors = $state<Record<string, string>>({});
let formError = $state('');
let name = $state('');
let dirty = $state(false);
$effect(() => {
if (form && 'success' in (form as any)) {
const result = form as any;
if (result.success) {
toastMessage = 'Saved successfully';
toastType = 'success';
closeModal();
} else {
formError = result.error ?? '';
@@ -33,6 +33,11 @@
}
});
$effect(() => {
name = String((editingRecord ?? {})['name'] ?? '');
dirty = false;
});
function openCreate() {
editingRecord = null;
formError = '';
@@ -53,10 +58,14 @@
formError = '';
fieldErrors = {};
}
function markDirty() {
if (editingRecord?.id) dirty = true;
}
</script>
<div class="p-6">
<h1 class="mb-4 text-xl font-bold text-gray-900 dark:text-gray-100">{config.displayName}</h1>
<div>
<h1>{config.displayName}</h1>
<AdminTable
records={data.records}
listFields={config.listFields}
@@ -77,14 +86,33 @@
{canDelete}
recordId={editingRecord?.id as string | undefined}
>
<UserRoleForm
record={editingRecord ?? {}}
{fieldErrors}
{formError}
submitLabel={editingRecord ? 'Update' : 'Create'}
/>
<form action="?/upsert" method="POST" use:enhance oninput={markDirty}>
{#if editingRecord?.id}
<input type="hidden" name="id" value={String(editingRecord.id)} />
{/if}
{#if formError}
<div>{formError}</div>
{/if}
<div>
<div>
<label for="field-name">Name</label>
<input type="text" id="field-name" name="name" bind:value={name} />
{#if fieldErrors.name}
<p>{fieldErrors.name}</p>
{/if}
</div>
<div>
<button type="submit" disabled={!!editingRecord?.id && !dirty}>
{editingRecord ? 'Update' : 'Create'}
</button>
</div>
</div>
</form>
</AdminModal>
{#if toastMessage}
<Toast message={toastMessage} type={toastType} />
<Toast message={toastMessage} />
{/if}