Build Admin pages

This commit is contained in:
NeilClack
2026-02-23 10:21:42 -06:00
parent bec8f57a93
commit a230a1e36d
24 changed files with 1591 additions and 8 deletions
+104
View File
@@ -0,0 +1,104 @@
<script lang="ts">
import { fieldNameToLabel } from '$lib/svelte-admin/introspect';
import type { PrismaScalarType, RelationOptionConfig } from '$lib/svelte-admin/types';
import ComboBox from './ComboBox.svelte';
type RelationOptionsData = Record<string, { label: string; value: string | number }[]>;
type Props = {
fields: string[];
fieldTypes: Record<string, PrismaScalarType>;
relationOptions?: Record<string, RelationOptionConfig>;
relationOptionsData?: RelationOptionsData;
record?: Record<string, unknown>;
form?: Record<string, unknown>;
action: string;
submitLabel?: string;
};
let {
fields,
fieldTypes,
relationOptions = {},
relationOptionsData = {},
record,
form,
action,
submitLabel = 'Save'
}: Props = $props();
function getValue(field: string): unknown {
return form?.[field] ?? record?.[field] ?? '';
}
function isRelation(field: string): boolean {
return field in relationOptions;
}
</script>
<form method="POST" {action} class="space-y-5">
{#if record?.id || form?.id}
<input type="hidden" name="id" value={String(form?.id ?? record?.id)} />
{/if}
{#each fields as field}
{@const type = fieldTypes[field] ?? 'String'}
{@const label = fieldNameToLabel(field)}
<div>
<label for={field} class="mb-1 block text-sm font-medium text-gray-700">{label}</label>
{#if isRelation(field)}
<ComboBox
id={field}
name={field}
options={relationOptionsData[field] ?? []}
placeholder="Select {label}"
value={getValue(field) as string | number | undefined}
/>
{:else if type === 'Boolean'}
<input
type="checkbox"
id={field}
name={field}
checked={getValue(field) === true ||
getValue(field) === 'on' ||
getValue(field) === 'true'}
class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-2 focus:ring-blue-500"
/>
{:else if type === 'DateTime'}
<input
type="datetime-local"
id={field}
name={field}
value={getValue(field) ? String(getValue(field)).slice(0, 16) : ''}
class="w-full rounded-lg border border-gray-300 px-3 py-2 transition-shadow outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500"
/>
{:else if type === 'Int' || type === 'Float'}
<input
type="number"
id={field}
name={field}
value={getValue(field) ?? ''}
step={type === 'Float' ? '0.01' : '1'}
class="w-full rounded-lg border border-gray-300 px-3 py-2 transition-shadow outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500"
/>
{:else}
<input
type="text"
id={field}
name={field}
value={getValue(field) ?? ''}
class="w-full rounded-lg border border-gray-300 px-3 py-2 transition-shadow outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500"
/>
{/if}
</div>
{/each}
<button
type="submit"
class="rounded-lg bg-blue-600 px-4 py-2.5 font-medium text-white transition-colors hover:bg-blue-700 focus:ring-4 focus:ring-blue-200"
>
{submitLabel}
</button>
</form>