@asteby/metacore-runtime-react 18.1.0 → 18.3.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.
- package/CHANGELOG.md +38 -0
- package/dist/dialogs/dynamic-record.d.ts +86 -2
- package/dist/dialogs/dynamic-record.d.ts.map +1 -1
- package/dist/dialogs/dynamic-record.js +305 -88
- package/dist/dynamic-form.d.ts.map +1 -1
- package/dist/dynamic-form.js +29 -3
- package/dist/dynamic-relation-helpers.d.ts.map +1 -1
- package/dist/dynamic-relation-helpers.js +18 -0
- package/dist/dynamic-relation.d.ts.map +1 -1
- package/dist/dynamic-relation.js +14 -0
- package/dist/dynamic-select-field.d.ts +29 -1
- package/dist/dynamic-select-field.d.ts.map +1 -1
- package/dist/dynamic-select-field.js +4 -3
- package/dist/image-url-context.d.ts +13 -0
- package/dist/image-url-context.d.ts.map +1 -0
- package/dist/image-url-context.js +17 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/__tests__/dynamic-relation.test.ts +17 -0
- package/src/dialogs/dynamic-record.tsx +476 -114
- package/src/dynamic-form.tsx +33 -2
- package/src/dynamic-relation-helpers.ts +18 -0
- package/src/dynamic-relation.tsx +19 -0
- package/src/dynamic-select-field.tsx +11 -3
- package/src/image-url-context.tsx +23 -0
- package/src/index.ts +2 -1
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// DynamicRecordDialog — renders a create/edit/view modal for a model based
|
|
3
|
-
// on metadata fetched from `/metadata/modal/:model`.
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// on metadata fetched from `/metadata/modal/:model`. This is the single,
|
|
4
|
+
// SDK-owned source of truth for declarative record rendering (the ops fork was
|
|
5
|
+
// consolidated back into here): tz-aware dates, FK image/label leads in both
|
|
6
|
+
// view and edit, resolved relation/user-object labels (never raw JSON), nil-UUID
|
|
7
|
+
// elision, pro option color/icon badges, and one_to_many child panels.
|
|
8
|
+
//
|
|
9
|
+
// Host-owned infra that was referenced by alias (axios client, branch store)
|
|
10
|
+
// flows through <ApiProvider> from runtime-react. Host-specific runtime values —
|
|
11
|
+
// the image-url resolver and the org IANA timezone — are passed as props so the
|
|
12
|
+
// SDK stays transport- and host-agnostic.
|
|
6
13
|
import { createContext, useContext, useEffect, useRef, useState } from 'react';
|
|
14
|
+
import { useTranslation } from 'react-i18next';
|
|
7
15
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button, Input, Textarea, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, Skeleton, Badge, Popover, PopoverContent, PopoverTrigger, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from '@asteby/metacore-ui/primitives';
|
|
8
16
|
import { cn } from '@asteby/metacore-ui/lib';
|
|
9
17
|
import { Calendar } from './_primitives';
|
|
@@ -12,30 +20,103 @@ import { format, parseISO } from 'date-fns';
|
|
|
12
20
|
import { es } from 'date-fns/locale';
|
|
13
21
|
import { ExternalLink, Loader2, CalendarIcon, ChevronDown, Check, Upload, X as XIcon } from 'lucide-react';
|
|
14
22
|
import { useApi } from '../api-context';
|
|
15
|
-
import { DynamicSelectField } from '../dynamic-select-field';
|
|
23
|
+
import { DynamicSelectField, OptionLead, OptionThumb } from '../dynamic-select-field';
|
|
24
|
+
import { DynamicRelations } from '../dynamic-relations';
|
|
25
|
+
import { useOptionsResolver } from '../use-options-resolver';
|
|
16
26
|
import { getFieldRef } from '../dynamic-form-schema';
|
|
17
|
-
import { normalizeNilUuid } from '../nil-uuid';
|
|
27
|
+
import { isNilUuid, normalizeNilUuid } from '../nil-uuid';
|
|
18
28
|
import { humanizeToken } from '../dynamic-columns-helpers';
|
|
29
|
+
import { formatDateCell } from '../dynamic-columns';
|
|
30
|
+
import { ImageUrlContext, identityImageUrl } from '../image-url-context';
|
|
31
|
+
// localizedModelName resolves the (possibly addon-i18n) model name: prefer the
|
|
32
|
+
// translated titleKey, fall back to the backend-provided raw title.
|
|
33
|
+
function localizedModelName(meta, t) {
|
|
34
|
+
if (meta.titleKey && t(meta.titleKey) !== meta.titleKey)
|
|
35
|
+
return t(meta.titleKey);
|
|
36
|
+
return meta.title || '';
|
|
37
|
+
}
|
|
19
38
|
function resolvePath(obj, path) {
|
|
20
39
|
return path.split('.').reduce((acc, part) => acc?.[part], obj);
|
|
21
40
|
}
|
|
41
|
+
// objectLabel pulls a human label off a resolved relation/user object the
|
|
42
|
+
// backend serves: `{value,label}` (FK sibling), `{name,...}` (user object such
|
|
43
|
+
// as created_by), or `{title}`. Returns undefined for plain/empty objects.
|
|
44
|
+
function objectLabel(value) {
|
|
45
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
46
|
+
return undefined;
|
|
47
|
+
const label = value.label ?? value.name ?? value.title;
|
|
48
|
+
if (label != null && label !== '')
|
|
49
|
+
return String(label);
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
// pickImage reads an image-ish path off a resolved object (FK sibling, user).
|
|
53
|
+
function pickImage(value) {
|
|
54
|
+
if (!value || typeof value !== 'object')
|
|
55
|
+
return undefined;
|
|
56
|
+
const img = value.image ?? value.avatar ?? value.logo ?? value.thumbnail;
|
|
57
|
+
return typeof img === 'string' && img !== '' ? img : undefined;
|
|
58
|
+
}
|
|
59
|
+
// relationSiblingValue reads the resolved relation the table served alongside an
|
|
60
|
+
// FK column. A field `category_id` (search/dynamic_select/ref) ships a sibling
|
|
61
|
+
// `record.category = {value,label,image?}` (or a bare string/{name}); returns
|
|
62
|
+
// the raw sibling (object or string) so the caller can extract label + image.
|
|
63
|
+
function relationSiblingValue(field, record) {
|
|
64
|
+
if (!record)
|
|
65
|
+
return undefined;
|
|
66
|
+
const candidates = [];
|
|
67
|
+
const ref = getFieldRef(field);
|
|
68
|
+
if (ref)
|
|
69
|
+
candidates.push(ref);
|
|
70
|
+
if (typeof field.key === 'string' && field.key.endsWith('_id'))
|
|
71
|
+
candidates.push(field.key.slice(0, -3));
|
|
72
|
+
for (const key of candidates) {
|
|
73
|
+
const sib = record[key];
|
|
74
|
+
if (sib === undefined || sib === null)
|
|
75
|
+
continue;
|
|
76
|
+
if (typeof sib === 'string') {
|
|
77
|
+
if (sib === '' || isNilUuid(sib))
|
|
78
|
+
continue;
|
|
79
|
+
return sib;
|
|
80
|
+
}
|
|
81
|
+
if (typeof sib === 'object')
|
|
82
|
+
return sib;
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
// servedOption matches a field's served option list (enum/select with
|
|
87
|
+
// {value,label,color,icon,image}) against the current value.
|
|
88
|
+
function servedOption(field, value) {
|
|
89
|
+
if (!field.options?.length)
|
|
90
|
+
return undefined;
|
|
91
|
+
return field.options.find(o => o.value === String(value ?? ''));
|
|
92
|
+
}
|
|
93
|
+
// createdBySibling reads the resolver object the backend serves for the
|
|
94
|
+
// auto-injected `created_by` avatar column: {name, avatar, email}.
|
|
95
|
+
function createdBySibling(value, record) {
|
|
96
|
+
const obj = (value && typeof value === 'object' ? value : undefined) ?? record?.created_by;
|
|
97
|
+
if (obj && typeof obj === 'object' && (obj.name || obj.avatar || obj.email))
|
|
98
|
+
return obj;
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
// isRelationField — a field that resolves to another row (so view renders a lead
|
|
102
|
+
// + label and edit renders the searchable picker).
|
|
103
|
+
function isRelationField(field) {
|
|
104
|
+
return (field.type === 'search' ||
|
|
105
|
+
field.type === 'dynamic_select' ||
|
|
106
|
+
field.widget === 'dynamic_select' ||
|
|
107
|
+
!!getFieldRef(field) ||
|
|
108
|
+
!!field.searchEndpoint);
|
|
109
|
+
}
|
|
22
110
|
function formatDisplayValue(rawValue, field) {
|
|
23
111
|
// Unset nullable FK serialized as the nil UUID renders as empty, not zeros.
|
|
24
112
|
const value = normalizeNilUuid(rawValue);
|
|
25
113
|
if (value === null || value === undefined || value === '')
|
|
26
114
|
return '—';
|
|
115
|
+
const objLabel = objectLabel(value);
|
|
116
|
+
if (objLabel !== undefined)
|
|
117
|
+
return objLabel;
|
|
27
118
|
if (field.type === 'boolean' || typeof value === 'boolean')
|
|
28
119
|
return value ? 'Sí' : 'No';
|
|
29
|
-
if (field.type === 'date') {
|
|
30
|
-
try {
|
|
31
|
-
return new Date(value).toLocaleDateString('es-MX', {
|
|
32
|
-
day: 'numeric', month: 'long', year: 'numeric',
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
return String(value);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
120
|
if (field.type === 'select' && field.options?.length) {
|
|
40
121
|
const match = field.options.find(o => o.value === String(value));
|
|
41
122
|
// Matched option label wins (localized); humanize the raw token only
|
|
@@ -46,31 +127,42 @@ function formatDisplayValue(rawValue, field) {
|
|
|
46
127
|
}
|
|
47
128
|
const MODE_CONFIG = {
|
|
48
129
|
create: {
|
|
49
|
-
getTitle: (meta) =>
|
|
130
|
+
getTitle: (meta, t) => {
|
|
131
|
+
const name = localizedModelName(meta, t);
|
|
132
|
+
return name ? `Crear ${name}` : (meta.createTitle || meta.title || 'Nuevo registro');
|
|
133
|
+
},
|
|
50
134
|
description: 'Completa los campos para crear un nuevo registro.',
|
|
51
135
|
submitLabel: 'Crear',
|
|
52
136
|
submittingLabel: 'Creando...',
|
|
53
137
|
cancelLabel: 'Cancelar',
|
|
54
138
|
},
|
|
55
139
|
edit: {
|
|
56
|
-
getTitle: (meta) =>
|
|
140
|
+
getTitle: (meta, t) => {
|
|
141
|
+
const name = localizedModelName(meta, t);
|
|
142
|
+
return name ? `Editar ${name}` : (meta.editTitle || meta.title || 'Editar registro');
|
|
143
|
+
},
|
|
57
144
|
description: 'Modifica los campos y guarda los cambios.',
|
|
58
145
|
submitLabel: 'Guardar cambios',
|
|
59
146
|
submittingLabel: 'Guardando...',
|
|
60
147
|
cancelLabel: 'Cancelar',
|
|
61
148
|
},
|
|
62
149
|
view: {
|
|
63
|
-
getTitle: (meta) => meta.title || 'Ver registro',
|
|
150
|
+
getTitle: (meta, t) => localizedModelName(meta, t) || meta.title || 'Ver registro',
|
|
64
151
|
description: 'Información detallada del registro.',
|
|
65
152
|
submitLabel: '',
|
|
66
153
|
submittingLabel: '',
|
|
67
154
|
cancelLabel: 'Cerrar',
|
|
68
155
|
},
|
|
69
156
|
};
|
|
157
|
+
// Context threading host runtime values to nested field components (uploads,
|
|
158
|
+
// image leads, tz-aware dates) without prop-drilling through every renderer.
|
|
70
159
|
const ModelContext = createContext('');
|
|
71
|
-
|
|
160
|
+
const TimeZoneContext = createContext(undefined);
|
|
161
|
+
export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId, endpoint, onSaved, onCreate, onUpdate, defaults, schema, onDelete, onEdit, onOpenFullPage, initialRecord, getImageUrl = identityImageUrl, timeZone, }) {
|
|
72
162
|
const api = useApi();
|
|
163
|
+
const { t } = useTranslation();
|
|
73
164
|
const [modalMeta, setModalMeta] = useState(schema ? schema : null);
|
|
165
|
+
const [relations, setRelations] = useState([]);
|
|
74
166
|
const [record, setRecord] = useState(null);
|
|
75
167
|
const [formValues, setFormValues] = useState({});
|
|
76
168
|
const [loading, setLoading] = useState(false);
|
|
@@ -80,14 +172,39 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
|
|
|
80
172
|
const isView = mode === 'view';
|
|
81
173
|
const isEditable = mode === 'create' || mode === 'edit';
|
|
82
174
|
const config = MODE_CONFIG[mode];
|
|
175
|
+
// ── Fetch metadata + record when dialog opens ──────────────────────────
|
|
83
176
|
useEffect(() => {
|
|
84
177
|
if (!open)
|
|
85
178
|
return;
|
|
86
179
|
if (!isCreate && !recordId)
|
|
87
180
|
return;
|
|
88
181
|
let cancelled = false;
|
|
182
|
+
// Seed instantly from the row the table already has so view/edit render
|
|
183
|
+
// without a spinner. The list row carries the pro siblings (resolved
|
|
184
|
+
// relation, served options, image url) the table cells used.
|
|
185
|
+
const seed = !isCreate && initialRecord ? initialRecord : null;
|
|
186
|
+
if (seed)
|
|
187
|
+
setRecord(seed);
|
|
188
|
+
const seedForm = (meta, rec) => {
|
|
189
|
+
const initial = {};
|
|
190
|
+
for (const field of meta.fields ?? []) {
|
|
191
|
+
initial[field.key] = resolvePath(rec, field.key) ?? field.defaultValue ?? '';
|
|
192
|
+
}
|
|
193
|
+
setFormValues(initial);
|
|
194
|
+
};
|
|
195
|
+
// A field value is "missing" from the seed row when the list omitted that
|
|
196
|
+
// column. Sibling pro fields aren't form fields, so we only check the
|
|
197
|
+
// declared field keys.
|
|
198
|
+
const seedIsComplete = (meta, rec) => (meta.fields ?? []).every(f => {
|
|
199
|
+
if (f.hidden)
|
|
200
|
+
return true;
|
|
201
|
+
const v = resolvePath(rec, f.key);
|
|
202
|
+
return v !== undefined;
|
|
203
|
+
});
|
|
89
204
|
const load = async () => {
|
|
90
|
-
|
|
205
|
+
// Only show the skeleton when we have nothing to render yet.
|
|
206
|
+
if (!seed)
|
|
207
|
+
setLoading(true);
|
|
91
208
|
try {
|
|
92
209
|
let meta = schema ? schema : null;
|
|
93
210
|
if (!meta) {
|
|
@@ -106,8 +223,14 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
|
|
|
106
223
|
: field.defaultValue) ?? '';
|
|
107
224
|
}
|
|
108
225
|
setFormValues(initial);
|
|
226
|
+
return;
|
|
109
227
|
}
|
|
110
|
-
|
|
228
|
+
// Render immediately from the seed row.
|
|
229
|
+
if (seed && meta)
|
|
230
|
+
seedForm(meta, seed);
|
|
231
|
+
// Only hit the record endpoint if the seed is absent or missing
|
|
232
|
+
// some declared field — keeps the modal instant for full rows.
|
|
233
|
+
if (!seed || (meta && !seedIsComplete(meta, seed))) {
|
|
111
234
|
const recordEndpoint = endpoint
|
|
112
235
|
? `${endpoint}/${recordId}`
|
|
113
236
|
: `/dynamic/${model}/${recordId}`;
|
|
@@ -115,17 +238,18 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
|
|
|
115
238
|
if (cancelled)
|
|
116
239
|
return;
|
|
117
240
|
const rec = recRes.data?.data ?? recRes.data;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
241
|
+
// Merge so the fetched record fills gaps without dropping the
|
|
242
|
+
// table's pro siblings (the detail endpoint may omit them).
|
|
243
|
+
const merged = seed ? { ...seed, ...rec } : rec;
|
|
244
|
+
setRecord(merged);
|
|
245
|
+
if (meta)
|
|
246
|
+
seedForm(meta, merged);
|
|
124
247
|
}
|
|
125
248
|
}
|
|
126
249
|
catch (err) {
|
|
127
250
|
console.error('[DynamicRecordDialog] load error:', err);
|
|
128
|
-
|
|
251
|
+
if (!seed)
|
|
252
|
+
toast.error('Error al cargar los datos');
|
|
129
253
|
}
|
|
130
254
|
finally {
|
|
131
255
|
if (!cancelled)
|
|
@@ -134,14 +258,51 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
|
|
|
134
258
|
};
|
|
135
259
|
load();
|
|
136
260
|
return () => { cancelled = true; };
|
|
137
|
-
|
|
261
|
+
// initialRecord intentionally omitted: the row identity is captured per open
|
|
262
|
+
// via recordId; re-seeding mid-open would clobber edits.
|
|
263
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
264
|
+
}, [open, recordId, model, endpoint, isCreate, schema]);
|
|
265
|
+
// Reset when closed
|
|
138
266
|
useEffect(() => {
|
|
139
267
|
if (!open) {
|
|
140
268
|
setModalMeta(null);
|
|
269
|
+
setRelations([]);
|
|
141
270
|
setRecord(null);
|
|
142
271
|
setFormValues({});
|
|
143
272
|
}
|
|
144
273
|
}, [open]);
|
|
274
|
+
// Fetch the model's declared one_to_many/many_to_many edges so view AND edit
|
|
275
|
+
// show child records (e.g. a sales order's line items) below the scalar
|
|
276
|
+
// fields. The modal form is driven by MODAL metadata (fields); relations live
|
|
277
|
+
// on TABLE metadata, hence the separate fetch. Skipped on create (no parent
|
|
278
|
+
// record yet). View renders them read-only; edit lets the user add/edit/delete.
|
|
279
|
+
useEffect(() => {
|
|
280
|
+
if (!open || mode === 'create' || !recordId) {
|
|
281
|
+
setRelations([]);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
let cancelled = false;
|
|
285
|
+
api.get(`/metadata/table/${model}`)
|
|
286
|
+
.then(res => {
|
|
287
|
+
if (cancelled)
|
|
288
|
+
return;
|
|
289
|
+
const meta = res.data?.data ?? res.data;
|
|
290
|
+
const rels = Array.isArray(meta?.relations) ? meta.relations : [];
|
|
291
|
+
// Localize each panel header: the backend serves `label` as an
|
|
292
|
+
// i18n key (addon bundle, loaded live) and the SDK renders it verbatim.
|
|
293
|
+
setRelations(rels.map(rel => ({
|
|
294
|
+
...rel,
|
|
295
|
+
label: rel.label && t(rel.label) !== rel.label
|
|
296
|
+
? t(rel.label)
|
|
297
|
+
: rel.label || rel.name,
|
|
298
|
+
})));
|
|
299
|
+
})
|
|
300
|
+
.catch(() => {
|
|
301
|
+
if (!cancelled)
|
|
302
|
+
setRelations([]);
|
|
303
|
+
});
|
|
304
|
+
return () => { cancelled = true; };
|
|
305
|
+
}, [open, mode, model, recordId, api, t]);
|
|
145
306
|
const handleSubmit = async (e) => {
|
|
146
307
|
e?.preventDefault();
|
|
147
308
|
if (!modalMeta)
|
|
@@ -157,16 +318,16 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
|
|
|
157
318
|
setSaving(true);
|
|
158
319
|
try {
|
|
159
320
|
if (isCreate && onCreate) {
|
|
160
|
-
await onCreate(formValues);
|
|
161
|
-
toast.success('Registro creado correctamente');
|
|
162
|
-
onSaved?.();
|
|
321
|
+
const created = await onCreate(formValues);
|
|
322
|
+
toast.success(modalMeta?.messages?.created || 'Registro creado correctamente');
|
|
323
|
+
onSaved?.(created ?? undefined);
|
|
163
324
|
onOpenChange(false);
|
|
164
325
|
return;
|
|
165
326
|
}
|
|
166
327
|
if (!isCreate && recordId && onUpdate) {
|
|
167
|
-
await onUpdate(String(recordId), formValues);
|
|
168
|
-
toast.success('Guardado correctamente');
|
|
169
|
-
onSaved?.();
|
|
328
|
+
const updated = await onUpdate(String(recordId), formValues);
|
|
329
|
+
toast.success(modalMeta?.messages?.updated || 'Guardado correctamente');
|
|
330
|
+
onSaved?.(updated ?? undefined);
|
|
170
331
|
onOpenChange(false);
|
|
171
332
|
return;
|
|
172
333
|
}
|
|
@@ -182,8 +343,13 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
|
|
|
182
343
|
res = await api.put(updateEndpoint, formValues);
|
|
183
344
|
}
|
|
184
345
|
if (res.data?.success !== false) {
|
|
185
|
-
|
|
186
|
-
|
|
346
|
+
// Prefer the addon's localized message (modal metadata), then a
|
|
347
|
+
// localized fallback. NOT res.data.message — the dynamic CRUD
|
|
348
|
+
// endpoint returns a raw English string that would leak into the toast.
|
|
349
|
+
toast.success(modalMeta?.messages?.[isCreate ? 'created' : 'updated']
|
|
350
|
+
|| (isCreate ? 'Registro creado correctamente' : 'Guardado correctamente'));
|
|
351
|
+
// Hand the persisted record back so callers can auto-select it.
|
|
352
|
+
onSaved?.(res.data?.data ?? res.data ?? undefined);
|
|
187
353
|
onOpenChange(false);
|
|
188
354
|
}
|
|
189
355
|
else {
|
|
@@ -213,7 +379,7 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
|
|
|
213
379
|
setDeleting(false);
|
|
214
380
|
}
|
|
215
381
|
};
|
|
216
|
-
const title = modalMeta ? config.getTitle(modalMeta) : '';
|
|
382
|
+
const title = modalMeta ? config.getTitle(modalMeta, t) : '';
|
|
217
383
|
const visibleFields = modalMeta?.fields?.filter(f => {
|
|
218
384
|
if (f.hidden)
|
|
219
385
|
return false;
|
|
@@ -221,10 +387,10 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
|
|
|
221
387
|
return false;
|
|
222
388
|
return true;
|
|
223
389
|
}) ?? [];
|
|
224
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-2xl max-h-[90vh] flex flex-col p-0 gap-0 overflow-hidden", children: [_jsxs(DialogHeader, { className: "p-6 pb-4 border-b shrink-0", children: [_jsx(DialogTitle, { children: title }), _jsx(DialogDescription, { children: config.description })] }), _jsx("div", { className: "flex-1 overflow-y-auto p-6", children: loading ? (_jsx(LoadingSkeleton, {})) : modalMeta ? (_jsx(ModelContext.Provider, { value: model, children: _jsxs("form", { id: "dynamic-record-form", onSubmit: handleSubmit, className: "grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-4", children: [visibleFields.map(field => {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
390
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-2xl max-h-[90vh] flex flex-col p-0 gap-0 overflow-hidden", children: [_jsxs(DialogHeader, { className: "p-6 pb-4 border-b shrink-0", children: [_jsx(DialogTitle, { children: title }), _jsx(DialogDescription, { children: config.description })] }), _jsx("div", { className: "flex-1 overflow-y-auto p-6", children: loading ? (_jsx(LoadingSkeleton, {})) : modalMeta ? (_jsx(ModelContext.Provider, { value: model, children: _jsx(ImageUrlContext.Provider, { value: getImageUrl, children: _jsxs(TimeZoneContext.Provider, { value: timeZone, children: [_jsxs("form", { id: "dynamic-record-form", onSubmit: handleSubmit, className: "grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-4", children: [visibleFields.map(field => {
|
|
391
|
+
const isFullWidth = field.type === 'textarea';
|
|
392
|
+
return (_jsx("div", { className: isFullWidth ? 'sm:col-span-2' : '', children: _jsx(FieldRow, { field: field, record: record, value: formValues[field.key] ?? '', mode: mode, onChange: val => setFormValues((prev) => ({ ...prev, [field.key]: val })) }) }, field.key));
|
|
393
|
+
}), record?.external_url && (_jsx("div", { className: "sm:col-span-2", children: _jsxs("a", { href: record.external_url, target: "_blank", rel: "noreferrer", className: "inline-flex items-center gap-1.5 text-sm text-primary hover:underline mt-1", children: [_jsx(ExternalLink, { className: "h-3.5 w-3.5" }), "Ver en ", record.external_provider ?? 'proveedor externo'] }) }))] }), !isCreate && record && relations.length > 0 && (_jsx("div", { className: "mt-6", children: _jsx(DynamicRelations, { record: record, relations: relations, canCreate: mode === 'edit', canEdit: mode === 'edit', canDelete: mode === 'edit' }) }))] }) }) })) : null }), _jsxs(DialogFooter, { className: "p-4 border-t shrink-0 sm:justify-between", children: [isView && onOpenFullPage ? (_jsxs(Button, { variant: "ghost", size: "sm", className: "text-muted-foreground", onClick: () => { onOpenChange(false); onOpenFullPage(); }, children: [_jsx(ExternalLink, { className: "mr-1.5 h-3.5 w-3.5" }), "Ver p\u00E1gina completa"] })) : _jsx("span", {}), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: saving || deleting, children: config.cancelLabel }), isView && onDelete && (_jsxs(Button, { variant: "destructive", onClick: handleDelete, disabled: deleting || loading, children: [deleting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), deleting ? 'Eliminando...' : 'Eliminar'] })), isView && onEdit && (_jsx(Button, { onClick: onEdit, disabled: deleting || loading, children: "Editar" })), isEditable && (_jsxs(Button, { type: "submit", form: "dynamic-record-form", disabled: saving || loading, children: [saving && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), saving ? config.submittingLabel : config.submitLabel] }))] })] })] }) }));
|
|
228
394
|
}
|
|
229
395
|
function LoadingSkeleton() {
|
|
230
396
|
return (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-4", children: Array.from({ length: 6 }).map((_, i) => (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Skeleton, { className: "h-3.5 w-24" }), _jsx(Skeleton, { className: "h-9 w-full" })] }, i))) }));
|
|
@@ -233,12 +399,75 @@ function FieldRow({ field, record, value, mode, onChange }) {
|
|
|
233
399
|
const isReadonly = field.readonly || mode === 'view';
|
|
234
400
|
return (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsxs(Label, { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: [field.label, field.required && mode !== 'view' && (_jsx("span", { className: "text-destructive ml-0.5", children: "*" }))] }), isReadonly ? (_jsx(ViewValue, { field: field, value: value, record: record })) : (_jsx(EditField, { field: field, value: value, onChange: onChange }))] }));
|
|
235
401
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
402
|
+
// RelationViewValue — read-only FK lead. Resolves the relation's label + image
|
|
403
|
+
// from (1) the sibling object the table served, then (2) the canonical options
|
|
404
|
+
// endpoint, and renders an OptionLead (thumbnail / icon / color dot) + label.
|
|
405
|
+
function RelationViewValue({ field, value, record }) {
|
|
406
|
+
const getImageUrl = useContext(ImageUrlContext);
|
|
407
|
+
const sib = relationSiblingValue(field, record);
|
|
408
|
+
const sibLabel = typeof sib === 'string' ? sib : objectLabel(sib);
|
|
409
|
+
const sibImage = pickImage(sib);
|
|
410
|
+
// The raw FK id, tolerating an inline resolved object as the value itself.
|
|
411
|
+
const rawVal = value && typeof value === 'object' ? (value.value ?? value.id) : value;
|
|
412
|
+
const inlineLabel = sibLabel ?? objectLabel(value);
|
|
413
|
+
const inlineImage = sibImage ?? pickImage(value);
|
|
414
|
+
const fieldRef = getFieldRef(field);
|
|
415
|
+
// Only resolve over the network when we still lack both label and image and
|
|
416
|
+
// there is something to look up.
|
|
417
|
+
const needResolve = !inlineLabel && !inlineImage && !!(fieldRef || field.searchEndpoint) && rawVal != null && rawVal !== '';
|
|
418
|
+
const { options } = useOptionsResolver({
|
|
419
|
+
modelKey: '',
|
|
420
|
+
fieldKey: 'id',
|
|
421
|
+
ref: fieldRef,
|
|
422
|
+
endpoint: fieldRef ? undefined : field.searchEndpoint,
|
|
423
|
+
query: '',
|
|
424
|
+
limit: 50,
|
|
425
|
+
enabled: needResolve,
|
|
426
|
+
});
|
|
427
|
+
const resolved = options.find(o => String(o.id) === String(rawVal));
|
|
428
|
+
const label = inlineLabel ??
|
|
429
|
+
resolved?.label ??
|
|
430
|
+
(rawVal != null && rawVal !== '' && !isNilUuid(rawVal) ? String(rawVal) : undefined);
|
|
431
|
+
const image = inlineImage ?? resolved?.image ?? undefined;
|
|
432
|
+
if (!label && !image) {
|
|
433
|
+
return _jsx("p", { className: "text-sm py-1 text-muted-foreground", children: "\u2014" });
|
|
434
|
+
}
|
|
435
|
+
const lead = {
|
|
436
|
+
image: image ? getImageUrl(image) : null,
|
|
437
|
+
color: resolved?.color ?? null,
|
|
438
|
+
icon: resolved?.icon ?? null,
|
|
439
|
+
};
|
|
440
|
+
return (_jsxs("div", { className: "flex items-center gap-2 py-1", children: [_jsx(OptionLead, { option: lead, size: 24 }), _jsx("span", { className: "text-sm", children: label ?? '—' })] }));
|
|
441
|
+
}
|
|
442
|
+
export function ViewValue({ field, value: rawValue, record, getImageUrl: getImageUrlProp, timeZone: timeZoneProp, }) {
|
|
443
|
+
const ctxImageUrl = useContext(ImageUrlContext);
|
|
444
|
+
const ctxTimeZone = useContext(TimeZoneContext);
|
|
445
|
+
const getImageUrl = getImageUrlProp ?? ctxImageUrl;
|
|
446
|
+
const timeZone = timeZoneProp ?? ctxTimeZone;
|
|
447
|
+
// created_by / avatar resolver sibling → name (+ avatar) instead of "—".
|
|
448
|
+
if (field.type === 'avatar' || field.key === 'created_by' || field.key === 'created_by_id') {
|
|
449
|
+
const user = createdBySibling(rawValue, record);
|
|
450
|
+
if (user) {
|
|
451
|
+
return (_jsxs("div", { className: "flex items-center gap-2 py-1", children: [user.avatar ? (_jsx("img", { src: getImageUrl(String(user.avatar)), alt: user.name ?? '', className: "h-6 w-6 rounded-full object-cover" })) : null, _jsx("span", { className: "text-sm", children: user.name ?? user.email ?? '—' })] }));
|
|
452
|
+
}
|
|
453
|
+
return _jsx("p", { className: "text-sm py-1 text-muted-foreground", children: "\u2014" });
|
|
454
|
+
}
|
|
455
|
+
// Nil/zero UUID (unset nullable FK serialized as all-zeros) → empty marker.
|
|
456
|
+
if (isNilUuid(rawValue)) {
|
|
457
|
+
return _jsx("p", { className: "text-sm py-1 text-muted-foreground", children: "\u2014" });
|
|
458
|
+
}
|
|
239
459
|
const value = normalizeNilUuid(rawValue);
|
|
240
|
-
|
|
241
|
-
|
|
460
|
+
// Relation (search / dynamic_select / ref / any *_id) → resolved thumbnail +
|
|
461
|
+
// label. The *_id catch-all covers plain-typed FK columns not tagged as a
|
|
462
|
+
// relation field.
|
|
463
|
+
if (isRelationField(field) || (typeof field.key === 'string' && field.key.endsWith('_id'))) {
|
|
464
|
+
return _jsx(RelationViewValue, { field: field, value: value, record: record });
|
|
465
|
+
}
|
|
466
|
+
// The value is itself a resolved object the backend served inline — render
|
|
467
|
+
// its label/name, never the raw JSON.
|
|
468
|
+
const inlineLabel = objectLabel(value);
|
|
469
|
+
if (inlineLabel !== undefined) {
|
|
470
|
+
return _jsx("p", { className: "text-sm py-1", children: inlineLabel });
|
|
242
471
|
}
|
|
243
472
|
if (field.type === 'boolean' || typeof value === 'boolean') {
|
|
244
473
|
return (_jsxs("div", { className: "flex items-center gap-2 py-1", children: [_jsx(Switch, { checked: !!value, disabled: true }), _jsx("span", { className: "text-sm text-muted-foreground", children: value ? 'Sí' : 'No' })] }));
|
|
@@ -247,16 +476,31 @@ function ViewValue({ field, value: rawValue }) {
|
|
|
247
476
|
return value ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "h-5 w-5 rounded-full border shadow-sm", style: { backgroundColor: value } }), _jsx("span", { className: "text-sm", children: value })] })) : (_jsx("p", { className: "text-sm py-1 text-muted-foreground", children: "-" }));
|
|
248
477
|
}
|
|
249
478
|
if (field.type === 'image') {
|
|
250
|
-
return value ? (_jsx("img", { src: value, alt: field.label, className: "h-16 w-16 rounded-lg object-cover border" })) : (_jsx("p", { className: "text-sm py-1 text-muted-foreground", children: "Sin imagen" }));
|
|
479
|
+
return value ? (_jsx("img", { src: getImageUrl(String(value)), alt: field.label, className: "h-16 w-16 rounded-lg object-cover border" })) : (_jsx("p", { className: "text-sm py-1 text-muted-foreground", children: "Sin imagen" }));
|
|
251
480
|
}
|
|
252
481
|
if (field.type === 'url' && value) {
|
|
253
482
|
return (_jsx("a", { href: value, target: "_blank", rel: "noreferrer", className: "text-sm text-primary hover:underline truncate", children: value }));
|
|
254
483
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
484
|
+
// Date/datetime/timestamp → tz-aware format. `date` pins to UTC (calendar
|
|
485
|
+
// day); instants render in the org timezone with a full-precision tooltip.
|
|
486
|
+
if (field.type === 'date' || field.type === 'datetime' || field.type === 'timestamp') {
|
|
487
|
+
const renderAs = field.type === 'date' ? 'date' : field.type;
|
|
488
|
+
const formatted = formatDateCell(value, renderAs, es, timeZone);
|
|
489
|
+
if (formatted) {
|
|
490
|
+
return (_jsx("p", { className: "text-sm py-1", title: formatted.title, children: formatted.display }));
|
|
259
491
|
}
|
|
492
|
+
return _jsx("p", { className: "text-sm py-1 text-muted-foreground", children: "\u2014" });
|
|
493
|
+
}
|
|
494
|
+
// Enum/option field with served options → colored/iconed badge using the
|
|
495
|
+
// served label (e.g. "Almacenable" instead of "storable").
|
|
496
|
+
const opt = servedOption(field, value);
|
|
497
|
+
if (opt) {
|
|
498
|
+
const lead = {
|
|
499
|
+
image: opt.image ? getImageUrl(opt.image) : null,
|
|
500
|
+
color: opt.color ?? null,
|
|
501
|
+
icon: opt.icon ?? null,
|
|
502
|
+
};
|
|
503
|
+
return (_jsxs(Badge, { variant: "secondary", className: "w-fit flex items-center gap-1", style: opt.color && !opt.icon ? { backgroundColor: opt.color, color: '#fff', borderColor: 'transparent' } : undefined, children: [_jsx(OptionLead, { option: lead, size: 16 }), opt.label] }));
|
|
260
504
|
}
|
|
261
505
|
const display = formatDisplayValue(value, field);
|
|
262
506
|
if (field.type === 'textarea') {
|
|
@@ -272,17 +516,14 @@ function EditField({ field, value, onChange }) {
|
|
|
272
516
|
return (_jsx(Textarea, { value: value ?? '', onChange: (e) => onChange(e.target.value), placeholder: field.placeholder, rows: 4 }));
|
|
273
517
|
}
|
|
274
518
|
// Media widgets: the kernel may serve an explicit `widget: 'upload'` (or the
|
|
275
|
-
// `image` type) for a file/photo column.
|
|
276
|
-
// that POSTs to the host upload endpoint — same control as the Brand logo.
|
|
519
|
+
// `image` type) for a file/photo column.
|
|
277
520
|
if (field.type === 'image' || field.widget === 'upload') {
|
|
278
521
|
return _jsx(ImageUploadField, { field: field, value: value, onChange: onChange });
|
|
279
522
|
}
|
|
280
523
|
// FK columns: a `ref` (kernel-derived belongs_to target) or an explicit
|
|
281
|
-
// `widget: 'dynamic_select'` renders the async searchable picker
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
// `options` are handled by the enum <Select> branch below; a ref column does
|
|
285
|
-
// not ship inline options, so this never shadows a static enum.
|
|
524
|
+
// `widget: 'dynamic_select'` renders the SDK's async searchable picker — with
|
|
525
|
+
// option thumbnails and the inline-create "+" — against /api/options/<ref>.
|
|
526
|
+
// Static inline `options` are handled by the enum <Select> branch below.
|
|
286
527
|
if ((getFieldRef(field) || field.widget === 'dynamic_select') && !field.options?.length) {
|
|
287
528
|
return _jsx(DynamicSelectField, { field: field, value: value, onChange: onChange });
|
|
288
529
|
}
|
|
@@ -315,6 +556,7 @@ function EditField({ field, value, onChange }) {
|
|
|
315
556
|
function ImageUploadField({ field: _field, value, onChange }) {
|
|
316
557
|
const api = useApi();
|
|
317
558
|
const model = useContext(ModelContext);
|
|
559
|
+
const getImageUrl = useContext(ImageUrlContext);
|
|
318
560
|
const [uploading, setUploading] = useState(false);
|
|
319
561
|
const inputRef = useRef(null);
|
|
320
562
|
async function handleFile(e) {
|
|
@@ -340,7 +582,7 @@ function ImageUploadField({ field: _field, value, onChange }) {
|
|
|
340
582
|
inputRef.current.value = '';
|
|
341
583
|
}
|
|
342
584
|
}
|
|
343
|
-
return (_jsxs("div", { className: "flex items-center gap-3", children: [value ? (_jsxs("div", { className: "relative", children: [_jsx("img", { src: value, alt: "", className: "h-16 w-16 rounded-lg object-cover border" }), _jsx("button", { type: "button", onClick: () => onChange(''), className: "absolute -top-1.5 -right-1.5 size-5 bg-destructive text-white rounded-full flex items-center justify-center hover:bg-destructive/90", children: _jsx(XIcon, { className: "size-3" }) })] })) : (_jsx("button", { type: "button", onClick: () => inputRef.current?.click(), disabled: uploading, className: "h-16 w-16 rounded-lg border-2 border-dashed border-muted-foreground/30 flex flex-col items-center justify-center gap-1 hover:border-primary/50 hover:bg-muted/50 transition-colors disabled:opacity-50", children: uploading ? (_jsx(Loader2, { className: "size-4 animate-spin text-muted-foreground" })) : (_jsx(Upload, { className: "size-4 text-muted-foreground" })) })), _jsx("input", { ref: inputRef, type: "file", accept: "image/*", onChange: handleFile, className: "hidden" }), !value && _jsx("span", { className: "text-xs text-muted-foreground", children: "PNG, JPG, WebP" })] }));
|
|
585
|
+
return (_jsxs("div", { className: "flex items-center gap-3", children: [value ? (_jsxs("div", { className: "relative", children: [_jsx("img", { src: getImageUrl(String(value)), alt: "", className: "h-16 w-16 rounded-lg object-cover border" }), _jsx("button", { type: "button", onClick: () => onChange(''), className: "absolute -top-1.5 -right-1.5 size-5 bg-destructive text-white rounded-full flex items-center justify-center hover:bg-destructive/90", children: _jsx(XIcon, { className: "size-3" }) })] })) : (_jsx("button", { type: "button", onClick: () => inputRef.current?.click(), disabled: uploading, className: "h-16 w-16 rounded-lg border-2 border-dashed border-muted-foreground/30 flex flex-col items-center justify-center gap-1 hover:border-primary/50 hover:bg-muted/50 transition-colors disabled:opacity-50", children: uploading ? (_jsx(Loader2, { className: "size-4 animate-spin text-muted-foreground" })) : (_jsx(Upload, { className: "size-4 text-muted-foreground" })) })), _jsx("input", { ref: inputRef, type: "file", accept: "image/*", onChange: handleFile, className: "hidden" }), !value && _jsx("span", { className: "text-xs text-muted-foreground", children: "PNG, JPG, WebP" })] }));
|
|
344
586
|
}
|
|
345
587
|
function extractArray(res) {
|
|
346
588
|
const d = res.data;
|
|
@@ -351,31 +593,6 @@ function extractArray(res) {
|
|
|
351
593
|
return [];
|
|
352
594
|
}
|
|
353
595
|
const searchCache = new Map();
|
|
354
|
-
function SearchViewValue({ field, value }) {
|
|
355
|
-
const api = useApi();
|
|
356
|
-
const [label, setLabel] = useState(String(value));
|
|
357
|
-
useEffect(() => {
|
|
358
|
-
if (!field.searchEndpoint || !value)
|
|
359
|
-
return;
|
|
360
|
-
const cacheKey = field.searchEndpoint;
|
|
361
|
-
const cached = searchCache.get(cacheKey);
|
|
362
|
-
if (cached) {
|
|
363
|
-
const match = cached.find((item) => item.value === value || item.id === value);
|
|
364
|
-
if (match) {
|
|
365
|
-
setLabel(match.label || match.name || String(value));
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
api.get(field.searchEndpoint, { params: { search: '', limit: 50 } }).then(res => {
|
|
370
|
-
const items = extractArray(res);
|
|
371
|
-
searchCache.set(cacheKey, items);
|
|
372
|
-
const match = items.find((item) => item.value === value || item.id === value);
|
|
373
|
-
if (match)
|
|
374
|
-
setLabel(match.label || match.name || String(value));
|
|
375
|
-
}).catch(() => { });
|
|
376
|
-
}, [value, field.searchEndpoint]);
|
|
377
|
-
return _jsx("p", { className: "text-sm py-1", children: label });
|
|
378
|
-
}
|
|
379
596
|
function SearchField({ field, value, onChange }) {
|
|
380
597
|
const api = useApi();
|
|
381
598
|
const [open, setOpen] = useState(false);
|
|
@@ -401,7 +618,7 @@ function SearchField({ field, value, onChange }) {
|
|
|
401
618
|
if (match)
|
|
402
619
|
setSelectedLabel(match.label || match.name || '');
|
|
403
620
|
}).catch(() => { });
|
|
404
|
-
}, [value, field.searchEndpoint]);
|
|
621
|
+
}, [value, field.searchEndpoint, api]);
|
|
405
622
|
useEffect(() => {
|
|
406
623
|
if (!open || !field.searchEndpoint)
|
|
407
624
|
return;
|
|
@@ -423,7 +640,7 @@ function SearchField({ field, value, onChange }) {
|
|
|
423
640
|
.finally(() => setLoading(false));
|
|
424
641
|
}, query ? 250 : 0);
|
|
425
642
|
return () => clearTimeout(timer);
|
|
426
|
-
}, [query, open, field.searchEndpoint]);
|
|
643
|
+
}, [query, open, field.searchEndpoint, api]);
|
|
427
644
|
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", role: "combobox", className: cn("w-full justify-between font-normal h-9", !value && "text-muted-foreground"), children: [_jsx("span", { className: "truncate", children: selectedLabel || `Seleccionar ${field.label?.toLowerCase() || ''}...` }), _jsx(ChevronDown, { className: "ml-auto h-4 w-4 shrink-0 opacity-50" })] }) }), _jsx(PopoverContent, { className: "w-[--radix-popover-trigger-width] p-0", align: "start", side: "bottom", sideOffset: 4, children: _jsxs(Command, { shouldFilter: false, children: [_jsx(CommandInput, { placeholder: `Buscar ${field.label?.toLowerCase() || ''}...`, value: query, onValueChange: setQuery }), _jsx(CommandList, { className: "max-h-[200px]", children: loading ? (_jsxs("div", { className: "py-6 text-center text-sm", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin mx-auto mb-1 text-muted-foreground" }), _jsx("span", { className: "text-muted-foreground text-xs", children: "Buscando..." })] })) : results.length === 0 ? (_jsx(CommandEmpty, { children: "Sin resultados." })) : (_jsx(CommandGroup, { children: results.map((item) => {
|
|
428
645
|
const itemValue = item.value ?? item.id;
|
|
429
646
|
const itemLabel = item.label ?? item.name ?? '';
|
|
@@ -433,6 +650,6 @@ function SearchField({ field, value, onChange }) {
|
|
|
433
650
|
setSelectedLabel(itemLabel);
|
|
434
651
|
setOpen(false);
|
|
435
652
|
setQuery('');
|
|
436
|
-
}, children: [isSelected && _jsx(Check, { className: "mr-2 h-3.5 w-3.5 shrink-0 text-primary" }), item.image && (_jsx(
|
|
653
|
+
}, children: [isSelected && _jsx(Check, { className: "mr-2 h-3.5 w-3.5 shrink-0 text-primary" }), item.image && (_jsx(OptionThumb, { image: item.image, size: 20 })), _jsxs("div", { className: "flex flex-col min-w-0 ml-2", children: [_jsx("span", { className: "truncate", children: itemLabel }), item.description && (_jsx("span", { className: "text-[11px] text-muted-foreground truncate", children: item.description }))] })] }, itemValue));
|
|
437
654
|
}) })) })] }) })] }));
|
|
438
655
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-form.d.ts","sourceRoot":"","sources":["../src/dynamic-form.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,EACH,cAAc,EACd,aAAa,EAGhB,MAAM,uBAAuB,CAAA;AAO9B,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,CAAA;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAE5C,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACnC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,wBAAgB,WAAW,CAAC,EACxB,MAAM,EACN,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,WAAuB,EACvB,WAAwB,EACxB,QAAgB,GACnB,EAAE,gBAAgB,+
|
|
1
|
+
{"version":3,"file":"dynamic-form.d.ts","sourceRoot":"","sources":["../src/dynamic-form.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,EACH,cAAc,EACd,aAAa,EAGhB,MAAM,uBAAuB,CAAA;AAO9B,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,CAAA;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAE5C,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACnC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,wBAAgB,WAAW,CAAC,EACxB,MAAM,EACN,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,WAAuB,EACvB,WAAwB,EACxB,QAAgB,GACnB,EAAE,gBAAgB,+BAmGlB"}
|