|
4 | 4 | extractErrorFromResponse, |
5 | 5 | formatErrorForDisplay, |
6 | 6 | } from "$lib/utils/errorHandler"; |
| 7 | + import { |
| 8 | + validateDynamicEntityFields, |
| 9 | + convertDynamicEntityFormData, |
| 10 | + initialDynamicEntityFormData, |
| 11 | + dynamicEntityInputType, |
| 12 | + type DynamicEntityFieldDef, |
| 13 | + } from "@obp/shared/obp"; |
7 | 14 |
|
8 | 15 | let { data }: { data: PageData } = $props(); |
9 | 16 |
|
|
16 | 23 | let schema = $derived(getSchema(entity)); |
17 | 24 | let entityName = $derived(entity.entity_name || "Unknown"); |
18 | 25 |
|
19 | | - interface FieldDef { |
20 | | - type?: string; |
21 | | - description?: string; |
22 | | - example?: unknown; |
23 | | - minimum?: number; |
24 | | - maximum?: number; |
25 | | - minLength?: number; |
26 | | - maxLength?: number; |
27 | | - } |
28 | | -
|
29 | | - let properties = $derived<Record<string, FieldDef>>(schema?.properties || {}); |
| 26 | + let properties = $derived<Record<string, DynamicEntityFieldDef>>( |
| 27 | + schema?.properties || {}, |
| 28 | + ); |
30 | 29 | let requiredFields = $derived<string[]>(schema?.required || []); |
31 | 30 | let description = $derived(schema?.description || "No description available"); |
32 | 31 |
|
|
118 | 117 | let schemaCollapsed = $state(true); |
119 | 118 |
|
120 | 119 | function initializeFormData(record?: any) { |
121 | | - formData = {}; |
122 | | - const recordData = record ? getRecordData(record) : null; |
123 | | - Object.keys(properties).forEach((fieldName) => { |
124 | | - formData[fieldName] = recordData ? recordData[fieldName] : ""; |
125 | | - }); |
| 120 | + formData = initialDynamicEntityFormData( |
| 121 | + properties, |
| 122 | + record ? getRecordData(record) : null, |
| 123 | + ); |
126 | 124 | validationErrors = {}; |
127 | 125 | } |
128 | 126 |
|
|
151 | 149 | validationErrors = {}; |
152 | 150 | } |
153 | 151 |
|
154 | | - function validateField(fieldName: string, value: any): string | null { |
155 | | - const fieldDef = properties[fieldName]; |
156 | | - const isRequired = requiredFields.includes(fieldName); |
157 | | -
|
158 | | - if (isRequired && (!value || value === "")) { |
159 | | - return "This field is required"; |
160 | | - } |
161 | | -
|
162 | | - if (value !== null && value !== undefined && value !== "") { |
163 | | - switch (fieldDef.type) { |
164 | | - case "integer": |
165 | | - case "number": |
166 | | - const num = Number(value); |
167 | | - if (isNaN(num)) { |
168 | | - return "Must be a valid number"; |
169 | | - } |
170 | | - if (fieldDef.type === "integer" && !Number.isInteger(num)) { |
171 | | - return "Must be an integer"; |
172 | | - } |
173 | | - if (fieldDef.minimum !== undefined && num < fieldDef.minimum) { |
174 | | - return `Must be at least ${fieldDef.minimum}`; |
175 | | - } |
176 | | - if (fieldDef.maximum !== undefined && num > fieldDef.maximum) { |
177 | | - return `Must be at most ${fieldDef.maximum}`; |
178 | | - } |
179 | | - break; |
180 | | - case "boolean": |
181 | | - break; |
182 | | - case "DATE_WITH_DAY": |
183 | | - if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) { |
184 | | - return "Must be in format YYYY-MM-DD"; |
185 | | - } |
186 | | - break; |
187 | | - default: |
188 | | - if (fieldDef.minLength && String(value).length < fieldDef.minLength) { |
189 | | - return `Must be at least ${fieldDef.minLength} characters`; |
190 | | - } |
191 | | - if (fieldDef.maxLength && String(value).length > fieldDef.maxLength) { |
192 | | - return `Must be at most ${fieldDef.maxLength} characters`; |
193 | | - } |
194 | | - break; |
195 | | - } |
196 | | - } |
197 | | -
|
198 | | - return null; |
199 | | - } |
200 | | -
|
201 | 152 | function validateAllFields(): boolean { |
202 | | - let isValid = true; |
203 | | - validationErrors = {}; |
204 | | -
|
205 | | - Object.keys(properties).forEach((fieldName) => { |
206 | | - const error = validateField(fieldName, formData[fieldName]); |
207 | | - if (error) { |
208 | | - validationErrors[fieldName] = error; |
209 | | - isValid = false; |
210 | | - } |
211 | | - }); |
212 | | -
|
213 | | - return isValid; |
214 | | - } |
215 | | -
|
216 | | - function convertFormDataToApiFormat( |
217 | | - data: Record<string, any>, |
218 | | - ): Record<string, any> { |
219 | | - const converted: Record<string, any> = {}; |
220 | | -
|
221 | | - Object.keys(properties).forEach((fieldName) => { |
222 | | - const fieldDef = properties[fieldName]; |
223 | | - const value = data[fieldName]; |
224 | | -
|
225 | | - if (value === "" || value === null || value === undefined) { |
226 | | - return; |
227 | | - } |
228 | | -
|
229 | | - switch (fieldDef.type) { |
230 | | - case "boolean": |
231 | | - converted[fieldName] = value ? "true" : "false"; |
232 | | - break; |
233 | | - case "integer": |
234 | | - converted[fieldName] = Math.round(Number(value)); |
235 | | - break; |
236 | | - case "number": |
237 | | - converted[fieldName] = Number(value); |
238 | | - break; |
239 | | - default: |
240 | | - converted[fieldName] = String(value); |
241 | | - break; |
242 | | - } |
243 | | - }); |
244 | | -
|
245 | | - return converted; |
246 | | - } |
247 | | -
|
248 | | - function renderFieldInput(fieldName: string, fieldDef: any) { |
249 | | - switch (fieldDef.type) { |
250 | | - case "boolean": |
251 | | - return "checkbox"; |
252 | | - case "integer": |
253 | | - case "number": |
254 | | - return "number"; |
255 | | - default: |
256 | | - return "text"; |
257 | | - } |
| 153 | + validationErrors = validateDynamicEntityFields( |
| 154 | + properties, |
| 155 | + requiredFields, |
| 156 | + formData, |
| 157 | + ); |
| 158 | + return Object.keys(validationErrors).length === 0; |
258 | 159 | } |
259 | 160 |
|
260 | 161 | // Extract records from response (client-side refetch) |
|
296 | 197 | isSubmitting = true; |
297 | 198 |
|
298 | 199 | try { |
299 | | - const convertedData = convertFormDataToApiFormat(formData); |
| 200 | + const convertedData = convertDynamicEntityFormData(properties, formData); |
300 | 201 |
|
301 | 202 | const response = await fetch( |
302 | 203 | `/proxy/obp/dynamic-entity/my/${entityName}`, |
|
346 | 247 | isSubmitting = true; |
347 | 248 |
|
348 | 249 | try { |
349 | | - const convertedData = convertFormDataToApiFormat(formData); |
| 250 | + const convertedData = convertDynamicEntityFormData(properties, formData); |
350 | 251 |
|
351 | 252 | const response = await fetch( |
352 | 253 | `/proxy/obp/dynamic-entity/my/${entityName}/${recordId}`, |
|
954 | 855 | <div class="space-y-4"> |
955 | 856 | {#each Object.entries(properties) as [fieldName, fieldDef]} |
956 | 857 | {@const isRequired = requiredFields.includes(fieldName)} |
957 | | - {@const inputType = renderFieldInput(fieldName, fieldDef)} |
| 858 | + {@const inputType = dynamicEntityInputType(fieldDef)} |
958 | 859 | <div> |
959 | 860 | <label |
960 | 861 | for="create-{fieldName}" |
|
984 | 885 | class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" |
985 | 886 | /> |
986 | 887 | </div> |
| 888 | + {:else if inputType === "textarea"} |
| 889 | + <textarea |
| 890 | + id="create-{fieldName}" |
| 891 | + bind:value={formData[fieldName]} |
| 892 | + rows="8" |
| 893 | + placeholder={fieldDef.example |
| 894 | + ? JSON.stringify(fieldDef.example, null, 2) |
| 895 | + : '{ }'} |
| 896 | + class="mt-2 block w-full rounded-lg border border-gray-300 bg-white px-3 py-2 font-mono text-sm text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100" |
| 897 | + ></textarea> |
987 | 898 | {:else} |
988 | 899 | <input |
989 | 900 | type={inputType} |
|
1086 | 997 | <div class="space-y-4"> |
1087 | 998 | {#each Object.entries(properties) as [fieldName, fieldDef]} |
1088 | 999 | {@const isRequired = requiredFields.includes(fieldName)} |
1089 | | - {@const inputType = renderFieldInput(fieldName, fieldDef)} |
| 1000 | + {@const inputType = dynamicEntityInputType(fieldDef)} |
1090 | 1001 | <div> |
1091 | 1002 | <label |
1092 | 1003 | for="edit-{fieldName}" |
|
1116 | 1027 | class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" |
1117 | 1028 | /> |
1118 | 1029 | </div> |
| 1030 | + {:else if inputType === "textarea"} |
| 1031 | + <textarea |
| 1032 | + id="edit-{fieldName}" |
| 1033 | + bind:value={formData[fieldName]} |
| 1034 | + rows="8" |
| 1035 | + placeholder={fieldDef.example |
| 1036 | + ? JSON.stringify(fieldDef.example, null, 2) |
| 1037 | + : '{ }'} |
| 1038 | + class="mt-2 block w-full rounded-lg border border-gray-300 bg-white px-3 py-2 font-mono text-sm text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100" |
| 1039 | + ></textarea> |
1119 | 1040 | {:else} |
1120 | 1041 | <input |
1121 | 1042 | type={inputType} |
|
0 commit comments