@airoom/nextmin-react 0.1.8 → 1.1.0
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.
|
@@ -180,6 +180,69 @@ function normalizeTimeRangeLoose(value) {
|
|
|
180
180
|
}
|
|
181
181
|
return {};
|
|
182
182
|
}
|
|
183
|
+
function getJsonGroupSpec(attr) {
|
|
184
|
+
// 1) Preferred: string field with format: 'json-group'
|
|
185
|
+
if (attr && typeof attr === 'object' && !Array.isArray(attr)) {
|
|
186
|
+
const a = attr;
|
|
187
|
+
const type = String(a.type ?? '').toLowerCase();
|
|
188
|
+
const fmt = String(a.format ?? '').toLowerCase();
|
|
189
|
+
if (fmt === 'json-group' &&
|
|
190
|
+
a.attributes &&
|
|
191
|
+
typeof a.attributes === 'object') {
|
|
192
|
+
const spec = {
|
|
193
|
+
attributes: a.attributes,
|
|
194
|
+
minItems: a.minItems,
|
|
195
|
+
maxItems: a.maxItems,
|
|
196
|
+
label: a.label,
|
|
197
|
+
required: !!a.required,
|
|
198
|
+
repeat: !!a.repeat, // repeat => render as repeater
|
|
199
|
+
};
|
|
200
|
+
return { kind: spec.repeat ? 'array' : 'single', spec };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// 2) Back-compat (if you still have it somewhere): array-head style
|
|
204
|
+
if (Array.isArray(attr) &&
|
|
205
|
+
attr.length > 0 &&
|
|
206
|
+
attr[0] &&
|
|
207
|
+
typeof attr[0] === 'object') {
|
|
208
|
+
const head = attr[0];
|
|
209
|
+
const t = String(head.type ?? head.format ?? '').toLowerCase();
|
|
210
|
+
if ((t === 'json-group' || head.group === true) &&
|
|
211
|
+
head.attributes &&
|
|
212
|
+
typeof head.attributes === 'object') {
|
|
213
|
+
return {
|
|
214
|
+
kind: 'array',
|
|
215
|
+
spec: {
|
|
216
|
+
attributes: head.attributes,
|
|
217
|
+
minItems: head.minItems,
|
|
218
|
+
maxItems: head.maxItems,
|
|
219
|
+
label: head.label,
|
|
220
|
+
required: !!head.required,
|
|
221
|
+
repeat: true,
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
function parseJsonGroupValue(raw, kind) {
|
|
229
|
+
if (raw == null || raw === '')
|
|
230
|
+
return (kind === 'array' ? [] : {});
|
|
231
|
+
if (typeof raw === 'string') {
|
|
232
|
+
try {
|
|
233
|
+
return JSON.parse(raw);
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return (kind === 'array' ? [] : {});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return raw;
|
|
240
|
+
}
|
|
241
|
+
function toJsonString(v) {
|
|
242
|
+
if (v == null || v === '')
|
|
243
|
+
return '';
|
|
244
|
+
return typeof v === 'string' ? v : JSON.stringify(v);
|
|
245
|
+
}
|
|
183
246
|
/** --------------------------------------------------------------------------------------- **/
|
|
184
247
|
export function SchemaForm({ model, schemaOverride, initialValues, submitLabel = 'Save', busy = false, showReset = true, onSubmit, fieldErrors, }) {
|
|
185
248
|
const mapsKey = useGoogleMapsKey();
|
|
@@ -256,7 +319,15 @@ export function SchemaForm({ model, schemaOverride, initialValues, submitLabel =
|
|
|
256
319
|
e.preventDefault();
|
|
257
320
|
setError(undefined);
|
|
258
321
|
try {
|
|
259
|
-
const { createdAt, updatedAt, baseId, exId, __childId, ...
|
|
322
|
+
const { createdAt, updatedAt, baseId, exId, __childId, ...rest } = form;
|
|
323
|
+
const payload = { ...rest };
|
|
324
|
+
if (schema) {
|
|
325
|
+
for (const [fname, fattr] of Object.entries(schema.attributes)) {
|
|
326
|
+
const jg = getJsonGroupSpec(fattr);
|
|
327
|
+
if (jg)
|
|
328
|
+
payload[fname] = toJsonString(payload[fname]);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
260
331
|
await onSubmit(payload);
|
|
261
332
|
}
|
|
262
333
|
catch (err) {
|
|
@@ -279,6 +350,41 @@ export function SchemaForm({ model, schemaOverride, initialValues, submitLabel =
|
|
|
279
350
|
};
|
|
280
351
|
return (_jsxs(Form, { className: gridClass, onSubmit: handleSubmit, onReset: handleReset, validationBehavior: "native", encType: "multipart/form-data", children: [fields.map(({ name, attr }) => {
|
|
281
352
|
const colClass = 'col-span-1';
|
|
353
|
+
const jsonGroup = getJsonGroupSpec(attr);
|
|
354
|
+
if (jsonGroup?.kind === 'array') {
|
|
355
|
+
const { spec } = jsonGroup;
|
|
356
|
+
const itemSchema = spec.attributes;
|
|
357
|
+
const items = Array.isArray(form[name])
|
|
358
|
+
? form[name]
|
|
359
|
+
: parseJsonGroupValue(form[name], 'array');
|
|
360
|
+
const canAdd = spec.maxItems == null || items.length < spec.maxItems;
|
|
361
|
+
const canRemove = spec.minItems == null || items.length > spec.minItems;
|
|
362
|
+
return (_jsxs("div", { className: "col-span-2", children: [_jsxs("label", { className: "text-sm font-medium", children: [attr?.label || spec.label || formatLabel(name), attr?.required ? ' *' : ''] }), _jsxs("div", { className: "flex flex-col gap-4 mt-2", children: [items.map((it, idx) => (_jsxs("div", { className: "grid grid-cols-2 gap-3 p-3 rounded-lg border border-default-200", children: [Object.entries(itemSchema).map(([k, a]) => (_jsx(Input, { variant: "bordered", classNames: inputClassNames, id: `${name}-${idx}-${k}`, name: `${name}.${idx}.${k}`, label: a?.label ?? formatLabel(k), labelPlacement: "outside-top", type: "text", value: typeof it?.[k] === 'string' ? it[k] : '', onChange: (e) => {
|
|
363
|
+
const next = items.slice();
|
|
364
|
+
next[idx] = {
|
|
365
|
+
...(next[idx] || {}),
|
|
366
|
+
[k]: e.target.value,
|
|
367
|
+
};
|
|
368
|
+
handleChange(name, next);
|
|
369
|
+
}, isDisabled: busy, description: a?.description, className: "w-full", isRequired: !!a?.required }, `${name}-${idx}-${k}`))), _jsx("div", { className: "col-span-2 flex justify-between", children: _jsx(Button, { size: "sm", variant: "flat", color: "danger", isDisabled: !canRemove, onPress: () => {
|
|
370
|
+
const next = items.slice();
|
|
371
|
+
next.splice(idx, 1);
|
|
372
|
+
handleChange(name, next);
|
|
373
|
+
}, children: "Remove" }) })] }, idx))), _jsxs(Button, { size: "sm", variant: "solid", isDisabled: !canAdd, onPress: () => handleChange(name, [...items, {}]), children: ["Add ", attr?.label || spec.label || 'Item'] })] })] }, name));
|
|
374
|
+
}
|
|
375
|
+
if (jsonGroup?.kind === 'single') {
|
|
376
|
+
const { spec } = jsonGroup;
|
|
377
|
+
const itemSchema = spec.attributes;
|
|
378
|
+
const obj = form[name] &&
|
|
379
|
+
typeof form[name] === 'object' &&
|
|
380
|
+
!Array.isArray(form[name])
|
|
381
|
+
? form[name]
|
|
382
|
+
: parseJsonGroupValue(form[name], 'single');
|
|
383
|
+
return (_jsxs("div", { className: "col-span-2", children: [_jsxs("label", { className: "text-sm font-medium", children: [attr?.label || spec.label || formatLabel(name), attr?.required ? ' *' : ''] }), _jsx("div", { className: "grid grid-cols-2 gap-3 p-3 rounded-lg border border-default-200 mt-2", children: Object.entries(itemSchema).map(([k, a]) => (_jsx(Input, { variant: "bordered", classNames: inputClassNames, id: `${name}-${k}`, name: `${name}.${k}`, label: a?.label ?? formatLabel(k), labelPlacement: "outside-top", type: "text", value: typeof obj?.[k] === 'string' ? obj[k] : '', onChange: (e) => {
|
|
384
|
+
const next = { ...(obj || {}), [k]: e.target.value };
|
|
385
|
+
handleChange(name, next);
|
|
386
|
+
}, isDisabled: busy, description: a?.description, className: "w-full", isRequired: !!a?.required }, `${name}-${k}`))) })] }, name));
|
|
387
|
+
}
|
|
282
388
|
// --- 1) Array of references → multi select (1 column)
|
|
283
389
|
const refArray = getRefArraySpec(attr);
|
|
284
390
|
if (refArray) {
|