|
179 | 179 | return idField ? recordData[idField] : null; |
180 | 180 | } |
181 | 181 |
|
| 182 | + // Render a field value for display. Objects/arrays (json fields) are |
| 183 | + // stringified so they don't show as "[object Object]". |
| 184 | + function displayValue(value: any): string { |
| 185 | + if (value === undefined || value === null) return "-"; |
| 186 | + if (typeof value === "object") return JSON.stringify(value); |
| 187 | + return String(value); |
| 188 | + } |
| 189 | +
|
182 | 190 | let dataRecords = $state(data.dataRecords || []); |
183 | 191 | let searchQuery = $state(""); |
184 | 192 | let showCreateModal = $state(false); |
|
217 | 225 | formData = {}; |
218 | 226 | const recordData = record ? getRecordData(record) : null; |
219 | 227 | Object.keys(properties).forEach((fieldName) => { |
220 | | - formData[fieldName] = recordData ? recordData[fieldName] : ""; |
| 228 | + const rawValue = recordData ? recordData[fieldName] : ""; |
| 229 | + // json fields are edited as text, so render existing objects/arrays as |
| 230 | + // pretty-printed JSON for the textarea. |
| 231 | + if (properties[fieldName].type === "json" && rawValue && typeof rawValue === "object") { |
| 232 | + formData[fieldName] = JSON.stringify(rawValue, null, 2); |
| 233 | + } else { |
| 234 | + formData[fieldName] = rawValue; |
| 235 | + } |
221 | 236 | }); |
222 | 237 | validationErrors = {}; |
223 | 238 | } |
|
276 | 291 | case "boolean": |
277 | 292 | // Accept true/false or convert from string |
278 | 293 | break; |
| 294 | + case "json": |
| 295 | + // Must parse to a JSON object or array (OBP rejects bare strings/numbers) |
| 296 | + if (typeof value === "object") break; |
| 297 | + try { |
| 298 | + const parsed = JSON.parse(value); |
| 299 | + if (typeof parsed !== "object" || parsed === null) { |
| 300 | + return "Must be a JSON object or array"; |
| 301 | + } |
| 302 | + } catch (e) { |
| 303 | + return e instanceof Error ? `Invalid JSON: ${e.message}` : "Invalid JSON"; |
| 304 | + } |
| 305 | + break; |
279 | 306 | case "DATE_WITH_DAY": |
280 | 307 | if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) { |
281 | 308 | return "Must be in format YYYY-MM-DD"; |
|
339 | 366 | // Convert to number (JavaScript/JSON doesn't distinguish int vs float) |
340 | 367 | converted[fieldName] = Number(value); |
341 | 368 | break; |
| 369 | + case "json": |
| 370 | + // Parse the textarea contents into a real JSON object/array. |
| 371 | + // If the value is already an object (e.g. loaded from an existing |
| 372 | + // record), pass it through unchanged. |
| 373 | + converted[fieldName] = |
| 374 | + typeof value === "string" ? JSON.parse(value) : value; |
| 375 | + break; |
342 | 376 | default: |
343 | 377 | // Keep as string |
344 | 378 | converted[fieldName] = String(value); |
|
576 | 610 | case "integer": |
577 | 611 | case "number": |
578 | 612 | return "number"; |
| 613 | + case "json": |
| 614 | + return "textarea"; |
579 | 615 | case "DATE_WITH_DAY": |
580 | 616 | return "text"; |
581 | 617 | default: |
|
939 | 975 | <td |
940 | 976 | class="max-w-xs truncate px-6 py-4 text-sm text-gray-900 dark:text-gray-100" |
941 | 977 | > |
942 | | - {recordData[fieldName] !== undefined && |
943 | | - recordData[fieldName] !== null |
944 | | - ? String(recordData[fieldName]) |
945 | | - : "-"} |
| 978 | + {displayValue(recordData[fieldName])} |
946 | 979 | </td> |
947 | 980 | {/each} |
948 | 981 | <td class="whitespace-nowrap px-6 py-4 text-right text-sm"> |
|
1037 | 1070 | class="sticky top-0 flex items-center justify-between border-b border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-800" |
1038 | 1071 | > |
1039 | 1072 | <h2 class="text-xl font-semibold text-gray-900 dark:text-gray-100"> |
1040 | | - Create New Record |
| 1073 | + Create New {entityName} Record |
1041 | 1074 | </h2> |
1042 | 1075 | <button |
1043 | 1076 | type="button" |
|
1100 | 1133 | class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" |
1101 | 1134 | /> |
1102 | 1135 | </div> |
| 1136 | + {:else if inputType === "textarea"} |
| 1137 | + <textarea |
| 1138 | + id="create-{fieldName}" |
| 1139 | + bind:value={formData[fieldName]} |
| 1140 | + rows="8" |
| 1141 | + placeholder={fieldDef.example |
| 1142 | + ? JSON.stringify(fieldDef.example, null, 2) |
| 1143 | + : '{ }'} |
| 1144 | + 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" |
| 1145 | + ></textarea> |
1103 | 1146 | {:else} |
1104 | 1147 | <input |
1105 | 1148 | type={inputType} |
|
1233 | 1276 | class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" |
1234 | 1277 | /> |
1235 | 1278 | </div> |
| 1279 | + {:else if inputType === "textarea"} |
| 1280 | + <textarea |
| 1281 | + id="edit-{fieldName}" |
| 1282 | + bind:value={formData[fieldName]} |
| 1283 | + rows="8" |
| 1284 | + placeholder={fieldDef.example |
| 1285 | + ? JSON.stringify(fieldDef.example, null, 2) |
| 1286 | + : '{ }'} |
| 1287 | + 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" |
| 1288 | + ></textarea> |
1236 | 1289 | {:else} |
1237 | 1290 | <input |
1238 | 1291 | type={inputType} |
|
1337 | 1390 | <span class="ml-1 text-xs text-red-600">(Required)</span> |
1338 | 1391 | {/if} |
1339 | 1392 | </dt> |
1340 | | - <dd class="mt-1 text-sm text-gray-900 dark:text-gray-100"> |
1341 | | - {recordData[fieldName] !== undefined && |
1342 | | - recordData[fieldName] !== null |
1343 | | - ? String(recordData[fieldName]) |
1344 | | - : "-"} |
| 1393 | + <dd class="mt-1 whitespace-pre-wrap break-words font-mono text-sm text-gray-900 dark:text-gray-100"> |
| 1394 | + {properties[fieldName].type === "json" && |
| 1395 | + recordData[fieldName] && |
| 1396 | + typeof recordData[fieldName] === "object" |
| 1397 | + ? JSON.stringify(recordData[fieldName], null, 2) |
| 1398 | + : displayValue(recordData[fieldName])} |
1345 | 1399 | </dd> |
1346 | 1400 | </div> |
1347 | 1401 | {/each} |
|
0 commit comments