sonamu 0.7.21 → 0.7.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/agents/agent.d.ts +6 -1
- package/dist/ai/agents/agent.d.ts.map +1 -1
- package/dist/ai/agents/agent.js +20 -5
- package/dist/api/base-frame.d.ts +4 -0
- package/dist/api/base-frame.d.ts.map +1 -1
- package/dist/api/base-frame.js +9 -1
- package/dist/api/caster.d.ts.map +1 -1
- package/dist/api/caster.js +2 -2
- package/dist/api/config.d.ts +35 -3
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +1 -1
- package/dist/api/decorators.d.ts +4 -4
- package/dist/api/decorators.d.ts.map +1 -1
- package/dist/api/decorators.js +80 -18
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +2 -1
- package/dist/api/secret.d.ts +7 -0
- package/dist/api/secret.d.ts.map +1 -0
- package/dist/api/secret.js +17 -0
- package/dist/api/sonamu.d.ts +17 -8
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +265 -47
- package/dist/cache/cache-manager.d.ts +11 -0
- package/dist/cache/cache-manager.d.ts.map +1 -0
- package/dist/cache/cache-manager.js +22 -0
- package/dist/cache/decorator.d.ts +31 -0
- package/dist/cache/decorator.d.ts.map +1 -0
- package/dist/cache/decorator.js +86 -0
- package/dist/cache/drivers.d.ts +33 -0
- package/dist/cache/drivers.d.ts.map +1 -0
- package/dist/cache/drivers.js +36 -0
- package/dist/cache/index.d.ts +4 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +8 -0
- package/dist/cache/types.d.ts +28 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cache/types.js +6 -0
- package/dist/database/base-model.d.ts +4 -2
- package/dist/database/base-model.d.ts.map +1 -1
- package/dist/database/base-model.js +9 -4
- package/dist/database/code-generator.d.ts +3 -1
- package/dist/database/code-generator.d.ts.map +1 -1
- package/dist/database/code-generator.js +3 -2
- package/dist/database/db.d.ts +1 -1
- package/dist/database/db.d.ts.map +1 -1
- package/dist/database/db.js +5 -5
- package/dist/database/knex.d.ts +3 -0
- package/dist/database/knex.d.ts.map +1 -0
- package/dist/database/knex.js +29 -0
- package/dist/database/puri.types.d.ts.map +1 -1
- package/dist/database/puri.types.js +1 -1
- package/dist/database/upsert-builder.d.ts.map +1 -1
- package/dist/database/upsert-builder.js +49 -5
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/logger/category.d.ts +4 -0
- package/dist/logger/category.d.ts.map +1 -0
- package/dist/logger/category.js +34 -0
- package/dist/logger/configure.d.ts +9 -0
- package/dist/logger/configure.d.ts.map +1 -0
- package/dist/logger/configure.js +115 -0
- package/dist/migration/code-generation.d.ts +5 -1
- package/dist/migration/code-generation.d.ts.map +1 -1
- package/dist/migration/code-generation.js +13 -7
- package/dist/migration/migrator.d.ts +1 -1
- package/dist/migration/migrator.d.ts.map +1 -1
- package/dist/migration/migrator.js +7 -7
- package/dist/migration/postgresql-schema-reader.d.ts.map +1 -1
- package/dist/migration/postgresql-schema-reader.js +5 -3
- package/dist/naite/naite.d.ts +0 -4
- package/dist/naite/naite.d.ts.map +1 -1
- package/dist/naite/naite.js +11 -19
- package/dist/ssr/index.d.ts +4 -0
- package/dist/ssr/index.d.ts.map +1 -0
- package/dist/ssr/index.js +4 -0
- package/dist/ssr/registry.d.ts +10 -0
- package/dist/ssr/registry.d.ts.map +1 -0
- package/dist/ssr/registry.js +43 -0
- package/dist/ssr/renderer.d.ts +6 -0
- package/dist/ssr/renderer.d.ts.map +1 -0
- package/dist/ssr/renderer.js +70 -0
- package/dist/ssr/types.d.ts +19 -0
- package/dist/ssr/types.d.ts.map +1 -0
- package/dist/ssr/types.js +4 -0
- package/dist/syncer/syncer.d.ts +1 -0
- package/dist/syncer/syncer.d.ts.map +1 -1
- package/dist/syncer/syncer.js +58 -1
- package/dist/tasks/decorator.d.ts +1 -0
- package/dist/tasks/decorator.d.ts.map +1 -1
- package/dist/tasks/decorator.js +9 -7
- package/dist/tasks/step-wrapper.d.ts +5 -0
- package/dist/tasks/step-wrapper.d.ts.map +1 -1
- package/dist/tasks/step-wrapper.js +11 -6
- package/dist/tasks/workflow-manager.d.ts +2 -0
- package/dist/tasks/workflow-manager.d.ts.map +1 -1
- package/dist/tasks/workflow-manager.js +5 -2
- package/dist/template/implementations/entry-server.template.d.ts +17 -0
- package/dist/template/implementations/entry-server.template.d.ts.map +1 -0
- package/dist/template/implementations/entry-server.template.js +78 -0
- package/dist/template/implementations/model.template.d.ts.map +1 -1
- package/dist/template/implementations/model.template.js +5 -3
- package/dist/template/implementations/queries.template.d.ts +17 -0
- package/dist/template/implementations/queries.template.d.ts.map +1 -0
- package/dist/template/implementations/queries.template.js +83 -0
- package/dist/template/implementations/view_enums_select.template.d.ts.map +1 -1
- package/dist/template/implementations/view_enums_select.template.js +34 -20
- package/dist/template/implementations/view_form.template.d.ts +2 -1
- package/dist/template/implementations/view_form.template.d.ts.map +1 -1
- package/dist/template/implementations/view_form.template.js +301 -129
- package/dist/template/implementations/view_id_async_select.template.d.ts.map +1 -1
- package/dist/template/implementations/view_id_async_select.template.js +136 -57
- package/dist/template/implementations/view_list.template.d.ts +2 -0
- package/dist/template/implementations/view_list.template.d.ts.map +1 -1
- package/dist/template/implementations/view_list.template.js +392 -227
- package/dist/template/implementations/view_search_input.template.d.ts.map +1 -1
- package/dist/template/implementations/view_search_input.template.js +46 -30
- package/dist/template/zod-converter.d.ts.map +1 -1
- package/dist/template/zod-converter.js +2 -2
- package/dist/testing/bootstrap.d.ts +28 -0
- package/dist/testing/bootstrap.d.ts.map +1 -0
- package/dist/testing/bootstrap.js +120 -0
- package/dist/testing/fixture-loader.d.ts +21 -0
- package/dist/testing/fixture-loader.d.ts.map +1 -0
- package/dist/testing/fixture-loader.js +28 -0
- package/dist/testing/fixture-manager.d.ts +1 -1
- package/dist/testing/fixture-manager.d.ts.map +1 -1
- package/dist/testing/fixture-manager.js +7 -7
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +5 -0
- package/dist/testing/naite-vitest-reporter.d.ts +12 -0
- package/dist/testing/naite-vitest-reporter.d.ts.map +1 -0
- package/dist/testing/naite-vitest-reporter.js +17 -0
- package/dist/types/types.d.ts +5 -6
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +7 -8
- package/dist/ui/ai-client.d.ts +3 -1
- package/dist/ui/ai-client.d.ts.map +1 -1
- package/dist/ui/ai-client.js +27 -8
- package/dist/ui-web/assets/index-CTYv3qL6.js +92 -0
- package/dist/ui-web/index.html +1 -1
- package/package.json +43 -20
- package/src/ai/agents/agent.ts +38 -19
- package/src/api/base-frame.ts +8 -0
- package/src/api/caster.ts +6 -1
- package/src/api/config.ts +38 -4
- package/src/api/decorators.ts +106 -20
- package/src/api/index.ts +1 -0
- package/src/api/secret.ts +23 -0
- package/src/api/sonamu.ts +334 -61
- package/src/cache/cache-manager.ts +23 -0
- package/src/cache/decorator.ts +116 -0
- package/src/cache/drivers.ts +42 -0
- package/src/cache/index.ts +16 -0
- package/src/cache/types.ts +32 -0
- package/src/database/base-model.ts +7 -3
- package/src/database/code-generator.ts +3 -1
- package/src/database/db.ts +5 -5
- package/src/database/knex.ts +34 -0
- package/src/database/puri.types.ts +2 -3
- package/src/database/upsert-builder.ts +58 -4
- package/src/index.ts +4 -0
- package/src/logger/category.ts +42 -0
- package/src/logger/configure.ts +132 -0
- package/src/migration/code-generation.ts +19 -6
- package/src/migration/migrator.ts +7 -6
- package/src/migration/postgresql-schema-reader.ts +7 -2
- package/src/naite/naite.ts +10 -18
- package/src/shared/web.shared.ts.txt +1 -1
- package/src/ssr/index.ts +13 -0
- package/src/ssr/registry.ts +52 -0
- package/src/ssr/renderer.ts +105 -0
- package/src/ssr/types.ts +20 -0
- package/src/syncer/syncer.ts +59 -0
- package/src/tasks/decorator.ts +20 -4
- package/src/tasks/step-wrapper.ts +14 -5
- package/src/tasks/workflow-manager.ts +9 -1
- package/src/template/implementations/entry-server.template.ts +81 -0
- package/src/template/implementations/model.template.ts +4 -2
- package/src/template/implementations/queries.template.ts +111 -0
- package/src/template/implementations/view_enums_select.template.ts +33 -19
- package/src/template/implementations/view_form.template.ts +324 -145
- package/src/template/implementations/view_id_async_select.template.ts +145 -56
- package/src/template/implementations/view_list.template.ts +446 -236
- package/src/template/implementations/view_search_input.template.ts +45 -29
- package/src/template/zod-converter.ts +4 -1
- package/src/testing/bootstrap.ts +176 -0
- package/src/testing/fixture-loader.ts +28 -0
- package/src/testing/fixture-manager.ts +7 -6
- package/src/testing/index.ts +3 -0
- package/src/testing/naite-vitest-reporter.ts +18 -0
- package/src/types/types.ts +4 -5
- package/src/ui/ai-client.ts +82 -50
- package/dist/template/implementations/view_enums_dropdown.template.d.ts +0 -17
- package/dist/template/implementations/view_enums_dropdown.template.d.ts.map +0 -1
- package/dist/template/implementations/view_enums_dropdown.template.js +0 -50
- package/dist/ui-web/assets/index-B87IyofX.js +0 -92
- package/src/template/implementations/view_enums_dropdown.template.ts +0 -53
|
@@ -6,7 +6,6 @@ import type { RenderingNode, TemplateKey, TemplateOptions } from "../../types/ty
|
|
|
6
6
|
import { getEnumInfoFromColName, getRelationPropFromColName } from "../helpers";
|
|
7
7
|
import type { RenderedTemplate } from "../template";
|
|
8
8
|
import { Template } from "../template";
|
|
9
|
-
import { getZodTypeById, zodTypeToRenderingNode } from "../zod-converter";
|
|
10
9
|
|
|
11
10
|
export class Template__view_form extends Template {
|
|
12
11
|
constructor() {
|
|
@@ -15,20 +14,20 @@ export class Template__view_form extends Template {
|
|
|
15
14
|
|
|
16
15
|
getTargetAndPath(names: EntityNamesRecord) {
|
|
17
16
|
return {
|
|
18
|
-
target: "web/src/
|
|
17
|
+
target: "web/src/routes/admin",
|
|
19
18
|
path: `${names.fsPlural}/form.tsx`,
|
|
20
19
|
};
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
wrapFC(body: string, label?: string): string {
|
|
24
23
|
return [
|
|
25
|
-
`<
|
|
26
|
-
body
|
|
27
|
-
`</
|
|
24
|
+
`<div className="space-y-2">${label ? `\n <Label>${label}</Label>` : ""}`,
|
|
25
|
+
` ${body}`,
|
|
26
|
+
`</div>`,
|
|
28
27
|
].join("\n");
|
|
29
28
|
}
|
|
30
29
|
wrapFG(body: string, label?: string): string {
|
|
31
|
-
return
|
|
30
|
+
return this.wrapFC(body, label);
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
renderColumnImport(entityId: string, col: RenderingNode) {
|
|
@@ -50,7 +49,7 @@ export class Template__view_form extends Template {
|
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
|
|
52
|
+
renderColumnOld(
|
|
54
53
|
entityId: string,
|
|
55
54
|
col: RenderingNode,
|
|
56
55
|
names: EntityNamesRecord,
|
|
@@ -64,24 +63,30 @@ export class Template__view_form extends Template {
|
|
|
64
63
|
if (col.zodType instanceof z.ZodString && (col.zodType.maxLength ?? 0) <= 512) {
|
|
65
64
|
return `<Input placeholder="${col.label}" ${regExpr} />`;
|
|
66
65
|
} else {
|
|
67
|
-
return `<
|
|
66
|
+
return `<Textarea rows={8} placeholder="${col.label}" ${regExpr} />`;
|
|
68
67
|
}
|
|
69
|
-
case "datetime":
|
|
70
|
-
return `<Input type="datetime-local" ${regExpr} />`;
|
|
71
68
|
case "string-datetime":
|
|
72
|
-
return `<
|
|
69
|
+
return `<DatePicker ${regExpr} />`;
|
|
73
70
|
case "string-date":
|
|
74
|
-
return `<
|
|
71
|
+
return `<DatePicker ${regExpr} />`;
|
|
75
72
|
case "number-id":
|
|
76
73
|
return `<input type="hidden" ${regExpr} />`;
|
|
77
74
|
case "number-plain":
|
|
78
|
-
return `<
|
|
75
|
+
return `<Input type="number" placeholder="${col.label}" ${regExpr} />`;
|
|
79
76
|
case "boolean":
|
|
80
|
-
return `<
|
|
77
|
+
return `<Switch ${regExpr} />`;
|
|
81
78
|
case "string-image":
|
|
82
|
-
return `<ImageUploader
|
|
79
|
+
return `<ImageUploader
|
|
80
|
+
${regExpr}
|
|
81
|
+
uploader={async (file: File) => {
|
|
82
|
+
const { file: uploadedFile } = await FileService.upload(file);
|
|
83
|
+
return uploadedFile.url;
|
|
84
|
+
}}
|
|
85
|
+
previewSize="md"
|
|
86
|
+
/>`;
|
|
83
87
|
case "array-images":
|
|
84
|
-
return
|
|
88
|
+
return `{/* TODO: Implement multiple image uploader */}
|
|
89
|
+
<Input placeholder="${col.label}" ${regExpr} />`;
|
|
85
90
|
case "enums":
|
|
86
91
|
try {
|
|
87
92
|
let enumId: string;
|
|
@@ -91,11 +96,9 @@ export class Template__view_form extends Template {
|
|
|
91
96
|
const { id } = getEnumInfoFromColName(entityId, col.name);
|
|
92
97
|
enumId = `${id}Select`;
|
|
93
98
|
}
|
|
94
|
-
return `<${enumId} ${regExpr} ${
|
|
95
|
-
col.optional || col.nullable ? "clearable" : ""
|
|
96
|
-
} textPrefix="" />`;
|
|
99
|
+
return `<${enumId} ${regExpr} ${col.optional || col.nullable ? "clearable" : ""} />`;
|
|
97
100
|
} catch {
|
|
98
|
-
return
|
|
101
|
+
return `<span className="text-destructive">찾을 수 없는 Enum ${col.name}</span>`;
|
|
99
102
|
}
|
|
100
103
|
case "number-fk_id":
|
|
101
104
|
try {
|
|
@@ -108,17 +111,100 @@ export class Template__view_form extends Template {
|
|
|
108
111
|
return `<Input ${regExpr} />`;
|
|
109
112
|
}
|
|
110
113
|
case "array":
|
|
111
|
-
return
|
|
114
|
+
return `<span className="text-muted-foreground">${col.name} array</span>`;
|
|
112
115
|
case "object":
|
|
113
|
-
return
|
|
114
|
-
case "vector":
|
|
115
|
-
// vector 타입은 일반적으로 API를 통해 생성되므로 읽기 전용으로 표시
|
|
116
|
-
return `<div className="p-8px text-gray-500">[Vector: ${col.name}] - 임베딩 데이터는 API를 통해 자동 생성됩니다.</div>`;
|
|
116
|
+
return `<span className="text-muted-foreground">${col.name} object</span>`;
|
|
117
117
|
default:
|
|
118
118
|
throw new Error(`대응 불가능한 렌더 타입 ${col.renderType} on ${col.name}`);
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
// New style rendering for feed-sites style form
|
|
123
|
+
renderColumn(entityId: string, col: RenderingNode, names: EntityNamesRecord): string {
|
|
124
|
+
const regExpr = `{...register("${col.name}")}`;
|
|
125
|
+
|
|
126
|
+
switch (col.renderType) {
|
|
127
|
+
case "string-plain":
|
|
128
|
+
if (col.zodType instanceof z.ZodString && (col.zodType.maxLength ?? 0) <= 256) {
|
|
129
|
+
return `<Input className="h-8 text-xs bg-white" placeholder="${col.label}" ${regExpr} />`;
|
|
130
|
+
} else {
|
|
131
|
+
return `<Textarea className="text-xs bg-white" rows={4} placeholder="${col.label}" ${regExpr} />`;
|
|
132
|
+
}
|
|
133
|
+
case "string-datetime":
|
|
134
|
+
return `<DateInput
|
|
135
|
+
className="h-8 text-xs bg-white"
|
|
136
|
+
value={form.${col.name} ? new Date(form.${col.name}) : null}
|
|
137
|
+
onValueChange={(value) => setForm({ ...form, ${col.name}: value })}
|
|
138
|
+
/>`;
|
|
139
|
+
case "string-date":
|
|
140
|
+
return `<DateInput
|
|
141
|
+
mode="date"
|
|
142
|
+
className="h-8 text-xs bg-white"
|
|
143
|
+
value={form.${col.name} ? new Date(form.${col.name}) : null}
|
|
144
|
+
onValueChange={(value) => setForm({ ...form, ${col.name}: value })}
|
|
145
|
+
/>`;
|
|
146
|
+
case "datetime":
|
|
147
|
+
return `<DateInput
|
|
148
|
+
className="h-8 text-xs bg-white"
|
|
149
|
+
${regExpr}
|
|
150
|
+
/>`;
|
|
151
|
+
case "number-id":
|
|
152
|
+
return `<input type="hidden" ${regExpr} />`;
|
|
153
|
+
case "number-plain":
|
|
154
|
+
return `<Input type="number" className="h-8 text-xs bg-white" placeholder="${col.label}" ${regExpr} />`;
|
|
155
|
+
case "boolean":
|
|
156
|
+
return `<Switch ${regExpr} />`;
|
|
157
|
+
case "string-image":
|
|
158
|
+
return `<ImageUploader
|
|
159
|
+
${regExpr}
|
|
160
|
+
uploader={async (file: File) => {
|
|
161
|
+
const { file: uploadedFile } = await FileService.upload(file);
|
|
162
|
+
return uploadedFile.url;
|
|
163
|
+
}}
|
|
164
|
+
previewSize="md"
|
|
165
|
+
/>`;
|
|
166
|
+
case "array-images":
|
|
167
|
+
return `<MultiImageUploader
|
|
168
|
+
value={Array.isArray(form.${col.name}) ? form.${col.name} : []}
|
|
169
|
+
onValueChange={(urls) => setForm({ ...form, ${col.name}: urls })}
|
|
170
|
+
uploader={async (file: File) => {
|
|
171
|
+
const { file: uploadedFile } = await FileService.upload(file);
|
|
172
|
+
return uploadedFile.url;
|
|
173
|
+
}}
|
|
174
|
+
previewSize="md"
|
|
175
|
+
placeholder="${col.label}"
|
|
176
|
+
/>`;
|
|
177
|
+
case "enums":
|
|
178
|
+
try {
|
|
179
|
+
let enumId: string;
|
|
180
|
+
if (col.name === "orderBy") {
|
|
181
|
+
enumId = `${names.capital}${inflection.camelize(col.name)}Select`;
|
|
182
|
+
} else {
|
|
183
|
+
const { id } = getEnumInfoFromColName(entityId, col.name);
|
|
184
|
+
enumId = `${id}Select`;
|
|
185
|
+
}
|
|
186
|
+
return `<${enumId} ${regExpr} ${col.optional || col.nullable ? "clearable" : ""} />`;
|
|
187
|
+
} catch {
|
|
188
|
+
return `<Input className="h-8 text-xs bg-white" ${regExpr} />`;
|
|
189
|
+
}
|
|
190
|
+
case "number-fk_id":
|
|
191
|
+
try {
|
|
192
|
+
const relProp = getRelationPropFromColName(entityId, col.name.replace("_id", ""));
|
|
193
|
+
const fkId = `${relProp.with}IdAsyncSelect`;
|
|
194
|
+
return `<${fkId} subset="A" ${regExpr} ${
|
|
195
|
+
col.optional || col.nullable ? "clearable" : ""
|
|
196
|
+
} className="h-8 text-xs" />`;
|
|
197
|
+
} catch {
|
|
198
|
+
return `<Input type="number" className="h-8 text-xs bg-white" placeholder="${col.label}" ${regExpr} />`;
|
|
199
|
+
}
|
|
200
|
+
case "array":
|
|
201
|
+
case "object":
|
|
202
|
+
return `<Input className="h-8 text-xs bg-white" placeholder="${col.name}" ${regExpr} />`;
|
|
203
|
+
default:
|
|
204
|
+
return `<Input className="h-8 text-xs bg-white" ${regExpr} />`;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
122
208
|
resolveDefaultValue(columns: RenderingNode[]): object {
|
|
123
209
|
return columns.reduce(
|
|
124
210
|
(result, col) => {
|
|
@@ -132,11 +218,9 @@ export class Template__view_form extends Template {
|
|
|
132
218
|
} else if (col.zodType instanceof z.ZodNumber) {
|
|
133
219
|
value = 0;
|
|
134
220
|
} else if (col.zodType instanceof z.ZodEnum) {
|
|
135
|
-
value = Object.keys(col.zodType.
|
|
221
|
+
value = Object.keys(col.zodType.enum)[0];
|
|
136
222
|
} else if (col.zodType instanceof z.ZodBoolean) {
|
|
137
223
|
value = false;
|
|
138
|
-
} else if (col.zodType instanceof z.ZodDate) {
|
|
139
|
-
value = new Date();
|
|
140
224
|
} else if (col.zodType instanceof z.ZodString) {
|
|
141
225
|
if (col.renderType === "string-datetime") {
|
|
142
226
|
value = "now()";
|
|
@@ -157,12 +241,23 @@ export class Template__view_form extends Template {
|
|
|
157
241
|
}
|
|
158
242
|
|
|
159
243
|
async render({ entityId }: TemplateOptions["view_form"]) {
|
|
160
|
-
const saveParamsZodType = await getZodTypeById(`${entityId}SaveParams`);
|
|
161
|
-
const saveParamsNode = zodTypeToRenderingNode(saveParamsZodType);
|
|
162
|
-
|
|
163
244
|
const entity = EntityManager.get(entityId);
|
|
164
245
|
const names = EntityManager.getNamesFromId(entityId);
|
|
165
|
-
|
|
246
|
+
|
|
247
|
+
// SaveParams 타입을 로드하여 saveParamsNode 생성
|
|
248
|
+
const { loadTypes } = await import("../../syncer/module-loader");
|
|
249
|
+
const loadedTypes = await loadTypes();
|
|
250
|
+
const SaveParamsZodType = loadedTypes[`${entityId}SaveParams`];
|
|
251
|
+
|
|
252
|
+
if (!SaveParamsZodType) {
|
|
253
|
+
throw new Error(`SaveParams for ${entityId} not found. Did you run 'sonamu sync'?`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Zod 타입을 RenderingNode로 변환
|
|
257
|
+
const { zodTypeToRenderingNode } = await import("../zod-converter");
|
|
258
|
+
const saveParamsNode = zodTypeToRenderingNode(SaveParamsZodType);
|
|
259
|
+
|
|
260
|
+
const columns = ((saveParamsNode?.children ?? []) as RenderingNode[])
|
|
166
261
|
.filter((col) => col.name !== "id")
|
|
167
262
|
.map((col) => {
|
|
168
263
|
const propCandidate = entity.props.find((prop) => prop.name === col.name);
|
|
@@ -236,28 +331,46 @@ export class Template__view_form extends Template {
|
|
|
236
331
|
return {
|
|
237
332
|
...this.getTargetAndPath(names),
|
|
238
333
|
body: `
|
|
239
|
-
import React, { useEffect, useState, Dispatch, SetStateAction, forwardRef, Ref, useImperativeHandle, useCallback } from 'react';
|
|
240
|
-
import { useSearchParams } from 'react-router-dom';
|
|
241
334
|
import {
|
|
242
335
|
Button,
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
} from
|
|
251
|
-
import {
|
|
252
|
-
|
|
253
|
-
import {
|
|
254
|
-
import {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
import { ${names.capital}
|
|
260
|
-
|
|
336
|
+
Card,
|
|
337
|
+
CardContent,
|
|
338
|
+
CardHeader,
|
|
339
|
+
CardTitle,
|
|
340
|
+
Input,${columns.some((col) => col.renderType === "string-plain" && col.zodType instanceof z.ZodString && (col.zodType.maxLength ?? 0) > 256) ? "\n Textarea," : ""}${columns.some((col) => col.renderType === "enums") ? "\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue," : ""}${columns.some((col) => col.renderType === "boolean") ? "\n Switch," : ""}${columns.some((col) => col.renderType === "string-image") ? "\n ImageUploader," : ""}${columns.some((col) => col.renderType === "array-images") ? "\n MultiImageUploader," : ""}${columns.some((col) => ["string-datetime", "string-date", "datetime"].includes(col.renderType)) ? "\n DateInput," : ""}
|
|
341
|
+
} from "@sonamu-kit/react-components/components";
|
|
342
|
+
import { useTypeForm } from "@sonamu-kit/react-components/lib";
|
|
343
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
344
|
+
import { createFileRoute, useRouter } from "@tanstack/react-router";
|
|
345
|
+
import { useEffect } from "react";
|
|
346
|
+
import { z } from "zod";
|
|
347
|
+
import { ${names.capital}Service${
|
|
348
|
+
columns.some((col) => ["string-image", "array-images"].includes(col.renderType))
|
|
349
|
+
? ", FileService"
|
|
350
|
+
: ""
|
|
351
|
+
} } from "@/services/services.generated";
|
|
352
|
+
import type { ${names.capital}SubsetA } from "@/services/sonamu.generated";${
|
|
353
|
+
columns.filter((col) => col.renderType === "enums").length > 0
|
|
354
|
+
? "\nimport { " +
|
|
355
|
+
unique(
|
|
356
|
+
columns
|
|
357
|
+
.filter((col) => col.renderType === "enums")
|
|
358
|
+
.map((col) => {
|
|
359
|
+
try {
|
|
360
|
+
const { id } = getEnumInfoFromColName(entityId, col.name);
|
|
361
|
+
return `${id}, ${id}Label`;
|
|
362
|
+
} catch {
|
|
363
|
+
return "";
|
|
364
|
+
}
|
|
365
|
+
}),
|
|
366
|
+
)
|
|
367
|
+
.filter(Boolean)
|
|
368
|
+
.join(", ") +
|
|
369
|
+
' } from "@/services/sonamu.generated";'
|
|
370
|
+
: ""
|
|
371
|
+
}
|
|
372
|
+
import { defaultCatch } from "@/services/sonamu.shared";
|
|
373
|
+
import { ${names.capital}SaveParams } from "@/services/${names.fs}/${names.fs}.types";
|
|
261
374
|
${unique(
|
|
262
375
|
columns
|
|
263
376
|
.filter((col) => ["number-fk_id", "enums"].includes(col.renderType))
|
|
@@ -266,119 +379,185 @@ ${unique(
|
|
|
266
379
|
}),
|
|
267
380
|
).join("\n")}
|
|
268
381
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
382
|
+
import ArrowLeftIcon from "~icons/lucide/arrow-left";
|
|
383
|
+
import SaveIcon from "~icons/lucide/save";
|
|
384
|
+
import FormIcon from "~icons/mdi/form-select";
|
|
385
|
+
|
|
386
|
+
const formSearchSchema = z.object({
|
|
387
|
+
id: z.number().optional(),
|
|
388
|
+
});
|
|
275
389
|
|
|
276
|
-
|
|
390
|
+
export const Route = createFileRoute("/admin/${names.fsPlural}/form")({
|
|
391
|
+
validateSearch: formSearchSchema,
|
|
392
|
+
component: ${names.capitalPlural}FormPage,
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
function ${names.capitalPlural}FormPage() {
|
|
396
|
+
const { id } = Route.useSearch();
|
|
397
|
+
return <${names.capitalPlural}Form id={id} />;
|
|
277
398
|
}
|
|
399
|
+
|
|
278
400
|
type ${names.capitalPlural}FormProps = {
|
|
279
401
|
id?: number;
|
|
280
|
-
mode?:
|
|
402
|
+
mode?: "page" | "modal";
|
|
281
403
|
};
|
|
404
|
+
|
|
282
405
|
export function ${names.capitalPlural}Form({ id, mode }: ${names.capitalPlural}FormProps) {
|
|
283
|
-
|
|
284
|
-
const
|
|
406
|
+
const router = useRouter();
|
|
407
|
+
const queryClient = useQueryClient();
|
|
408
|
+
|
|
409
|
+
const { form, setForm, register } = useTypeForm(${names.capital}SaveParams, ${JSON.stringify(defaultValue).replace(/"now\(\)"/g, '""')});
|
|
410
|
+
${(() => {
|
|
411
|
+
const hasDatetime = columns.some((col) => col.renderType === "string-datetime");
|
|
412
|
+
const hasDate = columns.some((col) => col.renderType === "string-date");
|
|
413
|
+
if (!hasDatetime && !hasDate) return "";
|
|
414
|
+
|
|
415
|
+
let helpers = "\n";
|
|
416
|
+
if (hasDatetime) {
|
|
417
|
+
helpers += ` // datetime-local 형식으로 변환 (YYYY-MM-DDTHH:MM)
|
|
418
|
+
const toDatetimeLocalString = (date: Date | string | null | undefined): string => {
|
|
419
|
+
if (!date) return "";
|
|
420
|
+
const d = typeof date === "string" ? new Date(date) : date;
|
|
421
|
+
return d.toISOString().slice(0, 16);
|
|
422
|
+
};
|
|
285
423
|
|
|
286
|
-
//
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
424
|
+
// datetime-local 문자열을 Date로 변환
|
|
425
|
+
const fromDatetimeLocalString = (value: string): Date | null => {
|
|
426
|
+
if (!value) return null;
|
|
427
|
+
return new Date(value);
|
|
428
|
+
};
|
|
429
|
+
`;
|
|
430
|
+
}
|
|
431
|
+
if (hasDate) {
|
|
432
|
+
helpers += ` // date 형식으로 변환 (YYYY-MM-DD)
|
|
433
|
+
const toDateString = (date: Date | string | null | undefined): string => {
|
|
434
|
+
if (!date) return "";
|
|
435
|
+
const d = typeof date === "string" ? new Date(date) : date;
|
|
436
|
+
return d.toISOString().split("T")[0];
|
|
437
|
+
};
|
|
290
438
|
|
|
291
|
-
//
|
|
439
|
+
// date 문자열을 Date로 변환
|
|
440
|
+
const fromDateString = (value: string): Date | null => {
|
|
441
|
+
if (!value) return null;
|
|
442
|
+
return new Date(value);
|
|
443
|
+
};
|
|
444
|
+
`;
|
|
445
|
+
}
|
|
446
|
+
return helpers;
|
|
447
|
+
})()}
|
|
292
448
|
useEffect(() => {
|
|
293
449
|
if (id) {
|
|
294
|
-
${names.capital}Service.get${names.capital}(
|
|
295
|
-
|
|
296
|
-
|
|
450
|
+
${names.capital}Service.get${names.capital}("A", id).then((row) => {
|
|
451
|
+
setForm((prevForm) => ({
|
|
452
|
+
...prevForm,
|
|
297
453
|
...row,
|
|
298
|
-
|
|
299
|
-
.filter((col) => col.renderType === "number-fk_id")
|
|
300
|
-
.map((col) => {
|
|
301
|
-
if (col.nullable) {
|
|
302
|
-
return `${col.name}: row.${col.name.replace("_id", "?.id")} ?? null`;
|
|
303
|
-
} else {
|
|
304
|
-
return `${col.name}: row.${col.name.replace("_id", ".id")}`;
|
|
305
|
-
}
|
|
306
|
-
})
|
|
307
|
-
.join(",\n")}
|
|
308
|
-
});
|
|
454
|
+
}));
|
|
309
455
|
});
|
|
310
456
|
}
|
|
311
|
-
}, [id]);
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
457
|
+
}, [id, setForm]);
|
|
458
|
+
|
|
459
|
+
const saveMutation = ${names.capital}Service.useSaveMutation();
|
|
460
|
+
const handleSubmit = () => {
|
|
461
|
+
saveMutation.mutate(
|
|
462
|
+
{ spa: [form] },
|
|
463
|
+
{
|
|
464
|
+
onSuccess: () => {
|
|
465
|
+
queryClient.invalidateQueries({
|
|
466
|
+
queryKey: ["${names.capital}"],
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
if (mode === "modal") {
|
|
470
|
+
// modal mode
|
|
471
|
+
} else {
|
|
472
|
+
router.navigate({ to: "/admin/${names.fsPlural}" });
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
onError: defaultCatch,
|
|
476
|
+
},
|
|
477
|
+
);
|
|
478
|
+
};
|
|
327
479
|
|
|
328
|
-
// 페이지
|
|
329
480
|
const PAGE = {
|
|
330
|
-
title: \`${entity.title ?? names.capital}\${id ?
|
|
331
|
-
}
|
|
481
|
+
title: \`${entity.title ?? names.capital}\${id ? \` #\${id} Edit\` : " Create"}\`,
|
|
482
|
+
};
|
|
332
483
|
|
|
333
484
|
return (
|
|
334
|
-
<div className="
|
|
335
|
-
<
|
|
336
|
-
<
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
485
|
+
<div className="flex-1 overflow-auto">
|
|
486
|
+
<div className="max-w-[1800px] mx-auto p-8">
|
|
487
|
+
<div className="space-y-6 mb-8">
|
|
488
|
+
{/* Header */}
|
|
489
|
+
<div className="flex items-center justify-between">
|
|
490
|
+
<div className="flex items-center gap-2">
|
|
491
|
+
<FormIcon className="h-5 w-5" />
|
|
492
|
+
<span className="text-lg font-semibold h-5">{PAGE.title}</span>
|
|
493
|
+
</div>
|
|
494
|
+
{mode !== "modal" && (
|
|
495
|
+
<Button
|
|
496
|
+
variant="outline"
|
|
497
|
+
onClick={() => router.navigate({ to: "/admin/${names.fsPlural}" })}
|
|
498
|
+
icon={<ArrowLeftIcon />}
|
|
499
|
+
>
|
|
500
|
+
Back To List
|
|
501
|
+
</Button>
|
|
502
|
+
)}
|
|
346
503
|
</div>
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
504
|
+
|
|
505
|
+
{/* Form Card */}
|
|
506
|
+
<Card className="border-border/40 bg-gray-50 shadow-sm">
|
|
507
|
+
<CardHeader className="px-4 border-b border-gray-200 flex items-center">
|
|
508
|
+
<CardTitle className="text-sm font-medium leading-none m-0">
|
|
509
|
+
{PAGE.title}
|
|
510
|
+
</CardTitle>
|
|
511
|
+
</CardHeader>
|
|
512
|
+
<CardContent className="p-6">
|
|
513
|
+
<div className="space-y-6">
|
|
514
|
+
${columns
|
|
515
|
+
.filter((col) => col.name !== "created_at")
|
|
516
|
+
.map((col) => {
|
|
517
|
+
const label = (() => {
|
|
518
|
+
if (col.label.endsWith("Id")) {
|
|
519
|
+
try {
|
|
520
|
+
const entity = EntityManager.get(col.label.replace("Id", ""));
|
|
521
|
+
return entity.title ?? col.label;
|
|
522
|
+
} catch {
|
|
523
|
+
return col.label;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return col.label;
|
|
527
|
+
})();
|
|
528
|
+
return ` {/* ${label} */}
|
|
529
|
+
<div className="space-y-2">
|
|
530
|
+
<label className="block text-xs mb-1 text-gray-600">${label}</label>
|
|
531
|
+
${this.renderColumn(entityId, col, names)}
|
|
532
|
+
</div>`;
|
|
533
|
+
})
|
|
534
|
+
.join("\n\n")}
|
|
535
|
+
|
|
536
|
+
{/* Save Button */}
|
|
537
|
+
<div className="flex items-center justify-between pt-4">
|
|
538
|
+
{form.id && form.created_at && (
|
|
539
|
+
<div className="flex items-center">
|
|
540
|
+
<label className="mr-2 text-xs text-gray-600">Created At:</label>
|
|
541
|
+
<span className="text-xs text-gray-600">
|
|
542
|
+
{String(form.created_at)}
|
|
543
|
+
</span>
|
|
544
|
+
</div>
|
|
545
|
+
)}
|
|
546
|
+
<Button
|
|
547
|
+
onClick={handleSubmit}
|
|
548
|
+
icon={<SaveIcon />}
|
|
549
|
+
>
|
|
550
|
+
Save
|
|
551
|
+
</Button>
|
|
552
|
+
</div>
|
|
553
|
+
</div>
|
|
554
|
+
</CardContent>
|
|
555
|
+
</Card>
|
|
556
|
+
</div>
|
|
557
|
+
</div>
|
|
379
558
|
</div>
|
|
380
559
|
);
|
|
381
|
-
}
|
|
560
|
+
}
|
|
382
561
|
`.trim(),
|
|
383
562
|
importKeys: [],
|
|
384
563
|
preTemplates,
|