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