@openg2p/registry-widgets 0.1.0 → 0.1.1
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/dist/components/PanelRenderer.d.ts.map +1 -1
- package/dist/components/SectionBuilder/JSONEditorPanel.d.ts +13 -0
- package/dist/components/SectionBuilder/JSONEditorPanel.d.ts.map +1 -0
- package/dist/components/SectionBuilder/PropertyEditor.d.ts +15 -0
- package/dist/components/SectionBuilder/PropertyEditor.d.ts.map +1 -0
- package/dist/components/SectionBuilder/SectionBuilder.d.ts +13 -0
- package/dist/components/SectionBuilder/SectionBuilder.d.ts.map +1 -0
- package/dist/components/SectionBuilder/SectionTree.d.ts +26 -0
- package/dist/components/SectionBuilder/SectionTree.d.ts.map +1 -0
- package/dist/components/SectionBuilder/VisualBuilderPanel.d.ts +19 -0
- package/dist/components/SectionBuilder/VisualBuilderPanel.d.ts.map +1 -0
- package/dist/components/SectionBuilder/index.d.ts +9 -0
- package/dist/components/SectionBuilder/index.d.ts.map +1 -0
- package/dist/components/SectionBuilder/schemas.d.ts +1947 -0
- package/dist/components/SectionBuilder/schemas.d.ts.map +1 -0
- package/dist/components/SectionRenderer.d.ts +4 -4
- package/dist/components/SectionRenderer.d.ts.map +1 -1
- package/dist/components/SectionsContainer.d.ts +2 -1
- package/dist/components/SectionsContainer.d.ts.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.esm.js +558 -173
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +558 -173
- package/dist/index.js.map +1 -1
- package/dist/utils/schemaNamespace.d.ts +12 -0
- package/dist/utils/schemaNamespace.d.ts.map +1 -0
- package/dist/widgets/RadioWidget.d.ts.map +1 -1
- package/dist/widgets/SelectWidget.d.ts.map +1 -1
- package/dist/widgets/TableWidget.d.ts.map +1 -1
- package/package.json +11 -11
package/dist/index.esm.js
CHANGED
|
@@ -1593,30 +1593,36 @@ const PanelRenderer = ({ panel, apiAdapter, schemaData, onValueChange, isEditMod
|
|
|
1593
1593
|
const nestedPanels = panel.panels || [];
|
|
1594
1594
|
const widgets = panel.widgets || [];
|
|
1595
1595
|
// For horizontal orientation, use grid for equal-width columns
|
|
1596
|
-
// Dynamic grid based on number of nested panels
|
|
1596
|
+
// Dynamic grid based on number of nested panels and their column spans
|
|
1597
1597
|
// For vertical orientation, use flex column
|
|
1598
1598
|
const getContainerClassAndStyle = () => {
|
|
1599
1599
|
if (orientation === 'horizontal' && nestedPanels.length > 0) {
|
|
1600
|
-
|
|
1601
|
-
//
|
|
1600
|
+
// Calculate total columns needed based on panel column spans
|
|
1601
|
+
// Sum up all column spans, or use panel count if no spans specified
|
|
1602
|
+
let totalColumns = 0;
|
|
1603
|
+
nestedPanels.forEach(panel => {
|
|
1604
|
+
const columnSpan = panel['panel-column-span'] || 1;
|
|
1605
|
+
totalColumns += columnSpan;
|
|
1606
|
+
});
|
|
1607
|
+
// Ensure at least as many columns as panels (for panels without explicit span)
|
|
1608
|
+
totalColumns = Math.max(totalColumns, nestedPanels.length);
|
|
1609
|
+
// Use predefined grid classes for common cases (1-5)
|
|
1602
1610
|
// For more than 5, use inline style
|
|
1603
1611
|
// Removed gap to allow borders to show properly
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
4: 'grid grid-cols-4',
|
|
1611
|
-
5: 'grid grid-cols-5',
|
|
1612
|
+
if (totalColumns <= 5) {
|
|
1613
|
+
// For grid with column spans, we need to use inline styles to set minmax
|
|
1614
|
+
// This ensures each column is at least 200px wide
|
|
1615
|
+
return {
|
|
1616
|
+
className: 'grid',
|
|
1617
|
+
style: { gridTemplateColumns: `repeat(${totalColumns}, minmax(200px, 1fr))` },
|
|
1612
1618
|
};
|
|
1613
|
-
return { className: gridClasses[numPanels] || 'grid', style: {} };
|
|
1614
1619
|
}
|
|
1615
1620
|
else {
|
|
1616
|
-
// For more than 5
|
|
1621
|
+
// For more than 5 columns, use inline style
|
|
1622
|
+
// Use minmax(200px, 1fr) to ensure minimum 200px per column
|
|
1617
1623
|
return {
|
|
1618
1624
|
className: 'grid',
|
|
1619
|
-
style: { gridTemplateColumns: `repeat(${
|
|
1625
|
+
style: { gridTemplateColumns: `repeat(${totalColumns}, minmax(200px, 1fr))` },
|
|
1620
1626
|
};
|
|
1621
1627
|
}
|
|
1622
1628
|
}
|
|
@@ -1636,15 +1642,44 @@ const PanelRenderer = ({ panel, apiAdapter, schemaData, onValueChange, isEditMod
|
|
|
1636
1642
|
}, children: [nestedPanels.map((nestedPanel, index) => {
|
|
1637
1643
|
const isLastPanel = index === nestedPanels.length - 1;
|
|
1638
1644
|
const isFirstPanel = index === 0;
|
|
1639
|
-
const
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
+
const nestedOrientation = nestedPanel['panel-orientation'] || 'vertical';
|
|
1646
|
+
const columnSpan = nestedPanel['panel-column-span'];
|
|
1647
|
+
// Calculate style for nested panel based on orientation and column span
|
|
1648
|
+
const getNestedPanelStyle = () => {
|
|
1649
|
+
if (orientation === 'horizontal') {
|
|
1650
|
+
// When nested inside horizontal panel, check for column span
|
|
1651
|
+
const baseStyle = {
|
|
1652
|
+
minWidth: '200px',
|
|
1653
|
+
paddingRight: !isLastPanel ? '40px' : undefined,
|
|
1654
|
+
paddingLeft: !isFirstPanel ? '40px' : undefined,
|
|
1655
|
+
position: 'relative',
|
|
1656
|
+
};
|
|
1657
|
+
// If vertical panel has column span, use CSS grid-column-span
|
|
1658
|
+
if (nestedOrientation === 'vertical' && columnSpan && columnSpan > 1) {
|
|
1659
|
+
return {
|
|
1660
|
+
...baseStyle,
|
|
1661
|
+
gridColumn: `span ${columnSpan}`,
|
|
1662
|
+
minWidth: 'auto', // Remove minWidth constraint when spanning columns
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
return baseStyle;
|
|
1666
|
+
}
|
|
1667
|
+
else {
|
|
1668
|
+
// Vertical panel nested in vertical panel
|
|
1669
|
+
if (columnSpan && columnSpan > 1) {
|
|
1670
|
+
// If column span is specified, calculate width based on 200px per column
|
|
1671
|
+
const width = columnSpan * 200;
|
|
1672
|
+
return {
|
|
1673
|
+
width: `${width}px`,
|
|
1674
|
+
maxWidth: '100%',
|
|
1675
|
+
flexShrink: 0,
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
return { width: '100%' };
|
|
1645
1679
|
}
|
|
1646
|
-
|
|
1647
|
-
|
|
1680
|
+
};
|
|
1681
|
+
const nestedPanelStyle = getNestedPanelStyle();
|
|
1682
|
+
return (jsxRuntimeExports.jsx(React.Fragment, { children: jsxRuntimeExports.jsxs("div", { className: orientation === 'horizontal' ? 'min-w-200 relative' : 'w-full', style: nestedPanelStyle, "data-panel-column-span": columnSpan || undefined, children: [jsxRuntimeExports.jsx(PanelRenderer, { panel: nestedPanel, apiAdapter: apiAdapter, schemaData: schemaData, onValueChange: onValueChange, isEditMode: isEditMode }), orientation === 'horizontal' && !isLastPanel && (jsxRuntimeExports.jsx("div", { style: {
|
|
1648
1683
|
position: 'absolute',
|
|
1649
1684
|
right: 0,
|
|
1650
1685
|
top: 0,
|
|
@@ -2246,6 +2281,104 @@ const FileInputWidget = ({ config }) => {
|
|
|
2246
2281
|
} }, `modal-${previewFile ? (previewFile instanceof File ? previewFile.name : previewFile) : 'none'}`)] }));
|
|
2247
2282
|
};
|
|
2248
2283
|
|
|
2284
|
+
/**
|
|
2285
|
+
* Namespace a data path by adding a namespace prefix
|
|
2286
|
+
*/
|
|
2287
|
+
const namespaceDataPath = (dataPath, namespace) => {
|
|
2288
|
+
if (!dataPath)
|
|
2289
|
+
return dataPath;
|
|
2290
|
+
if (typeof dataPath === 'string') {
|
|
2291
|
+
// Add namespace prefix to the data path
|
|
2292
|
+
return `${namespace}.${dataPath}`;
|
|
2293
|
+
}
|
|
2294
|
+
// Multi-path: namespace each path
|
|
2295
|
+
const namespaced = {};
|
|
2296
|
+
for (const [key, path] of Object.entries(dataPath)) {
|
|
2297
|
+
namespaced[key] = `${namespace}.${path}`;
|
|
2298
|
+
}
|
|
2299
|
+
return namespaced;
|
|
2300
|
+
};
|
|
2301
|
+
/**
|
|
2302
|
+
* Recursively namespace widget IDs and data paths in a widget configuration
|
|
2303
|
+
* This ensures unique widget IDs and data paths when the same section is rendered multiple times
|
|
2304
|
+
*/
|
|
2305
|
+
const namespaceWidgetConfig = (widgetConfig, namespace) => {
|
|
2306
|
+
const namespaced = { ...widgetConfig };
|
|
2307
|
+
// Namespace the widget-id
|
|
2308
|
+
if (namespaced['widget-id']) {
|
|
2309
|
+
namespaced['widget-id'] = `${namespace}__${namespaced['widget-id']}`;
|
|
2310
|
+
}
|
|
2311
|
+
// Namespace the widget-data-path to ensure values are stored separately
|
|
2312
|
+
if (namespaced['widget-data-path']) {
|
|
2313
|
+
namespaced['widget-data-path'] = namespaceDataPath(namespaced['widget-data-path'], namespace);
|
|
2314
|
+
}
|
|
2315
|
+
// Recursively namespace nested widgets (for layout widgets)
|
|
2316
|
+
if (namespaced.widgets && Array.isArray(namespaced.widgets)) {
|
|
2317
|
+
namespaced.widgets = namespaced.widgets.map((widget) => namespaceWidgetConfig(widget, namespace));
|
|
2318
|
+
}
|
|
2319
|
+
// Namespace widget-item (for array/group widgets)
|
|
2320
|
+
if (namespaced['widget-item']) {
|
|
2321
|
+
namespaced['widget-item'] = namespaceWidgetConfig(namespaced['widget-item'], namespace);
|
|
2322
|
+
}
|
|
2323
|
+
// Namespace widget-data-columns (for table widgets)
|
|
2324
|
+
if (namespaced['widget-data-columns'] && Array.isArray(namespaced['widget-data-columns'])) {
|
|
2325
|
+
namespaced['widget-data-columns'] = namespaced['widget-data-columns'].map((column) => {
|
|
2326
|
+
const namespacedColumn = { ...column };
|
|
2327
|
+
// Namespace column data paths if they exist (columns only support string paths, not multi-path)
|
|
2328
|
+
if (namespacedColumn['widget-data-path'] && typeof namespacedColumn['widget-data-path'] === 'string') {
|
|
2329
|
+
namespacedColumn['widget-data-path'] = namespaceDataPath(namespacedColumn['widget-data-path'], namespace); // Safe cast since we checked it's a string
|
|
2330
|
+
}
|
|
2331
|
+
return namespacedColumn;
|
|
2332
|
+
});
|
|
2333
|
+
}
|
|
2334
|
+
return namespaced;
|
|
2335
|
+
};
|
|
2336
|
+
/**
|
|
2337
|
+
* Recursively namespace widget IDs in a panel configuration
|
|
2338
|
+
*/
|
|
2339
|
+
const namespacePanelConfig = (panel, namespace) => {
|
|
2340
|
+
const namespaced = { ...panel };
|
|
2341
|
+
// Recursively namespace nested panels
|
|
2342
|
+
if (namespaced.panels && Array.isArray(namespaced.panels)) {
|
|
2343
|
+
namespaced.panels = namespaced.panels.map((p) => namespacePanelConfig(p, namespace));
|
|
2344
|
+
}
|
|
2345
|
+
// Namespace widgets in panel
|
|
2346
|
+
if (namespaced.widgets && Array.isArray(namespaced.widgets)) {
|
|
2347
|
+
namespaced.widgets = namespaced.widgets.map((widget) => namespaceWidgetConfig(widget, namespace));
|
|
2348
|
+
}
|
|
2349
|
+
return namespaced;
|
|
2350
|
+
};
|
|
2351
|
+
/**
|
|
2352
|
+
* Namespace widget IDs and data paths in a section configuration
|
|
2353
|
+
* This ensures unique widget IDs and data paths when the same section is rendered multiple times
|
|
2354
|
+
* (e.g., in CRView mode showing old and new records side by side)
|
|
2355
|
+
*
|
|
2356
|
+
* @param section - Section configuration to namespace
|
|
2357
|
+
* @param namespace - Namespace prefix to add to widget IDs and data paths (e.g., "old", "new", "instance-1")
|
|
2358
|
+
* @returns Namespaced section configuration
|
|
2359
|
+
*/
|
|
2360
|
+
const namespaceSectionConfig = (section, namespace) => {
|
|
2361
|
+
const namespaced = { ...section };
|
|
2362
|
+
// Namespace the section-id as well to ensure uniqueness
|
|
2363
|
+
if (namespaced['section-id']) {
|
|
2364
|
+
namespaced['section-id'] = `${namespace}__${namespaced['section-id']}`;
|
|
2365
|
+
}
|
|
2366
|
+
// Recursively namespace panels
|
|
2367
|
+
if (namespaced.panels && Array.isArray(namespaced.panels)) {
|
|
2368
|
+
namespaced.panels = namespaced.panels.map((panel) => namespacePanelConfig(panel, namespace));
|
|
2369
|
+
}
|
|
2370
|
+
// Namespace supporting documents data paths
|
|
2371
|
+
if (namespaced['section-supporting-documents'] && Array.isArray(namespaced['section-supporting-documents'])) {
|
|
2372
|
+
namespaced['section-supporting-documents'] = namespaced['section-supporting-documents'].map((doc) => ({
|
|
2373
|
+
...doc,
|
|
2374
|
+
'document-data-path': doc['document-data-path']
|
|
2375
|
+
? `${namespace}.${doc['document-data-path']}`
|
|
2376
|
+
: doc['document-data-path'],
|
|
2377
|
+
}));
|
|
2378
|
+
}
|
|
2379
|
+
return namespaced;
|
|
2380
|
+
};
|
|
2381
|
+
|
|
2249
2382
|
/**
|
|
2250
2383
|
* Renders a section with its panels
|
|
2251
2384
|
*
|
|
@@ -2254,7 +2387,7 @@ const FileInputWidget = ({ config }) => {
|
|
|
2254
2387
|
* - Panels wrap when they exceed available width
|
|
2255
2388
|
* - Sections can sit side-by-side if there's space
|
|
2256
2389
|
*/
|
|
2257
|
-
const SectionRenderer = ({ section, apiAdapter, schemaData, onValueChange, gridColumnSpan, onSectionSave, hideEditButton = false, mode = 'RegistryView', }) => {
|
|
2390
|
+
const SectionRenderer = ({ section, apiAdapter, schemaData, onValueChange, gridColumnSpan, onSectionSave, hideEditButton = false, mode = 'RegistryView', namespace, }) => {
|
|
2258
2391
|
const { translateConfig, translate } = useWidgetTranslation();
|
|
2259
2392
|
const { schemaData: contextSchemaData } = useWidgetContext();
|
|
2260
2393
|
const store = useStore();
|
|
@@ -2262,6 +2395,49 @@ const SectionRenderer = ({ section, apiAdapter, schemaData, onValueChange, gridC
|
|
|
2262
2395
|
// Get CRView data from schemaData (prefer prop over context, then Redux store)
|
|
2263
2396
|
const currentSchemaData = schemaData || contextSchemaData || {};
|
|
2264
2397
|
const storeValues = useSelector((state) => state.widget?.values || {});
|
|
2398
|
+
// Namespace the section if namespace is provided
|
|
2399
|
+
// This ensures unique widget IDs when the same section is rendered multiple times
|
|
2400
|
+
const namespacedSection = useMemo(() => {
|
|
2401
|
+
if (namespace) {
|
|
2402
|
+
return namespaceSectionConfig(section, namespace);
|
|
2403
|
+
}
|
|
2404
|
+
return section;
|
|
2405
|
+
}, [section, namespace]);
|
|
2406
|
+
// Create namespaced schemaData if namespace is provided
|
|
2407
|
+
// This ensures widgets can read initial values from schemaData at namespaced paths
|
|
2408
|
+
const namespacedSchemaData = useMemo(() => {
|
|
2409
|
+
if (!namespace || !currentSchemaData) {
|
|
2410
|
+
return schemaData;
|
|
2411
|
+
}
|
|
2412
|
+
// Create a namespaced version of schemaData by copying values to namespaced paths
|
|
2413
|
+
const namespaced = { ...currentSchemaData };
|
|
2414
|
+
// Copy all top-level keys to namespaced paths
|
|
2415
|
+
Object.keys(currentSchemaData).forEach(key => {
|
|
2416
|
+
const namespacedKey = `${namespace}.${key}`;
|
|
2417
|
+
if (!(namespacedKey in namespaced)) {
|
|
2418
|
+
namespaced[namespacedKey] = currentSchemaData[key];
|
|
2419
|
+
}
|
|
2420
|
+
});
|
|
2421
|
+
// Also handle nested objects - copy nested values to namespaced paths
|
|
2422
|
+
const copyNestedValues = (obj, prefix = '') => {
|
|
2423
|
+
if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
|
|
2424
|
+
Object.keys(obj).forEach(key => {
|
|
2425
|
+
const fullPath = prefix ? `${prefix}.${key}` : key;
|
|
2426
|
+
const namespacedPath = `${namespace}.${fullPath}`;
|
|
2427
|
+
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
|
|
2428
|
+
copyNestedValues(obj[key], fullPath);
|
|
2429
|
+
// Also set the nested object at the namespaced path
|
|
2430
|
+
setValueByPath(namespaced, namespacedPath, obj[key]);
|
|
2431
|
+
}
|
|
2432
|
+
else {
|
|
2433
|
+
setValueByPath(namespaced, namespacedPath, obj[key]);
|
|
2434
|
+
}
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2437
|
+
};
|
|
2438
|
+
copyNestedValues(currentSchemaData);
|
|
2439
|
+
return namespaced;
|
|
2440
|
+
}, [namespace, schemaData, currentSchemaData]);
|
|
2265
2441
|
const crViewData = useMemo(() => {
|
|
2266
2442
|
if (mode !== 'CRView')
|
|
2267
2443
|
return null;
|
|
@@ -2280,11 +2456,14 @@ const SectionRenderer = ({ section, apiAdapter, schemaData, onValueChange, gridC
|
|
|
2280
2456
|
}
|
|
2281
2457
|
return result;
|
|
2282
2458
|
}, [mode, currentSchemaData, storeValues]);
|
|
2283
|
-
|
|
2459
|
+
// Use namespaced section for rendering
|
|
2460
|
+
const sectionToRender = namespacedSection;
|
|
2461
|
+
const sectionId = sectionToRender['section-id'];
|
|
2284
2462
|
const gridId = `section-panels-${sectionId}`;
|
|
2285
2463
|
const sectionClassId = `section-${sectionId}`;
|
|
2286
2464
|
// Recursively count all vertical panels, especially those nested inside horizontal panels
|
|
2287
2465
|
// Typically: horizontal panels at first level contain vertical panels at second level
|
|
2466
|
+
// Accounts for panel-column-span: a panel with column-span 3 counts as 3 columns
|
|
2288
2467
|
const countVerticalPanels = (panels) => {
|
|
2289
2468
|
let count = 0;
|
|
2290
2469
|
for (const panel of panels) {
|
|
@@ -2294,8 +2473,9 @@ const SectionRenderer = ({ section, apiAdapter, schemaData, onValueChange, gridC
|
|
|
2294
2473
|
count += countVerticalPanels(panel.panels);
|
|
2295
2474
|
}
|
|
2296
2475
|
else if (orientation === 'vertical') {
|
|
2297
|
-
// Count this vertical panel
|
|
2298
|
-
|
|
2476
|
+
// Count this vertical panel, accounting for column span
|
|
2477
|
+
const columnSpan = panel['panel-column-span'] || 1;
|
|
2478
|
+
count += columnSpan;
|
|
2299
2479
|
// Also recursively count vertical panels nested inside this vertical panel
|
|
2300
2480
|
if (panel.panels && panel.panels.length > 0) {
|
|
2301
2481
|
count += countVerticalPanels(panel.panels);
|
|
@@ -2342,9 +2522,9 @@ const SectionRenderer = ({ section, apiAdapter, schemaData, onValueChange, gridC
|
|
|
2342
2522
|
}
|
|
2343
2523
|
return null;
|
|
2344
2524
|
};
|
|
2345
|
-
const hasTableWidget = checkForTableWidget(
|
|
2346
|
-
const tableWidgetColumnSpan = getTableWidgetColumnSpan(
|
|
2347
|
-
const verticalPanelsCount = countVerticalPanels(
|
|
2525
|
+
const hasTableWidget = checkForTableWidget(sectionToRender.panels);
|
|
2526
|
+
const tableWidgetColumnSpan = getTableWidgetColumnSpan(sectionToRender.panels);
|
|
2527
|
+
const verticalPanelsCount = countVerticalPanels(sectionToRender.panels);
|
|
2348
2528
|
// If section contains a table widget with explicit column span, use it
|
|
2349
2529
|
// Otherwise, if it has a table widget, ensure it spans at least 2 columns
|
|
2350
2530
|
// Otherwise, use the vertical panel count
|
|
@@ -2354,7 +2534,7 @@ const SectionRenderer = ({ section, apiAdapter, schemaData, onValueChange, gridC
|
|
|
2354
2534
|
// Check if table widget has explicit column span (not default)
|
|
2355
2535
|
const hasExplicitTableSpan = tableWidgetColumnSpan !== null;
|
|
2356
2536
|
// Supporting documents configuration
|
|
2357
|
-
const supportingDocuments =
|
|
2537
|
+
const supportingDocuments = sectionToRender['section-supporting-documents'] || [];
|
|
2358
2538
|
const hasSupportingDocuments = supportingDocuments.length > 0;
|
|
2359
2539
|
// Edit mode state
|
|
2360
2540
|
const [isEditMode, setIsEditMode] = useState(false);
|
|
@@ -2392,7 +2572,7 @@ const SectionRenderer = ({ section, apiAdapter, schemaData, onValueChange, gridC
|
|
|
2392
2572
|
}, [isEditMode]);
|
|
2393
2573
|
// Recursively modify panels to set readonly based on edit mode
|
|
2394
2574
|
const makePanelsEditable = (panels, editable) => {
|
|
2395
|
-
const sectionEditable =
|
|
2575
|
+
const sectionEditable = sectionToRender['section-editable'] === true;
|
|
2396
2576
|
return panels.map(panel => {
|
|
2397
2577
|
const modifiedPanel = {
|
|
2398
2578
|
...panel,
|
|
@@ -2415,10 +2595,10 @@ const SectionRenderer = ({ section, apiAdapter, schemaData, onValueChange, gridC
|
|
|
2415
2595
|
const editableSection = useMemo(() => {
|
|
2416
2596
|
// Always apply readonly/editable state based on edit mode
|
|
2417
2597
|
return {
|
|
2418
|
-
...
|
|
2419
|
-
panels: makePanelsEditable(
|
|
2598
|
+
...sectionToRender,
|
|
2599
|
+
panels: makePanelsEditable(sectionToRender.panels, isEditMode),
|
|
2420
2600
|
};
|
|
2421
|
-
}, [
|
|
2601
|
+
}, [sectionToRender, isEditMode]);
|
|
2422
2602
|
// Handle edit button click
|
|
2423
2603
|
const handleEdit = () => {
|
|
2424
2604
|
// Capture height BEFORE entering edit mode to preserve space
|
|
@@ -2485,9 +2665,9 @@ const SectionRenderer = ({ section, apiAdapter, schemaData, onValueChange, gridC
|
|
|
2485
2665
|
width: `${editSectionPosition.width}px`,
|
|
2486
2666
|
maxHeight: '90vh',
|
|
2487
2667
|
overflowY: 'auto',
|
|
2488
|
-
}, children: [
|
|
2668
|
+
}, children: [sectionToRender['section-title'] && (jsxRuntimeExports.jsx("h2", { className: "text-xl font-semibold mb-4", style: { fontFamily: 'Roboto, sans-serif', marginTop: '35px' }, children: translateConfig(sectionToRender['section-title']) })), jsxRuntimeExports.jsxs("div", { id: editGridId, className: "section-panels", children: [editableSection.panels.map((panel, index) => {
|
|
2489
2669
|
const isLastPanel = index === editableSection.panels.length - 1;
|
|
2490
|
-
return (jsxRuntimeExports.jsx("div", { className: `panel-wrapper ${isLastPanel ? 'last-panel-wrapper' : ''}`, children: jsxRuntimeExports.jsx(PanelRenderer, { panel: panel, apiAdapter: apiAdapter, schemaData:
|
|
2670
|
+
return (jsxRuntimeExports.jsx("div", { className: `panel-wrapper ${isLastPanel ? 'last-panel-wrapper' : ''}`, children: jsxRuntimeExports.jsx(PanelRenderer, { panel: panel, apiAdapter: apiAdapter, schemaData: namespacedSchemaData, onValueChange: onValueChange, isEditMode: true }) }, panel['panel-id'] || `section-panel-${index}`));
|
|
2491
2671
|
}), hasSupportingDocuments && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("hr", { className: "my-4 w-full", style: { height: '1px', backgroundColor: '#F2BA1A', border: 'none' } }), jsxRuntimeExports.jsxs("div", { className: "supporting-documents-container", children: [jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => setIsDocumentsExpanded(!isDocumentsExpanded), className: "supporting-documents-title-button w-full flex items-center text-left", children: [jsxRuntimeExports.jsx("span", { className: "font-semibold", style: { fontFamily: 'Roboto, sans-serif', fontSize: '16px' }, children: translate('common.supportedDocuments') || 'Supported Documents' }), jsxRuntimeExports.jsx("svg", { className: `w-5 h-5 text-[#ED7C22] transition-transform ml-2 ${isDocumentsExpanded ? 'rotate-180' : ''}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsxRuntimeExports.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) })] }), isDocumentsExpanded && (jsxRuntimeExports.jsx("div", { className: "supporting-documents-grid mt-4", children: supportingDocuments.map((doc, index) => {
|
|
2492
2672
|
const docConfig = createDocumentWidgetConfig(doc, sectionId, index);
|
|
2493
2673
|
return (jsxRuntimeExports.jsx("div", { className: "supporting-document-item", children: jsxRuntimeExports.jsx(FileInputWidget, { config: docConfig }) }, `${sectionId}-doc-${index}`));
|
|
@@ -2505,27 +2685,62 @@ const SectionRenderer = ({ section, apiAdapter, schemaData, onValueChange, gridC
|
|
|
2505
2685
|
});
|
|
2506
2686
|
return widgets;
|
|
2507
2687
|
};
|
|
2508
|
-
const
|
|
2688
|
+
const trackSectionChages = (widgets, sourceData, useNamespacedPaths = false) => {
|
|
2509
2689
|
const snapshot = {};
|
|
2690
|
+
let hasTable = false;
|
|
2691
|
+
const recordId = Object.keys(sourceData)[0];
|
|
2510
2692
|
widgets.forEach(widget => {
|
|
2511
|
-
const
|
|
2512
|
-
if (!
|
|
2693
|
+
const originalDataPath = widget['widget-data-path'];
|
|
2694
|
+
if (!originalDataPath)
|
|
2513
2695
|
return;
|
|
2696
|
+
if (widget['widget-type'] === 'table' || widget['widget-type'] === 'simple-table') {
|
|
2697
|
+
hasTable = true;
|
|
2698
|
+
}
|
|
2699
|
+
// If namespace was used and we're reading from store, use namespaced paths
|
|
2700
|
+
useNamespacedPaths && namespace && originalDataPath
|
|
2701
|
+
? (typeof originalDataPath === 'string'
|
|
2702
|
+
? `${namespace}.${originalDataPath}`
|
|
2703
|
+
: Object.fromEntries(Object.entries(originalDataPath).map(([key, path]) => [key, `${namespace}.${path}`])))
|
|
2704
|
+
: originalDataPath;
|
|
2705
|
+
// Always store snapshot using original paths (for change tracking)
|
|
2514
2706
|
// Handle multi-path (object) or single path (string)
|
|
2515
|
-
if (typeof
|
|
2516
|
-
// Multi-path: store each path separately
|
|
2517
|
-
Object.entries(
|
|
2707
|
+
if (typeof originalDataPath === 'object') {
|
|
2708
|
+
// Multi-path: store each path separately using original paths
|
|
2709
|
+
Object.entries(originalDataPath).forEach(([key, path]) => {
|
|
2518
2710
|
if (typeof path === 'string') {
|
|
2519
|
-
|
|
2711
|
+
// Read from source using namespaced path if needed
|
|
2712
|
+
const readPath = useNamespacedPaths && namespace ? `${namespace}.${path}` : path;
|
|
2713
|
+
snapshot[path] = getValueByPath(sourceData, readPath);
|
|
2520
2714
|
}
|
|
2521
2715
|
});
|
|
2522
2716
|
}
|
|
2523
|
-
else if (typeof
|
|
2524
|
-
|
|
2717
|
+
else if (typeof originalDataPath === 'string') {
|
|
2718
|
+
// Read from source using namespaced path if needed
|
|
2719
|
+
const readPath = useNamespacedPaths && namespace ? `${namespace}.${originalDataPath}` : originalDataPath;
|
|
2720
|
+
snapshot[originalDataPath] = getValueByPath(sourceData, readPath);
|
|
2525
2721
|
}
|
|
2526
2722
|
});
|
|
2527
|
-
|
|
2723
|
+
if (hasTable === false) {
|
|
2724
|
+
const cleanedSnapshot = {};
|
|
2725
|
+
Object.entries(snapshot).forEach(([key, value]) => {
|
|
2726
|
+
const removedFirstLevelPath = key.includes('.')
|
|
2727
|
+
? key.split('.').slice(1).join('.')
|
|
2728
|
+
: key;
|
|
2729
|
+
cleanedSnapshot[removedFirstLevelPath] = value;
|
|
2730
|
+
});
|
|
2731
|
+
return [
|
|
2732
|
+
{ ...sourceData[recordId],
|
|
2733
|
+
...cleanedSnapshot,
|
|
2734
|
+
edit_action: "UPDATE"
|
|
2735
|
+
}
|
|
2736
|
+
];
|
|
2737
|
+
}
|
|
2738
|
+
const recordEntry = Object.entries(snapshot).find(([key, value]) => key.endsWith('.records') && Array.isArray(value));
|
|
2739
|
+
return recordEntry ? recordEntry[1] : snapshot;
|
|
2528
2740
|
};
|
|
2741
|
+
// Get original section (without namespace) for building snapshots
|
|
2742
|
+
// This ensures we use the original data paths when saving
|
|
2743
|
+
const originalSection = section;
|
|
2529
2744
|
// Handle save button click
|
|
2530
2745
|
const handleSave = async () => {
|
|
2531
2746
|
if (!store || !onSectionSave) {
|
|
@@ -2533,31 +2748,37 @@ const SectionRenderer = ({ section, apiAdapter, schemaData, onValueChange, gridC
|
|
|
2533
2748
|
setIsEditMode(false);
|
|
2534
2749
|
return;
|
|
2535
2750
|
}
|
|
2536
|
-
|
|
2751
|
+
// Use original section (without namespace) for collecting widgets
|
|
2752
|
+
// This ensures we use the original widget IDs and data paths
|
|
2753
|
+
const sectionWidgets = collectWidgets(originalSection.panels);
|
|
2537
2754
|
const currentState = store.getState().widget;
|
|
2538
2755
|
const currentSchemaData = currentState.values || {};
|
|
2756
|
+
// schema data before section change
|
|
2539
2757
|
const oldSchemaData = schemaData || contextSchemaData;
|
|
2540
|
-
|
|
2541
|
-
const
|
|
2758
|
+
// schema data after section change
|
|
2759
|
+
const newSchemaData = trackSectionChages(sectionWidgets, currentSchemaData);
|
|
2542
2760
|
// Include supporting documents in the snapshot if they exist
|
|
2761
|
+
const sectionFiles = [];
|
|
2543
2762
|
if (hasSupportingDocuments) {
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
const
|
|
2548
|
-
|
|
2549
|
-
|
|
2763
|
+
// Use original section's supporting documents to get original data paths
|
|
2764
|
+
const originalSupportingDocuments = originalSection['section-supporting-documents'] || [];
|
|
2765
|
+
originalSupportingDocuments.forEach((doc) => {
|
|
2766
|
+
const originalDataPath = doc['document-data-path'];
|
|
2767
|
+
// Read new value from store (with namespace if used)
|
|
2768
|
+
const storeDataPath = namespace && originalDataPath
|
|
2769
|
+
? `${namespace}.${originalDataPath}`
|
|
2770
|
+
: originalDataPath;
|
|
2771
|
+
sectionFiles.push(getValueByPath(currentSchemaData, storeDataPath));
|
|
2550
2772
|
});
|
|
2551
2773
|
}
|
|
2552
|
-
if (JSON.stringify(
|
|
2553
|
-
const changes = {
|
|
2554
|
-
section_id: sectionId,
|
|
2555
|
-
section_schema: section,
|
|
2556
|
-
old_section_value: oldSectionValue,
|
|
2557
|
-
new_section_value: newSectionValue,
|
|
2558
|
-
};
|
|
2774
|
+
if (JSON.stringify(oldSchemaData) !== JSON.stringify(newSchemaData)) {
|
|
2559
2775
|
try {
|
|
2560
|
-
|
|
2776
|
+
const sectionchanges = {
|
|
2777
|
+
section_id: originalSection['section-id'],
|
|
2778
|
+
records: [...newSchemaData],
|
|
2779
|
+
files: [...sectionFiles]
|
|
2780
|
+
};
|
|
2781
|
+
await onSectionSave(sectionchanges);
|
|
2561
2782
|
}
|
|
2562
2783
|
catch (error) {
|
|
2563
2784
|
console.error('Section Changes Save failed', error);
|
|
@@ -2568,40 +2789,58 @@ const SectionRenderer = ({ section, apiAdapter, schemaData, onValueChange, gridC
|
|
|
2568
2789
|
// Handle cancel button click
|
|
2569
2790
|
const handleCancel = () => {
|
|
2570
2791
|
// Revert values in store to original schema data
|
|
2571
|
-
|
|
2792
|
+
// Use original section (without namespace) for collecting widgets
|
|
2793
|
+
const sectionWidgets = collectWidgets(originalSection.panels);
|
|
2572
2794
|
const oldSchemaData = schemaData || contextSchemaData;
|
|
2573
2795
|
const currentStoreValues = store.getState().widget.values;
|
|
2574
2796
|
let newStoreValues = currentStoreValues;
|
|
2575
2797
|
sectionWidgets.forEach(widget => {
|
|
2576
|
-
const
|
|
2577
|
-
|
|
2578
|
-
|
|
2798
|
+
const originalWidgetId = widget['widget-id'];
|
|
2799
|
+
// If namespace was used, we need to use namespaced widget ID and data path
|
|
2800
|
+
const namespacedWidgetId = namespace ? `${namespace}__${originalWidgetId}` : originalWidgetId;
|
|
2801
|
+
const widgetId = namespacedWidgetId;
|
|
2802
|
+
const originalDataPath = widget['widget-data-path'];
|
|
2803
|
+
// If namespace was used, data path in store is namespaced, but we read from original schema using original path
|
|
2804
|
+
const storeDataPath = namespace && originalDataPath
|
|
2805
|
+
? (typeof originalDataPath === 'string'
|
|
2806
|
+
? `${namespace}.${originalDataPath}`
|
|
2807
|
+
: Object.fromEntries(Object.entries(originalDataPath).map(([key, path]) => [key, `${namespace}.${path}`])))
|
|
2808
|
+
: originalDataPath;
|
|
2809
|
+
if (widgetId && originalDataPath) {
|
|
2579
2810
|
// Handle multi-path (object) or single path (string)
|
|
2811
|
+
// Read from original schema data using original paths
|
|
2580
2812
|
let oldValue;
|
|
2581
|
-
if (typeof
|
|
2813
|
+
if (typeof originalDataPath === 'object') {
|
|
2582
2814
|
// Multi-path: get values for each path
|
|
2583
2815
|
oldValue = {};
|
|
2584
|
-
Object.entries(
|
|
2816
|
+
Object.entries(originalDataPath).forEach(([key, path]) => {
|
|
2585
2817
|
if (typeof path === 'string') {
|
|
2586
2818
|
oldValue[key] = getValueByPath(oldSchemaData, path);
|
|
2587
2819
|
}
|
|
2588
2820
|
});
|
|
2589
2821
|
}
|
|
2590
|
-
else if (typeof
|
|
2591
|
-
oldValue = getValueByPath(oldSchemaData,
|
|
2822
|
+
else if (typeof originalDataPath === 'string') {
|
|
2823
|
+
oldValue = getValueByPath(oldSchemaData, originalDataPath);
|
|
2592
2824
|
}
|
|
2825
|
+
// Set in store using namespaced data path (if namespace was used)
|
|
2593
2826
|
if (oldValue !== undefined) {
|
|
2594
|
-
newStoreValues = setWidgetValue(newStoreValues,
|
|
2827
|
+
newStoreValues = setWidgetValue(newStoreValues, storeDataPath, widgetId, oldValue);
|
|
2595
2828
|
}
|
|
2596
2829
|
}
|
|
2597
2830
|
});
|
|
2598
2831
|
// Also revert supporting documents if any
|
|
2599
2832
|
if (hasSupportingDocuments) {
|
|
2600
|
-
|
|
2833
|
+
// Use original section's supporting documents to get original data paths
|
|
2834
|
+
const originalSupportingDocuments = originalSection['section-supporting-documents'] || [];
|
|
2835
|
+
originalSupportingDocuments.forEach((doc, index) => {
|
|
2601
2836
|
const widgetId = `supporting-doc-${sectionId}-${index}`;
|
|
2602
|
-
const
|
|
2603
|
-
|
|
2604
|
-
|
|
2837
|
+
const originalDataPath = doc['document-data-path'];
|
|
2838
|
+
// If namespace was used, data path in store is namespaced
|
|
2839
|
+
const storeDataPath = namespace && originalDataPath
|
|
2840
|
+
? `${namespace}.${originalDataPath}`
|
|
2841
|
+
: originalDataPath;
|
|
2842
|
+
const oldValue = getValueByPath(oldSchemaData, originalDataPath);
|
|
2843
|
+
newStoreValues = setWidgetValue(newStoreValues, storeDataPath, widgetId, oldValue);
|
|
2605
2844
|
});
|
|
2606
2845
|
}
|
|
2607
2846
|
if (newStoreValues !== currentStoreValues) {
|
|
@@ -2616,11 +2855,13 @@ const SectionRenderer = ({ section, apiAdapter, schemaData, onValueChange, gridC
|
|
|
2616
2855
|
(documentType === 'image' ? 'image/*' :
|
|
2617
2856
|
documentType === 'pdf' ? '.pdf' :
|
|
2618
2857
|
'*/*');
|
|
2858
|
+
// Use the namespaced section ID for widget ID to ensure uniqueness
|
|
2859
|
+
const widgetId = `supporting-doc-${sectionId}-${index}`;
|
|
2619
2860
|
return {
|
|
2620
2861
|
widget: 'file',
|
|
2621
2862
|
'widget-type': 'input',
|
|
2622
2863
|
'widget-label': doc['document-label'] || doc['document-data-path'] || `Document ${index + 1}`,
|
|
2623
|
-
'widget-id':
|
|
2864
|
+
'widget-id': widgetId,
|
|
2624
2865
|
'widget-data-path': doc['document-data-path'],
|
|
2625
2866
|
'widget-required': doc['document-required'] || false,
|
|
2626
2867
|
'widget-readonly': false,
|
|
@@ -2776,7 +3017,7 @@ const SectionRenderer = ({ section, apiAdapter, schemaData, onValueChange, gridC
|
|
|
2776
3017
|
minHeight: 'auto',
|
|
2777
3018
|
height: 'auto'
|
|
2778
3019
|
}),
|
|
2779
|
-
}, children: [
|
|
3020
|
+
}, children: [sectionToRender['section-title'] && (jsxRuntimeExports.jsx("h2", { className: "text-xl font-semibold mb-4", style: { marginTop: '35px' }, children: translateConfig(sectionToRender['section-title']) })), jsxRuntimeExports.jsxs("div", { id: gridId, className: "section-panels", style: mode === 'RegistryView' && hideEditButton ? { paddingBottom: '40px' } : {}, children: [editableSection.panels.map((panel, index) => (jsxRuntimeExports.jsx("div", { className: "panel-wrapper", children: jsxRuntimeExports.jsx(PanelRenderer, { panel: panel, apiAdapter: apiAdapter, schemaData: namespacedSchemaData, onValueChange: onValueChange }) }, panel['panel-id'] || `section-panel-${index}`))), mode === 'CRView' && crViewData && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("hr", { className: "border-gray-300 w-full", style: { height: '1px', marginTop: '20px', marginBottom: '0px' } }), jsxRuntimeExports.jsxs("div", { className: "cr-view-container", style: {
|
|
2780
3021
|
marginTop: '20px',
|
|
2781
3022
|
paddingBottom: '30px',
|
|
2782
3023
|
display: 'flex',
|
|
@@ -2880,6 +3121,7 @@ const getTableWidgetColumnSpan = (panels) => {
|
|
|
2880
3121
|
/**
|
|
2881
3122
|
* Recursively count all vertical panels in a section
|
|
2882
3123
|
* Handles nested structure: horizontal panels containing vertical panels
|
|
3124
|
+
* Accounts for panel-column-span: a panel with column-span 3 counts as 3 columns
|
|
2883
3125
|
*/
|
|
2884
3126
|
const countVerticalPanels = (panels) => {
|
|
2885
3127
|
let count = 0;
|
|
@@ -2890,8 +3132,9 @@ const countVerticalPanels = (panels) => {
|
|
|
2890
3132
|
count += countVerticalPanels(panel.panels);
|
|
2891
3133
|
}
|
|
2892
3134
|
else if (orientation === 'vertical') {
|
|
2893
|
-
// Count this vertical panel
|
|
2894
|
-
|
|
3135
|
+
// Count this vertical panel, accounting for column span
|
|
3136
|
+
const columnSpan = panel['panel-column-span'] || 1;
|
|
3137
|
+
count += columnSpan;
|
|
2895
3138
|
// Also recursively count vertical panels nested inside this vertical panel
|
|
2896
3139
|
if (panel.panels && panel.panels.length > 0) {
|
|
2897
3140
|
count += countVerticalPanels(panel.panels);
|
|
@@ -2910,7 +3153,7 @@ const countVerticalPanels = (panels) => {
|
|
|
2910
3153
|
* - All sections align to the same grid, ensuring right-side alignment
|
|
2911
3154
|
* - Handles nested structure: multiple horizontal panels, each with multiple vertical panels
|
|
2912
3155
|
*/
|
|
2913
|
-
const SectionsContainer = ({ sections, apiAdapter, schemaData, onValueChange, className = '', onSectionSave, hideEditButton = false, mode = 'RegistryView', }) => {
|
|
3156
|
+
const SectionsContainer = ({ sections, apiAdapter, schemaData, onValueChange, className = '', onSectionSave, hideEditButton = false, mode = 'RegistryView', namespace, }) => {
|
|
2914
3157
|
// Find the maximum number of vertical panels across all sections
|
|
2915
3158
|
// This determines the grid size (minimum 3 columns)
|
|
2916
3159
|
// Also account for table widgets and their explicit column spans
|
|
@@ -2952,10 +3195,14 @@ const SectionsContainer = ({ sections, apiAdapter, schemaData, onValueChange, cl
|
|
|
2952
3195
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
2953
3196
|
}
|
|
2954
3197
|
}
|
|
2955
|
-
` }), jsxRuntimeExports.jsx("div", { id: containerId, className: `sections-container ${className}`, children: sections.map((section) => {
|
|
3198
|
+
` }), jsxRuntimeExports.jsx("div", { id: containerId, className: `sections-container ${className}`, children: sections.map((section, index) => {
|
|
3199
|
+
// Determine namespace for this section
|
|
3200
|
+
const sectionNamespace = namespace
|
|
3201
|
+
? (typeof namespace === 'string' ? namespace : namespace(section['section-id'], index))
|
|
3202
|
+
: undefined;
|
|
2956
3203
|
// Check if section has explicit column span
|
|
2957
3204
|
if (section['section-column-span']) {
|
|
2958
|
-
return (jsxRuntimeExports.jsx(SectionRenderer, { section: section, apiAdapter: apiAdapter, schemaData: schemaData, onValueChange: onValueChange, gridColumnSpan: section['section-column-span'], onSectionSave: onSectionSave, hideEditButton: hideEditButton, mode: mode }, section['section-id']));
|
|
3205
|
+
return (jsxRuntimeExports.jsx(SectionRenderer, { section: section, apiAdapter: apiAdapter, schemaData: schemaData, onValueChange: onValueChange, gridColumnSpan: section['section-column-span'], onSectionSave: onSectionSave, hideEditButton: hideEditButton, mode: mode, namespace: sectionNamespace }, section['section-id']));
|
|
2959
3206
|
}
|
|
2960
3207
|
const verticalPanelsCount = countVerticalPanels(section.panels);
|
|
2961
3208
|
const tableWidgetColumnSpan = getTableWidgetColumnSpan(section.panels);
|
|
@@ -2966,7 +3213,7 @@ const SectionsContainer = ({ sections, apiAdapter, schemaData, onValueChange, cl
|
|
|
2966
3213
|
const columnSpan = tableWidgetColumnSpan !== null
|
|
2967
3214
|
? tableWidgetColumnSpan
|
|
2968
3215
|
: (containsTable ? Math.max(verticalPanelsCount, 2) : verticalPanelsCount);
|
|
2969
|
-
return (jsxRuntimeExports.jsx(SectionRenderer, { section: section, apiAdapter: apiAdapter, schemaData: schemaData, onValueChange: onValueChange, gridColumnSpan: columnSpan, onSectionSave: onSectionSave, hideEditButton: hideEditButton, mode: mode }, section['section-id']));
|
|
3216
|
+
return (jsxRuntimeExports.jsx(SectionRenderer, { section: section, apiAdapter: apiAdapter, schemaData: schemaData, onValueChange: onValueChange, gridColumnSpan: columnSpan, onSectionSave: onSectionSave, hideEditButton: hideEditButton, mode: mode, namespace: sectionNamespace }, section['section-id']));
|
|
2970
3217
|
}) })] }));
|
|
2971
3218
|
};
|
|
2972
3219
|
|
|
@@ -4400,6 +4647,14 @@ const DateTimeInputWidget = ({ config }) => {
|
|
|
4400
4647
|
const SelectWidget = ({ config }) => {
|
|
4401
4648
|
const { value, error, touched, isEnabled, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
4402
4649
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
4650
|
+
// For readonly mode, render as display text showing only the selected label
|
|
4651
|
+
if (widgetConfig['widget-readonly']) {
|
|
4652
|
+
const label = translateConfig(widgetConfig['widget-label']);
|
|
4653
|
+
// Find the selected option's label
|
|
4654
|
+
const selectedOption = dataSourceOptions.find((option) => option.value === value);
|
|
4655
|
+
const displayValue = selectedOption ? selectedOption.label : (value || '-');
|
|
4656
|
+
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] SelectDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", children: displayValue }) })] }));
|
|
4657
|
+
}
|
|
4403
4658
|
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("select", { value: value || '', onChange: (e) => onChange(e.target.value), onBlur: onBlur, disabled: !isEnabled || loading || widgetConfig['widget-readonly'], className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${(touched && error.length > 0) || (widgetConfig['widget-required'] && (!value || value === ''))
|
|
4404
4659
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
4405
4660
|
: 'border-gray-300'} ${!isEnabled || loading || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']), children: [jsxRuntimeExports.jsx("option", { value: "", children: translate('common.select') }), dataSourceOptions.map((option) => (jsxRuntimeExports.jsx("option", { value: option.value, children: option.label }, option.value)))] }), loading && (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500 mt-1", children: translate('common.loadingOptions') })), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
@@ -4425,54 +4680,87 @@ const RadioWidget = ({ config }) => {
|
|
|
4425
4680
|
}
|
|
4426
4681
|
return options;
|
|
4427
4682
|
}, [dataSourceOptions, sortOptions]);
|
|
4428
|
-
// Handle
|
|
4429
|
-
|
|
4430
|
-
onChange(
|
|
4683
|
+
// Handle radio button change - simple and direct
|
|
4684
|
+
useCallback((selectedValue) => {
|
|
4685
|
+
onChange(selectedValue);
|
|
4431
4686
|
}, [onChange]);
|
|
4432
|
-
//
|
|
4433
|
-
const handleUnset = useCallback(() => {
|
|
4434
|
-
if (allowUnset) {
|
|
4435
|
-
onChange(null);
|
|
4436
|
-
}
|
|
4437
|
-
}, [allowUnset, onChange]);
|
|
4438
|
-
// Determine current value (handle null/undefined for optional fields)
|
|
4687
|
+
// Determine current value
|
|
4439
4688
|
const currentValue = useMemo(() => {
|
|
4689
|
+
console.log('[RadioWidget] Value from useBaseWidget:', {
|
|
4690
|
+
value,
|
|
4691
|
+
type: typeof value,
|
|
4692
|
+
widgetId: widgetConfig['widget-id'],
|
|
4693
|
+
dataPath: widgetConfig['widget-data-path']
|
|
4694
|
+
});
|
|
4440
4695
|
if (value === null || value === undefined) {
|
|
4441
4696
|
return null;
|
|
4442
4697
|
}
|
|
4443
4698
|
return value;
|
|
4444
|
-
}, [value]);
|
|
4445
|
-
//
|
|
4446
|
-
const
|
|
4699
|
+
}, [value, widgetConfig]);
|
|
4700
|
+
// Helper to safely compare values (handles type coercion)
|
|
4701
|
+
const isValueSelected = useCallback((optionValue) => {
|
|
4702
|
+
// Handle null/undefined cases
|
|
4703
|
+
if (currentValue === null || currentValue === undefined) {
|
|
4704
|
+
return optionValue === null || optionValue === undefined;
|
|
4705
|
+
}
|
|
4706
|
+
if (optionValue === null || optionValue === undefined) {
|
|
4707
|
+
return false;
|
|
4708
|
+
}
|
|
4709
|
+
// Convert both to strings and compare (handles any type mismatch)
|
|
4710
|
+
const currentStr = String(currentValue).trim();
|
|
4711
|
+
const optionStr = String(optionValue).trim();
|
|
4712
|
+
return currentStr === optionStr;
|
|
4713
|
+
}, [currentValue]);
|
|
4714
|
+
// Get layout classes
|
|
4715
|
+
const getLayoutClass = useMemo(() => {
|
|
4447
4716
|
switch (layout) {
|
|
4448
4717
|
case 'horizontal':
|
|
4449
|
-
return
|
|
4450
|
-
className: 'flex flex-row flex-wrap gap-4',
|
|
4451
|
-
style: undefined,
|
|
4452
|
-
};
|
|
4718
|
+
return 'flex flex-row flex-wrap gap-4';
|
|
4453
4719
|
case 'grid':
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
return {
|
|
4457
|
-
className: 'grid gap-3',
|
|
4458
|
-
style: { gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))` },
|
|
4459
|
-
};
|
|
4720
|
+
Math.max(2, Math.min(processedOptions.length, 4));
|
|
4721
|
+
return 'grid gap-3';
|
|
4460
4722
|
case 'vertical':
|
|
4461
4723
|
default:
|
|
4462
|
-
return
|
|
4463
|
-
className: 'flex flex-col space-y-2',
|
|
4464
|
-
style: undefined,
|
|
4465
|
-
};
|
|
4724
|
+
return 'flex flex-col space-y-2';
|
|
4466
4725
|
}
|
|
4467
4726
|
}, [layout, processedOptions.length]);
|
|
4727
|
+
const getLayoutStyle = useMemo(() => {
|
|
4728
|
+
if (layout === 'grid') {
|
|
4729
|
+
const cols = Math.max(2, Math.min(processedOptions.length, 4));
|
|
4730
|
+
return { gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))` };
|
|
4731
|
+
}
|
|
4732
|
+
return undefined;
|
|
4733
|
+
}, [layout, processedOptions.length]);
|
|
4468
4734
|
// For readonly mode, render as display text
|
|
4469
4735
|
if (widgetConfig['widget-readonly']) {
|
|
4470
4736
|
const label = translateConfig(widgetConfig['widget-label']);
|
|
4471
|
-
const selectedOption = processedOptions.find(opt => opt.value
|
|
4737
|
+
const selectedOption = processedOptions.find(opt => isValueSelected(opt.value));
|
|
4472
4738
|
const displayValue = selectedOption ? selectedOption.label : (allowUnset && currentValue === null ? '-' : '');
|
|
4473
4739
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] RadioDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", children: displayValue }) })] }));
|
|
4474
4740
|
}
|
|
4475
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("div", { className:
|
|
4741
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("div", { className: getLayoutClass, style: getLayoutStyle, children: loading ? (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500", children: translate('common.loading') })) : (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [allowUnset && (jsxRuntimeExports.jsxs("label", { className: `flex items-center cursor-pointer ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : ''}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: widgetConfig['widget-id'], checked: currentValue === null,
|
|
4742
|
+
// onChange={() => handleRadioChange(null)}
|
|
4743
|
+
disabled: !isEnabled || widgetConfig['widget-readonly'], className: "mr-2 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-700", children: "-" })] })), processedOptions.map((option, index) => {
|
|
4744
|
+
const isChecked = isValueSelected(option.value);
|
|
4745
|
+
// Debug log (remove after testing)
|
|
4746
|
+
if (index === 0) {
|
|
4747
|
+
console.log('[RadioWidget] Value check:', {
|
|
4748
|
+
currentValue,
|
|
4749
|
+
optionValue: option.value,
|
|
4750
|
+
currentType: typeof currentValue,
|
|
4751
|
+
optionType: typeof option.value,
|
|
4752
|
+
isChecked,
|
|
4753
|
+
currentStr: String(currentValue),
|
|
4754
|
+
optionStr: String(option.value)
|
|
4755
|
+
});
|
|
4756
|
+
}
|
|
4757
|
+
return (jsxRuntimeExports.jsxs("label", { className: `flex items-center cursor-pointer ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : ''}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: widgetConfig['widget-id'], value: String(option.value), checked: isChecked,
|
|
4758
|
+
// onChange={() => {
|
|
4759
|
+
// console.log('[RadioWidget] onChange called:', option.value);
|
|
4760
|
+
// handleRadioChange(option.value);
|
|
4761
|
+
// }}
|
|
4762
|
+
disabled: !isEnabled || widgetConfig['widget-readonly'], className: "mr-2 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-700", children: translateConfig(option.label) })] }, `${widgetConfig['widget-id']}-${option.value}-${index}`));
|
|
4763
|
+
})] })) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
4476
4764
|
};
|
|
4477
4765
|
|
|
4478
4766
|
const CheckboxWidget = ({ config }) => {
|
|
@@ -4800,6 +5088,17 @@ const TableCellSelect = ({ config, value, onValueChange }) => {
|
|
|
4800
5088
|
const isReadonly = config['widget-readonly'] || false;
|
|
4801
5089
|
return (jsxRuntimeExports.jsxs("select", { value: value || '', onChange: (e) => onValueChange(e.target.value), disabled: isReadonly || loading, className: `w-full h-[28px] px-2 text-sm border focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 ${isReadonly || loading ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'} border-gray-300`, style: { borderRadius: '10px' }, children: [jsxRuntimeExports.jsx("option", { value: "", children: translate('common.select') || 'Select' }), dataSourceOptions.map((option) => (jsxRuntimeExports.jsx("option", { value: option.value, children: option.label }, option.value)))] }));
|
|
4802
5090
|
};
|
|
5091
|
+
const SelectDisplayValue = ({ config, value }) => {
|
|
5092
|
+
const { dataSourceOptions, loading } = useBaseWidget({ config });
|
|
5093
|
+
if (loading) {
|
|
5094
|
+
return jsxRuntimeExports.jsx("span", { children: "-" });
|
|
5095
|
+
}
|
|
5096
|
+
if (value === null || value === undefined || value === '') {
|
|
5097
|
+
return jsxRuntimeExports.jsx("span", { children: "-" });
|
|
5098
|
+
}
|
|
5099
|
+
const selectedOption = dataSourceOptions.find((option) => option.value === value);
|
|
5100
|
+
return jsxRuntimeExports.jsx("span", { children: selectedOption ? selectedOption.label : String(value) });
|
|
5101
|
+
};
|
|
4803
5102
|
const TableCellText = ({ config, value, onValueChange }) => {
|
|
4804
5103
|
const isReadonly = config['widget-readonly'] || false;
|
|
4805
5104
|
const placeholder = config['widget-data-placeholder'] || '';
|
|
@@ -4836,6 +5135,7 @@ const TableWidget = ({ config }) => {
|
|
|
4836
5135
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
4837
5136
|
const { apiAdapter } = useWidgetContext();
|
|
4838
5137
|
const dispatch = useDispatch();
|
|
5138
|
+
const storeValues = useSelector((state) => state.widget?.values || {});
|
|
4839
5139
|
const rows = Array.isArray(value) ? value : [];
|
|
4840
5140
|
const columns = widgetConfig['widget-data-columns'] || [];
|
|
4841
5141
|
const operations = widgetConfig['widget-data-operations'] || {};
|
|
@@ -4847,10 +5147,13 @@ const TableWidget = ({ config }) => {
|
|
|
4847
5147
|
const [confirmationState, setConfirmationState] = useState(null);
|
|
4848
5148
|
const [isAdding, setIsAdding] = useState(false);
|
|
4849
5149
|
const [newRowData, setNewRowData] = useState(null);
|
|
4850
|
-
//
|
|
5150
|
+
// Track original rows when entering section edit mode for edit_action tracking
|
|
5151
|
+
const [originalRows, setOriginalRows] = useState(null);
|
|
5152
|
+
// When section is in edit mode (isReadonly is false), rows can be edited individually
|
|
5153
|
+
// But they are NOT automatically editable - user must click Edit button for each row
|
|
4851
5154
|
const isSectionEditMode = !isReadonly && operations.edit;
|
|
4852
5155
|
// Check if any row is being edited (either manually or via section edit mode)
|
|
4853
|
-
const isAnyRowEditing = editingState !== null || isAdding
|
|
5156
|
+
const isAnyRowEditing = editingState !== null || isAdding;
|
|
4854
5157
|
// Show confirmation dialog
|
|
4855
5158
|
const showConfirmation = useCallback((message, onConfirm, onCancel) => {
|
|
4856
5159
|
setConfirmationState({
|
|
@@ -4916,19 +5219,8 @@ const TableWidget = ({ config }) => {
|
|
|
4916
5219
|
}, [isAnyRowEditing, rows, showConfirmation, cancelEdit, translate]);
|
|
4917
5220
|
// Update cell value during edit
|
|
4918
5221
|
const updateCellValue = useCallback((columnKey, newValue, rowIndex) => {
|
|
4919
|
-
if (
|
|
4920
|
-
//
|
|
4921
|
-
const newRows = [...rows];
|
|
4922
|
-
if (!newRows[rowIndex]) {
|
|
4923
|
-
newRows[rowIndex] = {};
|
|
4924
|
-
}
|
|
4925
|
-
newRows[rowIndex] = {
|
|
4926
|
-
...newRows[rowIndex],
|
|
4927
|
-
[columnKey]: newValue,
|
|
4928
|
-
};
|
|
4929
|
-
onChange(newRows);
|
|
4930
|
-
}
|
|
4931
|
-
else if (editingState) {
|
|
5222
|
+
if (editingState && rowIndex !== undefined) {
|
|
5223
|
+
// Update editing state (works for both section edit mode and normal mode)
|
|
4932
5224
|
setEditingState({
|
|
4933
5225
|
...editingState,
|
|
4934
5226
|
currentValue: {
|
|
@@ -4936,6 +5228,9 @@ const TableWidget = ({ config }) => {
|
|
|
4936
5228
|
[columnKey]: newValue,
|
|
4937
5229
|
},
|
|
4938
5230
|
});
|
|
5231
|
+
// Also update Redux store for the cell widget
|
|
5232
|
+
const cellWidgetId = `${widgetConfig['widget-id']}-row-${rowIndex}-col-${columnKey}`;
|
|
5233
|
+
dispatch(setValue({ widgetId: cellWidgetId, value: newValue }));
|
|
4939
5234
|
}
|
|
4940
5235
|
else if (isAdding && newRowData) {
|
|
4941
5236
|
setNewRowData({
|
|
@@ -4943,7 +5238,7 @@ const TableWidget = ({ config }) => {
|
|
|
4943
5238
|
[columnKey]: newValue,
|
|
4944
5239
|
});
|
|
4945
5240
|
}
|
|
4946
|
-
}, [editingState, isAdding, newRowData,
|
|
5241
|
+
}, [editingState, isAdding, newRowData, widgetConfig, dispatch]);
|
|
4947
5242
|
// Save edited row
|
|
4948
5243
|
const saveEdit = useCallback(async () => {
|
|
4949
5244
|
if (!editingState)
|
|
@@ -4968,8 +5263,53 @@ const TableWidget = ({ config }) => {
|
|
|
4968
5263
|
}
|
|
4969
5264
|
// Update local state
|
|
4970
5265
|
const newRows = [...rows];
|
|
4971
|
-
newRows[rowIndex]
|
|
5266
|
+
const currentRow = newRows[rowIndex] || {};
|
|
5267
|
+
const wasDeleted = currentRow.edit_action === 'DELETE';
|
|
5268
|
+
// Determine edit_action (for color coding)
|
|
5269
|
+
let editAction = currentRow.edit_action;
|
|
5270
|
+
if (isSectionEditMode) {
|
|
5271
|
+
// If row was deleted but is being saved, un-delete it
|
|
5272
|
+
if (wasDeleted) {
|
|
5273
|
+
// Check if this row exists in original rows
|
|
5274
|
+
if (originalRows) {
|
|
5275
|
+
const rowId = rowData.id;
|
|
5276
|
+
const existsInOriginal = rowId !== undefined
|
|
5277
|
+
? originalRows.some(or => or.id === rowId)
|
|
5278
|
+
: rowIndex < originalRows.length;
|
|
5279
|
+
editAction = existsInOriginal ? 'UPDATE' : 'ADD';
|
|
5280
|
+
}
|
|
5281
|
+
else {
|
|
5282
|
+
editAction = 'UPDATE';
|
|
5283
|
+
}
|
|
5284
|
+
}
|
|
5285
|
+
else if (!editAction && originalRows) {
|
|
5286
|
+
// Check if this row exists in original rows
|
|
5287
|
+
const rowId = rowData.id;
|
|
5288
|
+
const existsInOriginal = rowId !== undefined
|
|
5289
|
+
? originalRows.some(or => or.id === rowId)
|
|
5290
|
+
: rowIndex < originalRows.length;
|
|
5291
|
+
editAction = existsInOriginal ? 'UPDATE' : 'ADD';
|
|
5292
|
+
}
|
|
5293
|
+
else if (!editAction) {
|
|
5294
|
+
editAction = 'UPDATE';
|
|
5295
|
+
}
|
|
5296
|
+
}
|
|
5297
|
+
else {
|
|
5298
|
+
// In non-section edit mode, mark as UPDATE if not already set
|
|
5299
|
+
if (!editAction && !wasDeleted) {
|
|
5300
|
+
editAction = 'UPDATE';
|
|
5301
|
+
}
|
|
5302
|
+
}
|
|
5303
|
+
newRows[rowIndex] = {
|
|
5304
|
+
...rowData,
|
|
5305
|
+
...(editAction ? { edit_action: editAction } : {}),
|
|
5306
|
+
};
|
|
4972
5307
|
onChange(newRows);
|
|
5308
|
+
// Clear editing state and reset widget values in Redux
|
|
5309
|
+
columns.forEach((col) => {
|
|
5310
|
+
const cellWidgetId = `${widgetConfig['widget-id']}-row-${rowIndex}-col-${col['column-key']}`;
|
|
5311
|
+
dispatch(resetWidget(cellWidgetId));
|
|
5312
|
+
});
|
|
4973
5313
|
setEditingState(null);
|
|
4974
5314
|
}
|
|
4975
5315
|
catch (error) {
|
|
@@ -4980,7 +5320,7 @@ const TableWidget = ({ config }) => {
|
|
|
4980
5320
|
finally {
|
|
4981
5321
|
setLoadingRowIndex(null);
|
|
4982
5322
|
}
|
|
4983
|
-
}, [editingState, rows, onChange, apiAdapter, apiConfig, translate]);
|
|
5323
|
+
}, [editingState, rows, onChange, apiAdapter, apiConfig, translate, isSectionEditMode, originalRows, columns, widgetConfig, dispatch]);
|
|
4984
5324
|
// Add new row
|
|
4985
5325
|
const startAdd = useCallback(() => {
|
|
4986
5326
|
// If there's an unsaved edit, cancel it first (no confirmation needed)
|
|
@@ -5014,6 +5354,8 @@ const TableWidget = ({ config }) => {
|
|
|
5014
5354
|
savedRow = { ...savedRow, ...response };
|
|
5015
5355
|
}
|
|
5016
5356
|
}
|
|
5357
|
+
// Mark new row with edit_action: 'ADD' (for color coding)
|
|
5358
|
+
savedRow = { ...savedRow, edit_action: 'ADD' };
|
|
5017
5359
|
// Add to local state
|
|
5018
5360
|
onChange([...rows, savedRow]);
|
|
5019
5361
|
setIsAdding(false);
|
|
@@ -5026,7 +5368,7 @@ const TableWidget = ({ config }) => {
|
|
|
5026
5368
|
finally {
|
|
5027
5369
|
setLoadingRowIndex(null);
|
|
5028
5370
|
}
|
|
5029
|
-
}, [isAdding, newRowData, rows, onChange, apiAdapter, apiConfig, translate]);
|
|
5371
|
+
}, [isAdding, newRowData, rows, onChange, apiAdapter, apiConfig, translate, isSectionEditMode]);
|
|
5030
5372
|
// Delete row
|
|
5031
5373
|
const deleteRow = useCallback(async (rowIndex) => {
|
|
5032
5374
|
if (isAnyRowEditing) {
|
|
@@ -5058,9 +5400,20 @@ const TableWidget = ({ config }) => {
|
|
|
5058
5400
|
headers: deleteConfig.headers || {},
|
|
5059
5401
|
});
|
|
5060
5402
|
}
|
|
5061
|
-
//
|
|
5062
|
-
|
|
5063
|
-
|
|
5403
|
+
// In section edit mode, mark row as deleted instead of removing it
|
|
5404
|
+
if (isSectionEditMode) {
|
|
5405
|
+
const newRows = [...rows];
|
|
5406
|
+
newRows[rowIndex] = {
|
|
5407
|
+
...newRows[rowIndex],
|
|
5408
|
+
edit_action: 'DELETE',
|
|
5409
|
+
};
|
|
5410
|
+
onChange(newRows);
|
|
5411
|
+
}
|
|
5412
|
+
else {
|
|
5413
|
+
// Remove from local state (non-section edit mode)
|
|
5414
|
+
const newRows = rows.filter((_, i) => i !== rowIndex);
|
|
5415
|
+
onChange(newRows);
|
|
5416
|
+
}
|
|
5064
5417
|
}
|
|
5065
5418
|
catch (error) {
|
|
5066
5419
|
console.error('Error deleting record:', error);
|
|
@@ -5069,28 +5422,37 @@ const TableWidget = ({ config }) => {
|
|
|
5069
5422
|
finally {
|
|
5070
5423
|
setLoadingRowIndex(null);
|
|
5071
5424
|
}
|
|
5072
|
-
}, [rows, onChange, apiAdapter, apiConfig, translate]);
|
|
5425
|
+
}, [rows, onChange, apiAdapter, apiConfig, translate, isSectionEditMode]);
|
|
5073
5426
|
// Get cell value (from editing state or row data)
|
|
5074
5427
|
const getCellValue = useCallback((rowIndex, columnKey) => {
|
|
5075
|
-
// When
|
|
5076
|
-
if (isSectionEditMode) {
|
|
5077
|
-
return rows[rowIndex]?.[columnKey];
|
|
5078
|
-
}
|
|
5428
|
+
// When a specific row is being edited (either in section edit mode or normal mode)
|
|
5079
5429
|
if (editingState && editingState.rowIndex === rowIndex) {
|
|
5430
|
+
// Check Redux store first for most up-to-date value
|
|
5431
|
+
const cellWidgetId = `${widgetConfig['widget-id']}-row-${rowIndex}-col-${columnKey}`;
|
|
5432
|
+
const storeValue = storeValues[cellWidgetId];
|
|
5433
|
+
if (storeValue !== undefined) {
|
|
5434
|
+
return storeValue;
|
|
5435
|
+
}
|
|
5080
5436
|
return editingState.currentValue[columnKey];
|
|
5081
5437
|
}
|
|
5082
5438
|
if (isAdding && rowIndex === rows.length) {
|
|
5083
5439
|
return newRowData?.[columnKey];
|
|
5084
5440
|
}
|
|
5085
5441
|
return rows[rowIndex]?.[columnKey];
|
|
5086
|
-
}, [editingState, isAdding, rows, newRowData,
|
|
5442
|
+
}, [editingState, isAdding, rows, newRowData, widgetConfig, storeValues]);
|
|
5087
5443
|
// Get formatted display value for a cell
|
|
5088
5444
|
const getDisplayValue = useCallback((rowIndex, column) => {
|
|
5089
5445
|
const columnKey = column['column-key'];
|
|
5090
5446
|
const cellValue = getCellValue(rowIndex, columnKey);
|
|
5447
|
+
const widgetType = column.widget || 'text';
|
|
5091
5448
|
if (cellValue === null || cellValue === undefined || cellValue === '') {
|
|
5092
5449
|
return '-';
|
|
5093
5450
|
}
|
|
5451
|
+
// For select widgets, we'll use SelectDisplayValue component instead
|
|
5452
|
+
// This function is kept for other widget types
|
|
5453
|
+
if (widgetType === 'select') {
|
|
5454
|
+
return null; // Will be handled by SelectDisplayValue component
|
|
5455
|
+
}
|
|
5094
5456
|
// Use formatValue if format config exists
|
|
5095
5457
|
if (column['widget-data-format']) {
|
|
5096
5458
|
return formatValue(cellValue, column['widget-data-format'], column.widget);
|
|
@@ -5098,29 +5460,22 @@ const TableWidget = ({ config }) => {
|
|
|
5098
5460
|
return cellValue?.toString() || '-';
|
|
5099
5461
|
}, [getCellValue]);
|
|
5100
5462
|
// Check if row is being edited
|
|
5101
|
-
//
|
|
5463
|
+
// In section edit mode, only the row with active editingState is editable
|
|
5102
5464
|
const isRowEditing = useCallback((rowIndex) => {
|
|
5103
|
-
if (isSectionEditMode) {
|
|
5104
|
-
return true; // All rows are editable when section is in edit mode
|
|
5105
|
-
}
|
|
5106
5465
|
return editingState?.rowIndex === rowIndex || (isAdding && rowIndex === rows.length);
|
|
5107
|
-
}, [editingState, isAdding, rows.length
|
|
5108
|
-
//
|
|
5466
|
+
}, [editingState, isAdding, rows.length]);
|
|
5467
|
+
// Store original rows when entering section edit mode (for edit_action tracking)
|
|
5109
5468
|
useEffect(() => {
|
|
5110
|
-
if (isSectionEditMode) {
|
|
5111
|
-
//
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
const cellWidgetId = `${widgetConfig['widget-id']}-row-${rowIndex}-col-${columnKey}`;
|
|
5116
|
-
const cellValue = row[columnKey];
|
|
5117
|
-
const defaultValue = cellValue !== undefined ? cellValue : (col['widget-data-default'] ?? '');
|
|
5118
|
-
// Set value in Redux store
|
|
5119
|
-
dispatch(setValue({ widgetId: cellWidgetId, value: defaultValue }));
|
|
5120
|
-
});
|
|
5121
|
-
});
|
|
5469
|
+
if (isSectionEditMode && originalRows === null) {
|
|
5470
|
+
setOriginalRows(JSON.parse(JSON.stringify(rows))); // Deep clone
|
|
5471
|
+
}
|
|
5472
|
+
else if (!isSectionEditMode && originalRows !== null) {
|
|
5473
|
+
setOriginalRows(null);
|
|
5122
5474
|
}
|
|
5123
|
-
|
|
5475
|
+
}, [isSectionEditMode, rows, originalRows]);
|
|
5476
|
+
// Set cell widget value in Redux when entering edit mode for a specific row
|
|
5477
|
+
useEffect(() => {
|
|
5478
|
+
if (editingState) {
|
|
5124
5479
|
columns.forEach((col) => {
|
|
5125
5480
|
const columnKey = col['column-key'];
|
|
5126
5481
|
const cellWidgetId = `${widgetConfig['widget-id']}-row-${editingState.rowIndex}-col-${columnKey}`;
|
|
@@ -5130,7 +5485,7 @@ const TableWidget = ({ config }) => {
|
|
|
5130
5485
|
dispatch(setValue({ widgetId: cellWidgetId, value: defaultValue }));
|
|
5131
5486
|
});
|
|
5132
5487
|
}
|
|
5133
|
-
}, [
|
|
5488
|
+
}, [editingState, columns, widgetConfig, dispatch]);
|
|
5134
5489
|
useEffect(() => {
|
|
5135
5490
|
if (isAdding && newRowData) {
|
|
5136
5491
|
columns.forEach((col) => {
|
|
@@ -5177,18 +5532,48 @@ const TableWidget = ({ config }) => {
|
|
|
5177
5532
|
} }) }));
|
|
5178
5533
|
}, [widgetConfig, updateCellValue]);
|
|
5179
5534
|
// Render cell content (widget in edit mode, formatted value in view mode)
|
|
5180
|
-
const renderCell = useCallback((rowIndex, column) => {
|
|
5535
|
+
const renderCell = useCallback((rowIndex, column, row) => {
|
|
5181
5536
|
const columnKey = column['column-key'];
|
|
5182
5537
|
const isEditing = isRowEditing(rowIndex);
|
|
5183
5538
|
const cellValue = getCellValue(rowIndex, columnKey);
|
|
5184
5539
|
const columnReadonly = column['widget-readonly'] === true;
|
|
5540
|
+
// Get color styling based on edit_action
|
|
5541
|
+
const getCellStyle = () => {
|
|
5542
|
+
if (isEditing)
|
|
5543
|
+
return {}; // No special styling when editing
|
|
5544
|
+
const editAction = row?.edit_action;
|
|
5545
|
+
if (editAction === 'ADD') {
|
|
5546
|
+
return { color: '#16a34a' }; // green-600
|
|
5547
|
+
}
|
|
5548
|
+
else if (editAction === 'DELETE') {
|
|
5549
|
+
return { color: '#dc2626', textDecoration: 'line-through' }; // red-600 with strikethrough
|
|
5550
|
+
}
|
|
5551
|
+
else if (editAction === 'UPDATE') {
|
|
5552
|
+
return { color: '#ea580c' }; // orange-600
|
|
5553
|
+
}
|
|
5554
|
+
return {};
|
|
5555
|
+
};
|
|
5185
5556
|
if (isEditing) {
|
|
5186
5557
|
// Use lightweight cell renderer
|
|
5187
5558
|
return renderTableCell(rowIndex, column, cellValue, columnReadonly);
|
|
5188
5559
|
}
|
|
5189
5560
|
else {
|
|
5190
|
-
// Display formatted value in view mode
|
|
5191
|
-
|
|
5561
|
+
// Display formatted value in view mode with color styling
|
|
5562
|
+
const widgetType = column.widget || 'text';
|
|
5563
|
+
const displayValue = getDisplayValue(rowIndex, column);
|
|
5564
|
+
// For select widgets, use SelectDisplayValue component to show label
|
|
5565
|
+
if (widgetType === 'select' && displayValue === null) {
|
|
5566
|
+
const cellWidgetId = `${widgetConfig['widget-id']}-row-${rowIndex}-col-${columnKey}`;
|
|
5567
|
+
const cellConfig = {
|
|
5568
|
+
...column,
|
|
5569
|
+
'widget-id': cellWidgetId,
|
|
5570
|
+
'widget-label': '',
|
|
5571
|
+
'widget-readonly': true,
|
|
5572
|
+
'widget-data-path': undefined,
|
|
5573
|
+
};
|
|
5574
|
+
return (jsxRuntimeExports.jsx("div", { className: "text-sm", style: getCellStyle(), children: jsxRuntimeExports.jsx(SelectDisplayValue, { config: cellConfig, value: cellValue }) }));
|
|
5575
|
+
}
|
|
5576
|
+
return (jsxRuntimeExports.jsx("div", { className: "text-sm", style: getCellStyle(), children: displayValue }));
|
|
5192
5577
|
}
|
|
5193
5578
|
}, [isRowEditing, getCellValue, getDisplayValue, renderTableCell]);
|
|
5194
5579
|
const tableWidgetId = `table-widget-${widgetConfig['widget-id']}`;
|
|
@@ -5248,11 +5633,13 @@ const TableWidget = ({ config }) => {
|
|
|
5248
5633
|
.${tableWidgetId} button {
|
|
5249
5634
|
border-radius: 10px !important;
|
|
5250
5635
|
}
|
|
5251
|
-
` }), jsxRuntimeExports.jsxs("div", { className: `table-widget-container ${tableWidgetId}`, children: [confirmationState?.show && (jsxRuntimeExports.jsx("div", { className: "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50", children: jsxRuntimeExports.jsxs("div", { className: "bg-white rounded-lg p-6 max-w-md w-full mx-4", children: [jsxRuntimeExports.jsx("h3", { className: "text-lg font-semibold mb-4", children: translate('table.confirm') || 'Confirm Action' }), jsxRuntimeExports.jsx("p", { className: "text-gray-700 mb-6", children: confirmationState.message }), jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-3", children: [jsxRuntimeExports.jsx("button", { onClick: confirmationState.onCancel, className: "px-4 py-2 text-sm font-medium text-gray-700 bg-gray-200 hover:bg-gray-300", style: { borderRadius: '15px' }, children: translate('common.cancel') || 'Cancel' }), jsxRuntimeExports.jsx("button", { onClick: confirmationState.onConfirm, className: "px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700", style: { borderRadius: '15px' }, children: translate('table.discard') || 'Discard & Continue' })] })] }) })), operations.add && !isReadonly && isEnabled && (jsxRuntimeExports.jsx("div", { className: "flex justify-end mb-2", children: jsxRuntimeExports.jsx("button", { type: "button", onClick: startAdd, disabled: loadingRowIndex !== null, className: "px-3 py-1 text-sm bg-blue-500 text-white hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed", style: { borderRadius: '15px' }, children: translate('table.addRecord') || 'Add New Record' }) })), rows.length === 0 && !isAdding ? (jsxRuntimeExports.jsxs("div", { className: "text-gray-500 text-sm py-4 text-center border border-gray-300", style: { borderRadius: '15px' }, children: [translate('table.noData') || 'No records available.', operations.add && !isReadonly && ` ${translate('table.clickToAdd') || 'Click "Add New Record" to add one.'}`] })) : (jsxRuntimeExports.jsx("div", { className: "overflow-x-auto border border-gray-300", style: { borderRadius: '15px' }, children: jsxRuntimeExports.jsxs("table", { className: "min-w-full divide-y divide-gray-200", children: [jsxRuntimeExports.jsx("thead", { className: "bg-gray-50", children: jsxRuntimeExports.jsxs("tr", { children: [columns.map((col) => (jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: translateConfig(col['widget-label']) }, col['column-key']))), ((operations.edit || operations.remove) && !isReadonly) || isAnyRowEditing ? (jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: translate('common.actions') || 'Actions' })) : null] }) }), jsxRuntimeExports.jsxs("tbody", { className: "bg-white divide-y divide-gray-200", children: [rows.map((row, rowIndex) => {
|
|
5636
|
+
` }), jsxRuntimeExports.jsxs("div", { className: `table-widget-container ${tableWidgetId}`, children: [confirmationState?.show && (jsxRuntimeExports.jsx("div", { className: "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50", children: jsxRuntimeExports.jsxs("div", { className: "bg-white rounded-lg p-6 max-w-md w-full mx-4", children: [jsxRuntimeExports.jsx("h3", { className: "text-lg font-semibold mb-4", children: translate('table.confirm') || 'Confirm Action' }), jsxRuntimeExports.jsx("p", { className: "text-gray-700 mb-6", children: confirmationState.message }), jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-3", children: [jsxRuntimeExports.jsx("button", { onClick: confirmationState.onCancel, className: "px-4 py-2 text-sm font-medium text-gray-700 bg-gray-200 hover:bg-gray-300", style: { borderRadius: '15px' }, children: translate('common.cancel') || 'Cancel' }), jsxRuntimeExports.jsx("button", { onClick: confirmationState.onConfirm, className: "px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700", style: { borderRadius: '15px' }, children: translate('table.discard') || 'Discard & Continue' })] })] }) })), operations.add && !isReadonly && isEnabled && (isSectionEditMode || !isAnyRowEditing) && (jsxRuntimeExports.jsx("div", { className: "flex justify-end mb-2", children: jsxRuntimeExports.jsx("button", { type: "button", onClick: startAdd, disabled: loadingRowIndex !== null, className: "px-3 py-1 text-sm bg-blue-500 text-white hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed", style: { borderRadius: '15px' }, children: translate('table.addRecord') || 'Add New Record' }) })), rows.length === 0 && !isAdding ? (jsxRuntimeExports.jsxs("div", { className: "text-gray-500 text-sm py-4 text-center border border-gray-300", style: { borderRadius: '15px' }, children: [translate('table.noData') || 'No records available.', operations.add && !isReadonly && ` ${translate('table.clickToAdd') || 'Click "Add New Record" to add one.'}`] })) : (jsxRuntimeExports.jsx("div", { className: "overflow-x-auto border border-gray-300", style: { borderRadius: '15px' }, children: jsxRuntimeExports.jsxs("table", { className: "min-w-full divide-y divide-gray-200", children: [jsxRuntimeExports.jsx("thead", { className: "bg-gray-50", children: jsxRuntimeExports.jsxs("tr", { children: [columns.map((col) => (jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: translateConfig(col['widget-label']) }, col['column-key']))), ((operations.edit || operations.remove) && !isReadonly) || isAnyRowEditing || isSectionEditMode ? (jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: translate('common.actions') || 'Actions' })) : null] }) }), jsxRuntimeExports.jsxs("tbody", { className: "bg-white divide-y divide-gray-200", children: [rows.map((row, rowIndex) => {
|
|
5252
5637
|
const isEditing = isRowEditing(rowIndex);
|
|
5253
5638
|
const isLoading = loadingRowIndex === rowIndex;
|
|
5254
|
-
return (jsxRuntimeExports.jsxs("tr", { className: isEditing ? 'bg-blue-50' : isLoading ? 'opacity-50' :
|
|
5255
|
-
|
|
5639
|
+
return (jsxRuntimeExports.jsxs("tr", { className: isEditing ? 'bg-blue-50' : isLoading ? 'opacity-50' : row.edit_action === 'DELETE' ? 'bg-red-50' : '', children: [columns.map((col) => {
|
|
5640
|
+
return (jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", children: renderCell(rowIndex, col, row) }, col['column-key']));
|
|
5641
|
+
}), ((operations.edit || operations.remove) && !isReadonly) || isEditing || isSectionEditMode ? (jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", style: { minWidth: '120px' }, children: isEditing ? (
|
|
5642
|
+
// Show OK (Save)/Cancel buttons when row is being edited (works in both section edit mode and normal mode)
|
|
5256
5643
|
jsxRuntimeExports.jsxs("div", { className: "flex flex-row gap-2 items-center", style: { width: '100%' }, children: [jsxRuntimeExports.jsx("button", { type: "button", onClick: saveEdit, disabled: isLoading, className: "px-3 py-1 text-xs font-medium hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap flex-shrink-0", style: {
|
|
5257
5644
|
display: 'inline-block',
|
|
5258
5645
|
minWidth: '60px',
|
|
@@ -5260,12 +5647,10 @@ const TableWidget = ({ config }) => {
|
|
|
5260
5647
|
color: '#ffffff', // white text
|
|
5261
5648
|
border: 'none',
|
|
5262
5649
|
borderRadius: '15px'
|
|
5263
|
-
}, children:
|
|
5264
|
-
// Show Edit/Delete buttons
|
|
5265
|
-
jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [operations.edit && (jsxRuntimeExports.jsx("button", { type: "button", onClick: () => startEdit(rowIndex), disabled: isAnyRowEditing || isLoading, className: "px-3 py-1 text-xs text-blue-600 hover:text-blue-800 hover:bg-blue-50 disabled:opacity-50 disabled:cursor-not-allowed", style: { borderRadius: '15px' }, children: translate('common.edit') || 'Edit' })), operations.remove && (jsxRuntimeExports.jsx("button", { type: "button", onClick: () => deleteRow(rowIndex), disabled: isAnyRowEditing || isLoading, className: "px-3 py-1 text-xs text-red-600 hover:text-red-800 hover:bg-red-50 disabled:opacity-50 disabled:cursor-not-allowed", style: { borderRadius: '15px' }, children: translate('common.remove') || 'Delete' }))] })) :
|
|
5266
|
-
|
|
5267
|
-
operations.remove && (jsxRuntimeExports.jsx("div", { className: "flex gap-2", children: jsxRuntimeExports.jsx("button", { type: "button", onClick: () => deleteRow(rowIndex), disabled: isLoading, className: "px-3 py-1 text-xs text-red-600 hover:text-red-800 hover:bg-red-50 disabled:opacity-50 disabled:cursor-not-allowed", style: { borderRadius: '15px' }, children: translate('common.remove') || 'Delete' }) }))) })) : null] }, rowIndex));
|
|
5268
|
-
}), isAdding && newRowData && (jsxRuntimeExports.jsxs("tr", { className: "bg-blue-50", children: [columns.map((col) => (jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", children: renderCell(rows.length, col) }, col['column-key']))), jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", children: jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [jsxRuntimeExports.jsx("button", { type: "button", onClick: saveAdd, disabled: loadingRowIndex === -1, className: "px-3 py-1 text-xs bg-green-600 text-white hover:bg-green-700 disabled:opacity-50", style: { borderRadius: '15px' }, children: translate('common.save') || 'Save' }), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => {
|
|
5650
|
+
}, children: translate('common.ok') || 'OK' }), jsxRuntimeExports.jsx("button", { type: "button", onClick: cancelEdit, disabled: isLoading, className: "px-3 py-1 text-xs font-medium bg-gray-200 text-gray-700 hover:bg-gray-300 disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap flex-shrink-0", style: { display: 'inline-block', minWidth: '60px', borderRadius: '15px' }, children: translate('common.cancel') || 'Cancel' })] })) : (
|
|
5651
|
+
// Show Edit/Delete buttons when row is not being edited
|
|
5652
|
+
jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [operations.edit && (jsxRuntimeExports.jsx("button", { type: "button", onClick: () => startEdit(rowIndex), disabled: isAnyRowEditing || isLoading, className: "px-3 py-1 text-xs text-blue-600 hover:text-blue-800 hover:bg-blue-50 disabled:opacity-50 disabled:cursor-not-allowed", style: { borderRadius: '15px' }, children: translate('common.edit') || 'Edit' })), operations.remove && (jsxRuntimeExports.jsx("button", { type: "button", onClick: () => deleteRow(rowIndex), disabled: isAnyRowEditing || isLoading, className: "px-3 py-1 text-xs text-red-600 hover:text-red-800 hover:bg-red-50 disabled:opacity-50 disabled:cursor-not-allowed", style: { borderRadius: '15px' }, children: translate('common.remove') || 'Delete' }))] })) })) : null] }, rowIndex));
|
|
5653
|
+
}), isAdding && newRowData && (jsxRuntimeExports.jsxs("tr", { className: "bg-blue-50", children: [columns.map((col) => (jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", children: renderCell(rows.length, col, { ...newRowData, edit_action: 'ADD' }) }, col['column-key']))), jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", children: jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [jsxRuntimeExports.jsx("button", { type: "button", onClick: saveAdd, disabled: loadingRowIndex === -1, className: "px-3 py-1 text-xs bg-green-600 text-white hover:bg-green-700 disabled:opacity-50", style: { borderRadius: '15px' }, children: translate('common.save') || 'Save' }), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => {
|
|
5269
5654
|
setIsAdding(false);
|
|
5270
5655
|
setNewRowData(null);
|
|
5271
5656
|
}, disabled: loadingRowIndex === -1, className: "px-3 py-1 text-xs bg-gray-200 text-gray-700 hover:bg-gray-300 disabled:opacity-50", style: { borderRadius: '15px' }, children: translate('common.cancel') || 'Cancel' })] }) })] }))] })] }) })), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }));
|