Skip to content

Commit a696789

Browse files
feat: Add orders table, drop old tables, and improve relation field handling in CRUD forms.
1 parent 62bb642 commit a696789

File tree

11 files changed

+565
-79
lines changed

11 files changed

+565
-79
lines changed

playground/app/components/crud/Form.vue

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,23 @@ const formSchema = useDynamicZodSchema(filteredFields, !!props.initialState)
3838
const state = reactive<Record<string, unknown>>(
3939
filteredFields.reduce(
4040
(acc, field) => {
41-
acc[field.name]
42-
= props.initialState?.[field.name]
43-
?? (field.type === 'boolean' ? false : '')
41+
let value = props.initialState?.[field.name]
42+
43+
// Handle relation fields that might be objects
44+
// Case 1: value is an object with id (e.g. customer_id: { id: 1, ... })
45+
if ((field.name.endsWith('_id') || field.name.endsWith('Id')) && value && typeof value === 'object' && 'id' in value) {
46+
value = (value as { id: unknown }).id
47+
}
48+
// Case 2: value is undefined, but there might be a relation object (e.g. field is customer_id, but props has customer: { id: 1 })
49+
else if ((field.name.endsWith('_id') || field.name.endsWith('Id')) && value === undefined) {
50+
const relationName = field.name.endsWith('_id') ? field.name.replace('_id', '') : field.name.replace('Id', '')
51+
const relationValue = props.initialState?.[relationName]
52+
if (relationValue && typeof relationValue === 'object' && 'id' in relationValue) {
53+
value = (relationValue as { id: unknown }).id
54+
}
55+
}
56+
57+
acc[field.name] = value ?? (field.type === 'boolean' ? false : undefined)
4458
return acc
4559
},
4660
{} as Record<string, unknown>,
@@ -54,6 +68,9 @@ const processedFields = computed(() =>
5468
if (label.endsWith('_id')) {
5569
label = label.replace('_id', '')
5670
}
71+
else if (label.endsWith('Id')) {
72+
label = label.replace('Id', '')
73+
}
5774
label = useChangeCase(label, 'capitalCase').value
5875
return {
5976
...field,
@@ -81,7 +98,6 @@ function handleSubmit(event: FormSubmitEvent<Record<string, unknown>>) {
8198
:key="field.name"
8299
>
83100
<UFormField
84-
v-if="!props.initialState || field.name !== 'password'"
85101
:label="field.label"
86102
:name="field.name"
87103
>
@@ -91,23 +107,30 @@ function handleSubmit(event: FormSubmitEvent<Record<string, unknown>>) {
91107
/>
92108

93109
<CrudNameList
94-
v-else-if="field.name.endsWith('_id')"
110+
v-else-if="field.name.endsWith('_id') || field.name.endsWith('Id')"
95111
v-model="state[field.name] as string | number | null"
96112
:field-name="field.name"
97113
/>
98114

115+
<template v-else-if="field.name === 'password'">
116+
<CommonPassword
117+
v-if="!props.initialState"
118+
v-model="state[field.name] as string"
119+
type="password"
120+
/>
121+
<span
122+
v-else
123+
class="text-gray-500 italic text-sm"
124+
>
125+
Password can only be set on creation.
126+
</span>
127+
</template>
99128
<UInput
100129
v-else-if="field.type === 'date'"
101130
v-model="state[field.name] as string"
102131
type="datetime-local"
103132
/>
104133

105-
<CommonPassword
106-
v-else-if="field.name === 'password'"
107-
v-model="state[field.name] as string"
108-
type="password"
109-
/>
110-
111134
<USelect
112135
v-else-if="field.type === 'enum'"
113136
v-model="state[field.name] as string"
@@ -124,6 +147,7 @@ function handleSubmit(event: FormSubmitEvent<Record<string, unknown>>) {
124147
/>
125148
</UFormField>
126149
</template>
150+
127151
<UButton type="submit">
128152
Submit
129153
</UButton>

playground/app/components/crud/NameList.vue

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ if (props.tableName) {
1414
urlPath = props.tableName
1515
}
1616
else if (props.fieldName) {
17-
const baseName = props.fieldName.replace(/_id$/, '')
17+
const baseName = props.fieldName.replace(/(_id|Id)$/, '')
1818
urlPath = pluralize(baseName) // e.g., user_id → users
1919
}
2020
@@ -35,20 +35,36 @@ const { data: options } = await useFetch(() => `${crudBaseUrl}/${urlPath}`, {
3535
lazy: true,
3636
headers: crudHeaders(),
3737
})
38+
39+
function handleUpdate(value: unknown) {
40+
if (value && typeof value === 'object') {
41+
// If USelectMenu returns the full object despite value-attribute, extract the ID
42+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
43+
const id = (value as any).value || (value as any).id
44+
emit('update:modelValue', id)
45+
}
46+
else {
47+
emit('update:modelValue', value)
48+
}
49+
}
3850
</script>
3951

4052
<template>
4153
<USelectMenu
42-
:items="options"
43-
:model-value="modelValue"
54+
:items="options || []"
55+
:model-value="modelValue as any"
4456
value-attribute="value"
57+
option-attribute="label"
4558
placeholder="Select"
4659
class="w-full"
47-
@update:model-value="emit('update:modelValue', $event)"
60+
@update:model-value="handleUpdate"
4861
>
4962
<template #item-label="{ item }">
50-
{{ item.label }}
51-
<span class="text-muted">{{ item.extra }}</span>
63+
<span>{{ item.label }}</span>
64+
<span
65+
v-if="item.extra"
66+
class="text-gray-400 text-xs ml-2"
67+
>({{ item.extra }})</span>
5268
</template>
5369
</USelectMenu>
5470
</template>

playground/app/composables/useDynamicZodSchema.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ export function useDynamicZodSchema(
4141
}
4242
else if (field.type === 'number') {
4343
validators[field.name] = field.required
44-
? z.number()
45-
: z.number().optional()
44+
? z.coerce.number()
45+
: z.coerce.number().optional()
4646
}
4747
else if (field.type === 'date') {
4848
validators[field.name] = field.required
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CREATE TABLE `orders` (
2+
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
3+
`customer_id` integer NOT NULL,
4+
`product_id` integer NOT NULL,
5+
`quantity` integer DEFAULT 1 NOT NULL,
6+
`total` real NOT NULL,
7+
`status` text DEFAULT 'pending' NOT NULL,
8+
`created_at` integer NOT NULL,
9+
FOREIGN KEY (`customer_id`) REFERENCES `customers`(`id`) ON UPDATE no action ON DELETE no action,
10+
FOREIGN KEY (`product_id`) REFERENCES `products`(`id`) ON UPDATE no action ON DELETE no action
11+
);
12+
--> statement-breakpoint
13+
DROP TABLE `notifications`;--> statement-breakpoint
14+
DROP TABLE `sales`;

0 commit comments

Comments
 (0)