Remove the generic $lib/svelte-admin CRUD layer and AdminTable.svelte; each admin route now owns its own form/modal logic. Add DataTable.svelte, superform-errors.ts, and profile-specific modal/field components. Flatten proclamation categories/groups and drop subscription levels; add the v2 groups API; update v1/v2 endpoints, serializers, auth, and regenerated Prisma client with the new migrations. Document the overhaul plan in docs/ADMIN_OVERHAUL.md.
156 lines
3.9 KiB
Svelte
156 lines
3.9 KiB
Svelte
<script lang="ts">
|
|
import { enhance } from '$app/forms';
|
|
import { invalidateAll } from '$app/navigation';
|
|
import type { PageProps } from './$types';
|
|
import DataTable, { type DataTableColumn } from '$lib/components/DataTable.svelte';
|
|
import AdminModal from '$lib/components/admin/AdminModal.svelte';
|
|
|
|
type ActionResult = {
|
|
success?: boolean;
|
|
action?: string;
|
|
error?: string;
|
|
fieldErrors?: Record<string, string>;
|
|
} | null;
|
|
|
|
let { data, form }: PageProps = $props();
|
|
let canDelete = $derived((data.adminUser?.roleId ?? 4) <= 2);
|
|
|
|
const columns: DataTableColumn[] = [{ label: 'Name', value: (r) => r.name }];
|
|
|
|
let modalOpen = $state(false);
|
|
let editingRecord = $state<Record<string, unknown> | null>(null);
|
|
|
|
let name = $state('');
|
|
let formError = $state('');
|
|
let fieldErrors = $state<Record<string, string>>({});
|
|
let successMessage = $state('');
|
|
let lastForm = $state<ActionResult>(null);
|
|
|
|
$effect(() => {
|
|
const rec = editingRecord ?? {};
|
|
name = String(rec.name ?? '');
|
|
if (!editingRecord) {
|
|
formError = '';
|
|
fieldErrors = {};
|
|
successMessage = '';
|
|
}
|
|
});
|
|
|
|
$effect(() => {
|
|
if (!form || form === lastForm) return;
|
|
lastForm = form;
|
|
|
|
if (form.success) {
|
|
formError = '';
|
|
fieldErrors = {};
|
|
successMessage = editingRecord ? 'Group updated' : 'Group created';
|
|
setTimeout(() => {
|
|
modalOpen = false;
|
|
successMessage = '';
|
|
}, 1200);
|
|
} else {
|
|
successMessage = '';
|
|
formError = form.error ?? '';
|
|
fieldErrors = form.fieldErrors ?? {};
|
|
}
|
|
});
|
|
|
|
async function handleReorder(ids: (string | number)[]) {
|
|
const formData = new FormData();
|
|
formData.set('ids', JSON.stringify(ids));
|
|
await fetch('?/reorder', { method: 'POST', body: formData });
|
|
await invalidateAll();
|
|
}
|
|
|
|
function openCreate() {
|
|
editingRecord = null;
|
|
lastForm = null;
|
|
modalOpen = true;
|
|
}
|
|
|
|
function openEdit(record: Record<string, unknown>) {
|
|
editingRecord = record;
|
|
lastForm = null;
|
|
modalOpen = true;
|
|
}
|
|
|
|
function closeModal() {
|
|
modalOpen = false;
|
|
editingRecord = null;
|
|
}
|
|
</script>
|
|
|
|
<div>
|
|
<DataTable
|
|
title="Proclamation Groups"
|
|
{columns}
|
|
records={data.records}
|
|
newLabel="New Group"
|
|
searchPlaceholder="Search groups..."
|
|
draggable={true}
|
|
onReorder={handleReorder}
|
|
onRowClick={openEdit}
|
|
onNew={openCreate}
|
|
/>
|
|
</div>
|
|
|
|
{#if modalOpen}
|
|
<AdminModal
|
|
title={editingRecord ? 'Edit Group' : 'Create Group'}
|
|
open={modalOpen}
|
|
onClose={closeModal}
|
|
{canDelete}
|
|
recordId={editingRecord?.id as string | undefined}
|
|
>
|
|
{#if successMessage}
|
|
<div
|
|
class="mb-4 rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700"
|
|
>
|
|
{successMessage}
|
|
</div>
|
|
{/if}
|
|
{#if formError}
|
|
<div class="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
|
|
{formError}
|
|
</div>
|
|
{/if}
|
|
|
|
<form method="POST" action="?/upsert" use:enhance class="flex flex-col gap-4">
|
|
{#if editingRecord}
|
|
<input type="hidden" name="id" value={String(editingRecord.id)} />
|
|
{/if}
|
|
|
|
<div>
|
|
<label for="field-name" class="mb-1 block text-sm font-medium text-slate-700">Name</label>
|
|
<input
|
|
id="field-name"
|
|
type="text"
|
|
name="name"
|
|
required
|
|
bind:value={name}
|
|
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
|
|
/>
|
|
{#if fieldErrors.name}
|
|
<p class="mt-1 text-sm text-red-600">{fieldErrors.name}</p>
|
|
{/if}
|
|
</div>
|
|
|
|
<div class="mt-2 flex justify-end gap-3">
|
|
<button
|
|
type="button"
|
|
onclick={closeModal}
|
|
class="rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
class="rounded-md bg-indigo-600 px-4 py-2 text-sm font-medium text-white transition hover:bg-indigo-700"
|
|
>
|
|
{editingRecord ? 'Save Group' : 'Create Group'}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</AdminModal>
|
|
{/if}
|