@asteby/metacore-runtime-react 4.0.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.
Files changed (81) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +201 -0
  3. package/README.md +59 -0
  4. package/dist/action-modal-dispatcher.d.ts +4 -0
  5. package/dist/action-modal-dispatcher.d.ts.map +1 -0
  6. package/dist/action-modal-dispatcher.js +123 -0
  7. package/dist/addon-loader.d.ts +27 -0
  8. package/dist/addon-loader.d.ts.map +1 -0
  9. package/dist/addon-loader.js +73 -0
  10. package/dist/api-context.d.ts +40 -0
  11. package/dist/api-context.d.ts.map +1 -0
  12. package/dist/api-context.js +25 -0
  13. package/dist/capability-gate.d.ts +29 -0
  14. package/dist/capability-gate.d.ts.map +1 -0
  15. package/dist/capability-gate.js +43 -0
  16. package/dist/dialogs/_primitives.d.ts +29 -0
  17. package/dist/dialogs/_primitives.d.ts.map +1 -0
  18. package/dist/dialogs/_primitives.js +35 -0
  19. package/dist/dialogs/dynamic-record.d.ts +11 -0
  20. package/dist/dialogs/dynamic-record.d.ts.map +1 -0
  21. package/dist/dialogs/dynamic-record.js +377 -0
  22. package/dist/dialogs/export.d.ts +12 -0
  23. package/dist/dialogs/export.d.ts.map +1 -0
  24. package/dist/dialogs/export.js +146 -0
  25. package/dist/dialogs/import.d.ts +11 -0
  26. package/dist/dialogs/import.d.ts.map +1 -0
  27. package/dist/dialogs/import.js +128 -0
  28. package/dist/dynamic-columns-shim.d.ts +25 -0
  29. package/dist/dynamic-columns-shim.d.ts.map +1 -0
  30. package/dist/dynamic-columns-shim.js +1 -0
  31. package/dist/dynamic-form.d.ts +12 -0
  32. package/dist/dynamic-form.d.ts.map +1 -0
  33. package/dist/dynamic-form.js +51 -0
  34. package/dist/dynamic-icon.d.ts +6 -0
  35. package/dist/dynamic-icon.d.ts.map +1 -0
  36. package/dist/dynamic-icon.js +11 -0
  37. package/dist/dynamic-table.d.ts +22 -0
  38. package/dist/dynamic-table.d.ts.map +1 -0
  39. package/dist/dynamic-table.js +516 -0
  40. package/dist/i18n-provider.d.ts +16 -0
  41. package/dist/i18n-provider.d.ts.map +1 -0
  42. package/dist/i18n-provider.js +16 -0
  43. package/dist/index.d.ts +18 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +21 -0
  46. package/dist/metadata-cache.d.ts +42 -0
  47. package/dist/metadata-cache.d.ts.map +1 -0
  48. package/dist/metadata-cache.js +71 -0
  49. package/dist/navigation-builder.d.ts +34 -0
  50. package/dist/navigation-builder.d.ts.map +1 -0
  51. package/dist/navigation-builder.js +45 -0
  52. package/dist/options-context.d.ts +8 -0
  53. package/dist/options-context.d.ts.map +1 -0
  54. package/dist/options-context.js +5 -0
  55. package/dist/slot.d.ts +32 -0
  56. package/dist/slot.d.ts.map +1 -0
  57. package/dist/slot.js +45 -0
  58. package/dist/types.d.ts +114 -0
  59. package/dist/types.d.ts.map +1 -0
  60. package/dist/types.js +1 -0
  61. package/package.json +67 -0
  62. package/src/action-modal-dispatcher.tsx +275 -0
  63. package/src/addon-loader.tsx +111 -0
  64. package/src/api-context.tsx +55 -0
  65. package/src/capability-gate.tsx +69 -0
  66. package/src/dialogs/_primitives.tsx +114 -0
  67. package/src/dialogs/dynamic-record.tsx +770 -0
  68. package/src/dialogs/export.tsx +339 -0
  69. package/src/dialogs/import.tsx +404 -0
  70. package/src/dynamic-columns-shim.ts +36 -0
  71. package/src/dynamic-form.tsx +108 -0
  72. package/src/dynamic-icon.tsx +15 -0
  73. package/src/dynamic-table.tsx +766 -0
  74. package/src/i18n-provider.tsx +33 -0
  75. package/src/index.ts +30 -0
  76. package/src/metadata-cache.ts +103 -0
  77. package/src/navigation-builder.tsx +77 -0
  78. package/src/options-context.tsx +11 -0
  79. package/src/slot.tsx +77 -0
  80. package/src/types.ts +112 -0
  81. package/tsconfig.json +16 -0
@@ -0,0 +1,516 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ // DynamicTable — metadata-driven CRUD table used by every metacore host.
3
+ // Ported from the ops starter but with the host-specific aliases swapped
4
+ // for metacore packages + context-injected peer deps:
5
+ // * `@/lib/api` → <ApiProvider> (see api-context.tsx)
6
+ // * `@/stores/branch-store` → <BranchProvider> (optional)
7
+ // * `@/stores/metadata-cache` → internal ./metadata-cache zustand store
8
+ // * `@/components/ui/*` → @asteby/metacore-ui/primitives
9
+ // * `@/components/data-table/*` → @asteby/metacore-ui/data-table
10
+ // * `@/components/dynamic/{record,export,import}-dialog` → ./dialogs/*
11
+ // * `@/components/dynamic/dynamic-columns` → host-injected via the
12
+ // `getDynamicColumns` prop (hosts retain ownership because the rendered
13
+ // column cells are tightly coupled to their design system).
14
+ import { useEffect, useState, useMemo, useCallback, useRef } from 'react';
15
+ import { useNavigate } from '@tanstack/react-router';
16
+ import { useTranslation } from 'react-i18next';
17
+ import { format } from 'date-fns';
18
+ import { flexRender, getCoreRowModel, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, } from '@tanstack/react-table';
19
+ import { cn } from '@asteby/metacore-ui/lib';
20
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Button, Skeleton, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@asteby/metacore-ui/primitives';
21
+ import { DataTablePagination, DataTableToolbar, DataTableBulkActions, } from '@asteby/metacore-ui/data-table';
22
+ import { Inbox, Download, Upload, Trash2 } from 'lucide-react';
23
+ import { toast } from 'sonner';
24
+ import { Progress } from './dialogs/_primitives';
25
+ import { useMetadataCache } from './metadata-cache';
26
+ import { useApi, useCurrentBranch } from './api-context';
27
+ import { OptionsContext } from './options-context';
28
+ import { ActionModalDispatcher } from './action-modal-dispatcher';
29
+ import { DynamicRecordDialog } from './dialogs/dynamic-record';
30
+ import { ExportDialog } from './dialogs/export';
31
+ import { ImportDialog } from './dialogs/import';
32
+ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColumns = [], onAction, refreshTrigger, defaultFilters, extraColumns = [], getDynamicColumns = defaultGetDynamicColumns, }) {
33
+ const { t, i18n } = useTranslation();
34
+ const api = useApi();
35
+ const currentBranch = useCurrentBranch();
36
+ const navigate = useNavigate();
37
+ const prevBranchId = useRef(currentBranch?.id);
38
+ const { getMetadata, setMetadata: cacheMetadata } = useMetadataCache();
39
+ const cachedMeta = getMetadata(model);
40
+ const [metadata, setMetadata] = useState(cachedMeta || null);
41
+ const [data, setData] = useState([]);
42
+ const [loading, setLoading] = useState(!cachedMeta);
43
+ const [loadingData, setLoadingData] = useState(true);
44
+ const [optionsMap, setOptionsMap] = useState(new Map());
45
+ const [recordDialog, setRecordDialog] = useState({ open: false, mode: 'view', recordId: null });
46
+ const [rowToDelete, setRowToDelete] = useState(null);
47
+ const [isDeleting, setIsDeleting] = useState(false);
48
+ const [exportOpen, setExportOpen] = useState(false);
49
+ const [importOpen, setImportOpen] = useState(false);
50
+ const [actionModal, setActionModal] = useState({ open: false, action: null, record: null });
51
+ const [showBulkDeleteConfirm, setShowBulkDeleteConfirm] = useState(false);
52
+ const [isBulkDeleting, setIsBulkDeleting] = useState(false);
53
+ const [bulkDeleteProgress, setBulkDeleteProgress] = useState(0);
54
+ const [bulkDeleteTotal, setBulkDeleteTotal] = useState(0);
55
+ const [rowSelection, setRowSelection] = useState({});
56
+ const [sorting, setSorting] = useState([]);
57
+ const [columnVisibility, setColumnVisibility] = useState(() => {
58
+ const initial = {};
59
+ hiddenColumns.forEach(col => { initial[col] = false; });
60
+ return initial;
61
+ });
62
+ const [columnFilters, setColumnFilters] = useState([]);
63
+ const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
64
+ const [globalFilter, setGlobalFilter] = useState('');
65
+ const [rowCount, setRowCount] = useState(0);
66
+ const [dateRange, setDateRange] = useState(undefined);
67
+ const [dynamicFilters, setDynamicFilters] = useState({});
68
+ const [filterOptionsMap, setFilterOptionsMap] = useState(new Map());
69
+ const initializedFromUrl = useRef(false);
70
+ const urlHadPerPage = useRef(false);
71
+ useEffect(() => {
72
+ if (prevBranchId.current !== currentBranch?.id) {
73
+ prevBranchId.current = currentBranch?.id;
74
+ setPagination((prev) => ({ ...prev, pageIndex: 0 }));
75
+ setRowSelection({});
76
+ }
77
+ }, [currentBranch?.id]);
78
+ const urlAliasToOperator = {
79
+ 'contains': 'ILIKE', 'like': 'LIKE', 'in': 'IN', 'not_in': 'NOT_IN',
80
+ 'gt': 'GT', 'lt': 'LT', 'gte': 'GTE', 'lte': 'LTE',
81
+ 'range': 'RANGE', 'null': 'NULL', 'not_null': 'NOT_NULL',
82
+ };
83
+ const operatorToUrlAlias = Object.fromEntries(Object.entries(urlAliasToOperator).map(([alias, op]) => [op, alias]));
84
+ const urlValueToInternal = (value) => {
85
+ const colonIdx = value.indexOf(':');
86
+ if (colonIdx === -1)
87
+ return value;
88
+ const prefix = value.substring(0, colonIdx).toLowerCase();
89
+ const rest = value.substring(colonIdx + 1);
90
+ const operator = urlAliasToOperator[prefix];
91
+ return operator ? `${operator}:${rest}` : value;
92
+ };
93
+ const internalValueToUrl = (value) => {
94
+ const colonIdx = value.indexOf(':');
95
+ if (colonIdx === -1)
96
+ return value;
97
+ const prefix = value.substring(0, colonIdx);
98
+ const rest = value.substring(colonIdx + 1);
99
+ const alias = operatorToUrlAlias[prefix];
100
+ return alias ? `${alias}:${rest}` : value;
101
+ };
102
+ useEffect(() => {
103
+ if (!enableUrlSync || initializedFromUrl.current)
104
+ return;
105
+ initializedFromUrl.current = true;
106
+ const params = new URLSearchParams(window.location.search);
107
+ const page = params.get('page');
108
+ const perPage = params.get('per_page');
109
+ if (perPage)
110
+ urlHadPerPage.current = true;
111
+ if (page || perPage) {
112
+ setPagination((prev) => ({
113
+ pageIndex: page ? Math.max(0, parseInt(page, 10) - 1) : prev.pageIndex,
114
+ pageSize: perPage ? parseInt(perPage, 10) : prev.pageSize,
115
+ }));
116
+ }
117
+ const sortBy = params.get('sortBy');
118
+ const order = params.get('order');
119
+ if (sortBy)
120
+ setSorting([{ id: sortBy, desc: order === 'desc' }]);
121
+ const search = params.get('search');
122
+ if (search)
123
+ setGlobalFilter(search);
124
+ const filters = {};
125
+ params.forEach((rawValue, key) => {
126
+ if (key.startsWith('f_')) {
127
+ const filterKey = key.substring(2);
128
+ if (defaultFilters && filterKey in defaultFilters)
129
+ return;
130
+ const value = urlValueToInternal(rawValue);
131
+ if (value.startsWith('IN:'))
132
+ filters[filterKey] = value.substring(3).split(',');
133
+ else
134
+ filters[filterKey] = [value];
135
+ }
136
+ });
137
+ if (Object.keys(filters).length > 0)
138
+ setDynamicFilters(filters);
139
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
140
+ useEffect(() => {
141
+ if (!enableUrlSync || !initializedFromUrl.current)
142
+ return;
143
+ const params = new URLSearchParams();
144
+ if (pagination.pageIndex > 0)
145
+ params.set('page', String(pagination.pageIndex + 1));
146
+ if (pagination.pageSize !== 10)
147
+ params.set('per_page', String(pagination.pageSize));
148
+ if (sorting.length > 0) {
149
+ params.set('sortBy', sorting[0].id);
150
+ params.set('order', sorting[0].desc ? 'desc' : 'asc');
151
+ }
152
+ if (globalFilter)
153
+ params.set('search', globalFilter);
154
+ Object.entries(dynamicFilters).forEach(([key, values]) => {
155
+ if (values.length === 0)
156
+ return;
157
+ if (defaultFilters && key in defaultFilters)
158
+ return;
159
+ if (values.length === 1)
160
+ params.set(`f_${key}`, internalValueToUrl(values[0]));
161
+ else
162
+ params.set(`f_${key}`, `in:${values.join(',')}`);
163
+ });
164
+ const search = params.toString();
165
+ const newUrl = search ? `${window.location.pathname}?${search}` : window.location.pathname;
166
+ window.history.replaceState(null, '', newUrl);
167
+ }, [enableUrlSync, pagination, sorting, globalFilter, dynamicFilters, defaultFilters]);
168
+ const prefetchOptions = useCallback(async (endpoints) => {
169
+ if (endpoints.length === 0)
170
+ return new Map();
171
+ const uniqueEndpoints = Array.from(new Set(endpoints));
172
+ const promises = uniqueEndpoints.map(async (ep) => {
173
+ try {
174
+ const res = await api.get(ep);
175
+ return { endpoint: ep, data: res.data?.success ? res.data.data : [] };
176
+ }
177
+ catch (e) {
178
+ console.error(`Failed to fetch options for ${ep}`, e);
179
+ return { endpoint: ep, data: [] };
180
+ }
181
+ });
182
+ const results = await Promise.all(promises);
183
+ const map = new Map();
184
+ results.forEach(r => map.set(r.endpoint, r.data));
185
+ return map;
186
+ }, [api]);
187
+ const metaInitRef = useRef(false);
188
+ useEffect(() => {
189
+ if (metaInitRef.current)
190
+ return;
191
+ metaInitRef.current = true;
192
+ const initMetadataAndOptions = async () => {
193
+ let meta;
194
+ const cached = getMetadata(model);
195
+ if (cached) {
196
+ meta = cached;
197
+ setMetadata(meta);
198
+ if (!urlHadPerPage.current)
199
+ setPagination((prev) => ({ ...prev, pageSize: meta.defaultPerPage || 10 }));
200
+ setLoading(false);
201
+ }
202
+ else {
203
+ setLoading(true);
204
+ try {
205
+ const res = await api.get(`/metadata/table/${model}`);
206
+ if (!res.data.success)
207
+ return;
208
+ meta = res.data.data;
209
+ setMetadata(meta);
210
+ cacheMetadata(model, meta);
211
+ if (!urlHadPerPage.current)
212
+ setPagination((prev) => ({ ...prev, pageSize: meta.defaultPerPage || 10 }));
213
+ }
214
+ catch (error) {
215
+ console.error('Error al cargar la configuración de la tabla', error);
216
+ return;
217
+ }
218
+ finally {
219
+ setLoading(false);
220
+ }
221
+ }
222
+ const columnEndpoints = meta.columns.filter(c => c.useOptions && c.searchEndpoint).map(c => c.searchEndpoint);
223
+ const filterEndpoints = (meta.filters || []).filter(f => f.searchEndpoint && (f.type === 'select' || f.type === 'boolean')).map(f => f.searchEndpoint);
224
+ const allEndpoints = [...columnEndpoints, ...filterEndpoints];
225
+ if (allEndpoints.length > 0) {
226
+ prefetchOptions(allEndpoints).then(fetchedMap => {
227
+ const colMap = new Map();
228
+ columnEndpoints.forEach(ep => { if (fetchedMap.has(ep))
229
+ colMap.set(ep, fetchedMap.get(ep)); });
230
+ setOptionsMap(colMap);
231
+ const fMap = new Map();
232
+ filterEndpoints.forEach(ep => {
233
+ if (fetchedMap.has(ep)) {
234
+ fMap.set(ep, (fetchedMap.get(ep) || []).map((item) => ({
235
+ label: item.label || item.name || '',
236
+ value: String(item.value ?? item.id ?? ''),
237
+ icon: item.icon,
238
+ color: item.color || item.class,
239
+ })));
240
+ }
241
+ });
242
+ setFilterOptionsMap(fMap);
243
+ });
244
+ }
245
+ };
246
+ initMetadataAndOptions();
247
+ }, [model]); // eslint-disable-line react-hooks/exhaustive-deps
248
+ const buildFilterParams = useCallback(() => {
249
+ const params = {};
250
+ if (sorting.length > 0) {
251
+ params.sortBy = sorting[0].id;
252
+ params.order = sorting[0].desc ? 'desc' : 'asc';
253
+ }
254
+ if (globalFilter)
255
+ params.search = globalFilter;
256
+ columnFilters.forEach((filter) => { params[`f_${filter.id}`] = filter.value; });
257
+ if (defaultFilters)
258
+ Object.entries(defaultFilters).forEach(([key, value]) => { params[`f_${key}`] = value; });
259
+ Object.entries(dynamicFilters).forEach(([key, values]) => {
260
+ if (values.length === 0)
261
+ return;
262
+ const gteVal = values.find(v => v.startsWith('GTE:'));
263
+ const lteVal = values.find(v => v.startsWith('LTE:'));
264
+ if (gteVal || lteVal) {
265
+ const min = gteVal ? gteVal.replace('GTE:', '') : '';
266
+ const max = lteVal ? lteVal.replace('LTE:', '') : '';
267
+ params[`f_${key}`] = `RANGE:${min},${max}`;
268
+ return;
269
+ }
270
+ if (values.length === 1)
271
+ params[`f_${key}`] = values[0];
272
+ else
273
+ params[`f_${key}`] = `IN:${values.join(',')}`;
274
+ });
275
+ if (dateRange?.from) {
276
+ const startDate = format(dateRange.from, 'yyyy-MM-dd');
277
+ const endDate = dateRange.to ? format(dateRange.to, 'yyyy-MM-dd') : startDate;
278
+ params['f_created_at'] = `${startDate}_${endDate}`;
279
+ }
280
+ return params;
281
+ }, [sorting, globalFilter, columnFilters, defaultFilters, dynamicFilters, dateRange]);
282
+ const hasActiveFilters = useMemo(() => {
283
+ if (globalFilter)
284
+ return true;
285
+ if (columnFilters.length > 0)
286
+ return true;
287
+ if (Object.values(dynamicFilters).some(v => v.length > 0))
288
+ return true;
289
+ if (dateRange?.from)
290
+ return true;
291
+ return false;
292
+ }, [globalFilter, columnFilters, dynamicFilters, dateRange]);
293
+ const fetchData = useCallback(async () => {
294
+ if (!metadata)
295
+ return;
296
+ setLoadingData(true);
297
+ try {
298
+ const params = {
299
+ page: pagination.pageIndex + 1,
300
+ per_page: pagination.pageSize,
301
+ ...buildFilterParams(),
302
+ };
303
+ const res = await api.get(endpoint || `/data/${model}`, { params });
304
+ if (res.data.success) {
305
+ setData(res.data.data || []);
306
+ if (res.data.meta)
307
+ setRowCount(res.data.meta.total);
308
+ }
309
+ }
310
+ catch (error) {
311
+ console.error('Error al cargar los datos', error);
312
+ }
313
+ finally {
314
+ setLoadingData(false);
315
+ }
316
+ }, [model, metadata, pagination, buildFilterParams, refreshTrigger, endpoint, currentBranch?.id, api]);
317
+ const initialFetchDone = useRef(false);
318
+ useEffect(() => {
319
+ if (!metadata)
320
+ return;
321
+ if (!initialFetchDone.current) {
322
+ initialFetchDone.current = true;
323
+ fetchData();
324
+ return;
325
+ }
326
+ const timeoutId = setTimeout(fetchData, 300);
327
+ return () => clearTimeout(timeoutId);
328
+ }, [fetchData, metadata]);
329
+ const handleRefresh = useCallback(() => { fetchData(); }, [fetchData]);
330
+ const handleInternalAction = useCallback(async (action, row) => {
331
+ if (action === 'delete') {
332
+ setRowToDelete(row);
333
+ return;
334
+ }
335
+ if (action === 'view' || action === 'edit') {
336
+ if (onAction)
337
+ await Promise.resolve(onAction(action, row));
338
+ else
339
+ setRecordDialog({ open: true, mode: action, recordId: row.id });
340
+ return;
341
+ }
342
+ const linkDef = metadata?.actions?.find((a) => a.key === action && a.type === 'link');
343
+ if (linkDef?.linkUrl) {
344
+ const url = linkDef.linkUrl.replace(/\{(\w+)\}/g, (_, field) => String(row[field] ?? ''));
345
+ navigate({ to: url });
346
+ return;
347
+ }
348
+ const actionDef = metadata?.actions?.find((a) => a.key === action);
349
+ if (actionDef && (actionDef.fields?.length || actionDef.confirm || actionDef.executable)) {
350
+ setActionModal({
351
+ open: true,
352
+ action: {
353
+ key: actionDef.key,
354
+ label: actionDef.label,
355
+ icon: actionDef.icon || 'Zap',
356
+ color: actionDef.color,
357
+ confirm: actionDef.confirm,
358
+ confirmMessage: actionDef.confirmMessage,
359
+ fields: actionDef.fields,
360
+ requiresState: actionDef.requiresState,
361
+ executable: actionDef.executable,
362
+ },
363
+ record: row,
364
+ });
365
+ return;
366
+ }
367
+ if (onAction) {
368
+ await Promise.resolve(onAction(action, row));
369
+ handleRefresh();
370
+ }
371
+ else
372
+ handleRefresh();
373
+ }, [onAction, handleRefresh, metadata, navigate]);
374
+ const confirmDelete = async () => {
375
+ if (!rowToDelete)
376
+ return;
377
+ setIsDeleting(true);
378
+ try {
379
+ const deleteEndpoint = endpoint ? `${endpoint}/${rowToDelete.id}` : `/data/${model}/${rowToDelete.id}`;
380
+ const res = await api.delete(deleteEndpoint);
381
+ if (res.data.success) {
382
+ toast.success(res.data.message || 'Eliminado correctamente');
383
+ handleRefresh();
384
+ }
385
+ else
386
+ toast.error(res.data.message || 'Error al eliminar');
387
+ }
388
+ catch (error) {
389
+ console.error('Error al eliminar', error);
390
+ toast.error('Error al eliminar el registro');
391
+ }
392
+ finally {
393
+ setIsDeleting(false);
394
+ setRowToDelete(null);
395
+ }
396
+ };
397
+ const confirmBulkDelete = async () => {
398
+ const selectedRows = table.getFilteredSelectedRowModel().rows;
399
+ if (selectedRows.length === 0)
400
+ return;
401
+ setIsBulkDeleting(true);
402
+ setBulkDeleteTotal(selectedRows.length);
403
+ setBulkDeleteProgress(0);
404
+ let successCount = 0, errorCount = 0;
405
+ for (let i = 0; i < selectedRows.length; i++) {
406
+ const row = selectedRows[i];
407
+ try {
408
+ const deleteEndpoint = endpoint ? `${endpoint}/${row.original.id}` : `/data/${model}/${row.original.id}`;
409
+ const res = await api.delete(deleteEndpoint);
410
+ if (res.data.success)
411
+ successCount++;
412
+ else
413
+ errorCount++;
414
+ }
415
+ catch (e) {
416
+ console.error('Error al eliminar', e);
417
+ errorCount++;
418
+ }
419
+ setBulkDeleteProgress(i + 1);
420
+ }
421
+ await new Promise(resolve => setTimeout(resolve, 500));
422
+ setIsBulkDeleting(false);
423
+ setShowBulkDeleteConfirm(false);
424
+ setBulkDeleteProgress(0);
425
+ setBulkDeleteTotal(0);
426
+ setRowSelection({});
427
+ if (successCount > 0)
428
+ toast.success(`${successCount} registro(s) eliminado(s) correctamente`);
429
+ if (errorCount > 0)
430
+ toast.error(`${errorCount} registro(s) no pudieron ser eliminados`);
431
+ handleRefresh();
432
+ };
433
+ const handleDynamicFilterChange = useCallback((filterKey, values) => {
434
+ setDynamicFilters((prev) => ({ ...prev, [filterKey]: values }));
435
+ setPagination((prev) => ({ ...prev, pageIndex: 0 }));
436
+ }, []);
437
+ const columnFilterConfigs = useMemo(() => {
438
+ const map = new Map();
439
+ if (!metadata?.filters)
440
+ return map;
441
+ for (const f of metadata.filters) {
442
+ const fType = f.type;
443
+ let options = [];
444
+ if (f.options && f.options.length > 0) {
445
+ options = f.options.map(o => ({ label: o.label, value: String(o.value), icon: o.icon, color: o.color }));
446
+ }
447
+ if (f.searchEndpoint && filterOptionsMap.has(f.searchEndpoint)) {
448
+ options = filterOptionsMap.get(f.searchEndpoint) || [];
449
+ }
450
+ if (fType === 'select' && options.length === 0 && !f.searchEndpoint)
451
+ continue;
452
+ map.set(f.key, {
453
+ filterType: fType,
454
+ filterKey: f.column || f.key,
455
+ options,
456
+ selectedValues: dynamicFilters[f.column || f.key] || [],
457
+ onFilterChange: handleDynamicFilterChange,
458
+ loading: f.searchEndpoint ? !filterOptionsMap.has(f.searchEndpoint) : false,
459
+ searchEndpoint: f.searchEndpoint,
460
+ });
461
+ }
462
+ return map;
463
+ }, [metadata, filterOptionsMap, dynamicFilters, handleDynamicFilterChange]);
464
+ const columns = useMemo(() => {
465
+ if (!metadata)
466
+ return [];
467
+ const baseColumns = getDynamicColumns(metadata, handleInternalAction, t, i18n.language, columnFilterConfigs);
468
+ const filteredBase = baseColumns.filter((col) => !hiddenColumns.includes(col.id));
469
+ const actionsCol = filteredBase.find((c) => c.id === 'actions');
470
+ const otherCols = filteredBase.filter((c) => c.id !== 'actions');
471
+ return [...otherCols, ...extraColumns, ...(actionsCol ? [actionsCol] : [])];
472
+ }, [metadata, handleInternalAction, hiddenColumns, extraColumns, t, i18n.language, columnFilterConfigs, getDynamicColumns]);
473
+ const filters = useMemo(() => [], []);
474
+ const table = useReactTable({
475
+ data,
476
+ columns,
477
+ state: { sorting, columnVisibility, rowSelection, columnFilters, globalFilter, pagination },
478
+ pageCount: Math.ceil(rowCount / pagination.pageSize),
479
+ manualPagination: true,
480
+ manualSorting: true,
481
+ manualFiltering: true,
482
+ enableRowSelection: true,
483
+ onRowSelectionChange: setRowSelection,
484
+ onSortingChange: setSorting,
485
+ onColumnVisibilityChange: setColumnVisibility,
486
+ onColumnFiltersChange: setColumnFilters,
487
+ onGlobalFilterChange: setGlobalFilter,
488
+ onPaginationChange: setPagination,
489
+ getCoreRowModel: getCoreRowModel(),
490
+ getFilteredRowModel: getFilteredRowModel(),
491
+ getPaginationRowModel: getPaginationRowModel(),
492
+ getSortedRowModel: getSortedRowModel(),
493
+ getFacetedRowModel: getFacetedRowModel(),
494
+ getFacetedUniqueValues: getFacetedUniqueValues(),
495
+ });
496
+ const TableSkeleton = () => (_jsx(_Fragment, { children: Array.from({ length: 5 }).map((_, i) => (_jsxs(TableRow, { className: "hover:bg-transparent", children: [_jsx(TableCell, { className: "py-3 w-10", children: _jsx(Skeleton, { className: "h-4 w-4" }) }), _jsx(TableCell, { className: "py-3", children: _jsx(Skeleton, { className: "h-4 w-[70%]" }) }), _jsx(TableCell, { className: "py-3", children: _jsx(Skeleton, { className: "h-4 w-[50%]" }) }), _jsx(TableCell, { className: "py-3", children: _jsx(Skeleton, { className: "h-4 w-[60%]" }) }), _jsx(TableCell, { className: "py-3 w-16", children: _jsx(Skeleton, { className: "h-6 w-6" }) })] }, `skeleton-${i}`))) }));
497
+ if (loading) {
498
+ return (_jsxs("div", { className: 'flex flex-col h-full min-h-0 w-full', children: [_jsx("div", { className: 'pb-4 shrink-0', children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Skeleton, { className: "h-9 w-[280px]" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Skeleton, { className: "h-9 w-9" }), _jsx(Skeleton, { className: "h-9 w-[70px]" })] })] }) }), _jsx("div", { className: 'flex-1 min-h-0 overflow-auto border rounded-md bg-card', children: _jsxs(Table, { className: 'min-w-max w-full', children: [_jsx(TableHeader, { className: 'sticky top-0 z-10', children: _jsxs(TableRow, { className: 'border-b-0 hover:bg-transparent', children: [_jsx(TableHead, { className: 'bg-card border-b h-10 w-10', children: _jsx(Skeleton, { className: "h-4 w-4" }) }), _jsx(TableHead, { className: 'bg-card border-b h-10', children: _jsx(Skeleton, { className: "h-4 w-16" }) }), _jsx(TableHead, { className: 'bg-card border-b h-10', children: _jsx(Skeleton, { className: "h-4 w-14" }) }), _jsx(TableHead, { className: 'bg-card border-b h-10', children: _jsx(Skeleton, { className: "h-4 w-20" }) }), _jsx(TableHead, { className: 'bg-card border-b h-10 w-16', children: _jsx(Skeleton, { className: "h-4 w-12" }) })] }) }), _jsx(TableBody, { children: _jsx(TableSkeleton, {}) })] }) })] }));
499
+ }
500
+ if (!metadata) {
501
+ return _jsx("div", { className: "text-center text-muted-foreground py-8", children: "Error al cargar la configuraci\u00F3n de la tabla." });
502
+ }
503
+ return (_jsxs(OptionsContext.Provider, { value: { optionsMap }, children: [_jsxs("div", { className: 'flex flex-col h-full min-h-0 w-full', children: [_jsx("div", { className: 'pb-4 shrink-0', children: _jsx(DataTableToolbar, { table: table, searchPlaceholder: metadata.searchPlaceholder || 'Buscar...', filters: filters, activeFilters: dynamicFilters, onDynamicFilterChange: handleDynamicFilterChange, dateFilter: { value: dateRange, onChange: setDateRange, placeholder: 'Filtrar por fecha' }, perPageOptions: metadata.perPageOptions, onRefresh: handleRefresh, isLoading: loadingData, selectedCount: Object.keys(rowSelection).length, onBulkDelete: () => setShowBulkDeleteConfirm(true), extraActions: _jsxs(_Fragment, { children: [metadata.canExport && (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => setExportOpen(true), children: [_jsx(Download, { className: "h-4 w-4 mr-1" }), " Exportar"] })), metadata.canImport && (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => setImportOpen(true), children: [_jsx(Upload, { className: "h-4 w-4 mr-1" }), " Importar"] }))] }) }) }), _jsx("div", { className: 'flex-1 min-h-0 overflow-auto border rounded-md bg-card', children: _jsxs(Table, { className: 'min-w-max w-full', children: [_jsx(TableHeader, { className: 'sticky top-0 z-10', children: table.getHeaderGroups().map((headerGroup) => (_jsx(TableRow, { className: 'border-b-0 hover:bg-transparent', children: headerGroup.headers.map((header) => {
504
+ const isActionsColumn = header.id === 'actions';
505
+ return (_jsx(TableHead, { colSpan: header.colSpan, style: header.column.columnDef.size ? { width: header.column.columnDef.size } : undefined, className: cn('bg-card border-b h-10', isActionsColumn && 'sticky right-0 z-20 bg-card shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]'), children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()) }, header.id));
506
+ }) }, headerGroup.id))) }), _jsx(TableBody, { children: loadingData && data.length === 0 ? (_jsx(TableSkeleton, {})) : table.getRowModel().rows?.length ? (table.getRowModel().rows.map((row) => (_jsx(TableRow, { "data-state": row.getIsSelected() && 'selected', children: row.getVisibleCells().map((cell) => {
507
+ const isActionsColumn = cell.column.id === 'actions';
508
+ return (_jsx(TableCell, { style: cell.column.columnDef.size ? { width: cell.column.columnDef.size } : undefined, className: cn('py-2', isActionsColumn && 'sticky right-0 bg-card shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]'), children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id));
509
+ }) }, row.id)))) : (_jsx(TableRow, { className: 'border-b-0 hover:bg-transparent', children: _jsx(TableCell, { colSpan: columns.length, className: 'h-full p-0', children: _jsxs("div", { className: "flex h-full py-12 flex-col items-center justify-center gap-2 text-muted-foreground", children: [_jsx("div", { className: "flex h-20 w-20 items-center justify-center rounded-full bg-muted/50", children: _jsx(Inbox, { className: "h-10 w-10" }) }), _jsxs("div", { className: "flex flex-col items-center gap-1", children: [_jsx("h3", { className: "text-lg font-semibold text-foreground", children: "No se encontraron resultados" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "No hay datos para mostrar en este momento." })] })] }) }) })) })] }) }), _jsx("div", { className: 'shrink-0 pt-4', children: _jsx(DataTablePagination, { table: table, pageSizeOptions: metadata.perPageOptions }) })] }), _jsx(AlertDialog, { open: !!rowToDelete, onOpenChange: (open) => !open && setRowToDelete(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "\u00BFEst\u00E1 absolutamente seguro?" }), _jsx(AlertDialogDescription, { children: "Esta acci\u00F3n no se puede deshacer. Esto eliminar\u00E1 permanentemente el registro seleccionado de nuestros servidores." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: isDeleting, children: t('common.cancel') }), _jsx(AlertDialogAction, { onClick: (e) => { e.preventDefault(); confirmDelete(); }, className: "bg-red-600 hover:bg-red-700", disabled: isDeleting, children: isDeleting ? 'Eliminando...' : 'Eliminar' })] })] }) }), _jsx(AlertDialog, { open: showBulkDeleteConfirm, onOpenChange: (open) => !open && !isBulkDeleting && setShowBulkDeleteConfirm(false), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: isBulkDeleting ? 'Eliminando registros...' : '¿Eliminar múltiples registros?' }), _jsx(AlertDialogDescription, { children: isBulkDeleting ? (_jsxs("div", { className: "space-y-4 mt-4", children: [_jsx(Progress, { value: (bulkDeleteProgress / bulkDeleteTotal) * 100 }), _jsxs("p", { className: "text-center text-sm", children: ["Procesando ", bulkDeleteProgress, " de ", bulkDeleteTotal, " registros..."] })] })) : (_jsxs(_Fragment, { children: ["Esta acci\u00F3n no se puede deshacer. Se eliminar\u00E1n permanentemente ", _jsx("strong", { children: Object.keys(rowSelection).length }), " registro(s) de nuestros servidores."] })) })] }), !isBulkDeleting && (_jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: t('common.cancel') }), _jsx(AlertDialogAction, { onClick: (e) => { e.preventDefault(); confirmBulkDelete(); }, className: "bg-red-600 hover:bg-red-700", children: "Eliminar todos" })] }))] }) }), _jsx(DynamicRecordDialog, { open: recordDialog.open, onOpenChange: (open) => setRecordDialog((prev) => ({ ...prev, open })), mode: recordDialog.mode, model: model, recordId: recordDialog.recordId, endpoint: endpoint, onSaved: handleRefresh }), metadata.canExport && (_jsx(ExportDialog, { open: exportOpen, onOpenChange: setExportOpen, model: model, metadata: metadata, currentFilters: buildFilterParams(), hasActiveFilters: hasActiveFilters })), metadata.canImport && (_jsx(ImportDialog, { open: importOpen, onOpenChange: setImportOpen, model: model, metadata: metadata, onImported: handleRefresh })), actionModal.action && (_jsx(ActionModalDispatcher, { open: actionModal.open, onOpenChange: (open) => setActionModal((prev) => ({ ...prev, open })), action: actionModal.action, model: model, record: actionModal.record, endpoint: endpoint, onSuccess: handleRefresh })), _jsx(DataTableBulkActions, { table: table, entityName: "registro", children: _jsxs(Button, { variant: "destructive", size: "sm", className: "h-8", onClick: () => setShowBulkDeleteConfirm(true), children: [_jsx(Trash2, { className: "h-4 w-4 mr-1.5" }), " Eliminar"] }) })] }));
510
+ }
511
+ /** Sensible default when hosts don't provide their own getDynamicColumns. */
512
+ const defaultGetDynamicColumns = (metadata, _handleAction, _t, _lang, _filters) => (metadata.columns ?? []).map((col) => ({
513
+ accessorKey: col.name,
514
+ header: col.label ?? col.name,
515
+ enableSorting: col.sortable ?? false,
516
+ }));
@@ -0,0 +1,16 @@
1
+ import type { i18n as I18nInstance } from 'i18next';
2
+ export interface AddonI18nResources {
3
+ /** Addon key — used as the i18next namespace. */
4
+ source: string;
5
+ /** Map of locale → key/value tree. */
6
+ resources: Record<string, Record<string, any>>;
7
+ }
8
+ export interface I18nProviderProps {
9
+ /** The host's i18next instance. */
10
+ i18n: I18nInstance;
11
+ /** All addon translations contributed via `manifest.i18n`. */
12
+ contributions: AddonI18nResources[];
13
+ children: React.ReactNode;
14
+ }
15
+ export declare function I18nProvider({ i18n, contributions, children }: I18nProviderProps): import("react/jsx-runtime").JSX.Element;
16
+ //# sourceMappingURL=i18n-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n-provider.d.ts","sourceRoot":"","sources":["../src/i18n-provider.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,IAAI,YAAY,EAAE,MAAM,SAAS,CAAA;AAEnD,MAAM,WAAW,kBAAkB;IAC/B,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAA;IACd,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;CACjD;AAED,MAAM,WAAW,iBAAiB;IAC9B,mCAAmC;IACnC,IAAI,EAAE,YAAY,CAAA;IAClB,8DAA8D;IAC9D,aAAa,EAAE,kBAAkB,EAAE,CAAA;IACnC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC5B;AAED,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,EAAE,iBAAiB,2CAWhF"}
@@ -0,0 +1,16 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ // I18nProvider — merges `manifest.i18n` resources from every loaded addon
3
+ // into the host's i18next instance. The host passes the i18n instance; this
4
+ // component just hydrates new namespaces and keeps them live.
5
+ import { useEffect } from 'react';
6
+ export function I18nProvider({ i18n, contributions, children }) {
7
+ useEffect(() => {
8
+ for (const c of contributions) {
9
+ for (const [locale, tree] of Object.entries(c.resources)) {
10
+ // addBundle(locale, namespace, resources, deep?, overwrite?)
11
+ i18n.addResourceBundle(locale, c.source, tree, true, false);
12
+ }
13
+ }
14
+ }, [i18n, contributions]);
15
+ return _jsx(_Fragment, { children: children });
16
+ }
@@ -0,0 +1,18 @@
1
+ export * from './types';
2
+ export * from './options-context';
3
+ export * from './dynamic-table';
4
+ export * from './dynamic-form';
5
+ export { ActionModalDispatcher, type ActionModalProps, } from './action-modal-dispatcher';
6
+ export * from './addon-loader';
7
+ export * from './slot';
8
+ export * from './capability-gate';
9
+ export * from './navigation-builder';
10
+ export * from './i18n-provider';
11
+ export * from './api-context';
12
+ export * from './metadata-cache';
13
+ export * from './dynamic-icon';
14
+ export type { ColumnFilterConfig, FilterOption as DynamicColumnFilterOption, GetDynamicColumns, DynamicIconComponent, } from './dynamic-columns-shim';
15
+ export { DynamicRecordDialog } from './dialogs/dynamic-record';
16
+ export { ExportDialog } from './dialogs/export';
17
+ export { ImportDialog } from './dialogs/import';
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,qBAAqB,EACrB,KAAK,gBAAgB,GACxB,MAAM,2BAA2B,CAAA;AAClC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,QAAQ,CAAA;AACtB,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,YAAY,EACR,kBAAkB,EAClB,YAAY,IAAI,yBAAyB,EACzC,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ // Public surface — keep names stable. `ActionMetadata` is intentionally
2
+ // re-exported once from `./types` (the mirror used by runtime-react internally)
3
+ // and NOT re-exported from `./action-modal-dispatcher` here to avoid a
4
+ // duplicate-symbol conflict; consumers who want the canonical SDK type
5
+ // should import from `@asteby/metacore-sdk` directly.
6
+ export * from './types';
7
+ export * from './options-context';
8
+ export * from './dynamic-table';
9
+ export * from './dynamic-form';
10
+ export { ActionModalDispatcher, } from './action-modal-dispatcher';
11
+ export * from './addon-loader';
12
+ export * from './slot';
13
+ export * from './capability-gate';
14
+ export * from './navigation-builder';
15
+ export * from './i18n-provider';
16
+ export * from './api-context';
17
+ export * from './metadata-cache';
18
+ export * from './dynamic-icon';
19
+ export { DynamicRecordDialog } from './dialogs/dynamic-record';
20
+ export { ExportDialog } from './dialogs/export';
21
+ export { ImportDialog } from './dialogs/import';
@@ -0,0 +1,42 @@
1
+ import type { TableMetadata } from './types';
2
+ export interface MetadataApiClient {
3
+ get: (url: string, config?: any) => Promise<{
4
+ data: any;
5
+ }>;
6
+ }
7
+ interface MetadataCacheState {
8
+ cache: Record<string, TableMetadata>;
9
+ modalCache: Record<string, TableMetadata>;
10
+ metadataVersion: string;
11
+ prefetched: boolean;
12
+ getMetadata: (key: string) => TableMetadata | undefined;
13
+ getModalMetadata: (key: string) => TableMetadata | undefined;
14
+ setMetadata: (key: string, metadata: TableMetadata) => void;
15
+ setModalMetadata: (key: string, metadata: TableMetadata) => void;
16
+ hasMetadata: (key: string) => boolean;
17
+ hasModalMetadata: (key: string) => boolean;
18
+ prefetchAll: (api: MetadataApiClient) => Promise<void>;
19
+ }
20
+ export declare const useMetadataCache: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<MetadataCacheState>, "setState" | "persist"> & {
21
+ setState(partial: MetadataCacheState | Partial<MetadataCacheState> | ((state: MetadataCacheState) => MetadataCacheState | Partial<MetadataCacheState>), replace?: false | undefined): unknown;
22
+ setState(state: MetadataCacheState | ((state: MetadataCacheState) => MetadataCacheState), replace: true): unknown;
23
+ persist: {
24
+ setOptions: (options: Partial<import("zustand/middleware").PersistOptions<MetadataCacheState, {
25
+ cache: Record<string, TableMetadata>;
26
+ modalCache: Record<string, TableMetadata>;
27
+ metadataVersion: string;
28
+ }, unknown>>) => void;
29
+ clearStorage: () => void;
30
+ rehydrate: () => Promise<void> | void;
31
+ hasHydrated: () => boolean;
32
+ onHydrate: (fn: (state: MetadataCacheState) => void) => () => void;
33
+ onFinishHydration: (fn: (state: MetadataCacheState) => void) => () => void;
34
+ getOptions: () => Partial<import("zustand/middleware").PersistOptions<MetadataCacheState, {
35
+ cache: Record<string, TableMetadata>;
36
+ modalCache: Record<string, TableMetadata>;
37
+ metadataVersion: string;
38
+ }, unknown>>;
39
+ };
40
+ }>;
41
+ export {};
42
+ //# sourceMappingURL=metadata-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata-cache.d.ts","sourceRoot":"","sources":["../src/metadata-cache.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE5C,MAAM,WAAW,iBAAiB;IAC9B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAA;KAAE,CAAC,CAAA;CAC7D;AAED,UAAU,kBAAkB;IACxB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;IACpC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;IACzC,eAAe,EAAE,MAAM,CAAA;IACvB,UAAU,EAAE,OAAO,CAAA;IACnB,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,aAAa,GAAG,SAAS,CAAA;IACvD,gBAAgB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,aAAa,GAAG,SAAS,CAAA;IAC5D,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAA;IAC3D,gBAAgB,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAA;IAChE,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAA;IACrC,gBAAgB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAA;IAC1C,WAAW,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACzD;AAED,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;EAwE5B,CAAA"}