@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.
@@ -1,4 +1,3 @@
1
- // packages/nextmin-react/src/components/FileUploader.tsx
2
1
  'use client';
3
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
4
3
  import { useEffect, useMemo, useRef, useState } from 'react';
@@ -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, ...payload } = form;
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@airoom/nextmin-react",
3
- "version": "0.1.8",
3
+ "version": "1.1.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",