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