@openg2p/registry-widgets 1.1.2-dev.6 → 1.1.2-dev.7
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/SectionBuilder/schemas.d.ts +112 -0
- package/dist/components/SectionBuilder/schemas.d.ts.map +1 -1
- package/dist/components/SectionRenderer.d.ts.map +1 -1
- package/dist/components/WidgetFieldLabel.d.ts +11 -0
- package/dist/components/WidgetFieldLabel.d.ts.map +1 -0
- package/dist/hooks/useBaseWidget.d.ts +2 -0
- package/dist/hooks/useBaseWidget.d.ts.map +1 -1
- package/dist/index.d.ts +45 -13
- package/dist/index.esm.js +878 -258
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +885 -257
- package/dist/index.js.map +1 -1
- package/dist/registry/defaultWidgets.d.ts.map +1 -1
- package/dist/types/index.d.ts +11 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/conditions.d.ts +17 -11
- package/dist/utils/conditions.d.ts.map +1 -1
- package/dist/utils/geoHierarchy.d.ts +9 -0
- package/dist/utils/geoHierarchy.d.ts.map +1 -1
- package/dist/utils/schemaNamespace.d.ts.map +1 -1
- package/dist/utils/schemaTranslation.d.ts.map +1 -1
- package/dist/utils/sectionRevert.d.ts +24 -0
- package/dist/utils/sectionRevert.d.ts.map +1 -0
- package/dist/utils/sectionValidate.d.ts.map +1 -1
- package/dist/widgets/ArrayWidget.d.ts.map +1 -1
- package/dist/widgets/BooleanWidget.d.ts.map +1 -1
- package/dist/widgets/CheckboxWidget.d.ts.map +1 -1
- package/dist/widgets/CurrencyInputWidget.d.ts.map +1 -1
- package/dist/widgets/DateInputWidget.d.ts.map +1 -1
- package/dist/widgets/DateTimeInputWidget.d.ts.map +1 -1
- package/dist/widgets/DialogTableWidget.d.ts.map +1 -1
- package/dist/widgets/FileInputWidget.d.ts.map +1 -1
- package/dist/widgets/IterableAccordionWidget.d.ts.map +1 -1
- package/dist/widgets/MultiSelectWidget.d.ts +7 -0
- package/dist/widgets/MultiSelectWidget.d.ts.map +1 -0
- package/dist/widgets/NumberInputWidget.d.ts.map +1 -1
- package/dist/widgets/PhoneInputWidget.d.ts.map +1 -1
- package/dist/widgets/RadioWidget.d.ts.map +1 -1
- package/dist/widgets/RegisterLookupWidget.d.ts.map +1 -1
- package/dist/widgets/SelectWidget.d.ts.map +1 -1
- package/dist/widgets/TextAreaWidget.d.ts.map +1 -1
- package/dist/widgets/TextInputWidget.d.ts.map +1 -1
- package/dist/widgets/index.d.ts +1 -0
- package/dist/widgets/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -403,6 +403,18 @@ const createZodSchema = (validation, required = false) => {
|
|
|
403
403
|
return schema;
|
|
404
404
|
};
|
|
405
405
|
|
|
406
|
+
const normalizeBooleanLike = (val) => {
|
|
407
|
+
if (val === true || val === 1)
|
|
408
|
+
return true;
|
|
409
|
+
if (val === false || val === 0 || val === null || val === undefined || val === '') {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
if (typeof val === 'string') {
|
|
413
|
+
const normalized = val.trim().toLowerCase();
|
|
414
|
+
return normalized === 'true' || normalized === 'yes' || normalized === '1';
|
|
415
|
+
}
|
|
416
|
+
return Boolean(val);
|
|
417
|
+
};
|
|
406
418
|
/**
|
|
407
419
|
* Evaluate condition against field value
|
|
408
420
|
*/
|
|
@@ -411,6 +423,9 @@ const evaluateCondition = (condition, allValues) => {
|
|
|
411
423
|
const { operator, value } = condition;
|
|
412
424
|
switch (operator) {
|
|
413
425
|
case 'equals':
|
|
426
|
+
if (typeof value === 'boolean' || typeof fieldValue === 'boolean') {
|
|
427
|
+
return normalizeBooleanLike(fieldValue) === normalizeBooleanLike(value);
|
|
428
|
+
}
|
|
414
429
|
return fieldValue === value;
|
|
415
430
|
case 'notEquals':
|
|
416
431
|
return fieldValue !== value;
|
|
@@ -443,37 +458,62 @@ const evaluateCondition = (condition, allValues) => {
|
|
|
443
458
|
}
|
|
444
459
|
};
|
|
445
460
|
/**
|
|
446
|
-
*
|
|
461
|
+
* Normalize widget-data-options into a sequential list of action rules.
|
|
462
|
+
* Supports legacy single { action, condition } and new { actions: [...] }.
|
|
447
463
|
*/
|
|
448
|
-
const
|
|
449
|
-
if (!options
|
|
450
|
-
return
|
|
464
|
+
const normalizeOptionRules = (options) => {
|
|
465
|
+
if (!options) {
|
|
466
|
+
return [];
|
|
451
467
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
return conditionResult;
|
|
468
|
+
if (Array.isArray(options.actions) && options.actions.length > 0) {
|
|
469
|
+
return options.actions.filter((rule) => !!rule?.action);
|
|
455
470
|
}
|
|
456
|
-
if (options.action
|
|
457
|
-
return
|
|
471
|
+
if (options.action && options.condition) {
|
|
472
|
+
return [{ action: options.action, condition: options.condition }];
|
|
458
473
|
}
|
|
459
|
-
return
|
|
474
|
+
return [];
|
|
475
|
+
};
|
|
476
|
+
const hasVisibilityRules = (options) => {
|
|
477
|
+
return normalizeOptionRules(options).some((rule) => rule.action === 'show' || rule.action === 'hide');
|
|
460
478
|
};
|
|
461
479
|
/**
|
|
462
|
-
*
|
|
480
|
+
* Evaluate widget-data-options rules sequentially.
|
|
481
|
+
* show/hide and enable/disable only affect visibility and enabled state.
|
|
482
|
+
* require is independent: required = widget-required OR require-condition-match.
|
|
463
483
|
*/
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
484
|
+
const evaluateWidgetConditions = (options, allValues, baseRequired = false) => {
|
|
485
|
+
let visible = true;
|
|
486
|
+
let enabled = true;
|
|
487
|
+
let required = baseRequired;
|
|
488
|
+
const rules = normalizeOptionRules(options);
|
|
489
|
+
for (const rule of rules) {
|
|
490
|
+
if (!rule.condition) {
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
const match = evaluateCondition(rule.condition, allValues);
|
|
494
|
+
switch (rule.action) {
|
|
495
|
+
case 'show':
|
|
496
|
+
visible = match;
|
|
497
|
+
break;
|
|
498
|
+
case 'hide':
|
|
499
|
+
visible = !match;
|
|
500
|
+
break;
|
|
501
|
+
case 'enable':
|
|
502
|
+
enabled = match;
|
|
503
|
+
break;
|
|
504
|
+
case 'disable':
|
|
505
|
+
enabled = !match;
|
|
506
|
+
break;
|
|
507
|
+
case 'require':
|
|
508
|
+
required = baseRequired || match;
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
474
511
|
}
|
|
475
|
-
return
|
|
512
|
+
return { visible, enabled, required };
|
|
476
513
|
};
|
|
514
|
+
const shouldShowWidget = (options, allValues) => evaluateWidgetConditions(options, allValues).visible;
|
|
515
|
+
const shouldEnableWidget = (options, allValues) => evaluateWidgetConditions(options, allValues).enabled;
|
|
516
|
+
const shouldRequireWidget = (options, allValues, baseRequired = false) => evaluateWidgetConditions(options, allValues, baseRequired).required;
|
|
477
517
|
|
|
478
518
|
/**
|
|
479
519
|
* Format number with thousand and decimal separators
|
|
@@ -1937,6 +1977,98 @@ const WidgetProvider = ({ store, dataSourceRequestHandler, schemaData, translate
|
|
|
1937
1977
|
return content;
|
|
1938
1978
|
};
|
|
1939
1979
|
|
|
1980
|
+
/**
|
|
1981
|
+
* Custom hook for widget translations
|
|
1982
|
+
* Provides translation function with widget-specific namespace and fallback support
|
|
1983
|
+
*/
|
|
1984
|
+
const useWidgetTranslation = () => {
|
|
1985
|
+
const { translate: translateFunction } = useWidgetContext();
|
|
1986
|
+
/**
|
|
1987
|
+
* Translate a key with flexible namespace support
|
|
1988
|
+
* Supports translation keys in various formats and direct strings
|
|
1989
|
+
*
|
|
1990
|
+
* Translation key formats supported:
|
|
1991
|
+
* - "widgets:common.addItem" - Namespaced key (for widget-specific translations)
|
|
1992
|
+
* - "Name" - Direct string (will be looked up in flat translation structure)
|
|
1993
|
+
* - "sections.personalDetails" - Nested key (for backward compatibility)
|
|
1994
|
+
*
|
|
1995
|
+
* With flat translation structure, direct strings like "Name" are automatically
|
|
1996
|
+
* translated by looking them up in the translation resources.
|
|
1997
|
+
*
|
|
1998
|
+
* @param keyOrString - Translation key (e.g., "widgets:common.addItem") or direct string (e.g., "Name")
|
|
1999
|
+
* @param options - Translation options (interpolation values, default value, etc.)
|
|
2000
|
+
* @returns Translated string or original string if translation not found
|
|
2001
|
+
*/
|
|
2002
|
+
const translate = (keyOrString, options) => {
|
|
2003
|
+
if (!keyOrString) {
|
|
2004
|
+
return options?.defaultValue || '';
|
|
2005
|
+
}
|
|
2006
|
+
// Use the provided translation function or fallback to the key
|
|
2007
|
+
if (translateFunction) {
|
|
2008
|
+
return translateFunction(keyOrString, options) || options?.defaultValue || keyOrString;
|
|
2009
|
+
}
|
|
2010
|
+
// Fallback to key if no translation function available
|
|
2011
|
+
return options?.defaultValue || keyOrString;
|
|
2012
|
+
};
|
|
2013
|
+
/**
|
|
2014
|
+
* Translate widget config property
|
|
2015
|
+
* Attempts to translate the value, but if translation is not found,
|
|
2016
|
+
* returns the original value as-is (graceful fallback)
|
|
2017
|
+
*
|
|
2018
|
+
* This function will:
|
|
2019
|
+
* - Try to translate any string value
|
|
2020
|
+
* - If translation exists, use the translated value
|
|
2021
|
+
* - If translation doesn't exist (returns same value or throws), use original value
|
|
2022
|
+
* - This prevents errors when literal strings like "XXXX-XXXX-XXXX" are used
|
|
2023
|
+
*/
|
|
2024
|
+
const translateConfig = (value, fallback) => {
|
|
2025
|
+
if (!value) {
|
|
2026
|
+
return fallback || '';
|
|
2027
|
+
}
|
|
2028
|
+
// Try to translate the value
|
|
2029
|
+
if (translateFunction) {
|
|
2030
|
+
try {
|
|
2031
|
+
// Pass defaultValue to ensure we get the original value if translation fails
|
|
2032
|
+
const translated = translateFunction(value, { defaultValue: value });
|
|
2033
|
+
// If translation returns empty, null, undefined, or the exact same value,
|
|
2034
|
+
// it means no translation was found - return the original value
|
|
2035
|
+
if (!translated || translated === value) {
|
|
2036
|
+
return value;
|
|
2037
|
+
}
|
|
2038
|
+
// Translation found, return it
|
|
2039
|
+
return translated;
|
|
2040
|
+
}
|
|
2041
|
+
catch (error) {
|
|
2042
|
+
// If translation throws an error (e.g., missing key warning), return original value
|
|
2043
|
+
return value;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
// No translation function available, return value as-is
|
|
2047
|
+
return value;
|
|
2048
|
+
};
|
|
2049
|
+
// No need of this getLanguage and changeLanguage functions
|
|
2050
|
+
/**
|
|
2051
|
+
* Get current language
|
|
2052
|
+
*/
|
|
2053
|
+
// const getLanguage = (): string => {
|
|
2054
|
+
// return i18n.language || 'en';
|
|
2055
|
+
// };
|
|
2056
|
+
/**
|
|
2057
|
+
* Change language
|
|
2058
|
+
*/
|
|
2059
|
+
// const changeLanguage = (lng: string): Promise<void> => {
|
|
2060
|
+
// return i18n.changeLanguage(lng).then(() => undefined);
|
|
2061
|
+
// };
|
|
2062
|
+
return {
|
|
2063
|
+
t: translate,
|
|
2064
|
+
translate,
|
|
2065
|
+
translateConfig,
|
|
2066
|
+
// getLanguage,
|
|
2067
|
+
// changeLanguage,
|
|
2068
|
+
// i18n: null,
|
|
2069
|
+
};
|
|
2070
|
+
};
|
|
2071
|
+
|
|
1940
2072
|
/**
|
|
1941
2073
|
* Geo Hierarchy Builder
|
|
1942
2074
|
* Manages geo hierarchy state and builds hierarchy JSON structure
|
|
@@ -2136,6 +2268,42 @@ function isUpstreamGeoAncestor(changedWidgetId, widgetId, immediateParentWidgetI
|
|
|
2136
2268
|
}
|
|
2137
2269
|
return false;
|
|
2138
2270
|
}
|
|
2271
|
+
/** Group id for geo widgets sharing the same register prefix (e.g. `{registerId}`). */
|
|
2272
|
+
function getGeoGroupId(dataPath) {
|
|
2273
|
+
if (typeof dataPath === 'string' && dataPath.includes('.')) {
|
|
2274
|
+
return dataPath.split('.').slice(0, -1).join('.');
|
|
2275
|
+
}
|
|
2276
|
+
return 'default';
|
|
2277
|
+
}
|
|
2278
|
+
/**
|
|
2279
|
+
* Resolve the human-readable label for a geo level from persisted hierarchy JSON.
|
|
2280
|
+
* Used in readonly mode when API options are not loaded.
|
|
2281
|
+
*/
|
|
2282
|
+
function resolveGeoWidgetLevelLabel(values, widgetId, dataPath, geoConfig) {
|
|
2283
|
+
if (!dataPath || typeof dataPath !== 'string') {
|
|
2284
|
+
return undefined;
|
|
2285
|
+
}
|
|
2286
|
+
const stored = getWidgetValue(values, dataPath, widgetId);
|
|
2287
|
+
const hierarchy = stored?.hierarchy || stored?.geo_code_hierarchy_json?.hierarchy;
|
|
2288
|
+
if (!Array.isArray(hierarchy)) {
|
|
2289
|
+
return undefined;
|
|
2290
|
+
}
|
|
2291
|
+
const levelData = hierarchy.find((l) => l.level === geoConfig.level);
|
|
2292
|
+
if (levelData?.level_value_mnemonic) {
|
|
2293
|
+
return String(levelData.level_value_mnemonic);
|
|
2294
|
+
}
|
|
2295
|
+
return undefined;
|
|
2296
|
+
}
|
|
2297
|
+
/** All registered geo widgets that are descendants of ancestorWidgetId. */
|
|
2298
|
+
function getGeoDescendantWidgetIds(ancestorWidgetId) {
|
|
2299
|
+
const descendants = [];
|
|
2300
|
+
for (const [childId, parentId] of geoWidgetParentRegistry.entries()) {
|
|
2301
|
+
if (isUpstreamGeoAncestor(ancestorWidgetId, childId, parentId)) {
|
|
2302
|
+
descendants.push(childId);
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
return descendants;
|
|
2306
|
+
}
|
|
2139
2307
|
function readStoredHierarchyLevels(values, dataPath, widgetId) {
|
|
2140
2308
|
const stored = getWidgetValue(values, dataPath, widgetId);
|
|
2141
2309
|
const hierarchy = stored?.hierarchy || stored?.geo_code_hierarchy_json?.hierarchy;
|
|
@@ -2186,6 +2354,7 @@ const useBaseWidget = (options) => {
|
|
|
2186
2354
|
const dispatch = reactRedux.useDispatch();
|
|
2187
2355
|
const context = useWidgetContext();
|
|
2188
2356
|
const eventBus = useWidgetEventBus();
|
|
2357
|
+
const { translateConfig } = useWidgetTranslation();
|
|
2189
2358
|
const widgetId = config['widget-id'];
|
|
2190
2359
|
// Fall back to WidgetContext for dataSourceRequestHandler
|
|
2191
2360
|
const dataSourceRequestHandler = propHandler || context.dataSourceRequestHandler;
|
|
@@ -2356,6 +2525,15 @@ const useBaseWidget = (options) => {
|
|
|
2356
2525
|
}
|
|
2357
2526
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2358
2527
|
}, [isLayoutWidget]); // Only run once on mount
|
|
2528
|
+
const resolveIsRequired = React.useCallback((currentValues) => {
|
|
2529
|
+
if (isLayoutWidget) {
|
|
2530
|
+
return false;
|
|
2531
|
+
}
|
|
2532
|
+
if (config['widget-readonly']) {
|
|
2533
|
+
return false;
|
|
2534
|
+
}
|
|
2535
|
+
return evaluateWidgetConditions(config['widget-data-options'], currentValues, config['widget-required'] ?? false).required;
|
|
2536
|
+
}, [config, isLayoutWidget]);
|
|
2359
2537
|
// Handle value change
|
|
2360
2538
|
// CRITICAL: Don't include 'values' in dependency array - it causes the callback to be recreated
|
|
2361
2539
|
// every time values change, which can lead to stale closures and double dispatches
|
|
@@ -2399,29 +2577,26 @@ const useBaseWidget = (options) => {
|
|
|
2399
2577
|
lastDispatchedValueRef.current = newValue;
|
|
2400
2578
|
dispatch(setValue({ widgetId, value: newValue }));
|
|
2401
2579
|
}
|
|
2580
|
+
else if (config['widget-geo-config']) {
|
|
2581
|
+
// Geo widgets: hierarchy dataPath is managed by useGeoWidgetCascade
|
|
2582
|
+
getGeoDescendantWidgetIds(widgetId).forEach((descendantId) => {
|
|
2583
|
+
dispatch(setValue({ widgetId: descendantId, value: GEO_LEVEL_CLEARED }));
|
|
2584
|
+
dispatch(setDataSource({ widgetId: descendantId, data: [] }));
|
|
2585
|
+
});
|
|
2586
|
+
dispatch(setValue({ widgetId, value: newValue }));
|
|
2587
|
+
}
|
|
2402
2588
|
else {
|
|
2403
|
-
//
|
|
2404
|
-
// CRITICAL: For geo widgets, we do NOT want to overwrite the shared hierarchy dataPath
|
|
2405
|
-
// with a primitive value (the selected ID). The hierarchy object is managed by useGeoWidgetCascade.
|
|
2406
|
-
if (config['widget-geo-config']) {
|
|
2407
|
-
dispatch(setValue({ widgetId, value: newValue }));
|
|
2408
|
-
return;
|
|
2409
|
-
}
|
|
2410
|
-
// For non-geo widgets, update both widgetId and dataPath
|
|
2411
|
-
// CRITICAL: Create updated values object with newValue already set
|
|
2412
|
-
// This prevents setWidgetValue from reading stale values
|
|
2589
|
+
// Non-geo widgets: update both widgetId and dataPath
|
|
2413
2590
|
const currentValuesWithUpdate = {
|
|
2414
2591
|
...valuesRef.current,
|
|
2415
|
-
[widgetId]: newValue,
|
|
2592
|
+
[widgetId]: newValue,
|
|
2416
2593
|
};
|
|
2417
2594
|
const updatedValues = setWidgetValue(currentValuesWithUpdate, config['widget-data-path'], widgetId, newValue);
|
|
2418
|
-
// setWidgetValue returns the complete updated structure with all existing data preserved
|
|
2419
|
-
// Use setValues to update the entire state with deep merge
|
|
2420
2595
|
dispatch(setValues(updatedValues));
|
|
2421
2596
|
}
|
|
2422
2597
|
// Validate if needed
|
|
2423
2598
|
if (validate) {
|
|
2424
|
-
const validationErrors = validateWidget(newValue, config['widget-data-validation'],
|
|
2599
|
+
const validationErrors = validateWidget(newValue, config['widget-data-validation'], resolveIsRequired(currentValues));
|
|
2425
2600
|
dispatch(setError({ widgetId, errors: validationErrors }));
|
|
2426
2601
|
}
|
|
2427
2602
|
// Call custom onChange if provided
|
|
@@ -2440,13 +2615,12 @@ const useBaseWidget = (options) => {
|
|
|
2440
2615
|
timestamp: Date.now(),
|
|
2441
2616
|
});
|
|
2442
2617
|
}
|
|
2443
|
-
}, [config, widgetId, dispatch, onValueChange, eventBus] // Removed 'values' to prevent stale closures
|
|
2618
|
+
}, [config, widgetId, dispatch, onValueChange, eventBus, resolveIsRequired] // Removed 'values' to prevent stale closures
|
|
2444
2619
|
);
|
|
2445
2620
|
// Handle blur
|
|
2446
2621
|
const handleBlur = React.useCallback(() => {
|
|
2447
2622
|
dispatch(setTouched({ widgetId, touched: true }));
|
|
2448
|
-
|
|
2449
|
-
const validationErrors = validateWidget(currentValue, config['widget-data-validation'], config['widget-required']);
|
|
2623
|
+
const validationErrors = validateWidget(currentValue, config['widget-data-validation'], resolveIsRequired(valuesRef.current));
|
|
2450
2624
|
dispatch(setError({ widgetId, errors: validationErrors }));
|
|
2451
2625
|
// Publish widget:blur event
|
|
2452
2626
|
if (eventBus) {
|
|
@@ -2457,7 +2631,7 @@ const useBaseWidget = (options) => {
|
|
|
2457
2631
|
timestamp: Date.now(),
|
|
2458
2632
|
});
|
|
2459
2633
|
}
|
|
2460
|
-
}, [currentValue, config, widgetId, dispatch, eventBus]);
|
|
2634
|
+
}, [currentValue, config, widgetId, dispatch, eventBus, resolveIsRequired]);
|
|
2461
2635
|
// Get field value helper
|
|
2462
2636
|
const getFieldValue = React.useCallback((path) => {
|
|
2463
2637
|
return getWidgetValue(values, path, '');
|
|
@@ -2465,7 +2639,7 @@ const useBaseWidget = (options) => {
|
|
|
2465
2639
|
// Conditional visibility and enablement
|
|
2466
2640
|
const isVisible = React.useMemo(() => {
|
|
2467
2641
|
// Layout widgets are always visible unless explicitly hidden
|
|
2468
|
-
if (isLayoutWidget && !config['widget-data-options']
|
|
2642
|
+
if (isLayoutWidget && !hasVisibilityRules(config['widget-data-options'])) {
|
|
2469
2643
|
return true;
|
|
2470
2644
|
}
|
|
2471
2645
|
return shouldShowWidget(config['widget-data-options'], values);
|
|
@@ -2480,6 +2654,7 @@ const useBaseWidget = (options) => {
|
|
|
2480
2654
|
}
|
|
2481
2655
|
return shouldEnableWidget(config['widget-data-options'], values);
|
|
2482
2656
|
}, [config['widget-readonly'], config['widget-data-options'], values, isLayoutWidget]);
|
|
2657
|
+
const isRequired = React.useMemo(() => resolveIsRequired(values), [resolveIsRequired, values]);
|
|
2483
2658
|
// Format value for display
|
|
2484
2659
|
const formattedValue = React.useMemo(() => {
|
|
2485
2660
|
if (!config['widget-data-format']) {
|
|
@@ -2526,10 +2701,9 @@ const useBaseWidget = (options) => {
|
|
|
2526
2701
|
if (!dataSource) {
|
|
2527
2702
|
return;
|
|
2528
2703
|
}
|
|
2529
|
-
//
|
|
2530
|
-
//
|
|
2531
|
-
|
|
2532
|
-
if (dataSource.type === 'api' && isReadonly) {
|
|
2704
|
+
// Non-geo readonly widgets skip API loads. Geo widgets still load in readonly so
|
|
2705
|
+
// labels can be resolved and translated on initial page view (not only after Edit).
|
|
2706
|
+
if (dataSource.type === 'api' && isReadonly && !geoConfig) {
|
|
2533
2707
|
return;
|
|
2534
2708
|
}
|
|
2535
2709
|
// For widgets with dependencies, check if dependency value exists
|
|
@@ -2623,15 +2797,24 @@ const useBaseWidget = (options) => {
|
|
|
2623
2797
|
// Use configKey and dependencyValue to ensure effect runs only when relevant state changes
|
|
2624
2798
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2625
2799
|
}, [configKey, dependencyValue, dataSourceRequestHandler, schemaData, widgetId, dispatch]);
|
|
2800
|
+
const geoDisplayLabel = React.useMemo(() => {
|
|
2801
|
+
if (!geoConfig) {
|
|
2802
|
+
return undefined;
|
|
2803
|
+
}
|
|
2804
|
+
const rawLabel = resolveGeoWidgetLevelLabel(values, widgetId, config['widget-data-path'], geoConfig);
|
|
2805
|
+
return rawLabel ? translateConfig(rawLabel) : undefined;
|
|
2806
|
+
}, [values, widgetId, config, geoConfig, translateConfig]);
|
|
2626
2807
|
return {
|
|
2627
2808
|
widgetId,
|
|
2628
2809
|
value: currentValue,
|
|
2810
|
+
geoDisplayLabel,
|
|
2629
2811
|
formattedValue,
|
|
2630
2812
|
error: errors,
|
|
2631
2813
|
touched,
|
|
2632
2814
|
loading,
|
|
2633
2815
|
isVisible,
|
|
2634
2816
|
isEnabled,
|
|
2817
|
+
isRequired,
|
|
2635
2818
|
onChange: handleChange,
|
|
2636
2819
|
onBlur: handleBlur,
|
|
2637
2820
|
setError: (errors) => dispatch(setError({ widgetId, errors })),
|
|
@@ -2722,6 +2905,7 @@ const useGeoWidgetCascade = (options) => {
|
|
|
2722
2905
|
const valuesRef = React.useRef(values);
|
|
2723
2906
|
const handlerRef = React.useRef(dataSourceRequestHandler);
|
|
2724
2907
|
const lastCascadePublishRef = React.useRef(undefined);
|
|
2908
|
+
const lastDirectParentValueRef = React.useRef(undefined);
|
|
2725
2909
|
// Keep refs updated
|
|
2726
2910
|
React.useEffect(() => {
|
|
2727
2911
|
valuesRef.current = values;
|
|
@@ -2791,6 +2975,13 @@ const useGeoWidgetCascade = (options) => {
|
|
|
2791
2975
|
event.value === null ||
|
|
2792
2976
|
event.value === '' ||
|
|
2793
2977
|
event.value === GEO_LEVEL_CLEARED;
|
|
2978
|
+
const isFirstParentEvent = lastDirectParentValueRef.current === undefined;
|
|
2979
|
+
const parentValueChanged = !isFirstParentEvent &&
|
|
2980
|
+
lastDirectParentValueRef.current !== event.value;
|
|
2981
|
+
lastDirectParentValueRef.current = event.value;
|
|
2982
|
+
if (!parentCleared && !parentValueChanged && !isFirstParentEvent) {
|
|
2983
|
+
return;
|
|
2984
|
+
}
|
|
2794
2985
|
let parentValue = event.value;
|
|
2795
2986
|
if (!parentCleared && (parentValue === undefined || parentValue === null)) {
|
|
2796
2987
|
parentValue = currentValues[parentWidgetId];
|
|
@@ -2898,17 +3089,7 @@ const useGeoWidgetCascade = (options) => {
|
|
|
2898
3089
|
if (dataPath && geoHierarchyBuilder.buildHierarchyJson(groupId)) {
|
|
2899
3090
|
dispatch(setValues(applySharedGeoHierarchyToValues(valuesRef.current, groupId, dataPath, widgetId)));
|
|
2900
3091
|
}
|
|
2901
|
-
|
|
2902
|
-
if (!isLastLevel && eventBus && lastCascadePublishRef.current !== level_value_id) {
|
|
2903
|
-
lastCascadePublishRef.current = level_value_id;
|
|
2904
|
-
eventBus.publish({
|
|
2905
|
-
type: 'widget:change',
|
|
2906
|
-
widgetId,
|
|
2907
|
-
value: level_value_id,
|
|
2908
|
-
timestamp: Date.now(),
|
|
2909
|
-
});
|
|
2910
|
-
}
|
|
2911
|
-
}, [geoConfig, currentValue, widgetId, dataPath, dispatch, dataSourceOptions, eventBus, groupId]);
|
|
3092
|
+
}, [geoConfig, currentValue, widgetId, dataPath, dispatch, dataSourceOptions, groupId]);
|
|
2912
3093
|
};
|
|
2913
3094
|
|
|
2914
3095
|
class WidgetRegistry {
|
|
@@ -3033,98 +3214,6 @@ const WidgetRenderer = ({ config, dataSourceRequestHandler: propDataSourceReques
|
|
|
3033
3214
|
return (jsxRuntimeExports.jsx("div", { className: "widget-container", "data-widget-id": widgetContext.widgetId, style: { marginBottom: 0 }, children: widgetRegistry.render(config, widgetContext, defaultComponent) }));
|
|
3034
3215
|
};
|
|
3035
3216
|
|
|
3036
|
-
/**
|
|
3037
|
-
* Custom hook for widget translations
|
|
3038
|
-
* Provides translation function with widget-specific namespace and fallback support
|
|
3039
|
-
*/
|
|
3040
|
-
const useWidgetTranslation = () => {
|
|
3041
|
-
const { translate: translateFunction } = useWidgetContext();
|
|
3042
|
-
/**
|
|
3043
|
-
* Translate a key with flexible namespace support
|
|
3044
|
-
* Supports translation keys in various formats and direct strings
|
|
3045
|
-
*
|
|
3046
|
-
* Translation key formats supported:
|
|
3047
|
-
* - "widgets:common.addItem" - Namespaced key (for widget-specific translations)
|
|
3048
|
-
* - "Name" - Direct string (will be looked up in flat translation structure)
|
|
3049
|
-
* - "sections.personalDetails" - Nested key (for backward compatibility)
|
|
3050
|
-
*
|
|
3051
|
-
* With flat translation structure, direct strings like "Name" are automatically
|
|
3052
|
-
* translated by looking them up in the translation resources.
|
|
3053
|
-
*
|
|
3054
|
-
* @param keyOrString - Translation key (e.g., "widgets:common.addItem") or direct string (e.g., "Name")
|
|
3055
|
-
* @param options - Translation options (interpolation values, default value, etc.)
|
|
3056
|
-
* @returns Translated string or original string if translation not found
|
|
3057
|
-
*/
|
|
3058
|
-
const translate = (keyOrString, options) => {
|
|
3059
|
-
if (!keyOrString) {
|
|
3060
|
-
return options?.defaultValue || '';
|
|
3061
|
-
}
|
|
3062
|
-
// Use the provided translation function or fallback to the key
|
|
3063
|
-
if (translateFunction) {
|
|
3064
|
-
return translateFunction(keyOrString, options) || options?.defaultValue || keyOrString;
|
|
3065
|
-
}
|
|
3066
|
-
// Fallback to key if no translation function available
|
|
3067
|
-
return options?.defaultValue || keyOrString;
|
|
3068
|
-
};
|
|
3069
|
-
/**
|
|
3070
|
-
* Translate widget config property
|
|
3071
|
-
* Attempts to translate the value, but if translation is not found,
|
|
3072
|
-
* returns the original value as-is (graceful fallback)
|
|
3073
|
-
*
|
|
3074
|
-
* This function will:
|
|
3075
|
-
* - Try to translate any string value
|
|
3076
|
-
* - If translation exists, use the translated value
|
|
3077
|
-
* - If translation doesn't exist (returns same value or throws), use original value
|
|
3078
|
-
* - This prevents errors when literal strings like "XXXX-XXXX-XXXX" are used
|
|
3079
|
-
*/
|
|
3080
|
-
const translateConfig = (value, fallback) => {
|
|
3081
|
-
if (!value) {
|
|
3082
|
-
return fallback || '';
|
|
3083
|
-
}
|
|
3084
|
-
// Try to translate the value
|
|
3085
|
-
if (translateFunction) {
|
|
3086
|
-
try {
|
|
3087
|
-
// Pass defaultValue to ensure we get the original value if translation fails
|
|
3088
|
-
const translated = translateFunction(value, { defaultValue: value });
|
|
3089
|
-
// If translation returns empty, null, undefined, or the exact same value,
|
|
3090
|
-
// it means no translation was found - return the original value
|
|
3091
|
-
if (!translated || translated === value) {
|
|
3092
|
-
return value;
|
|
3093
|
-
}
|
|
3094
|
-
// Translation found, return it
|
|
3095
|
-
return translated;
|
|
3096
|
-
}
|
|
3097
|
-
catch (error) {
|
|
3098
|
-
// If translation throws an error (e.g., missing key warning), return original value
|
|
3099
|
-
return value;
|
|
3100
|
-
}
|
|
3101
|
-
}
|
|
3102
|
-
// No translation function available, return value as-is
|
|
3103
|
-
return value;
|
|
3104
|
-
};
|
|
3105
|
-
// No need of this getLanguage and changeLanguage functions
|
|
3106
|
-
/**
|
|
3107
|
-
* Get current language
|
|
3108
|
-
*/
|
|
3109
|
-
// const getLanguage = (): string => {
|
|
3110
|
-
// return i18n.language || 'en';
|
|
3111
|
-
// };
|
|
3112
|
-
/**
|
|
3113
|
-
* Change language
|
|
3114
|
-
*/
|
|
3115
|
-
// const changeLanguage = (lng: string): Promise<void> => {
|
|
3116
|
-
// return i18n.changeLanguage(lng).then(() => undefined);
|
|
3117
|
-
// };
|
|
3118
|
-
return {
|
|
3119
|
-
t: translate,
|
|
3120
|
-
translate,
|
|
3121
|
-
translateConfig,
|
|
3122
|
-
// getLanguage,
|
|
3123
|
-
// changeLanguage,
|
|
3124
|
-
// i18n: null,
|
|
3125
|
-
};
|
|
3126
|
-
};
|
|
3127
|
-
|
|
3128
3217
|
/**
|
|
3129
3218
|
* Renders a panel with its nested panels or widgets
|
|
3130
3219
|
*
|
|
@@ -3243,6 +3332,16 @@ const PanelRenderer = ({ panel, dataSourceRequestHandler, schemaData, onValueCha
|
|
|
3243
3332
|
return (jsxRuntimeExports.jsx("div", { className: `panel panel-${orientation}`, "data-panel-id": panel['panel-id'], style: orientation === 'horizontal' ? { width: '100%' } : {}, children: content }));
|
|
3244
3333
|
};
|
|
3245
3334
|
|
|
3335
|
+
/**
|
|
3336
|
+
* Field label: long text truncates with ellipsis; required asterisk always stays visible.
|
|
3337
|
+
*/
|
|
3338
|
+
const WidgetFieldLabel = ({ label, required = false, className = '', title, }) => {
|
|
3339
|
+
const { translateConfig } = useWidgetTranslation();
|
|
3340
|
+
const translatedLabel = translateConfig(label);
|
|
3341
|
+
const tooltip = title !== undefined ? translateConfig(title) : translatedLabel;
|
|
3342
|
+
return (jsxRuntimeExports.jsxs("label", { className: `flex items-baseline min-w-0 max-w-full ${className}`, style: { fontFamily: 'Roboto, sans-serif' }, title: tooltip, children: [jsxRuntimeExports.jsx("span", { className: "min-w-0 truncate", children: translatedLabel }), required && jsxRuntimeExports.jsx("span", { className: "ml-1 shrink-0 text-red-500", children: "*" })] }));
|
|
3343
|
+
};
|
|
3344
|
+
|
|
3246
3345
|
/**
|
|
3247
3346
|
* Utility functions for file preview functionality
|
|
3248
3347
|
*/
|
|
@@ -3592,7 +3691,7 @@ const deserializeValue = (value) => {
|
|
|
3592
3691
|
};
|
|
3593
3692
|
|
|
3594
3693
|
const FileInputWidget = ({ config }) => {
|
|
3595
|
-
const { value, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
3694
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
3596
3695
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
3597
3696
|
const accept = widgetConfig['widget-data-options']?.accept;
|
|
3598
3697
|
const multiple = widgetConfig['widget-data-options']?.multiple || false;
|
|
@@ -3832,7 +3931,7 @@ const FileInputWidget = ({ config }) => {
|
|
|
3832
3931
|
setPreviewFile(null);
|
|
3833
3932
|
} })] }));
|
|
3834
3933
|
}
|
|
3835
|
-
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px]", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
3934
|
+
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px]", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-center gap-2 sm:space-x-4", children: [jsxRuntimeExports.jsxs("label", { className: `cursor-pointer inline-flex items-center justify-between gap-2 border border-gray-300 shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 ${!isEnabled
|
|
3836
3935
|
? 'opacity-50 cursor-not-allowed'
|
|
3837
3936
|
: ''}`, style: {
|
|
3838
3937
|
width: '100%',
|
|
@@ -3893,6 +3992,13 @@ const namespaceWidgetConfig = (widgetConfig, namespace) => {
|
|
|
3893
3992
|
if (namespaced['widget-data-path']) {
|
|
3894
3993
|
namespaced['widget-data-path'] = namespaceDataPath(namespaced['widget-data-path'], namespace);
|
|
3895
3994
|
}
|
|
3995
|
+
// Namespace geo parent references so cascade events match namespaced widget-id
|
|
3996
|
+
if (namespaced['widget-geo-config']?.parentWidgetId) {
|
|
3997
|
+
namespaced['widget-geo-config'] = {
|
|
3998
|
+
...namespaced['widget-geo-config'],
|
|
3999
|
+
parentWidgetId: `${namespace}__${namespaced['widget-geo-config'].parentWidgetId}`,
|
|
4000
|
+
};
|
|
4001
|
+
}
|
|
3896
4002
|
// Recursively namespace nested widgets (for layout widgets)
|
|
3897
4003
|
if (namespaced.widgets && Array.isArray(namespaced.widgets)) {
|
|
3898
4004
|
namespaced.widgets = namespaced.widgets.map((widget) => namespaceWidgetConfig(widget, namespace));
|
|
@@ -4097,6 +4203,9 @@ const sectionValidate = (section, currentSchemaData, dispatch, skipRequired = fa
|
|
|
4097
4203
|
const isVisible = shouldShowWidget(widget['widget-data-options'], currentSchemaData);
|
|
4098
4204
|
if (!isVisible)
|
|
4099
4205
|
continue;
|
|
4206
|
+
const isEnabled = shouldEnableWidget(widget['widget-data-options'], currentSchemaData);
|
|
4207
|
+
if (!isEnabled)
|
|
4208
|
+
continue;
|
|
4100
4209
|
const widgetId = widget['widget-id'];
|
|
4101
4210
|
if (isTableLikeWidget(widget)) {
|
|
4102
4211
|
const tableValid = validateTableLikeWidget(widget, currentSchemaData, dispatch, skipRequired);
|
|
@@ -4106,7 +4215,8 @@ const sectionValidate = (section, currentSchemaData, dispatch, skipRequired = fa
|
|
|
4106
4215
|
continue;
|
|
4107
4216
|
}
|
|
4108
4217
|
const value = getWidgetValue(currentSchemaData, widget['widget-data-path'], widgetId);
|
|
4109
|
-
const
|
|
4218
|
+
const isRequired = shouldRequireWidget(widget['widget-data-options'], currentSchemaData, widget['widget-required'] ?? false);
|
|
4219
|
+
const errors = validateWidget(value, widget['widget-data-validation'], isRequired, skipRequired);
|
|
4110
4220
|
if (errors.length > 0) {
|
|
4111
4221
|
isValid = false;
|
|
4112
4222
|
dispatch(setTouched({ widgetId, touched: true }));
|
|
@@ -4139,6 +4249,108 @@ const sectionValidate = (section, currentSchemaData, dispatch, skipRequired = fa
|
|
|
4139
4249
|
return isValid;
|
|
4140
4250
|
};
|
|
4141
4251
|
|
|
4252
|
+
const cloneValue = (value) => {
|
|
4253
|
+
if (value === undefined) {
|
|
4254
|
+
return undefined;
|
|
4255
|
+
}
|
|
4256
|
+
try {
|
|
4257
|
+
return structuredClone(value);
|
|
4258
|
+
}
|
|
4259
|
+
catch {
|
|
4260
|
+
return JSON.parse(JSON.stringify(value));
|
|
4261
|
+
}
|
|
4262
|
+
};
|
|
4263
|
+
const resolveNamespacedWidgetId = (widgetId, namespace) => namespace ? `${namespace}__${widgetId}` : widgetId;
|
|
4264
|
+
const resolveStoreDataPath = (dataPath, namespace) => {
|
|
4265
|
+
if (!dataPath) {
|
|
4266
|
+
return dataPath;
|
|
4267
|
+
}
|
|
4268
|
+
if (!namespace) {
|
|
4269
|
+
return dataPath;
|
|
4270
|
+
}
|
|
4271
|
+
if (typeof dataPath === 'string') {
|
|
4272
|
+
return `${namespace}.${dataPath}`;
|
|
4273
|
+
}
|
|
4274
|
+
return Object.fromEntries(Object.entries(dataPath).map(([key, path]) => [key, `${namespace}.${path}`]));
|
|
4275
|
+
};
|
|
4276
|
+
/**
|
|
4277
|
+
* Capture Redux widget values for a section at edit entry.
|
|
4278
|
+
* Used to restore exact pre-edit state on Cancel (schemaData may be stale or shared with Redux).
|
|
4279
|
+
*/
|
|
4280
|
+
function captureSectionEditSnapshot(values, section, options) {
|
|
4281
|
+
const { namespace, sectionId, supportingDocuments = [] } = options ?? {};
|
|
4282
|
+
const dataPaths = [];
|
|
4283
|
+
const processedPaths = new Set();
|
|
4284
|
+
const widgetIds = {};
|
|
4285
|
+
const addPath = (path) => {
|
|
4286
|
+
if (!path || processedPaths.has(path)) {
|
|
4287
|
+
return;
|
|
4288
|
+
}
|
|
4289
|
+
processedPaths.add(path);
|
|
4290
|
+
dataPaths.push({
|
|
4291
|
+
path,
|
|
4292
|
+
value: cloneValue(getValueByPath(values, path)),
|
|
4293
|
+
});
|
|
4294
|
+
if (path.endsWith('.geo_code_hierarchy_json')) {
|
|
4295
|
+
const prefix = path.slice(0, -'.geo_code_hierarchy_json'.length);
|
|
4296
|
+
addPath(`${prefix}.geo_lowest_level_value_id`);
|
|
4297
|
+
}
|
|
4298
|
+
};
|
|
4299
|
+
collectWidgets(section.panels).forEach((widget) => {
|
|
4300
|
+
const widgetId = resolveNamespacedWidgetId(widget['widget-id'], namespace);
|
|
4301
|
+
const storeDataPath = resolveStoreDataPath(widget['widget-data-path'], namespace);
|
|
4302
|
+
if (Object.prototype.hasOwnProperty.call(values, widgetId)) {
|
|
4303
|
+
widgetIds[widgetId] = { present: true, value: cloneValue(values[widgetId]) };
|
|
4304
|
+
}
|
|
4305
|
+
else {
|
|
4306
|
+
widgetIds[widgetId] = { present: false };
|
|
4307
|
+
}
|
|
4308
|
+
if (typeof storeDataPath === 'string') {
|
|
4309
|
+
addPath(storeDataPath);
|
|
4310
|
+
}
|
|
4311
|
+
else if (storeDataPath && typeof storeDataPath === 'object') {
|
|
4312
|
+
Object.values(storeDataPath).forEach((path) => {
|
|
4313
|
+
if (typeof path === 'string') {
|
|
4314
|
+
addPath(path);
|
|
4315
|
+
}
|
|
4316
|
+
});
|
|
4317
|
+
}
|
|
4318
|
+
});
|
|
4319
|
+
supportingDocuments.forEach((doc, index) => {
|
|
4320
|
+
const widgetId = `supporting-doc-${sectionId ?? 'section'}-${index}`;
|
|
4321
|
+
const storeDataPath = namespace && doc['document-data-path']
|
|
4322
|
+
? `${namespace}.${doc['document-data-path']}`
|
|
4323
|
+
: doc['document-data-path'];
|
|
4324
|
+
if (Object.prototype.hasOwnProperty.call(values, widgetId)) {
|
|
4325
|
+
widgetIds[widgetId] = { present: true, value: cloneValue(values[widgetId]) };
|
|
4326
|
+
}
|
|
4327
|
+
else {
|
|
4328
|
+
widgetIds[widgetId] = { present: false };
|
|
4329
|
+
}
|
|
4330
|
+
if (typeof storeDataPath === 'string') {
|
|
4331
|
+
addPath(storeDataPath);
|
|
4332
|
+
}
|
|
4333
|
+
});
|
|
4334
|
+
return { dataPaths, widgetIds };
|
|
4335
|
+
}
|
|
4336
|
+
/** Apply a section edit snapshot back onto the full Redux values object. */
|
|
4337
|
+
function applySectionEditSnapshot(currentValues, snapshot) {
|
|
4338
|
+
let result = currentValues;
|
|
4339
|
+
for (const { path, value } of snapshot.dataPaths) {
|
|
4340
|
+
result = setValueByPath(result, path, cloneValue(value));
|
|
4341
|
+
}
|
|
4342
|
+
for (const [widgetId, entry] of Object.entries(snapshot.widgetIds)) {
|
|
4343
|
+
if (entry.present) {
|
|
4344
|
+
result = { ...result, [widgetId]: cloneValue(entry.value) };
|
|
4345
|
+
}
|
|
4346
|
+
else {
|
|
4347
|
+
const { [widgetId]: _removed, ...rest } = result;
|
|
4348
|
+
result = rest;
|
|
4349
|
+
}
|
|
4350
|
+
}
|
|
4351
|
+
return result;
|
|
4352
|
+
}
|
|
4353
|
+
|
|
4142
4354
|
/** Root class on readonly label/value rows; SectionRenderer scopes overflow/ellipsis rules here. */
|
|
4143
4355
|
const READONLY_VALUE_ROW_ROOT_CLASSES = [
|
|
4144
4356
|
'TextDisplayWidget',
|
|
@@ -4350,6 +4562,8 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4350
4562
|
}, [forceExitEdit]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
4351
4563
|
const [isDocumentsExpanded, setIsDocumentsExpanded] = React.useState(true);
|
|
4352
4564
|
const sectionRef = React.useRef(null);
|
|
4565
|
+
const baselineSnapshotRef = React.useRef(null);
|
|
4566
|
+
const editEntrySnapshotRef = React.useRef(null);
|
|
4353
4567
|
const [sectionHeight, setSectionHeight] = React.useState(null);
|
|
4354
4568
|
const [editSectionPosition, setEditSectionPosition] = React.useState(null);
|
|
4355
4569
|
// Capture section position when entering edit mode and update on scroll
|
|
@@ -4414,6 +4628,18 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4414
4628
|
panels: makePanelsEditable(sectionToRender.panels, widgetsEditable),
|
|
4415
4629
|
};
|
|
4416
4630
|
}, [sectionToRender, widgetsEditable]);
|
|
4631
|
+
const effectiveHideEditButton = hideEditButton ||
|
|
4632
|
+
section['section-hide-edit-button'] === true ||
|
|
4633
|
+
!collectWidgets(section.panels || []).some((w) => section['section-editable'] === true || w['widget-readonly'] !== true);
|
|
4634
|
+
const captureEditEntrySnapshot = React.useCallback(() => {
|
|
4635
|
+
const currentValues = store.getState().widget.values;
|
|
4636
|
+
const supportingDocuments = section['section-supporting-documents'] || [];
|
|
4637
|
+
editEntrySnapshotRef.current = captureSectionEditSnapshot(currentValues, section, {
|
|
4638
|
+
namespace,
|
|
4639
|
+
sectionId,
|
|
4640
|
+
supportingDocuments: hasSupportingDocuments ? supportingDocuments : [],
|
|
4641
|
+
});
|
|
4642
|
+
}, [store, section, namespace, sectionId, hasSupportingDocuments]);
|
|
4417
4643
|
// Handle edit button click
|
|
4418
4644
|
const handleEdit = () => {
|
|
4419
4645
|
// Capture height BEFORE entering edit mode to preserve space
|
|
@@ -4421,6 +4647,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4421
4647
|
const height = sectionRef.current.offsetHeight;
|
|
4422
4648
|
setSectionHeight(height);
|
|
4423
4649
|
}
|
|
4650
|
+
captureEditEntrySnapshot();
|
|
4424
4651
|
setIsEditMode(true);
|
|
4425
4652
|
onEditModeChange?.(originalSectionId, true);
|
|
4426
4653
|
};
|
|
@@ -4595,8 +4822,6 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4595
4822
|
}
|
|
4596
4823
|
return { records, files };
|
|
4597
4824
|
}, [originalSection, hasSupportingDocuments]);
|
|
4598
|
-
// Capture baseline when entering edit mode (used for isDirty comparison)
|
|
4599
|
-
const baselineSnapshotRef = React.useRef(null);
|
|
4600
4825
|
// IntakeForm only: increment when baseline is updated after save - forces badge to update (refs don't trigger re-renders)
|
|
4601
4826
|
const [intakeFormBaselineTrigger, setIntakeFormBaselineTrigger] = React.useState(0);
|
|
4602
4827
|
// IntakeForm: treat as edit mode for dirty tracking when isDraft. RegistryView: use isEditMode.
|
|
@@ -4614,6 +4839,9 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4614
4839
|
// Set baseline when entering edit mode; clear when leaving (baseline captured only on entry)
|
|
4615
4840
|
React.useEffect(() => {
|
|
4616
4841
|
if (effectiveEditModeForDirty) {
|
|
4842
|
+
if (!editEntrySnapshotRef.current) {
|
|
4843
|
+
captureEditEntrySnapshot();
|
|
4844
|
+
}
|
|
4617
4845
|
const oldSchemaData = schemaData || contextSchemaData || {};
|
|
4618
4846
|
if (namespace) {
|
|
4619
4847
|
const namespacedSchema = getValueByPath(oldSchemaData, namespace);
|
|
@@ -4622,11 +4850,12 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4622
4850
|
: buildSectionSnapshot(oldSchemaData);
|
|
4623
4851
|
}
|
|
4624
4852
|
else {
|
|
4625
|
-
baselineSnapshotRef.current = buildSectionSnapshot(
|
|
4853
|
+
baselineSnapshotRef.current = buildSectionSnapshot(store.getState().widget.values, namespace);
|
|
4626
4854
|
}
|
|
4627
4855
|
}
|
|
4628
4856
|
else {
|
|
4629
4857
|
baselineSnapshotRef.current = null;
|
|
4858
|
+
editEntrySnapshotRef.current = null;
|
|
4630
4859
|
onSectionDirtyChange?.(sectionId, false);
|
|
4631
4860
|
}
|
|
4632
4861
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- baseline must be captured only when effectiveEditModeForDirty toggles
|
|
@@ -4652,56 +4881,103 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4652
4881
|
// and handleCancel.
|
|
4653
4882
|
const revertToOriginalValues = React.useCallback(() => {
|
|
4654
4883
|
const sectionWidgets = collectWidgets(originalSection.panels);
|
|
4655
|
-
const oldSchemaData = schemaData || contextSchemaData;
|
|
4656
4884
|
const currentStoreValues = store.getState().widget.values;
|
|
4885
|
+
const snapshot = editEntrySnapshotRef.current;
|
|
4657
4886
|
let newStoreValues = currentStoreValues;
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
const
|
|
4663
|
-
const
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4887
|
+
if (snapshot) {
|
|
4888
|
+
newStoreValues = applySectionEditSnapshot(currentStoreValues, snapshot);
|
|
4889
|
+
}
|
|
4890
|
+
else {
|
|
4891
|
+
const oldSchemaData = schemaData || contextSchemaData;
|
|
4892
|
+
const processedGeoGroups = new Set();
|
|
4893
|
+
sectionWidgets.forEach((widget) => {
|
|
4894
|
+
const originalWidgetId = widget['widget-id'];
|
|
4895
|
+
const namespacedWidgetId = namespace ? `${namespace}__${originalWidgetId}` : originalWidgetId;
|
|
4896
|
+
const widgetId = namespacedWidgetId;
|
|
4897
|
+
const originalDataPath = widget['widget-data-path'];
|
|
4898
|
+
const storeDataPath = namespace && originalDataPath
|
|
4899
|
+
? (typeof originalDataPath === 'string'
|
|
4900
|
+
? `${namespace}.${originalDataPath}`
|
|
4901
|
+
: Object.fromEntries(Object.entries(originalDataPath).map(([key, path]) => [key, `${namespace}.${path}`])))
|
|
4902
|
+
: originalDataPath;
|
|
4903
|
+
const geoConfig = widget['widget-geo-config'];
|
|
4904
|
+
if (widgetId && originalDataPath) {
|
|
4905
|
+
let oldValue;
|
|
4906
|
+
if (typeof originalDataPath === 'object') {
|
|
4907
|
+
oldValue = {};
|
|
4908
|
+
Object.entries(originalDataPath).forEach(([key, path]) => {
|
|
4909
|
+
if (typeof path === 'string') {
|
|
4910
|
+
oldValue[key] = getValueByPath(oldSchemaData, path);
|
|
4911
|
+
}
|
|
4912
|
+
});
|
|
4913
|
+
}
|
|
4914
|
+
else if (typeof originalDataPath === 'string') {
|
|
4915
|
+
oldValue = getValueByPath(oldSchemaData, originalDataPath);
|
|
4916
|
+
}
|
|
4917
|
+
if (oldValue !== undefined) {
|
|
4918
|
+
newStoreValues = setWidgetValue(newStoreValues, storeDataPath, widgetId, oldValue);
|
|
4919
|
+
if (geoConfig && typeof storeDataPath === 'string') {
|
|
4920
|
+
const groupId = getGeoGroupId(storeDataPath);
|
|
4921
|
+
const levelValue = resolveGeoWidgetLevelValue(newStoreValues, widgetId, storeDataPath, geoConfig);
|
|
4922
|
+
if (levelValue !== undefined && levelValue !== null && levelValue !== '') {
|
|
4923
|
+
newStoreValues = { ...newStoreValues, [widgetId]: levelValue };
|
|
4924
|
+
}
|
|
4925
|
+
else {
|
|
4926
|
+
const { [widgetId]: _removed, ...rest } = newStoreValues;
|
|
4927
|
+
newStoreValues = rest;
|
|
4928
|
+
}
|
|
4929
|
+
if (!processedGeoGroups.has(groupId)) {
|
|
4930
|
+
resetAndSeedGeoHierarchyFromValues(newStoreValues, storeDataPath, widgetId, groupId);
|
|
4931
|
+
processedGeoGroups.add(groupId);
|
|
4932
|
+
}
|
|
4933
|
+
if (geoConfig.parentWidgetId) {
|
|
4934
|
+
dispatch(setDataSource({ widgetId, data: [] }));
|
|
4935
|
+
}
|
|
4675
4936
|
}
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4937
|
+
else {
|
|
4938
|
+
newStoreValues = { ...newStoreValues, [widgetId]: oldValue };
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4680
4941
|
}
|
|
4681
|
-
|
|
4942
|
+
});
|
|
4943
|
+
if (hasSupportingDocuments) {
|
|
4944
|
+
const originalSupportingDocuments = originalSection['section-supporting-documents'] || [];
|
|
4945
|
+
originalSupportingDocuments.forEach((doc, index) => {
|
|
4946
|
+
const widgetId = `supporting-doc-${sectionId}-${index}`;
|
|
4947
|
+
const originalDataPath = doc['document-data-path'];
|
|
4948
|
+
const storeDataPath = namespace && originalDataPath
|
|
4949
|
+
? `${namespace}.${originalDataPath}`
|
|
4950
|
+
: originalDataPath;
|
|
4951
|
+
const oldValue = getValueByPath(oldSchemaData, originalDataPath);
|
|
4682
4952
|
newStoreValues = setWidgetValue(newStoreValues, storeDataPath, widgetId, oldValue);
|
|
4683
|
-
|
|
4684
|
-
// sets values[widgetId] during editing, and useBaseWidget.currentValue
|
|
4685
|
-
// reads values[widgetId] first before falling through to the dataPath.
|
|
4686
|
-
newStoreValues = { ...newStoreValues, [widgetId]: oldValue };
|
|
4687
|
-
}
|
|
4953
|
+
});
|
|
4688
4954
|
}
|
|
4689
|
-
});
|
|
4690
|
-
if (hasSupportingDocuments) {
|
|
4691
|
-
const originalSupportingDocuments = originalSection['section-supporting-documents'] || [];
|
|
4692
|
-
originalSupportingDocuments.forEach((doc, index) => {
|
|
4693
|
-
const widgetId = `supporting-doc-${sectionId}-${index}`;
|
|
4694
|
-
const originalDataPath = doc['document-data-path'];
|
|
4695
|
-
const storeDataPath = namespace && originalDataPath
|
|
4696
|
-
? `${namespace}.${originalDataPath}`
|
|
4697
|
-
: originalDataPath;
|
|
4698
|
-
const oldValue = getValueByPath(oldSchemaData, originalDataPath);
|
|
4699
|
-
newStoreValues = setWidgetValue(newStoreValues, storeDataPath, widgetId, oldValue);
|
|
4700
|
-
});
|
|
4701
|
-
}
|
|
4702
|
-
if (newStoreValues !== currentStoreValues) {
|
|
4703
|
-
dispatch(setValues(newStoreValues));
|
|
4704
4955
|
}
|
|
4956
|
+
const processedGeoGroups = new Set();
|
|
4957
|
+
sectionWidgets.forEach((widget) => {
|
|
4958
|
+
const geoConfig = widget['widget-geo-config'];
|
|
4959
|
+
if (!geoConfig) {
|
|
4960
|
+
return;
|
|
4961
|
+
}
|
|
4962
|
+
const originalWidgetId = widget['widget-id'];
|
|
4963
|
+
const widgetId = namespace ? `${namespace}__${originalWidgetId}` : originalWidgetId;
|
|
4964
|
+
const originalDataPath = widget['widget-data-path'];
|
|
4965
|
+
const storeDataPath = namespace && typeof originalDataPath === 'string'
|
|
4966
|
+
? `${namespace}.${originalDataPath}`
|
|
4967
|
+
: originalDataPath;
|
|
4968
|
+
if (typeof storeDataPath !== 'string') {
|
|
4969
|
+
return;
|
|
4970
|
+
}
|
|
4971
|
+
const groupId = getGeoGroupId(storeDataPath);
|
|
4972
|
+
if (!processedGeoGroups.has(groupId)) {
|
|
4973
|
+
resetAndSeedGeoHierarchyFromValues(newStoreValues, storeDataPath, widgetId, groupId);
|
|
4974
|
+
processedGeoGroups.add(groupId);
|
|
4975
|
+
}
|
|
4976
|
+
if (geoConfig.parentWidgetId) {
|
|
4977
|
+
dispatch(setDataSource({ widgetId, data: [] }));
|
|
4978
|
+
}
|
|
4979
|
+
});
|
|
4980
|
+
dispatch(setValues(newStoreValues));
|
|
4705
4981
|
}, [originalSection, schemaData, contextSchemaData, store, namespace, hasSupportingDocuments, sectionId, dispatch]);
|
|
4706
4982
|
// Handle save button click
|
|
4707
4983
|
const handleSave = async () => {
|
|
@@ -5197,7 +5473,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
5197
5473
|
color: changeRequestType === 'new' ? 'var(--owt-color-bg, #FFFFFF)' : 'var(--owt-color-error, #B91C1C)',
|
|
5198
5474
|
whiteSpace: 'nowrap',
|
|
5199
5475
|
boxShadow: changeRequestType === 'new' ? '0 2px 4px rgba(40, 167, 69, 0.3)' : 'none',
|
|
5200
|
-
}, children: changeRequestType === 'new' ? 'New' : 'Old' }))] })), jsxRuntimeExports.jsxs("div", { id: gridId, className: "section-panels", style: mode === 'RegistryView' &&
|
|
5476
|
+
}, children: changeRequestType === 'new' ? 'New' : 'Old' }))] })), jsxRuntimeExports.jsxs("div", { id: gridId, className: "section-panels", style: mode === 'RegistryView' && effectiveHideEditButton ? { paddingBottom: '30px' } : {}, children: [editableSection.panels.map((panel, index) => (jsxRuntimeExports.jsx("div", { className: "panel-wrapper", children: jsxRuntimeExports.jsx(PanelRenderer, { panel: panel, dataSourceRequestHandler: dataSourceRequestHandler, schemaData: namespacedSchemaData, onValueChange: onValueChange }) }, panel['panel-id'] || `section-panel-${index}`))), mode === 'CRView' && crViewData && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("hr", { className: "w-full", style: { height: '1px', marginTop: '20px', marginBottom: '0px', border: 'none', backgroundColor: 'var(--owt-color-border, #C4C4C4)' } }), jsxRuntimeExports.jsxs("div", { className: "cr-view-container", style: {
|
|
5201
5477
|
marginTop: '20px',
|
|
5202
5478
|
paddingBottom: '30px',
|
|
5203
5479
|
display: 'flex',
|
|
@@ -5245,7 +5521,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
5245
5521
|
fontSize: '14px',
|
|
5246
5522
|
color: 'var(--owt-color-text, #011627)',
|
|
5247
5523
|
fontWeight: 'normal',
|
|
5248
|
-
}, children: crViewData.approvedDate }))] })] })] })), mode === 'RegistryView' && !
|
|
5524
|
+
}, children: crViewData.approvedDate }))] })] })] })), mode === 'RegistryView' && !effectiveHideEditButton && (jsxRuntimeExports.jsx("hr", { className: "w-full", style: { height: '1px', marginTop: !isEditMode ? '10px' : 0, marginBottom: '14px', border: 'none', backgroundColor: 'var(--owt-color-border, #C4C4C4)' } })), mode === 'RegistryView' && !isEditMode && !effectiveHideEditButton && (jsxRuntimeExports.jsx("div", { className: "flex justify-center items-center", style: { marginBottom: '20px' }, children: jsxRuntimeExports.jsxs("button", { onClick: handleEdit, className: "font-normal inline-flex items-center gap-2 bg-transparent border-0 p-0 cursor-pointer hover:opacity-80", style: {
|
|
5249
5525
|
fontFamily: 'Roboto, sans-serif',
|
|
5250
5526
|
fontSize: '16px',
|
|
5251
5527
|
color: 'var(--owt-color-text-muted, #727474)',
|
|
@@ -6051,7 +6327,7 @@ const JSONEditorPanel = ({ section, onChange, onReset, context = 'section', }) =
|
|
|
6051
6327
|
'widget-data-format.dateConstraint': ['any', 'past-only', 'future-only'],
|
|
6052
6328
|
'widget-data-format.dateTimeConstraint': ['any', 'past-only', 'future-only'],
|
|
6053
6329
|
// Widget options
|
|
6054
|
-
'widget-data-options.action': ['show', 'hide', 'enable', 'disable'],
|
|
6330
|
+
'widget-data-options.action': ['show', 'hide', 'enable', 'disable', 'require'],
|
|
6055
6331
|
'widget-data-options.condition.operator': CONDITION_OPERATORS,
|
|
6056
6332
|
};
|
|
6057
6333
|
}, []);
|
|
@@ -7436,7 +7712,7 @@ const removeMask = (value, mask) => {
|
|
|
7436
7712
|
};
|
|
7437
7713
|
|
|
7438
7714
|
const TextInputWidget = ({ config }) => {
|
|
7439
|
-
const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
7715
|
+
const { value, formattedValue, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
7440
7716
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
7441
7717
|
// Track raw value separately for masking (to preserve unmasked value internally)
|
|
7442
7718
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
@@ -7573,7 +7849,7 @@ const TextInputWidget = ({ config }) => {
|
|
|
7573
7849
|
const label = translateConfig(widgetConfig['widget-label']);
|
|
7574
7850
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] TextDisplayWidget 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' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
|
|
7575
7851
|
}
|
|
7576
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
7852
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxRuntimeExports.jsx("input", { type: getInputType(), value: displayValue, onChange: handleChange, onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, maxLength: formatConfig?.mask ? undefined : maxLength, inputMode: formatConfig?.currency
|
|
7577
7853
|
? 'decimal'
|
|
7578
7854
|
: formatConfig?.characterType === 'numeric' || formatConfig?.characterType === 'numeric-decimal'
|
|
7579
7855
|
? 'numeric'
|
|
@@ -7596,7 +7872,7 @@ const NumberInputWidget = ({ config }) => {
|
|
|
7596
7872
|
}
|
|
7597
7873
|
return { ...config, 'widget-data-default': normalizedDefault };
|
|
7598
7874
|
}, [config]);
|
|
7599
|
-
const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config: resolvedConfig });
|
|
7875
|
+
const { value, formattedValue, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config: resolvedConfig });
|
|
7600
7876
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
7601
7877
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
7602
7878
|
const validationConfig = widgetConfig['widget-data-validation'];
|
|
@@ -7727,7 +8003,7 @@ const NumberInputWidget = ({ config }) => {
|
|
|
7727
8003
|
const display = numValue !== null ? formatNumber(numValue, formatConfig) : '';
|
|
7728
8004
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] NumberDisplayWidget 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' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: `text-base text-gray-900 font-medium ${textAlignClass}`, title: String(display ?? ''), children: display }) })] }));
|
|
7729
8005
|
}
|
|
7730
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
8006
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-1", children: [jsxRuntimeExports.jsx("input", { type: "text", inputMode: "decimal", value: displayValue, onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, onKeyDown: handleKeyDown, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, maxLength: maxLength, 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 ${textAlignClass} ${(touched && error.length > 0) || (widgetConfig['widget-required'] && (value === null || value === undefined || value === ''))
|
|
7731
8007
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
7732
8008
|
: 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }), maxLength && (jsxRuntimeExports.jsxs("span", { className: `text-xs ml-2 flex-shrink-0 ${currentLength > maxLength
|
|
7733
8009
|
? 'text-red-500'
|
|
@@ -7735,7 +8011,7 @@ const NumberInputWidget = ({ config }) => {
|
|
|
7735
8011
|
};
|
|
7736
8012
|
|
|
7737
8013
|
const BooleanWidget = ({ config }) => {
|
|
7738
|
-
const { value, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
8014
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
7739
8015
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
7740
8016
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
7741
8017
|
const representation = formatConfig?.booleanRepresentation || 'true-false';
|
|
@@ -7804,7 +8080,7 @@ const BooleanWidget = ({ config }) => {
|
|
|
7804
8080
|
}
|
|
7805
8081
|
// Render based on control type
|
|
7806
8082
|
if (controlType === 'checkbox') {
|
|
7807
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.
|
|
8083
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium leading-normal text-gray-700 md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("label", { className: "inline-flex items-baseline cursor-pointer gap-2", children: [jsxRuntimeExports.jsx("input", { type: "checkbox", checked: currentValue === true, onChange: handleCheckboxChange, onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), (currentValue === true || currentValue === false) && (jsxRuntimeExports.jsx("span", { className: "text-base text-gray-700 leading-normal", children: currentValue === true ? trueLabel : falseLabel }))] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
7808
8084
|
}
|
|
7809
8085
|
if (controlType === 'radio') {
|
|
7810
8086
|
const containerClass = orientation === 'horizontal'
|
|
@@ -7812,10 +8088,10 @@ const BooleanWidget = ({ config }) => {
|
|
|
7812
8088
|
: 'flex flex-col items-start gap-2';
|
|
7813
8089
|
const radioDisabled = !isEnabled || widgetConfig['widget-readonly'];
|
|
7814
8090
|
const optionDisabledClass = radioDisabled ? 'opacity-50 cursor-not-allowed' : '';
|
|
7815
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.
|
|
8091
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium leading-normal text-gray-700 md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: containerClass, onBlur: onBlur, children: [allowUnset && (jsxRuntimeExports.jsxs("label", { className: `inline-flex items-baseline gap-2 cursor-pointer ${optionDisabledClass}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: radioGroupName, checked: currentValue === null, onChange: () => handleRadioChange(null), disabled: radioDisabled, className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-base text-gray-700 leading-normal", children: unsetLabel })] })), jsxRuntimeExports.jsxs("label", { className: `inline-flex items-baseline gap-2 cursor-pointer ${optionDisabledClass}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: radioGroupName, checked: currentValue === true, onChange: () => handleRadioChange(true), disabled: radioDisabled, className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-base text-gray-700 leading-normal", children: trueLabel })] }), jsxRuntimeExports.jsxs("label", { className: `inline-flex items-baseline gap-2 cursor-pointer ${optionDisabledClass}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: radioGroupName, checked: currentValue === false, onChange: () => handleRadioChange(false), disabled: radioDisabled, className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-base text-gray-700 leading-normal", children: falseLabel })] })] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
7816
8092
|
}
|
|
7817
8093
|
// Toggle/switch control type
|
|
7818
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.
|
|
8094
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium leading-normal text-gray-700 sm:min-w-[150px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-3", onBlur: onBlur, children: [allowUnset && (jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleChange(null), disabled: !isEnabled || widgetConfig['widget-readonly'], className: `px-3 py-1 text-sm border ${currentValue === null
|
|
7819
8095
|
? 'bg-blue-600 text-white border-blue-600'
|
|
7820
8096
|
: 'bg-white text-gray-700 border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-50'}`, style: { borderRadius: '15px' }, children: unsetLabel })), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleChange(true), disabled: !isEnabled || widgetConfig['widget-readonly'], className: `px-3 py-1 text-sm border ${currentValue === true
|
|
7821
8097
|
? 'bg-blue-600 text-white border-blue-600'
|
|
@@ -7825,7 +8101,7 @@ const BooleanWidget = ({ config }) => {
|
|
|
7825
8101
|
};
|
|
7826
8102
|
|
|
7827
8103
|
const DateInputWidget = ({ config }) => {
|
|
7828
|
-
const { value, error, touched, isEnabled, onChange, onBlur, setError, config: widgetConfig, } = useBaseWidget({ config });
|
|
8104
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, setError, config: widgetConfig, } = useBaseWidget({ config });
|
|
7829
8105
|
const formValues = reactRedux.useSelector((state) => state.widget.values);
|
|
7830
8106
|
const { translateConfig } = useWidgetTranslation();
|
|
7831
8107
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
@@ -8026,7 +8302,7 @@ const DateInputWidget = ({ config }) => {
|
|
|
8026
8302
|
}
|
|
8027
8303
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] DateDisplayWidget 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' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
|
|
8028
8304
|
}
|
|
8029
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
8305
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("input", { type: inputType, value: getDisplayValue(), onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, min: inputMethod === 'picker' ? effectiveMinDate : undefined, max: inputMethod === 'picker' ? effectiveMaxDate : undefined, 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 ${showValidationError || showRequiredError
|
|
8030
8306
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
8031
8307
|
: 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }), showValidationError && jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] })] })] }) }));
|
|
8032
8308
|
};
|
|
@@ -8316,7 +8592,7 @@ const validateDateTimeConstraints = (date, minDateTime, maxDateTime, constraint)
|
|
|
8316
8592
|
};
|
|
8317
8593
|
|
|
8318
8594
|
const DateTimeInputWidget = ({ config }) => {
|
|
8319
|
-
const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
8595
|
+
const { value, formattedValue, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
8320
8596
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8321
8597
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
8322
8598
|
const optionsConfig = widgetConfig['widget-data-options'];
|
|
@@ -8469,29 +8745,31 @@ const DateTimeInputWidget = ({ config }) => {
|
|
|
8469
8745
|
}
|
|
8470
8746
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] DateTimeDisplayWidget 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' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
|
|
8471
8747
|
}
|
|
8472
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
8748
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("input", { type: inputType, value: getDisplayValue(), onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, min: inputMethod === 'picker' ? effectiveMinDateTime : undefined, max: inputMethod === 'picker' ? effectiveMaxDateTime : undefined, 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 === ''))
|
|
8473
8749
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
8474
8750
|
: 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
8475
8751
|
};
|
|
8476
8752
|
|
|
8477
8753
|
const SelectWidget = ({ config }) => {
|
|
8478
|
-
const { value, error, touched, isEnabled, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
8754
|
+
const { value, geoDisplayLabel, error, touched, isEnabled, isRequired, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
8479
8755
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8480
8756
|
// For readonly mode, render as display text showing only the selected label
|
|
8481
8757
|
if (widgetConfig['widget-readonly']) {
|
|
8482
8758
|
const label = translateConfig(widgetConfig['widget-label']);
|
|
8483
8759
|
// Find the selected option's label
|
|
8484
8760
|
const selectedOption = dataSourceOptions.find((option) => option.value === value);
|
|
8485
|
-
const displayValue = selectedOption
|
|
8761
|
+
const displayValue = selectedOption
|
|
8762
|
+
? translateConfig(selectedOption.label)
|
|
8763
|
+
: (geoDisplayLabel || (value != null && value !== '' ? translateConfig(String(value)) : '-'));
|
|
8486
8764
|
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' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
|
|
8487
8765
|
}
|
|
8488
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
8766
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("select", { value: value || '', onChange: (e) => onChange(e.target.value === '' ? undefined : 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 === ''))
|
|
8489
8767
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
8490
|
-
: '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] }))] })] }) }));
|
|
8768
|
+
: '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: translateConfig(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] }))] })] }) }));
|
|
8491
8769
|
};
|
|
8492
8770
|
|
|
8493
8771
|
const RadioWidget = ({ config }) => {
|
|
8494
|
-
const { value, error, touched, isEnabled, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
8772
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
8495
8773
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8496
8774
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
8497
8775
|
const layout = formatConfig?.layout || widgetConfig['widget-orientation'] || 'vertical';
|
|
@@ -8554,14 +8832,16 @@ const RadioWidget = ({ config }) => {
|
|
|
8554
8832
|
if (widgetConfig['widget-readonly']) {
|
|
8555
8833
|
const label = translateConfig(widgetConfig['widget-label']);
|
|
8556
8834
|
const selectedOption = processedOptions.find(opt => opt.value === currentValue);
|
|
8557
|
-
const displayValue = selectedOption
|
|
8835
|
+
const displayValue = selectedOption
|
|
8836
|
+
? translateConfig(selectedOption.label)
|
|
8837
|
+
: (allowUnset && currentValue === null ? '-' : '');
|
|
8558
8838
|
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' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
|
|
8559
8839
|
}
|
|
8560
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
8840
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("div", { className: layoutConfig.className, style: layoutConfig.style, onBlur: onBlur, 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, onChange: handleUnset, 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) => (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: option.value, checked: currentValue === option.value, onChange: (e) => handleChange(option.value), 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) })] }, option.value)))] })) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
8561
8841
|
};
|
|
8562
8842
|
|
|
8563
8843
|
const CheckboxWidget = ({ config }) => {
|
|
8564
|
-
const { value, error, touched, isEnabled, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
8844
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
8565
8845
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8566
8846
|
const hasDataSource = !!widgetConfig['widget-data-source'];
|
|
8567
8847
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
@@ -8576,7 +8856,7 @@ const CheckboxWidget = ({ config }) => {
|
|
|
8576
8856
|
const displayValue = isChecked ? 'Yes' : 'No';
|
|
8577
8857
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] CheckboxDisplayWidget 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' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
|
|
8578
8858
|
}
|
|
8579
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.
|
|
8859
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium leading-normal text-gray-700 md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("label", { className: "inline-flex cursor-pointer items-baseline gap-2", children: [jsxRuntimeExports.jsx("input", { type: "checkbox", checked: isChecked, onChange: (e) => onChange(e.target.checked), onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), jsxRuntimeExports.jsx("span", { className: "text-base leading-normal text-gray-700", children: isChecked ? 'Yes' : 'No' })] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
8580
8860
|
}
|
|
8581
8861
|
// Multiple checkboxes (with data source) - for array values
|
|
8582
8862
|
// Process and sort options if needed
|
|
@@ -8646,7 +8926,7 @@ const CheckboxWidget = ({ config }) => {
|
|
|
8646
8926
|
: '-';
|
|
8647
8927
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-3 CheckboxDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-sm text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
|
|
8648
8928
|
}
|
|
8649
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.
|
|
8929
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium leading-normal text-gray-700 md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("div", { className: layoutConfig.className, style: layoutConfig.style, onBlur: onBlur, children: loading ? (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500", children: translate('common.loading') })) : (processedOptions.map((option) => (jsxRuntimeExports.jsxs("label", { className: `inline-flex cursor-pointer items-baseline gap-2 ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : ''}`, children: [jsxRuntimeExports.jsx("input", { type: "checkbox", value: option.value, checked: selectedValues.includes(option.value), onChange: (e) => handleCheckboxChange(option.value, e.target.checked), disabled: !isEnabled || widgetConfig['widget-readonly'], className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), jsxRuntimeExports.jsx("span", { className: "text-base leading-normal text-gray-700", children: translateConfig(option.label) })] }, option.value)))) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
8650
8930
|
};
|
|
8651
8931
|
|
|
8652
8932
|
const SimpleTableWidget = ({ config }) => {
|
|
@@ -8697,7 +8977,7 @@ const SimpleTableWidget = ({ config }) => {
|
|
|
8697
8977
|
};
|
|
8698
8978
|
|
|
8699
8979
|
const ArrayWidget = ({ config }) => {
|
|
8700
|
-
const { value, error, touched, isEnabled, onChange, config: widgetConfig, } = useBaseWidget({ config });
|
|
8980
|
+
const { value, error, touched, isEnabled, isRequired, onChange, config: widgetConfig, } = useBaseWidget({ config });
|
|
8701
8981
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8702
8982
|
const items = Array.isArray(value) ? value : [];
|
|
8703
8983
|
const itemConfig = widgetConfig['widget-item'];
|
|
@@ -8721,7 +9001,7 @@ const ArrayWidget = ({ config }) => {
|
|
|
8721
9001
|
newItems[index] = newValue;
|
|
8722
9002
|
onChange(newItems);
|
|
8723
9003
|
};
|
|
8724
|
-
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px]", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start mb-2", children: [jsxRuntimeExports.
|
|
9004
|
+
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px]", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start mb-2", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0 flex justify-between items-center", children: [jsxRuntimeExports.jsx("div", { className: "flex-1" }), operations.add && !isReadonly && isEnabled && (jsxRuntimeExports.jsx("button", { type: "button", onClick: addItem, className: "px-3 py-1 text-sm bg-blue-500 text-white hover:bg-blue-600", style: { borderRadius: '15px' }, children: addLabel }))] })] }), items.length === 0 ? (jsxRuntimeExports.jsxs("div", { className: "text-gray-500 text-sm py-4 text-center border border-gray-300 rounded", children: [translate('common.noItems'), ". ", operations.add && !isReadonly && translate('common.clickToAdd', { label: addLabel })] })) : (jsxRuntimeExports.jsx("div", { className: "space-y-2", children: items.map((itemValue, index) => {
|
|
8725
9005
|
({
|
|
8726
9006
|
...itemConfig,
|
|
8727
9007
|
'widget-id': `${widgetConfig['widget-id']}-item-${index}`,
|
|
@@ -8732,7 +9012,7 @@ const ArrayWidget = ({ config }) => {
|
|
|
8732
9012
|
};
|
|
8733
9013
|
|
|
8734
9014
|
const IterableAccordionWidget = ({ config }) => {
|
|
8735
|
-
const { value, error, touched, isEnabled, onChange, config: widgetConfig, } = useBaseWidget({ config });
|
|
9015
|
+
const { value, error, touched, isEnabled, isRequired, onChange, config: widgetConfig, } = useBaseWidget({ config });
|
|
8736
9016
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8737
9017
|
const items = Array.isArray(value) ? value : [];
|
|
8738
9018
|
const itemConfig = widgetConfig['widget-item'];
|
|
@@ -8781,7 +9061,7 @@ const IterableAccordionWidget = ({ config }) => {
|
|
|
8781
9061
|
newItems[index] = newValue;
|
|
8782
9062
|
onChange(newItems);
|
|
8783
9063
|
};
|
|
8784
|
-
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px]", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start mb-2", children: [jsxRuntimeExports.
|
|
9064
|
+
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px]", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start mb-2", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0 flex justify-between items-center", children: [jsxRuntimeExports.jsx("div", { className: "flex-1" }), operations.add && !isReadonly && isEnabled && (jsxRuntimeExports.jsx("button", { type: "button", onClick: addItem, className: "px-3 py-1 text-sm bg-blue-500 text-white hover:bg-blue-600", style: { borderRadius: '15px' }, children: addLabel }))] })] }), items.length === 0 ? (jsxRuntimeExports.jsxs("div", { className: "text-gray-500 text-sm py-4 text-center border border-gray-300 rounded", children: [translate('common.noItems'), ". ", operations.add && !isReadonly && translate('common.clickToAdd', { label: addLabel })] })) : (jsxRuntimeExports.jsx("div", { className: "space-y-2", children: items.map((itemValue, index) => {
|
|
8785
9065
|
const isCollapsed = collapsedItems[index] ?? defaultCollapsed;
|
|
8786
9066
|
const parentPath = widgetConfig['widget-data-path'];
|
|
8787
9067
|
const childPath = itemConfig['widget-data-path'];
|
|
@@ -8820,7 +9100,7 @@ const IterableAccordionWidget = ({ config }) => {
|
|
|
8820
9100
|
};
|
|
8821
9101
|
|
|
8822
9102
|
const PhoneInputWidget = ({ config }) => {
|
|
8823
|
-
const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
9103
|
+
const { value, formattedValue, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
8824
9104
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8825
9105
|
// Use formatted value if available, otherwise raw value
|
|
8826
9106
|
const displayValue = formattedValue !== undefined && formattedValue !== value
|
|
@@ -8831,13 +9111,13 @@ const PhoneInputWidget = ({ config }) => {
|
|
|
8831
9111
|
const label = translateConfig(widgetConfig['widget-label']);
|
|
8832
9112
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] PhoneDisplayWidget 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' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue || ''), children: displayValue || '-' }) })] }));
|
|
8833
9113
|
}
|
|
8834
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
9114
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("input", { type: "tel", value: displayValue, onChange: (e) => onChange(e.target.value), onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: translateConfig(widgetConfig['widget-data-placeholder']), 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 === ''))
|
|
8835
9115
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
8836
9116
|
: 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
8837
9117
|
};
|
|
8838
9118
|
|
|
8839
9119
|
const CurrencyInputWidget = ({ config }) => {
|
|
8840
|
-
const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
9120
|
+
const { value, formattedValue, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
8841
9121
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8842
9122
|
// For input, use raw numeric value; formatted value is for display only
|
|
8843
9123
|
const numericValue = typeof value === 'number' ? value : (value ? parseFloat(String(value)) : '');
|
|
@@ -8859,7 +9139,7 @@ const CurrencyInputWidget = ({ config }) => {
|
|
|
8859
9139
|
const display = formattedValue || (value !== null && value !== undefined ? String(value) : '-');
|
|
8860
9140
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] CurrencyDisplayWidget 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' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(display ?? ''), children: display }) })] }));
|
|
8861
9141
|
}
|
|
8862
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
9142
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: "relative", children: [jsxRuntimeExports.jsx("input", { type: "text", inputMode: "decimal", value: numericValue, onChange: handleChange, onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: translateConfig(widgetConfig['widget-data-placeholder']), 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 === null || value === undefined || value === ''))
|
|
8863
9143
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
8864
9144
|
: 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }), formattedValue && formattedValue !== String(value) && (jsxRuntimeExports.jsx("span", { className: "absolute right-3 top-2 text-gray-500 text-sm", children: formattedValue }))] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
8865
9145
|
};
|
|
@@ -9702,6 +9982,7 @@ const TableWidget = ({ config }) => {
|
|
|
9702
9982
|
}, children: translate('common.cancel') || 'Cancel' })] }) })] }))] })] }) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-sm mt-1", style: { color: 'var(--owt-widget-error-color, #B91C1C)' }, children: error[0] }))] })] }));
|
|
9703
9983
|
};
|
|
9704
9984
|
|
|
9985
|
+
const isUnsetRowValue = (value) => value === null || value === undefined || value === '';
|
|
9705
9986
|
// Display select value label in view mode
|
|
9706
9987
|
const SelectDisplayValue = ({ config, value }) => {
|
|
9707
9988
|
const { dataSourceOptions, loading } = useBaseWidget({ config });
|
|
@@ -9734,7 +10015,6 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9734
10015
|
const { value, error, touched, isEnabled, onChange, config: widgetConfig } = useBaseWidget({ config });
|
|
9735
10016
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
9736
10017
|
const dispatch = reactRedux.useDispatch();
|
|
9737
|
-
const storeValues = reactRedux.useSelector((state) => state.widget?.values ?? {});
|
|
9738
10018
|
const rows = Array.isArray(value) ? value : [];
|
|
9739
10019
|
const columns = widgetConfig['widget-data-columns'] || [];
|
|
9740
10020
|
const operations = widgetConfig['widget-data-operations'] || {};
|
|
@@ -9766,7 +10046,12 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9766
10046
|
const emptyRow = {};
|
|
9767
10047
|
columns.forEach((col) => {
|
|
9768
10048
|
const key = col['column-key'];
|
|
9769
|
-
|
|
10049
|
+
if (col['widget-data-default'] !== undefined) {
|
|
10050
|
+
emptyRow[key] = col['widget-data-default'];
|
|
10051
|
+
}
|
|
10052
|
+
else if (col.widget === 'checkbox') {
|
|
10053
|
+
emptyRow[key] = false;
|
|
10054
|
+
}
|
|
9770
10055
|
});
|
|
9771
10056
|
return emptyRow;
|
|
9772
10057
|
}, [columns]);
|
|
@@ -9819,17 +10104,48 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9819
10104
|
const updateField = React.useCallback((columnKey, newValue) => {
|
|
9820
10105
|
setFormData((prev) => ({ ...prev, [columnKey]: newValue }));
|
|
9821
10106
|
}, []);
|
|
9822
|
-
const
|
|
9823
|
-
|
|
10107
|
+
const membersWidgetId = widgetConfig['widget-id'];
|
|
10108
|
+
const dialogStoreValues = reactRedux.useSelector((state) => {
|
|
10109
|
+
if (dialogSessionId <= 0) {
|
|
10110
|
+
return {};
|
|
10111
|
+
}
|
|
10112
|
+
const values = state.widget?.values ?? {};
|
|
10113
|
+
const row = {};
|
|
10114
|
+
columns.forEach((col) => {
|
|
10115
|
+
const k = col['column-key'];
|
|
10116
|
+
const wid = `${membersWidgetId}-dlg-${dialogSessionId}-${k}`;
|
|
10117
|
+
if (values[wid] !== undefined) {
|
|
10118
|
+
row[k] = values[wid];
|
|
10119
|
+
}
|
|
10120
|
+
});
|
|
10121
|
+
return row;
|
|
10122
|
+
}, (a, b) => JSON.stringify(a) === JSON.stringify(b));
|
|
10123
|
+
const buildDialogRowValues = React.useCallback((storeSlice) => {
|
|
10124
|
+
const row = { ...formData };
|
|
9824
10125
|
columns.forEach((col) => {
|
|
9825
10126
|
const k = col['column-key'];
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
merged[k] = fromStore;
|
|
10127
|
+
if (storeSlice[k] !== undefined) {
|
|
10128
|
+
row[k] = storeSlice[k];
|
|
10129
|
+
}
|
|
9830
10130
|
});
|
|
9831
|
-
return
|
|
9832
|
-
}, [formData, columns
|
|
10131
|
+
return row;
|
|
10132
|
+
}, [formData, columns]);
|
|
10133
|
+
const dialogRowValues = React.useMemo(() => buildDialogRowValues(dialogStoreValues), [buildDialogRowValues, dialogStoreValues]);
|
|
10134
|
+
const collectMergedRowPayload = React.useCallback(() => buildDialogRowValues(dialogStoreValues), [buildDialogRowValues, dialogStoreValues]);
|
|
10135
|
+
const finalizeDialogRowPayload = React.useCallback((raw) => {
|
|
10136
|
+
const result = {};
|
|
10137
|
+
columns.forEach((col) => {
|
|
10138
|
+
const key = col['column-key'];
|
|
10139
|
+
if (!shouldShowWidget(col['widget-data-options'], raw)) {
|
|
10140
|
+
return;
|
|
10141
|
+
}
|
|
10142
|
+
const val = raw[key];
|
|
10143
|
+
if (!isUnsetRowValue(val)) {
|
|
10144
|
+
result[key] = val;
|
|
10145
|
+
}
|
|
10146
|
+
});
|
|
10147
|
+
return result;
|
|
10148
|
+
}, [columns]);
|
|
9833
10149
|
const saveDialog = React.useCallback(() => {
|
|
9834
10150
|
const payload = collectMergedRowPayload();
|
|
9835
10151
|
let hasErrors = false;
|
|
@@ -9839,8 +10155,11 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9839
10155
|
const isColReadonly = isReadonly || col['widget-readonly'] === true;
|
|
9840
10156
|
if (isColReadonly)
|
|
9841
10157
|
return;
|
|
10158
|
+
if (!shouldShowWidget(col['widget-data-options'], payload))
|
|
10159
|
+
return;
|
|
9842
10160
|
const cellValue = payload[key];
|
|
9843
|
-
const
|
|
10161
|
+
const isRequired = shouldRequireWidget(col['widget-data-options'], payload, col['widget-required']);
|
|
10162
|
+
const validationErrors = validateWidget(cellValue, col['widget-data-validation'], isRequired);
|
|
9844
10163
|
if (validationErrors && validationErrors.length > 0) {
|
|
9845
10164
|
hasErrors = true;
|
|
9846
10165
|
dispatch(setError({ widgetId: cellWidgetId, errors: validationErrors }));
|
|
@@ -9853,8 +10172,9 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9853
10172
|
if (hasErrors) {
|
|
9854
10173
|
return;
|
|
9855
10174
|
}
|
|
10175
|
+
const cleaned = finalizeDialogRowPayload(payload);
|
|
9856
10176
|
if (dialogMode === 'add') {
|
|
9857
|
-
const savedRow = { ...
|
|
10177
|
+
const savedRow = { ...cleaned, edit_action: 'ADD' };
|
|
9858
10178
|
onChange([...rows, savedRow]);
|
|
9859
10179
|
closeDialog();
|
|
9860
10180
|
return;
|
|
@@ -9864,11 +10184,18 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9864
10184
|
const currentRow = newRows[activeRowIndex] || {};
|
|
9865
10185
|
const wasDeleted = currentRow.edit_action === 'DELETE';
|
|
9866
10186
|
const editAction = wasDeleted ? 'UPDATE' : (currentRow.edit_action ?? 'UPDATE');
|
|
9867
|
-
|
|
10187
|
+
const merged = { ...currentRow, ...cleaned, edit_action: editAction };
|
|
10188
|
+
columns.forEach((col) => {
|
|
10189
|
+
const key = col['column-key'];
|
|
10190
|
+
if (!(key in cleaned)) {
|
|
10191
|
+
delete merged[key];
|
|
10192
|
+
}
|
|
10193
|
+
});
|
|
10194
|
+
newRows[activeRowIndex] = merged;
|
|
9868
10195
|
onChange(newRows);
|
|
9869
10196
|
closeDialog();
|
|
9870
10197
|
}
|
|
9871
|
-
}, [collectMergedRowPayload, dialogMode, onChange, rows, closeDialog, activeRowIndex, columns, dialogFieldWidgetId, isReadonly, dispatch]);
|
|
10198
|
+
}, [collectMergedRowPayload, finalizeDialogRowPayload, dialogMode, onChange, rows, closeDialog, activeRowIndex, columns, dialogFieldWidgetId, isReadonly, dispatch]);
|
|
9872
10199
|
const deleteRow = React.useCallback((rowIndex) => {
|
|
9873
10200
|
const newRows = rows.filter((_, i) => i !== rowIndex);
|
|
9874
10201
|
onChange(newRows);
|
|
@@ -9955,9 +10282,12 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9955
10282
|
lineHeight: 1,
|
|
9956
10283
|
}, "aria-label": "Close", children: "\u00D7" })] }), jsxRuntimeExports.jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", style: { maxHeight: '70vh', overflow: 'auto' }, children: columns.map((col) => {
|
|
9957
10284
|
const key = col['column-key'];
|
|
10285
|
+
if (!shouldShowWidget(col['widget-data-options'], dialogRowValues)) {
|
|
10286
|
+
return null;
|
|
10287
|
+
}
|
|
9958
10288
|
const widgetType = col.widget || 'text';
|
|
9959
10289
|
const cellWidgetId = dialogFieldWidgetId(key);
|
|
9960
|
-
const initialValue = formData[key] ?? col['widget-data-default']
|
|
10290
|
+
const initialValue = formData[key] ?? col['widget-data-default'];
|
|
9961
10291
|
const fieldConfig = {
|
|
9962
10292
|
...col,
|
|
9963
10293
|
widget: widgetType,
|
|
@@ -9967,6 +10297,8 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9967
10297
|
'widget-readonly': isReadonly || col['widget-readonly'] === true,
|
|
9968
10298
|
'widget-data-path': undefined,
|
|
9969
10299
|
'widget-data-default': initialValue,
|
|
10300
|
+
'widget-data-options': undefined,
|
|
10301
|
+
'widget-required': shouldRequireWidget(col['widget-data-options'], dialogRowValues, col['widget-required']),
|
|
9970
10302
|
};
|
|
9971
10303
|
return (jsxRuntimeExports.jsx("div", { className: "min-w-0", children: jsxRuntimeExports.jsx(WidgetRenderer, { config: fieldConfig, schemaData: { [cellWidgetId]: initialValue }, onValueChange: (_widgetId, newValue) => updateField(key, newValue) }) }, `${dialogSessionId}-${key}`));
|
|
9972
10304
|
}) }, `dialog-fields-${dialogSessionId}`), jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-3 mt-6", children: [jsxRuntimeExports.jsx("button", { type: "button", onClick: closeDialog, className: "px-4 py-2 text-sm font-medium", style: {
|
|
@@ -10164,7 +10496,7 @@ const ProfileWidget = ({ config }) => {
|
|
|
10164
10496
|
const TextAreaWidget = ({ config }) => {
|
|
10165
10497
|
// Check readonly early from original config
|
|
10166
10498
|
const isReadonly = config['widget-readonly'] || false;
|
|
10167
|
-
const { value, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
10499
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
10168
10500
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
10169
10501
|
const formatConfig = widgetConfig['widget-data-format'] || {};
|
|
10170
10502
|
const validationConfig = widgetConfig['widget-data-validation'] || {};
|
|
@@ -10212,7 +10544,6 @@ const TextAreaWidget = ({ config }) => {
|
|
|
10212
10544
|
? translateConfig(widgetConfig['widget-label'])
|
|
10213
10545
|
: '';
|
|
10214
10546
|
// Check if required
|
|
10215
|
-
const isRequired = widgetConfig['widget-required'] || false;
|
|
10216
10547
|
// Error display
|
|
10217
10548
|
const hasError = touched && error && error.length > 0;
|
|
10218
10549
|
const errorMessage = hasError ? error[0] : '';
|
|
@@ -10230,7 +10561,7 @@ const TextAreaWidget = ({ config }) => {
|
|
|
10230
10561
|
border: 'none',
|
|
10231
10562
|
}, children: displayValue }) })] }));
|
|
10232
10563
|
}
|
|
10233
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
10564
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: label, required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { style: { position: 'relative' }, children: [jsxRuntimeExports.jsx("textarea", { id: widgetConfig['widget-id'], rows: rows, value: getStringValue(), onChange: handleChange, onBlur: onBlur, disabled: !isEnabled, placeholder: placeholder, className: `w-full px-3 py-2 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${hasError
|
|
10234
10565
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
10235
10566
|
: 'border-gray-300'} ${!isEnabled ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: {
|
|
10236
10567
|
borderRadius: '10px',
|
|
@@ -11708,7 +12039,7 @@ const ResultsTable = ({ rows, selectedRowKey, onRowClick, onRowDoubleClick, }) =
|
|
|
11708
12039
|
}) })] }) }));
|
|
11709
12040
|
};
|
|
11710
12041
|
const RegisterLookupWidget = ({ config }) => {
|
|
11711
|
-
const { value, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
12042
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
11712
12043
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
11713
12044
|
const { dataSourceRequestHandler } = useWidgetContext();
|
|
11714
12045
|
const dataSource = widgetConfig['widget-data-source'];
|
|
@@ -11871,7 +12202,7 @@ const RegisterLookupWidget = ({ config }) => {
|
|
|
11871
12202
|
onChange(null);
|
|
11872
12203
|
setAppliedRecord(null);
|
|
11873
12204
|
setPendingRow(null);
|
|
11874
|
-
}, className: "text-sm underline text-red-500 p-0 border-0 bg-transparent cursor-pointer focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-1 rounded", children: translate('common.remove', { defaultValue: 'Remove' }) })] })), !isReadonly && touched && error.length > 0 && jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] })] })) : !isReadonly ? (jsxRuntimeExports.jsxs("div", { className: "w-full min-w-0", children: [jsxRuntimeExports.jsxs("button", { type: "button", disabled: !isEnabled, onClick: openLookup, title: actionLabel, className: `flex items-center gap-2 w-full sm:w-[180px] max-w-full px-3 h-[30px] text-sm border rounded-[10px] shadow-sm transition-colors ${hasError ? 'border-red-500 text-gray-700' : 'border-gray-300 text-gray-700'} ${!isEnabled ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-white cursor-pointer'}`, children: [jsxRuntimeExports.jsx("img", { src: img$g, alt: "", className: "w-4 h-4 opacity-50 flex-shrink-0" }), jsxRuntimeExports.jsx("span", { className: "truncate", children: actionLabel }),
|
|
12205
|
+
}, className: "text-sm underline text-red-500 p-0 border-0 bg-transparent cursor-pointer focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-1 rounded", children: translate('common.remove', { defaultValue: 'Remove' }) })] })), !isReadonly && touched && error.length > 0 && jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] })] })) : !isReadonly ? (jsxRuntimeExports.jsxs("div", { className: "w-full min-w-0", children: [jsxRuntimeExports.jsxs("button", { type: "button", disabled: !isEnabled, onClick: openLookup, title: actionLabel, className: `flex items-center gap-2 w-full sm:w-[180px] max-w-full px-3 h-[30px] text-sm border rounded-[10px] shadow-sm transition-colors ${hasError ? 'border-red-500 text-gray-700' : 'border-gray-300 text-gray-700'} ${!isEnabled ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-white cursor-pointer'}`, children: [jsxRuntimeExports.jsx("img", { src: img$g, alt: "", className: "w-4 h-4 opacity-50 flex-shrink-0" }), jsxRuntimeExports.jsx("span", { className: "min-w-0 truncate", children: actionLabel }), isRequired && jsxRuntimeExports.jsx("span", { className: "shrink-0 text-red-500", children: "*" })] }), touched && error.length > 0 && jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] })] })) : null, !isReadonly && isOpen && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-50", style: { backgroundColor: 'rgba(0,0,0,0.5)' }, onClick: () => { setIsOpen(false); onBlur(); } }), jsxRuntimeExports.jsxs("div", { className: "flex flex-col overflow-hidden", style: {
|
|
11875
12206
|
position: 'fixed',
|
|
11876
12207
|
top: modalPos.y,
|
|
11877
12208
|
left: modalPos.x,
|
|
@@ -11900,6 +12231,288 @@ const RegisterLookupWidget = ({ config }) => {
|
|
|
11900
12231
|
: translate('common.searchHint', { defaultValue: 'Type and press Enter or click search' }) })) : (jsxRuntimeExports.jsx(ResultsTable, { rows: searchResults, selectedRowKey: pendingRow?.internal_record_id ?? null, onRowClick: setPendingRow, onRowDoubleClick: applySelection })) }), jsxRuntimeExports.jsxs("div", { className: `flex-shrink-0 flex flex-wrap items-center gap-3 px-5 py-3 border-t border-gray-200 ${totalCount !== null ? 'justify-between' : 'justify-end'}`, children: [totalCount !== null && (jsxRuntimeExports.jsx(PaginationFooter, { embedded: true, currentPage: currentPage, totalPages: totalPages, totalCount: totalCount, pageSize: pageSize, onPageChange: (page) => runSearch(searchText, page), onPrev: () => currentPage > 1 && runSearch(searchText, currentPage - 1), onNext: () => currentPage < totalPages && runSearch(searchText, currentPage + 1), translate: translate })), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => pendingRow && applySelection(pendingRow), disabled: !pendingRow, className: "px-4 h-9 text-sm font-medium rounded-[10px] text-white disabled:opacity-40 disabled:cursor-not-allowed flex-shrink-0", style: { backgroundColor: 'var(--owt-color-info, #2563eb)' }, children: selectRecordLabel })] })] })] }))] }));
|
|
11901
12232
|
};
|
|
11902
12233
|
|
|
12234
|
+
const MultiSelectWidget = ({ config }) => {
|
|
12235
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
12236
|
+
const { translate, translateConfig } = useWidgetTranslation();
|
|
12237
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
12238
|
+
const [isListPopupOpen, setIsListPopupOpen] = React.useState(false);
|
|
12239
|
+
const [searchQuery, setSearchQuery] = React.useState('');
|
|
12240
|
+
const [dropdownPosition, setDropdownPosition] = React.useState(null);
|
|
12241
|
+
const [listPopupPosition, setListPopupPosition] = React.useState(null);
|
|
12242
|
+
const [mounted, setMounted] = React.useState(false);
|
|
12243
|
+
const containerRef = React.useRef(null);
|
|
12244
|
+
const triggerRef = React.useRef(null);
|
|
12245
|
+
const dropdownRef = React.useRef(null);
|
|
12246
|
+
const listPopupRef = React.useRef(null);
|
|
12247
|
+
const moreButtonRef = React.useRef(null);
|
|
12248
|
+
const searchInputRef = React.useRef(null);
|
|
12249
|
+
const formatConfig = widgetConfig['widget-data-format'];
|
|
12250
|
+
const sortOptions = formatConfig?.sortOptions ?? false;
|
|
12251
|
+
React.useEffect(() => {
|
|
12252
|
+
setMounted(true);
|
|
12253
|
+
}, []);
|
|
12254
|
+
const updateDropdownPosition = React.useCallback(() => {
|
|
12255
|
+
const trigger = triggerRef.current;
|
|
12256
|
+
if (!trigger)
|
|
12257
|
+
return;
|
|
12258
|
+
const rect = trigger.getBoundingClientRect();
|
|
12259
|
+
const gap = 4;
|
|
12260
|
+
const spaceBelow = window.innerHeight - rect.bottom - gap;
|
|
12261
|
+
const spaceAbove = rect.top - gap;
|
|
12262
|
+
const openDown = spaceBelow >= 160 || spaceBelow >= spaceAbove;
|
|
12263
|
+
const availableSpace = openDown ? spaceBelow : spaceAbove;
|
|
12264
|
+
const maxHeight = Math.min(320, Math.max(160, availableSpace - 8));
|
|
12265
|
+
setDropdownPosition(openDown
|
|
12266
|
+
? {
|
|
12267
|
+
top: rect.bottom + gap,
|
|
12268
|
+
left: rect.left,
|
|
12269
|
+
width: rect.width,
|
|
12270
|
+
maxHeight,
|
|
12271
|
+
placement: 'bottom',
|
|
12272
|
+
}
|
|
12273
|
+
: {
|
|
12274
|
+
bottom: window.innerHeight - rect.top + gap,
|
|
12275
|
+
left: rect.left,
|
|
12276
|
+
width: rect.width,
|
|
12277
|
+
maxHeight,
|
|
12278
|
+
placement: 'top',
|
|
12279
|
+
});
|
|
12280
|
+
}, []);
|
|
12281
|
+
const updateListPopupPosition = React.useCallback(() => {
|
|
12282
|
+
const anchor = moreButtonRef.current;
|
|
12283
|
+
if (!anchor)
|
|
12284
|
+
return;
|
|
12285
|
+
const rect = anchor.getBoundingClientRect();
|
|
12286
|
+
const gap = 4;
|
|
12287
|
+
const spaceBelow = window.innerHeight - rect.bottom - gap;
|
|
12288
|
+
const spaceAbove = rect.top - gap;
|
|
12289
|
+
const openDown = spaceBelow >= 120 || spaceBelow >= spaceAbove;
|
|
12290
|
+
const availableSpace = openDown ? spaceBelow : spaceAbove;
|
|
12291
|
+
const maxHeight = Math.min(280, Math.max(120, availableSpace - 8));
|
|
12292
|
+
setListPopupPosition(openDown
|
|
12293
|
+
? {
|
|
12294
|
+
top: rect.bottom + gap,
|
|
12295
|
+
left: rect.left,
|
|
12296
|
+
width: Math.max(rect.width, 220),
|
|
12297
|
+
maxHeight,
|
|
12298
|
+
placement: 'bottom',
|
|
12299
|
+
}
|
|
12300
|
+
: {
|
|
12301
|
+
bottom: window.innerHeight - rect.top + gap,
|
|
12302
|
+
left: rect.left,
|
|
12303
|
+
width: Math.max(rect.width, 220),
|
|
12304
|
+
maxHeight,
|
|
12305
|
+
placement: 'top',
|
|
12306
|
+
});
|
|
12307
|
+
}, []);
|
|
12308
|
+
React.useEffect(() => {
|
|
12309
|
+
if (!isOpen) {
|
|
12310
|
+
setDropdownPosition(null);
|
|
12311
|
+
setSearchQuery('');
|
|
12312
|
+
return;
|
|
12313
|
+
}
|
|
12314
|
+
updateDropdownPosition();
|
|
12315
|
+
const handleResize = () => updateDropdownPosition();
|
|
12316
|
+
window.addEventListener('resize', handleResize);
|
|
12317
|
+
return () => {
|
|
12318
|
+
window.removeEventListener('resize', handleResize);
|
|
12319
|
+
};
|
|
12320
|
+
}, [isOpen, updateDropdownPosition]);
|
|
12321
|
+
React.useEffect(() => {
|
|
12322
|
+
if (!isListPopupOpen) {
|
|
12323
|
+
setListPopupPosition(null);
|
|
12324
|
+
return;
|
|
12325
|
+
}
|
|
12326
|
+
updateListPopupPosition();
|
|
12327
|
+
const handleResize = () => updateListPopupPosition();
|
|
12328
|
+
window.addEventListener('resize', handleResize);
|
|
12329
|
+
return () => {
|
|
12330
|
+
window.removeEventListener('resize', handleResize);
|
|
12331
|
+
};
|
|
12332
|
+
}, [isListPopupOpen, updateListPopupPosition]);
|
|
12333
|
+
React.useEffect(() => {
|
|
12334
|
+
if (!isOpen && !isListPopupOpen)
|
|
12335
|
+
return;
|
|
12336
|
+
const handleScroll = (event) => {
|
|
12337
|
+
const target = event.target;
|
|
12338
|
+
if (dropdownRef.current?.contains(target))
|
|
12339
|
+
return;
|
|
12340
|
+
if (listPopupRef.current?.contains(target))
|
|
12341
|
+
return;
|
|
12342
|
+
if (isOpen)
|
|
12343
|
+
setIsOpen(false);
|
|
12344
|
+
if (isListPopupOpen)
|
|
12345
|
+
setIsListPopupOpen(false);
|
|
12346
|
+
};
|
|
12347
|
+
window.addEventListener('scroll', handleScroll, true);
|
|
12348
|
+
return () => window.removeEventListener('scroll', handleScroll, true);
|
|
12349
|
+
}, [isOpen, isListPopupOpen]);
|
|
12350
|
+
React.useEffect(() => {
|
|
12351
|
+
if (!isOpen && !isListPopupOpen)
|
|
12352
|
+
return;
|
|
12353
|
+
const handleClickOutside = (event) => {
|
|
12354
|
+
const target = event.target;
|
|
12355
|
+
if (isOpen) {
|
|
12356
|
+
if (containerRef.current?.contains(target))
|
|
12357
|
+
return;
|
|
12358
|
+
if (dropdownRef.current?.contains(target))
|
|
12359
|
+
return;
|
|
12360
|
+
setIsOpen(false);
|
|
12361
|
+
}
|
|
12362
|
+
if (isListPopupOpen) {
|
|
12363
|
+
if (listPopupRef.current?.contains(target))
|
|
12364
|
+
return;
|
|
12365
|
+
if (moreButtonRef.current?.contains(target))
|
|
12366
|
+
return;
|
|
12367
|
+
setIsListPopupOpen(false);
|
|
12368
|
+
}
|
|
12369
|
+
};
|
|
12370
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
12371
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
12372
|
+
}, [isOpen, isListPopupOpen]);
|
|
12373
|
+
React.useEffect(() => {
|
|
12374
|
+
if (isOpen && searchInputRef.current) {
|
|
12375
|
+
searchInputRef.current.focus();
|
|
12376
|
+
}
|
|
12377
|
+
}, [isOpen]);
|
|
12378
|
+
const processedOptions = React.useMemo(() => {
|
|
12379
|
+
let options = dataSourceOptions.map((opt) => {
|
|
12380
|
+
const rawLabel = String(opt.label ?? opt.value ?? '');
|
|
12381
|
+
return {
|
|
12382
|
+
value: opt.value,
|
|
12383
|
+
label: translateConfig(rawLabel),
|
|
12384
|
+
rawLabel,
|
|
12385
|
+
};
|
|
12386
|
+
});
|
|
12387
|
+
if (sortOptions) {
|
|
12388
|
+
options.sort((a, b) => a.label.localeCompare(b.label));
|
|
12389
|
+
}
|
|
12390
|
+
return options;
|
|
12391
|
+
}, [dataSourceOptions, sortOptions, translateConfig]);
|
|
12392
|
+
const filteredOptions = React.useMemo(() => {
|
|
12393
|
+
if (!searchQuery.trim())
|
|
12394
|
+
return processedOptions;
|
|
12395
|
+
const q = searchQuery.trim().toLowerCase();
|
|
12396
|
+
return processedOptions.filter((opt) => opt.label.toLowerCase().includes(q) ||
|
|
12397
|
+
opt.rawLabel.toLowerCase().includes(q));
|
|
12398
|
+
}, [processedOptions, searchQuery]);
|
|
12399
|
+
const selectedValues = React.useMemo(() => {
|
|
12400
|
+
if (value === null || value === undefined)
|
|
12401
|
+
return [];
|
|
12402
|
+
if (Array.isArray(value))
|
|
12403
|
+
return value;
|
|
12404
|
+
return [value];
|
|
12405
|
+
}, [value]);
|
|
12406
|
+
const allFilteredSelected = React.useMemo(() => {
|
|
12407
|
+
if (filteredOptions.length === 0)
|
|
12408
|
+
return false;
|
|
12409
|
+
return filteredOptions.every((opt) => selectedValues.includes(opt.value));
|
|
12410
|
+
}, [filteredOptions, selectedValues]);
|
|
12411
|
+
const handleToggle = React.useCallback((optionValue, checked) => {
|
|
12412
|
+
if (checked) {
|
|
12413
|
+
onChange([...selectedValues, optionValue]);
|
|
12414
|
+
}
|
|
12415
|
+
else {
|
|
12416
|
+
onChange(selectedValues.filter((v) => v !== optionValue));
|
|
12417
|
+
}
|
|
12418
|
+
}, [selectedValues, onChange]);
|
|
12419
|
+
const handleSelectAll = React.useCallback(() => {
|
|
12420
|
+
const filteredVals = filteredOptions.map((o) => o.value);
|
|
12421
|
+
const merged = Array.from(new Set([...selectedValues, ...filteredVals]));
|
|
12422
|
+
onChange(merged);
|
|
12423
|
+
}, [filteredOptions, selectedValues, onChange]);
|
|
12424
|
+
const handleClearAll = React.useCallback(() => {
|
|
12425
|
+
onChange([]);
|
|
12426
|
+
}, [onChange]);
|
|
12427
|
+
const selectedLabels = React.useMemo(() => {
|
|
12428
|
+
return selectedValues.map((val) => {
|
|
12429
|
+
const opt = processedOptions.find((o) => o.value === val);
|
|
12430
|
+
return opt ? opt.label : translateConfig(String(val));
|
|
12431
|
+
});
|
|
12432
|
+
}, [selectedValues, processedOptions, translateConfig]);
|
|
12433
|
+
const fullSelectionText = selectedLabels.join(', ');
|
|
12434
|
+
const visibleLabels = selectedLabels.slice(0, 5);
|
|
12435
|
+
const overflowCount = Math.max(0, selectedLabels.length - 10);
|
|
12436
|
+
const disabled = !isEnabled || loading || widgetConfig['widget-readonly'];
|
|
12437
|
+
const renderSelectedLabels = (options) => {
|
|
12438
|
+
if (selectedLabels.length === 0)
|
|
12439
|
+
return null;
|
|
12440
|
+
const readonly = options?.readonly ?? false;
|
|
12441
|
+
return (jsxRuntimeExports.jsxs("div", { className: `flex flex-wrap gap-1 ${readonly ? '' : 'mt-1.5'}`, children: [visibleLabels.map((label, index) => (jsxRuntimeExports.jsxs("span", { className: "inline-flex max-w-full items-center gap-1 rounded-md bg-blue-50 px-2 py-0.5 text-xs text-blue-800", title: label, children: [jsxRuntimeExports.jsx("span", { className: "truncate", children: label }), !readonly && !disabled && (jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleToggle(selectedValues[index], false), className: "shrink-0 text-blue-600 hover:text-blue-900 focus:outline-none", "aria-label": translate('common.removeItem', {
|
|
12442
|
+
label,
|
|
12443
|
+
defaultValue: `Remove ${label}`,
|
|
12444
|
+
}), children: "\u00D7" }))] }, `${selectedValues[index]}-${label}`))), overflowCount > 0 && (jsxRuntimeExports.jsx("button", { ref: moreButtonRef, type: "button", onClick: () => setIsListPopupOpen((prev) => !prev), className: "inline-flex items-center rounded-md bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-700 hover:bg-gray-200 focus:outline-none focus:ring-1 focus:ring-blue-500", children: translate('common.moreSelected', {
|
|
12445
|
+
count: overflowCount,
|
|
12446
|
+
defaultValue: `+${overflowCount} more`,
|
|
12447
|
+
}) }))] }));
|
|
12448
|
+
};
|
|
12449
|
+
const listPopupPanel = isListPopupOpen && listPopupPosition && mounted ? (jsxRuntimeExports.jsxs("div", { ref: listPopupRef, className: "fixed z-[201] bg-white border border-gray-300 shadow-lg", style: {
|
|
12450
|
+
...(listPopupPosition.placement === 'bottom'
|
|
12451
|
+
? { top: listPopupPosition.top }
|
|
12452
|
+
: { bottom: listPopupPosition.bottom }),
|
|
12453
|
+
left: listPopupPosition.left,
|
|
12454
|
+
width: listPopupPosition.width,
|
|
12455
|
+
maxWidth: '320px',
|
|
12456
|
+
maxHeight: listPopupPosition.maxHeight,
|
|
12457
|
+
borderRadius: '10px',
|
|
12458
|
+
display: 'flex',
|
|
12459
|
+
flexDirection: 'column',
|
|
12460
|
+
}, children: [jsxRuntimeExports.jsx("div", { className: "px-3 py-2 text-xs font-semibold text-gray-500 shrink-0", style: { borderBottom: '1px solid #e5e7eb' }, children: translate('common.allSelected', {
|
|
12461
|
+
count: selectedLabels.length,
|
|
12462
|
+
defaultValue: `All selected (${selectedLabels.length})`,
|
|
12463
|
+
}) }), jsxRuntimeExports.jsx("div", { className: "overflow-y-auto flex-1 min-h-0 py-1 overscroll-contain", children: selectedLabels.map((label, index) => (jsxRuntimeExports.jsx("div", { className: "px-3 py-1.5 text-sm text-gray-700", title: label, children: label }, `${selectedValues[index]}-${label}`))) })] })) : null;
|
|
12464
|
+
if (widgetConfig['widget-readonly']) {
|
|
12465
|
+
const fieldLabel = widgetConfig['widget-label'];
|
|
12466
|
+
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] MultiSelectDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [fieldLabel && (jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", label: fieldLabel })), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [selectedLabels.length === 0 ? (jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", children: "-" })) : (renderSelectedLabels({ readonly: true })), mounted && listPopupPanel && typeof document !== 'undefined'
|
|
12467
|
+
? reactDom.createPortal(listPopupPanel, document.body)
|
|
12468
|
+
: null] })] }));
|
|
12469
|
+
}
|
|
12470
|
+
const optionsMaxHeight = dropdownPosition
|
|
12471
|
+
? Math.min(280, dropdownPosition.maxHeight - 100)
|
|
12472
|
+
: 280;
|
|
12473
|
+
const dropdownPanel = isOpen && dropdownPosition && mounted ? (jsxRuntimeExports.jsxs("div", { ref: dropdownRef, className: "fixed z-[200] bg-white border border-gray-300 shadow-lg", style: {
|
|
12474
|
+
...(dropdownPosition.placement === 'bottom'
|
|
12475
|
+
? { top: dropdownPosition.top }
|
|
12476
|
+
: { bottom: dropdownPosition.bottom }),
|
|
12477
|
+
left: dropdownPosition.left,
|
|
12478
|
+
width: dropdownPosition.width,
|
|
12479
|
+
maxWidth: '280px',
|
|
12480
|
+
maxHeight: dropdownPosition.maxHeight,
|
|
12481
|
+
borderRadius: '10px',
|
|
12482
|
+
display: 'flex',
|
|
12483
|
+
flexDirection: 'column',
|
|
12484
|
+
}, children: [jsxRuntimeExports.jsx("div", { className: "px-3 pt-2 pb-1 shrink-0", style: { borderBottom: '1px solid #e5e7eb' }, children: jsxRuntimeExports.jsx("input", { ref: searchInputRef, type: "text", placeholder: translate('common.searchPlaceholder', { defaultValue: 'Search...' }), value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), className: "w-full h-[28px] px-2 text-sm border border-gray-300 bg-gray-50 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500", style: { borderRadius: '6px' } }) }), jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between px-3 py-1 shrink-0", style: { borderBottom: '1px solid #e5e7eb' }, children: [jsxRuntimeExports.jsx("button", { type: "button", onClick: allFilteredSelected ? () => {
|
|
12485
|
+
const filteredVals = new Set(filteredOptions.map((o) => o.value));
|
|
12486
|
+
onChange(selectedValues.filter((v) => !filteredVals.has(v)));
|
|
12487
|
+
} : handleSelectAll, className: "text-xs font-medium text-blue-600 hover:text-blue-800 focus:outline-none", children: allFilteredSelected
|
|
12488
|
+
? translate('common.deselectAll', { defaultValue: 'Deselect All' })
|
|
12489
|
+
: translate('common.selectAll', { defaultValue: 'Select All' }) }), selectedValues.length > 0 && (jsxRuntimeExports.jsx("button", { type: "button", onClick: handleClearAll, className: "text-xs font-medium text-red-500 hover:text-red-700 focus:outline-none", children: translate('common.clearAll', { defaultValue: 'Clear All' }) }))] }), jsxRuntimeExports.jsx("div", { className: "overflow-y-auto flex-1 min-h-0 py-1 overscroll-contain", style: { maxHeight: `${Math.max(80, optionsMaxHeight)}px` }, children: loading ? (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500 px-3 py-2", children: translate('common.loading') })) : filteredOptions.length === 0 ? (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-400 px-3 py-2", children: translate('common.noOptionsFound', { defaultValue: 'No options found' }) })) : (filteredOptions.map((option) => {
|
|
12490
|
+
const isChecked = selectedValues.includes(option.value);
|
|
12491
|
+
return (jsxRuntimeExports.jsxs("label", { className: `flex items-center gap-2 px-3 py-1 cursor-pointer hover:bg-blue-50 ${isChecked ? 'bg-blue-50/60' : ''}`, children: [jsxRuntimeExports.jsx("input", { type: "checkbox", checked: isChecked, onChange: (e) => handleToggle(option.value, e.target.checked), className: "h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-700 leading-normal select-none", children: option.label })] }, option.value));
|
|
12492
|
+
})) })] })) : null;
|
|
12493
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: widgetConfig['widget-label'] ?? '', required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", ref: containerRef, children: [jsxRuntimeExports.jsxs("button", { ref: triggerRef, type: "button", onClick: () => {
|
|
12494
|
+
if (!disabled)
|
|
12495
|
+
setIsOpen((prev) => !prev);
|
|
12496
|
+
}, onBlur: () => {
|
|
12497
|
+
if (!isOpen)
|
|
12498
|
+
onBlur();
|
|
12499
|
+
}, disabled: disabled, className: `w-full sm:w-[280px] max-w-full h-[30px] px-3 border shadow-sm text-left flex items-center justify-between gap-2 ${(touched && error.length > 0) ||
|
|
12500
|
+
(widgetConfig['widget-required'] && selectedValues.length === 0)
|
|
12501
|
+
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
12502
|
+
: 'border-gray-300'} ${disabled ? 'bg-gray-100 cursor-not-allowed' : 'bg-white cursor-pointer'} focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500`, style: { borderRadius: '10px' }, title: selectedValues.length > 0
|
|
12503
|
+
? fullSelectionText
|
|
12504
|
+
: translateConfig(widgetConfig['widget-data-tooltip']), children: [jsxRuntimeExports.jsx("span", { className: `truncate text-sm ${selectedValues.length === 0 ? 'text-gray-400' : 'text-gray-900'}`, children: selectedLabels.length === 0
|
|
12505
|
+
? translate('common.select', { defaultValue: 'Select...' })
|
|
12506
|
+
: translate('common.selectedCount', {
|
|
12507
|
+
count: selectedLabels.length,
|
|
12508
|
+
defaultValue: `${selectedLabels.length} selected`,
|
|
12509
|
+
}) }), jsxRuntimeExports.jsx("svg", { className: `w-4 h-4 flex-shrink-0 text-gray-500 transition-transform ${isOpen ? '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" }) })] }), mounted && dropdownPanel && typeof document !== 'undefined'
|
|
12510
|
+
? reactDom.createPortal(dropdownPanel, document.body)
|
|
12511
|
+
: null, selectedLabels.length > 0 && renderSelectedLabels(), mounted && listPopupPanel && typeof document !== 'undefined'
|
|
12512
|
+
? reactDom.createPortal(listPopupPanel, document.body)
|
|
12513
|
+
: null, touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] })), loading && (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500 mt-1", children: translate('common.loadingOptions') }))] })] }) }));
|
|
12514
|
+
};
|
|
12515
|
+
|
|
11903
12516
|
/**
|
|
11904
12517
|
* Register all default/generic widgets
|
|
11905
12518
|
* This is called automatically when the package is imported
|
|
@@ -11949,6 +12562,8 @@ const registerDefaultWidgets = () => {
|
|
|
11949
12562
|
widgetRegistry.register({ widget: 'id-authentication', component: IdAuthenticationWidget });
|
|
11950
12563
|
// Register lookup widget — searchable popup to select a record from any register
|
|
11951
12564
|
widgetRegistry.register({ widget: 'register-lookup', component: RegisterLookupWidget });
|
|
12565
|
+
// Multi-select widget — searchable dropdown with checkbox-style options, select all, and clear all
|
|
12566
|
+
widgetRegistry.register({ widget: 'multi-select', component: MultiSelectWidget });
|
|
11952
12567
|
};
|
|
11953
12568
|
// Auto-register on import
|
|
11954
12569
|
registerDefaultWidgets();
|
|
@@ -12012,6 +12627,14 @@ var enTranslations = {
|
|
|
12012
12627
|
"common.sectionModified": "Modified and not saved",
|
|
12013
12628
|
"common.supportedDocuments": "Supported Documents",
|
|
12014
12629
|
"common.searchPlaceholder": "Search...",
|
|
12630
|
+
"common.selectAll": "Select All",
|
|
12631
|
+
"common.deselectAll": "Deselect All",
|
|
12632
|
+
"common.clearAll": "Clear All",
|
|
12633
|
+
"common.noOptionsFound": "No options found",
|
|
12634
|
+
"common.allSelected": "All selected ({{count}})",
|
|
12635
|
+
"common.moreSelected": "+{{count}} more",
|
|
12636
|
+
"common.selectedCount": "{{count}} selected",
|
|
12637
|
+
"common.removeItem": "Remove {{label}}",
|
|
12015
12638
|
"common.selectAction": "Select {{label}}",
|
|
12016
12639
|
"common.selectTitle": "Select {{label}}",
|
|
12017
12640
|
"common.change": "Change",
|
|
@@ -12264,13 +12887,10 @@ const translateWidgetConfig = (widgetConfig, translate) => {
|
|
|
12264
12887
|
...dataSource,
|
|
12265
12888
|
options: dataSource.options.map((option) => {
|
|
12266
12889
|
if (option.label && typeof option.label === 'string') {
|
|
12267
|
-
|
|
12268
|
-
|
|
12269
|
-
|
|
12270
|
-
|
|
12271
|
-
label: translate(optionLabel, { defaultValue: optionLabel }),
|
|
12272
|
-
};
|
|
12273
|
-
}
|
|
12890
|
+
return {
|
|
12891
|
+
...option,
|
|
12892
|
+
label: translate(option.label, { defaultValue: option.label }),
|
|
12893
|
+
};
|
|
12274
12894
|
}
|
|
12275
12895
|
return option;
|
|
12276
12896
|
}),
|
|
@@ -12338,6 +12958,7 @@ exports.HeaderSectionWidget = HeaderSectionWidget;
|
|
|
12338
12958
|
exports.IdAuthenticationWidget = IdAuthenticationWidget;
|
|
12339
12959
|
exports.IterableAccordionWidget = IterableAccordionWidget;
|
|
12340
12960
|
exports.JSONEditorPanel = JSONEditorPanel;
|
|
12961
|
+
exports.MultiSelectWidget = MultiSelectWidget;
|
|
12341
12962
|
exports.NumberInputWidget = NumberInputWidget;
|
|
12342
12963
|
exports.PanelRenderer = PanelRenderer;
|
|
12343
12964
|
exports.PhoneInputWidget = PhoneInputWidget;
|
|
@@ -12367,6 +12988,7 @@ exports.createWidgetStore = createWidgetStore;
|
|
|
12367
12988
|
exports.createZodSchema = createZodSchema;
|
|
12368
12989
|
exports.defaultTheme = defaultTheme;
|
|
12369
12990
|
exports.evaluateCondition = evaluateCondition;
|
|
12991
|
+
exports.evaluateWidgetConditions = evaluateWidgetConditions;
|
|
12370
12992
|
exports.filterByCharacterType = filterByCharacterType;
|
|
12371
12993
|
exports.formatCurrency = formatCurrency;
|
|
12372
12994
|
exports.formatDate = formatDate;
|
|
@@ -12376,14 +12998,18 @@ exports.formatValue = formatValue;
|
|
|
12376
12998
|
exports.geoHierarchyBuilder = geoHierarchyBuilder;
|
|
12377
12999
|
exports.getApiDataSource = getApiDataSource;
|
|
12378
13000
|
exports.getFormattedNumberLength = getFormattedNumberLength;
|
|
13001
|
+
exports.getGeoDescendantWidgetIds = getGeoDescendantWidgetIds;
|
|
13002
|
+
exports.getGeoGroupId = getGeoGroupId;
|
|
12379
13003
|
exports.getSchemaDataSource = getSchemaDataSource;
|
|
12380
13004
|
exports.getStaticDataSource = getStaticDataSource;
|
|
12381
13005
|
exports.getValueByPath = getValueByPath;
|
|
12382
13006
|
exports.getWidgetValue = getWidgetValue;
|
|
13007
|
+
exports.hasVisibilityRules = hasVisibilityRules;
|
|
12383
13008
|
exports.initI18n = initI18n;
|
|
12384
13009
|
exports.isAllowedKey = isAllowedKey;
|
|
12385
13010
|
exports.isUpstreamGeoAncestor = isUpstreamGeoAncestor;
|
|
12386
13011
|
exports.normalizeNumericDefault = normalizeNumericDefault;
|
|
13012
|
+
exports.normalizeOptionRules = normalizeOptionRules;
|
|
12387
13013
|
exports.parseDataPath = parseDataPath;
|
|
12388
13014
|
exports.parseNumber = parseNumber;
|
|
12389
13015
|
exports.registerDefaultWidgets = registerDefaultWidgets;
|
|
@@ -12392,6 +13018,7 @@ exports.removeMask = removeMask;
|
|
|
12392
13018
|
exports.resetAll = resetAll;
|
|
12393
13019
|
exports.resetAndSeedGeoHierarchyFromValues = resetAndSeedGeoHierarchyFromValues;
|
|
12394
13020
|
exports.resetWidget = resetWidget;
|
|
13021
|
+
exports.resolveGeoWidgetLevelLabel = resolveGeoWidgetLevelLabel;
|
|
12395
13022
|
exports.resolveGeoWidgetLevelValue = resolveGeoWidgetLevelValue;
|
|
12396
13023
|
exports.resolveTheme = resolveTheme;
|
|
12397
13024
|
exports.resolveWidgetIdValue = resolveWidgetIdValue;
|
|
@@ -12405,6 +13032,7 @@ exports.setValueByPath = setValueByPath;
|
|
|
12405
13032
|
exports.setValues = setValues;
|
|
12406
13033
|
exports.setWidgetValue = setWidgetValue;
|
|
12407
13034
|
exports.shouldEnableWidget = shouldEnableWidget;
|
|
13035
|
+
exports.shouldRequireWidget = shouldRequireWidget;
|
|
12408
13036
|
exports.shouldShowWidget = shouldShowWidget;
|
|
12409
13037
|
exports.transformDataSourceOptions = transformDataSourceOptions;
|
|
12410
13038
|
exports.translatePanelConfig = translatePanelConfig;
|