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:
@@ -54,8 +54,6 @@
|
||||
: filtered
|
||||
);
|
||||
|
||||
let isDragging = $state(false);
|
||||
|
||||
let dragOverride = $state<Record<string, unknown>[] | null>(null);
|
||||
|
||||
let rows = $derived(dragOverride ?? sorted);
|
||||
@@ -74,7 +72,6 @@
|
||||
|
||||
function handleDragStart(e: DragEvent, i: number) {
|
||||
dragOverride = sorted;
|
||||
isDragging = true;
|
||||
dragIndex = i;
|
||||
hoverIndex = i;
|
||||
e.dataTransfer!.effectAllowed = 'move';
|
||||
@@ -99,14 +96,12 @@
|
||||
if (dragIndex === null) return;
|
||||
const ids = rows.map((r) => r.id as string | number);
|
||||
await onReorder?.(ids);
|
||||
isDragging = false;
|
||||
dragIndex = null;
|
||||
hoverIndex = null;
|
||||
}
|
||||
|
||||
function handleDragEnd() {
|
||||
dragOverride = null;
|
||||
isDragging = false;
|
||||
dragIndex = null;
|
||||
hoverIndex = null;
|
||||
}
|
||||
@@ -131,45 +126,32 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<div>
|
||||
{#if searchFields.length > 0}
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search {singular}s..."
|
||||
bind:value={search}
|
||||
class="max-w-xs border border-gray-300 px-3 py-1.5 text-sm dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100"
|
||||
/>
|
||||
<input type="text" placeholder="Search {singular}s..." bind:value={search} />
|
||||
{:else}
|
||||
<div></div>
|
||||
{/if}
|
||||
{#if onNew}
|
||||
<button
|
||||
type="button"
|
||||
onclick={onNew}
|
||||
class="border border-gray-300 px-3 py-1.5 text-sm font-medium dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
>
|
||||
<button type="button" onclick={onNew}>
|
||||
New {singular} +
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto border border-gray-200 dark:border-gray-700">
|
||||
<table class="w-full">
|
||||
<div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200 bg-gray-50 dark:border-gray-700 dark:bg-gray-800">
|
||||
<tr>
|
||||
{#if draggable}
|
||||
<th scope="col" class="w-10 px-2 py-2"></th>
|
||||
<th scope="col"></th>
|
||||
{/if}
|
||||
{#each listFields as field (field)}
|
||||
<th
|
||||
scope="col"
|
||||
onclick={() => toggleSort(field)}
|
||||
class="cursor-pointer px-4 py-2 text-left text-xs font-medium tracking-wider text-gray-500 uppercase hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
>
|
||||
<th scope="col" onclick={() => toggleSort(field)}>
|
||||
{fieldNameToLabel(field)}
|
||||
{#if sortCol === field}
|
||||
<span class="ml-1">{sortAsc ? '\u25B2' : '\u25BC'}</span>
|
||||
<span>{sortAsc ? '\u25B2' : '\u25BC'}</span>
|
||||
{/if}
|
||||
</th>
|
||||
{/each}
|
||||
@@ -184,25 +166,15 @@
|
||||
ondrop={handleDrop}
|
||||
ondragend={handleDragEnd}
|
||||
onclick={() => onRowClick?.(record)}
|
||||
class="border-b border-gray-200 hover:bg-gray-100 dark:border-gray-700 dark:text-gray-100 dark:hover:bg-gray-800 {draggable &&
|
||||
isDragging &&
|
||||
dragIndex === i
|
||||
? 'opacity-40'
|
||||
: ''} {draggable && isDragging && hoverIndex === i
|
||||
? 'border-t-2 border-t-blue-500'
|
||||
: ''}"
|
||||
>
|
||||
{#if draggable}
|
||||
<td
|
||||
class="w-10 cursor-grab px-2 py-2 text-center"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<span class="text-gray-400 select-none">⠿</span>
|
||||
<td onclick={(e) => e.stopPropagation()}>
|
||||
<span>⠿</span>
|
||||
</td>
|
||||
{/if}
|
||||
{#each listFields as field (field)}
|
||||
<td class="px-4 py-2 text-sm">
|
||||
<span class="inline-flex items-center gap-1.5">
|
||||
<td>
|
||||
<span>
|
||||
{formatCellValue(resolveFieldValue(record, field), field)}
|
||||
{#if copyableFields.includes(field)}
|
||||
<button
|
||||
@@ -212,10 +184,8 @@
|
||||
copyValue(resolveFieldValue(record, field));
|
||||
}}
|
||||
title="Copy to clipboard"
|
||||
class="shrink-0 leading-none text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
>
|
||||
<svg
|
||||
class="size-3.5"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
@@ -234,10 +204,7 @@
|
||||
</tr>
|
||||
{:else}
|
||||
<tr>
|
||||
<td
|
||||
colspan={listFields.length + (draggable ? 1 : 0)}
|
||||
class="px-4 py-8 text-center text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<td colspan={listFields.length + (draggable ? 1 : 0)}>
|
||||
No {singular}s found.
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user