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