120 lines
3.5 KiB
Svelte
120 lines
3.5 KiB
Svelte
<script lang="ts">
|
|
import { fieldNameToLabel } from '$lib/svelte-admin/introspect';
|
|
import type { PrismaScalarType, RelationOptionConfig } from '$lib/svelte-admin/types';
|
|
import ComboBox from './ComboBox.svelte';
|
|
import TipTap from '$lib/components/tiptap/TipTap.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();
|
|
|
|
$inspect(fields);
|
|
|
|
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'}
|
|
{#if field === 'price'}
|
|
<input
|
|
type="number"
|
|
id={field}
|
|
name={field}
|
|
placeholder="0.99"
|
|
value={getValue(field) ?? '0.99'}
|
|
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="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"
|
|
/>
|
|
{/if}
|
|
{:else if type === 'String'}
|
|
<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"
|
|
/>
|
|
{:else}
|
|
<TipTap name={field} initialContent={String(getValue(field) ?? '')} />
|
|
{/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>
|