Skip to content

Commit fc2a450

Browse files
wip: create field as enum value
PR suggestion
1 parent 17da025 commit fc2a450

File tree

5 files changed

+64
-17
lines changed

5 files changed

+64
-17
lines changed

packages/app-builder/public/locales/en/data.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
"create_field.description_placeholder": "Field description",
1818
"create_field.required": "Required",
1919
"create_field.type": "Type",
20+
"create_field.is_enum.title": "This field is an enumerated value",
21+
"create_field.is_enum.subtitle": "It takes a limited number of distinct values, like a status code",
2022
"create_table.title": "Create a new table",
2123
"create_table.name_placeholder": "Table name",
2224
"create_table.button_accept": "Create table",
@@ -39,5 +41,7 @@
3941
"edit_table.button_accept": "Save changes",
4042
"edit_table.title": "Edit description",
4143
"empty_description": "No description",
42-
"upload_data": "Upload data"
44+
"upload_data": "Upload data",
45+
"required": "Required",
46+
"nullable": "Nullable"
4347
}

packages/app-builder/src/routes/__builder/data.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ const mapFieldToTableRow = (field: DataModelField) => ({
4343
id: field.id,
4444
name: field.name,
4545
description: field.description,
46-
type: field.dataType,
47-
required: field.nullable ? 'optional' : 'required',
46+
type: field.isEnum ? `${field.dataType} (enum)` : field.dataType,
47+
nullable: field.nullable,
48+
isEnum: field.isEnum,
4849
});
4950

5051
const mapLinkToTableRow = (table: TableModel, link: LinksToSingle) => ({
@@ -122,14 +123,19 @@ function TableDetails({
122123
{
123124
id: 'type',
124125
accessorKey: 'type',
125-
size: 80,
126+
size: 130,
126127
header: t('data:field_type'),
127128
},
128129
{
129130
id: 'required',
130131
accessorKey: 'required',
131132
size: 80,
132133
header: t('data:field_required'),
134+
cell: ({ cell }) => {
135+
return cell.row.original.nullable
136+
? t('data:nullable')
137+
: t('data:required');
138+
},
133139
},
134140
{
135141
id: 'description',

packages/app-builder/src/routes/ressources/data/createField.tsx

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,17 @@ import {
88
import { setToastMessage } from '@app-builder/components/MarbleToaster';
99
import { isStatusConflictHttpError } from '@app-builder/models';
1010
import { serverServices } from '@app-builder/services/init.server';
11-
import { parseFormSafe } from '@app-builder/utils/input-validation';
1211
import { zodResolver } from '@hookform/resolvers/zod';
1312
import { type ActionArgs, json } from '@remix-run/node';
1413
import { useFetcher } from '@remix-run/react';
15-
import { Button, HiddenInputs, Input, Modal, Select } from '@ui-design-system';
14+
import {
15+
Button,
16+
Checkbox,
17+
HiddenInputs,
18+
Input,
19+
Modal,
20+
Select,
21+
} from '@ui-design-system';
1622
import { Plus } from '@ui-icons';
1723
import { type Namespace } from 'i18next';
1824
import { useEffect, useState } from 'react';
@@ -27,12 +33,13 @@ export const handle = {
2733
const createFieldFormSchema = z.object({
2834
name: z
2935
.string()
30-
.nonempty()
36+
.min(1)
3137
.regex(/^[a-zA-Z0-9_]+$/, { message: 'Only alphanumeric and _' }),
3238
description: z.string(),
3339
required: z.string(),
3440
type: z.enum(['String', 'Bool', 'Timestamp', 'Float', 'Int']),
3541
tableId: z.string(),
42+
isEnum: z.boolean(),
3643
});
3744

3845
const VALUE_TYPES = [
@@ -52,24 +59,27 @@ export async function action({ request }: ActionArgs) {
5259
failureRedirect: '/login',
5360
});
5461

55-
const parsedForm = await parseFormSafe(request, createFieldFormSchema);
56-
if (!parsedForm.success) {
57-
parsedForm.error.flatten((issue) => issue);
62+
const parsedData = createFieldFormSchema.safeParse(await request.json());
63+
64+
if (!parsedData.success) {
65+
parsedData.error.flatten((issue) => issue);
5866

5967
return json({
6068
success: false as const,
61-
values: parsedForm.formData,
62-
error: parsedForm.error.format(),
69+
values: null,
70+
error: parsedData.error.format(),
6371
});
6472
}
65-
const { name, description, type, required, tableId } = parsedForm.data;
73+
const { name, description, type, required, tableId, isEnum } =
74+
parsedData.data;
6675

6776
try {
6877
await apiClient.postDataModelTableField(tableId, {
6978
name: name,
7079
description: description,
7180
type,
7281
nullable: required === 'optional',
82+
is_enum: isEnum,
7383
});
7484
return json({
7585
success: true as const,
@@ -87,15 +97,15 @@ export async function action({ request }: ActionArgs) {
8797
return json(
8898
{
8999
success: false as const,
90-
values: parsedForm.data,
100+
values: parsedData.data,
91101
error: error,
92102
},
93103
{ headers: { 'Set-Cookie': await commitSession(session) } }
94104
);
95105
} else {
96106
return json({
97107
success: false as const,
98-
values: parsedForm.data,
108+
values: parsedData.data,
99109
error: error,
100110
});
101111
}
@@ -115,6 +125,7 @@ export function CreateField({ tableId }: { tableId: string }) {
115125
description: '',
116126
type: VALUE_TYPES[0].value,
117127
tableId: tableId,
128+
isEnum: false,
118129
},
119130
});
120131
const { control, register, reset } = formMethods;
@@ -137,10 +148,11 @@ export function CreateField({ tableId }: { tableId: string }) {
137148
<Modal.Content>
138149
<Form
139150
control={control}
140-
onSubmit={({ formData }) => {
141-
fetcher.submit(formData, {
151+
onSubmit={({ formDataJson }) => {
152+
fetcher.submit(formDataJson, {
142153
method: 'POST',
143154
action: '/ressources/data/createField',
155+
encType: 'application/json',
144156
});
145157
}}
146158
>
@@ -242,6 +254,28 @@ export function CreateField({ tableId }: { tableId: string }) {
242254
)}
243255
/>
244256
</div>
257+
<FormField
258+
name="isEnum"
259+
control={control}
260+
render={({ field }) => (
261+
<FormItem className="flex flex-row items-center gap-4">
262+
<FormControl>
263+
<Checkbox
264+
onCheckedChange={(checked) => {
265+
field.onChange(checked);
266+
}}
267+
/>
268+
</FormControl>
269+
<FormLabel>
270+
<p>{t('data:create_field.is_enum.title')}</p>
271+
<p className="text-xs">
272+
{t('data:create_field.is_enum.subtitle')}
273+
</p>
274+
</FormLabel>
275+
<FormError />
276+
</FormItem>
277+
)}
278+
/>
245279
</div>
246280
<div className="flex flex-1 flex-row gap-2">
247281
<Modal.Close asChild>

packages/marble-api/scripts/openapi.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1923,6 +1923,8 @@ components:
19231923
enum: [Bool, Int, Float, String, Timestamp]
19241924
nullable:
19251925
type: boolean
1926+
is_enum:
1927+
type: boolean
19261928
UpdateTableFieldBody:
19271929
type: object
19281930
properties:

packages/marble-api/src/generated/marble-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ export type CreateTableFieldBody = {
279279
description: string;
280280
"type": "Bool" | "Int" | "Float" | "String" | "Timestamp";
281281
nullable: boolean;
282+
is_enum?: boolean;
282283
};
283284
export type UpdateTableFieldBody = {
284285
description?: string;

0 commit comments

Comments
 (0)