@openg2p/registry-widgets 1.1.2-dev.6 → 1.1.2-dev.8
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 +52 -15
- package/dist/index.esm.js +1038 -414
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1046 -413
- 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/dataSource.d.ts +6 -0
- package/dist/utils/dataSource.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/TableWidget.d.ts.map +1 -1
- package/dist/widgets/TextAreaWidget.d.ts.map +1 -1
- package/dist/widgets/TextInputWidget.d.ts.map +1 -1
- package/dist/widgets/index.d.ts +1 -0
- package/dist/widgets/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -403,6 +403,18 @@ const createZodSchema = (validation, required = false) => {
|
|
|
403
403
|
return schema;
|
|
404
404
|
};
|
|
405
405
|
|
|
406
|
+
const normalizeBooleanLike = (val) => {
|
|
407
|
+
if (val === true || val === 1)
|
|
408
|
+
return true;
|
|
409
|
+
if (val === false || val === 0 || val === null || val === undefined || val === '') {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
if (typeof val === 'string') {
|
|
413
|
+
const normalized = val.trim().toLowerCase();
|
|
414
|
+
return normalized === 'true' || normalized === 'yes' || normalized === '1';
|
|
415
|
+
}
|
|
416
|
+
return Boolean(val);
|
|
417
|
+
};
|
|
406
418
|
/**
|
|
407
419
|
* Evaluate condition against field value
|
|
408
420
|
*/
|
|
@@ -411,6 +423,9 @@ const evaluateCondition = (condition, allValues) => {
|
|
|
411
423
|
const { operator, value } = condition;
|
|
412
424
|
switch (operator) {
|
|
413
425
|
case 'equals':
|
|
426
|
+
if (typeof value === 'boolean' || typeof fieldValue === 'boolean') {
|
|
427
|
+
return normalizeBooleanLike(fieldValue) === normalizeBooleanLike(value);
|
|
428
|
+
}
|
|
414
429
|
return fieldValue === value;
|
|
415
430
|
case 'notEquals':
|
|
416
431
|
return fieldValue !== value;
|
|
@@ -443,37 +458,62 @@ const evaluateCondition = (condition, allValues) => {
|
|
|
443
458
|
}
|
|
444
459
|
};
|
|
445
460
|
/**
|
|
446
|
-
*
|
|
461
|
+
* Normalize widget-data-options into a sequential list of action rules.
|
|
462
|
+
* Supports legacy single { action, condition } and new { actions: [...] }.
|
|
447
463
|
*/
|
|
448
|
-
const
|
|
449
|
-
if (!options
|
|
450
|
-
return
|
|
464
|
+
const normalizeOptionRules = (options) => {
|
|
465
|
+
if (!options) {
|
|
466
|
+
return [];
|
|
451
467
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
return conditionResult;
|
|
468
|
+
if (Array.isArray(options.actions) && options.actions.length > 0) {
|
|
469
|
+
return options.actions.filter((rule) => !!rule?.action);
|
|
455
470
|
}
|
|
456
|
-
if (options.action
|
|
457
|
-
return
|
|
471
|
+
if (options.action && options.condition) {
|
|
472
|
+
return [{ action: options.action, condition: options.condition }];
|
|
458
473
|
}
|
|
459
|
-
return
|
|
474
|
+
return [];
|
|
475
|
+
};
|
|
476
|
+
const hasVisibilityRules = (options) => {
|
|
477
|
+
return normalizeOptionRules(options).some((rule) => rule.action === 'show' || rule.action === 'hide');
|
|
460
478
|
};
|
|
461
479
|
/**
|
|
462
|
-
*
|
|
480
|
+
* Evaluate widget-data-options rules sequentially.
|
|
481
|
+
* show/hide and enable/disable only affect visibility and enabled state.
|
|
482
|
+
* require is independent: required = widget-required OR require-condition-match.
|
|
463
483
|
*/
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
484
|
+
const evaluateWidgetConditions = (options, allValues, baseRequired = false) => {
|
|
485
|
+
let visible = true;
|
|
486
|
+
let enabled = true;
|
|
487
|
+
let required = baseRequired;
|
|
488
|
+
const rules = normalizeOptionRules(options);
|
|
489
|
+
for (const rule of rules) {
|
|
490
|
+
if (!rule.condition) {
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
const match = evaluateCondition(rule.condition, allValues);
|
|
494
|
+
switch (rule.action) {
|
|
495
|
+
case 'show':
|
|
496
|
+
visible = match;
|
|
497
|
+
break;
|
|
498
|
+
case 'hide':
|
|
499
|
+
visible = !match;
|
|
500
|
+
break;
|
|
501
|
+
case 'enable':
|
|
502
|
+
enabled = match;
|
|
503
|
+
break;
|
|
504
|
+
case 'disable':
|
|
505
|
+
enabled = !match;
|
|
506
|
+
break;
|
|
507
|
+
case 'require':
|
|
508
|
+
required = baseRequired || match;
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
474
511
|
}
|
|
475
|
-
return
|
|
512
|
+
return { visible, enabled, required };
|
|
476
513
|
};
|
|
514
|
+
const shouldShowWidget = (options, allValues) => evaluateWidgetConditions(options, allValues).visible;
|
|
515
|
+
const shouldEnableWidget = (options, allValues) => evaluateWidgetConditions(options, allValues).enabled;
|
|
516
|
+
const shouldRequireWidget = (options, allValues, baseRequired = false) => evaluateWidgetConditions(options, allValues, baseRequired).required;
|
|
477
517
|
|
|
478
518
|
/**
|
|
479
519
|
* Format number with thousand and decimal separators
|
|
@@ -1043,6 +1083,67 @@ const formatValue = (value, format, widgetType) => {
|
|
|
1043
1083
|
return value?.toString() || '';
|
|
1044
1084
|
};
|
|
1045
1085
|
|
|
1086
|
+
const apiDataSourceCache = new Map();
|
|
1087
|
+
const apiDataSourceInflight = new Map();
|
|
1088
|
+
function buildApiRequestContext(dataSource, allValues, levelId) {
|
|
1089
|
+
let depValue = null;
|
|
1090
|
+
if (dataSource.dependsOn) {
|
|
1091
|
+
if (dataSource.dependsOn.includes('.')) {
|
|
1092
|
+
depValue = getValueByPath(allValues, dataSource.dependsOn);
|
|
1093
|
+
}
|
|
1094
|
+
else {
|
|
1095
|
+
depValue = resolveWidgetIdValue(allValues, dataSource.dependsOn);
|
|
1096
|
+
}
|
|
1097
|
+
if (depValue === null || depValue === undefined || depValue === '') {
|
|
1098
|
+
return null;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
const method = dataSource.method || 'GET';
|
|
1102
|
+
const staticParams = { ...dataSource.params };
|
|
1103
|
+
const standardFields = ['type', 'service', 'endpoint', 'url', 'method', 'dependsOn', 'valueKey', 'labelKey', 'headers', 'body', 'params', 'level_id'];
|
|
1104
|
+
for (const [key, value] of Object.entries(dataSource)) {
|
|
1105
|
+
if (!standardFields.includes(key) && value !== undefined && value !== null) {
|
|
1106
|
+
staticParams[key] = value;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
if (levelId) {
|
|
1110
|
+
staticParams.level_id = levelId;
|
|
1111
|
+
}
|
|
1112
|
+
const requestParams = { ...staticParams };
|
|
1113
|
+
if (dataSource.dependsOn && depValue !== null && depValue !== undefined) {
|
|
1114
|
+
const parentValueId = typeof depValue === 'object' && depValue !== null
|
|
1115
|
+
? (depValue.level_value_id || depValue.id || depValue.value || depValue)
|
|
1116
|
+
: depValue;
|
|
1117
|
+
if (staticParams.level_id) {
|
|
1118
|
+
requestParams.parent_level_value_id = parentValueId;
|
|
1119
|
+
}
|
|
1120
|
+
else {
|
|
1121
|
+
const paramKey = dataSource.dependsOn.split('.').pop() || 'filter';
|
|
1122
|
+
requestParams[paramKey] = parentValueId;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
else if (staticParams.level_id) {
|
|
1126
|
+
requestParams.parent_level_value_id = '';
|
|
1127
|
+
}
|
|
1128
|
+
const service = dataSource.service;
|
|
1129
|
+
const endpoint = dataSource.endpoint;
|
|
1130
|
+
if (!service || !endpoint) {
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
return { service, endpoint, method, requestParams };
|
|
1134
|
+
}
|
|
1135
|
+
function buildApiDataSourceCacheKey(service, endpoint, method, requestParams) {
|
|
1136
|
+
return `${service}|${endpoint}|${method}|${JSON.stringify(requestParams)}`;
|
|
1137
|
+
}
|
|
1138
|
+
/** Return cached API options when already fetched (e.g. duplicate table cells). */
|
|
1139
|
+
function getCachedApiDataSource(dataSource, allValues, levelId) {
|
|
1140
|
+
const context = buildApiRequestContext(dataSource, allValues, levelId);
|
|
1141
|
+
if (!context) {
|
|
1142
|
+
return undefined;
|
|
1143
|
+
}
|
|
1144
|
+
const cacheKey = buildApiDataSourceCacheKey(context.service, context.endpoint, context.method, context.requestParams);
|
|
1145
|
+
return apiDataSourceCache.get(cacheKey);
|
|
1146
|
+
}
|
|
1046
1147
|
/**
|
|
1047
1148
|
* Get static data source options
|
|
1048
1149
|
*/
|
|
@@ -1060,98 +1161,33 @@ const getApiDataSource = async (dataSource, allValues, dataSourceRequestHandler,
|
|
|
1060
1161
|
return [];
|
|
1061
1162
|
}
|
|
1062
1163
|
try {
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
let depValue = null;
|
|
1066
|
-
if (dataSource.dependsOn) {
|
|
1067
|
-
if (dataSource.dependsOn.includes('.')) {
|
|
1068
|
-
depValue = getValueByPath(allValues, dataSource.dependsOn);
|
|
1069
|
-
}
|
|
1070
|
-
else {
|
|
1071
|
-
depValue = resolveWidgetIdValue(allValues, dataSource.dependsOn);
|
|
1072
|
-
}
|
|
1073
|
-
if (depValue === null || depValue === undefined || depValue === '') {
|
|
1074
|
-
// If dependency is empty, return empty array
|
|
1075
|
-
return [];
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
// Build request parameters
|
|
1079
|
-
const method = dataSource.method || 'GET';
|
|
1080
|
-
// Extract static params from dataSource
|
|
1081
|
-
// Include explicit params object and any additional fields (like level_id)
|
|
1082
|
-
const staticParams = { ...dataSource.params };
|
|
1083
|
-
// Extract additional fields that aren't part of the standard ApiDataSource interface
|
|
1084
|
-
// These are fields like level_id that might be directly on the dataSource
|
|
1085
|
-
// BUT: level_id should come from widget-geo-config.level, not from dataSource
|
|
1086
|
-
const standardFields = ['type', 'service', 'endpoint', 'url', 'method', 'dependsOn', 'valueKey', 'labelKey', 'headers', 'body', 'params', 'level_id'];
|
|
1087
|
-
for (const [key, value] of Object.entries(dataSource)) {
|
|
1088
|
-
if (!standardFields.includes(key) && value !== undefined && value !== null) {
|
|
1089
|
-
staticParams[key] = value;
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
// If levelId is provided (from widget-geo-config.level), use it instead of any level_id in dataSource
|
|
1093
|
-
if (levelId) {
|
|
1094
|
-
staticParams.level_id = levelId;
|
|
1095
|
-
}
|
|
1096
|
-
// Build request params object
|
|
1097
|
-
const requestParams = { ...staticParams };
|
|
1098
|
-
// Add dependency value to params
|
|
1099
|
-
if (dataSource.dependsOn && depValue !== null && depValue !== undefined) {
|
|
1100
|
-
// Extract the actual value ID if depValue is an object
|
|
1101
|
-
const parentValueId = typeof depValue === 'object' && depValue !== null
|
|
1102
|
-
? (depValue.level_value_id || depValue.id || depValue.value || depValue)
|
|
1103
|
-
: depValue;
|
|
1104
|
-
// For geo APIs, use parent_level_value_id
|
|
1105
|
-
if (staticParams.level_id) {
|
|
1106
|
-
requestParams.parent_level_value_id = parentValueId;
|
|
1107
|
-
}
|
|
1108
|
-
else {
|
|
1109
|
-
// For other APIs, use the dependency field name as param key
|
|
1110
|
-
const paramKey = dataSource.dependsOn.split('.').pop() || 'filter';
|
|
1111
|
-
requestParams[paramKey] = parentValueId;
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
else if (staticParams.level_id) {
|
|
1115
|
-
// First level has no parent, send empty string as many OpenG2P APIs expect it
|
|
1116
|
-
requestParams.parent_level_value_id = "";
|
|
1117
|
-
}
|
|
1118
|
-
// Get service mnemonic and endpoint (required)
|
|
1119
|
-
const service = dataSource.service;
|
|
1120
|
-
const endpoint = dataSource.endpoint;
|
|
1121
|
-
if (!service) {
|
|
1122
|
-
console.error('[getApiDataSource] API data source missing service mnemonic. Use "service" field instead of "url"');
|
|
1123
|
-
return [];
|
|
1124
|
-
}
|
|
1125
|
-
if (!endpoint) {
|
|
1126
|
-
console.error('[getApiDataSource] API data source missing endpoint. Use "endpoint" field to specify the operation (e.g., "get_g2p_geo_level_values")');
|
|
1164
|
+
const context = buildApiRequestContext(dataSource, allValues, levelId);
|
|
1165
|
+
if (!context) {
|
|
1127
1166
|
return [];
|
|
1128
1167
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
if (
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
return
|
|
1168
|
+
const { service, endpoint, method, requestParams } = context;
|
|
1169
|
+
const cacheKey = buildApiDataSourceCacheKey(service, endpoint, method, requestParams);
|
|
1170
|
+
const cached = apiDataSourceCache.get(cacheKey);
|
|
1171
|
+
if (cached) {
|
|
1172
|
+
return cached;
|
|
1173
|
+
}
|
|
1174
|
+
const inflight = apiDataSourceInflight.get(cacheKey);
|
|
1175
|
+
if (inflight) {
|
|
1176
|
+
return inflight;
|
|
1177
|
+
}
|
|
1178
|
+
const fetchPromise = (async () => {
|
|
1179
|
+
const response = await dataSourceRequestHandler(service, endpoint, method, requestParams, { headers: dataSource.headers });
|
|
1180
|
+
const parsed = Array.isArray(response) ? response : [];
|
|
1181
|
+
apiDataSourceCache.set(cacheKey, parsed);
|
|
1182
|
+
return parsed;
|
|
1183
|
+
})();
|
|
1184
|
+
apiDataSourceInflight.set(cacheKey, fetchPromise);
|
|
1185
|
+
try {
|
|
1186
|
+
return await fetchPromise;
|
|
1144
1187
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
if (response.data && Array.isArray(response.data)) {
|
|
1148
|
-
return response.data;
|
|
1149
|
-
}
|
|
1150
|
-
if (response.results && Array.isArray(response.results)) {
|
|
1151
|
-
return response.results;
|
|
1152
|
-
}
|
|
1188
|
+
finally {
|
|
1189
|
+
apiDataSourceInflight.delete(cacheKey);
|
|
1153
1190
|
}
|
|
1154
|
-
return [];
|
|
1155
1191
|
}
|
|
1156
1192
|
catch (error) {
|
|
1157
1193
|
// Rethrow so useBaseWidget's catch can log it with full widget context
|
|
@@ -1937,6 +1973,98 @@ const WidgetProvider = ({ store, dataSourceRequestHandler, schemaData, translate
|
|
|
1937
1973
|
return content;
|
|
1938
1974
|
};
|
|
1939
1975
|
|
|
1976
|
+
/**
|
|
1977
|
+
* Custom hook for widget translations
|
|
1978
|
+
* Provides translation function with widget-specific namespace and fallback support
|
|
1979
|
+
*/
|
|
1980
|
+
const useWidgetTranslation = () => {
|
|
1981
|
+
const { translate: translateFunction } = useWidgetContext();
|
|
1982
|
+
/**
|
|
1983
|
+
* Translate a key with flexible namespace support
|
|
1984
|
+
* Supports translation keys in various formats and direct strings
|
|
1985
|
+
*
|
|
1986
|
+
* Translation key formats supported:
|
|
1987
|
+
* - "widgets:common.addItem" - Namespaced key (for widget-specific translations)
|
|
1988
|
+
* - "Name" - Direct string (will be looked up in flat translation structure)
|
|
1989
|
+
* - "sections.personalDetails" - Nested key (for backward compatibility)
|
|
1990
|
+
*
|
|
1991
|
+
* With flat translation structure, direct strings like "Name" are automatically
|
|
1992
|
+
* translated by looking them up in the translation resources.
|
|
1993
|
+
*
|
|
1994
|
+
* @param keyOrString - Translation key (e.g., "widgets:common.addItem") or direct string (e.g., "Name")
|
|
1995
|
+
* @param options - Translation options (interpolation values, default value, etc.)
|
|
1996
|
+
* @returns Translated string or original string if translation not found
|
|
1997
|
+
*/
|
|
1998
|
+
const translate = (keyOrString, options) => {
|
|
1999
|
+
if (!keyOrString) {
|
|
2000
|
+
return options?.defaultValue || '';
|
|
2001
|
+
}
|
|
2002
|
+
// Use the provided translation function or fallback to the key
|
|
2003
|
+
if (translateFunction) {
|
|
2004
|
+
return translateFunction(keyOrString, options) || options?.defaultValue || keyOrString;
|
|
2005
|
+
}
|
|
2006
|
+
// Fallback to key if no translation function available
|
|
2007
|
+
return options?.defaultValue || keyOrString;
|
|
2008
|
+
};
|
|
2009
|
+
/**
|
|
2010
|
+
* Translate widget config property
|
|
2011
|
+
* Attempts to translate the value, but if translation is not found,
|
|
2012
|
+
* returns the original value as-is (graceful fallback)
|
|
2013
|
+
*
|
|
2014
|
+
* This function will:
|
|
2015
|
+
* - Try to translate any string value
|
|
2016
|
+
* - If translation exists, use the translated value
|
|
2017
|
+
* - If translation doesn't exist (returns same value or throws), use original value
|
|
2018
|
+
* - This prevents errors when literal strings like "XXXX-XXXX-XXXX" are used
|
|
2019
|
+
*/
|
|
2020
|
+
const translateConfig = (value, fallback) => {
|
|
2021
|
+
if (!value) {
|
|
2022
|
+
return fallback || '';
|
|
2023
|
+
}
|
|
2024
|
+
// Try to translate the value
|
|
2025
|
+
if (translateFunction) {
|
|
2026
|
+
try {
|
|
2027
|
+
// Pass defaultValue to ensure we get the original value if translation fails
|
|
2028
|
+
const translated = translateFunction(value, { defaultValue: value });
|
|
2029
|
+
// If translation returns empty, null, undefined, or the exact same value,
|
|
2030
|
+
// it means no translation was found - return the original value
|
|
2031
|
+
if (!translated || translated === value) {
|
|
2032
|
+
return value;
|
|
2033
|
+
}
|
|
2034
|
+
// Translation found, return it
|
|
2035
|
+
return translated;
|
|
2036
|
+
}
|
|
2037
|
+
catch (error) {
|
|
2038
|
+
// If translation throws an error (e.g., missing key warning), return original value
|
|
2039
|
+
return value;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
// No translation function available, return value as-is
|
|
2043
|
+
return value;
|
|
2044
|
+
};
|
|
2045
|
+
// No need of this getLanguage and changeLanguage functions
|
|
2046
|
+
/**
|
|
2047
|
+
* Get current language
|
|
2048
|
+
*/
|
|
2049
|
+
// const getLanguage = (): string => {
|
|
2050
|
+
// return i18n.language || 'en';
|
|
2051
|
+
// };
|
|
2052
|
+
/**
|
|
2053
|
+
* Change language
|
|
2054
|
+
*/
|
|
2055
|
+
// const changeLanguage = (lng: string): Promise<void> => {
|
|
2056
|
+
// return i18n.changeLanguage(lng).then(() => undefined);
|
|
2057
|
+
// };
|
|
2058
|
+
return {
|
|
2059
|
+
t: translate,
|
|
2060
|
+
translate,
|
|
2061
|
+
translateConfig,
|
|
2062
|
+
// getLanguage,
|
|
2063
|
+
// changeLanguage,
|
|
2064
|
+
// i18n: null,
|
|
2065
|
+
};
|
|
2066
|
+
};
|
|
2067
|
+
|
|
1940
2068
|
/**
|
|
1941
2069
|
* Geo Hierarchy Builder
|
|
1942
2070
|
* Manages geo hierarchy state and builds hierarchy JSON structure
|
|
@@ -2136,6 +2264,42 @@ function isUpstreamGeoAncestor(changedWidgetId, widgetId, immediateParentWidgetI
|
|
|
2136
2264
|
}
|
|
2137
2265
|
return false;
|
|
2138
2266
|
}
|
|
2267
|
+
/** Group id for geo widgets sharing the same register prefix (e.g. `{registerId}`). */
|
|
2268
|
+
function getGeoGroupId(dataPath) {
|
|
2269
|
+
if (typeof dataPath === 'string' && dataPath.includes('.')) {
|
|
2270
|
+
return dataPath.split('.').slice(0, -1).join('.');
|
|
2271
|
+
}
|
|
2272
|
+
return 'default';
|
|
2273
|
+
}
|
|
2274
|
+
/**
|
|
2275
|
+
* Resolve the human-readable label for a geo level from persisted hierarchy JSON.
|
|
2276
|
+
* Used in readonly mode when API options are not loaded.
|
|
2277
|
+
*/
|
|
2278
|
+
function resolveGeoWidgetLevelLabel(values, widgetId, dataPath, geoConfig) {
|
|
2279
|
+
if (!dataPath || typeof dataPath !== 'string') {
|
|
2280
|
+
return undefined;
|
|
2281
|
+
}
|
|
2282
|
+
const stored = getWidgetValue(values, dataPath, widgetId);
|
|
2283
|
+
const hierarchy = stored?.hierarchy || stored?.geo_code_hierarchy_json?.hierarchy;
|
|
2284
|
+
if (!Array.isArray(hierarchy)) {
|
|
2285
|
+
return undefined;
|
|
2286
|
+
}
|
|
2287
|
+
const levelData = hierarchy.find((l) => l.level === geoConfig.level);
|
|
2288
|
+
if (levelData?.level_value_mnemonic) {
|
|
2289
|
+
return String(levelData.level_value_mnemonic);
|
|
2290
|
+
}
|
|
2291
|
+
return undefined;
|
|
2292
|
+
}
|
|
2293
|
+
/** All registered geo widgets that are descendants of ancestorWidgetId. */
|
|
2294
|
+
function getGeoDescendantWidgetIds(ancestorWidgetId) {
|
|
2295
|
+
const descendants = [];
|
|
2296
|
+
for (const [childId, parentId] of geoWidgetParentRegistry.entries()) {
|
|
2297
|
+
if (isUpstreamGeoAncestor(ancestorWidgetId, childId, parentId)) {
|
|
2298
|
+
descendants.push(childId);
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
return descendants;
|
|
2302
|
+
}
|
|
2139
2303
|
function readStoredHierarchyLevels(values, dataPath, widgetId) {
|
|
2140
2304
|
const stored = getWidgetValue(values, dataPath, widgetId);
|
|
2141
2305
|
const hierarchy = stored?.hierarchy || stored?.geo_code_hierarchy_json?.hierarchy;
|
|
@@ -2186,6 +2350,7 @@ const useBaseWidget = (options) => {
|
|
|
2186
2350
|
const dispatch = reactRedux.useDispatch();
|
|
2187
2351
|
const context = useWidgetContext();
|
|
2188
2352
|
const eventBus = useWidgetEventBus();
|
|
2353
|
+
const { translateConfig } = useWidgetTranslation();
|
|
2189
2354
|
const widgetId = config['widget-id'];
|
|
2190
2355
|
// Fall back to WidgetContext for dataSourceRequestHandler
|
|
2191
2356
|
const dataSourceRequestHandler = propHandler || context.dataSourceRequestHandler;
|
|
@@ -2356,6 +2521,15 @@ const useBaseWidget = (options) => {
|
|
|
2356
2521
|
}
|
|
2357
2522
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2358
2523
|
}, [isLayoutWidget]); // Only run once on mount
|
|
2524
|
+
const resolveIsRequired = React.useCallback((currentValues) => {
|
|
2525
|
+
if (isLayoutWidget) {
|
|
2526
|
+
return false;
|
|
2527
|
+
}
|
|
2528
|
+
if (config['widget-readonly']) {
|
|
2529
|
+
return false;
|
|
2530
|
+
}
|
|
2531
|
+
return evaluateWidgetConditions(config['widget-data-options'], currentValues, config['widget-required'] ?? false).required;
|
|
2532
|
+
}, [config, isLayoutWidget]);
|
|
2359
2533
|
// Handle value change
|
|
2360
2534
|
// CRITICAL: Don't include 'values' in dependency array - it causes the callback to be recreated
|
|
2361
2535
|
// every time values change, which can lead to stale closures and double dispatches
|
|
@@ -2399,29 +2573,26 @@ const useBaseWidget = (options) => {
|
|
|
2399
2573
|
lastDispatchedValueRef.current = newValue;
|
|
2400
2574
|
dispatch(setValue({ widgetId, value: newValue }));
|
|
2401
2575
|
}
|
|
2576
|
+
else if (config['widget-geo-config']) {
|
|
2577
|
+
// Geo widgets: hierarchy dataPath is managed by useGeoWidgetCascade
|
|
2578
|
+
getGeoDescendantWidgetIds(widgetId).forEach((descendantId) => {
|
|
2579
|
+
dispatch(setValue({ widgetId: descendantId, value: GEO_LEVEL_CLEARED }));
|
|
2580
|
+
dispatch(setDataSource({ widgetId: descendantId, data: [] }));
|
|
2581
|
+
});
|
|
2582
|
+
dispatch(setValue({ widgetId, value: newValue }));
|
|
2583
|
+
}
|
|
2402
2584
|
else {
|
|
2403
|
-
//
|
|
2404
|
-
// CRITICAL: For geo widgets, we do NOT want to overwrite the shared hierarchy dataPath
|
|
2405
|
-
// with a primitive value (the selected ID). The hierarchy object is managed by useGeoWidgetCascade.
|
|
2406
|
-
if (config['widget-geo-config']) {
|
|
2407
|
-
dispatch(setValue({ widgetId, value: newValue }));
|
|
2408
|
-
return;
|
|
2409
|
-
}
|
|
2410
|
-
// For non-geo widgets, update both widgetId and dataPath
|
|
2411
|
-
// CRITICAL: Create updated values object with newValue already set
|
|
2412
|
-
// This prevents setWidgetValue from reading stale values
|
|
2585
|
+
// Non-geo widgets: update both widgetId and dataPath
|
|
2413
2586
|
const currentValuesWithUpdate = {
|
|
2414
2587
|
...valuesRef.current,
|
|
2415
|
-
[widgetId]: newValue,
|
|
2588
|
+
[widgetId]: newValue,
|
|
2416
2589
|
};
|
|
2417
2590
|
const updatedValues = setWidgetValue(currentValuesWithUpdate, config['widget-data-path'], widgetId, newValue);
|
|
2418
|
-
// setWidgetValue returns the complete updated structure with all existing data preserved
|
|
2419
|
-
// Use setValues to update the entire state with deep merge
|
|
2420
2591
|
dispatch(setValues(updatedValues));
|
|
2421
2592
|
}
|
|
2422
2593
|
// Validate if needed
|
|
2423
2594
|
if (validate) {
|
|
2424
|
-
const validationErrors = validateWidget(newValue, config['widget-data-validation'],
|
|
2595
|
+
const validationErrors = validateWidget(newValue, config['widget-data-validation'], resolveIsRequired(currentValues));
|
|
2425
2596
|
dispatch(setError({ widgetId, errors: validationErrors }));
|
|
2426
2597
|
}
|
|
2427
2598
|
// Call custom onChange if provided
|
|
@@ -2440,13 +2611,12 @@ const useBaseWidget = (options) => {
|
|
|
2440
2611
|
timestamp: Date.now(),
|
|
2441
2612
|
});
|
|
2442
2613
|
}
|
|
2443
|
-
}, [config, widgetId, dispatch, onValueChange, eventBus] // Removed 'values' to prevent stale closures
|
|
2614
|
+
}, [config, widgetId, dispatch, onValueChange, eventBus, resolveIsRequired] // Removed 'values' to prevent stale closures
|
|
2444
2615
|
);
|
|
2445
2616
|
// Handle blur
|
|
2446
2617
|
const handleBlur = React.useCallback(() => {
|
|
2447
2618
|
dispatch(setTouched({ widgetId, touched: true }));
|
|
2448
|
-
|
|
2449
|
-
const validationErrors = validateWidget(currentValue, config['widget-data-validation'], config['widget-required']);
|
|
2619
|
+
const validationErrors = validateWidget(currentValue, config['widget-data-validation'], resolveIsRequired(valuesRef.current));
|
|
2450
2620
|
dispatch(setError({ widgetId, errors: validationErrors }));
|
|
2451
2621
|
// Publish widget:blur event
|
|
2452
2622
|
if (eventBus) {
|
|
@@ -2457,7 +2627,7 @@ const useBaseWidget = (options) => {
|
|
|
2457
2627
|
timestamp: Date.now(),
|
|
2458
2628
|
});
|
|
2459
2629
|
}
|
|
2460
|
-
}, [currentValue, config, widgetId, dispatch, eventBus]);
|
|
2630
|
+
}, [currentValue, config, widgetId, dispatch, eventBus, resolveIsRequired]);
|
|
2461
2631
|
// Get field value helper
|
|
2462
2632
|
const getFieldValue = React.useCallback((path) => {
|
|
2463
2633
|
return getWidgetValue(values, path, '');
|
|
@@ -2465,7 +2635,7 @@ const useBaseWidget = (options) => {
|
|
|
2465
2635
|
// Conditional visibility and enablement
|
|
2466
2636
|
const isVisible = React.useMemo(() => {
|
|
2467
2637
|
// Layout widgets are always visible unless explicitly hidden
|
|
2468
|
-
if (isLayoutWidget && !config['widget-data-options']
|
|
2638
|
+
if (isLayoutWidget && !hasVisibilityRules(config['widget-data-options'])) {
|
|
2469
2639
|
return true;
|
|
2470
2640
|
}
|
|
2471
2641
|
return shouldShowWidget(config['widget-data-options'], values);
|
|
@@ -2480,6 +2650,7 @@ const useBaseWidget = (options) => {
|
|
|
2480
2650
|
}
|
|
2481
2651
|
return shouldEnableWidget(config['widget-data-options'], values);
|
|
2482
2652
|
}, [config['widget-readonly'], config['widget-data-options'], values, isLayoutWidget]);
|
|
2653
|
+
const isRequired = React.useMemo(() => resolveIsRequired(values), [resolveIsRequired, values]);
|
|
2483
2654
|
// Format value for display
|
|
2484
2655
|
const formattedValue = React.useMemo(() => {
|
|
2485
2656
|
if (!config['widget-data-format']) {
|
|
@@ -2526,10 +2697,9 @@ const useBaseWidget = (options) => {
|
|
|
2526
2697
|
if (!dataSource) {
|
|
2527
2698
|
return;
|
|
2528
2699
|
}
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
if (dataSource.type === 'api' && isReadonly) {
|
|
2700
|
+
const loadApiInReadonly = !!geoConfig ||
|
|
2701
|
+
['select', 'radio', 'checkbox', 'multi-select'].includes(config.widget);
|
|
2702
|
+
if (dataSource.type === 'api' && isReadonly && !loadApiInReadonly) {
|
|
2533
2703
|
return;
|
|
2534
2704
|
}
|
|
2535
2705
|
// For widgets with dependencies, check if dependency value exists
|
|
@@ -2571,6 +2741,30 @@ const useBaseWidget = (options) => {
|
|
|
2571
2741
|
// React will call this effect again when the handler is ready
|
|
2572
2742
|
return;
|
|
2573
2743
|
}
|
|
2744
|
+
const resolveOptionKeys = () => {
|
|
2745
|
+
if (dataSource.type === 'static') {
|
|
2746
|
+
return { valueKey: undefined, labelKey: undefined };
|
|
2747
|
+
}
|
|
2748
|
+
if (geoConfig) {
|
|
2749
|
+
return {
|
|
2750
|
+
valueKey: dataSource.valueKey || 'level_value_id',
|
|
2751
|
+
labelKey: dataSource.labelKey || 'level_value_mnemonic',
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
return { valueKey: dataSource.valueKey, labelKey: dataSource.labelKey };
|
|
2755
|
+
};
|
|
2756
|
+
if (dataSource.type === 'api') {
|
|
2757
|
+
const levelId = geoConfig?.level;
|
|
2758
|
+
const cached = getCachedApiDataSource(dataSource, valuesRef.current, levelId);
|
|
2759
|
+
if (cached) {
|
|
2760
|
+
const { valueKey, labelKey } = resolveOptionKeys();
|
|
2761
|
+
dispatch(setDataSource({
|
|
2762
|
+
widgetId,
|
|
2763
|
+
data: transformDataSourceOptions(cached, valueKey, labelKey),
|
|
2764
|
+
}));
|
|
2765
|
+
return;
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2574
2768
|
dispatch(setLoading({ widgetId, loading: true }));
|
|
2575
2769
|
let data = [];
|
|
2576
2770
|
if (dataSource.type === 'static') {
|
|
@@ -2583,31 +2777,13 @@ const useBaseWidget = (options) => {
|
|
|
2583
2777
|
dispatch(setDataSource({ widgetId, data: [] }));
|
|
2584
2778
|
return;
|
|
2585
2779
|
}
|
|
2586
|
-
// Extract level_id from widget-geo-config.level if available
|
|
2587
2780
|
const levelId = geoConfig?.level;
|
|
2588
2781
|
data = await getApiDataSource(dataSource, valuesRef.current, currentHandler, levelId);
|
|
2589
2782
|
}
|
|
2590
2783
|
else if (dataSource.type === 'schema') {
|
|
2591
2784
|
data = getSchemaDataSource(dataSource, schemaData || {});
|
|
2592
2785
|
}
|
|
2593
|
-
|
|
2594
|
-
// For geo widgets, default to level_value_id and level_value_mnemonic
|
|
2595
|
-
let valueKey;
|
|
2596
|
-
let labelKey;
|
|
2597
|
-
if (dataSource.type === 'static') {
|
|
2598
|
-
valueKey = undefined;
|
|
2599
|
-
labelKey = undefined;
|
|
2600
|
-
}
|
|
2601
|
-
else if (geoConfig) {
|
|
2602
|
-
// Geo widgets: default to level_value_id and level_value_mnemonic
|
|
2603
|
-
valueKey = dataSource.valueKey || 'level_value_id';
|
|
2604
|
-
labelKey = dataSource.labelKey || 'level_value_mnemonic';
|
|
2605
|
-
}
|
|
2606
|
-
else {
|
|
2607
|
-
// Non-geo widgets: use specified keys or undefined
|
|
2608
|
-
valueKey = dataSource.valueKey;
|
|
2609
|
-
labelKey = dataSource.labelKey;
|
|
2610
|
-
}
|
|
2786
|
+
const { valueKey, labelKey } = resolveOptionKeys();
|
|
2611
2787
|
const transformed = transformDataSourceOptions(data, valueKey, labelKey);
|
|
2612
2788
|
dispatch(setDataSource({ widgetId, data: transformed }));
|
|
2613
2789
|
}
|
|
@@ -2623,15 +2799,24 @@ const useBaseWidget = (options) => {
|
|
|
2623
2799
|
// Use configKey and dependencyValue to ensure effect runs only when relevant state changes
|
|
2624
2800
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2625
2801
|
}, [configKey, dependencyValue, dataSourceRequestHandler, schemaData, widgetId, dispatch]);
|
|
2802
|
+
const geoDisplayLabel = React.useMemo(() => {
|
|
2803
|
+
if (!geoConfig) {
|
|
2804
|
+
return undefined;
|
|
2805
|
+
}
|
|
2806
|
+
const rawLabel = resolveGeoWidgetLevelLabel(values, widgetId, config['widget-data-path'], geoConfig);
|
|
2807
|
+
return rawLabel ? translateConfig(rawLabel) : undefined;
|
|
2808
|
+
}, [values, widgetId, config, geoConfig, translateConfig]);
|
|
2626
2809
|
return {
|
|
2627
2810
|
widgetId,
|
|
2628
2811
|
value: currentValue,
|
|
2812
|
+
geoDisplayLabel,
|
|
2629
2813
|
formattedValue,
|
|
2630
2814
|
error: errors,
|
|
2631
2815
|
touched,
|
|
2632
2816
|
loading,
|
|
2633
2817
|
isVisible,
|
|
2634
2818
|
isEnabled,
|
|
2819
|
+
isRequired,
|
|
2635
2820
|
onChange: handleChange,
|
|
2636
2821
|
onBlur: handleBlur,
|
|
2637
2822
|
setError: (errors) => dispatch(setError({ widgetId, errors })),
|
|
@@ -2722,6 +2907,7 @@ const useGeoWidgetCascade = (options) => {
|
|
|
2722
2907
|
const valuesRef = React.useRef(values);
|
|
2723
2908
|
const handlerRef = React.useRef(dataSourceRequestHandler);
|
|
2724
2909
|
const lastCascadePublishRef = React.useRef(undefined);
|
|
2910
|
+
const lastDirectParentValueRef = React.useRef(undefined);
|
|
2725
2911
|
// Keep refs updated
|
|
2726
2912
|
React.useEffect(() => {
|
|
2727
2913
|
valuesRef.current = values;
|
|
@@ -2791,6 +2977,13 @@ const useGeoWidgetCascade = (options) => {
|
|
|
2791
2977
|
event.value === null ||
|
|
2792
2978
|
event.value === '' ||
|
|
2793
2979
|
event.value === GEO_LEVEL_CLEARED;
|
|
2980
|
+
const isFirstParentEvent = lastDirectParentValueRef.current === undefined;
|
|
2981
|
+
const parentValueChanged = !isFirstParentEvent &&
|
|
2982
|
+
lastDirectParentValueRef.current !== event.value;
|
|
2983
|
+
lastDirectParentValueRef.current = event.value;
|
|
2984
|
+
if (!parentCleared && !parentValueChanged && !isFirstParentEvent) {
|
|
2985
|
+
return;
|
|
2986
|
+
}
|
|
2794
2987
|
let parentValue = event.value;
|
|
2795
2988
|
if (!parentCleared && (parentValue === undefined || parentValue === null)) {
|
|
2796
2989
|
parentValue = currentValues[parentWidgetId];
|
|
@@ -2898,17 +3091,7 @@ const useGeoWidgetCascade = (options) => {
|
|
|
2898
3091
|
if (dataPath && geoHierarchyBuilder.buildHierarchyJson(groupId)) {
|
|
2899
3092
|
dispatch(setValues(applySharedGeoHierarchyToValues(valuesRef.current, groupId, dataPath, widgetId)));
|
|
2900
3093
|
}
|
|
2901
|
-
|
|
2902
|
-
if (!isLastLevel && eventBus && lastCascadePublishRef.current !== level_value_id) {
|
|
2903
|
-
lastCascadePublishRef.current = level_value_id;
|
|
2904
|
-
eventBus.publish({
|
|
2905
|
-
type: 'widget:change',
|
|
2906
|
-
widgetId,
|
|
2907
|
-
value: level_value_id,
|
|
2908
|
-
timestamp: Date.now(),
|
|
2909
|
-
});
|
|
2910
|
-
}
|
|
2911
|
-
}, [geoConfig, currentValue, widgetId, dataPath, dispatch, dataSourceOptions, eventBus, groupId]);
|
|
3094
|
+
}, [geoConfig, currentValue, widgetId, dataPath, dispatch, dataSourceOptions, groupId]);
|
|
2912
3095
|
};
|
|
2913
3096
|
|
|
2914
3097
|
class WidgetRegistry {
|
|
@@ -2985,144 +3168,52 @@ class WidgetRegistry {
|
|
|
2985
3168
|
config,
|
|
2986
3169
|
...context,
|
|
2987
3170
|
...entry.defaultProps,
|
|
2988
|
-
});
|
|
2989
|
-
}
|
|
2990
|
-
}
|
|
2991
|
-
// Singleton instance
|
|
2992
|
-
const widgetRegistry = new WidgetRegistry();
|
|
2993
|
-
|
|
2994
|
-
const WidgetRenderer = ({ config, dataSourceRequestHandler: propDataSourceRequestHandler, schemaData: propSchemaData, onValueChange, defaultComponent, }) => {
|
|
2995
|
-
// Use context values as fallback
|
|
2996
|
-
const context = useWidgetContext();
|
|
2997
|
-
const dataSourceRequestHandler = propDataSourceRequestHandler || context.dataSourceRequestHandler;
|
|
2998
|
-
const schemaData = propSchemaData || context.schemaData;
|
|
2999
|
-
// Warn if dataSourceRequestHandler is missing for API data sources, but don't break rendering
|
|
3000
|
-
// This allows widgets to render in read-only or static modes (e.g., CRView)
|
|
3001
|
-
if (!dataSourceRequestHandler && config['widget-data-source']?.type === 'api') {
|
|
3002
|
-
console.warn(`[WidgetRenderer] dataSourceRequestHandler is not provided for widget ${config['widget-id']} with API data source. ` +
|
|
3003
|
-
`The widget will render but API data source functionality will be disabled.`);
|
|
3004
|
-
}
|
|
3005
|
-
// Get values from Redux for cascade hooks
|
|
3006
|
-
const values = reactRedux.useSelector((state) => state.widget.values);
|
|
3007
|
-
const widgetContext = useBaseWidget({
|
|
3008
|
-
config,
|
|
3009
|
-
dataSourceRequestHandler,
|
|
3010
|
-
schemaData,
|
|
3011
|
-
onValueChange,
|
|
3012
|
-
});
|
|
3013
|
-
// Apply cascade hooks if configured (only if handler is available)
|
|
3014
|
-
if (dataSourceRequestHandler) {
|
|
3015
|
-
useWidgetCascade({
|
|
3016
|
-
config,
|
|
3017
|
-
dataSourceRequestHandler,
|
|
3018
|
-
values,
|
|
3019
|
-
});
|
|
3020
|
-
useGeoWidgetCascade({
|
|
3021
|
-
config,
|
|
3022
|
-
dataSourceRequestHandler,
|
|
3023
|
-
values,
|
|
3024
|
-
});
|
|
3025
|
-
}
|
|
3026
|
-
// Don't render if not visible
|
|
3027
|
-
if (!widgetContext.isVisible) {
|
|
3028
|
-
return null;
|
|
3029
|
-
}
|
|
3030
|
-
// Render widget using registry
|
|
3031
|
-
// Don't use key based on readonly state - it causes remounting which resets userHasSetValueRef
|
|
3032
|
-
// The readonly state is already handled in the widget components themselves
|
|
3033
|
-
return (jsxRuntimeExports.jsx("div", { className: "widget-container", "data-widget-id": widgetContext.widgetId, style: { marginBottom: 0 }, children: widgetRegistry.render(config, widgetContext, defaultComponent) }));
|
|
3034
|
-
};
|
|
3035
|
-
|
|
3036
|
-
/**
|
|
3037
|
-
* Custom hook for widget translations
|
|
3038
|
-
* Provides translation function with widget-specific namespace and fallback support
|
|
3039
|
-
*/
|
|
3040
|
-
const useWidgetTranslation = () => {
|
|
3041
|
-
const { translate: translateFunction } = useWidgetContext();
|
|
3042
|
-
/**
|
|
3043
|
-
* Translate a key with flexible namespace support
|
|
3044
|
-
* Supports translation keys in various formats and direct strings
|
|
3045
|
-
*
|
|
3046
|
-
* Translation key formats supported:
|
|
3047
|
-
* - "widgets:common.addItem" - Namespaced key (for widget-specific translations)
|
|
3048
|
-
* - "Name" - Direct string (will be looked up in flat translation structure)
|
|
3049
|
-
* - "sections.personalDetails" - Nested key (for backward compatibility)
|
|
3050
|
-
*
|
|
3051
|
-
* With flat translation structure, direct strings like "Name" are automatically
|
|
3052
|
-
* translated by looking them up in the translation resources.
|
|
3053
|
-
*
|
|
3054
|
-
* @param keyOrString - Translation key (e.g., "widgets:common.addItem") or direct string (e.g., "Name")
|
|
3055
|
-
* @param options - Translation options (interpolation values, default value, etc.)
|
|
3056
|
-
* @returns Translated string or original string if translation not found
|
|
3057
|
-
*/
|
|
3058
|
-
const translate = (keyOrString, options) => {
|
|
3059
|
-
if (!keyOrString) {
|
|
3060
|
-
return options?.defaultValue || '';
|
|
3061
|
-
}
|
|
3062
|
-
// Use the provided translation function or fallback to the key
|
|
3063
|
-
if (translateFunction) {
|
|
3064
|
-
return translateFunction(keyOrString, options) || options?.defaultValue || keyOrString;
|
|
3065
|
-
}
|
|
3066
|
-
// Fallback to key if no translation function available
|
|
3067
|
-
return options?.defaultValue || keyOrString;
|
|
3068
|
-
};
|
|
3069
|
-
/**
|
|
3070
|
-
* Translate widget config property
|
|
3071
|
-
* Attempts to translate the value, but if translation is not found,
|
|
3072
|
-
* returns the original value as-is (graceful fallback)
|
|
3073
|
-
*
|
|
3074
|
-
* This function will:
|
|
3075
|
-
* - Try to translate any string value
|
|
3076
|
-
* - If translation exists, use the translated value
|
|
3077
|
-
* - If translation doesn't exist (returns same value or throws), use original value
|
|
3078
|
-
* - This prevents errors when literal strings like "XXXX-XXXX-XXXX" are used
|
|
3079
|
-
*/
|
|
3080
|
-
const translateConfig = (value, fallback) => {
|
|
3081
|
-
if (!value) {
|
|
3082
|
-
return fallback || '';
|
|
3083
|
-
}
|
|
3084
|
-
// Try to translate the value
|
|
3085
|
-
if (translateFunction) {
|
|
3086
|
-
try {
|
|
3087
|
-
// Pass defaultValue to ensure we get the original value if translation fails
|
|
3088
|
-
const translated = translateFunction(value, { defaultValue: value });
|
|
3089
|
-
// If translation returns empty, null, undefined, or the exact same value,
|
|
3090
|
-
// it means no translation was found - return the original value
|
|
3091
|
-
if (!translated || translated === value) {
|
|
3092
|
-
return value;
|
|
3093
|
-
}
|
|
3094
|
-
// Translation found, return it
|
|
3095
|
-
return translated;
|
|
3096
|
-
}
|
|
3097
|
-
catch (error) {
|
|
3098
|
-
// If translation throws an error (e.g., missing key warning), return original value
|
|
3099
|
-
return value;
|
|
3100
|
-
}
|
|
3101
|
-
}
|
|
3102
|
-
// No translation function available, return value as-is
|
|
3103
|
-
return value;
|
|
3104
|
-
};
|
|
3105
|
-
// No need of this getLanguage and changeLanguage functions
|
|
3106
|
-
/**
|
|
3107
|
-
* Get current language
|
|
3108
|
-
*/
|
|
3109
|
-
// const getLanguage = (): string => {
|
|
3110
|
-
// return i18n.language || 'en';
|
|
3111
|
-
// };
|
|
3112
|
-
/**
|
|
3113
|
-
* Change language
|
|
3114
|
-
*/
|
|
3115
|
-
// const changeLanguage = (lng: string): Promise<void> => {
|
|
3116
|
-
// return i18n.changeLanguage(lng).then(() => undefined);
|
|
3117
|
-
// };
|
|
3118
|
-
return {
|
|
3119
|
-
t: translate,
|
|
3120
|
-
translate,
|
|
3121
|
-
translateConfig,
|
|
3122
|
-
// getLanguage,
|
|
3123
|
-
// changeLanguage,
|
|
3124
|
-
// i18n: null,
|
|
3125
|
-
};
|
|
3171
|
+
});
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
// Singleton instance
|
|
3175
|
+
const widgetRegistry = new WidgetRegistry();
|
|
3176
|
+
|
|
3177
|
+
const WidgetRenderer = ({ config, dataSourceRequestHandler: propDataSourceRequestHandler, schemaData: propSchemaData, onValueChange, defaultComponent, }) => {
|
|
3178
|
+
// Use context values as fallback
|
|
3179
|
+
const context = useWidgetContext();
|
|
3180
|
+
const dataSourceRequestHandler = propDataSourceRequestHandler || context.dataSourceRequestHandler;
|
|
3181
|
+
const schemaData = propSchemaData || context.schemaData;
|
|
3182
|
+
// Warn if dataSourceRequestHandler is missing for API data sources, but don't break rendering
|
|
3183
|
+
// This allows widgets to render in read-only or static modes (e.g., CRView)
|
|
3184
|
+
if (!dataSourceRequestHandler && config['widget-data-source']?.type === 'api') {
|
|
3185
|
+
console.warn(`[WidgetRenderer] dataSourceRequestHandler is not provided for widget ${config['widget-id']} with API data source. ` +
|
|
3186
|
+
`The widget will render but API data source functionality will be disabled.`);
|
|
3187
|
+
}
|
|
3188
|
+
// Get values from Redux for cascade hooks
|
|
3189
|
+
const values = reactRedux.useSelector((state) => state.widget.values);
|
|
3190
|
+
const widgetContext = useBaseWidget({
|
|
3191
|
+
config,
|
|
3192
|
+
dataSourceRequestHandler,
|
|
3193
|
+
schemaData,
|
|
3194
|
+
onValueChange,
|
|
3195
|
+
});
|
|
3196
|
+
// Apply cascade hooks if configured (only if handler is available)
|
|
3197
|
+
if (dataSourceRequestHandler) {
|
|
3198
|
+
useWidgetCascade({
|
|
3199
|
+
config,
|
|
3200
|
+
dataSourceRequestHandler,
|
|
3201
|
+
values,
|
|
3202
|
+
});
|
|
3203
|
+
useGeoWidgetCascade({
|
|
3204
|
+
config,
|
|
3205
|
+
dataSourceRequestHandler,
|
|
3206
|
+
values,
|
|
3207
|
+
});
|
|
3208
|
+
}
|
|
3209
|
+
// Don't render if not visible
|
|
3210
|
+
if (!widgetContext.isVisible) {
|
|
3211
|
+
return null;
|
|
3212
|
+
}
|
|
3213
|
+
// Render widget using registry
|
|
3214
|
+
// Don't use key based on readonly state - it causes remounting which resets userHasSetValueRef
|
|
3215
|
+
// The readonly state is already handled in the widget components themselves
|
|
3216
|
+
return (jsxRuntimeExports.jsx("div", { className: "widget-container", "data-widget-id": widgetContext.widgetId, style: { marginBottom: 0 }, children: widgetRegistry.render(config, widgetContext, defaultComponent) }));
|
|
3126
3217
|
};
|
|
3127
3218
|
|
|
3128
3219
|
/**
|
|
@@ -3243,6 +3334,16 @@ const PanelRenderer = ({ panel, dataSourceRequestHandler, schemaData, onValueCha
|
|
|
3243
3334
|
return (jsxRuntimeExports.jsx("div", { className: `panel panel-${orientation}`, "data-panel-id": panel['panel-id'], style: orientation === 'horizontal' ? { width: '100%' } : {}, children: content }));
|
|
3244
3335
|
};
|
|
3245
3336
|
|
|
3337
|
+
/**
|
|
3338
|
+
* Field label: long text truncates with ellipsis; required asterisk always stays visible.
|
|
3339
|
+
*/
|
|
3340
|
+
const WidgetFieldLabel = ({ label, required = false, className = '', title, }) => {
|
|
3341
|
+
const { translateConfig } = useWidgetTranslation();
|
|
3342
|
+
const translatedLabel = translateConfig(label);
|
|
3343
|
+
const tooltip = title !== undefined ? translateConfig(title) : translatedLabel;
|
|
3344
|
+
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: "*" })] }));
|
|
3345
|
+
};
|
|
3346
|
+
|
|
3246
3347
|
/**
|
|
3247
3348
|
* Utility functions for file preview functionality
|
|
3248
3349
|
*/
|
|
@@ -3592,7 +3693,7 @@ const deserializeValue = (value) => {
|
|
|
3592
3693
|
};
|
|
3593
3694
|
|
|
3594
3695
|
const FileInputWidget = ({ config }) => {
|
|
3595
|
-
const { value, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
3696
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
3596
3697
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
3597
3698
|
const accept = widgetConfig['widget-data-options']?.accept;
|
|
3598
3699
|
const multiple = widgetConfig['widget-data-options']?.multiple || false;
|
|
@@ -3832,7 +3933,7 @@ const FileInputWidget = ({ config }) => {
|
|
|
3832
3933
|
setPreviewFile(null);
|
|
3833
3934
|
} })] }));
|
|
3834
3935
|
}
|
|
3835
|
-
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px]", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
3936
|
+
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px]", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-center gap-2 sm:space-x-4", children: [jsxRuntimeExports.jsxs("label", { className: `cursor-pointer inline-flex items-center justify-between gap-2 border border-gray-300 shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 ${!isEnabled
|
|
3836
3937
|
? 'opacity-50 cursor-not-allowed'
|
|
3837
3938
|
: ''}`, style: {
|
|
3838
3939
|
width: '100%',
|
|
@@ -3893,6 +3994,13 @@ const namespaceWidgetConfig = (widgetConfig, namespace) => {
|
|
|
3893
3994
|
if (namespaced['widget-data-path']) {
|
|
3894
3995
|
namespaced['widget-data-path'] = namespaceDataPath(namespaced['widget-data-path'], namespace);
|
|
3895
3996
|
}
|
|
3997
|
+
// Namespace geo parent references so cascade events match namespaced widget-id
|
|
3998
|
+
if (namespaced['widget-geo-config']?.parentWidgetId) {
|
|
3999
|
+
namespaced['widget-geo-config'] = {
|
|
4000
|
+
...namespaced['widget-geo-config'],
|
|
4001
|
+
parentWidgetId: `${namespace}__${namespaced['widget-geo-config'].parentWidgetId}`,
|
|
4002
|
+
};
|
|
4003
|
+
}
|
|
3896
4004
|
// Recursively namespace nested widgets (for layout widgets)
|
|
3897
4005
|
if (namespaced.widgets && Array.isArray(namespaced.widgets)) {
|
|
3898
4006
|
namespaced.widgets = namespaced.widgets.map((widget) => namespaceWidgetConfig(widget, namespace));
|
|
@@ -4097,6 +4205,9 @@ const sectionValidate = (section, currentSchemaData, dispatch, skipRequired = fa
|
|
|
4097
4205
|
const isVisible = shouldShowWidget(widget['widget-data-options'], currentSchemaData);
|
|
4098
4206
|
if (!isVisible)
|
|
4099
4207
|
continue;
|
|
4208
|
+
const isEnabled = shouldEnableWidget(widget['widget-data-options'], currentSchemaData);
|
|
4209
|
+
if (!isEnabled)
|
|
4210
|
+
continue;
|
|
4100
4211
|
const widgetId = widget['widget-id'];
|
|
4101
4212
|
if (isTableLikeWidget(widget)) {
|
|
4102
4213
|
const tableValid = validateTableLikeWidget(widget, currentSchemaData, dispatch, skipRequired);
|
|
@@ -4106,7 +4217,8 @@ const sectionValidate = (section, currentSchemaData, dispatch, skipRequired = fa
|
|
|
4106
4217
|
continue;
|
|
4107
4218
|
}
|
|
4108
4219
|
const value = getWidgetValue(currentSchemaData, widget['widget-data-path'], widgetId);
|
|
4109
|
-
const
|
|
4220
|
+
const isRequired = shouldRequireWidget(widget['widget-data-options'], currentSchemaData, widget['widget-required'] ?? false);
|
|
4221
|
+
const errors = validateWidget(value, widget['widget-data-validation'], isRequired, skipRequired);
|
|
4110
4222
|
if (errors.length > 0) {
|
|
4111
4223
|
isValid = false;
|
|
4112
4224
|
dispatch(setTouched({ widgetId, touched: true }));
|
|
@@ -4139,6 +4251,108 @@ const sectionValidate = (section, currentSchemaData, dispatch, skipRequired = fa
|
|
|
4139
4251
|
return isValid;
|
|
4140
4252
|
};
|
|
4141
4253
|
|
|
4254
|
+
const cloneValue = (value) => {
|
|
4255
|
+
if (value === undefined) {
|
|
4256
|
+
return undefined;
|
|
4257
|
+
}
|
|
4258
|
+
try {
|
|
4259
|
+
return structuredClone(value);
|
|
4260
|
+
}
|
|
4261
|
+
catch {
|
|
4262
|
+
return JSON.parse(JSON.stringify(value));
|
|
4263
|
+
}
|
|
4264
|
+
};
|
|
4265
|
+
const resolveNamespacedWidgetId = (widgetId, namespace) => namespace ? `${namespace}__${widgetId}` : widgetId;
|
|
4266
|
+
const resolveStoreDataPath = (dataPath, namespace) => {
|
|
4267
|
+
if (!dataPath) {
|
|
4268
|
+
return dataPath;
|
|
4269
|
+
}
|
|
4270
|
+
if (!namespace) {
|
|
4271
|
+
return dataPath;
|
|
4272
|
+
}
|
|
4273
|
+
if (typeof dataPath === 'string') {
|
|
4274
|
+
return `${namespace}.${dataPath}`;
|
|
4275
|
+
}
|
|
4276
|
+
return Object.fromEntries(Object.entries(dataPath).map(([key, path]) => [key, `${namespace}.${path}`]));
|
|
4277
|
+
};
|
|
4278
|
+
/**
|
|
4279
|
+
* Capture Redux widget values for a section at edit entry.
|
|
4280
|
+
* Used to restore exact pre-edit state on Cancel (schemaData may be stale or shared with Redux).
|
|
4281
|
+
*/
|
|
4282
|
+
function captureSectionEditSnapshot(values, section, options) {
|
|
4283
|
+
const { namespace, sectionId, supportingDocuments = [] } = options ?? {};
|
|
4284
|
+
const dataPaths = [];
|
|
4285
|
+
const processedPaths = new Set();
|
|
4286
|
+
const widgetIds = {};
|
|
4287
|
+
const addPath = (path) => {
|
|
4288
|
+
if (!path || processedPaths.has(path)) {
|
|
4289
|
+
return;
|
|
4290
|
+
}
|
|
4291
|
+
processedPaths.add(path);
|
|
4292
|
+
dataPaths.push({
|
|
4293
|
+
path,
|
|
4294
|
+
value: cloneValue(getValueByPath(values, path)),
|
|
4295
|
+
});
|
|
4296
|
+
if (path.endsWith('.geo_code_hierarchy_json')) {
|
|
4297
|
+
const prefix = path.slice(0, -'.geo_code_hierarchy_json'.length);
|
|
4298
|
+
addPath(`${prefix}.geo_lowest_level_value_id`);
|
|
4299
|
+
}
|
|
4300
|
+
};
|
|
4301
|
+
collectWidgets(section.panels).forEach((widget) => {
|
|
4302
|
+
const widgetId = resolveNamespacedWidgetId(widget['widget-id'], namespace);
|
|
4303
|
+
const storeDataPath = resolveStoreDataPath(widget['widget-data-path'], namespace);
|
|
4304
|
+
if (Object.prototype.hasOwnProperty.call(values, widgetId)) {
|
|
4305
|
+
widgetIds[widgetId] = { present: true, value: cloneValue(values[widgetId]) };
|
|
4306
|
+
}
|
|
4307
|
+
else {
|
|
4308
|
+
widgetIds[widgetId] = { present: false };
|
|
4309
|
+
}
|
|
4310
|
+
if (typeof storeDataPath === 'string') {
|
|
4311
|
+
addPath(storeDataPath);
|
|
4312
|
+
}
|
|
4313
|
+
else if (storeDataPath && typeof storeDataPath === 'object') {
|
|
4314
|
+
Object.values(storeDataPath).forEach((path) => {
|
|
4315
|
+
if (typeof path === 'string') {
|
|
4316
|
+
addPath(path);
|
|
4317
|
+
}
|
|
4318
|
+
});
|
|
4319
|
+
}
|
|
4320
|
+
});
|
|
4321
|
+
supportingDocuments.forEach((doc, index) => {
|
|
4322
|
+
const widgetId = `supporting-doc-${sectionId ?? 'section'}-${index}`;
|
|
4323
|
+
const storeDataPath = namespace && doc['document-data-path']
|
|
4324
|
+
? `${namespace}.${doc['document-data-path']}`
|
|
4325
|
+
: doc['document-data-path'];
|
|
4326
|
+
if (Object.prototype.hasOwnProperty.call(values, widgetId)) {
|
|
4327
|
+
widgetIds[widgetId] = { present: true, value: cloneValue(values[widgetId]) };
|
|
4328
|
+
}
|
|
4329
|
+
else {
|
|
4330
|
+
widgetIds[widgetId] = { present: false };
|
|
4331
|
+
}
|
|
4332
|
+
if (typeof storeDataPath === 'string') {
|
|
4333
|
+
addPath(storeDataPath);
|
|
4334
|
+
}
|
|
4335
|
+
});
|
|
4336
|
+
return { dataPaths, widgetIds };
|
|
4337
|
+
}
|
|
4338
|
+
/** Apply a section edit snapshot back onto the full Redux values object. */
|
|
4339
|
+
function applySectionEditSnapshot(currentValues, snapshot) {
|
|
4340
|
+
let result = currentValues;
|
|
4341
|
+
for (const { path, value } of snapshot.dataPaths) {
|
|
4342
|
+
result = setValueByPath(result, path, cloneValue(value));
|
|
4343
|
+
}
|
|
4344
|
+
for (const [widgetId, entry] of Object.entries(snapshot.widgetIds)) {
|
|
4345
|
+
if (entry.present) {
|
|
4346
|
+
result = { ...result, [widgetId]: cloneValue(entry.value) };
|
|
4347
|
+
}
|
|
4348
|
+
else {
|
|
4349
|
+
const { [widgetId]: _removed, ...rest } = result;
|
|
4350
|
+
result = rest;
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
return result;
|
|
4354
|
+
}
|
|
4355
|
+
|
|
4142
4356
|
/** Root class on readonly label/value rows; SectionRenderer scopes overflow/ellipsis rules here. */
|
|
4143
4357
|
const READONLY_VALUE_ROW_ROOT_CLASSES = [
|
|
4144
4358
|
'TextDisplayWidget',
|
|
@@ -4350,6 +4564,8 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4350
4564
|
}, [forceExitEdit]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
4351
4565
|
const [isDocumentsExpanded, setIsDocumentsExpanded] = React.useState(true);
|
|
4352
4566
|
const sectionRef = React.useRef(null);
|
|
4567
|
+
const baselineSnapshotRef = React.useRef(null);
|
|
4568
|
+
const editEntrySnapshotRef = React.useRef(null);
|
|
4353
4569
|
const [sectionHeight, setSectionHeight] = React.useState(null);
|
|
4354
4570
|
const [editSectionPosition, setEditSectionPosition] = React.useState(null);
|
|
4355
4571
|
// Capture section position when entering edit mode and update on scroll
|
|
@@ -4414,6 +4630,18 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4414
4630
|
panels: makePanelsEditable(sectionToRender.panels, widgetsEditable),
|
|
4415
4631
|
};
|
|
4416
4632
|
}, [sectionToRender, widgetsEditable]);
|
|
4633
|
+
const effectiveHideEditButton = hideEditButton ||
|
|
4634
|
+
section['section-hide-edit-button'] === true ||
|
|
4635
|
+
!collectWidgets(section.panels || []).some((w) => section['section-editable'] === true || w['widget-readonly'] !== true);
|
|
4636
|
+
const captureEditEntrySnapshot = React.useCallback(() => {
|
|
4637
|
+
const currentValues = store.getState().widget.values;
|
|
4638
|
+
const supportingDocuments = section['section-supporting-documents'] || [];
|
|
4639
|
+
editEntrySnapshotRef.current = captureSectionEditSnapshot(currentValues, section, {
|
|
4640
|
+
namespace,
|
|
4641
|
+
sectionId,
|
|
4642
|
+
supportingDocuments: hasSupportingDocuments ? supportingDocuments : [],
|
|
4643
|
+
});
|
|
4644
|
+
}, [store, section, namespace, sectionId, hasSupportingDocuments]);
|
|
4417
4645
|
// Handle edit button click
|
|
4418
4646
|
const handleEdit = () => {
|
|
4419
4647
|
// Capture height BEFORE entering edit mode to preserve space
|
|
@@ -4421,6 +4649,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4421
4649
|
const height = sectionRef.current.offsetHeight;
|
|
4422
4650
|
setSectionHeight(height);
|
|
4423
4651
|
}
|
|
4652
|
+
captureEditEntrySnapshot();
|
|
4424
4653
|
setIsEditMode(true);
|
|
4425
4654
|
onEditModeChange?.(originalSectionId, true);
|
|
4426
4655
|
};
|
|
@@ -4595,8 +4824,6 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4595
4824
|
}
|
|
4596
4825
|
return { records, files };
|
|
4597
4826
|
}, [originalSection, hasSupportingDocuments]);
|
|
4598
|
-
// Capture baseline when entering edit mode (used for isDirty comparison)
|
|
4599
|
-
const baselineSnapshotRef = React.useRef(null);
|
|
4600
4827
|
// IntakeForm only: increment when baseline is updated after save - forces badge to update (refs don't trigger re-renders)
|
|
4601
4828
|
const [intakeFormBaselineTrigger, setIntakeFormBaselineTrigger] = React.useState(0);
|
|
4602
4829
|
// IntakeForm: treat as edit mode for dirty tracking when isDraft. RegistryView: use isEditMode.
|
|
@@ -4614,6 +4841,9 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4614
4841
|
// Set baseline when entering edit mode; clear when leaving (baseline captured only on entry)
|
|
4615
4842
|
React.useEffect(() => {
|
|
4616
4843
|
if (effectiveEditModeForDirty) {
|
|
4844
|
+
if (!editEntrySnapshotRef.current) {
|
|
4845
|
+
captureEditEntrySnapshot();
|
|
4846
|
+
}
|
|
4617
4847
|
const oldSchemaData = schemaData || contextSchemaData || {};
|
|
4618
4848
|
if (namespace) {
|
|
4619
4849
|
const namespacedSchema = getValueByPath(oldSchemaData, namespace);
|
|
@@ -4622,11 +4852,12 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4622
4852
|
: buildSectionSnapshot(oldSchemaData);
|
|
4623
4853
|
}
|
|
4624
4854
|
else {
|
|
4625
|
-
baselineSnapshotRef.current = buildSectionSnapshot(
|
|
4855
|
+
baselineSnapshotRef.current = buildSectionSnapshot(store.getState().widget.values, namespace);
|
|
4626
4856
|
}
|
|
4627
4857
|
}
|
|
4628
4858
|
else {
|
|
4629
4859
|
baselineSnapshotRef.current = null;
|
|
4860
|
+
editEntrySnapshotRef.current = null;
|
|
4630
4861
|
onSectionDirtyChange?.(sectionId, false);
|
|
4631
4862
|
}
|
|
4632
4863
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- baseline must be captured only when effectiveEditModeForDirty toggles
|
|
@@ -4652,56 +4883,103 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4652
4883
|
// and handleCancel.
|
|
4653
4884
|
const revertToOriginalValues = React.useCallback(() => {
|
|
4654
4885
|
const sectionWidgets = collectWidgets(originalSection.panels);
|
|
4655
|
-
const oldSchemaData = schemaData || contextSchemaData;
|
|
4656
4886
|
const currentStoreValues = store.getState().widget.values;
|
|
4887
|
+
const snapshot = editEntrySnapshotRef.current;
|
|
4657
4888
|
let newStoreValues = currentStoreValues;
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
const
|
|
4663
|
-
const
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4889
|
+
if (snapshot) {
|
|
4890
|
+
newStoreValues = applySectionEditSnapshot(currentStoreValues, snapshot);
|
|
4891
|
+
}
|
|
4892
|
+
else {
|
|
4893
|
+
const oldSchemaData = schemaData || contextSchemaData;
|
|
4894
|
+
const processedGeoGroups = new Set();
|
|
4895
|
+
sectionWidgets.forEach((widget) => {
|
|
4896
|
+
const originalWidgetId = widget['widget-id'];
|
|
4897
|
+
const namespacedWidgetId = namespace ? `${namespace}__${originalWidgetId}` : originalWidgetId;
|
|
4898
|
+
const widgetId = namespacedWidgetId;
|
|
4899
|
+
const originalDataPath = widget['widget-data-path'];
|
|
4900
|
+
const storeDataPath = namespace && originalDataPath
|
|
4901
|
+
? (typeof originalDataPath === 'string'
|
|
4902
|
+
? `${namespace}.${originalDataPath}`
|
|
4903
|
+
: Object.fromEntries(Object.entries(originalDataPath).map(([key, path]) => [key, `${namespace}.${path}`])))
|
|
4904
|
+
: originalDataPath;
|
|
4905
|
+
const geoConfig = widget['widget-geo-config'];
|
|
4906
|
+
if (widgetId && originalDataPath) {
|
|
4907
|
+
let oldValue;
|
|
4908
|
+
if (typeof originalDataPath === 'object') {
|
|
4909
|
+
oldValue = {};
|
|
4910
|
+
Object.entries(originalDataPath).forEach(([key, path]) => {
|
|
4911
|
+
if (typeof path === 'string') {
|
|
4912
|
+
oldValue[key] = getValueByPath(oldSchemaData, path);
|
|
4913
|
+
}
|
|
4914
|
+
});
|
|
4915
|
+
}
|
|
4916
|
+
else if (typeof originalDataPath === 'string') {
|
|
4917
|
+
oldValue = getValueByPath(oldSchemaData, originalDataPath);
|
|
4918
|
+
}
|
|
4919
|
+
if (oldValue !== undefined) {
|
|
4920
|
+
newStoreValues = setWidgetValue(newStoreValues, storeDataPath, widgetId, oldValue);
|
|
4921
|
+
if (geoConfig && typeof storeDataPath === 'string') {
|
|
4922
|
+
const groupId = getGeoGroupId(storeDataPath);
|
|
4923
|
+
const levelValue = resolveGeoWidgetLevelValue(newStoreValues, widgetId, storeDataPath, geoConfig);
|
|
4924
|
+
if (levelValue !== undefined && levelValue !== null && levelValue !== '') {
|
|
4925
|
+
newStoreValues = { ...newStoreValues, [widgetId]: levelValue };
|
|
4926
|
+
}
|
|
4927
|
+
else {
|
|
4928
|
+
const { [widgetId]: _removed, ...rest } = newStoreValues;
|
|
4929
|
+
newStoreValues = rest;
|
|
4930
|
+
}
|
|
4931
|
+
if (!processedGeoGroups.has(groupId)) {
|
|
4932
|
+
resetAndSeedGeoHierarchyFromValues(newStoreValues, storeDataPath, widgetId, groupId);
|
|
4933
|
+
processedGeoGroups.add(groupId);
|
|
4934
|
+
}
|
|
4935
|
+
if (geoConfig.parentWidgetId) {
|
|
4936
|
+
dispatch(setDataSource({ widgetId, data: [] }));
|
|
4937
|
+
}
|
|
4675
4938
|
}
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4939
|
+
else {
|
|
4940
|
+
newStoreValues = { ...newStoreValues, [widgetId]: oldValue };
|
|
4941
|
+
}
|
|
4942
|
+
}
|
|
4680
4943
|
}
|
|
4681
|
-
|
|
4944
|
+
});
|
|
4945
|
+
if (hasSupportingDocuments) {
|
|
4946
|
+
const originalSupportingDocuments = originalSection['section-supporting-documents'] || [];
|
|
4947
|
+
originalSupportingDocuments.forEach((doc, index) => {
|
|
4948
|
+
const widgetId = `supporting-doc-${sectionId}-${index}`;
|
|
4949
|
+
const originalDataPath = doc['document-data-path'];
|
|
4950
|
+
const storeDataPath = namespace && originalDataPath
|
|
4951
|
+
? `${namespace}.${originalDataPath}`
|
|
4952
|
+
: originalDataPath;
|
|
4953
|
+
const oldValue = getValueByPath(oldSchemaData, originalDataPath);
|
|
4682
4954
|
newStoreValues = setWidgetValue(newStoreValues, storeDataPath, widgetId, oldValue);
|
|
4683
|
-
|
|
4684
|
-
// sets values[widgetId] during editing, and useBaseWidget.currentValue
|
|
4685
|
-
// reads values[widgetId] first before falling through to the dataPath.
|
|
4686
|
-
newStoreValues = { ...newStoreValues, [widgetId]: oldValue };
|
|
4687
|
-
}
|
|
4955
|
+
});
|
|
4688
4956
|
}
|
|
4689
|
-
});
|
|
4690
|
-
if (hasSupportingDocuments) {
|
|
4691
|
-
const originalSupportingDocuments = originalSection['section-supporting-documents'] || [];
|
|
4692
|
-
originalSupportingDocuments.forEach((doc, index) => {
|
|
4693
|
-
const widgetId = `supporting-doc-${sectionId}-${index}`;
|
|
4694
|
-
const originalDataPath = doc['document-data-path'];
|
|
4695
|
-
const storeDataPath = namespace && originalDataPath
|
|
4696
|
-
? `${namespace}.${originalDataPath}`
|
|
4697
|
-
: originalDataPath;
|
|
4698
|
-
const oldValue = getValueByPath(oldSchemaData, originalDataPath);
|
|
4699
|
-
newStoreValues = setWidgetValue(newStoreValues, storeDataPath, widgetId, oldValue);
|
|
4700
|
-
});
|
|
4701
|
-
}
|
|
4702
|
-
if (newStoreValues !== currentStoreValues) {
|
|
4703
|
-
dispatch(setValues(newStoreValues));
|
|
4704
4957
|
}
|
|
4958
|
+
const processedGeoGroups = new Set();
|
|
4959
|
+
sectionWidgets.forEach((widget) => {
|
|
4960
|
+
const geoConfig = widget['widget-geo-config'];
|
|
4961
|
+
if (!geoConfig) {
|
|
4962
|
+
return;
|
|
4963
|
+
}
|
|
4964
|
+
const originalWidgetId = widget['widget-id'];
|
|
4965
|
+
const widgetId = namespace ? `${namespace}__${originalWidgetId}` : originalWidgetId;
|
|
4966
|
+
const originalDataPath = widget['widget-data-path'];
|
|
4967
|
+
const storeDataPath = namespace && typeof originalDataPath === 'string'
|
|
4968
|
+
? `${namespace}.${originalDataPath}`
|
|
4969
|
+
: originalDataPath;
|
|
4970
|
+
if (typeof storeDataPath !== 'string') {
|
|
4971
|
+
return;
|
|
4972
|
+
}
|
|
4973
|
+
const groupId = getGeoGroupId(storeDataPath);
|
|
4974
|
+
if (!processedGeoGroups.has(groupId)) {
|
|
4975
|
+
resetAndSeedGeoHierarchyFromValues(newStoreValues, storeDataPath, widgetId, groupId);
|
|
4976
|
+
processedGeoGroups.add(groupId);
|
|
4977
|
+
}
|
|
4978
|
+
if (geoConfig.parentWidgetId) {
|
|
4979
|
+
dispatch(setDataSource({ widgetId, data: [] }));
|
|
4980
|
+
}
|
|
4981
|
+
});
|
|
4982
|
+
dispatch(setValues(newStoreValues));
|
|
4705
4983
|
}, [originalSection, schemaData, contextSchemaData, store, namespace, hasSupportingDocuments, sectionId, dispatch]);
|
|
4706
4984
|
// Handle save button click
|
|
4707
4985
|
const handleSave = async () => {
|
|
@@ -5197,7 +5475,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
5197
5475
|
color: changeRequestType === 'new' ? 'var(--owt-color-bg, #FFFFFF)' : 'var(--owt-color-error, #B91C1C)',
|
|
5198
5476
|
whiteSpace: 'nowrap',
|
|
5199
5477
|
boxShadow: changeRequestType === 'new' ? '0 2px 4px rgba(40, 167, 69, 0.3)' : 'none',
|
|
5200
|
-
}, children: changeRequestType === 'new' ? 'New' : 'Old' }))] })), jsxRuntimeExports.jsxs("div", { id: gridId, className: "section-panels", style: mode === 'RegistryView' &&
|
|
5478
|
+
}, children: changeRequestType === 'new' ? 'New' : 'Old' }))] })), jsxRuntimeExports.jsxs("div", { id: gridId, className: "section-panels", style: mode === 'RegistryView' && effectiveHideEditButton ? { paddingBottom: '30px' } : {}, children: [editableSection.panels.map((panel, index) => (jsxRuntimeExports.jsx("div", { className: "panel-wrapper", children: jsxRuntimeExports.jsx(PanelRenderer, { panel: panel, dataSourceRequestHandler: dataSourceRequestHandler, schemaData: namespacedSchemaData, onValueChange: onValueChange }) }, panel['panel-id'] || `section-panel-${index}`))), mode === 'CRView' && crViewData && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("hr", { className: "w-full", style: { height: '1px', marginTop: '20px', marginBottom: '0px', border: 'none', backgroundColor: 'var(--owt-color-border, #C4C4C4)' } }), jsxRuntimeExports.jsxs("div", { className: "cr-view-container", style: {
|
|
5201
5479
|
marginTop: '20px',
|
|
5202
5480
|
paddingBottom: '30px',
|
|
5203
5481
|
display: 'flex',
|
|
@@ -5245,7 +5523,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
5245
5523
|
fontSize: '14px',
|
|
5246
5524
|
color: 'var(--owt-color-text, #011627)',
|
|
5247
5525
|
fontWeight: 'normal',
|
|
5248
|
-
}, children: crViewData.approvedDate }))] })] })] })), mode === 'RegistryView' && !
|
|
5526
|
+
}, children: crViewData.approvedDate }))] })] })] })), mode === 'RegistryView' && !effectiveHideEditButton && (jsxRuntimeExports.jsx("hr", { className: "w-full", style: { height: '1px', marginTop: !isEditMode ? '10px' : 0, marginBottom: '14px', border: 'none', backgroundColor: 'var(--owt-color-border, #C4C4C4)' } })), mode === 'RegistryView' && !isEditMode && !effectiveHideEditButton && (jsxRuntimeExports.jsx("div", { className: "flex justify-center items-center", style: { marginBottom: '20px' }, children: jsxRuntimeExports.jsxs("button", { onClick: handleEdit, className: "font-normal inline-flex items-center gap-2 bg-transparent border-0 p-0 cursor-pointer hover:opacity-80", style: {
|
|
5249
5527
|
fontFamily: 'Roboto, sans-serif',
|
|
5250
5528
|
fontSize: '16px',
|
|
5251
5529
|
color: 'var(--owt-color-text-muted, #727474)',
|
|
@@ -6051,7 +6329,7 @@ const JSONEditorPanel = ({ section, onChange, onReset, context = 'section', }) =
|
|
|
6051
6329
|
'widget-data-format.dateConstraint': ['any', 'past-only', 'future-only'],
|
|
6052
6330
|
'widget-data-format.dateTimeConstraint': ['any', 'past-only', 'future-only'],
|
|
6053
6331
|
// Widget options
|
|
6054
|
-
'widget-data-options.action': ['show', 'hide', 'enable', 'disable'],
|
|
6332
|
+
'widget-data-options.action': ['show', 'hide', 'enable', 'disable', 'require'],
|
|
6055
6333
|
'widget-data-options.condition.operator': CONDITION_OPERATORS,
|
|
6056
6334
|
};
|
|
6057
6335
|
}, []);
|
|
@@ -7436,7 +7714,7 @@ const removeMask = (value, mask) => {
|
|
|
7436
7714
|
};
|
|
7437
7715
|
|
|
7438
7716
|
const TextInputWidget = ({ config }) => {
|
|
7439
|
-
const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
7717
|
+
const { value, formattedValue, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
7440
7718
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
7441
7719
|
// Track raw value separately for masking (to preserve unmasked value internally)
|
|
7442
7720
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
@@ -7573,7 +7851,7 @@ const TextInputWidget = ({ config }) => {
|
|
|
7573
7851
|
const label = translateConfig(widgetConfig['widget-label']);
|
|
7574
7852
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] TextDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
|
|
7575
7853
|
}
|
|
7576
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
7854
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxRuntimeExports.jsx("input", { type: getInputType(), value: displayValue, onChange: handleChange, onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, maxLength: formatConfig?.mask ? undefined : maxLength, inputMode: formatConfig?.currency
|
|
7577
7855
|
? 'decimal'
|
|
7578
7856
|
: formatConfig?.characterType === 'numeric' || formatConfig?.characterType === 'numeric-decimal'
|
|
7579
7857
|
? 'numeric'
|
|
@@ -7596,7 +7874,7 @@ const NumberInputWidget = ({ config }) => {
|
|
|
7596
7874
|
}
|
|
7597
7875
|
return { ...config, 'widget-data-default': normalizedDefault };
|
|
7598
7876
|
}, [config]);
|
|
7599
|
-
const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config: resolvedConfig });
|
|
7877
|
+
const { value, formattedValue, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config: resolvedConfig });
|
|
7600
7878
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
7601
7879
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
7602
7880
|
const validationConfig = widgetConfig['widget-data-validation'];
|
|
@@ -7727,7 +8005,7 @@ const NumberInputWidget = ({ config }) => {
|
|
|
7727
8005
|
const display = numValue !== null ? formatNumber(numValue, formatConfig) : '';
|
|
7728
8006
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] NumberDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: `text-base text-gray-900 font-medium ${textAlignClass}`, title: String(display ?? ''), children: display }) })] }));
|
|
7729
8007
|
}
|
|
7730
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
8008
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-1", children: [jsxRuntimeExports.jsx("input", { type: "text", inputMode: "decimal", value: displayValue, onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, onKeyDown: handleKeyDown, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, maxLength: maxLength, className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${textAlignClass} ${(touched && error.length > 0) || (widgetConfig['widget-required'] && (value === null || value === undefined || value === ''))
|
|
7731
8009
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
7732
8010
|
: 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }), maxLength && (jsxRuntimeExports.jsxs("span", { className: `text-xs ml-2 flex-shrink-0 ${currentLength > maxLength
|
|
7733
8011
|
? 'text-red-500'
|
|
@@ -7735,7 +8013,7 @@ const NumberInputWidget = ({ config }) => {
|
|
|
7735
8013
|
};
|
|
7736
8014
|
|
|
7737
8015
|
const BooleanWidget = ({ config }) => {
|
|
7738
|
-
const { value, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
8016
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
7739
8017
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
7740
8018
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
7741
8019
|
const representation = formatConfig?.booleanRepresentation || 'true-false';
|
|
@@ -7804,7 +8082,7 @@ const BooleanWidget = ({ config }) => {
|
|
|
7804
8082
|
}
|
|
7805
8083
|
// Render based on control type
|
|
7806
8084
|
if (controlType === 'checkbox') {
|
|
7807
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.
|
|
8085
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium leading-normal text-gray-700 md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("label", { className: "inline-flex items-baseline cursor-pointer gap-2", children: [jsxRuntimeExports.jsx("input", { type: "checkbox", checked: currentValue === true, onChange: handleCheckboxChange, onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), (currentValue === true || currentValue === false) && (jsxRuntimeExports.jsx("span", { className: "text-base text-gray-700 leading-normal", children: currentValue === true ? trueLabel : falseLabel }))] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
7808
8086
|
}
|
|
7809
8087
|
if (controlType === 'radio') {
|
|
7810
8088
|
const containerClass = orientation === 'horizontal'
|
|
@@ -7812,10 +8090,10 @@ const BooleanWidget = ({ config }) => {
|
|
|
7812
8090
|
: 'flex flex-col items-start gap-2';
|
|
7813
8091
|
const radioDisabled = !isEnabled || widgetConfig['widget-readonly'];
|
|
7814
8092
|
const optionDisabledClass = radioDisabled ? 'opacity-50 cursor-not-allowed' : '';
|
|
7815
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.
|
|
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 md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: containerClass, onBlur: onBlur, children: [allowUnset && (jsxRuntimeExports.jsxs("label", { className: `inline-flex items-baseline gap-2 cursor-pointer ${optionDisabledClass}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: radioGroupName, checked: currentValue === null, onChange: () => handleRadioChange(null), disabled: radioDisabled, className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-base text-gray-700 leading-normal", children: unsetLabel })] })), jsxRuntimeExports.jsxs("label", { className: `inline-flex items-baseline gap-2 cursor-pointer ${optionDisabledClass}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: radioGroupName, checked: currentValue === true, onChange: () => handleRadioChange(true), disabled: radioDisabled, className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-base text-gray-700 leading-normal", children: trueLabel })] }), jsxRuntimeExports.jsxs("label", { className: `inline-flex items-baseline gap-2 cursor-pointer ${optionDisabledClass}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: radioGroupName, checked: currentValue === false, onChange: () => handleRadioChange(false), disabled: radioDisabled, className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-base text-gray-700 leading-normal", children: falseLabel })] })] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
7816
8094
|
}
|
|
7817
8095
|
// Toggle/switch control type
|
|
7818
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.
|
|
8096
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium leading-normal text-gray-700 sm:min-w-[150px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-3", onBlur: onBlur, children: [allowUnset && (jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleChange(null), disabled: !isEnabled || widgetConfig['widget-readonly'], className: `px-3 py-1 text-sm border ${currentValue === null
|
|
7819
8097
|
? 'bg-blue-600 text-white border-blue-600'
|
|
7820
8098
|
: 'bg-white text-gray-700 border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-50'}`, style: { borderRadius: '15px' }, children: unsetLabel })), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleChange(true), disabled: !isEnabled || widgetConfig['widget-readonly'], className: `px-3 py-1 text-sm border ${currentValue === true
|
|
7821
8099
|
? 'bg-blue-600 text-white border-blue-600'
|
|
@@ -7825,7 +8103,7 @@ const BooleanWidget = ({ config }) => {
|
|
|
7825
8103
|
};
|
|
7826
8104
|
|
|
7827
8105
|
const DateInputWidget = ({ config }) => {
|
|
7828
|
-
const { value, error, touched, isEnabled, onChange, onBlur, setError, config: widgetConfig, } = useBaseWidget({ config });
|
|
8106
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, setError, config: widgetConfig, } = useBaseWidget({ config });
|
|
7829
8107
|
const formValues = reactRedux.useSelector((state) => state.widget.values);
|
|
7830
8108
|
const { translateConfig } = useWidgetTranslation();
|
|
7831
8109
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
@@ -8026,7 +8304,7 @@ const DateInputWidget = ({ config }) => {
|
|
|
8026
8304
|
}
|
|
8027
8305
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] DateDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
|
|
8028
8306
|
}
|
|
8029
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
8307
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("input", { type: inputType, value: getDisplayValue(), onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, min: inputMethod === 'picker' ? effectiveMinDate : undefined, max: inputMethod === 'picker' ? effectiveMaxDate : undefined, className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${showValidationError || showRequiredError
|
|
8030
8308
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
8031
8309
|
: 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }), showValidationError && jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] })] })] }) }));
|
|
8032
8310
|
};
|
|
@@ -8316,7 +8594,7 @@ const validateDateTimeConstraints = (date, minDateTime, maxDateTime, constraint)
|
|
|
8316
8594
|
};
|
|
8317
8595
|
|
|
8318
8596
|
const DateTimeInputWidget = ({ config }) => {
|
|
8319
|
-
const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
8597
|
+
const { value, formattedValue, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
8320
8598
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8321
8599
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
8322
8600
|
const optionsConfig = widgetConfig['widget-data-options'];
|
|
@@ -8469,29 +8747,33 @@ const DateTimeInputWidget = ({ config }) => {
|
|
|
8469
8747
|
}
|
|
8470
8748
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] DateTimeDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
|
|
8471
8749
|
}
|
|
8472
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
8750
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("input", { type: inputType, value: getDisplayValue(), onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, min: inputMethod === 'picker' ? effectiveMinDateTime : undefined, max: inputMethod === 'picker' ? effectiveMaxDateTime : undefined, className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${(touched && error.length > 0) || (widgetConfig['widget-required'] && (!value || value === ''))
|
|
8473
8751
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
8474
8752
|
: 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
8475
8753
|
};
|
|
8476
8754
|
|
|
8477
8755
|
const SelectWidget = ({ config }) => {
|
|
8478
|
-
const { value, error, touched, isEnabled, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
8756
|
+
const { value, geoDisplayLabel, error, touched, isEnabled, isRequired, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
8479
8757
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8480
8758
|
// For readonly mode, render as display text showing only the selected label
|
|
8481
8759
|
if (widgetConfig['widget-readonly']) {
|
|
8482
8760
|
const label = translateConfig(widgetConfig['widget-label']);
|
|
8483
8761
|
// Find the selected option's label
|
|
8484
|
-
const selectedOption = dataSourceOptions.find((option) => option.value === value);
|
|
8485
|
-
const displayValue = selectedOption
|
|
8762
|
+
const selectedOption = dataSourceOptions.find((option) => option.value === value || String(option.value) === String(value));
|
|
8763
|
+
const displayValue = selectedOption
|
|
8764
|
+
? translateConfig(selectedOption.label)
|
|
8765
|
+
: loading
|
|
8766
|
+
? (geoDisplayLabel || '-')
|
|
8767
|
+
: (geoDisplayLabel || (value != null && value !== '' ? translateConfig(String(value)) : '-'));
|
|
8486
8768
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] SelectDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
|
|
8487
8769
|
}
|
|
8488
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
8770
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("select", { value: value || '', onChange: (e) => onChange(e.target.value === '' ? undefined : e.target.value), onBlur: onBlur, disabled: !isEnabled || loading || widgetConfig['widget-readonly'], className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${(touched && error.length > 0) || (widgetConfig['widget-required'] && (!value || value === ''))
|
|
8489
8771
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
8490
|
-
: 'border-gray-300'} ${!isEnabled || loading || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']), children: [jsxRuntimeExports.jsx("option", { value: "", children: translate('common.select') }), dataSourceOptions.map((option) => (jsxRuntimeExports.jsx("option", { value: option.value, children: option.label }, option.value)))] }), loading && (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500 mt-1", children: translate('common.loadingOptions') })), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
8772
|
+
: 'border-gray-300'} ${!isEnabled || loading || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']), children: [jsxRuntimeExports.jsx("option", { value: "", children: translate('common.select') }), dataSourceOptions.map((option) => (jsxRuntimeExports.jsx("option", { value: option.value, children: translateConfig(option.label) }, option.value)))] }), loading && (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500 mt-1", children: translate('common.loadingOptions') })), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
8491
8773
|
};
|
|
8492
8774
|
|
|
8493
8775
|
const RadioWidget = ({ config }) => {
|
|
8494
|
-
const { value, error, touched, isEnabled, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
8776
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
8495
8777
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8496
8778
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
8497
8779
|
const layout = formatConfig?.layout || widgetConfig['widget-orientation'] || 'vertical';
|
|
@@ -8554,14 +8836,16 @@ const RadioWidget = ({ config }) => {
|
|
|
8554
8836
|
if (widgetConfig['widget-readonly']) {
|
|
8555
8837
|
const label = translateConfig(widgetConfig['widget-label']);
|
|
8556
8838
|
const selectedOption = processedOptions.find(opt => opt.value === currentValue);
|
|
8557
|
-
const displayValue = selectedOption
|
|
8839
|
+
const displayValue = selectedOption
|
|
8840
|
+
? translateConfig(selectedOption.label)
|
|
8841
|
+
: (allowUnset && currentValue === null ? '-' : '');
|
|
8558
8842
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] RadioDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
|
|
8559
8843
|
}
|
|
8560
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
8844
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("div", { className: layoutConfig.className, style: layoutConfig.style, onBlur: onBlur, children: loading ? (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500", children: translate('common.loading') })) : (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [allowUnset && (jsxRuntimeExports.jsxs("label", { className: `flex items-center cursor-pointer ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : ''}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: widgetConfig['widget-id'], checked: currentValue === null, onChange: handleUnset, disabled: !isEnabled || widgetConfig['widget-readonly'], className: "mr-2 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-700", children: "-" })] })), processedOptions.map((option) => (jsxRuntimeExports.jsxs("label", { className: `flex items-center cursor-pointer ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : ''}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: widgetConfig['widget-id'], value: option.value, checked: currentValue === option.value, onChange: (e) => handleChange(option.value), disabled: !isEnabled || widgetConfig['widget-readonly'], className: "mr-2 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-700", children: translateConfig(option.label) })] }, option.value)))] })) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
8561
8845
|
};
|
|
8562
8846
|
|
|
8563
8847
|
const CheckboxWidget = ({ config }) => {
|
|
8564
|
-
const { value, error, touched, isEnabled, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
8848
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
8565
8849
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8566
8850
|
const hasDataSource = !!widgetConfig['widget-data-source'];
|
|
8567
8851
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
@@ -8576,7 +8860,7 @@ const CheckboxWidget = ({ config }) => {
|
|
|
8576
8860
|
const displayValue = isChecked ? 'Yes' : 'No';
|
|
8577
8861
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] CheckboxDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
|
|
8578
8862
|
}
|
|
8579
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.
|
|
8863
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium leading-normal text-gray-700 md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("label", { className: "inline-flex cursor-pointer items-baseline gap-2", children: [jsxRuntimeExports.jsx("input", { type: "checkbox", checked: isChecked, onChange: (e) => onChange(e.target.checked), onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), jsxRuntimeExports.jsx("span", { className: "text-base leading-normal text-gray-700", children: isChecked ? 'Yes' : 'No' })] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
8580
8864
|
}
|
|
8581
8865
|
// Multiple checkboxes (with data source) - for array values
|
|
8582
8866
|
// Process and sort options if needed
|
|
@@ -8646,7 +8930,7 @@ const CheckboxWidget = ({ config }) => {
|
|
|
8646
8930
|
: '-';
|
|
8647
8931
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-3 CheckboxDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-sm text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
|
|
8648
8932
|
}
|
|
8649
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.
|
|
8933
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium leading-normal text-gray-700 md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("div", { className: layoutConfig.className, style: layoutConfig.style, onBlur: onBlur, children: loading ? (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500", children: translate('common.loading') })) : (processedOptions.map((option) => (jsxRuntimeExports.jsxs("label", { className: `inline-flex cursor-pointer items-baseline gap-2 ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : ''}`, children: [jsxRuntimeExports.jsx("input", { type: "checkbox", value: option.value, checked: selectedValues.includes(option.value), onChange: (e) => handleCheckboxChange(option.value, e.target.checked), disabled: !isEnabled || widgetConfig['widget-readonly'], className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), jsxRuntimeExports.jsx("span", { className: "text-base leading-normal text-gray-700", children: translateConfig(option.label) })] }, option.value)))) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
8650
8934
|
};
|
|
8651
8935
|
|
|
8652
8936
|
const SimpleTableWidget = ({ config }) => {
|
|
@@ -8697,7 +8981,7 @@ const SimpleTableWidget = ({ config }) => {
|
|
|
8697
8981
|
};
|
|
8698
8982
|
|
|
8699
8983
|
const ArrayWidget = ({ config }) => {
|
|
8700
|
-
const { value, error, touched, isEnabled, onChange, config: widgetConfig, } = useBaseWidget({ config });
|
|
8984
|
+
const { value, error, touched, isEnabled, isRequired, onChange, config: widgetConfig, } = useBaseWidget({ config });
|
|
8701
8985
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8702
8986
|
const items = Array.isArray(value) ? value : [];
|
|
8703
8987
|
const itemConfig = widgetConfig['widget-item'];
|
|
@@ -8721,7 +9005,7 @@ const ArrayWidget = ({ config }) => {
|
|
|
8721
9005
|
newItems[index] = newValue;
|
|
8722
9006
|
onChange(newItems);
|
|
8723
9007
|
};
|
|
8724
|
-
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px]", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start mb-2", children: [jsxRuntimeExports.
|
|
9008
|
+
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px]", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start mb-2", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0 flex justify-between items-center", children: [jsxRuntimeExports.jsx("div", { className: "flex-1" }), operations.add && !isReadonly && isEnabled && (jsxRuntimeExports.jsx("button", { type: "button", onClick: addItem, className: "px-3 py-1 text-sm bg-blue-500 text-white hover:bg-blue-600", style: { borderRadius: '15px' }, children: addLabel }))] })] }), items.length === 0 ? (jsxRuntimeExports.jsxs("div", { className: "text-gray-500 text-sm py-4 text-center border border-gray-300 rounded", children: [translate('common.noItems'), ". ", operations.add && !isReadonly && translate('common.clickToAdd', { label: addLabel })] })) : (jsxRuntimeExports.jsx("div", { className: "space-y-2", children: items.map((itemValue, index) => {
|
|
8725
9009
|
({
|
|
8726
9010
|
...itemConfig,
|
|
8727
9011
|
'widget-id': `${widgetConfig['widget-id']}-item-${index}`,
|
|
@@ -8732,7 +9016,7 @@ const ArrayWidget = ({ config }) => {
|
|
|
8732
9016
|
};
|
|
8733
9017
|
|
|
8734
9018
|
const IterableAccordionWidget = ({ config }) => {
|
|
8735
|
-
const { value, error, touched, isEnabled, onChange, config: widgetConfig, } = useBaseWidget({ config });
|
|
9019
|
+
const { value, error, touched, isEnabled, isRequired, onChange, config: widgetConfig, } = useBaseWidget({ config });
|
|
8736
9020
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8737
9021
|
const items = Array.isArray(value) ? value : [];
|
|
8738
9022
|
const itemConfig = widgetConfig['widget-item'];
|
|
@@ -8781,7 +9065,7 @@ const IterableAccordionWidget = ({ config }) => {
|
|
|
8781
9065
|
newItems[index] = newValue;
|
|
8782
9066
|
onChange(newItems);
|
|
8783
9067
|
};
|
|
8784
|
-
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px]", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start mb-2", children: [jsxRuntimeExports.
|
|
9068
|
+
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px]", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start mb-2", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0 flex justify-between items-center", children: [jsxRuntimeExports.jsx("div", { className: "flex-1" }), operations.add && !isReadonly && isEnabled && (jsxRuntimeExports.jsx("button", { type: "button", onClick: addItem, className: "px-3 py-1 text-sm bg-blue-500 text-white hover:bg-blue-600", style: { borderRadius: '15px' }, children: addLabel }))] })] }), items.length === 0 ? (jsxRuntimeExports.jsxs("div", { className: "text-gray-500 text-sm py-4 text-center border border-gray-300 rounded", children: [translate('common.noItems'), ". ", operations.add && !isReadonly && translate('common.clickToAdd', { label: addLabel })] })) : (jsxRuntimeExports.jsx("div", { className: "space-y-2", children: items.map((itemValue, index) => {
|
|
8785
9069
|
const isCollapsed = collapsedItems[index] ?? defaultCollapsed;
|
|
8786
9070
|
const parentPath = widgetConfig['widget-data-path'];
|
|
8787
9071
|
const childPath = itemConfig['widget-data-path'];
|
|
@@ -8820,7 +9104,7 @@ const IterableAccordionWidget = ({ config }) => {
|
|
|
8820
9104
|
};
|
|
8821
9105
|
|
|
8822
9106
|
const PhoneInputWidget = ({ config }) => {
|
|
8823
|
-
const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
9107
|
+
const { value, formattedValue, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
8824
9108
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8825
9109
|
// Use formatted value if available, otherwise raw value
|
|
8826
9110
|
const displayValue = formattedValue !== undefined && formattedValue !== value
|
|
@@ -8831,13 +9115,13 @@ const PhoneInputWidget = ({ config }) => {
|
|
|
8831
9115
|
const label = translateConfig(widgetConfig['widget-label']);
|
|
8832
9116
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] PhoneDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue || ''), children: displayValue || '-' }) })] }));
|
|
8833
9117
|
}
|
|
8834
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
9118
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("input", { type: "tel", value: displayValue, onChange: (e) => onChange(e.target.value), onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: translateConfig(widgetConfig['widget-data-placeholder']), className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${(touched && error.length > 0) || (widgetConfig['widget-required'] && (!value || value === ''))
|
|
8835
9119
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
8836
9120
|
: 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
8837
9121
|
};
|
|
8838
9122
|
|
|
8839
9123
|
const CurrencyInputWidget = ({ config }) => {
|
|
8840
|
-
const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
9124
|
+
const { value, formattedValue, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
8841
9125
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8842
9126
|
// For input, use raw numeric value; formatted value is for display only
|
|
8843
9127
|
const numericValue = typeof value === 'number' ? value : (value ? parseFloat(String(value)) : '');
|
|
@@ -8859,7 +9143,7 @@ const CurrencyInputWidget = ({ config }) => {
|
|
|
8859
9143
|
const display = formattedValue || (value !== null && value !== undefined ? String(value) : '-');
|
|
8860
9144
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] CurrencyDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(display ?? ''), children: display }) })] }));
|
|
8861
9145
|
}
|
|
8862
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
9146
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: translateConfig(widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: "relative", children: [jsxRuntimeExports.jsx("input", { type: "text", inputMode: "decimal", value: numericValue, onChange: handleChange, onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: translateConfig(widgetConfig['widget-data-placeholder']), className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${(touched && error.length > 0) || (widgetConfig['widget-required'] && (value === null || value === undefined || value === ''))
|
|
8863
9147
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
8864
9148
|
: 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }), formattedValue && formattedValue !== String(value) && (jsxRuntimeExports.jsx("span", { className: "absolute right-3 top-2 text-gray-500 text-sm", children: formattedValue }))] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
8865
9149
|
};
|
|
@@ -8979,7 +9263,7 @@ const SelectDisplayValue$1 = ({ config, value }) => {
|
|
|
8979
9263
|
if (value === null || value === undefined || value === '') {
|
|
8980
9264
|
return jsxRuntimeExports.jsx("span", { children: "-" });
|
|
8981
9265
|
}
|
|
8982
|
-
const selectedOption = dataSourceOptions.find((option) => option.value === value);
|
|
9266
|
+
const selectedOption = dataSourceOptions.find((option) => option.value === value || String(option.value) === String(value));
|
|
8983
9267
|
return jsxRuntimeExports.jsx("span", { children: selectedOption ? selectedOption.label : String(value) });
|
|
8984
9268
|
};
|
|
8985
9269
|
const TableCellText = ({ config, value, onValueChange }) => {
|
|
@@ -9702,6 +9986,7 @@ const TableWidget = ({ config }) => {
|
|
|
9702
9986
|
}, children: translate('common.cancel') || 'Cancel' })] }) })] }))] })] }) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-sm mt-1", style: { color: 'var(--owt-widget-error-color, #B91C1C)' }, children: error[0] }))] })] }));
|
|
9703
9987
|
};
|
|
9704
9988
|
|
|
9989
|
+
const isUnsetRowValue = (value) => value === null || value === undefined || value === '';
|
|
9705
9990
|
// Display select value label in view mode
|
|
9706
9991
|
const SelectDisplayValue = ({ config, value }) => {
|
|
9707
9992
|
const { dataSourceOptions, loading } = useBaseWidget({ config });
|
|
@@ -9709,7 +9994,7 @@ const SelectDisplayValue = ({ config, value }) => {
|
|
|
9709
9994
|
return jsxRuntimeExports.jsx("span", { children: "-" });
|
|
9710
9995
|
if (value === null || value === undefined || value === '')
|
|
9711
9996
|
return jsxRuntimeExports.jsx("span", { children: "-" });
|
|
9712
|
-
const selectedOption = dataSourceOptions.find((option) => option.value === value);
|
|
9997
|
+
const selectedOption = dataSourceOptions.find((option) => option.value === value || String(option.value) === String(value));
|
|
9713
9998
|
return jsxRuntimeExports.jsx("span", { children: selectedOption ? selectedOption.label : String(value) });
|
|
9714
9999
|
};
|
|
9715
10000
|
/**
|
|
@@ -9734,7 +10019,6 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9734
10019
|
const { value, error, touched, isEnabled, onChange, config: widgetConfig } = useBaseWidget({ config });
|
|
9735
10020
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
9736
10021
|
const dispatch = reactRedux.useDispatch();
|
|
9737
|
-
const storeValues = reactRedux.useSelector((state) => state.widget?.values ?? {});
|
|
9738
10022
|
const rows = Array.isArray(value) ? value : [];
|
|
9739
10023
|
const columns = widgetConfig['widget-data-columns'] || [];
|
|
9740
10024
|
const operations = widgetConfig['widget-data-operations'] || {};
|
|
@@ -9766,7 +10050,12 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9766
10050
|
const emptyRow = {};
|
|
9767
10051
|
columns.forEach((col) => {
|
|
9768
10052
|
const key = col['column-key'];
|
|
9769
|
-
|
|
10053
|
+
if (col['widget-data-default'] !== undefined) {
|
|
10054
|
+
emptyRow[key] = col['widget-data-default'];
|
|
10055
|
+
}
|
|
10056
|
+
else if (col.widget === 'checkbox') {
|
|
10057
|
+
emptyRow[key] = false;
|
|
10058
|
+
}
|
|
9770
10059
|
});
|
|
9771
10060
|
return emptyRow;
|
|
9772
10061
|
}, [columns]);
|
|
@@ -9819,17 +10108,48 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9819
10108
|
const updateField = React.useCallback((columnKey, newValue) => {
|
|
9820
10109
|
setFormData((prev) => ({ ...prev, [columnKey]: newValue }));
|
|
9821
10110
|
}, []);
|
|
9822
|
-
const
|
|
9823
|
-
|
|
10111
|
+
const membersWidgetId = widgetConfig['widget-id'];
|
|
10112
|
+
const dialogStoreValues = reactRedux.useSelector((state) => {
|
|
10113
|
+
if (dialogSessionId <= 0) {
|
|
10114
|
+
return {};
|
|
10115
|
+
}
|
|
10116
|
+
const values = state.widget?.values ?? {};
|
|
10117
|
+
const row = {};
|
|
10118
|
+
columns.forEach((col) => {
|
|
10119
|
+
const k = col['column-key'];
|
|
10120
|
+
const wid = `${membersWidgetId}-dlg-${dialogSessionId}-${k}`;
|
|
10121
|
+
if (values[wid] !== undefined) {
|
|
10122
|
+
row[k] = values[wid];
|
|
10123
|
+
}
|
|
10124
|
+
});
|
|
10125
|
+
return row;
|
|
10126
|
+
}, (a, b) => JSON.stringify(a) === JSON.stringify(b));
|
|
10127
|
+
const buildDialogRowValues = React.useCallback((storeSlice) => {
|
|
10128
|
+
const row = { ...formData };
|
|
9824
10129
|
columns.forEach((col) => {
|
|
9825
10130
|
const k = col['column-key'];
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
|
|
10131
|
+
if (storeSlice[k] !== undefined) {
|
|
10132
|
+
row[k] = storeSlice[k];
|
|
10133
|
+
}
|
|
10134
|
+
});
|
|
10135
|
+
return row;
|
|
10136
|
+
}, [formData, columns]);
|
|
10137
|
+
const dialogRowValues = React.useMemo(() => buildDialogRowValues(dialogStoreValues), [buildDialogRowValues, dialogStoreValues]);
|
|
10138
|
+
const collectMergedRowPayload = React.useCallback(() => buildDialogRowValues(dialogStoreValues), [buildDialogRowValues, dialogStoreValues]);
|
|
10139
|
+
const finalizeDialogRowPayload = React.useCallback((raw) => {
|
|
10140
|
+
const result = {};
|
|
10141
|
+
columns.forEach((col) => {
|
|
10142
|
+
const key = col['column-key'];
|
|
10143
|
+
if (!shouldShowWidget(col['widget-data-options'], raw)) {
|
|
10144
|
+
return;
|
|
10145
|
+
}
|
|
10146
|
+
const val = raw[key];
|
|
10147
|
+
if (!isUnsetRowValue(val)) {
|
|
10148
|
+
result[key] = val;
|
|
10149
|
+
}
|
|
9830
10150
|
});
|
|
9831
|
-
return
|
|
9832
|
-
}, [
|
|
10151
|
+
return result;
|
|
10152
|
+
}, [columns]);
|
|
9833
10153
|
const saveDialog = React.useCallback(() => {
|
|
9834
10154
|
const payload = collectMergedRowPayload();
|
|
9835
10155
|
let hasErrors = false;
|
|
@@ -9839,8 +10159,11 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9839
10159
|
const isColReadonly = isReadonly || col['widget-readonly'] === true;
|
|
9840
10160
|
if (isColReadonly)
|
|
9841
10161
|
return;
|
|
10162
|
+
if (!shouldShowWidget(col['widget-data-options'], payload))
|
|
10163
|
+
return;
|
|
9842
10164
|
const cellValue = payload[key];
|
|
9843
|
-
const
|
|
10165
|
+
const isRequired = shouldRequireWidget(col['widget-data-options'], payload, col['widget-required']);
|
|
10166
|
+
const validationErrors = validateWidget(cellValue, col['widget-data-validation'], isRequired);
|
|
9844
10167
|
if (validationErrors && validationErrors.length > 0) {
|
|
9845
10168
|
hasErrors = true;
|
|
9846
10169
|
dispatch(setError({ widgetId: cellWidgetId, errors: validationErrors }));
|
|
@@ -9853,8 +10176,9 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9853
10176
|
if (hasErrors) {
|
|
9854
10177
|
return;
|
|
9855
10178
|
}
|
|
10179
|
+
const cleaned = finalizeDialogRowPayload(payload);
|
|
9856
10180
|
if (dialogMode === 'add') {
|
|
9857
|
-
const savedRow = { ...
|
|
10181
|
+
const savedRow = { ...cleaned, edit_action: 'ADD' };
|
|
9858
10182
|
onChange([...rows, savedRow]);
|
|
9859
10183
|
closeDialog();
|
|
9860
10184
|
return;
|
|
@@ -9864,11 +10188,18 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9864
10188
|
const currentRow = newRows[activeRowIndex] || {};
|
|
9865
10189
|
const wasDeleted = currentRow.edit_action === 'DELETE';
|
|
9866
10190
|
const editAction = wasDeleted ? 'UPDATE' : (currentRow.edit_action ?? 'UPDATE');
|
|
9867
|
-
|
|
10191
|
+
const merged = { ...currentRow, ...cleaned, edit_action: editAction };
|
|
10192
|
+
columns.forEach((col) => {
|
|
10193
|
+
const key = col['column-key'];
|
|
10194
|
+
if (!(key in cleaned)) {
|
|
10195
|
+
delete merged[key];
|
|
10196
|
+
}
|
|
10197
|
+
});
|
|
10198
|
+
newRows[activeRowIndex] = merged;
|
|
9868
10199
|
onChange(newRows);
|
|
9869
10200
|
closeDialog();
|
|
9870
10201
|
}
|
|
9871
|
-
}, [collectMergedRowPayload, dialogMode, onChange, rows, closeDialog, activeRowIndex, columns, dialogFieldWidgetId, isReadonly, dispatch]);
|
|
10202
|
+
}, [collectMergedRowPayload, finalizeDialogRowPayload, dialogMode, onChange, rows, closeDialog, activeRowIndex, columns, dialogFieldWidgetId, isReadonly, dispatch]);
|
|
9872
10203
|
const deleteRow = React.useCallback((rowIndex) => {
|
|
9873
10204
|
const newRows = rows.filter((_, i) => i !== rowIndex);
|
|
9874
10205
|
onChange(newRows);
|
|
@@ -9955,9 +10286,12 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9955
10286
|
lineHeight: 1,
|
|
9956
10287
|
}, "aria-label": "Close", children: "\u00D7" })] }), jsxRuntimeExports.jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", style: { maxHeight: '70vh', overflow: 'auto' }, children: columns.map((col) => {
|
|
9957
10288
|
const key = col['column-key'];
|
|
10289
|
+
if (!shouldShowWidget(col['widget-data-options'], dialogRowValues)) {
|
|
10290
|
+
return null;
|
|
10291
|
+
}
|
|
9958
10292
|
const widgetType = col.widget || 'text';
|
|
9959
10293
|
const cellWidgetId = dialogFieldWidgetId(key);
|
|
9960
|
-
const initialValue = formData[key] ?? col['widget-data-default']
|
|
10294
|
+
const initialValue = formData[key] ?? col['widget-data-default'];
|
|
9961
10295
|
const fieldConfig = {
|
|
9962
10296
|
...col,
|
|
9963
10297
|
widget: widgetType,
|
|
@@ -9967,6 +10301,8 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9967
10301
|
'widget-readonly': isReadonly || col['widget-readonly'] === true,
|
|
9968
10302
|
'widget-data-path': undefined,
|
|
9969
10303
|
'widget-data-default': initialValue,
|
|
10304
|
+
'widget-data-options': undefined,
|
|
10305
|
+
'widget-required': shouldRequireWidget(col['widget-data-options'], dialogRowValues, col['widget-required']),
|
|
9970
10306
|
};
|
|
9971
10307
|
return (jsxRuntimeExports.jsx("div", { className: "min-w-0", children: jsxRuntimeExports.jsx(WidgetRenderer, { config: fieldConfig, schemaData: { [cellWidgetId]: initialValue }, onValueChange: (_widgetId, newValue) => updateField(key, newValue) }) }, `${dialogSessionId}-${key}`));
|
|
9972
10308
|
}) }, `dialog-fields-${dialogSessionId}`), jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-3 mt-6", children: [jsxRuntimeExports.jsx("button", { type: "button", onClick: closeDialog, className: "px-4 py-2 text-sm font-medium", style: {
|
|
@@ -10164,7 +10500,7 @@ const ProfileWidget = ({ config }) => {
|
|
|
10164
10500
|
const TextAreaWidget = ({ config }) => {
|
|
10165
10501
|
// Check readonly early from original config
|
|
10166
10502
|
const isReadonly = config['widget-readonly'] || false;
|
|
10167
|
-
const { value, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
10503
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
10168
10504
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
10169
10505
|
const formatConfig = widgetConfig['widget-data-format'] || {};
|
|
10170
10506
|
const validationConfig = widgetConfig['widget-data-validation'] || {};
|
|
@@ -10212,7 +10548,6 @@ const TextAreaWidget = ({ config }) => {
|
|
|
10212
10548
|
? translateConfig(widgetConfig['widget-label'])
|
|
10213
10549
|
: '';
|
|
10214
10550
|
// Check if required
|
|
10215
|
-
const isRequired = widgetConfig['widget-required'] || false;
|
|
10216
10551
|
// Error display
|
|
10217
10552
|
const hasError = touched && error && error.length > 0;
|
|
10218
10553
|
const errorMessage = hasError ? error[0] : '';
|
|
@@ -10230,7 +10565,7 @@ const TextAreaWidget = ({ config }) => {
|
|
|
10230
10565
|
border: 'none',
|
|
10231
10566
|
}, children: displayValue }) })] }));
|
|
10232
10567
|
}
|
|
10233
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
10568
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: label, required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { style: { position: 'relative' }, children: [jsxRuntimeExports.jsx("textarea", { id: widgetConfig['widget-id'], rows: rows, value: getStringValue(), onChange: handleChange, onBlur: onBlur, disabled: !isEnabled, placeholder: placeholder, className: `w-full px-3 py-2 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${hasError
|
|
10234
10569
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
10235
10570
|
: 'border-gray-300'} ${!isEnabled ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: {
|
|
10236
10571
|
borderRadius: '10px',
|
|
@@ -11708,7 +12043,7 @@ const ResultsTable = ({ rows, selectedRowKey, onRowClick, onRowDoubleClick, }) =
|
|
|
11708
12043
|
}) })] }) }));
|
|
11709
12044
|
};
|
|
11710
12045
|
const RegisterLookupWidget = ({ config }) => {
|
|
11711
|
-
const { value, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
12046
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
11712
12047
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
11713
12048
|
const { dataSourceRequestHandler } = useWidgetContext();
|
|
11714
12049
|
const dataSource = widgetConfig['widget-data-source'];
|
|
@@ -11871,7 +12206,7 @@ const RegisterLookupWidget = ({ config }) => {
|
|
|
11871
12206
|
onChange(null);
|
|
11872
12207
|
setAppliedRecord(null);
|
|
11873
12208
|
setPendingRow(null);
|
|
11874
|
-
}, className: "text-sm underline text-red-500 p-0 border-0 bg-transparent cursor-pointer focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-1 rounded", children: translate('common.remove', { defaultValue: 'Remove' }) })] })), !isReadonly && touched && error.length > 0 && jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] })] })) : !isReadonly ? (jsxRuntimeExports.jsxs("div", { className: "w-full min-w-0", children: [jsxRuntimeExports.jsxs("button", { type: "button", disabled: !isEnabled, onClick: openLookup, title: actionLabel, className: `flex items-center gap-2 w-full sm:w-[180px] max-w-full px-3 h-[30px] text-sm border rounded-[10px] shadow-sm transition-colors ${hasError ? 'border-red-500 text-gray-700' : 'border-gray-300 text-gray-700'} ${!isEnabled ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-white cursor-pointer'}`, children: [jsxRuntimeExports.jsx("img", { src: img$g, alt: "", className: "w-4 h-4 opacity-50 flex-shrink-0" }), jsxRuntimeExports.jsx("span", { className: "truncate", children: actionLabel }),
|
|
12209
|
+
}, className: "text-sm underline text-red-500 p-0 border-0 bg-transparent cursor-pointer focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-1 rounded", children: translate('common.remove', { defaultValue: 'Remove' }) })] })), !isReadonly && touched && error.length > 0 && jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] })] })) : !isReadonly ? (jsxRuntimeExports.jsxs("div", { className: "w-full min-w-0", children: [jsxRuntimeExports.jsxs("button", { type: "button", disabled: !isEnabled, onClick: openLookup, title: actionLabel, className: `flex items-center gap-2 w-full sm:w-[180px] max-w-full px-3 h-[30px] text-sm border rounded-[10px] shadow-sm transition-colors ${hasError ? 'border-red-500 text-gray-700' : 'border-gray-300 text-gray-700'} ${!isEnabled ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-white cursor-pointer'}`, children: [jsxRuntimeExports.jsx("img", { src: img$g, alt: "", className: "w-4 h-4 opacity-50 flex-shrink-0" }), jsxRuntimeExports.jsx("span", { className: "min-w-0 truncate", children: actionLabel }), isRequired && jsxRuntimeExports.jsx("span", { className: "shrink-0 text-red-500", children: "*" })] }), touched && error.length > 0 && jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] })] })) : null, !isReadonly && isOpen && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-50", style: { backgroundColor: 'rgba(0,0,0,0.5)' }, onClick: () => { setIsOpen(false); onBlur(); } }), jsxRuntimeExports.jsxs("div", { className: "flex flex-col overflow-hidden", style: {
|
|
11875
12210
|
position: 'fixed',
|
|
11876
12211
|
top: modalPos.y,
|
|
11877
12212
|
left: modalPos.x,
|
|
@@ -11900,6 +12235,288 @@ const RegisterLookupWidget = ({ config }) => {
|
|
|
11900
12235
|
: translate('common.searchHint', { defaultValue: 'Type and press Enter or click search' }) })) : (jsxRuntimeExports.jsx(ResultsTable, { rows: searchResults, selectedRowKey: pendingRow?.internal_record_id ?? null, onRowClick: setPendingRow, onRowDoubleClick: applySelection })) }), jsxRuntimeExports.jsxs("div", { className: `flex-shrink-0 flex flex-wrap items-center gap-3 px-5 py-3 border-t border-gray-200 ${totalCount !== null ? 'justify-between' : 'justify-end'}`, children: [totalCount !== null && (jsxRuntimeExports.jsx(PaginationFooter, { embedded: true, currentPage: currentPage, totalPages: totalPages, totalCount: totalCount, pageSize: pageSize, onPageChange: (page) => runSearch(searchText, page), onPrev: () => currentPage > 1 && runSearch(searchText, currentPage - 1), onNext: () => currentPage < totalPages && runSearch(searchText, currentPage + 1), translate: translate })), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => pendingRow && applySelection(pendingRow), disabled: !pendingRow, className: "px-4 h-9 text-sm font-medium rounded-[10px] text-white disabled:opacity-40 disabled:cursor-not-allowed flex-shrink-0", style: { backgroundColor: 'var(--owt-color-info, #2563eb)' }, children: selectRecordLabel })] })] })] }))] }));
|
|
11901
12236
|
};
|
|
11902
12237
|
|
|
12238
|
+
const MultiSelectWidget = ({ config }) => {
|
|
12239
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
12240
|
+
const { translate, translateConfig } = useWidgetTranslation();
|
|
12241
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
12242
|
+
const [isListPopupOpen, setIsListPopupOpen] = React.useState(false);
|
|
12243
|
+
const [searchQuery, setSearchQuery] = React.useState('');
|
|
12244
|
+
const [dropdownPosition, setDropdownPosition] = React.useState(null);
|
|
12245
|
+
const [listPopupPosition, setListPopupPosition] = React.useState(null);
|
|
12246
|
+
const [mounted, setMounted] = React.useState(false);
|
|
12247
|
+
const containerRef = React.useRef(null);
|
|
12248
|
+
const triggerRef = React.useRef(null);
|
|
12249
|
+
const dropdownRef = React.useRef(null);
|
|
12250
|
+
const listPopupRef = React.useRef(null);
|
|
12251
|
+
const moreButtonRef = React.useRef(null);
|
|
12252
|
+
const searchInputRef = React.useRef(null);
|
|
12253
|
+
const formatConfig = widgetConfig['widget-data-format'];
|
|
12254
|
+
const sortOptions = formatConfig?.sortOptions ?? false;
|
|
12255
|
+
React.useEffect(() => {
|
|
12256
|
+
setMounted(true);
|
|
12257
|
+
}, []);
|
|
12258
|
+
const updateDropdownPosition = React.useCallback(() => {
|
|
12259
|
+
const trigger = triggerRef.current;
|
|
12260
|
+
if (!trigger)
|
|
12261
|
+
return;
|
|
12262
|
+
const rect = trigger.getBoundingClientRect();
|
|
12263
|
+
const gap = 4;
|
|
12264
|
+
const spaceBelow = window.innerHeight - rect.bottom - gap;
|
|
12265
|
+
const spaceAbove = rect.top - gap;
|
|
12266
|
+
const openDown = spaceBelow >= 160 || spaceBelow >= spaceAbove;
|
|
12267
|
+
const availableSpace = openDown ? spaceBelow : spaceAbove;
|
|
12268
|
+
const maxHeight = Math.min(320, Math.max(160, availableSpace - 8));
|
|
12269
|
+
setDropdownPosition(openDown
|
|
12270
|
+
? {
|
|
12271
|
+
top: rect.bottom + gap,
|
|
12272
|
+
left: rect.left,
|
|
12273
|
+
width: rect.width,
|
|
12274
|
+
maxHeight,
|
|
12275
|
+
placement: 'bottom',
|
|
12276
|
+
}
|
|
12277
|
+
: {
|
|
12278
|
+
bottom: window.innerHeight - rect.top + gap,
|
|
12279
|
+
left: rect.left,
|
|
12280
|
+
width: rect.width,
|
|
12281
|
+
maxHeight,
|
|
12282
|
+
placement: 'top',
|
|
12283
|
+
});
|
|
12284
|
+
}, []);
|
|
12285
|
+
const updateListPopupPosition = React.useCallback(() => {
|
|
12286
|
+
const anchor = moreButtonRef.current;
|
|
12287
|
+
if (!anchor)
|
|
12288
|
+
return;
|
|
12289
|
+
const rect = anchor.getBoundingClientRect();
|
|
12290
|
+
const gap = 4;
|
|
12291
|
+
const spaceBelow = window.innerHeight - rect.bottom - gap;
|
|
12292
|
+
const spaceAbove = rect.top - gap;
|
|
12293
|
+
const openDown = spaceBelow >= 120 || spaceBelow >= spaceAbove;
|
|
12294
|
+
const availableSpace = openDown ? spaceBelow : spaceAbove;
|
|
12295
|
+
const maxHeight = Math.min(280, Math.max(120, availableSpace - 8));
|
|
12296
|
+
setListPopupPosition(openDown
|
|
12297
|
+
? {
|
|
12298
|
+
top: rect.bottom + gap,
|
|
12299
|
+
left: rect.left,
|
|
12300
|
+
width: Math.max(rect.width, 220),
|
|
12301
|
+
maxHeight,
|
|
12302
|
+
placement: 'bottom',
|
|
12303
|
+
}
|
|
12304
|
+
: {
|
|
12305
|
+
bottom: window.innerHeight - rect.top + gap,
|
|
12306
|
+
left: rect.left,
|
|
12307
|
+
width: Math.max(rect.width, 220),
|
|
12308
|
+
maxHeight,
|
|
12309
|
+
placement: 'top',
|
|
12310
|
+
});
|
|
12311
|
+
}, []);
|
|
12312
|
+
React.useEffect(() => {
|
|
12313
|
+
if (!isOpen) {
|
|
12314
|
+
setDropdownPosition(null);
|
|
12315
|
+
setSearchQuery('');
|
|
12316
|
+
return;
|
|
12317
|
+
}
|
|
12318
|
+
updateDropdownPosition();
|
|
12319
|
+
const handleResize = () => updateDropdownPosition();
|
|
12320
|
+
window.addEventListener('resize', handleResize);
|
|
12321
|
+
return () => {
|
|
12322
|
+
window.removeEventListener('resize', handleResize);
|
|
12323
|
+
};
|
|
12324
|
+
}, [isOpen, updateDropdownPosition]);
|
|
12325
|
+
React.useEffect(() => {
|
|
12326
|
+
if (!isListPopupOpen) {
|
|
12327
|
+
setListPopupPosition(null);
|
|
12328
|
+
return;
|
|
12329
|
+
}
|
|
12330
|
+
updateListPopupPosition();
|
|
12331
|
+
const handleResize = () => updateListPopupPosition();
|
|
12332
|
+
window.addEventListener('resize', handleResize);
|
|
12333
|
+
return () => {
|
|
12334
|
+
window.removeEventListener('resize', handleResize);
|
|
12335
|
+
};
|
|
12336
|
+
}, [isListPopupOpen, updateListPopupPosition]);
|
|
12337
|
+
React.useEffect(() => {
|
|
12338
|
+
if (!isOpen && !isListPopupOpen)
|
|
12339
|
+
return;
|
|
12340
|
+
const handleScroll = (event) => {
|
|
12341
|
+
const target = event.target;
|
|
12342
|
+
if (dropdownRef.current?.contains(target))
|
|
12343
|
+
return;
|
|
12344
|
+
if (listPopupRef.current?.contains(target))
|
|
12345
|
+
return;
|
|
12346
|
+
if (isOpen)
|
|
12347
|
+
setIsOpen(false);
|
|
12348
|
+
if (isListPopupOpen)
|
|
12349
|
+
setIsListPopupOpen(false);
|
|
12350
|
+
};
|
|
12351
|
+
window.addEventListener('scroll', handleScroll, true);
|
|
12352
|
+
return () => window.removeEventListener('scroll', handleScroll, true);
|
|
12353
|
+
}, [isOpen, isListPopupOpen]);
|
|
12354
|
+
React.useEffect(() => {
|
|
12355
|
+
if (!isOpen && !isListPopupOpen)
|
|
12356
|
+
return;
|
|
12357
|
+
const handleClickOutside = (event) => {
|
|
12358
|
+
const target = event.target;
|
|
12359
|
+
if (isOpen) {
|
|
12360
|
+
if (containerRef.current?.contains(target))
|
|
12361
|
+
return;
|
|
12362
|
+
if (dropdownRef.current?.contains(target))
|
|
12363
|
+
return;
|
|
12364
|
+
setIsOpen(false);
|
|
12365
|
+
}
|
|
12366
|
+
if (isListPopupOpen) {
|
|
12367
|
+
if (listPopupRef.current?.contains(target))
|
|
12368
|
+
return;
|
|
12369
|
+
if (moreButtonRef.current?.contains(target))
|
|
12370
|
+
return;
|
|
12371
|
+
setIsListPopupOpen(false);
|
|
12372
|
+
}
|
|
12373
|
+
};
|
|
12374
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
12375
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
12376
|
+
}, [isOpen, isListPopupOpen]);
|
|
12377
|
+
React.useEffect(() => {
|
|
12378
|
+
if (isOpen && searchInputRef.current) {
|
|
12379
|
+
searchInputRef.current.focus();
|
|
12380
|
+
}
|
|
12381
|
+
}, [isOpen]);
|
|
12382
|
+
const processedOptions = React.useMemo(() => {
|
|
12383
|
+
let options = dataSourceOptions.map((opt) => {
|
|
12384
|
+
const rawLabel = String(opt.label ?? opt.value ?? '');
|
|
12385
|
+
return {
|
|
12386
|
+
value: opt.value,
|
|
12387
|
+
label: translateConfig(rawLabel),
|
|
12388
|
+
rawLabel,
|
|
12389
|
+
};
|
|
12390
|
+
});
|
|
12391
|
+
if (sortOptions) {
|
|
12392
|
+
options.sort((a, b) => a.label.localeCompare(b.label));
|
|
12393
|
+
}
|
|
12394
|
+
return options;
|
|
12395
|
+
}, [dataSourceOptions, sortOptions, translateConfig]);
|
|
12396
|
+
const filteredOptions = React.useMemo(() => {
|
|
12397
|
+
if (!searchQuery.trim())
|
|
12398
|
+
return processedOptions;
|
|
12399
|
+
const q = searchQuery.trim().toLowerCase();
|
|
12400
|
+
return processedOptions.filter((opt) => opt.label.toLowerCase().includes(q) ||
|
|
12401
|
+
opt.rawLabel.toLowerCase().includes(q));
|
|
12402
|
+
}, [processedOptions, searchQuery]);
|
|
12403
|
+
const selectedValues = React.useMemo(() => {
|
|
12404
|
+
if (value === null || value === undefined)
|
|
12405
|
+
return [];
|
|
12406
|
+
if (Array.isArray(value))
|
|
12407
|
+
return value;
|
|
12408
|
+
return [value];
|
|
12409
|
+
}, [value]);
|
|
12410
|
+
const allFilteredSelected = React.useMemo(() => {
|
|
12411
|
+
if (filteredOptions.length === 0)
|
|
12412
|
+
return false;
|
|
12413
|
+
return filteredOptions.every((opt) => selectedValues.includes(opt.value));
|
|
12414
|
+
}, [filteredOptions, selectedValues]);
|
|
12415
|
+
const handleToggle = React.useCallback((optionValue, checked) => {
|
|
12416
|
+
if (checked) {
|
|
12417
|
+
onChange([...selectedValues, optionValue]);
|
|
12418
|
+
}
|
|
12419
|
+
else {
|
|
12420
|
+
onChange(selectedValues.filter((v) => v !== optionValue));
|
|
12421
|
+
}
|
|
12422
|
+
}, [selectedValues, onChange]);
|
|
12423
|
+
const handleSelectAll = React.useCallback(() => {
|
|
12424
|
+
const filteredVals = filteredOptions.map((o) => o.value);
|
|
12425
|
+
const merged = Array.from(new Set([...selectedValues, ...filteredVals]));
|
|
12426
|
+
onChange(merged);
|
|
12427
|
+
}, [filteredOptions, selectedValues, onChange]);
|
|
12428
|
+
const handleClearAll = React.useCallback(() => {
|
|
12429
|
+
onChange([]);
|
|
12430
|
+
}, [onChange]);
|
|
12431
|
+
const selectedLabels = React.useMemo(() => {
|
|
12432
|
+
return selectedValues.map((val) => {
|
|
12433
|
+
const opt = processedOptions.find((o) => o.value === val);
|
|
12434
|
+
return opt ? opt.label : translateConfig(String(val));
|
|
12435
|
+
});
|
|
12436
|
+
}, [selectedValues, processedOptions, translateConfig]);
|
|
12437
|
+
const fullSelectionText = selectedLabels.join(', ');
|
|
12438
|
+
const visibleLabels = selectedLabels.slice(0, 5);
|
|
12439
|
+
const overflowCount = Math.max(0, selectedLabels.length - 10);
|
|
12440
|
+
const disabled = !isEnabled || loading || widgetConfig['widget-readonly'];
|
|
12441
|
+
const renderSelectedLabels = (options) => {
|
|
12442
|
+
if (selectedLabels.length === 0)
|
|
12443
|
+
return null;
|
|
12444
|
+
const readonly = options?.readonly ?? false;
|
|
12445
|
+
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', {
|
|
12446
|
+
label,
|
|
12447
|
+
defaultValue: `Remove ${label}`,
|
|
12448
|
+
}), 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', {
|
|
12449
|
+
count: overflowCount,
|
|
12450
|
+
defaultValue: `+${overflowCount} more`,
|
|
12451
|
+
}) }))] }));
|
|
12452
|
+
};
|
|
12453
|
+
const listPopupPanel = isListPopupOpen && listPopupPosition && mounted ? (jsxRuntimeExports.jsxs("div", { ref: listPopupRef, className: "fixed z-[201] bg-white border border-gray-300 shadow-lg", style: {
|
|
12454
|
+
...(listPopupPosition.placement === 'bottom'
|
|
12455
|
+
? { top: listPopupPosition.top }
|
|
12456
|
+
: { bottom: listPopupPosition.bottom }),
|
|
12457
|
+
left: listPopupPosition.left,
|
|
12458
|
+
width: listPopupPosition.width,
|
|
12459
|
+
maxWidth: '320px',
|
|
12460
|
+
maxHeight: listPopupPosition.maxHeight,
|
|
12461
|
+
borderRadius: '10px',
|
|
12462
|
+
display: 'flex',
|
|
12463
|
+
flexDirection: 'column',
|
|
12464
|
+
}, 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', {
|
|
12465
|
+
count: selectedLabels.length,
|
|
12466
|
+
defaultValue: `All selected (${selectedLabels.length})`,
|
|
12467
|
+
}) }), 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;
|
|
12468
|
+
if (widgetConfig['widget-readonly']) {
|
|
12469
|
+
const fieldLabel = widgetConfig['widget-label'];
|
|
12470
|
+
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'
|
|
12471
|
+
? reactDom.createPortal(listPopupPanel, document.body)
|
|
12472
|
+
: null] })] }));
|
|
12473
|
+
}
|
|
12474
|
+
const optionsMaxHeight = dropdownPosition
|
|
12475
|
+
? Math.min(280, dropdownPosition.maxHeight - 100)
|
|
12476
|
+
: 280;
|
|
12477
|
+
const dropdownPanel = isOpen && dropdownPosition && mounted ? (jsxRuntimeExports.jsxs("div", { ref: dropdownRef, className: "fixed z-[200] bg-white border border-gray-300 shadow-lg", style: {
|
|
12478
|
+
...(dropdownPosition.placement === 'bottom'
|
|
12479
|
+
? { top: dropdownPosition.top }
|
|
12480
|
+
: { bottom: dropdownPosition.bottom }),
|
|
12481
|
+
left: dropdownPosition.left,
|
|
12482
|
+
width: dropdownPosition.width,
|
|
12483
|
+
maxWidth: '280px',
|
|
12484
|
+
maxHeight: dropdownPosition.maxHeight,
|
|
12485
|
+
borderRadius: '10px',
|
|
12486
|
+
display: 'flex',
|
|
12487
|
+
flexDirection: 'column',
|
|
12488
|
+
}, 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 ? () => {
|
|
12489
|
+
const filteredVals = new Set(filteredOptions.map((o) => o.value));
|
|
12490
|
+
onChange(selectedValues.filter((v) => !filteredVals.has(v)));
|
|
12491
|
+
} : handleSelectAll, className: "text-xs font-medium text-blue-600 hover:text-blue-800 focus:outline-none", children: allFilteredSelected
|
|
12492
|
+
? translate('common.deselectAll', { defaultValue: 'Deselect All' })
|
|
12493
|
+
: 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) => {
|
|
12494
|
+
const isChecked = selectedValues.includes(option.value);
|
|
12495
|
+
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));
|
|
12496
|
+
})) })] })) : null;
|
|
12497
|
+
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: () => {
|
|
12498
|
+
if (!disabled)
|
|
12499
|
+
setIsOpen((prev) => !prev);
|
|
12500
|
+
}, onBlur: () => {
|
|
12501
|
+
if (!isOpen)
|
|
12502
|
+
onBlur();
|
|
12503
|
+
}, 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) ||
|
|
12504
|
+
(widgetConfig['widget-required'] && selectedValues.length === 0)
|
|
12505
|
+
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
12506
|
+
: '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
|
|
12507
|
+
? fullSelectionText
|
|
12508
|
+
: 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
|
|
12509
|
+
? translate('common.select', { defaultValue: 'Select...' })
|
|
12510
|
+
: translate('common.selectedCount', {
|
|
12511
|
+
count: selectedLabels.length,
|
|
12512
|
+
defaultValue: `${selectedLabels.length} selected`,
|
|
12513
|
+
}) }), 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'
|
|
12514
|
+
? reactDom.createPortal(dropdownPanel, document.body)
|
|
12515
|
+
: null, selectedLabels.length > 0 && renderSelectedLabels(), mounted && listPopupPanel && typeof document !== 'undefined'
|
|
12516
|
+
? reactDom.createPortal(listPopupPanel, document.body)
|
|
12517
|
+
: 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') }))] })] }) }));
|
|
12518
|
+
};
|
|
12519
|
+
|
|
11903
12520
|
/**
|
|
11904
12521
|
* Register all default/generic widgets
|
|
11905
12522
|
* This is called automatically when the package is imported
|
|
@@ -11949,6 +12566,8 @@ const registerDefaultWidgets = () => {
|
|
|
11949
12566
|
widgetRegistry.register({ widget: 'id-authentication', component: IdAuthenticationWidget });
|
|
11950
12567
|
// Register lookup widget — searchable popup to select a record from any register
|
|
11951
12568
|
widgetRegistry.register({ widget: 'register-lookup', component: RegisterLookupWidget });
|
|
12569
|
+
// Multi-select widget — searchable dropdown with checkbox-style options, select all, and clear all
|
|
12570
|
+
widgetRegistry.register({ widget: 'multi-select', component: MultiSelectWidget });
|
|
11952
12571
|
};
|
|
11953
12572
|
// Auto-register on import
|
|
11954
12573
|
registerDefaultWidgets();
|
|
@@ -12012,6 +12631,14 @@ var enTranslations = {
|
|
|
12012
12631
|
"common.sectionModified": "Modified and not saved",
|
|
12013
12632
|
"common.supportedDocuments": "Supported Documents",
|
|
12014
12633
|
"common.searchPlaceholder": "Search...",
|
|
12634
|
+
"common.selectAll": "Select All",
|
|
12635
|
+
"common.deselectAll": "Deselect All",
|
|
12636
|
+
"common.clearAll": "Clear All",
|
|
12637
|
+
"common.noOptionsFound": "No options found",
|
|
12638
|
+
"common.allSelected": "All selected ({{count}})",
|
|
12639
|
+
"common.moreSelected": "+{{count}} more",
|
|
12640
|
+
"common.selectedCount": "{{count}} selected",
|
|
12641
|
+
"common.removeItem": "Remove {{label}}",
|
|
12015
12642
|
"common.selectAction": "Select {{label}}",
|
|
12016
12643
|
"common.selectTitle": "Select {{label}}",
|
|
12017
12644
|
"common.change": "Change",
|
|
@@ -12264,13 +12891,10 @@ const translateWidgetConfig = (widgetConfig, translate) => {
|
|
|
12264
12891
|
...dataSource,
|
|
12265
12892
|
options: dataSource.options.map((option) => {
|
|
12266
12893
|
if (option.label && typeof option.label === 'string') {
|
|
12267
|
-
|
|
12268
|
-
|
|
12269
|
-
|
|
12270
|
-
|
|
12271
|
-
label: translate(optionLabel, { defaultValue: optionLabel }),
|
|
12272
|
-
};
|
|
12273
|
-
}
|
|
12894
|
+
return {
|
|
12895
|
+
...option,
|
|
12896
|
+
label: translate(option.label, { defaultValue: option.label }),
|
|
12897
|
+
};
|
|
12274
12898
|
}
|
|
12275
12899
|
return option;
|
|
12276
12900
|
}),
|
|
@@ -12338,6 +12962,7 @@ exports.HeaderSectionWidget = HeaderSectionWidget;
|
|
|
12338
12962
|
exports.IdAuthenticationWidget = IdAuthenticationWidget;
|
|
12339
12963
|
exports.IterableAccordionWidget = IterableAccordionWidget;
|
|
12340
12964
|
exports.JSONEditorPanel = JSONEditorPanel;
|
|
12965
|
+
exports.MultiSelectWidget = MultiSelectWidget;
|
|
12341
12966
|
exports.NumberInputWidget = NumberInputWidget;
|
|
12342
12967
|
exports.PanelRenderer = PanelRenderer;
|
|
12343
12968
|
exports.PhoneInputWidget = PhoneInputWidget;
|
|
@@ -12367,6 +12992,7 @@ exports.createWidgetStore = createWidgetStore;
|
|
|
12367
12992
|
exports.createZodSchema = createZodSchema;
|
|
12368
12993
|
exports.defaultTheme = defaultTheme;
|
|
12369
12994
|
exports.evaluateCondition = evaluateCondition;
|
|
12995
|
+
exports.evaluateWidgetConditions = evaluateWidgetConditions;
|
|
12370
12996
|
exports.filterByCharacterType = filterByCharacterType;
|
|
12371
12997
|
exports.formatCurrency = formatCurrency;
|
|
12372
12998
|
exports.formatDate = formatDate;
|
|
@@ -12375,15 +13001,20 @@ exports.formatPhone = formatPhone;
|
|
|
12375
13001
|
exports.formatValue = formatValue;
|
|
12376
13002
|
exports.geoHierarchyBuilder = geoHierarchyBuilder;
|
|
12377
13003
|
exports.getApiDataSource = getApiDataSource;
|
|
13004
|
+
exports.getCachedApiDataSource = getCachedApiDataSource;
|
|
12378
13005
|
exports.getFormattedNumberLength = getFormattedNumberLength;
|
|
13006
|
+
exports.getGeoDescendantWidgetIds = getGeoDescendantWidgetIds;
|
|
13007
|
+
exports.getGeoGroupId = getGeoGroupId;
|
|
12379
13008
|
exports.getSchemaDataSource = getSchemaDataSource;
|
|
12380
13009
|
exports.getStaticDataSource = getStaticDataSource;
|
|
12381
13010
|
exports.getValueByPath = getValueByPath;
|
|
12382
13011
|
exports.getWidgetValue = getWidgetValue;
|
|
13012
|
+
exports.hasVisibilityRules = hasVisibilityRules;
|
|
12383
13013
|
exports.initI18n = initI18n;
|
|
12384
13014
|
exports.isAllowedKey = isAllowedKey;
|
|
12385
13015
|
exports.isUpstreamGeoAncestor = isUpstreamGeoAncestor;
|
|
12386
13016
|
exports.normalizeNumericDefault = normalizeNumericDefault;
|
|
13017
|
+
exports.normalizeOptionRules = normalizeOptionRules;
|
|
12387
13018
|
exports.parseDataPath = parseDataPath;
|
|
12388
13019
|
exports.parseNumber = parseNumber;
|
|
12389
13020
|
exports.registerDefaultWidgets = registerDefaultWidgets;
|
|
@@ -12392,6 +13023,7 @@ exports.removeMask = removeMask;
|
|
|
12392
13023
|
exports.resetAll = resetAll;
|
|
12393
13024
|
exports.resetAndSeedGeoHierarchyFromValues = resetAndSeedGeoHierarchyFromValues;
|
|
12394
13025
|
exports.resetWidget = resetWidget;
|
|
13026
|
+
exports.resolveGeoWidgetLevelLabel = resolveGeoWidgetLevelLabel;
|
|
12395
13027
|
exports.resolveGeoWidgetLevelValue = resolveGeoWidgetLevelValue;
|
|
12396
13028
|
exports.resolveTheme = resolveTheme;
|
|
12397
13029
|
exports.resolveWidgetIdValue = resolveWidgetIdValue;
|
|
@@ -12405,6 +13037,7 @@ exports.setValueByPath = setValueByPath;
|
|
|
12405
13037
|
exports.setValues = setValues;
|
|
12406
13038
|
exports.setWidgetValue = setWidgetValue;
|
|
12407
13039
|
exports.shouldEnableWidget = shouldEnableWidget;
|
|
13040
|
+
exports.shouldRequireWidget = shouldRequireWidget;
|
|
12408
13041
|
exports.shouldShowWidget = shouldShowWidget;
|
|
12409
13042
|
exports.transformDataSourceOptions = transformDataSourceOptions;
|
|
12410
13043
|
exports.translatePanelConfig = translatePanelConfig;
|