@openg2p/registry-widgets 1.1.2-dev.0 → 1.1.2-dev.10
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/assets/index.d.ts +3 -1
- package/dist/assets/index.d.ts.map +1 -1
- package/dist/components/SectionBuilder/schemas.d.ts +204 -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/hooks/useGeoWidgetCascade.d.ts.map +1 -1
- package/dist/index.d.ts +117 -28
- package/dist/index.esm.js +2203 -883
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +2228 -881
- 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 +65 -0
- package/dist/utils/geoHierarchy.d.ts.map +1 -1
- package/dist/utils/pathUtils.d.ts +5 -0
- package/dist/utils/pathUtils.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 +0 -13
- package/dist/widgets/DialogTableWidget.d.ts.map +1 -1
- package/dist/widgets/FileInputWidget.d.ts.map +1 -1
- package/dist/widgets/HeaderSectionWidget.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 +5 -0
- package/dist/widgets/RegisterLookupWidget.d.ts.map +1 -0
- 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 +2 -0
- package/dist/widgets/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -174,15 +174,33 @@ const parseDataPath = (dataPath) => {
|
|
|
174
174
|
/**
|
|
175
175
|
* Get value from widget state using data path
|
|
176
176
|
*/
|
|
177
|
+
/**
|
|
178
|
+
* Resolve a widget-id reference in Redux values.
|
|
179
|
+
* Supports namespaced ids (e.g. "rv-section-0__region_code" when ref is "region_code").
|
|
180
|
+
*/
|
|
181
|
+
const resolveWidgetIdValue = (values, ref) => {
|
|
182
|
+
if (!ref) {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
if (ref.includes('.')) {
|
|
186
|
+
return getValueByPath(values, ref);
|
|
187
|
+
}
|
|
188
|
+
if (Object.prototype.hasOwnProperty.call(values, ref)) {
|
|
189
|
+
return values[ref];
|
|
190
|
+
}
|
|
191
|
+
const suffix = `__${ref}`;
|
|
192
|
+
for (const [key, val] of Object.entries(values)) {
|
|
193
|
+
if (key.endsWith(suffix)) {
|
|
194
|
+
return val;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return undefined;
|
|
198
|
+
};
|
|
177
199
|
const getWidgetValue = (values, dataPath, widgetId) => {
|
|
178
200
|
if (!dataPath) {
|
|
179
201
|
// Fallback to widget-id if no data path
|
|
180
202
|
return values[widgetId];
|
|
181
203
|
}
|
|
182
|
-
if (widgetId == "user-profile") {
|
|
183
|
-
console.log('values', values);
|
|
184
|
-
console.log('dataPath', dataPath);
|
|
185
|
-
}
|
|
186
204
|
if (typeof dataPath === 'string') {
|
|
187
205
|
return getValueByPath(values, dataPath);
|
|
188
206
|
}
|
|
@@ -385,6 +403,18 @@ const createZodSchema = (validation, required = false) => {
|
|
|
385
403
|
return schema;
|
|
386
404
|
};
|
|
387
405
|
|
|
406
|
+
const normalizeBooleanLike = (val) => {
|
|
407
|
+
if (val === true || val === 1)
|
|
408
|
+
return true;
|
|
409
|
+
if (val === false || val === 0 || val === null || val === undefined || val === '') {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
if (typeof val === 'string') {
|
|
413
|
+
const normalized = val.trim().toLowerCase();
|
|
414
|
+
return normalized === 'true' || normalized === 'yes' || normalized === '1';
|
|
415
|
+
}
|
|
416
|
+
return Boolean(val);
|
|
417
|
+
};
|
|
388
418
|
/**
|
|
389
419
|
* Evaluate condition against field value
|
|
390
420
|
*/
|
|
@@ -393,6 +423,9 @@ const evaluateCondition = (condition, allValues) => {
|
|
|
393
423
|
const { operator, value } = condition;
|
|
394
424
|
switch (operator) {
|
|
395
425
|
case 'equals':
|
|
426
|
+
if (typeof value === 'boolean' || typeof fieldValue === 'boolean') {
|
|
427
|
+
return normalizeBooleanLike(fieldValue) === normalizeBooleanLike(value);
|
|
428
|
+
}
|
|
396
429
|
return fieldValue === value;
|
|
397
430
|
case 'notEquals':
|
|
398
431
|
return fieldValue !== value;
|
|
@@ -425,37 +458,62 @@ const evaluateCondition = (condition, allValues) => {
|
|
|
425
458
|
}
|
|
426
459
|
};
|
|
427
460
|
/**
|
|
428
|
-
*
|
|
461
|
+
* Normalize widget-data-options into a sequential list of action rules.
|
|
462
|
+
* Supports legacy single { action, condition } and new { actions: [...] }.
|
|
429
463
|
*/
|
|
430
|
-
const
|
|
431
|
-
if (!options
|
|
432
|
-
return
|
|
464
|
+
const normalizeOptionRules = (options) => {
|
|
465
|
+
if (!options) {
|
|
466
|
+
return [];
|
|
433
467
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
return conditionResult;
|
|
468
|
+
if (Array.isArray(options.actions) && options.actions.length > 0) {
|
|
469
|
+
return options.actions.filter((rule) => !!rule?.action);
|
|
437
470
|
}
|
|
438
|
-
if (options.action
|
|
439
|
-
return
|
|
471
|
+
if (options.action && options.condition) {
|
|
472
|
+
return [{ action: options.action, condition: options.condition }];
|
|
440
473
|
}
|
|
441
|
-
return
|
|
474
|
+
return [];
|
|
475
|
+
};
|
|
476
|
+
const hasVisibilityRules = (options) => {
|
|
477
|
+
return normalizeOptionRules(options).some((rule) => rule.action === 'show' || rule.action === 'hide');
|
|
442
478
|
};
|
|
443
479
|
/**
|
|
444
|
-
*
|
|
480
|
+
* Evaluate widget-data-options rules sequentially.
|
|
481
|
+
* show/hide and enable/disable only affect visibility and enabled state.
|
|
482
|
+
* require is independent: required = widget-required OR require-condition-match.
|
|
445
483
|
*/
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
484
|
+
const evaluateWidgetConditions = (options, allValues, baseRequired = false) => {
|
|
485
|
+
let visible = true;
|
|
486
|
+
let enabled = true;
|
|
487
|
+
let required = baseRequired;
|
|
488
|
+
const rules = normalizeOptionRules(options);
|
|
489
|
+
for (const rule of rules) {
|
|
490
|
+
if (!rule.condition) {
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
const match = evaluateCondition(rule.condition, allValues);
|
|
494
|
+
switch (rule.action) {
|
|
495
|
+
case 'show':
|
|
496
|
+
visible = match;
|
|
497
|
+
break;
|
|
498
|
+
case 'hide':
|
|
499
|
+
visible = !match;
|
|
500
|
+
break;
|
|
501
|
+
case 'enable':
|
|
502
|
+
enabled = match;
|
|
503
|
+
break;
|
|
504
|
+
case 'disable':
|
|
505
|
+
enabled = !match;
|
|
506
|
+
break;
|
|
507
|
+
case 'require':
|
|
508
|
+
required = baseRequired || match;
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
456
511
|
}
|
|
457
|
-
return
|
|
512
|
+
return { visible, enabled, required };
|
|
458
513
|
};
|
|
514
|
+
const shouldShowWidget = (options, allValues) => evaluateWidgetConditions(options, allValues).visible;
|
|
515
|
+
const shouldEnableWidget = (options, allValues) => evaluateWidgetConditions(options, allValues).enabled;
|
|
516
|
+
const shouldRequireWidget = (options, allValues, baseRequired = false) => evaluateWidgetConditions(options, allValues, baseRequired).required;
|
|
459
517
|
|
|
460
518
|
/**
|
|
461
519
|
* Format number with thousand and decimal separators
|
|
@@ -1025,6 +1083,67 @@ const formatValue = (value, format, widgetType) => {
|
|
|
1025
1083
|
return value?.toString() || '';
|
|
1026
1084
|
};
|
|
1027
1085
|
|
|
1086
|
+
const apiDataSourceCache = new Map();
|
|
1087
|
+
const apiDataSourceInflight = new Map();
|
|
1088
|
+
function buildApiRequestContext(dataSource, allValues, levelId) {
|
|
1089
|
+
let depValue = null;
|
|
1090
|
+
if (dataSource.dependsOn) {
|
|
1091
|
+
if (dataSource.dependsOn.includes('.')) {
|
|
1092
|
+
depValue = getValueByPath(allValues, dataSource.dependsOn);
|
|
1093
|
+
}
|
|
1094
|
+
else {
|
|
1095
|
+
depValue = resolveWidgetIdValue(allValues, dataSource.dependsOn);
|
|
1096
|
+
}
|
|
1097
|
+
if (depValue === null || depValue === undefined || depValue === '') {
|
|
1098
|
+
return null;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
const method = dataSource.method || 'GET';
|
|
1102
|
+
const staticParams = { ...dataSource.params };
|
|
1103
|
+
const standardFields = ['type', 'service', 'endpoint', 'url', 'method', 'dependsOn', 'valueKey', 'labelKey', 'headers', 'body', 'params', 'level_id'];
|
|
1104
|
+
for (const [key, value] of Object.entries(dataSource)) {
|
|
1105
|
+
if (!standardFields.includes(key) && value !== undefined && value !== null) {
|
|
1106
|
+
staticParams[key] = value;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
if (levelId) {
|
|
1110
|
+
staticParams.level_id = levelId;
|
|
1111
|
+
}
|
|
1112
|
+
const requestParams = { ...staticParams };
|
|
1113
|
+
if (dataSource.dependsOn && depValue !== null && depValue !== undefined) {
|
|
1114
|
+
const parentValueId = typeof depValue === 'object' && depValue !== null
|
|
1115
|
+
? (depValue.level_value_id || depValue.id || depValue.value || depValue)
|
|
1116
|
+
: depValue;
|
|
1117
|
+
if (staticParams.level_id) {
|
|
1118
|
+
requestParams.parent_level_value_id = parentValueId;
|
|
1119
|
+
}
|
|
1120
|
+
else {
|
|
1121
|
+
const paramKey = dataSource.dependsOn.split('.').pop() || 'filter';
|
|
1122
|
+
requestParams[paramKey] = parentValueId;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
else if (staticParams.level_id) {
|
|
1126
|
+
requestParams.parent_level_value_id = '';
|
|
1127
|
+
}
|
|
1128
|
+
const service = dataSource.service;
|
|
1129
|
+
const endpoint = dataSource.endpoint;
|
|
1130
|
+
if (!service || !endpoint) {
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
return { service, endpoint, method, requestParams };
|
|
1134
|
+
}
|
|
1135
|
+
function buildApiDataSourceCacheKey(service, endpoint, method, requestParams) {
|
|
1136
|
+
return `${service}|${endpoint}|${method}|${JSON.stringify(requestParams)}`;
|
|
1137
|
+
}
|
|
1138
|
+
/** Return cached API options when already fetched (e.g. duplicate table cells). */
|
|
1139
|
+
function getCachedApiDataSource(dataSource, allValues, levelId) {
|
|
1140
|
+
const context = buildApiRequestContext(dataSource, allValues, levelId);
|
|
1141
|
+
if (!context) {
|
|
1142
|
+
return undefined;
|
|
1143
|
+
}
|
|
1144
|
+
const cacheKey = buildApiDataSourceCacheKey(context.service, context.endpoint, context.method, context.requestParams);
|
|
1145
|
+
return apiDataSourceCache.get(cacheKey);
|
|
1146
|
+
}
|
|
1028
1147
|
/**
|
|
1029
1148
|
* Get static data source options
|
|
1030
1149
|
*/
|
|
@@ -1042,111 +1161,33 @@ const getApiDataSource = async (dataSource, allValues, dataSourceRequestHandler,
|
|
|
1042
1161
|
return [];
|
|
1043
1162
|
}
|
|
1044
1163
|
try {
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
let depValue = null;
|
|
1048
|
-
if (dataSource.dependsOn) {
|
|
1049
|
-
// First try as data path
|
|
1050
|
-
depValue = getValueByPath(allValues, dataSource.dependsOn);
|
|
1051
|
-
// If not found and doesn't contain dots, try as widget-id
|
|
1052
|
-
if ((depValue === null || depValue === undefined) && !dataSource.dependsOn.includes('.')) {
|
|
1053
|
-
depValue = allValues[dataSource.dependsOn];
|
|
1054
|
-
// Smart resolution: If not found at top level, try to find the dependency in the same nested object
|
|
1055
|
-
// by looking for other keys in allValues that might contain the dependency.
|
|
1056
|
-
// We look for objects that contain both the current widget's path (if we can guess it) and the dependency.
|
|
1057
|
-
// But since we don't know the current widget's path here, we search for any object that has this dependency key.
|
|
1058
|
-
if (depValue === null || depValue === undefined || depValue === '') {
|
|
1059
|
-
for (const val of Object.values(allValues)) {
|
|
1060
|
-
if (val && typeof val === 'object' && !Array.isArray(val) && dataSource.dependsOn in val) {
|
|
1061
|
-
depValue = val[dataSource.dependsOn];
|
|
1062
|
-
if (depValue !== null && depValue !== undefined && depValue !== '')
|
|
1063
|
-
break;
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
if (depValue === null || depValue === undefined || depValue === '') {
|
|
1069
|
-
// If dependency is empty, return empty array
|
|
1070
|
-
return [];
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
// Build request parameters
|
|
1074
|
-
const method = dataSource.method || 'GET';
|
|
1075
|
-
// Extract static params from dataSource
|
|
1076
|
-
// Include explicit params object and any additional fields (like level_id)
|
|
1077
|
-
const staticParams = { ...dataSource.params };
|
|
1078
|
-
// Extract additional fields that aren't part of the standard ApiDataSource interface
|
|
1079
|
-
// These are fields like level_id that might be directly on the dataSource
|
|
1080
|
-
// BUT: level_id should come from widget-geo-config.level, not from dataSource
|
|
1081
|
-
const standardFields = ['type', 'service', 'endpoint', 'url', 'method', 'dependsOn', 'valueKey', 'labelKey', 'headers', 'body', 'params', 'level_id'];
|
|
1082
|
-
for (const [key, value] of Object.entries(dataSource)) {
|
|
1083
|
-
if (!standardFields.includes(key) && value !== undefined && value !== null) {
|
|
1084
|
-
staticParams[key] = value;
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
// If levelId is provided (from widget-geo-config.level), use it instead of any level_id in dataSource
|
|
1088
|
-
if (levelId) {
|
|
1089
|
-
staticParams.level_id = levelId;
|
|
1090
|
-
}
|
|
1091
|
-
// Build request params object
|
|
1092
|
-
const requestParams = { ...staticParams };
|
|
1093
|
-
// Add dependency value to params
|
|
1094
|
-
if (dataSource.dependsOn && depValue !== null && depValue !== undefined) {
|
|
1095
|
-
// Extract the actual value ID if depValue is an object
|
|
1096
|
-
const parentValueId = typeof depValue === 'object' && depValue !== null
|
|
1097
|
-
? (depValue.level_value_id || depValue.id || depValue.value || depValue)
|
|
1098
|
-
: depValue;
|
|
1099
|
-
// For geo APIs, use parent_level_value_id
|
|
1100
|
-
if (staticParams.level_id) {
|
|
1101
|
-
requestParams.parent_level_value_id = parentValueId;
|
|
1102
|
-
}
|
|
1103
|
-
else {
|
|
1104
|
-
// For other APIs, use the dependency field name as param key
|
|
1105
|
-
const paramKey = dataSource.dependsOn.split('.').pop() || 'filter';
|
|
1106
|
-
requestParams[paramKey] = parentValueId;
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
else if (staticParams.level_id) {
|
|
1110
|
-
// First level has no parent, send empty string as many OpenG2P APIs expect it
|
|
1111
|
-
requestParams.parent_level_value_id = "";
|
|
1112
|
-
}
|
|
1113
|
-
// Get service mnemonic and endpoint (required)
|
|
1114
|
-
const service = dataSource.service;
|
|
1115
|
-
const endpoint = dataSource.endpoint;
|
|
1116
|
-
if (!service) {
|
|
1117
|
-
console.error('[getApiDataSource] API data source missing service mnemonic. Use "service" field instead of "url"');
|
|
1164
|
+
const context = buildApiRequestContext(dataSource, allValues, levelId);
|
|
1165
|
+
if (!context) {
|
|
1118
1166
|
return [];
|
|
1119
1167
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
return
|
|
1168
|
+
const { service, endpoint, method, requestParams } = context;
|
|
1169
|
+
const cacheKey = buildApiDataSourceCacheKey(service, endpoint, method, requestParams);
|
|
1170
|
+
const cached = apiDataSourceCache.get(cacheKey);
|
|
1171
|
+
if (cached) {
|
|
1172
|
+
return cached;
|
|
1173
|
+
}
|
|
1174
|
+
const inflight = apiDataSourceInflight.get(cacheKey);
|
|
1175
|
+
if (inflight) {
|
|
1176
|
+
return inflight;
|
|
1177
|
+
}
|
|
1178
|
+
const fetchPromise = (async () => {
|
|
1179
|
+
const response = await dataSourceRequestHandler(service, endpoint, method, requestParams, { headers: dataSource.headers });
|
|
1180
|
+
const parsed = Array.isArray(response) ? response : [];
|
|
1181
|
+
apiDataSourceCache.set(cacheKey, parsed);
|
|
1182
|
+
return parsed;
|
|
1183
|
+
})();
|
|
1184
|
+
apiDataSourceInflight.set(cacheKey, fetchPromise);
|
|
1185
|
+
try {
|
|
1186
|
+
return await fetchPromise;
|
|
1139
1187
|
}
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
if (response.data && Array.isArray(response.data)) {
|
|
1143
|
-
return response.data;
|
|
1144
|
-
}
|
|
1145
|
-
if (response.results && Array.isArray(response.results)) {
|
|
1146
|
-
return response.results;
|
|
1147
|
-
}
|
|
1188
|
+
finally {
|
|
1189
|
+
apiDataSourceInflight.delete(cacheKey);
|
|
1148
1190
|
}
|
|
1149
|
-
return [];
|
|
1150
1191
|
}
|
|
1151
1192
|
catch (error) {
|
|
1152
1193
|
// Rethrow so useBaseWidget's catch can log it with full widget context
|
|
@@ -1932,85 +1973,588 @@ const WidgetProvider = ({ store, dataSourceRequestHandler, schemaData, translate
|
|
|
1932
1973
|
return content;
|
|
1933
1974
|
};
|
|
1934
1975
|
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
const
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
const dataSourceOptionsRef = React.useRef(dataSourceOptions);
|
|
1961
|
-
React.useEffect(() => {
|
|
1962
|
-
valuesRef.current = values;
|
|
1963
|
-
loadingRef.current = loading;
|
|
1964
|
-
dataSourceOptionsRef.current = dataSourceOptions;
|
|
1965
|
-
}, [values, loading, dataSourceOptions]);
|
|
1966
|
-
// Track last dispatched value to prevent duplicate dispatches
|
|
1967
|
-
const lastDispatchedValueRef = React.useRef(null);
|
|
1968
|
-
// Helper to extract displayable value from object (especially geo hierarchy objects)
|
|
1969
|
-
const extractValueFromObject = React.useCallback((obj) => {
|
|
1970
|
-
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
|
|
1971
|
-
return obj;
|
|
1976
|
+
/**
|
|
1977
|
+
* Custom hook for widget translations
|
|
1978
|
+
* Provides translation function with widget-specific namespace and fallback support
|
|
1979
|
+
*/
|
|
1980
|
+
const useWidgetTranslation = () => {
|
|
1981
|
+
const { translate: translateFunction } = useWidgetContext();
|
|
1982
|
+
/**
|
|
1983
|
+
* Translate a key with flexible namespace support
|
|
1984
|
+
* Supports translation keys in various formats and direct strings
|
|
1985
|
+
*
|
|
1986
|
+
* Translation key formats supported:
|
|
1987
|
+
* - "widgets:common.addItem" - Namespaced key (for widget-specific translations)
|
|
1988
|
+
* - "Name" - Direct string (will be looked up in flat translation structure)
|
|
1989
|
+
* - "sections.personalDetails" - Nested key (for backward compatibility)
|
|
1990
|
+
*
|
|
1991
|
+
* With flat translation structure, direct strings like "Name" are automatically
|
|
1992
|
+
* translated by looking them up in the translation resources.
|
|
1993
|
+
*
|
|
1994
|
+
* @param keyOrString - Translation key (e.g., "widgets:common.addItem") or direct string (e.g., "Name")
|
|
1995
|
+
* @param options - Translation options (interpolation values, default value, etc.)
|
|
1996
|
+
* @returns Translated string or original string if translation not found
|
|
1997
|
+
*/
|
|
1998
|
+
const translate = (keyOrString, options) => {
|
|
1999
|
+
if (!keyOrString) {
|
|
2000
|
+
return options?.defaultValue || '';
|
|
1972
2001
|
}
|
|
1973
|
-
//
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
// If we have a geo hierarchy object, extract the value for this specific level
|
|
1977
|
-
const hierarchy = obj.hierarchy || obj.geo_code_hierarchy_json?.hierarchy;
|
|
1978
|
-
if (Array.isArray(hierarchy)) {
|
|
1979
|
-
const levelData = hierarchy.find((l) => l.level === geoConfig.level);
|
|
1980
|
-
if (levelData) {
|
|
1981
|
-
return levelData.level_value_id;
|
|
1982
|
-
}
|
|
1983
|
-
}
|
|
2002
|
+
// Use the provided translation function or fallback to the key
|
|
2003
|
+
if (translateFunction) {
|
|
2004
|
+
return translateFunction(keyOrString, options) || options?.defaultValue || keyOrString;
|
|
1984
2005
|
}
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
2006
|
+
// Fallback to key if no translation function available
|
|
2007
|
+
return options?.defaultValue || keyOrString;
|
|
2008
|
+
};
|
|
2009
|
+
/**
|
|
2010
|
+
* Translate widget config property
|
|
2011
|
+
* Attempts to translate the value, but if translation is not found,
|
|
2012
|
+
* returns the original value as-is (graceful fallback)
|
|
2013
|
+
*
|
|
2014
|
+
* This function will:
|
|
2015
|
+
* - Try to translate any string value
|
|
2016
|
+
* - If translation exists, use the translated value
|
|
2017
|
+
* - If translation doesn't exist (returns same value or throws), use original value
|
|
2018
|
+
* - This prevents errors when literal strings like "XXXX-XXXX-XXXX" are used
|
|
2019
|
+
*/
|
|
2020
|
+
const translateConfig = (value, fallback) => {
|
|
2021
|
+
if (!value) {
|
|
2022
|
+
return fallback || '';
|
|
2023
|
+
}
|
|
2024
|
+
// Try to translate the value
|
|
2025
|
+
if (translateFunction) {
|
|
2026
|
+
try {
|
|
2027
|
+
// Pass defaultValue to ensure we get the original value if translation fails
|
|
2028
|
+
const translated = translateFunction(value, { defaultValue: value });
|
|
2029
|
+
// If translation returns empty, null, undefined, or the exact same value,
|
|
2030
|
+
// it means no translation was found - return the original value
|
|
2031
|
+
if (!translated || translated === value) {
|
|
2032
|
+
return value;
|
|
2033
|
+
}
|
|
2034
|
+
// Translation found, return it
|
|
2035
|
+
return translated;
|
|
1995
2036
|
}
|
|
1996
|
-
|
|
1997
|
-
return
|
|
2037
|
+
catch (error) {
|
|
2038
|
+
// If translation throws an error (e.g., missing key warning), return original value
|
|
2039
|
+
return value;
|
|
1998
2040
|
}
|
|
1999
|
-
// If it's a geo hierarchy object but no extractable ID, return undefined to avoid rendering object
|
|
2000
|
-
return undefined;
|
|
2001
2041
|
}
|
|
2002
|
-
//
|
|
2003
|
-
|
|
2004
|
-
|
|
2042
|
+
// No translation function available, return value as-is
|
|
2043
|
+
return value;
|
|
2044
|
+
};
|
|
2045
|
+
// No need of this getLanguage and changeLanguage functions
|
|
2046
|
+
/**
|
|
2047
|
+
* Get current language
|
|
2048
|
+
*/
|
|
2049
|
+
// const getLanguage = (): string => {
|
|
2050
|
+
// return i18n.language || 'en';
|
|
2051
|
+
// };
|
|
2052
|
+
/**
|
|
2053
|
+
* Change language
|
|
2054
|
+
*/
|
|
2055
|
+
// const changeLanguage = (lng: string): Promise<void> => {
|
|
2056
|
+
// return i18n.changeLanguage(lng).then(() => undefined);
|
|
2057
|
+
// };
|
|
2058
|
+
return {
|
|
2059
|
+
t: translate,
|
|
2060
|
+
translate,
|
|
2061
|
+
translateConfig,
|
|
2062
|
+
// getLanguage,
|
|
2063
|
+
// changeLanguage,
|
|
2064
|
+
// i18n: null,
|
|
2065
|
+
};
|
|
2066
|
+
};
|
|
2067
|
+
|
|
2068
|
+
/**
|
|
2069
|
+
* Geo Hierarchy Builder
|
|
2070
|
+
* Manages geo hierarchy state and builds hierarchy JSON structure
|
|
2071
|
+
*/
|
|
2072
|
+
function extractLevelValueFromStored(value, geoConfig) {
|
|
2073
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
2074
|
+
return value;
|
|
2075
|
+
}
|
|
2076
|
+
const hierarchy = value.hierarchy || value.geo_code_hierarchy_json?.hierarchy;
|
|
2077
|
+
if (Array.isArray(hierarchy)) {
|
|
2078
|
+
const levelData = hierarchy.find((l) => l.level === geoConfig.level);
|
|
2079
|
+
if (levelData) {
|
|
2080
|
+
return levelData.level_value_id;
|
|
2005
2081
|
}
|
|
2006
|
-
|
|
2007
|
-
|
|
2082
|
+
// Level absent from hierarchy (e.g. cleared by upstream cascade) — do not use lowest-level fallback
|
|
2083
|
+
return undefined;
|
|
2084
|
+
}
|
|
2085
|
+
if ('geo_lowest_level_value_id' in value) {
|
|
2086
|
+
return value.geo_lowest_level_value_id;
|
|
2087
|
+
}
|
|
2088
|
+
if ('lowest_level_value_id' in value) {
|
|
2089
|
+
return value.lowest_level_value_id;
|
|
2090
|
+
}
|
|
2091
|
+
if (value.geo_code_hierarchy_json?.lowest_level_value_id) {
|
|
2092
|
+
return value.geo_code_hierarchy_json.lowest_level_value_id;
|
|
2093
|
+
}
|
|
2094
|
+
if (value.geo_code_hierarchy_json?.geo_lowest_level_value_id) {
|
|
2095
|
+
return value.geo_code_hierarchy_json.geo_lowest_level_value_id;
|
|
2096
|
+
}
|
|
2097
|
+
return undefined;
|
|
2098
|
+
}
|
|
2099
|
+
/**
|
|
2100
|
+
* Resolve the display value for a geo level widget.
|
|
2101
|
+
* When widgetId is explicitly set in Redux (including cleared undefined/null), do not
|
|
2102
|
+
* fall back to shared hierarchy dataPath — that stale path was keeping grandchildren visible.
|
|
2103
|
+
*/
|
|
2104
|
+
function resolveGeoWidgetLevelValue(values, widgetId, dataPath, geoConfig) {
|
|
2105
|
+
if (Object.prototype.hasOwnProperty.call(values, widgetId)) {
|
|
2106
|
+
let value = values[widgetId];
|
|
2107
|
+
if (value === undefined || value === null || value === '') {
|
|
2108
|
+
return value;
|
|
2008
2109
|
}
|
|
2009
|
-
if ('
|
|
2010
|
-
return
|
|
2110
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
2111
|
+
return extractLevelValueFromStored(value, geoConfig);
|
|
2011
2112
|
}
|
|
2012
|
-
|
|
2013
|
-
|
|
2113
|
+
return value;
|
|
2114
|
+
}
|
|
2115
|
+
if (!dataPath) {
|
|
2116
|
+
return undefined;
|
|
2117
|
+
}
|
|
2118
|
+
let value = getWidgetValue(values, dataPath, widgetId);
|
|
2119
|
+
if (value !== null && value !== undefined && typeof value === 'object' && !Array.isArray(value)) {
|
|
2120
|
+
value = extractLevelValueFromStored(value, geoConfig);
|
|
2121
|
+
}
|
|
2122
|
+
return value;
|
|
2123
|
+
}
|
|
2124
|
+
/**
|
|
2125
|
+
* Write the in-memory geo hierarchy builder state into Redux at the shared dataPath.
|
|
2126
|
+
*/
|
|
2127
|
+
function applySharedGeoHierarchyToValues(baseValues, groupId, dataPath, widgetId) {
|
|
2128
|
+
const hierarchyJson = geoHierarchyBuilder.buildHierarchyJson(groupId);
|
|
2129
|
+
if (!dataPath || typeof dataPath !== 'string') {
|
|
2130
|
+
return baseValues;
|
|
2131
|
+
}
|
|
2132
|
+
const inner = hierarchyJson?.geo_code_hierarchy_json;
|
|
2133
|
+
const lowestId = hierarchyJson?.geo_lowest_level_value_id;
|
|
2134
|
+
if (dataPath.endsWith('.geo_code_hierarchy_json')) {
|
|
2135
|
+
const prefix = dataPath.substring(0, dataPath.lastIndexOf('.'));
|
|
2136
|
+
let finalUpdatedValues = setWidgetValue(baseValues, dataPath, widgetId, inner);
|
|
2137
|
+
finalUpdatedValues = setWidgetValue(finalUpdatedValues, `${prefix}.geo_lowest_level_value_id`, widgetId, lowestId);
|
|
2138
|
+
return finalUpdatedValues;
|
|
2139
|
+
}
|
|
2140
|
+
return setWidgetValue(baseValues, dataPath, widgetId, inner);
|
|
2141
|
+
}
|
|
2142
|
+
class GeoHierarchyBuilder {
|
|
2143
|
+
constructor() {
|
|
2144
|
+
this.hierarchies = new Map();
|
|
2145
|
+
}
|
|
2146
|
+
/**
|
|
2147
|
+
* Get or create hierarchy state for a group
|
|
2148
|
+
*/
|
|
2149
|
+
getHierarchy(groupId = 'default') {
|
|
2150
|
+
if (!this.hierarchies.has(groupId)) {
|
|
2151
|
+
this.hierarchies.set(groupId, {
|
|
2152
|
+
levels: new Map(),
|
|
2153
|
+
order: [],
|
|
2154
|
+
});
|
|
2155
|
+
}
|
|
2156
|
+
return this.hierarchies.get(groupId);
|
|
2157
|
+
}
|
|
2158
|
+
/**
|
|
2159
|
+
* Add a level to the hierarchy
|
|
2160
|
+
*/
|
|
2161
|
+
addLevel(level, level_value_id, level_value_mnemonic, groupId = 'default') {
|
|
2162
|
+
const hierarchy = this.getHierarchy(groupId);
|
|
2163
|
+
// If level already exists, remove it and everything after it
|
|
2164
|
+
const existingIndex = hierarchy.order.indexOf(level);
|
|
2165
|
+
if (existingIndex >= 0) {
|
|
2166
|
+
// Remove this level and all subsequent levels
|
|
2167
|
+
const levelsToRemove = hierarchy.order.slice(existingIndex);
|
|
2168
|
+
levelsToRemove.forEach((l) => {
|
|
2169
|
+
hierarchy.levels.delete(l);
|
|
2170
|
+
hierarchy.order = hierarchy.order.filter((o) => o !== l);
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
2173
|
+
// Add new level
|
|
2174
|
+
hierarchy.levels.set(level, {
|
|
2175
|
+
level,
|
|
2176
|
+
level_value_id,
|
|
2177
|
+
level_value_mnemonic,
|
|
2178
|
+
});
|
|
2179
|
+
hierarchy.order.push(level);
|
|
2180
|
+
}
|
|
2181
|
+
/**
|
|
2182
|
+
* Remove a level and all levels below it
|
|
2183
|
+
*/
|
|
2184
|
+
removeLevelAndBelow(level, groupId = 'default') {
|
|
2185
|
+
const hierarchy = this.getHierarchy(groupId);
|
|
2186
|
+
const index = hierarchy.order.indexOf(level);
|
|
2187
|
+
if (index >= 0) {
|
|
2188
|
+
// Remove this level and all subsequent levels
|
|
2189
|
+
const levelsToRemove = hierarchy.order.slice(index);
|
|
2190
|
+
levelsToRemove.forEach((l) => {
|
|
2191
|
+
hierarchy.levels.delete(l);
|
|
2192
|
+
hierarchy.order = hierarchy.order.filter((o) => o !== l);
|
|
2193
|
+
});
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
/**
|
|
2197
|
+
* Build hierarchy JSON structure
|
|
2198
|
+
*/
|
|
2199
|
+
buildHierarchyJson(groupId = 'default') {
|
|
2200
|
+
const hierarchy = this.getHierarchy(groupId);
|
|
2201
|
+
if (hierarchy.order.length === 0) {
|
|
2202
|
+
return null;
|
|
2203
|
+
}
|
|
2204
|
+
const hierarchyArray = hierarchy.order.map((level) => {
|
|
2205
|
+
const data = hierarchy.levels.get(level);
|
|
2206
|
+
return {
|
|
2207
|
+
level: data.level,
|
|
2208
|
+
level_value_id: data.level_value_id,
|
|
2209
|
+
level_value_mnemonic: data.level_value_mnemonic,
|
|
2210
|
+
};
|
|
2211
|
+
});
|
|
2212
|
+
const lowestLevel = hierarchy.order[hierarchy.order.length - 1];
|
|
2213
|
+
const lowestLevelData = hierarchy.levels.get(lowestLevel);
|
|
2214
|
+
return {
|
|
2215
|
+
geo_lowest_level_value_id: lowestLevelData.level_value_id,
|
|
2216
|
+
geo_code_hierarchy_json: {
|
|
2217
|
+
hierarchy: hierarchyArray,
|
|
2218
|
+
lowest_level_value_id: lowestLevelData.level_value_id,
|
|
2219
|
+
},
|
|
2220
|
+
};
|
|
2221
|
+
}
|
|
2222
|
+
/**
|
|
2223
|
+
* Clear hierarchy for a group
|
|
2224
|
+
*/
|
|
2225
|
+
clear(groupId = 'default') {
|
|
2226
|
+
this.hierarchies.delete(groupId);
|
|
2227
|
+
}
|
|
2228
|
+
/**
|
|
2229
|
+
* Clear all hierarchies
|
|
2230
|
+
*/
|
|
2231
|
+
clearAll() {
|
|
2232
|
+
this.hierarchies.clear();
|
|
2233
|
+
}
|
|
2234
|
+
/**
|
|
2235
|
+
* Get current levels for a group
|
|
2236
|
+
*/
|
|
2237
|
+
getLevels(groupId = 'default') {
|
|
2238
|
+
const hierarchy = this.getHierarchy(groupId);
|
|
2239
|
+
return hierarchy.order.map((level) => hierarchy.levels.get(level));
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
// Singleton instance
|
|
2243
|
+
const geoHierarchyBuilder = new GeoHierarchyBuilder();
|
|
2244
|
+
/** Sentinel written to Redux when a geo level is cleared by cascade (distinct from "never set"). */
|
|
2245
|
+
const GEO_LEVEL_CLEARED = null;
|
|
2246
|
+
const geoWidgetParentRegistry = new Map();
|
|
2247
|
+
const geoWidgetConfigRegistry = new Map();
|
|
2248
|
+
function registerGeoWidgetParent(widgetId, parentWidgetId) {
|
|
2249
|
+
geoWidgetParentRegistry.set(widgetId, parentWidgetId || null);
|
|
2250
|
+
}
|
|
2251
|
+
function unregisterGeoWidgetParent(widgetId) {
|
|
2252
|
+
geoWidgetParentRegistry.delete(widgetId);
|
|
2253
|
+
}
|
|
2254
|
+
function registerGeoWidget(widgetId, geoConfig, dataPath) {
|
|
2255
|
+
if (typeof dataPath !== 'string') {
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
const parentWidgetId = geoConfig.parentWidgetId?.trim() ? geoConfig.parentWidgetId : null;
|
|
2259
|
+
registerGeoWidgetParent(widgetId, parentWidgetId);
|
|
2260
|
+
geoWidgetConfigRegistry.set(widgetId, {
|
|
2261
|
+
widgetId,
|
|
2262
|
+
parentWidgetId,
|
|
2263
|
+
level: geoConfig.level,
|
|
2264
|
+
geoConfig,
|
|
2265
|
+
dataPath,
|
|
2266
|
+
groupId: getGeoGroupId(dataPath),
|
|
2267
|
+
});
|
|
2268
|
+
}
|
|
2269
|
+
function unregisterGeoWidget(widgetId) {
|
|
2270
|
+
unregisterGeoWidgetParent(widgetId);
|
|
2271
|
+
geoWidgetConfigRegistry.delete(widgetId);
|
|
2272
|
+
}
|
|
2273
|
+
function orderGeoWidgetRegistrations(registrations) {
|
|
2274
|
+
if (registrations.length <= 1) {
|
|
2275
|
+
return registrations;
|
|
2276
|
+
}
|
|
2277
|
+
const roots = registrations.filter((entry) => !entry.parentWidgetId);
|
|
2278
|
+
if (roots.length === 0) {
|
|
2279
|
+
return registrations;
|
|
2280
|
+
}
|
|
2281
|
+
const ordered = [];
|
|
2282
|
+
let current = roots[0];
|
|
2283
|
+
const visited = new Set();
|
|
2284
|
+
while (current && !visited.has(current.widgetId)) {
|
|
2285
|
+
visited.add(current.widgetId);
|
|
2286
|
+
ordered.push(current);
|
|
2287
|
+
current = registrations.find((entry) => entry.parentWidgetId === current.widgetId);
|
|
2288
|
+
}
|
|
2289
|
+
return ordered.length > 0 ? ordered : registrations;
|
|
2290
|
+
}
|
|
2291
|
+
function resolveLevelValueId(rawValue) {
|
|
2292
|
+
if (rawValue === null || rawValue === undefined || rawValue === '') {
|
|
2293
|
+
return null;
|
|
2294
|
+
}
|
|
2295
|
+
if (typeof rawValue === 'string' || typeof rawValue === 'number') {
|
|
2296
|
+
return String(rawValue);
|
|
2297
|
+
}
|
|
2298
|
+
if (typeof rawValue === 'object') {
|
|
2299
|
+
const id = rawValue.level_value_id || rawValue.id || rawValue.value;
|
|
2300
|
+
return id != null && id !== '' ? String(id) : null;
|
|
2301
|
+
}
|
|
2302
|
+
return null;
|
|
2303
|
+
}
|
|
2304
|
+
function resolveStoredMnemonic(values, registration, valueId) {
|
|
2305
|
+
const stored = getWidgetValue(values, registration.dataPath, registration.widgetId);
|
|
2306
|
+
const hierarchy = stored?.hierarchy || stored?.geo_code_hierarchy_json?.hierarchy;
|
|
2307
|
+
if (!Array.isArray(hierarchy)) {
|
|
2308
|
+
return undefined;
|
|
2309
|
+
}
|
|
2310
|
+
const levelData = hierarchy.find((entry) => entry.level === registration.level);
|
|
2311
|
+
if (levelData && String(levelData.level_value_id) === String(valueId)) {
|
|
2312
|
+
return levelData.level_value_mnemonic ? String(levelData.level_value_mnemonic) : undefined;
|
|
2313
|
+
}
|
|
2314
|
+
return undefined;
|
|
2315
|
+
}
|
|
2316
|
+
/** Resolve display mnemonic from cached dropdown options, then stored hierarchy. */
|
|
2317
|
+
function createGeoLevelMnemonicResolver(values, dataSources) {
|
|
2318
|
+
return (registration, valueId) => {
|
|
2319
|
+
const options = dataSources[registration.widgetId];
|
|
2320
|
+
const option = options?.find((entry) => String(entry.value) === String(valueId));
|
|
2321
|
+
if (option?.label) {
|
|
2322
|
+
return option.label;
|
|
2323
|
+
}
|
|
2324
|
+
return resolveStoredMnemonic(values, registration, valueId);
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2327
|
+
/** Rebuild group hierarchy from widget values in parent→child order; stop at first missing level. */
|
|
2328
|
+
function rebuildGeoHierarchyFromRegistrations(groupId, values, registrations, resolveMnemonic) {
|
|
2329
|
+
const ordered = orderGeoWidgetRegistrations(registrations.filter((entry) => entry.groupId === groupId));
|
|
2330
|
+
geoHierarchyBuilder.clear(groupId);
|
|
2331
|
+
for (const registration of ordered) {
|
|
2332
|
+
const rawValue = resolveGeoWidgetLevelValue(values, registration.widgetId, registration.dataPath, registration.geoConfig);
|
|
2333
|
+
const valueId = resolveLevelValueId(rawValue);
|
|
2334
|
+
if (!valueId) {
|
|
2335
|
+
break;
|
|
2336
|
+
}
|
|
2337
|
+
const mnemonic = resolveMnemonic?.(registration, valueId) ??
|
|
2338
|
+
resolveStoredMnemonic(values, registration, valueId) ??
|
|
2339
|
+
valueId;
|
|
2340
|
+
geoHierarchyBuilder.addLevel(registration.level, valueId, mnemonic, groupId);
|
|
2341
|
+
}
|
|
2342
|
+
return geoHierarchyBuilder.buildHierarchyJson(groupId) !== null;
|
|
2343
|
+
}
|
|
2344
|
+
function collectGeoWidgetRegistrationsFromWidgets(widgets, namespace) {
|
|
2345
|
+
return widgets
|
|
2346
|
+
.filter((widget) => widget['widget-geo-config'] && typeof widget['widget-data-path'] === 'string')
|
|
2347
|
+
.map((widget) => {
|
|
2348
|
+
const originalWidgetId = widget['widget-id'];
|
|
2349
|
+
const originalDataPath = widget['widget-data-path'];
|
|
2350
|
+
const geoConfig = widget['widget-geo-config'];
|
|
2351
|
+
const widgetId = namespace ? `${namespace}__${originalWidgetId}` : originalWidgetId;
|
|
2352
|
+
const parentWidgetId = geoConfig.parentWidgetId?.trim()
|
|
2353
|
+
? (namespace ? `${namespace}__${geoConfig.parentWidgetId}` : geoConfig.parentWidgetId)
|
|
2354
|
+
: null;
|
|
2355
|
+
const dataPath = namespace ? `${namespace}.${originalDataPath}` : originalDataPath;
|
|
2356
|
+
return {
|
|
2357
|
+
widgetId,
|
|
2358
|
+
parentWidgetId,
|
|
2359
|
+
level: geoConfig.level,
|
|
2360
|
+
geoConfig,
|
|
2361
|
+
dataPath,
|
|
2362
|
+
groupId: getGeoGroupId(dataPath),
|
|
2363
|
+
};
|
|
2364
|
+
});
|
|
2365
|
+
}
|
|
2366
|
+
/** Reconcile all geo groups in section values before save. */
|
|
2367
|
+
function reconcileGeoHierarchiesInValues(values, registrations, dataSources = {}) {
|
|
2368
|
+
const groupIds = [...new Set(registrations.map((entry) => entry.groupId))];
|
|
2369
|
+
let updatedValues = values;
|
|
2370
|
+
const resolveMnemonic = createGeoLevelMnemonicResolver(updatedValues, dataSources);
|
|
2371
|
+
for (const groupId of groupIds) {
|
|
2372
|
+
const groupRegistrations = registrations.filter((entry) => entry.groupId === groupId);
|
|
2373
|
+
const dataPath = groupRegistrations[0]?.dataPath;
|
|
2374
|
+
const widgetId = groupRegistrations[0]?.widgetId;
|
|
2375
|
+
if (!dataPath || !widgetId) {
|
|
2376
|
+
continue;
|
|
2377
|
+
}
|
|
2378
|
+
rebuildGeoHierarchyFromRegistrations(groupId, updatedValues, groupRegistrations, resolveMnemonic);
|
|
2379
|
+
updatedValues = applySharedGeoHierarchyToValues(updatedValues, groupId, dataPath, widgetId);
|
|
2380
|
+
}
|
|
2381
|
+
return updatedValues;
|
|
2382
|
+
}
|
|
2383
|
+
function getGeoWidgetRegistrationsInGroup(groupId) {
|
|
2384
|
+
return orderGeoWidgetRegistrations([...geoWidgetConfigRegistry.values()].filter((entry) => entry.groupId === groupId));
|
|
2385
|
+
}
|
|
2386
|
+
/** True when changedWidgetId is any upstream geo parent of widgetId (not only immediate parent). */
|
|
2387
|
+
function isUpstreamGeoAncestor(changedWidgetId, widgetId, immediateParentWidgetId) {
|
|
2388
|
+
if (changedWidgetId === widgetId) {
|
|
2389
|
+
return false;
|
|
2390
|
+
}
|
|
2391
|
+
let cursor = immediateParentWidgetId;
|
|
2392
|
+
while (cursor) {
|
|
2393
|
+
if (cursor === changedWidgetId) {
|
|
2394
|
+
return true;
|
|
2395
|
+
}
|
|
2396
|
+
cursor = geoWidgetParentRegistry.get(cursor) ?? null;
|
|
2397
|
+
}
|
|
2398
|
+
return false;
|
|
2399
|
+
}
|
|
2400
|
+
/** Group id for geo widgets sharing the same register prefix (e.g. `{registerId}`). */
|
|
2401
|
+
function getGeoGroupId(dataPath) {
|
|
2402
|
+
if (typeof dataPath === 'string' && dataPath.includes('.')) {
|
|
2403
|
+
return dataPath.split('.').slice(0, -1).join('.');
|
|
2404
|
+
}
|
|
2405
|
+
return 'default';
|
|
2406
|
+
}
|
|
2407
|
+
/**
|
|
2408
|
+
* Resolve the human-readable label for a geo level from persisted hierarchy JSON.
|
|
2409
|
+
* Used in readonly mode when API options are not loaded.
|
|
2410
|
+
*/
|
|
2411
|
+
function resolveGeoWidgetLevelLabel(values, widgetId, dataPath, geoConfig) {
|
|
2412
|
+
if (!dataPath || typeof dataPath !== 'string') {
|
|
2413
|
+
return undefined;
|
|
2414
|
+
}
|
|
2415
|
+
const stored = getWidgetValue(values, dataPath, widgetId);
|
|
2416
|
+
const hierarchy = stored?.hierarchy || stored?.geo_code_hierarchy_json?.hierarchy;
|
|
2417
|
+
if (!Array.isArray(hierarchy)) {
|
|
2418
|
+
return undefined;
|
|
2419
|
+
}
|
|
2420
|
+
const levelData = hierarchy.find((l) => l.level === geoConfig.level);
|
|
2421
|
+
if (levelData?.level_value_mnemonic) {
|
|
2422
|
+
return String(levelData.level_value_mnemonic);
|
|
2423
|
+
}
|
|
2424
|
+
return undefined;
|
|
2425
|
+
}
|
|
2426
|
+
/** All registered geo widgets that are descendants of ancestorWidgetId. */
|
|
2427
|
+
function getGeoDescendantWidgetIds(ancestorWidgetId) {
|
|
2428
|
+
const descendants = [];
|
|
2429
|
+
for (const [childId, parentId] of geoWidgetParentRegistry.entries()) {
|
|
2430
|
+
if (isUpstreamGeoAncestor(ancestorWidgetId, childId, parentId)) {
|
|
2431
|
+
descendants.push(childId);
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
return descendants;
|
|
2435
|
+
}
|
|
2436
|
+
function readStoredHierarchyLevels(values, dataPath, widgetId) {
|
|
2437
|
+
const stored = getWidgetValue(values, dataPath, widgetId);
|
|
2438
|
+
const hierarchy = stored?.hierarchy || stored?.geo_code_hierarchy_json?.hierarchy;
|
|
2439
|
+
if (!Array.isArray(hierarchy)) {
|
|
2440
|
+
return [];
|
|
2441
|
+
}
|
|
2442
|
+
return hierarchy.filter((entry) => entry?.level && entry.level_value_id);
|
|
2443
|
+
}
|
|
2444
|
+
function builderMatchesStored(groupId, storedLevels) {
|
|
2445
|
+
const builderLevels = geoHierarchyBuilder.getLevels(groupId);
|
|
2446
|
+
if (builderLevels.length !== storedLevels.length) {
|
|
2447
|
+
return false;
|
|
2448
|
+
}
|
|
2449
|
+
return storedLevels.every((stored, index) => {
|
|
2450
|
+
const built = builderLevels[index];
|
|
2451
|
+
return (built.level === stored.level &&
|
|
2452
|
+
String(built.level_value_id) === String(stored.level_value_id));
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
/** Seed in-memory builder from persisted hierarchy JSON (edit-mode rehydration). */
|
|
2456
|
+
function seedGeoHierarchyFromValues(values, dataPath, widgetId, groupId) {
|
|
2457
|
+
if (!dataPath || typeof dataPath !== 'string') {
|
|
2458
|
+
return;
|
|
2459
|
+
}
|
|
2460
|
+
const storedLevels = readStoredHierarchyLevels(values, dataPath, widgetId);
|
|
2461
|
+
if (storedLevels.length === 0) {
|
|
2462
|
+
return;
|
|
2463
|
+
}
|
|
2464
|
+
if (builderMatchesStored(groupId, storedLevels)) {
|
|
2465
|
+
return;
|
|
2466
|
+
}
|
|
2467
|
+
geoHierarchyBuilder.clear(groupId);
|
|
2468
|
+
storedLevels.forEach((entry) => {
|
|
2469
|
+
geoHierarchyBuilder.addLevel(entry.level, String(entry.level_value_id), entry.level_value_mnemonic || String(entry.level_value_id), groupId);
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2472
|
+
/** Force-clear builder for a group, then seed from Redux/schema values (e.g. after Cancel). */
|
|
2473
|
+
function resetAndSeedGeoHierarchyFromValues(values, dataPath, widgetId, groupId) {
|
|
2474
|
+
geoHierarchyBuilder.clear(groupId);
|
|
2475
|
+
seedGeoHierarchyFromValues(values, dataPath, widgetId, groupId);
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
// Define stable empty arrays to avoid selector reference issues
|
|
2479
|
+
const EMPTY_ERRORS = [];
|
|
2480
|
+
const EMPTY_DATA_SOURCE = [];
|
|
2481
|
+
const useBaseWidget = (options) => {
|
|
2482
|
+
const { config, dataSourceRequestHandler: propHandler, schemaData, onValueChange } = options;
|
|
2483
|
+
const dispatch = reactRedux.useDispatch();
|
|
2484
|
+
const context = useWidgetContext();
|
|
2485
|
+
const eventBus = useWidgetEventBus();
|
|
2486
|
+
const { translateConfig } = useWidgetTranslation();
|
|
2487
|
+
const widgetId = config['widget-id'];
|
|
2488
|
+
// Fall back to WidgetContext for dataSourceRequestHandler
|
|
2489
|
+
const dataSourceRequestHandler = propHandler || context.dataSourceRequestHandler;
|
|
2490
|
+
// Get state from Redux
|
|
2491
|
+
const values = reactRedux.useSelector((state) => state.widget.values);
|
|
2492
|
+
const errors = reactRedux.useSelector((state) => state.widget.errors[widgetId] ?? EMPTY_ERRORS);
|
|
2493
|
+
const touched = reactRedux.useSelector((state) => state.widget.touched[widgetId] || false);
|
|
2494
|
+
const loading = reactRedux.useSelector((state) => state.widget.loading[widgetId] || false);
|
|
2495
|
+
const dataSourceOptions = reactRedux.useSelector((state) => state.widget.dataSources[widgetId] ?? EMPTY_DATA_SOURCE);
|
|
2496
|
+
// Skip value handling for layout widgets (they don't store data values)
|
|
2497
|
+
// Infer layout from widget-type
|
|
2498
|
+
const isLayoutWidget = config['widget-type'] === 'layout';
|
|
2499
|
+
// Track if user has explicitly set a value to prevent default from overwriting
|
|
2500
|
+
const userHasSetValueRef = React.useRef(false);
|
|
2501
|
+
// Use ref for values to avoid stale closures in handleChange
|
|
2502
|
+
const valuesRef = React.useRef(values);
|
|
2503
|
+
const loadingRef = React.useRef(loading);
|
|
2504
|
+
const dataSourceOptionsRef = React.useRef(dataSourceOptions);
|
|
2505
|
+
React.useEffect(() => {
|
|
2506
|
+
valuesRef.current = values;
|
|
2507
|
+
loadingRef.current = loading;
|
|
2508
|
+
dataSourceOptionsRef.current = dataSourceOptions;
|
|
2509
|
+
}, [values, loading, dataSourceOptions]);
|
|
2510
|
+
// Track last dispatched value to prevent duplicate dispatches
|
|
2511
|
+
const lastDispatchedValueRef = React.useRef(null);
|
|
2512
|
+
// Helper to extract displayable value from object (especially geo hierarchy objects)
|
|
2513
|
+
const extractValueFromObject = React.useCallback((obj) => {
|
|
2514
|
+
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
|
|
2515
|
+
return obj;
|
|
2516
|
+
}
|
|
2517
|
+
// Check for geo hierarchy structure first
|
|
2518
|
+
const geoConfig = config['widget-geo-config'];
|
|
2519
|
+
if (geoConfig) {
|
|
2520
|
+
// If we have a geo hierarchy object, extract the value for this specific level
|
|
2521
|
+
const hierarchy = obj.hierarchy || obj.geo_code_hierarchy_json?.hierarchy;
|
|
2522
|
+
if (Array.isArray(hierarchy)) {
|
|
2523
|
+
const levelData = hierarchy.find((l) => l.level === geoConfig.level);
|
|
2524
|
+
if (levelData) {
|
|
2525
|
+
return levelData.level_value_id;
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
if ('geo_code_hierarchy_json' in obj || 'geo_lowest_level_value_id' in obj || 'hierarchy' in obj) {
|
|
2530
|
+
if ('geo_lowest_level_value_id' in obj) {
|
|
2531
|
+
return obj.geo_lowest_level_value_id;
|
|
2532
|
+
}
|
|
2533
|
+
if ('lowest_level_value_id' in obj) {
|
|
2534
|
+
return obj.lowest_level_value_id;
|
|
2535
|
+
}
|
|
2536
|
+
// Fallback for nested geo_code_hierarchy_json
|
|
2537
|
+
if (obj.geo_code_hierarchy_json?.lowest_level_value_id) {
|
|
2538
|
+
return obj.geo_code_hierarchy_json.lowest_level_value_id;
|
|
2539
|
+
}
|
|
2540
|
+
if (obj.geo_code_hierarchy_json?.geo_lowest_level_value_id) {
|
|
2541
|
+
return obj.geo_code_hierarchy_json.geo_lowest_level_value_id;
|
|
2542
|
+
}
|
|
2543
|
+
// If it's a geo hierarchy object but no extractable ID, return undefined to avoid rendering object
|
|
2544
|
+
return undefined;
|
|
2545
|
+
}
|
|
2546
|
+
// Try common value fields
|
|
2547
|
+
if ('value' in obj) {
|
|
2548
|
+
return obj.value;
|
|
2549
|
+
}
|
|
2550
|
+
if ('id' in obj) {
|
|
2551
|
+
return obj.id;
|
|
2552
|
+
}
|
|
2553
|
+
if ('label' in obj) {
|
|
2554
|
+
return obj.label;
|
|
2555
|
+
}
|
|
2556
|
+
if ('name' in obj) {
|
|
2557
|
+
return obj.name;
|
|
2014
2558
|
}
|
|
2015
2559
|
// If no extractable value found, return undefined to avoid rendering object as React child
|
|
2016
2560
|
// This prevents "Objects are not valid as a React child" errors
|
|
@@ -2021,6 +2565,17 @@ const useBaseWidget = (options) => {
|
|
|
2021
2565
|
if (isLayoutWidget) {
|
|
2022
2566
|
return undefined; // Layout widgets don't have values
|
|
2023
2567
|
}
|
|
2568
|
+
const geoConfig = config['widget-geo-config'];
|
|
2569
|
+
if (geoConfig) {
|
|
2570
|
+
const value = resolveGeoWidgetLevelValue(values, widgetId, config['widget-data-path'], geoConfig);
|
|
2571
|
+
if (userHasSetValueRef.current) {
|
|
2572
|
+
return value;
|
|
2573
|
+
}
|
|
2574
|
+
if (value === null) {
|
|
2575
|
+
return null;
|
|
2576
|
+
}
|
|
2577
|
+
return value !== undefined ? value : config['widget-data-default'];
|
|
2578
|
+
}
|
|
2024
2579
|
// Try to get value from widgetId first (this should have the actual selected value)
|
|
2025
2580
|
// For geo widgets with dataPath, widgetId stores the actual ID, while dataPath stores the hierarchy object
|
|
2026
2581
|
let value = values[widgetId];
|
|
@@ -2099,6 +2654,15 @@ const useBaseWidget = (options) => {
|
|
|
2099
2654
|
}
|
|
2100
2655
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2101
2656
|
}, [isLayoutWidget]); // Only run once on mount
|
|
2657
|
+
const resolveIsRequired = React.useCallback((currentValues) => {
|
|
2658
|
+
if (isLayoutWidget) {
|
|
2659
|
+
return false;
|
|
2660
|
+
}
|
|
2661
|
+
if (config['widget-readonly']) {
|
|
2662
|
+
return false;
|
|
2663
|
+
}
|
|
2664
|
+
return evaluateWidgetConditions(config['widget-data-options'], currentValues, config['widget-required'] ?? false).required;
|
|
2665
|
+
}, [config, isLayoutWidget]);
|
|
2102
2666
|
// Handle value change
|
|
2103
2667
|
// CRITICAL: Don't include 'values' in dependency array - it causes the callback to be recreated
|
|
2104
2668
|
// every time values change, which can lead to stale closures and double dispatches
|
|
@@ -2115,13 +2679,16 @@ const useBaseWidget = (options) => {
|
|
|
2115
2679
|
// This prevents data disappearance when switching to Edit mode and components
|
|
2116
2680
|
// incorrectly clear values before options load or if handler is temporarily missing.
|
|
2117
2681
|
if (newValue === '' || newValue === null || newValue === undefined) {
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2682
|
+
const allowEmptyClear = config.widget === 'register-lookup';
|
|
2683
|
+
if (!allowEmptyClear) {
|
|
2684
|
+
if (loadingRef.current) {
|
|
2685
|
+
console.warn(`[useBaseWidget] Ignoring empty value for ${widgetId} because data source is loading`);
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2688
|
+
if (config['widget-data-source']?.type === 'api' && dataSourceOptionsRef.current.length === 0) {
|
|
2689
|
+
console.warn(`[useBaseWidget] Ignoring empty value for ${widgetId} because API options are empty`);
|
|
2690
|
+
return;
|
|
2691
|
+
}
|
|
2125
2692
|
}
|
|
2126
2693
|
}
|
|
2127
2694
|
// Mark that user has set a value (unless this is the default initialization)
|
|
@@ -2139,29 +2706,26 @@ const useBaseWidget = (options) => {
|
|
|
2139
2706
|
lastDispatchedValueRef.current = newValue;
|
|
2140
2707
|
dispatch(setValue({ widgetId, value: newValue }));
|
|
2141
2708
|
}
|
|
2709
|
+
else if (config['widget-geo-config']) {
|
|
2710
|
+
// Geo widgets: hierarchy dataPath is managed by useGeoWidgetCascade
|
|
2711
|
+
getGeoDescendantWidgetIds(widgetId).forEach((descendantId) => {
|
|
2712
|
+
dispatch(setValue({ widgetId: descendantId, value: GEO_LEVEL_CLEARED }));
|
|
2713
|
+
dispatch(setDataSource({ widgetId: descendantId, data: [] }));
|
|
2714
|
+
});
|
|
2715
|
+
dispatch(setValue({ widgetId, value: newValue }));
|
|
2716
|
+
}
|
|
2142
2717
|
else {
|
|
2143
|
-
//
|
|
2144
|
-
// CRITICAL: For geo widgets, we do NOT want to overwrite the shared hierarchy dataPath
|
|
2145
|
-
// with a primitive value (the selected ID). The hierarchy object is managed by useGeoWidgetCascade.
|
|
2146
|
-
if (config['widget-geo-config']) {
|
|
2147
|
-
dispatch(setValue({ widgetId, value: newValue }));
|
|
2148
|
-
return;
|
|
2149
|
-
}
|
|
2150
|
-
// For non-geo widgets, update both widgetId and dataPath
|
|
2151
|
-
// CRITICAL: Create updated values object with newValue already set
|
|
2152
|
-
// This prevents setWidgetValue from reading stale values
|
|
2718
|
+
// Non-geo widgets: update both widgetId and dataPath
|
|
2153
2719
|
const currentValuesWithUpdate = {
|
|
2154
2720
|
...valuesRef.current,
|
|
2155
|
-
[widgetId]: newValue,
|
|
2721
|
+
[widgetId]: newValue,
|
|
2156
2722
|
};
|
|
2157
2723
|
const updatedValues = setWidgetValue(currentValuesWithUpdate, config['widget-data-path'], widgetId, newValue);
|
|
2158
|
-
// setWidgetValue returns the complete updated structure with all existing data preserved
|
|
2159
|
-
// Use setValues to update the entire state with deep merge
|
|
2160
2724
|
dispatch(setValues(updatedValues));
|
|
2161
2725
|
}
|
|
2162
2726
|
// Validate if needed
|
|
2163
2727
|
if (validate) {
|
|
2164
|
-
const validationErrors = validateWidget(newValue, config['widget-data-validation'],
|
|
2728
|
+
const validationErrors = validateWidget(newValue, config['widget-data-validation'], resolveIsRequired(currentValues));
|
|
2165
2729
|
dispatch(setError({ widgetId, errors: validationErrors }));
|
|
2166
2730
|
}
|
|
2167
2731
|
// Call custom onChange if provided
|
|
@@ -2180,13 +2744,12 @@ const useBaseWidget = (options) => {
|
|
|
2180
2744
|
timestamp: Date.now(),
|
|
2181
2745
|
});
|
|
2182
2746
|
}
|
|
2183
|
-
}, [config, widgetId, dispatch, onValueChange, eventBus] // Removed 'values' to prevent stale closures
|
|
2747
|
+
}, [config, widgetId, dispatch, onValueChange, eventBus, resolveIsRequired] // Removed 'values' to prevent stale closures
|
|
2184
2748
|
);
|
|
2185
2749
|
// Handle blur
|
|
2186
2750
|
const handleBlur = React.useCallback(() => {
|
|
2187
2751
|
dispatch(setTouched({ widgetId, touched: true }));
|
|
2188
|
-
|
|
2189
|
-
const validationErrors = validateWidget(currentValue, config['widget-data-validation'], config['widget-required']);
|
|
2752
|
+
const validationErrors = validateWidget(currentValue, config['widget-data-validation'], resolveIsRequired(valuesRef.current));
|
|
2190
2753
|
dispatch(setError({ widgetId, errors: validationErrors }));
|
|
2191
2754
|
// Publish widget:blur event
|
|
2192
2755
|
if (eventBus) {
|
|
@@ -2197,7 +2760,7 @@ const useBaseWidget = (options) => {
|
|
|
2197
2760
|
timestamp: Date.now(),
|
|
2198
2761
|
});
|
|
2199
2762
|
}
|
|
2200
|
-
}, [currentValue, config, widgetId, dispatch, eventBus]);
|
|
2763
|
+
}, [currentValue, config, widgetId, dispatch, eventBus, resolveIsRequired]);
|
|
2201
2764
|
// Get field value helper
|
|
2202
2765
|
const getFieldValue = React.useCallback((path) => {
|
|
2203
2766
|
return getWidgetValue(values, path, '');
|
|
@@ -2205,7 +2768,7 @@ const useBaseWidget = (options) => {
|
|
|
2205
2768
|
// Conditional visibility and enablement
|
|
2206
2769
|
const isVisible = React.useMemo(() => {
|
|
2207
2770
|
// Layout widgets are always visible unless explicitly hidden
|
|
2208
|
-
if (isLayoutWidget && !config['widget-data-options']
|
|
2771
|
+
if (isLayoutWidget && !hasVisibilityRules(config['widget-data-options'])) {
|
|
2209
2772
|
return true;
|
|
2210
2773
|
}
|
|
2211
2774
|
return shouldShowWidget(config['widget-data-options'], values);
|
|
@@ -2220,6 +2783,7 @@ const useBaseWidget = (options) => {
|
|
|
2220
2783
|
}
|
|
2221
2784
|
return shouldEnableWidget(config['widget-data-options'], values);
|
|
2222
2785
|
}, [config['widget-readonly'], config['widget-data-options'], values, isLayoutWidget]);
|
|
2786
|
+
const isRequired = React.useMemo(() => resolveIsRequired(values), [resolveIsRequired, values]);
|
|
2223
2787
|
// Format value for display
|
|
2224
2788
|
const formattedValue = React.useMemo(() => {
|
|
2225
2789
|
if (!config['widget-data-format']) {
|
|
@@ -2230,6 +2794,14 @@ const useBaseWidget = (options) => {
|
|
|
2230
2794
|
// Track readonly state explicitly to detect changes
|
|
2231
2795
|
// Use JSON.stringify to create a stable reference for the dependency array
|
|
2232
2796
|
const isReadonly = config['widget-readonly'] ?? false;
|
|
2797
|
+
// Leaving edit mode (Cancel): allow mirror/rehydration on next Edit
|
|
2798
|
+
React.useEffect(() => {
|
|
2799
|
+
if (config['widget-readonly']) {
|
|
2800
|
+
userHasSetValueRef.current = false;
|
|
2801
|
+
lastMirroredValueRef.current = null;
|
|
2802
|
+
lastDispatchedValueRef.current = null;
|
|
2803
|
+
}
|
|
2804
|
+
}, [config['widget-readonly']]);
|
|
2233
2805
|
const dataSource = config['widget-data-source'];
|
|
2234
2806
|
const geoConfig = config['widget-geo-config'];
|
|
2235
2807
|
// Use ref to store handler to avoid stale closures
|
|
@@ -2242,6 +2814,8 @@ const useBaseWidget = (options) => {
|
|
|
2242
2814
|
const apiService = dataSource?.type === 'api' ? dataSource.service : '';
|
|
2243
2815
|
const apiEndpoint = dataSource?.type === 'api' ? dataSource.endpoint : '';
|
|
2244
2816
|
const configKey = `${widgetId}-${isReadonly}-${dataSource?.type || 'none'}-${apiService}-${apiEndpoint}`;
|
|
2817
|
+
// Stable key so inline schemaData objects (e.g. dialog-table fields) don't retrigger loads every render
|
|
2818
|
+
const schemaDataKey = React.useMemo(() => (schemaData ? JSON.stringify(schemaData) : ''), [schemaData]);
|
|
2245
2819
|
// Extract dependency value using a granular selector to prevent unnecessary re-renders
|
|
2246
2820
|
// and infinite loops when other unrelated values in the state change.
|
|
2247
2821
|
const dependencyValue = reactRedux.useSelector((state) => {
|
|
@@ -2258,10 +2832,9 @@ const useBaseWidget = (options) => {
|
|
|
2258
2832
|
if (!dataSource) {
|
|
2259
2833
|
return;
|
|
2260
2834
|
}
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
if (dataSource.type === 'api' && isReadonly) {
|
|
2835
|
+
const loadApiInReadonly = !!geoConfig ||
|
|
2836
|
+
['select', 'radio', 'checkbox', 'multi-select'].includes(config.widget);
|
|
2837
|
+
if (dataSource.type === 'api' && isReadonly && !loadApiInReadonly) {
|
|
2265
2838
|
return;
|
|
2266
2839
|
}
|
|
2267
2840
|
// For widgets with dependencies, check if dependency value exists
|
|
@@ -2303,6 +2876,30 @@ const useBaseWidget = (options) => {
|
|
|
2303
2876
|
// React will call this effect again when the handler is ready
|
|
2304
2877
|
return;
|
|
2305
2878
|
}
|
|
2879
|
+
const resolveOptionKeys = () => {
|
|
2880
|
+
if (dataSource.type === 'static') {
|
|
2881
|
+
return { valueKey: undefined, labelKey: undefined };
|
|
2882
|
+
}
|
|
2883
|
+
if (geoConfig) {
|
|
2884
|
+
return {
|
|
2885
|
+
valueKey: dataSource.valueKey || 'level_value_id',
|
|
2886
|
+
labelKey: dataSource.labelKey || 'level_value_mnemonic',
|
|
2887
|
+
};
|
|
2888
|
+
}
|
|
2889
|
+
return { valueKey: dataSource.valueKey, labelKey: dataSource.labelKey };
|
|
2890
|
+
};
|
|
2891
|
+
if (dataSource.type === 'api') {
|
|
2892
|
+
const levelId = geoConfig?.level;
|
|
2893
|
+
const cached = getCachedApiDataSource(dataSource, valuesRef.current, levelId);
|
|
2894
|
+
if (cached) {
|
|
2895
|
+
const { valueKey, labelKey } = resolveOptionKeys();
|
|
2896
|
+
dispatch(setDataSource({
|
|
2897
|
+
widgetId,
|
|
2898
|
+
data: transformDataSourceOptions(cached, valueKey, labelKey),
|
|
2899
|
+
}));
|
|
2900
|
+
return;
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2306
2903
|
dispatch(setLoading({ widgetId, loading: true }));
|
|
2307
2904
|
let data = [];
|
|
2308
2905
|
if (dataSource.type === 'static') {
|
|
@@ -2315,31 +2912,13 @@ const useBaseWidget = (options) => {
|
|
|
2315
2912
|
dispatch(setDataSource({ widgetId, data: [] }));
|
|
2316
2913
|
return;
|
|
2317
2914
|
}
|
|
2318
|
-
// Extract level_id from widget-geo-config.level if available
|
|
2319
2915
|
const levelId = geoConfig?.level;
|
|
2320
2916
|
data = await getApiDataSource(dataSource, valuesRef.current, currentHandler, levelId);
|
|
2321
2917
|
}
|
|
2322
2918
|
else if (dataSource.type === 'schema') {
|
|
2323
2919
|
data = getSchemaDataSource(dataSource, schemaData || {});
|
|
2324
2920
|
}
|
|
2325
|
-
|
|
2326
|
-
// For geo widgets, default to level_value_id and level_value_mnemonic
|
|
2327
|
-
let valueKey;
|
|
2328
|
-
let labelKey;
|
|
2329
|
-
if (dataSource.type === 'static') {
|
|
2330
|
-
valueKey = undefined;
|
|
2331
|
-
labelKey = undefined;
|
|
2332
|
-
}
|
|
2333
|
-
else if (geoConfig) {
|
|
2334
|
-
// Geo widgets: default to level_value_id and level_value_mnemonic
|
|
2335
|
-
valueKey = dataSource.valueKey || 'level_value_id';
|
|
2336
|
-
labelKey = dataSource.labelKey || 'level_value_mnemonic';
|
|
2337
|
-
}
|
|
2338
|
-
else {
|
|
2339
|
-
// Non-geo widgets: use specified keys or undefined
|
|
2340
|
-
valueKey = dataSource.valueKey;
|
|
2341
|
-
labelKey = dataSource.labelKey;
|
|
2342
|
-
}
|
|
2921
|
+
const { valueKey, labelKey } = resolveOptionKeys();
|
|
2343
2922
|
const transformed = transformDataSourceOptions(data, valueKey, labelKey);
|
|
2344
2923
|
dispatch(setDataSource({ widgetId, data: transformed }));
|
|
2345
2924
|
}
|
|
@@ -2354,16 +2933,25 @@ const useBaseWidget = (options) => {
|
|
|
2354
2933
|
loadDataSource();
|
|
2355
2934
|
// Use configKey and dependencyValue to ensure effect runs only when relevant state changes
|
|
2356
2935
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2357
|
-
}, [configKey, dependencyValue, dataSourceRequestHandler,
|
|
2936
|
+
}, [configKey, dependencyValue, dataSourceRequestHandler, schemaDataKey, widgetId, dispatch]);
|
|
2937
|
+
const geoDisplayLabel = React.useMemo(() => {
|
|
2938
|
+
if (!geoConfig) {
|
|
2939
|
+
return undefined;
|
|
2940
|
+
}
|
|
2941
|
+
const rawLabel = resolveGeoWidgetLevelLabel(values, widgetId, config['widget-data-path'], geoConfig);
|
|
2942
|
+
return rawLabel ? translateConfig(rawLabel) : undefined;
|
|
2943
|
+
}, [values, widgetId, config, geoConfig, translateConfig]);
|
|
2358
2944
|
return {
|
|
2359
2945
|
widgetId,
|
|
2360
2946
|
value: currentValue,
|
|
2947
|
+
geoDisplayLabel,
|
|
2361
2948
|
formattedValue,
|
|
2362
2949
|
error: errors,
|
|
2363
2950
|
touched,
|
|
2364
2951
|
loading,
|
|
2365
2952
|
isVisible,
|
|
2366
2953
|
isEnabled,
|
|
2954
|
+
isRequired,
|
|
2367
2955
|
onChange: handleChange,
|
|
2368
2956
|
onBlur: handleBlur,
|
|
2369
2957
|
setError: (errors) => dispatch(setError({ widgetId, errors })),
|
|
@@ -2434,115 +3022,6 @@ const useWidgetCascade = (options) => {
|
|
|
2434
3022
|
}, [cascadeConfig, eventBus, dataSource, widgetId, dispatch]);
|
|
2435
3023
|
};
|
|
2436
3024
|
|
|
2437
|
-
/**
|
|
2438
|
-
* Geo Hierarchy Builder
|
|
2439
|
-
* Manages geo hierarchy state and builds hierarchy JSON structure
|
|
2440
|
-
*/
|
|
2441
|
-
class GeoHierarchyBuilder {
|
|
2442
|
-
constructor() {
|
|
2443
|
-
this.hierarchies = new Map();
|
|
2444
|
-
}
|
|
2445
|
-
/**
|
|
2446
|
-
* Get or create hierarchy state for a group
|
|
2447
|
-
*/
|
|
2448
|
-
getHierarchy(groupId = 'default') {
|
|
2449
|
-
if (!this.hierarchies.has(groupId)) {
|
|
2450
|
-
this.hierarchies.set(groupId, {
|
|
2451
|
-
levels: new Map(),
|
|
2452
|
-
order: [],
|
|
2453
|
-
});
|
|
2454
|
-
}
|
|
2455
|
-
return this.hierarchies.get(groupId);
|
|
2456
|
-
}
|
|
2457
|
-
/**
|
|
2458
|
-
* Add a level to the hierarchy
|
|
2459
|
-
*/
|
|
2460
|
-
addLevel(level, level_value_id, level_value_mnemonic, groupId = 'default') {
|
|
2461
|
-
const hierarchy = this.getHierarchy(groupId);
|
|
2462
|
-
// If level already exists, remove it and everything after it
|
|
2463
|
-
const existingIndex = hierarchy.order.indexOf(level);
|
|
2464
|
-
if (existingIndex >= 0) {
|
|
2465
|
-
// Remove this level and all subsequent levels
|
|
2466
|
-
const levelsToRemove = hierarchy.order.slice(existingIndex);
|
|
2467
|
-
levelsToRemove.forEach((l) => {
|
|
2468
|
-
hierarchy.levels.delete(l);
|
|
2469
|
-
hierarchy.order = hierarchy.order.filter((o) => o !== l);
|
|
2470
|
-
});
|
|
2471
|
-
}
|
|
2472
|
-
// Add new level
|
|
2473
|
-
hierarchy.levels.set(level, {
|
|
2474
|
-
level,
|
|
2475
|
-
level_value_id,
|
|
2476
|
-
level_value_mnemonic,
|
|
2477
|
-
});
|
|
2478
|
-
hierarchy.order.push(level);
|
|
2479
|
-
}
|
|
2480
|
-
/**
|
|
2481
|
-
* Remove a level and all levels below it
|
|
2482
|
-
*/
|
|
2483
|
-
removeLevelAndBelow(level, groupId = 'default') {
|
|
2484
|
-
const hierarchy = this.getHierarchy(groupId);
|
|
2485
|
-
const index = hierarchy.order.indexOf(level);
|
|
2486
|
-
if (index >= 0) {
|
|
2487
|
-
// Remove this level and all subsequent levels
|
|
2488
|
-
const levelsToRemove = hierarchy.order.slice(index);
|
|
2489
|
-
levelsToRemove.forEach((l) => {
|
|
2490
|
-
hierarchy.levels.delete(l);
|
|
2491
|
-
hierarchy.order = hierarchy.order.filter((o) => o !== l);
|
|
2492
|
-
});
|
|
2493
|
-
}
|
|
2494
|
-
}
|
|
2495
|
-
/**
|
|
2496
|
-
* Build hierarchy JSON structure
|
|
2497
|
-
*/
|
|
2498
|
-
buildHierarchyJson(groupId = 'default') {
|
|
2499
|
-
const hierarchy = this.getHierarchy(groupId);
|
|
2500
|
-
if (hierarchy.order.length === 0) {
|
|
2501
|
-
return null;
|
|
2502
|
-
}
|
|
2503
|
-
const hierarchyArray = hierarchy.order.map((level) => {
|
|
2504
|
-
const data = hierarchy.levels.get(level);
|
|
2505
|
-
return {
|
|
2506
|
-
level: data.level,
|
|
2507
|
-
level_value_id: data.level_value_id,
|
|
2508
|
-
level_value_mnemonic: data.level_value_mnemonic,
|
|
2509
|
-
};
|
|
2510
|
-
});
|
|
2511
|
-
const lowestLevel = hierarchy.order[hierarchy.order.length - 1];
|
|
2512
|
-
const lowestLevelData = hierarchy.levels.get(lowestLevel);
|
|
2513
|
-
return {
|
|
2514
|
-
geo_lowest_level_value_id: lowestLevelData.level_value_id,
|
|
2515
|
-
geo_code_hierarchy_json: {
|
|
2516
|
-
hierarchy: hierarchyArray,
|
|
2517
|
-
lowest_level_value_id: lowestLevelData.level_value_id,
|
|
2518
|
-
},
|
|
2519
|
-
};
|
|
2520
|
-
}
|
|
2521
|
-
/**
|
|
2522
|
-
* Clear hierarchy for a group
|
|
2523
|
-
*/
|
|
2524
|
-
clear(groupId = 'default') {
|
|
2525
|
-
this.hierarchies.delete(groupId);
|
|
2526
|
-
}
|
|
2527
|
-
/**
|
|
2528
|
-
* Clear all hierarchies
|
|
2529
|
-
*/
|
|
2530
|
-
clearAll() {
|
|
2531
|
-
this.hierarchies.clear();
|
|
2532
|
-
}
|
|
2533
|
-
/**
|
|
2534
|
-
* Get current levels for a group
|
|
2535
|
-
*/
|
|
2536
|
-
getLevels(groupId = 'default') {
|
|
2537
|
-
const hierarchy = this.getHierarchy(groupId);
|
|
2538
|
-
return hierarchy.order.map((level) => hierarchy.levels.get(level));
|
|
2539
|
-
}
|
|
2540
|
-
}
|
|
2541
|
-
// Singleton instance
|
|
2542
|
-
const geoHierarchyBuilder = new GeoHierarchyBuilder();
|
|
2543
|
-
|
|
2544
|
-
// Define stable empty array to avoid selector reference issues
|
|
2545
|
-
const EMPTY_DATA_SOURCE = [];
|
|
2546
3025
|
/**
|
|
2547
3026
|
* Hook for geo widget cascade functionality
|
|
2548
3027
|
* Handles geo hierarchy building and cascade behavior
|
|
@@ -2560,236 +3039,151 @@ const useGeoWidgetCascade = (options) => {
|
|
|
2560
3039
|
: 'default';
|
|
2561
3040
|
const valuesRef = React.useRef(values);
|
|
2562
3041
|
const handlerRef = React.useRef(dataSourceRequestHandler);
|
|
3042
|
+
const lastCascadePublishRef = React.useRef(undefined);
|
|
3043
|
+
const lastDirectParentValueRef = React.useRef(undefined);
|
|
2563
3044
|
// Keep refs updated
|
|
2564
3045
|
React.useEffect(() => {
|
|
2565
3046
|
valuesRef.current = values;
|
|
2566
3047
|
handlerRef.current = dataSourceRequestHandler;
|
|
2567
3048
|
}, [values, dataSourceRequestHandler]);
|
|
2568
3049
|
// Get current value and data source options
|
|
2569
|
-
const currentValue = reactRedux.useSelector((state) =>
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
// If not found in widgetId, try dataPath
|
|
2573
|
-
if (value === undefined && dataPath) {
|
|
2574
|
-
value = getWidgetValue(state.widget.values, dataPath, widgetId);
|
|
2575
|
-
}
|
|
2576
|
-
// Extract value if it's a geo hierarchy object
|
|
2577
|
-
if (value && typeof value === 'object' && !Array.isArray(value) && geoConfig) {
|
|
2578
|
-
const hierarchy = value.hierarchy || value.geo_code_hierarchy_json?.hierarchy;
|
|
2579
|
-
if (Array.isArray(hierarchy)) {
|
|
2580
|
-
const levelData = hierarchy.find((l) => l.level === geoConfig.level);
|
|
2581
|
-
if (levelData) {
|
|
2582
|
-
return levelData.level_value_id;
|
|
2583
|
-
}
|
|
2584
|
-
}
|
|
2585
|
-
// Extended fallbacks (matching useBaseWidget)
|
|
2586
|
-
if ('geo_lowest_level_value_id' in value) {
|
|
2587
|
-
return value.geo_lowest_level_value_id;
|
|
2588
|
-
}
|
|
2589
|
-
if ('lowest_level_value_id' in value) {
|
|
2590
|
-
return value.lowest_level_value_id;
|
|
2591
|
-
}
|
|
2592
|
-
if (value.geo_code_hierarchy_json?.lowest_level_value_id) {
|
|
2593
|
-
return value.geo_code_hierarchy_json.lowest_level_value_id;
|
|
2594
|
-
}
|
|
2595
|
-
if (value.geo_code_hierarchy_json?.geo_lowest_level_value_id) {
|
|
2596
|
-
return value.geo_code_hierarchy_json.geo_lowest_level_value_id;
|
|
2597
|
-
}
|
|
2598
|
-
}
|
|
2599
|
-
return value;
|
|
2600
|
-
});
|
|
3050
|
+
const currentValue = reactRedux.useSelector((state) => geoConfig
|
|
3051
|
+
? resolveGeoWidgetLevelValue(state.widget.values, widgetId, dataPath, geoConfig)
|
|
3052
|
+
: state.widget.values[widgetId]);
|
|
2601
3053
|
// Memoize selector to avoid returning new array reference
|
|
2602
|
-
const
|
|
3054
|
+
const allDataSources = reactRedux.useSelector((state) => state.widget.dataSources);
|
|
3055
|
+
// Register parent chain for upstream-ancestor detection (grandparent → grandchild reset)
|
|
3056
|
+
React.useEffect(() => {
|
|
3057
|
+
if (!geoConfig || typeof dataPath !== 'string') {
|
|
3058
|
+
return;
|
|
3059
|
+
}
|
|
3060
|
+
registerGeoWidget(widgetId, geoConfig, dataPath);
|
|
3061
|
+
return () => unregisterGeoWidget(widgetId);
|
|
3062
|
+
}, [widgetId, geoConfig, dataPath]);
|
|
3063
|
+
// Keep in-memory builder in sync with persisted hierarchy (reload, cancel→re-edit, etc.)
|
|
3064
|
+
React.useEffect(() => {
|
|
3065
|
+
if (!geoConfig || typeof dataPath !== 'string') {
|
|
3066
|
+
return;
|
|
3067
|
+
}
|
|
3068
|
+
seedGeoHierarchyFromValues(values, dataPath, widgetId, groupId);
|
|
3069
|
+
}, [geoConfig, dataPath, widgetId, groupId, values]);
|
|
2603
3070
|
React.useEffect(() => {
|
|
2604
3071
|
if (!geoConfig || !eventBus || !dataSource || dataSource.type !== 'api') {
|
|
2605
3072
|
return;
|
|
2606
3073
|
}
|
|
2607
|
-
const { level, isLastLevel, parentWidgetId } = geoConfig;
|
|
2608
|
-
|
|
2609
|
-
if (parentWidgetId) {
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
3074
|
+
const { level, isLastLevel, parentWidgetId: rawParentWidgetId } = geoConfig;
|
|
3075
|
+
const parentWidgetId = rawParentWidgetId || null;
|
|
3076
|
+
if (!parentWidgetId) {
|
|
3077
|
+
return;
|
|
3078
|
+
}
|
|
3079
|
+
const clearThisLevel = (baseValues) => {
|
|
3080
|
+
geoHierarchyBuilder.removeLevelAndBelow(level, groupId);
|
|
3081
|
+
if (dataPath) {
|
|
3082
|
+
dispatch(setValues(applySharedGeoHierarchyToValues(baseValues, groupId, dataPath, widgetId)));
|
|
3083
|
+
}
|
|
3084
|
+
dispatch(setValue({ widgetId, value: GEO_LEVEL_CLEARED }));
|
|
3085
|
+
if (!isLastLevel) {
|
|
3086
|
+
eventBus.publish({
|
|
3087
|
+
type: 'widget:change',
|
|
3088
|
+
widgetId,
|
|
3089
|
+
value: GEO_LEVEL_CLEARED,
|
|
3090
|
+
timestamp: Date.now(),
|
|
3091
|
+
});
|
|
3092
|
+
}
|
|
3093
|
+
dispatch(setDataSource({ widgetId, data: [] }));
|
|
3094
|
+
};
|
|
3095
|
+
const handleParentChange = async (event) => {
|
|
3096
|
+
const isDirectParent = event.widgetId === parentWidgetId;
|
|
3097
|
+
const isAncestor = isUpstreamGeoAncestor(event.widgetId, widgetId, parentWidgetId);
|
|
3098
|
+
if (!isDirectParent && !isAncestor) {
|
|
3099
|
+
return;
|
|
3100
|
+
}
|
|
3101
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
3102
|
+
const currentValues = valuesRef.current;
|
|
3103
|
+
const currentHandler = handlerRef.current;
|
|
3104
|
+
// Grandparent (or higher) changed: clear this level; only immediate parent drives reload
|
|
3105
|
+
if (isAncestor && !isDirectParent) {
|
|
3106
|
+
clearThisLevel(currentValues);
|
|
3107
|
+
return;
|
|
3108
|
+
}
|
|
3109
|
+
const parentCleared = event.value === undefined ||
|
|
3110
|
+
event.value === null ||
|
|
3111
|
+
event.value === '' ||
|
|
3112
|
+
event.value === GEO_LEVEL_CLEARED;
|
|
3113
|
+
const isFirstParentEvent = lastDirectParentValueRef.current === undefined;
|
|
3114
|
+
const parentValueChanged = !isFirstParentEvent &&
|
|
3115
|
+
lastDirectParentValueRef.current !== event.value;
|
|
3116
|
+
lastDirectParentValueRef.current = event.value;
|
|
3117
|
+
if (!parentCleared && !parentValueChanged && !isFirstParentEvent) {
|
|
3118
|
+
return;
|
|
3119
|
+
}
|
|
3120
|
+
let parentValue = event.value;
|
|
3121
|
+
if (!parentCleared && (parentValue === undefined || parentValue === null)) {
|
|
3122
|
+
parentValue = currentValues[parentWidgetId];
|
|
3123
|
+
if (parentValue === undefined && dataSource.dependsOn) {
|
|
3124
|
+
parentValue = getWidgetValue(currentValues, dataSource.dependsOn, '');
|
|
2658
3125
|
}
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
const valueKey = dataSource.valueKey || 'level_value_id';
|
|
2674
|
-
const labelKey = dataSource.labelKey || 'level_value_mnemonic';
|
|
2675
|
-
const transformed = transformDataSourceOptions(data, valueKey, labelKey);
|
|
2676
|
-
dispatch(setDataSource({ widgetId, data: transformed }));
|
|
2677
|
-
}
|
|
2678
|
-
catch (error) {
|
|
2679
|
-
console.error('Error reloading geo data source:', error);
|
|
2680
|
-
dispatch(setDataSource({ widgetId, data: [] }));
|
|
2681
|
-
}
|
|
3126
|
+
}
|
|
3127
|
+
clearThisLevel(currentValues);
|
|
3128
|
+
if (currentHandler && parentValue !== null && parentValue !== undefined && parentValue !== '') {
|
|
3129
|
+
try {
|
|
3130
|
+
const updatedValues = {
|
|
3131
|
+
...currentValues,
|
|
3132
|
+
[parentWidgetId]: parentValue,
|
|
3133
|
+
};
|
|
3134
|
+
const levelId = geoConfig.level;
|
|
3135
|
+
const data = await getApiDataSource(dataSource, updatedValues, currentHandler, levelId);
|
|
3136
|
+
const valueKey = dataSource.valueKey || 'level_value_id';
|
|
3137
|
+
const labelKey = dataSource.labelKey || 'level_value_mnemonic';
|
|
3138
|
+
const transformed = transformDataSourceOptions(data, valueKey, labelKey);
|
|
3139
|
+
dispatch(setDataSource({ widgetId, data: transformed }));
|
|
2682
3140
|
}
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
geoHierarchyBuilder.removeLevelAndBelow(level, groupId);
|
|
3141
|
+
catch (error) {
|
|
3142
|
+
console.error('Error reloading geo data source:', error);
|
|
2686
3143
|
dispatch(setDataSource({ widgetId, data: [] }));
|
|
2687
3144
|
}
|
|
2688
|
-
}
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
}
|
|
2694
|
-
}, [geoConfig, eventBus, dataSource, widgetId, dataPath, dispatch]);
|
|
3145
|
+
}
|
|
3146
|
+
};
|
|
3147
|
+
const unsubscribe = eventBus.subscribe('widget:change', handleParentChange);
|
|
3148
|
+
return () => {
|
|
3149
|
+
unsubscribe();
|
|
3150
|
+
};
|
|
3151
|
+
}, [geoConfig, eventBus, dataSource, widgetId, dataPath, dispatch, groupId]);
|
|
2695
3152
|
// Handle value changes to build hierarchy
|
|
2696
3153
|
React.useEffect(() => {
|
|
2697
|
-
if (!geoConfig) {
|
|
3154
|
+
if (!geoConfig || typeof dataPath !== 'string') {
|
|
2698
3155
|
return;
|
|
2699
3156
|
}
|
|
2700
|
-
|
|
3157
|
+
const { level, isLastLevel } = geoConfig;
|
|
3158
|
+
const groupRegistrations = getGeoWidgetRegistrationsInGroup(groupId);
|
|
3159
|
+
const applyGroupRebuild = () => {
|
|
3160
|
+
const resolveMnemonic = createGeoLevelMnemonicResolver(valuesRef.current, allDataSources);
|
|
3161
|
+
rebuildGeoHierarchyFromRegistrations(groupId, valuesRef.current, groupRegistrations, resolveMnemonic);
|
|
3162
|
+
dispatch(setValues(applySharedGeoHierarchyToValues(valuesRef.current, groupId, dataPath, widgetId)));
|
|
3163
|
+
};
|
|
2701
3164
|
// ONLY clear hierarchy if the value is explicitly null or empty string (user action)
|
|
2702
3165
|
if (currentValue === null || currentValue === '') {
|
|
2703
|
-
const { level } = geoConfig;
|
|
2704
3166
|
geoHierarchyBuilder.removeLevelAndBelow(level, groupId);
|
|
2705
|
-
|
|
2706
|
-
if (
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
}
|
|
2715
|
-
else {
|
|
2716
|
-
finalUpdatedValues = setWidgetValue(finalUpdatedValues, dataPath, widgetId, hierarchyJson?.geo_code_hierarchy_json);
|
|
2717
|
-
}
|
|
2718
|
-
dispatch(setValues(finalUpdatedValues));
|
|
3167
|
+
applyGroupRebuild();
|
|
3168
|
+
if (!isLastLevel && eventBus && lastCascadePublishRef.current !== GEO_LEVEL_CLEARED) {
|
|
3169
|
+
lastCascadePublishRef.current = GEO_LEVEL_CLEARED;
|
|
3170
|
+
eventBus.publish({
|
|
3171
|
+
type: 'widget:change',
|
|
3172
|
+
widgetId,
|
|
3173
|
+
value: GEO_LEVEL_CLEARED,
|
|
3174
|
+
timestamp: Date.now(),
|
|
3175
|
+
});
|
|
2719
3176
|
}
|
|
2720
3177
|
return;
|
|
2721
3178
|
}
|
|
2722
3179
|
if (currentValue === undefined) {
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
// Check if hierarchy is already built to prevent endless loops
|
|
2727
|
-
if (dataPath) {
|
|
2728
|
-
const currentHierarchy = getWidgetValue(valuesRef.current, dataPath, widgetId);
|
|
2729
|
-
// If hierarchy JSON is already set and matches current value, skip rebuilding
|
|
2730
|
-
if (currentHierarchy && typeof currentHierarchy === 'object') {
|
|
2731
|
-
// Check if this specific level's value matches the hierarchy
|
|
2732
|
-
const hierarchyArray = currentHierarchy.hierarchy || currentHierarchy.geo_code_hierarchy_json?.hierarchy;
|
|
2733
|
-
if (Array.isArray(hierarchyArray)) {
|
|
2734
|
-
const currentLevelValue = typeof currentValue === 'object'
|
|
2735
|
-
? (currentValue.level_value_id || currentValue.id || currentValue.value)
|
|
2736
|
-
: currentValue;
|
|
2737
|
-
const levelData = hierarchyArray.find((l) => l.level === geoConfig.level);
|
|
2738
|
-
// If this level is already correctly represented in the hierarchy, skip rebuilding
|
|
2739
|
-
// String conversion ensures comparison works for mixed types
|
|
2740
|
-
if (levelData && String(levelData.level_value_id) === String(currentLevelValue)) {
|
|
2741
|
-
return;
|
|
2742
|
-
}
|
|
2743
|
-
}
|
|
2744
|
-
}
|
|
2745
|
-
}
|
|
2746
|
-
// Extract level_value_id and level_value_mnemonic from current value
|
|
2747
|
-
// The value could be the ID itself or an object with id/name
|
|
2748
|
-
let level_value_id;
|
|
2749
|
-
let level_value_mnemonic;
|
|
2750
|
-
if (typeof currentValue === 'string' || typeof currentValue === 'number') {
|
|
2751
|
-
// Value is just the ID, need to find mnemonic from data source
|
|
2752
|
-
level_value_id = String(currentValue);
|
|
2753
|
-
// Try to get mnemonic from data source options
|
|
2754
|
-
const option = dataSourceOptions.find((opt) => opt.value === currentValue);
|
|
2755
|
-
level_value_mnemonic = option?.label || String(currentValue);
|
|
2756
|
-
}
|
|
2757
|
-
else if (currentValue && typeof currentValue === 'object') {
|
|
2758
|
-
level_value_id = currentValue.level_value_id || currentValue.id || currentValue.value;
|
|
2759
|
-
level_value_mnemonic = currentValue.level_value_mnemonic || currentValue.name || currentValue.label;
|
|
2760
|
-
}
|
|
2761
|
-
else {
|
|
2762
|
-
return;
|
|
2763
|
-
}
|
|
2764
|
-
// When a widget's own value changes, remove this level and all below from hierarchy first
|
|
2765
|
-
geoHierarchyBuilder.removeLevelAndBelow(level, groupId);
|
|
2766
|
-
// Add level to hierarchy
|
|
2767
|
-
geoHierarchyBuilder.addLevel(level, level_value_id, level_value_mnemonic, groupId);
|
|
2768
|
-
// Build and store hierarchy JSON on every change
|
|
2769
|
-
if (dataPath) {
|
|
2770
|
-
const hierarchyJson = geoHierarchyBuilder.buildHierarchyJson(groupId);
|
|
2771
|
-
if (hierarchyJson) {
|
|
2772
|
-
// Fix: Avoid double nesting of geo_code_hierarchy_json
|
|
2773
|
-
// If dataPath ends with .geo_code_hierarchy_json, we want to save the content directly to it
|
|
2774
|
-
// and save the lowest level ID as a sibling
|
|
2775
|
-
let finalUpdatedValues = valuesRef.current;
|
|
2776
|
-
if (typeof dataPath === 'string' && dataPath.endsWith('.geo_code_hierarchy_json')) {
|
|
2777
|
-
const prefix = dataPath.substring(0, dataPath.lastIndexOf('.'));
|
|
2778
|
-
// Save hierarchy JSON content directly to dataPath (avoiding double nesting)
|
|
2779
|
-
finalUpdatedValues = setWidgetValue(finalUpdatedValues, dataPath, widgetId, hierarchyJson.geo_code_hierarchy_json);
|
|
2780
|
-
// Save lowest level ID as sibling
|
|
2781
|
-
finalUpdatedValues = setWidgetValue(finalUpdatedValues, `${prefix}.geo_lowest_level_value_id`, widgetId, hierarchyJson.geo_lowest_level_value_id);
|
|
2782
|
-
}
|
|
2783
|
-
else {
|
|
2784
|
-
// Fallback if path doesn't follow the naming convention
|
|
2785
|
-
finalUpdatedValues = setWidgetValue(finalUpdatedValues, dataPath, widgetId, hierarchyJson.geo_code_hierarchy_json);
|
|
2786
|
-
}
|
|
2787
|
-
// CRITICAL: Use setValues for deep merge instead of replacing root keys with setValue
|
|
2788
|
-
// setWidgetValue returns the complete updated state object with all keys preserved
|
|
2789
|
-
dispatch(setValues(finalUpdatedValues));
|
|
3180
|
+
const hasOwnValue = Object.prototype.hasOwnProperty.call(valuesRef.current, widgetId);
|
|
3181
|
+
if (!hasOwnValue) {
|
|
3182
|
+
return;
|
|
2790
3183
|
}
|
|
2791
3184
|
}
|
|
2792
|
-
|
|
3185
|
+
applyGroupRebuild();
|
|
3186
|
+
}, [geoConfig, currentValue, widgetId, dataPath, dispatch, allDataSources, groupId, eventBus]);
|
|
2793
3187
|
};
|
|
2794
3188
|
|
|
2795
3189
|
class WidgetRegistry {
|
|
@@ -2914,98 +3308,6 @@ const WidgetRenderer = ({ config, dataSourceRequestHandler: propDataSourceReques
|
|
|
2914
3308
|
return (jsxRuntimeExports.jsx("div", { className: "widget-container", "data-widget-id": widgetContext.widgetId, style: { marginBottom: 0 }, children: widgetRegistry.render(config, widgetContext, defaultComponent) }));
|
|
2915
3309
|
};
|
|
2916
3310
|
|
|
2917
|
-
/**
|
|
2918
|
-
* Custom hook for widget translations
|
|
2919
|
-
* Provides translation function with widget-specific namespace and fallback support
|
|
2920
|
-
*/
|
|
2921
|
-
const useWidgetTranslation = () => {
|
|
2922
|
-
const { translate: translateFunction } = useWidgetContext();
|
|
2923
|
-
/**
|
|
2924
|
-
* Translate a key with flexible namespace support
|
|
2925
|
-
* Supports translation keys in various formats and direct strings
|
|
2926
|
-
*
|
|
2927
|
-
* Translation key formats supported:
|
|
2928
|
-
* - "widgets:common.addItem" - Namespaced key (for widget-specific translations)
|
|
2929
|
-
* - "Name" - Direct string (will be looked up in flat translation structure)
|
|
2930
|
-
* - "sections.personalDetails" - Nested key (for backward compatibility)
|
|
2931
|
-
*
|
|
2932
|
-
* With flat translation structure, direct strings like "Name" are automatically
|
|
2933
|
-
* translated by looking them up in the translation resources.
|
|
2934
|
-
*
|
|
2935
|
-
* @param keyOrString - Translation key (e.g., "widgets:common.addItem") or direct string (e.g., "Name")
|
|
2936
|
-
* @param options - Translation options (interpolation values, default value, etc.)
|
|
2937
|
-
* @returns Translated string or original string if translation not found
|
|
2938
|
-
*/
|
|
2939
|
-
const translate = (keyOrString, options) => {
|
|
2940
|
-
if (!keyOrString) {
|
|
2941
|
-
return options?.defaultValue || '';
|
|
2942
|
-
}
|
|
2943
|
-
// Use the provided translation function or fallback to the key
|
|
2944
|
-
if (translateFunction) {
|
|
2945
|
-
return translateFunction(keyOrString, options) || options?.defaultValue || keyOrString;
|
|
2946
|
-
}
|
|
2947
|
-
// Fallback to key if no translation function available
|
|
2948
|
-
return options?.defaultValue || keyOrString;
|
|
2949
|
-
};
|
|
2950
|
-
/**
|
|
2951
|
-
* Translate widget config property
|
|
2952
|
-
* Attempts to translate the value, but if translation is not found,
|
|
2953
|
-
* returns the original value as-is (graceful fallback)
|
|
2954
|
-
*
|
|
2955
|
-
* This function will:
|
|
2956
|
-
* - Try to translate any string value
|
|
2957
|
-
* - If translation exists, use the translated value
|
|
2958
|
-
* - If translation doesn't exist (returns same value or throws), use original value
|
|
2959
|
-
* - This prevents errors when literal strings like "XXXX-XXXX-XXXX" are used
|
|
2960
|
-
*/
|
|
2961
|
-
const translateConfig = (value, fallback) => {
|
|
2962
|
-
if (!value) {
|
|
2963
|
-
return fallback || '';
|
|
2964
|
-
}
|
|
2965
|
-
// Try to translate the value
|
|
2966
|
-
if (translateFunction) {
|
|
2967
|
-
try {
|
|
2968
|
-
// Pass defaultValue to ensure we get the original value if translation fails
|
|
2969
|
-
const translated = translateFunction(value, { defaultValue: value });
|
|
2970
|
-
// If translation returns empty, null, undefined, or the exact same value,
|
|
2971
|
-
// it means no translation was found - return the original value
|
|
2972
|
-
if (!translated || translated === value) {
|
|
2973
|
-
return value;
|
|
2974
|
-
}
|
|
2975
|
-
// Translation found, return it
|
|
2976
|
-
return translated;
|
|
2977
|
-
}
|
|
2978
|
-
catch (error) {
|
|
2979
|
-
// If translation throws an error (e.g., missing key warning), return original value
|
|
2980
|
-
return value;
|
|
2981
|
-
}
|
|
2982
|
-
}
|
|
2983
|
-
// No translation function available, return value as-is
|
|
2984
|
-
return value;
|
|
2985
|
-
};
|
|
2986
|
-
// No need of this getLanguage and changeLanguage functions
|
|
2987
|
-
/**
|
|
2988
|
-
* Get current language
|
|
2989
|
-
*/
|
|
2990
|
-
// const getLanguage = (): string => {
|
|
2991
|
-
// return i18n.language || 'en';
|
|
2992
|
-
// };
|
|
2993
|
-
/**
|
|
2994
|
-
* Change language
|
|
2995
|
-
*/
|
|
2996
|
-
// const changeLanguage = (lng: string): Promise<void> => {
|
|
2997
|
-
// return i18n.changeLanguage(lng).then(() => undefined);
|
|
2998
|
-
// };
|
|
2999
|
-
return {
|
|
3000
|
-
t: translate,
|
|
3001
|
-
translate,
|
|
3002
|
-
translateConfig,
|
|
3003
|
-
// getLanguage,
|
|
3004
|
-
// changeLanguage,
|
|
3005
|
-
// i18n: null,
|
|
3006
|
-
};
|
|
3007
|
-
};
|
|
3008
|
-
|
|
3009
3311
|
/**
|
|
3010
3312
|
* Renders a panel with its nested panels or widgets
|
|
3011
3313
|
*
|
|
@@ -3124,6 +3426,16 @@ const PanelRenderer = ({ panel, dataSourceRequestHandler, schemaData, onValueCha
|
|
|
3124
3426
|
return (jsxRuntimeExports.jsx("div", { className: `panel panel-${orientation}`, "data-panel-id": panel['panel-id'], style: orientation === 'horizontal' ? { width: '100%' } : {}, children: content }));
|
|
3125
3427
|
};
|
|
3126
3428
|
|
|
3429
|
+
/**
|
|
3430
|
+
* Field label: long text truncates with ellipsis; required asterisk always stays visible.
|
|
3431
|
+
*/
|
|
3432
|
+
const WidgetFieldLabel = ({ label, required = false, className = '', title, }) => {
|
|
3433
|
+
const { translateConfig } = useWidgetTranslation();
|
|
3434
|
+
const translatedLabel = translateConfig(label);
|
|
3435
|
+
const tooltip = title !== undefined ? translateConfig(title) : translatedLabel;
|
|
3436
|
+
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: "*" })] }));
|
|
3437
|
+
};
|
|
3438
|
+
|
|
3127
3439
|
/**
|
|
3128
3440
|
* Utility functions for file preview functionality
|
|
3129
3441
|
*/
|
|
@@ -3167,7 +3479,11 @@ const canPreviewInWeb = (file) => {
|
|
|
3167
3479
|
return previewableExtensions.includes(extension.toLowerCase());
|
|
3168
3480
|
};
|
|
3169
3481
|
|
|
3170
|
-
var img$f = "data:image/png;base64,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";
|
|
3482
|
+
var img$h = "data:image/png;base64,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";
|
|
3483
|
+
|
|
3484
|
+
var img$g = "data:image/png;base64,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";
|
|
3485
|
+
|
|
3486
|
+
var img$f = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAACAKADAAQAAAABAAACAAAAAAAL+LWFAAAkvUlEQVR4Ae3d344sVb0H8I1GuMFofAD5p95443ucGwHJifgg8g7wDpoYJecCvOGEIHAFr2ACMeobqES4OCGBsxZ7L2emd/f8urtqVa0/n04qvWemu2rVp35rfX/Tu2fmwQM3AgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBA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AfCGPyVNIBYYqQnIa5cbAQILBF5Kz+29AfCd/4IC8NTpBEZpAl6c7so5YQIrC+T3aXyStl6bAN/5r1wQdjeFQO9NQF6zvMdsilJ1krUFcifdYwPgO//alWH/Iwv03AT8fOQL49wIbC3wbjpgT02A8N+6QhxvRIEem4D3R7wQzonAngLPp4Pnvw/QQxPgZf89K8WxRxPoqQnIa1Req9wIEFhZ4JW0v9YbAN/5r3zR7Y5AEuilCXjV1SJAoJ7A62nXrTYBvvOvd93tmUDrTUBem9wIEKgo8ETa92/S1loT4Dv/ihfdrgk8Emi1Cfh9Gp93/StTAhsI5EXgd2lrpQnIY8ljciNAoL6A+V/f2BEINC2QXwl4I217NwH5Zb88FjcCBLYTMP+3s3YkAs0K5N8U+I+0bd0IfJaO+ctmVQyMwBwC5v8c19lZEjgp8EL6Sv7Z262agPxXCv2oz8nL4QsENhUw/zfldjACbQrk7wY+TVutRiD/ek+/37vNa29UBMx/NUBgcoH8TtyX0/Zx2tb4U8J5Hx+lLS8u3uWbENwINCxg/jd8cQyNwJYCz6aDvZa2D9P2RdrOfWUgP/aDtP06bc+kzY0Agf4Enk1DNv/7u26bjdi7tzej3v1AT6YR/DRtP0rbc2n7QdqeTlu+fZ62/EbCv6ftL2n7c9q+TJsbAQJjCJj/Y1xHZ0GAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECgssD/A1hw9f23AMmGAAAAAElFTkSuQmCC";
|
|
3171
3487
|
|
|
3172
3488
|
var img$e = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAkCAYAAACe0YppAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAHqADAAQAAAABAAAAJAAAAAAh0JciAAACrUlEQVRYCcWXPW8TQRCG31kbpNBChGiiRJQ0tFDggPgBtKGhJgEJUGxDFCmJUBKwDUoV2jRxS2NEhZCgAPELKBAhIPFRpKIwUXIe3vFHFJzbs2zWupHWu7e7M8/OenbuVkDReUzoLjbZvGDP/ysiWJQSlpLsiN7HOBp4rco6oBC+Svicz2QWERYULSjh605Q3dvHnk/B138sg1EV1DrjtPVAC9j1eS6NPD5x8lko3rgKch3FfmvbOY2w1a3n89xxlWdsMuvP3UqDPhO2xPLF9NueL3Tbct0dIZ4jJdTh8iH4Irf9H/hQwLZ4edSCs7ltz/Tc4CvWNhka2IwbXDKYZLMDt4Brep61CUFE8Jt26iwj9KbcKGDW7GrDfnGi+csf8zwqoh7MY1nFjhLYBpziKTl3UIDRDthqp5gJ5zENZsrMCXlscQE3DoMO2sJ8wURFr08GBRtAythgZeWIRAVsSDtZBdvqI5QeHamBE7c6yqPKJND/G0sZvIqXmQpmfI57wVrEGI/CFINhIOGCp3lml/mS+B5nwAuWx/iqs5hnDr8ap9ijT7noF+5JPNR0vWAblAqWWVkJLqkFV2rgxK1mVG8ySC4Oss/NqC5j2qfrBdf5RSERrg8c1cBNvYOHsoYfcXAveISvNHpcZIrLxSkm9rXO8SvngZquF2yDTPolVlaCS2rBlRo4cav56fscgvNJ+8wYqDEt3k6aEzfmBVtU82P/Wq+oZiq/pfdQkqf4Fgfw9XnBFtXM1XeZq73nmGPK68871yfUFuMF2yBz9RorK8ElteDiHY2blYI4/ks7xuUCxofNJ2OizfiZZVTW2GGfKJNMkc9cFlUu5k/QRQiOYx9TfHFcMrtkvhWdw2l2fmDnWFCY39i2XWucrOAXb1A5ruKjf26wkfeEXrE71V+iV9IhdJQEvwAAAABJRU5ErkJggg==";
|
|
3173
3489
|
|
|
@@ -3469,7 +3785,7 @@ const deserializeValue = (value) => {
|
|
|
3469
3785
|
};
|
|
3470
3786
|
|
|
3471
3787
|
const FileInputWidget = ({ config }) => {
|
|
3472
|
-
const { value, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
3788
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
3473
3789
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
3474
3790
|
const accept = widgetConfig['widget-data-options']?.accept;
|
|
3475
3791
|
const multiple = widgetConfig['widget-data-options']?.multiple || false;
|
|
@@ -3709,7 +4025,7 @@ const FileInputWidget = ({ config }) => {
|
|
|
3709
4025
|
setPreviewFile(null);
|
|
3710
4026
|
} })] }));
|
|
3711
4027
|
}
|
|
3712
|
-
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px]", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
4028
|
+
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
|
|
3713
4029
|
? 'opacity-50 cursor-not-allowed'
|
|
3714
4030
|
: ''}`, style: {
|
|
3715
4031
|
width: '100%',
|
|
@@ -3770,6 +4086,13 @@ const namespaceWidgetConfig = (widgetConfig, namespace) => {
|
|
|
3770
4086
|
if (namespaced['widget-data-path']) {
|
|
3771
4087
|
namespaced['widget-data-path'] = namespaceDataPath(namespaced['widget-data-path'], namespace);
|
|
3772
4088
|
}
|
|
4089
|
+
// Namespace geo parent references so cascade events match namespaced widget-id
|
|
4090
|
+
if (namespaced['widget-geo-config']?.parentWidgetId) {
|
|
4091
|
+
namespaced['widget-geo-config'] = {
|
|
4092
|
+
...namespaced['widget-geo-config'],
|
|
4093
|
+
parentWidgetId: `${namespace}__${namespaced['widget-geo-config'].parentWidgetId}`,
|
|
4094
|
+
};
|
|
4095
|
+
}
|
|
3773
4096
|
// Recursively namespace nested widgets (for layout widgets)
|
|
3774
4097
|
if (namespaced.widgets && Array.isArray(namespaced.widgets)) {
|
|
3775
4098
|
namespaced.widgets = namespaced.widgets.map((widget) => namespaceWidgetConfig(widget, namespace));
|
|
@@ -3974,6 +4297,9 @@ const sectionValidate = (section, currentSchemaData, dispatch, skipRequired = fa
|
|
|
3974
4297
|
const isVisible = shouldShowWidget(widget['widget-data-options'], currentSchemaData);
|
|
3975
4298
|
if (!isVisible)
|
|
3976
4299
|
continue;
|
|
4300
|
+
const isEnabled = shouldEnableWidget(widget['widget-data-options'], currentSchemaData);
|
|
4301
|
+
if (!isEnabled)
|
|
4302
|
+
continue;
|
|
3977
4303
|
const widgetId = widget['widget-id'];
|
|
3978
4304
|
if (isTableLikeWidget(widget)) {
|
|
3979
4305
|
const tableValid = validateTableLikeWidget(widget, currentSchemaData, dispatch, skipRequired);
|
|
@@ -3983,7 +4309,8 @@ const sectionValidate = (section, currentSchemaData, dispatch, skipRequired = fa
|
|
|
3983
4309
|
continue;
|
|
3984
4310
|
}
|
|
3985
4311
|
const value = getWidgetValue(currentSchemaData, widget['widget-data-path'], widgetId);
|
|
3986
|
-
const
|
|
4312
|
+
const isRequired = shouldRequireWidget(widget['widget-data-options'], currentSchemaData, widget['widget-required'] ?? false);
|
|
4313
|
+
const errors = validateWidget(value, widget['widget-data-validation'], isRequired, skipRequired);
|
|
3987
4314
|
if (errors.length > 0) {
|
|
3988
4315
|
isValid = false;
|
|
3989
4316
|
dispatch(setTouched({ widgetId, touched: true }));
|
|
@@ -4016,6 +4343,108 @@ const sectionValidate = (section, currentSchemaData, dispatch, skipRequired = fa
|
|
|
4016
4343
|
return isValid;
|
|
4017
4344
|
};
|
|
4018
4345
|
|
|
4346
|
+
const cloneValue = (value) => {
|
|
4347
|
+
if (value === undefined) {
|
|
4348
|
+
return undefined;
|
|
4349
|
+
}
|
|
4350
|
+
try {
|
|
4351
|
+
return structuredClone(value);
|
|
4352
|
+
}
|
|
4353
|
+
catch {
|
|
4354
|
+
return JSON.parse(JSON.stringify(value));
|
|
4355
|
+
}
|
|
4356
|
+
};
|
|
4357
|
+
const resolveNamespacedWidgetId = (widgetId, namespace) => namespace ? `${namespace}__${widgetId}` : widgetId;
|
|
4358
|
+
const resolveStoreDataPath = (dataPath, namespace) => {
|
|
4359
|
+
if (!dataPath) {
|
|
4360
|
+
return dataPath;
|
|
4361
|
+
}
|
|
4362
|
+
if (!namespace) {
|
|
4363
|
+
return dataPath;
|
|
4364
|
+
}
|
|
4365
|
+
if (typeof dataPath === 'string') {
|
|
4366
|
+
return `${namespace}.${dataPath}`;
|
|
4367
|
+
}
|
|
4368
|
+
return Object.fromEntries(Object.entries(dataPath).map(([key, path]) => [key, `${namespace}.${path}`]));
|
|
4369
|
+
};
|
|
4370
|
+
/**
|
|
4371
|
+
* Capture Redux widget values for a section at edit entry.
|
|
4372
|
+
* Used to restore exact pre-edit state on Cancel (schemaData may be stale or shared with Redux).
|
|
4373
|
+
*/
|
|
4374
|
+
function captureSectionEditSnapshot(values, section, options) {
|
|
4375
|
+
const { namespace, sectionId, supportingDocuments = [] } = options ?? {};
|
|
4376
|
+
const dataPaths = [];
|
|
4377
|
+
const processedPaths = new Set();
|
|
4378
|
+
const widgetIds = {};
|
|
4379
|
+
const addPath = (path) => {
|
|
4380
|
+
if (!path || processedPaths.has(path)) {
|
|
4381
|
+
return;
|
|
4382
|
+
}
|
|
4383
|
+
processedPaths.add(path);
|
|
4384
|
+
dataPaths.push({
|
|
4385
|
+
path,
|
|
4386
|
+
value: cloneValue(getValueByPath(values, path)),
|
|
4387
|
+
});
|
|
4388
|
+
if (path.endsWith('.geo_code_hierarchy_json')) {
|
|
4389
|
+
const prefix = path.slice(0, -'.geo_code_hierarchy_json'.length);
|
|
4390
|
+
addPath(`${prefix}.geo_lowest_level_value_id`);
|
|
4391
|
+
}
|
|
4392
|
+
};
|
|
4393
|
+
collectWidgets(section.panels).forEach((widget) => {
|
|
4394
|
+
const widgetId = resolveNamespacedWidgetId(widget['widget-id'], namespace);
|
|
4395
|
+
const storeDataPath = resolveStoreDataPath(widget['widget-data-path'], namespace);
|
|
4396
|
+
if (Object.prototype.hasOwnProperty.call(values, widgetId)) {
|
|
4397
|
+
widgetIds[widgetId] = { present: true, value: cloneValue(values[widgetId]) };
|
|
4398
|
+
}
|
|
4399
|
+
else {
|
|
4400
|
+
widgetIds[widgetId] = { present: false };
|
|
4401
|
+
}
|
|
4402
|
+
if (typeof storeDataPath === 'string') {
|
|
4403
|
+
addPath(storeDataPath);
|
|
4404
|
+
}
|
|
4405
|
+
else if (storeDataPath && typeof storeDataPath === 'object') {
|
|
4406
|
+
Object.values(storeDataPath).forEach((path) => {
|
|
4407
|
+
if (typeof path === 'string') {
|
|
4408
|
+
addPath(path);
|
|
4409
|
+
}
|
|
4410
|
+
});
|
|
4411
|
+
}
|
|
4412
|
+
});
|
|
4413
|
+
supportingDocuments.forEach((doc, index) => {
|
|
4414
|
+
const widgetId = `supporting-doc-${sectionId ?? 'section'}-${index}`;
|
|
4415
|
+
const storeDataPath = namespace && doc['document-data-path']
|
|
4416
|
+
? `${namespace}.${doc['document-data-path']}`
|
|
4417
|
+
: doc['document-data-path'];
|
|
4418
|
+
if (Object.prototype.hasOwnProperty.call(values, widgetId)) {
|
|
4419
|
+
widgetIds[widgetId] = { present: true, value: cloneValue(values[widgetId]) };
|
|
4420
|
+
}
|
|
4421
|
+
else {
|
|
4422
|
+
widgetIds[widgetId] = { present: false };
|
|
4423
|
+
}
|
|
4424
|
+
if (typeof storeDataPath === 'string') {
|
|
4425
|
+
addPath(storeDataPath);
|
|
4426
|
+
}
|
|
4427
|
+
});
|
|
4428
|
+
return { dataPaths, widgetIds };
|
|
4429
|
+
}
|
|
4430
|
+
/** Apply a section edit snapshot back onto the full Redux values object. */
|
|
4431
|
+
function applySectionEditSnapshot(currentValues, snapshot) {
|
|
4432
|
+
let result = currentValues;
|
|
4433
|
+
for (const { path, value } of snapshot.dataPaths) {
|
|
4434
|
+
result = setValueByPath(result, path, cloneValue(value));
|
|
4435
|
+
}
|
|
4436
|
+
for (const [widgetId, entry] of Object.entries(snapshot.widgetIds)) {
|
|
4437
|
+
if (entry.present) {
|
|
4438
|
+
result = { ...result, [widgetId]: cloneValue(entry.value) };
|
|
4439
|
+
}
|
|
4440
|
+
else {
|
|
4441
|
+
const { [widgetId]: _removed, ...rest } = result;
|
|
4442
|
+
result = rest;
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4445
|
+
return result;
|
|
4446
|
+
}
|
|
4447
|
+
|
|
4019
4448
|
/** Root class on readonly label/value rows; SectionRenderer scopes overflow/ellipsis rules here. */
|
|
4020
4449
|
const READONLY_VALUE_ROW_ROOT_CLASSES = [
|
|
4021
4450
|
'TextDisplayWidget',
|
|
@@ -4227,6 +4656,8 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4227
4656
|
}, [forceExitEdit]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
4228
4657
|
const [isDocumentsExpanded, setIsDocumentsExpanded] = React.useState(true);
|
|
4229
4658
|
const sectionRef = React.useRef(null);
|
|
4659
|
+
const baselineSnapshotRef = React.useRef(null);
|
|
4660
|
+
const editEntrySnapshotRef = React.useRef(null);
|
|
4230
4661
|
const [sectionHeight, setSectionHeight] = React.useState(null);
|
|
4231
4662
|
const [editSectionPosition, setEditSectionPosition] = React.useState(null);
|
|
4232
4663
|
// Capture section position when entering edit mode and update on scroll
|
|
@@ -4291,6 +4722,18 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4291
4722
|
panels: makePanelsEditable(sectionToRender.panels, widgetsEditable),
|
|
4292
4723
|
};
|
|
4293
4724
|
}, [sectionToRender, widgetsEditable]);
|
|
4725
|
+
const effectiveHideEditButton = hideEditButton ||
|
|
4726
|
+
section['section-hide-edit-button'] === true ||
|
|
4727
|
+
!collectWidgets(section.panels || []).some((w) => section['section-editable'] === true || w['widget-readonly'] !== true);
|
|
4728
|
+
const captureEditEntrySnapshot = React.useCallback(() => {
|
|
4729
|
+
const currentValues = store.getState().widget.values;
|
|
4730
|
+
const supportingDocuments = section['section-supporting-documents'] || [];
|
|
4731
|
+
editEntrySnapshotRef.current = captureSectionEditSnapshot(currentValues, section, {
|
|
4732
|
+
namespace,
|
|
4733
|
+
sectionId,
|
|
4734
|
+
supportingDocuments: hasSupportingDocuments ? supportingDocuments : [],
|
|
4735
|
+
});
|
|
4736
|
+
}, [store, section, namespace, sectionId, hasSupportingDocuments]);
|
|
4294
4737
|
// Handle edit button click
|
|
4295
4738
|
const handleEdit = () => {
|
|
4296
4739
|
// Capture height BEFORE entering edit mode to preserve space
|
|
@@ -4298,6 +4741,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4298
4741
|
const height = sectionRef.current.offsetHeight;
|
|
4299
4742
|
setSectionHeight(height);
|
|
4300
4743
|
}
|
|
4744
|
+
captureEditEntrySnapshot();
|
|
4301
4745
|
setIsEditMode(true);
|
|
4302
4746
|
onEditModeChange?.(originalSectionId, true);
|
|
4303
4747
|
};
|
|
@@ -4472,8 +4916,6 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4472
4916
|
}
|
|
4473
4917
|
return { records, files };
|
|
4474
4918
|
}, [originalSection, hasSupportingDocuments]);
|
|
4475
|
-
// Capture baseline when entering edit mode (used for isDirty comparison)
|
|
4476
|
-
const baselineSnapshotRef = React.useRef(null);
|
|
4477
4919
|
// IntakeForm only: increment when baseline is updated after save - forces badge to update (refs don't trigger re-renders)
|
|
4478
4920
|
const [intakeFormBaselineTrigger, setIntakeFormBaselineTrigger] = React.useState(0);
|
|
4479
4921
|
// IntakeForm: treat as edit mode for dirty tracking when isDraft. RegistryView: use isEditMode.
|
|
@@ -4491,6 +4933,9 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4491
4933
|
// Set baseline when entering edit mode; clear when leaving (baseline captured only on entry)
|
|
4492
4934
|
React.useEffect(() => {
|
|
4493
4935
|
if (effectiveEditModeForDirty) {
|
|
4936
|
+
if (!editEntrySnapshotRef.current) {
|
|
4937
|
+
captureEditEntrySnapshot();
|
|
4938
|
+
}
|
|
4494
4939
|
const oldSchemaData = schemaData || contextSchemaData || {};
|
|
4495
4940
|
if (namespace) {
|
|
4496
4941
|
const namespacedSchema = getValueByPath(oldSchemaData, namespace);
|
|
@@ -4499,11 +4944,12 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4499
4944
|
: buildSectionSnapshot(oldSchemaData);
|
|
4500
4945
|
}
|
|
4501
4946
|
else {
|
|
4502
|
-
baselineSnapshotRef.current = buildSectionSnapshot(
|
|
4947
|
+
baselineSnapshotRef.current = buildSectionSnapshot(store.getState().widget.values, namespace);
|
|
4503
4948
|
}
|
|
4504
4949
|
}
|
|
4505
4950
|
else {
|
|
4506
4951
|
baselineSnapshotRef.current = null;
|
|
4952
|
+
editEntrySnapshotRef.current = null;
|
|
4507
4953
|
onSectionDirtyChange?.(sectionId, false);
|
|
4508
4954
|
}
|
|
4509
4955
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- baseline must be captured only when effectiveEditModeForDirty toggles
|
|
@@ -4529,56 +4975,103 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4529
4975
|
// and handleCancel.
|
|
4530
4976
|
const revertToOriginalValues = React.useCallback(() => {
|
|
4531
4977
|
const sectionWidgets = collectWidgets(originalSection.panels);
|
|
4532
|
-
const oldSchemaData = schemaData || contextSchemaData;
|
|
4533
4978
|
const currentStoreValues = store.getState().widget.values;
|
|
4979
|
+
const snapshot = editEntrySnapshotRef.current;
|
|
4534
4980
|
let newStoreValues = currentStoreValues;
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
const
|
|
4540
|
-
const
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4981
|
+
if (snapshot) {
|
|
4982
|
+
newStoreValues = applySectionEditSnapshot(currentStoreValues, snapshot);
|
|
4983
|
+
}
|
|
4984
|
+
else {
|
|
4985
|
+
const oldSchemaData = schemaData || contextSchemaData;
|
|
4986
|
+
const processedGeoGroups = new Set();
|
|
4987
|
+
sectionWidgets.forEach((widget) => {
|
|
4988
|
+
const originalWidgetId = widget['widget-id'];
|
|
4989
|
+
const namespacedWidgetId = namespace ? `${namespace}__${originalWidgetId}` : originalWidgetId;
|
|
4990
|
+
const widgetId = namespacedWidgetId;
|
|
4991
|
+
const originalDataPath = widget['widget-data-path'];
|
|
4992
|
+
const storeDataPath = namespace && originalDataPath
|
|
4993
|
+
? (typeof originalDataPath === 'string'
|
|
4994
|
+
? `${namespace}.${originalDataPath}`
|
|
4995
|
+
: Object.fromEntries(Object.entries(originalDataPath).map(([key, path]) => [key, `${namespace}.${path}`])))
|
|
4996
|
+
: originalDataPath;
|
|
4997
|
+
const geoConfig = widget['widget-geo-config'];
|
|
4998
|
+
if (widgetId && originalDataPath) {
|
|
4999
|
+
let oldValue;
|
|
5000
|
+
if (typeof originalDataPath === 'object') {
|
|
5001
|
+
oldValue = {};
|
|
5002
|
+
Object.entries(originalDataPath).forEach(([key, path]) => {
|
|
5003
|
+
if (typeof path === 'string') {
|
|
5004
|
+
oldValue[key] = getValueByPath(oldSchemaData, path);
|
|
5005
|
+
}
|
|
5006
|
+
});
|
|
5007
|
+
}
|
|
5008
|
+
else if (typeof originalDataPath === 'string') {
|
|
5009
|
+
oldValue = getValueByPath(oldSchemaData, originalDataPath);
|
|
5010
|
+
}
|
|
5011
|
+
if (oldValue !== undefined) {
|
|
5012
|
+
newStoreValues = setWidgetValue(newStoreValues, storeDataPath, widgetId, oldValue);
|
|
5013
|
+
if (geoConfig && typeof storeDataPath === 'string') {
|
|
5014
|
+
const groupId = getGeoGroupId(storeDataPath);
|
|
5015
|
+
const levelValue = resolveGeoWidgetLevelValue(newStoreValues, widgetId, storeDataPath, geoConfig);
|
|
5016
|
+
if (levelValue !== undefined && levelValue !== null && levelValue !== '') {
|
|
5017
|
+
newStoreValues = { ...newStoreValues, [widgetId]: levelValue };
|
|
5018
|
+
}
|
|
5019
|
+
else {
|
|
5020
|
+
const { [widgetId]: _removed, ...rest } = newStoreValues;
|
|
5021
|
+
newStoreValues = rest;
|
|
5022
|
+
}
|
|
5023
|
+
if (!processedGeoGroups.has(groupId)) {
|
|
5024
|
+
resetAndSeedGeoHierarchyFromValues(newStoreValues, storeDataPath, widgetId, groupId);
|
|
5025
|
+
processedGeoGroups.add(groupId);
|
|
5026
|
+
}
|
|
5027
|
+
if (geoConfig.parentWidgetId) {
|
|
5028
|
+
dispatch(setDataSource({ widgetId, data: [] }));
|
|
5029
|
+
}
|
|
4552
5030
|
}
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
5031
|
+
else {
|
|
5032
|
+
newStoreValues = { ...newStoreValues, [widgetId]: oldValue };
|
|
5033
|
+
}
|
|
5034
|
+
}
|
|
4557
5035
|
}
|
|
4558
|
-
|
|
5036
|
+
});
|
|
5037
|
+
if (hasSupportingDocuments) {
|
|
5038
|
+
const originalSupportingDocuments = originalSection['section-supporting-documents'] || [];
|
|
5039
|
+
originalSupportingDocuments.forEach((doc, index) => {
|
|
5040
|
+
const widgetId = `supporting-doc-${sectionId}-${index}`;
|
|
5041
|
+
const originalDataPath = doc['document-data-path'];
|
|
5042
|
+
const storeDataPath = namespace && originalDataPath
|
|
5043
|
+
? `${namespace}.${originalDataPath}`
|
|
5044
|
+
: originalDataPath;
|
|
5045
|
+
const oldValue = getValueByPath(oldSchemaData, originalDataPath);
|
|
4559
5046
|
newStoreValues = setWidgetValue(newStoreValues, storeDataPath, widgetId, oldValue);
|
|
4560
|
-
|
|
4561
|
-
// sets values[widgetId] during editing, and useBaseWidget.currentValue
|
|
4562
|
-
// reads values[widgetId] first before falling through to the dataPath.
|
|
4563
|
-
newStoreValues = { ...newStoreValues, [widgetId]: oldValue };
|
|
4564
|
-
}
|
|
5047
|
+
});
|
|
4565
5048
|
}
|
|
4566
|
-
});
|
|
4567
|
-
if (hasSupportingDocuments) {
|
|
4568
|
-
const originalSupportingDocuments = originalSection['section-supporting-documents'] || [];
|
|
4569
|
-
originalSupportingDocuments.forEach((doc, index) => {
|
|
4570
|
-
const widgetId = `supporting-doc-${sectionId}-${index}`;
|
|
4571
|
-
const originalDataPath = doc['document-data-path'];
|
|
4572
|
-
const storeDataPath = namespace && originalDataPath
|
|
4573
|
-
? `${namespace}.${originalDataPath}`
|
|
4574
|
-
: originalDataPath;
|
|
4575
|
-
const oldValue = getValueByPath(oldSchemaData, originalDataPath);
|
|
4576
|
-
newStoreValues = setWidgetValue(newStoreValues, storeDataPath, widgetId, oldValue);
|
|
4577
|
-
});
|
|
4578
|
-
}
|
|
4579
|
-
if (newStoreValues !== currentStoreValues) {
|
|
4580
|
-
dispatch(setValues(newStoreValues));
|
|
4581
5049
|
}
|
|
5050
|
+
const processedGeoGroups = new Set();
|
|
5051
|
+
sectionWidgets.forEach((widget) => {
|
|
5052
|
+
const geoConfig = widget['widget-geo-config'];
|
|
5053
|
+
if (!geoConfig) {
|
|
5054
|
+
return;
|
|
5055
|
+
}
|
|
5056
|
+
const originalWidgetId = widget['widget-id'];
|
|
5057
|
+
const widgetId = namespace ? `${namespace}__${originalWidgetId}` : originalWidgetId;
|
|
5058
|
+
const originalDataPath = widget['widget-data-path'];
|
|
5059
|
+
const storeDataPath = namespace && typeof originalDataPath === 'string'
|
|
5060
|
+
? `${namespace}.${originalDataPath}`
|
|
5061
|
+
: originalDataPath;
|
|
5062
|
+
if (typeof storeDataPath !== 'string') {
|
|
5063
|
+
return;
|
|
5064
|
+
}
|
|
5065
|
+
const groupId = getGeoGroupId(storeDataPath);
|
|
5066
|
+
if (!processedGeoGroups.has(groupId)) {
|
|
5067
|
+
resetAndSeedGeoHierarchyFromValues(newStoreValues, storeDataPath, widgetId, groupId);
|
|
5068
|
+
processedGeoGroups.add(groupId);
|
|
5069
|
+
}
|
|
5070
|
+
if (geoConfig.parentWidgetId) {
|
|
5071
|
+
dispatch(setDataSource({ widgetId, data: [] }));
|
|
5072
|
+
}
|
|
5073
|
+
});
|
|
5074
|
+
dispatch(setValues(newStoreValues));
|
|
4582
5075
|
}, [originalSection, schemaData, contextSchemaData, store, namespace, hasSupportingDocuments, sectionId, dispatch]);
|
|
4583
5076
|
// Handle save button click
|
|
4584
5077
|
const handleSave = async () => {
|
|
@@ -4592,7 +5085,12 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4592
5085
|
// This ensures we use the original widget IDs and data paths
|
|
4593
5086
|
const sectionWidgets = collectWidgets(originalSection.panels);
|
|
4594
5087
|
const currentState = store.getState().widget;
|
|
4595
|
-
|
|
5088
|
+
let currentSchemaData = currentState.values || {};
|
|
5089
|
+
const geoRegistrations = collectGeoWidgetRegistrationsFromWidgets(sectionWidgets, namespace);
|
|
5090
|
+
if (geoRegistrations.length > 0) {
|
|
5091
|
+
currentSchemaData = reconcileGeoHierarchiesInValues(currentSchemaData, geoRegistrations, currentState.dataSources || {});
|
|
5092
|
+
dispatch(setValues(currentSchemaData));
|
|
5093
|
+
}
|
|
4596
5094
|
const isSectionValid = sectionValidate(originalSection, currentSchemaData, dispatch, true);
|
|
4597
5095
|
if (!isSectionValid) {
|
|
4598
5096
|
return;
|
|
@@ -4658,7 +5156,12 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4658
5156
|
if (isDraft !== false && store && onSectionSave) {
|
|
4659
5157
|
const sectionWidgets = collectWidgets(originalSection.panels);
|
|
4660
5158
|
const currentState = store.getState().widget;
|
|
4661
|
-
|
|
5159
|
+
let currentSchemaData = currentState.values || {};
|
|
5160
|
+
const geoRegistrations = collectGeoWidgetRegistrationsFromWidgets(sectionWidgets, namespace);
|
|
5161
|
+
if (geoRegistrations.length > 0) {
|
|
5162
|
+
currentSchemaData = reconcileGeoHierarchiesInValues(currentSchemaData, geoRegistrations, currentState.dataSources || {});
|
|
5163
|
+
dispatch(setValues(currentSchemaData));
|
|
5164
|
+
}
|
|
4662
5165
|
const isSectionValid = sectionValidate(originalSection, currentSchemaData, dispatch, true);
|
|
4663
5166
|
if (!isSectionValid)
|
|
4664
5167
|
return;
|
|
@@ -5074,7 +5577,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
5074
5577
|
color: changeRequestType === 'new' ? 'var(--owt-color-bg, #FFFFFF)' : 'var(--owt-color-error, #B91C1C)',
|
|
5075
5578
|
whiteSpace: 'nowrap',
|
|
5076
5579
|
boxShadow: changeRequestType === 'new' ? '0 2px 4px rgba(40, 167, 69, 0.3)' : 'none',
|
|
5077
|
-
}, children: changeRequestType === 'new' ? 'New' : 'Old' }))] })), jsxRuntimeExports.jsxs("div", { id: gridId, className: "section-panels", style: mode === 'RegistryView' &&
|
|
5580
|
+
}, 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: {
|
|
5078
5581
|
marginTop: '20px',
|
|
5079
5582
|
paddingBottom: '30px',
|
|
5080
5583
|
display: 'flex',
|
|
@@ -5122,7 +5625,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
5122
5625
|
fontSize: '14px',
|
|
5123
5626
|
color: 'var(--owt-color-text, #011627)',
|
|
5124
5627
|
fontWeight: 'normal',
|
|
5125
|
-
}, children: crViewData.approvedDate }))] })] })] })), mode === 'RegistryView' && !
|
|
5628
|
+
}, 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: {
|
|
5126
5629
|
fontFamily: 'Roboto, sans-serif',
|
|
5127
5630
|
fontSize: '16px',
|
|
5128
5631
|
color: 'var(--owt-color-text-muted, #727474)',
|
|
@@ -5928,7 +6431,7 @@ const JSONEditorPanel = ({ section, onChange, onReset, context = 'section', }) =
|
|
|
5928
6431
|
'widget-data-format.dateConstraint': ['any', 'past-only', 'future-only'],
|
|
5929
6432
|
'widget-data-format.dateTimeConstraint': ['any', 'past-only', 'future-only'],
|
|
5930
6433
|
// Widget options
|
|
5931
|
-
'widget-data-options.action': ['show', 'hide', 'enable', 'disable'],
|
|
6434
|
+
'widget-data-options.action': ['show', 'hide', 'enable', 'disable', 'require'],
|
|
5932
6435
|
'widget-data-options.condition.operator': CONDITION_OPERATORS,
|
|
5933
6436
|
};
|
|
5934
6437
|
}, []);
|
|
@@ -7313,7 +7816,7 @@ const removeMask = (value, mask) => {
|
|
|
7313
7816
|
};
|
|
7314
7817
|
|
|
7315
7818
|
const TextInputWidget = ({ config }) => {
|
|
7316
|
-
const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
7819
|
+
const { value, formattedValue, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
7317
7820
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
7318
7821
|
// Track raw value separately for masking (to preserve unmasked value internally)
|
|
7319
7822
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
@@ -7450,7 +7953,7 @@ const TextInputWidget = ({ config }) => {
|
|
|
7450
7953
|
const label = translateConfig(widgetConfig['widget-label']);
|
|
7451
7954
|
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 }) })] }));
|
|
7452
7955
|
}
|
|
7453
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
7956
|
+
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
|
|
7454
7957
|
? 'decimal'
|
|
7455
7958
|
: formatConfig?.characterType === 'numeric' || formatConfig?.characterType === 'numeric-decimal'
|
|
7456
7959
|
? 'numeric'
|
|
@@ -7473,7 +7976,7 @@ const NumberInputWidget = ({ config }) => {
|
|
|
7473
7976
|
}
|
|
7474
7977
|
return { ...config, 'widget-data-default': normalizedDefault };
|
|
7475
7978
|
}, [config]);
|
|
7476
|
-
const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config: resolvedConfig });
|
|
7979
|
+
const { value, formattedValue, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config: resolvedConfig });
|
|
7477
7980
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
7478
7981
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
7479
7982
|
const validationConfig = widgetConfig['widget-data-validation'];
|
|
@@ -7604,7 +8107,7 @@ const NumberInputWidget = ({ config }) => {
|
|
|
7604
8107
|
const display = numValue !== null ? formatNumber(numValue, formatConfig) : '';
|
|
7605
8108
|
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 }) })] }));
|
|
7606
8109
|
}
|
|
7607
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
8110
|
+
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 === ''))
|
|
7608
8111
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
7609
8112
|
: '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
|
|
7610
8113
|
? 'text-red-500'
|
|
@@ -7612,7 +8115,7 @@ const NumberInputWidget = ({ config }) => {
|
|
|
7612
8115
|
};
|
|
7613
8116
|
|
|
7614
8117
|
const BooleanWidget = ({ config }) => {
|
|
7615
|
-
const { value, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
8118
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
7616
8119
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
7617
8120
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
7618
8121
|
const representation = formatConfig?.booleanRepresentation || 'true-false';
|
|
@@ -7681,7 +8184,7 @@ const BooleanWidget = ({ config }) => {
|
|
|
7681
8184
|
}
|
|
7682
8185
|
// Render based on control type
|
|
7683
8186
|
if (controlType === 'checkbox') {
|
|
7684
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.
|
|
8187
|
+
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] }))] })] }) }));
|
|
7685
8188
|
}
|
|
7686
8189
|
if (controlType === 'radio') {
|
|
7687
8190
|
const containerClass = orientation === 'horizontal'
|
|
@@ -7689,10 +8192,10 @@ const BooleanWidget = ({ config }) => {
|
|
|
7689
8192
|
: 'flex flex-col items-start gap-2';
|
|
7690
8193
|
const radioDisabled = !isEnabled || widgetConfig['widget-readonly'];
|
|
7691
8194
|
const optionDisabledClass = radioDisabled ? 'opacity-50 cursor-not-allowed' : '';
|
|
7692
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.
|
|
8195
|
+
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] }))] })] }) }));
|
|
7693
8196
|
}
|
|
7694
8197
|
// Toggle/switch control type
|
|
7695
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.
|
|
8198
|
+
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
|
|
7696
8199
|
? 'bg-blue-600 text-white border-blue-600'
|
|
7697
8200
|
: '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
|
|
7698
8201
|
? 'bg-blue-600 text-white border-blue-600'
|
|
@@ -7702,7 +8205,7 @@ const BooleanWidget = ({ config }) => {
|
|
|
7702
8205
|
};
|
|
7703
8206
|
|
|
7704
8207
|
const DateInputWidget = ({ config }) => {
|
|
7705
|
-
const { value, error, touched, isEnabled, onChange, onBlur, setError, config: widgetConfig, } = useBaseWidget({ config });
|
|
8208
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, setError, config: widgetConfig, } = useBaseWidget({ config });
|
|
7706
8209
|
const formValues = reactRedux.useSelector((state) => state.widget.values);
|
|
7707
8210
|
const { translateConfig } = useWidgetTranslation();
|
|
7708
8211
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
@@ -7903,7 +8406,7 @@ const DateInputWidget = ({ config }) => {
|
|
|
7903
8406
|
}
|
|
7904
8407
|
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 }) })] }));
|
|
7905
8408
|
}
|
|
7906
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
8409
|
+
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
|
|
7907
8410
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
7908
8411
|
: '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] })] })] }) }));
|
|
7909
8412
|
};
|
|
@@ -8193,7 +8696,7 @@ const validateDateTimeConstraints = (date, minDateTime, maxDateTime, constraint)
|
|
|
8193
8696
|
};
|
|
8194
8697
|
|
|
8195
8698
|
const DateTimeInputWidget = ({ config }) => {
|
|
8196
|
-
const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
8699
|
+
const { value, formattedValue, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
8197
8700
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8198
8701
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
8199
8702
|
const optionsConfig = widgetConfig['widget-data-options'];
|
|
@@ -8346,29 +8849,33 @@ const DateTimeInputWidget = ({ config }) => {
|
|
|
8346
8849
|
}
|
|
8347
8850
|
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 }) })] }));
|
|
8348
8851
|
}
|
|
8349
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
8852
|
+
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 === ''))
|
|
8350
8853
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
8351
8854
|
: '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] }))] })] }) }));
|
|
8352
8855
|
};
|
|
8353
8856
|
|
|
8354
8857
|
const SelectWidget = ({ config }) => {
|
|
8355
|
-
const { value, error, touched, isEnabled, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
8858
|
+
const { value, geoDisplayLabel, error, touched, isEnabled, isRequired, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
8356
8859
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8357
8860
|
// For readonly mode, render as display text showing only the selected label
|
|
8358
8861
|
if (widgetConfig['widget-readonly']) {
|
|
8359
8862
|
const label = translateConfig(widgetConfig['widget-label']);
|
|
8360
8863
|
// Find the selected option's label
|
|
8361
|
-
const selectedOption = dataSourceOptions.find((option) => option.value === value);
|
|
8362
|
-
const displayValue = selectedOption
|
|
8864
|
+
const selectedOption = dataSourceOptions.find((option) => option.value === value || String(option.value) === String(value));
|
|
8865
|
+
const displayValue = selectedOption
|
|
8866
|
+
? translateConfig(selectedOption.label)
|
|
8867
|
+
: loading
|
|
8868
|
+
? (geoDisplayLabel || '-')
|
|
8869
|
+
: (geoDisplayLabel || (value != null && value !== '' ? translateConfig(String(value)) : '-'));
|
|
8363
8870
|
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 }) })] }));
|
|
8364
8871
|
}
|
|
8365
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
8872
|
+
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 === ''))
|
|
8366
8873
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
8367
|
-
: '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] }))] })] }) }));
|
|
8874
|
+
: '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] }))] })] }) }));
|
|
8368
8875
|
};
|
|
8369
8876
|
|
|
8370
8877
|
const RadioWidget = ({ config }) => {
|
|
8371
|
-
const { value, error, touched, isEnabled, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
8878
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
8372
8879
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8373
8880
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
8374
8881
|
const layout = formatConfig?.layout || widgetConfig['widget-orientation'] || 'vertical';
|
|
@@ -8431,14 +8938,16 @@ const RadioWidget = ({ config }) => {
|
|
|
8431
8938
|
if (widgetConfig['widget-readonly']) {
|
|
8432
8939
|
const label = translateConfig(widgetConfig['widget-label']);
|
|
8433
8940
|
const selectedOption = processedOptions.find(opt => opt.value === currentValue);
|
|
8434
|
-
const displayValue = selectedOption
|
|
8941
|
+
const displayValue = selectedOption
|
|
8942
|
+
? translateConfig(selectedOption.label)
|
|
8943
|
+
: (allowUnset && currentValue === null ? '-' : '');
|
|
8435
8944
|
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 }) })] }));
|
|
8436
8945
|
}
|
|
8437
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
8946
|
+
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] }))] })] }) }));
|
|
8438
8947
|
};
|
|
8439
8948
|
|
|
8440
8949
|
const CheckboxWidget = ({ config }) => {
|
|
8441
|
-
const { value, error, touched, isEnabled, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
8950
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
8442
8951
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8443
8952
|
const hasDataSource = !!widgetConfig['widget-data-source'];
|
|
8444
8953
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
@@ -8453,7 +8962,7 @@ const CheckboxWidget = ({ config }) => {
|
|
|
8453
8962
|
const displayValue = isChecked ? 'Yes' : 'No';
|
|
8454
8963
|
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 }) })] }));
|
|
8455
8964
|
}
|
|
8456
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.
|
|
8965
|
+
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] }))] })] }) }));
|
|
8457
8966
|
}
|
|
8458
8967
|
// Multiple checkboxes (with data source) - for array values
|
|
8459
8968
|
// Process and sort options if needed
|
|
@@ -8523,7 +9032,7 @@ const CheckboxWidget = ({ config }) => {
|
|
|
8523
9032
|
: '-';
|
|
8524
9033
|
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 }) })] }));
|
|
8525
9034
|
}
|
|
8526
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.
|
|
9035
|
+
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] }))] })] }) }));
|
|
8527
9036
|
};
|
|
8528
9037
|
|
|
8529
9038
|
const SimpleTableWidget = ({ config }) => {
|
|
@@ -8574,7 +9083,7 @@ const SimpleTableWidget = ({ config }) => {
|
|
|
8574
9083
|
};
|
|
8575
9084
|
|
|
8576
9085
|
const ArrayWidget = ({ config }) => {
|
|
8577
|
-
const { value, error, touched, isEnabled, onChange, config: widgetConfig, } = useBaseWidget({ config });
|
|
9086
|
+
const { value, error, touched, isEnabled, isRequired, onChange, config: widgetConfig, } = useBaseWidget({ config });
|
|
8578
9087
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8579
9088
|
const items = Array.isArray(value) ? value : [];
|
|
8580
9089
|
const itemConfig = widgetConfig['widget-item'];
|
|
@@ -8598,7 +9107,7 @@ const ArrayWidget = ({ config }) => {
|
|
|
8598
9107
|
newItems[index] = newValue;
|
|
8599
9108
|
onChange(newItems);
|
|
8600
9109
|
};
|
|
8601
|
-
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.
|
|
9110
|
+
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) => {
|
|
8602
9111
|
({
|
|
8603
9112
|
...itemConfig,
|
|
8604
9113
|
'widget-id': `${widgetConfig['widget-id']}-item-${index}`,
|
|
@@ -8609,7 +9118,7 @@ const ArrayWidget = ({ config }) => {
|
|
|
8609
9118
|
};
|
|
8610
9119
|
|
|
8611
9120
|
const IterableAccordionWidget = ({ config }) => {
|
|
8612
|
-
const { value, error, touched, isEnabled, onChange, config: widgetConfig, } = useBaseWidget({ config });
|
|
9121
|
+
const { value, error, touched, isEnabled, isRequired, onChange, config: widgetConfig, } = useBaseWidget({ config });
|
|
8613
9122
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8614
9123
|
const items = Array.isArray(value) ? value : [];
|
|
8615
9124
|
const itemConfig = widgetConfig['widget-item'];
|
|
@@ -8658,7 +9167,7 @@ const IterableAccordionWidget = ({ config }) => {
|
|
|
8658
9167
|
newItems[index] = newValue;
|
|
8659
9168
|
onChange(newItems);
|
|
8660
9169
|
};
|
|
8661
|
-
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.
|
|
9170
|
+
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) => {
|
|
8662
9171
|
const isCollapsed = collapsedItems[index] ?? defaultCollapsed;
|
|
8663
9172
|
const parentPath = widgetConfig['widget-data-path'];
|
|
8664
9173
|
const childPath = itemConfig['widget-data-path'];
|
|
@@ -8697,7 +9206,7 @@ const IterableAccordionWidget = ({ config }) => {
|
|
|
8697
9206
|
};
|
|
8698
9207
|
|
|
8699
9208
|
const PhoneInputWidget = ({ config }) => {
|
|
8700
|
-
const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
9209
|
+
const { value, formattedValue, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
8701
9210
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8702
9211
|
// Use formatted value if available, otherwise raw value
|
|
8703
9212
|
const displayValue = formattedValue !== undefined && formattedValue !== value
|
|
@@ -8708,13 +9217,13 @@ const PhoneInputWidget = ({ config }) => {
|
|
|
8708
9217
|
const label = translateConfig(widgetConfig['widget-label']);
|
|
8709
9218
|
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 || '-' }) })] }));
|
|
8710
9219
|
}
|
|
8711
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
9220
|
+
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 === ''))
|
|
8712
9221
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
8713
9222
|
: '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] }))] })] }) }));
|
|
8714
9223
|
};
|
|
8715
9224
|
|
|
8716
9225
|
const CurrencyInputWidget = ({ config }) => {
|
|
8717
|
-
const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
9226
|
+
const { value, formattedValue, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
8718
9227
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
8719
9228
|
// For input, use raw numeric value; formatted value is for display only
|
|
8720
9229
|
const numericValue = typeof value === 'number' ? value : (value ? parseFloat(String(value)) : '');
|
|
@@ -8736,7 +9245,7 @@ const CurrencyInputWidget = ({ config }) => {
|
|
|
8736
9245
|
const display = formattedValue || (value !== null && value !== undefined ? String(value) : '-');
|
|
8737
9246
|
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 }) })] }));
|
|
8738
9247
|
}
|
|
8739
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
9248
|
+
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 === ''))
|
|
8740
9249
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
8741
9250
|
: '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] }))] })] }) }));
|
|
8742
9251
|
};
|
|
@@ -8784,20 +9293,69 @@ const DisplayWidget = ({ config }) => {
|
|
|
8784
9293
|
if (!label || label.trim() === '') {
|
|
8785
9294
|
return (jsxRuntimeExports.jsx("div", { className: "DisplayFieldWidget mb-3 min-w-0 w-full overflow-hidden text-ellipsis whitespace-nowrap text-base text-gray-700", title: String(displayValue ?? ''), children: displayValue }));
|
|
8786
9295
|
}
|
|
8787
|
-
// With label, render as key-value pair (structure matches other readonly widgets for SectionRenderer ellipsis)
|
|
8788
|
-
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] DisplayFieldWidget flex flex-col sm:flex-row sm:items-start", children: [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 min-w-0", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
|
|
9296
|
+
// With label, render as key-value pair (structure matches other readonly widgets for SectionRenderer ellipsis)
|
|
9297
|
+
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] DisplayFieldWidget flex flex-col sm:flex-row sm:items-start", children: [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 min-w-0", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
|
|
9298
|
+
};
|
|
9299
|
+
|
|
9300
|
+
const getDateColumnConstraintError = (column, cellValue, rowValues, translateConfig) => {
|
|
9301
|
+
const displayValue = cellValue && typeof cellValue === 'string' ? cellValue.split('T')[0] : '';
|
|
9302
|
+
if (!displayValue) {
|
|
9303
|
+
return null;
|
|
9304
|
+
}
|
|
9305
|
+
const optionsConfig = column['widget-data-options'];
|
|
9306
|
+
const formatConfig = column['widget-data-format'];
|
|
9307
|
+
const dateConstraint = formatConfig?.dateConstraint || 'any';
|
|
9308
|
+
const minDate = optionsConfig?.minDate;
|
|
9309
|
+
const maxDate = optionsConfig?.maxDate;
|
|
9310
|
+
const minDateField = optionsConfig?.minDateField;
|
|
9311
|
+
const maxDateField = optionsConfig?.maxDateField;
|
|
9312
|
+
const minDateMessage = optionsConfig?.minDateMessage
|
|
9313
|
+
? translateConfig(optionsConfig.minDateMessage)
|
|
9314
|
+
: undefined;
|
|
9315
|
+
const maxDateMessage = optionsConfig?.maxDateMessage
|
|
9316
|
+
? translateConfig(optionsConfig.maxDateMessage)
|
|
9317
|
+
: undefined;
|
|
9318
|
+
const resolveSiblingDate = (fieldRef) => {
|
|
9319
|
+
if (!fieldRef) {
|
|
9320
|
+
return undefined;
|
|
9321
|
+
}
|
|
9322
|
+
const raw = getValueByPath(rowValues, fieldRef) ?? rowValues[fieldRef];
|
|
9323
|
+
return resolveDateBoundFromFieldValue(raw);
|
|
9324
|
+
};
|
|
9325
|
+
const effectiveMinDate = mergeMinDateBounds(getMinDate(dateConstraint, minDate), resolveSiblingDate(minDateField));
|
|
9326
|
+
const effectiveMaxDate = mergeMaxDateBounds(getMaxDate(dateConstraint, maxDate), resolveSiblingDate(maxDateField));
|
|
9327
|
+
return validateDateConstraints(displayValue, effectiveMinDate, effectiveMaxDate, dateConstraint, { minDateMessage, maxDateMessage });
|
|
9328
|
+
};
|
|
9329
|
+
const isTableRowDataValid = (rowData, columns, tableReadonly, translateConfig) => {
|
|
9330
|
+
for (const col of columns) {
|
|
9331
|
+
if (tableReadonly || col['widget-readonly'] === true) {
|
|
9332
|
+
continue;
|
|
9333
|
+
}
|
|
9334
|
+
const columnKey = col['column-key'];
|
|
9335
|
+
const cellValue = rowData[columnKey];
|
|
9336
|
+
const widgetErrors = validateWidget(cellValue, col['widget-data-validation'], col['widget-required']);
|
|
9337
|
+
if (widgetErrors.length > 0) {
|
|
9338
|
+
return false;
|
|
9339
|
+
}
|
|
9340
|
+
if ((col.widget || 'text') === 'date') {
|
|
9341
|
+
const dateError = getDateColumnConstraintError(col, cellValue, rowData, translateConfig);
|
|
9342
|
+
if (dateError) {
|
|
9343
|
+
return false;
|
|
9344
|
+
}
|
|
9345
|
+
}
|
|
9346
|
+
}
|
|
9347
|
+
return true;
|
|
8789
9348
|
};
|
|
8790
|
-
|
|
8791
9349
|
const TableCellSelect = ({ config, value, onValueChange }) => {
|
|
8792
9350
|
const { translate } = useWidgetTranslation();
|
|
8793
9351
|
// Use useBaseWidget to get data source options (it handles loading)
|
|
8794
9352
|
const { dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
8795
9353
|
const isReadonly = config['widget-readonly'] || false;
|
|
8796
|
-
return (jsxRuntimeExports.jsxs("select", { value: value || '', onChange: (e) => onValueChange(e.target.value), disabled: isReadonly || loading, className: `w-full h-[28px] px-2 text-sm border focus:outline-none ${isReadonly || loading ? 'cursor-not-allowed' : ''} table-cell-input`, style: {
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
9354
|
+
return (jsxRuntimeExports.jsxs("div", { className: "table-cell-field w-full", children: [jsxRuntimeExports.jsxs("select", { value: value || '', onChange: (e) => onValueChange(e.target.value === '' ? undefined : e.target.value), disabled: isReadonly || loading, className: `w-full h-[28px] px-2 text-sm border focus:outline-none ${isReadonly || loading ? 'cursor-not-allowed' : ''} table-cell-input`, style: {
|
|
9355
|
+
borderRadius: '10px',
|
|
9356
|
+
borderColor: 'var(--owt-widget-input-border, #C4C4C4)',
|
|
9357
|
+
backgroundColor: isReadonly || loading ? 'var(--owt-color-bg-alt, #F6F6F6)' : 'var(--owt-color-bg, #FFFFFF)',
|
|
9358
|
+
}, children: [jsxRuntimeExports.jsx("option", { value: "", children: translate('common.select') || 'Select' }), dataSourceOptions.map((option) => (jsxRuntimeExports.jsx("option", { value: option.value, children: option.label }, option.value)))] }), jsxRuntimeExports.jsx("p", { className: "table-cell-field-error", "aria-hidden": "true", children: '\u00a0' })] }));
|
|
8801
9359
|
};
|
|
8802
9360
|
const SelectDisplayValue$1 = ({ config, value }) => {
|
|
8803
9361
|
const { dataSourceOptions, loading } = useBaseWidget({ config });
|
|
@@ -8807,7 +9365,7 @@ const SelectDisplayValue$1 = ({ config, value }) => {
|
|
|
8807
9365
|
if (value === null || value === undefined || value === '') {
|
|
8808
9366
|
return jsxRuntimeExports.jsx("span", { children: "-" });
|
|
8809
9367
|
}
|
|
8810
|
-
const selectedOption = dataSourceOptions.find((option) => option.value === value);
|
|
9368
|
+
const selectedOption = dataSourceOptions.find((option) => option.value === value || String(option.value) === String(value));
|
|
8811
9369
|
return jsxRuntimeExports.jsx("span", { children: selectedOption ? selectedOption.label : String(value) });
|
|
8812
9370
|
};
|
|
8813
9371
|
const TableCellText = ({ config, value, onValueChange }) => {
|
|
@@ -8816,11 +9374,11 @@ const TableCellText = ({ config, value, onValueChange }) => {
|
|
|
8816
9374
|
config['widget-data-format'];
|
|
8817
9375
|
const maxLength = config['widget-data-validation']?.maxLength;
|
|
8818
9376
|
const displayValue = value !== null && value !== undefined ? String(value) : '';
|
|
8819
|
-
return (jsxRuntimeExports.jsx("input", { type: "text", value: displayValue, onChange: (e) => onValueChange(e.target.value), disabled: isReadonly, placeholder: placeholder, maxLength: maxLength, className: `w-full h-[28px] px-2 text-sm border focus:outline-none ${isReadonly ? 'cursor-not-allowed' : ''} table-cell-input`, style: {
|
|
8820
|
-
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
|
|
9377
|
+
return (jsxRuntimeExports.jsxs("div", { className: "table-cell-field w-full", children: [jsxRuntimeExports.jsx("input", { type: "text", value: displayValue, onChange: (e) => onValueChange(e.target.value), disabled: isReadonly, placeholder: placeholder, maxLength: maxLength, className: `w-full h-[28px] px-2 text-sm border focus:outline-none ${isReadonly ? 'cursor-not-allowed' : ''} table-cell-input`, style: {
|
|
9378
|
+
borderRadius: '10px',
|
|
9379
|
+
borderColor: 'var(--owt-widget-input-border, #C4C4C4)',
|
|
9380
|
+
backgroundColor: isReadonly ? 'var(--owt-color-bg-alt, #F6F6F6)' : 'var(--owt-color-bg, #FFFFFF)',
|
|
9381
|
+
} }), jsxRuntimeExports.jsx("p", { className: "table-cell-field-error", "aria-hidden": "true", children: '\u00a0' })] }));
|
|
8824
9382
|
};
|
|
8825
9383
|
const TableCellNumber = ({ config, value, onValueChange }) => {
|
|
8826
9384
|
const isReadonly = config['widget-readonly'] || false;
|
|
@@ -8843,11 +9401,11 @@ const TableCellNumber = ({ config, value, onValueChange }) => {
|
|
|
8843
9401
|
onValueChange(inputValue);
|
|
8844
9402
|
}
|
|
8845
9403
|
};
|
|
8846
|
-
return (jsxRuntimeExports.jsx("input", { type: "number", value: displayValue, onChange: handleChange, disabled: isReadonly, placeholder: placeholder, min: validationConfig?.min, max: validationConfig?.max, step: formatConfig?.decimalPlaces ? Math.pow(0.1, formatConfig.decimalPlaces) : undefined, className: `w-full h-[28px] px-2 text-sm border focus:outline-none text-right ${isReadonly ? 'cursor-not-allowed' : ''} table-cell-input`, style: {
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
9404
|
+
return (jsxRuntimeExports.jsxs("div", { className: "table-cell-field w-full", children: [jsxRuntimeExports.jsx("input", { type: "number", value: displayValue, onChange: handleChange, disabled: isReadonly, placeholder: placeholder, min: validationConfig?.min, max: validationConfig?.max, step: formatConfig?.decimalPlaces ? Math.pow(0.1, formatConfig.decimalPlaces) : undefined, className: `w-full h-[28px] px-2 text-sm border focus:outline-none text-right ${isReadonly ? 'cursor-not-allowed' : ''} table-cell-input`, style: {
|
|
9405
|
+
borderRadius: '10px',
|
|
9406
|
+
borderColor: 'var(--owt-widget-input-border, #C4C4C4)',
|
|
9407
|
+
backgroundColor: isReadonly ? 'var(--owt-color-bg-alt, #F6F6F6)' : 'var(--owt-color-bg, #FFFFFF)',
|
|
9408
|
+
} }), jsxRuntimeExports.jsx("p", { className: "table-cell-field-error", "aria-hidden": "true", children: '\u00a0' })] }));
|
|
8851
9409
|
};
|
|
8852
9410
|
const TableCellDate = ({ config, value, rowValues, onValueChange }) => {
|
|
8853
9411
|
const { translateConfig } = useWidgetTranslation();
|
|
@@ -8906,13 +9464,15 @@ const TableCellDate = ({ config, value, rowValues, onValueChange }) => {
|
|
|
8906
9464
|
setConstraintError(error);
|
|
8907
9465
|
};
|
|
8908
9466
|
const hasError = Boolean(constraintError);
|
|
8909
|
-
return (jsxRuntimeExports.jsxs("div", { className: "w-full", children: [jsxRuntimeExports.jsx("input", { type: "date", value: displayValue, onChange: handleChange, disabled: isReadonly, placeholder: placeholder, min: effectiveMinDate, max: effectiveMaxDate, title: constraintError || translateConfig(config['widget-data-tooltip']), className: `w-full h-[28px] px-2 text-sm border focus:outline-none ${isReadonly ? 'cursor-not-allowed' : ''}
|
|
9467
|
+
return (jsxRuntimeExports.jsxs("div", { className: "table-cell-field w-full", children: [jsxRuntimeExports.jsx("input", { type: "date", value: displayValue, onChange: handleChange, disabled: isReadonly, placeholder: placeholder, min: effectiveMinDate, max: effectiveMaxDate, title: constraintError || translateConfig(config['widget-data-tooltip']), className: `w-full h-[28px] px-2 text-sm border focus:outline-none ${isReadonly ? 'cursor-not-allowed' : ''} table-cell-input`, style: {
|
|
8910
9468
|
borderRadius: '10px',
|
|
8911
9469
|
borderColor: hasError
|
|
8912
9470
|
? 'var(--owt-color-error, #B91C1C)'
|
|
8913
9471
|
: 'var(--owt-widget-input-border, #C4C4C4)',
|
|
8914
9472
|
backgroundColor: isReadonly ? 'var(--owt-color-bg-alt, #F6F6F6)' : 'var(--owt-color-bg, #FFFFFF)',
|
|
8915
|
-
} }),
|
|
9473
|
+
} }), jsxRuntimeExports.jsx("p", { className: "table-cell-field-error text-xs mt-0.5 leading-tight", style: {
|
|
9474
|
+
color: hasError ? 'var(--owt-color-error, #B91C1C)' : 'transparent',
|
|
9475
|
+
}, "aria-live": "polite", children: constraintError ?? '\u00a0' })] }));
|
|
8916
9476
|
};
|
|
8917
9477
|
const TableWidget = ({ config }) => {
|
|
8918
9478
|
const { value, error, touched, isEnabled, onChange, config: widgetConfig, } = useBaseWidget({ config });
|
|
@@ -8938,6 +9498,18 @@ const TableWidget = ({ config }) => {
|
|
|
8938
9498
|
const isSectionEditMode = !isReadonly && operations.edit;
|
|
8939
9499
|
// Check if any row is being edited (either manually or via section edit mode)
|
|
8940
9500
|
const isAnyRowEditing = editingState !== null || isAdding;
|
|
9501
|
+
const canSaveEditingRow = React.useMemo(() => {
|
|
9502
|
+
if (!editingState) {
|
|
9503
|
+
return false;
|
|
9504
|
+
}
|
|
9505
|
+
return isTableRowDataValid(editingState.currentValue, columns, isReadonly, translateConfig);
|
|
9506
|
+
}, [editingState, columns, isReadonly, translateConfig]);
|
|
9507
|
+
const canSaveNewRow = React.useMemo(() => {
|
|
9508
|
+
if (!isAdding || !newRowData) {
|
|
9509
|
+
return false;
|
|
9510
|
+
}
|
|
9511
|
+
return isTableRowDataValid(newRowData, columns, isReadonly, translateConfig);
|
|
9512
|
+
}, [isAdding, newRowData, columns, isReadonly, translateConfig]);
|
|
8941
9513
|
// Show confirmation dialog
|
|
8942
9514
|
const showConfirmation = React.useCallback((message, onConfirm, onCancel) => {
|
|
8943
9515
|
setConfirmationState({
|
|
@@ -9027,6 +9599,8 @@ const TableWidget = ({ config }) => {
|
|
|
9027
9599
|
const saveEdit = React.useCallback(async () => {
|
|
9028
9600
|
if (!editingState)
|
|
9029
9601
|
return;
|
|
9602
|
+
if (!canSaveEditingRow)
|
|
9603
|
+
return;
|
|
9030
9604
|
const rowData = editingState.currentValue;
|
|
9031
9605
|
const rowIndex = editingState.rowIndex;
|
|
9032
9606
|
setLoadingRowIndex(rowIndex);
|
|
@@ -9098,7 +9672,7 @@ const TableWidget = ({ config }) => {
|
|
|
9098
9672
|
finally {
|
|
9099
9673
|
setLoadingRowIndex(null);
|
|
9100
9674
|
}
|
|
9101
|
-
}, [editingState, rows, onChange, dataSourceRequestHandler, apiConfig, translate, isSectionEditMode, originalRows, columns, widgetConfig, dispatch]);
|
|
9675
|
+
}, [editingState, canSaveEditingRow, rows, onChange, dataSourceRequestHandler, apiConfig, translate, isSectionEditMode, originalRows, columns, widgetConfig, dispatch]);
|
|
9102
9676
|
// Add new row
|
|
9103
9677
|
const startAdd = React.useCallback(() => {
|
|
9104
9678
|
// If there's an unsaved edit, cancel it first (no confirmation needed)
|
|
@@ -9116,6 +9690,8 @@ const TableWidget = ({ config }) => {
|
|
|
9116
9690
|
const saveAdd = React.useCallback(async () => {
|
|
9117
9691
|
if (!isAdding || !newRowData)
|
|
9118
9692
|
return;
|
|
9693
|
+
if (!canSaveNewRow)
|
|
9694
|
+
return;
|
|
9119
9695
|
setLoadingRowIndex(-1); // Use -1 to indicate new row
|
|
9120
9696
|
try {
|
|
9121
9697
|
let savedRow = { ...newRowData };
|
|
@@ -9146,7 +9722,7 @@ const TableWidget = ({ config }) => {
|
|
|
9146
9722
|
finally {
|
|
9147
9723
|
setLoadingRowIndex(null);
|
|
9148
9724
|
}
|
|
9149
|
-
}, [isAdding, newRowData, rows, onChange, dataSourceRequestHandler, apiConfig, translate, isSectionEditMode]);
|
|
9725
|
+
}, [isAdding, newRowData, canSaveNewRow, rows, onChange, dataSourceRequestHandler, apiConfig, translate, isSectionEditMode]);
|
|
9150
9726
|
// Delete row
|
|
9151
9727
|
const deleteRow = React.useCallback(async (rowIndex) => {
|
|
9152
9728
|
if (isAnyRowEditing) {
|
|
@@ -9424,6 +10000,22 @@ const TableWidget = ({ config }) => {
|
|
|
9424
10000
|
box-shadow: 0 0 0 1px var(--owt-widget-input-focus-border, #F07B1A);
|
|
9425
10001
|
border-color: var(--owt-widget-input-focus-border, #F07B1A);
|
|
9426
10002
|
}
|
|
10003
|
+
|
|
10004
|
+
/* Keep inputs and action buttons top-aligned when a cell shows validation text */
|
|
10005
|
+
.${tableWidgetId} tr.table-row-editing td {
|
|
10006
|
+
vertical-align: top;
|
|
10007
|
+
}
|
|
10008
|
+
|
|
10009
|
+
.${tableWidgetId} .table-cell-field-error {
|
|
10010
|
+
min-height: 1.125rem;
|
|
10011
|
+
}
|
|
10012
|
+
|
|
10013
|
+
.${tableWidgetId} .table-cell-actions {
|
|
10014
|
+
display: flex;
|
|
10015
|
+
flex-direction: row;
|
|
10016
|
+
gap: 0.5rem;
|
|
10017
|
+
align-items: flex-start;
|
|
10018
|
+
}
|
|
9427
10019
|
` }), jsxRuntimeExports.jsxs("div", { className: `table-widget-container ${tableWidgetId}`, children: [confirmationState?.show && (jsxRuntimeExports.jsx("div", { className: "fixed inset-0 flex items-center justify-center z-50", style: { backgroundColor: 'rgba(0,0,0,0.5)' }, children: jsxRuntimeExports.jsxs("div", { className: "rounded-lg p-6 max-w-md w-full mx-4", style: { backgroundColor: 'var(--owt-color-bg, #FFFFFF)' }, children: [jsxRuntimeExports.jsx("h3", { className: "text-lg font-semibold mb-4", style: { color: 'var(--owt-color-text, #011627)' }, children: translate('table.confirm') || 'Confirm Action' }), jsxRuntimeExports.jsx("p", { className: "mb-6", style: { color: 'var(--owt-color-text, #011627)' }, children: confirmationState.message }), jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-3", children: [jsxRuntimeExports.jsx("button", { onClick: confirmationState.onCancel, className: "px-4 py-2 text-sm font-medium", style: {
|
|
9428
10020
|
borderRadius: 'var(--owt-btn-border-radius, 10px)',
|
|
9429
10021
|
border: '1px solid var(--owt-btn-secondary-border, #C4C4C4)',
|
|
@@ -9442,7 +10034,7 @@ const TableWidget = ({ config }) => {
|
|
|
9442
10034
|
}, children: translate('table.addRecord') || 'Add New Record' }) })), jsxRuntimeExports.jsx("div", { className: "overflow-x-auto border", style: { borderRadius: 'var(--owt-widget-table-border-radius, 15px)', borderColor: 'var(--owt-widget-table-border-color, #C4C4C4)' }, children: jsxRuntimeExports.jsxs("table", { className: "min-w-full", style: { borderCollapse: 'separate', borderSpacing: 0 }, children: [jsxRuntimeExports.jsx("thead", { style: { backgroundColor: 'var(--owt-widget-table-header-bg, #F6F6F6)' }, children: jsxRuntimeExports.jsxs("tr", { style: { borderBottom: '1px solid var(--owt-widget-table-row-divider, #E4E4E4)' }, children: [columns.map((col) => (jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wider", style: { color: 'var(--owt-widget-table-header-color, #727474)' }, children: translateConfig(col['widget-label']) }, col['column-key']))), ((operations.edit || operations.remove) && !isReadonly) || isAnyRowEditing || isSectionEditMode ? (jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wider", style: { color: 'var(--owt-widget-table-header-color, #727474)' }, children: translate('common.actions') || 'Actions' })) : null] }) }), jsxRuntimeExports.jsxs("tbody", { style: { backgroundColor: 'var(--owt-widget-table-body-bg, #FFFFFF)' }, children: [rows.length === 0 && !isAdding && (jsxRuntimeExports.jsx("tr", { children: jsxRuntimeExports.jsxs("td", { colSpan: columns.length + (((operations.edit || operations.remove) && !isReadonly) || isSectionEditMode ? 1 : 0), className: "px-4 py-6 text-center text-sm", style: { color: 'var(--owt-widget-table-empty-color, #727474)' }, children: [translate('table.noData') || 'No records available.', operations.add && !isReadonly && ` ${translate('table.clickToAdd') || 'Click "Add New Record" to add one.'}`] }) })), rows.map((row, rowIndex) => {
|
|
9443
10035
|
const isEditing = isRowEditing(rowIndex);
|
|
9444
10036
|
const isLoading = loadingRowIndex === rowIndex;
|
|
9445
|
-
return (jsxRuntimeExports.jsxs("tr", { className: isLoading ? 'opacity-50' : ''
|
|
10037
|
+
return (jsxRuntimeExports.jsxs("tr", { className: `${isLoading ? 'opacity-50' : ''}${isEditing ? ' table-row-editing' : ''}`, style: {
|
|
9446
10038
|
borderBottom: '1px solid var(--owt-widget-table-row-divider, #E4E4E4)',
|
|
9447
10039
|
backgroundColor: isEditing
|
|
9448
10040
|
? 'var(--owt-widget-table-editing-row-bg, #FBE6AA)'
|
|
@@ -9453,7 +10045,7 @@ const TableWidget = ({ config }) => {
|
|
|
9453
10045
|
return (jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", children: renderCell(rowIndex, col, row) }, col['column-key']));
|
|
9454
10046
|
}), ((operations.edit || operations.remove) && !isReadonly) || isEditing || isSectionEditMode ? (jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", style: { minWidth: '120px' }, children: isEditing ? (
|
|
9455
10047
|
// Show OK (Save)/Cancel buttons when row is being edited (works in both section edit mode and normal mode)
|
|
9456
|
-
jsxRuntimeExports.jsxs("div", { className: "
|
|
10048
|
+
jsxRuntimeExports.jsxs("div", { className: "table-cell-actions", style: { width: '100%' }, children: [jsxRuntimeExports.jsx("button", { type: "button", onClick: saveEdit, disabled: isLoading || !canSaveEditingRow, className: "px-3 py-1 text-xs font-medium disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap flex-shrink-0", style: {
|
|
9457
10049
|
display: 'inline-block',
|
|
9458
10050
|
minWidth: '60px',
|
|
9459
10051
|
backgroundColor: 'var(--owt-color-success, #16A34A)',
|
|
@@ -9480,7 +10072,7 @@ const TableWidget = ({ config }) => {
|
|
|
9480
10072
|
backgroundColor: 'transparent',
|
|
9481
10073
|
border: 'none',
|
|
9482
10074
|
}, children: translate('common.remove') || 'Delete' }))] })) })) : null] }, rowIndex));
|
|
9483
|
-
}), isAdding && newRowData && (jsxRuntimeExports.jsxs("tr", { style: { backgroundColor: 'var(--owt-widget-table-editing-row-bg, #FBE6AA)' }, children: [columns.map((col) => (jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", children: renderCell(rows.length, col, { ...newRowData, edit_action: 'ADD' }) }, col['column-key']))), jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", children: jsxRuntimeExports.jsxs("div", { className: "
|
|
10075
|
+
}), isAdding && newRowData && (jsxRuntimeExports.jsxs("tr", { className: "table-row-editing", style: { backgroundColor: 'var(--owt-widget-table-editing-row-bg, #FBE6AA)' }, children: [columns.map((col) => (jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", children: renderCell(rows.length, col, { ...newRowData, edit_action: 'ADD' }) }, col['column-key']))), jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", children: jsxRuntimeExports.jsxs("div", { className: "table-cell-actions", children: [jsxRuntimeExports.jsx("button", { type: "button", onClick: saveAdd, disabled: loadingRowIndex === -1 || !canSaveNewRow, className: "px-3 py-1 text-xs disabled:opacity-50 disabled:cursor-not-allowed", style: {
|
|
9484
10076
|
borderRadius: 'var(--owt-btn-border-radius, 10px)',
|
|
9485
10077
|
backgroundColor: 'var(--owt-color-success, #16A34A)',
|
|
9486
10078
|
color: 'var(--owt-color-bg, #FFFFFF)',
|
|
@@ -9496,6 +10088,23 @@ const TableWidget = ({ config }) => {
|
|
|
9496
10088
|
}, 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] }))] })] }));
|
|
9497
10089
|
};
|
|
9498
10090
|
|
|
10091
|
+
const isUnsetRowValue = (value) => value === null || value === undefined || value === '';
|
|
10092
|
+
/** Match TableWidget cell styling for add / update / delete rows */
|
|
10093
|
+
const getRowCellStyle = (editAction) => {
|
|
10094
|
+
if (editAction === 'ADD') {
|
|
10095
|
+
return { color: 'var(--owt-color-success, #16A34A)' };
|
|
10096
|
+
}
|
|
10097
|
+
if (editAction === 'DELETE') {
|
|
10098
|
+
return {
|
|
10099
|
+
color: 'var(--owt-color-error, #B91C1C)',
|
|
10100
|
+
textDecoration: 'line-through',
|
|
10101
|
+
};
|
|
10102
|
+
}
|
|
10103
|
+
if (editAction === 'UPDATE') {
|
|
10104
|
+
return { color: 'var(--owt-color-warning, #F59E0B)' };
|
|
10105
|
+
}
|
|
10106
|
+
return {};
|
|
10107
|
+
};
|
|
9499
10108
|
// Display select value label in view mode
|
|
9500
10109
|
const SelectDisplayValue = ({ config, value }) => {
|
|
9501
10110
|
const { dataSourceOptions, loading } = useBaseWidget({ config });
|
|
@@ -9503,53 +10112,57 @@ const SelectDisplayValue = ({ config, value }) => {
|
|
|
9503
10112
|
return jsxRuntimeExports.jsx("span", { children: "-" });
|
|
9504
10113
|
if (value === null || value === undefined || value === '')
|
|
9505
10114
|
return jsxRuntimeExports.jsx("span", { children: "-" });
|
|
9506
|
-
const selectedOption = dataSourceOptions.find((option) => option.value === value);
|
|
10115
|
+
const selectedOption = dataSourceOptions.find((option) => option.value === value || String(option.value) === String(value));
|
|
9507
10116
|
return jsxRuntimeExports.jsx("span", { children: selectedOption ? selectedOption.label : String(value) });
|
|
9508
10117
|
};
|
|
10118
|
+
/** Isolated dialog field — avoids re-running data-source effects when sibling fields update. */
|
|
10119
|
+
const DialogTableField = React.memo(function DialogTableField({ col, cellWidgetId, dialogRowValues, isReadonly, }) {
|
|
10120
|
+
const widgetType = col.widget || 'text';
|
|
10121
|
+
const fieldConfig = React.useMemo(() => {
|
|
10122
|
+
return {
|
|
10123
|
+
...col,
|
|
10124
|
+
widget: widgetType,
|
|
10125
|
+
'widget-type': col['widget-type'] || 'input',
|
|
10126
|
+
'widget-id': cellWidgetId,
|
|
10127
|
+
'widget-label': col['widget-label'],
|
|
10128
|
+
'widget-readonly': isReadonly || col['widget-readonly'] === true,
|
|
10129
|
+
'widget-data-path': undefined,
|
|
10130
|
+
'widget-data-default': col['widget-data-default'],
|
|
10131
|
+
'widget-data-options': undefined,
|
|
10132
|
+
'widget-required': shouldRequireWidget(col['widget-data-options'], dialogRowValues, col['widget-required']),
|
|
10133
|
+
};
|
|
10134
|
+
}, [col, cellWidgetId, dialogRowValues, isReadonly, widgetType]);
|
|
10135
|
+
return (jsxRuntimeExports.jsx("div", { className: "min-w-0", children: jsxRuntimeExports.jsx(WidgetRenderer, { config: fieldConfig }) }));
|
|
10136
|
+
});
|
|
9509
10137
|
/**
|
|
9510
10138
|
* Dialog table widget:
|
|
9511
10139
|
* - Table displays a subset of columns (n out of x)
|
|
9512
10140
|
* - Add/Edit happens in a modal dialog that shows ALL columns as a form
|
|
9513
|
-
*
|
|
9514
|
-
* Usage in schema:
|
|
9515
|
-
* {
|
|
9516
|
-
* "widget": "dialog-table",
|
|
9517
|
-
* "widget-type": "table",
|
|
9518
|
-
* "widget-label": "Household Members",
|
|
9519
|
-
* "widget-id": "householdMembers",
|
|
9520
|
-
* "widget-data-path": "household.members",
|
|
9521
|
-
* "widget-data-columns": [ ...all columns... ],
|
|
9522
|
-
* "widget-data-visible-columns": ["firstName", "lastName", "dob"], // optional override; default = by column flag
|
|
9523
|
-
* // Per-column control (recommended): set "column-visible-in-table": false to hide it in the table
|
|
9524
|
-
* "widget-data-operations": { "add": true, "edit": true, "remove": true }
|
|
9525
|
-
* }
|
|
9526
10141
|
*/
|
|
9527
10142
|
const DialogTableWidget = ({ config }) => {
|
|
9528
10143
|
const { value, error, touched, isEnabled, onChange, config: widgetConfig } = useBaseWidget({ config });
|
|
9529
10144
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
9530
10145
|
const dispatch = reactRedux.useDispatch();
|
|
9531
|
-
const storeValues = reactRedux.useSelector((state) => state.widget?.values ?? {});
|
|
9532
10146
|
const rows = Array.isArray(value) ? value : [];
|
|
9533
10147
|
const columns = widgetConfig['widget-data-columns'] || [];
|
|
9534
10148
|
const operations = widgetConfig['widget-data-operations'] || {};
|
|
9535
10149
|
const isReadonly = widgetConfig['widget-readonly'] || false;
|
|
10150
|
+
// Soft-delete (keep row, red + strikethrough) whenever remove is allowed — matches TableWidget
|
|
10151
|
+
const shouldSoftDeleteOnRemove = !isReadonly && !!operations.remove;
|
|
9536
10152
|
const visibleColumnKeys = widgetConfig['widget-data-visible-columns'];
|
|
9537
10153
|
const visibleColumns = React.useMemo(() => {
|
|
9538
|
-
// 1) If explicit list provided, it wins
|
|
9539
10154
|
if (Array.isArray(visibleColumnKeys) && visibleColumnKeys.length > 0) {
|
|
9540
10155
|
const keySet = new Set(visibleColumnKeys);
|
|
9541
10156
|
return columns.filter((c) => keySet.has(c['column-key']));
|
|
9542
10157
|
}
|
|
9543
|
-
// 2) Otherwise decide per column (default = visible)
|
|
9544
10158
|
return columns.filter((c) => c['column-visible-in-table'] !== false);
|
|
9545
10159
|
}, [columns, visibleColumnKeys]);
|
|
9546
10160
|
const [dialogOpen, setDialogOpen] = React.useState(false);
|
|
9547
10161
|
const [dialogMode, setDialogMode] = React.useState('add');
|
|
9548
10162
|
const [activeRowIndex, setActiveRowIndex] = React.useState(null);
|
|
9549
|
-
const [formData, setFormData] = React.useState({});
|
|
9550
|
-
/** Unique per dialog open so Redux widget ids don't reuse stale values across rows/add sessions */
|
|
9551
10163
|
const dialogSessionRef = React.useRef(0);
|
|
9552
10164
|
const [dialogSessionId, setDialogSessionId] = React.useState(0);
|
|
10165
|
+
const membersWidgetId = widgetConfig['widget-id'];
|
|
9553
10166
|
const addDialogTitle = translateConfig(widgetConfig['widget-data-dialog-title-add']) ||
|
|
9554
10167
|
translate('table.addRecordDialog') ||
|
|
9555
10168
|
'Add record';
|
|
@@ -9560,19 +10173,35 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9560
10173
|
const emptyRow = {};
|
|
9561
10174
|
columns.forEach((col) => {
|
|
9562
10175
|
const key = col['column-key'];
|
|
9563
|
-
|
|
10176
|
+
if (col['widget-data-default'] !== undefined) {
|
|
10177
|
+
emptyRow[key] = col['widget-data-default'];
|
|
10178
|
+
}
|
|
10179
|
+
else if (col.widget === 'checkbox') {
|
|
10180
|
+
emptyRow[key] = false;
|
|
10181
|
+
}
|
|
9564
10182
|
});
|
|
9565
10183
|
return emptyRow;
|
|
9566
10184
|
}, [columns]);
|
|
9567
|
-
const dialogFieldWidgetId = React.useCallback((columnKey) => `${
|
|
10185
|
+
const dialogFieldWidgetId = React.useCallback((sessionId, columnKey) => `${membersWidgetId}-dlg-${sessionId}-${columnKey}`, [membersWidgetId]);
|
|
9568
10186
|
const resetDialogWidgets = React.useCallback((sessionId) => {
|
|
9569
10187
|
if (sessionId <= 0)
|
|
9570
10188
|
return;
|
|
9571
10189
|
columns.forEach((col) => {
|
|
9572
|
-
|
|
9573
|
-
|
|
10190
|
+
dispatch(resetWidget(dialogFieldWidgetId(sessionId, col['column-key'])));
|
|
10191
|
+
});
|
|
10192
|
+
}, [columns, dialogFieldWidgetId, dispatch]);
|
|
10193
|
+
const seedDialogReduxValues = React.useCallback((sessionId, rowData) => {
|
|
10194
|
+
const seeds = {};
|
|
10195
|
+
columns.forEach((col) => {
|
|
10196
|
+
const key = col['column-key'];
|
|
10197
|
+
if (rowData[key] !== undefined) {
|
|
10198
|
+
seeds[dialogFieldWidgetId(sessionId, key)] = rowData[key];
|
|
10199
|
+
}
|
|
9574
10200
|
});
|
|
9575
|
-
|
|
10201
|
+
if (Object.keys(seeds).length > 0) {
|
|
10202
|
+
dispatch(setValues(seeds));
|
|
10203
|
+
}
|
|
10204
|
+
}, [columns, dialogFieldWidgetId, dispatch]);
|
|
9576
10205
|
const beginDialogSession = React.useCallback(() => {
|
|
9577
10206
|
dialogSessionRef.current += 1;
|
|
9578
10207
|
const nextSession = dialogSessionRef.current;
|
|
@@ -9581,15 +10210,21 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9581
10210
|
}, []);
|
|
9582
10211
|
const openAddDialog = React.useCallback(() => {
|
|
9583
10212
|
resetDialogWidgets(dialogSessionId);
|
|
9584
|
-
beginDialogSession();
|
|
10213
|
+
const sessionId = beginDialogSession();
|
|
10214
|
+
seedDialogReduxValues(sessionId, buildEmptyRow());
|
|
9585
10215
|
setDialogMode('add');
|
|
9586
10216
|
setActiveRowIndex(null);
|
|
9587
|
-
setFormData(buildEmptyRow());
|
|
9588
10217
|
setDialogOpen(true);
|
|
9589
|
-
}, [
|
|
10218
|
+
}, [
|
|
10219
|
+
buildEmptyRow,
|
|
10220
|
+
beginDialogSession,
|
|
10221
|
+
resetDialogWidgets,
|
|
10222
|
+
dialogSessionId,
|
|
10223
|
+
seedDialogReduxValues,
|
|
10224
|
+
]);
|
|
9590
10225
|
const openEditDialog = React.useCallback((rowIndex) => {
|
|
9591
10226
|
resetDialogWidgets(dialogSessionId);
|
|
9592
|
-
beginDialogSession();
|
|
10227
|
+
const sessionId = beginDialogSession();
|
|
9593
10228
|
const row = rows[rowIndex] || {};
|
|
9594
10229
|
const nextFormData = buildEmptyRow();
|
|
9595
10230
|
columns.forEach((col) => {
|
|
@@ -9597,44 +10232,71 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9597
10232
|
if (row[key] !== undefined)
|
|
9598
10233
|
nextFormData[key] = row[key];
|
|
9599
10234
|
});
|
|
10235
|
+
seedDialogReduxValues(sessionId, nextFormData);
|
|
9600
10236
|
setDialogMode('edit');
|
|
9601
10237
|
setActiveRowIndex(rowIndex);
|
|
9602
|
-
setFormData(nextFormData);
|
|
9603
10238
|
setDialogOpen(true);
|
|
9604
|
-
}, [
|
|
10239
|
+
}, [
|
|
10240
|
+
rows,
|
|
10241
|
+
columns,
|
|
10242
|
+
buildEmptyRow,
|
|
10243
|
+
resetDialogWidgets,
|
|
10244
|
+
dialogSessionId,
|
|
10245
|
+
beginDialogSession,
|
|
10246
|
+
seedDialogReduxValues,
|
|
10247
|
+
]);
|
|
9605
10248
|
const closeDialog = React.useCallback(() => {
|
|
9606
10249
|
const sessionToClear = dialogSessionId;
|
|
9607
10250
|
setDialogOpen(false);
|
|
9608
10251
|
setActiveRowIndex(null);
|
|
9609
|
-
setFormData({});
|
|
9610
10252
|
resetDialogWidgets(sessionToClear);
|
|
9611
10253
|
setDialogSessionId(0);
|
|
9612
10254
|
}, [dialogSessionId, resetDialogWidgets]);
|
|
9613
|
-
const
|
|
9614
|
-
|
|
9615
|
-
|
|
9616
|
-
|
|
9617
|
-
const
|
|
10255
|
+
const dialogStoreValues = reactRedux.useSelector((state) => {
|
|
10256
|
+
if (dialogSessionId <= 0) {
|
|
10257
|
+
return {};
|
|
10258
|
+
}
|
|
10259
|
+
const values = state.widget?.values ?? {};
|
|
10260
|
+
const row = {};
|
|
9618
10261
|
columns.forEach((col) => {
|
|
9619
10262
|
const k = col['column-key'];
|
|
9620
|
-
const wid = dialogFieldWidgetId(k);
|
|
9621
|
-
|
|
9622
|
-
|
|
9623
|
-
|
|
10263
|
+
const wid = dialogFieldWidgetId(dialogSessionId, k);
|
|
10264
|
+
if (values[wid] !== undefined) {
|
|
10265
|
+
row[k] = values[wid];
|
|
10266
|
+
}
|
|
10267
|
+
});
|
|
10268
|
+
return row;
|
|
10269
|
+
});
|
|
10270
|
+
const dialogRowValues = dialogStoreValues;
|
|
10271
|
+
const collectMergedRowPayload = React.useCallback(() => dialogStoreValues, [dialogStoreValues]);
|
|
10272
|
+
const finalizeDialogRowPayload = React.useCallback((raw) => {
|
|
10273
|
+
const result = {};
|
|
10274
|
+
columns.forEach((col) => {
|
|
10275
|
+
const key = col['column-key'];
|
|
10276
|
+
if (!shouldShowWidget(col['widget-data-options'], raw)) {
|
|
10277
|
+
return;
|
|
10278
|
+
}
|
|
10279
|
+
const val = raw[key];
|
|
10280
|
+
if (!isUnsetRowValue(val)) {
|
|
10281
|
+
result[key] = val;
|
|
10282
|
+
}
|
|
9624
10283
|
});
|
|
9625
|
-
return
|
|
9626
|
-
}, [
|
|
10284
|
+
return result;
|
|
10285
|
+
}, [columns]);
|
|
9627
10286
|
const saveDialog = React.useCallback(() => {
|
|
9628
10287
|
const payload = collectMergedRowPayload();
|
|
9629
10288
|
let hasErrors = false;
|
|
9630
10289
|
columns.forEach((col) => {
|
|
9631
10290
|
const key = col['column-key'];
|
|
9632
|
-
const cellWidgetId = dialogFieldWidgetId(key);
|
|
10291
|
+
const cellWidgetId = dialogFieldWidgetId(dialogSessionId, key);
|
|
9633
10292
|
const isColReadonly = isReadonly || col['widget-readonly'] === true;
|
|
9634
10293
|
if (isColReadonly)
|
|
9635
10294
|
return;
|
|
10295
|
+
if (!shouldShowWidget(col['widget-data-options'], payload))
|
|
10296
|
+
return;
|
|
9636
10297
|
const cellValue = payload[key];
|
|
9637
|
-
const
|
|
10298
|
+
const isRequired = shouldRequireWidget(col['widget-data-options'], payload, col['widget-required']);
|
|
10299
|
+
const validationErrors = validateWidget(cellValue, col['widget-data-validation'], isRequired);
|
|
9638
10300
|
if (validationErrors && validationErrors.length > 0) {
|
|
9639
10301
|
hasErrors = true;
|
|
9640
10302
|
dispatch(setError({ widgetId: cellWidgetId, errors: validationErrors }));
|
|
@@ -9647,8 +10309,9 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9647
10309
|
if (hasErrors) {
|
|
9648
10310
|
return;
|
|
9649
10311
|
}
|
|
10312
|
+
const cleaned = finalizeDialogRowPayload(payload);
|
|
9650
10313
|
if (dialogMode === 'add') {
|
|
9651
|
-
const savedRow = { ...
|
|
10314
|
+
const savedRow = { ...cleaned, edit_action: 'ADD' };
|
|
9652
10315
|
onChange([...rows, savedRow]);
|
|
9653
10316
|
closeDialog();
|
|
9654
10317
|
return;
|
|
@@ -9658,15 +10321,43 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9658
10321
|
const currentRow = newRows[activeRowIndex] || {};
|
|
9659
10322
|
const wasDeleted = currentRow.edit_action === 'DELETE';
|
|
9660
10323
|
const editAction = wasDeleted ? 'UPDATE' : (currentRow.edit_action ?? 'UPDATE');
|
|
9661
|
-
|
|
10324
|
+
const merged = { ...currentRow, ...cleaned, edit_action: editAction };
|
|
10325
|
+
columns.forEach((col) => {
|
|
10326
|
+
const key = col['column-key'];
|
|
10327
|
+
if (!(key in cleaned)) {
|
|
10328
|
+
delete merged[key];
|
|
10329
|
+
}
|
|
10330
|
+
});
|
|
10331
|
+
newRows[activeRowIndex] = merged;
|
|
9662
10332
|
onChange(newRows);
|
|
9663
10333
|
closeDialog();
|
|
9664
10334
|
}
|
|
9665
|
-
}, [
|
|
10335
|
+
}, [
|
|
10336
|
+
collectMergedRowPayload,
|
|
10337
|
+
finalizeDialogRowPayload,
|
|
10338
|
+
dialogMode,
|
|
10339
|
+
onChange,
|
|
10340
|
+
rows,
|
|
10341
|
+
closeDialog,
|
|
10342
|
+
activeRowIndex,
|
|
10343
|
+
columns,
|
|
10344
|
+
dialogSessionId,
|
|
10345
|
+
dialogFieldWidgetId,
|
|
10346
|
+
isReadonly,
|
|
10347
|
+
dispatch,
|
|
10348
|
+
]);
|
|
9666
10349
|
const deleteRow = React.useCallback((rowIndex) => {
|
|
9667
|
-
|
|
9668
|
-
|
|
9669
|
-
|
|
10350
|
+
if (shouldSoftDeleteOnRemove) {
|
|
10351
|
+
const newRows = [...rows];
|
|
10352
|
+
newRows[rowIndex] = {
|
|
10353
|
+
...newRows[rowIndex],
|
|
10354
|
+
edit_action: 'DELETE',
|
|
10355
|
+
};
|
|
10356
|
+
onChange(newRows);
|
|
10357
|
+
return;
|
|
10358
|
+
}
|
|
10359
|
+
onChange(rows.filter((_, i) => i !== rowIndex));
|
|
10360
|
+
}, [rows, onChange, shouldSoftDeleteOnRemove]);
|
|
9670
10361
|
const getDisplayValue = React.useCallback((rowIndex, column) => {
|
|
9671
10362
|
const key = column['column-key'];
|
|
9672
10363
|
const cellValue = rows[rowIndex]?.[key];
|
|
@@ -9674,11 +10365,12 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9674
10365
|
if (cellValue === null || cellValue === undefined || cellValue === '')
|
|
9675
10366
|
return '-';
|
|
9676
10367
|
if (widgetType === 'select')
|
|
9677
|
-
return null;
|
|
10368
|
+
return null;
|
|
9678
10369
|
if (column['widget-data-format'])
|
|
9679
10370
|
return formatValue(cellValue, column['widget-data-format'], column.widget);
|
|
9680
10371
|
return String(cellValue);
|
|
9681
10372
|
}, [rows]);
|
|
10373
|
+
const visibleDialogColumns = React.useMemo(() => columns.filter((col) => shouldShowWidget(col['widget-data-options'], dialogRowValues)), [columns, dialogRowValues]);
|
|
9682
10374
|
const tableWidgetId = `dialog-table-widget-${widgetConfig['widget-id']}`;
|
|
9683
10375
|
const columnSpan = widgetConfig['widget-column-span'] || 2;
|
|
9684
10376
|
const minWidth = columnSpan * 200;
|
|
@@ -9706,37 +10398,40 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9706
10398
|
}, children: translate('table.addRecord') || 'Add New Record' }) })), jsxRuntimeExports.jsx("div", { className: "overflow-x-auto border", style: {
|
|
9707
10399
|
borderRadius: 'var(--owt-widget-table-border-radius, 15px)',
|
|
9708
10400
|
borderColor: 'var(--owt-widget-table-border-color, #C4C4C4)',
|
|
9709
|
-
}, children: jsxRuntimeExports.jsxs("table", { className: "min-w-full", style: { borderCollapse: 'separate', borderSpacing: 0 }, children: [jsxRuntimeExports.jsx("thead", { style: { backgroundColor: 'var(--owt-widget-table-header-bg, #F6F6F6)' }, children: jsxRuntimeExports.jsxs("tr", { style: { borderBottom: '1px solid var(--owt-widget-table-row-divider, #E4E4E4)' }, children: [visibleColumns.map((col) => (jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wider", style: { color: 'var(--owt-widget-table-header-color, #727474)' }, children: translateConfig(col['widget-label']) }, col['column-key']))), ((operations.edit || operations.remove) && !isReadonly) && (jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wider", style: { color: 'var(--owt-widget-table-header-color, #727474)' }, children: translate('common.actions') || 'Actions' }))] }) }), jsxRuntimeExports.jsxs("tbody", { style: { backgroundColor: 'var(--owt-widget-table-body-bg, #FFFFFF)' }, children: [rows.length === 0 && (jsxRuntimeExports.jsx("tr", { children: jsxRuntimeExports.jsxs("td", { colSpan: visibleColumns.length + (((operations.edit || operations.remove) && !isReadonly) ? 1 : 0), className: "px-4 py-6 text-center text-sm", style: { color: 'var(--owt-widget-table-empty-color, #727474)' }, children: [translate('table.noData') || 'No records available.', operations.add && !isReadonly && ` ${translate('table.clickToAdd') || 'Click "Add New Record" to add one.'}`] }) })), rows.map((row, rowIndex) =>
|
|
9710
|
-
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
:
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
|
|
9718
|
-
|
|
9719
|
-
const
|
|
9720
|
-
|
|
9721
|
-
|
|
9722
|
-
|
|
9723
|
-
|
|
9724
|
-
|
|
9725
|
-
|
|
9726
|
-
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
|
|
9730
|
-
|
|
9731
|
-
|
|
9732
|
-
|
|
9733
|
-
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
|
|
10401
|
+
}, children: jsxRuntimeExports.jsxs("table", { className: "min-w-full", style: { borderCollapse: 'separate', borderSpacing: 0 }, children: [jsxRuntimeExports.jsx("thead", { style: { backgroundColor: 'var(--owt-widget-table-header-bg, #F6F6F6)' }, children: jsxRuntimeExports.jsxs("tr", { style: { borderBottom: '1px solid var(--owt-widget-table-row-divider, #E4E4E4)' }, children: [visibleColumns.map((col) => (jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wider", style: { color: 'var(--owt-widget-table-header-color, #727474)' }, children: translateConfig(col['widget-label']) }, col['column-key']))), ((operations.edit || operations.remove) && !isReadonly) && (jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wider", style: { color: 'var(--owt-widget-table-header-color, #727474)' }, children: translate('common.actions') || 'Actions' }))] }) }), jsxRuntimeExports.jsxs("tbody", { style: { backgroundColor: 'var(--owt-widget-table-body-bg, #FFFFFF)' }, children: [rows.length === 0 && (jsxRuntimeExports.jsx("tr", { children: jsxRuntimeExports.jsxs("td", { colSpan: visibleColumns.length + (((operations.edit || operations.remove) && !isReadonly) ? 1 : 0), className: "px-4 py-6 text-center text-sm", style: { color: 'var(--owt-widget-table-empty-color, #727474)' }, children: [translate('table.noData') || 'No records available.', operations.add && !isReadonly && ` ${translate('table.clickToAdd') || 'Click "Add New Record" to add one.'}`] }) })), rows.map((row, rowIndex) => {
|
|
10402
|
+
const cellStyle = getRowCellStyle(row?.edit_action);
|
|
10403
|
+
return (jsxRuntimeExports.jsxs("tr", { style: {
|
|
10404
|
+
borderBottom: '1px solid var(--owt-widget-table-row-divider, #E4E4E4)',
|
|
10405
|
+
backgroundColor: row?.edit_action === 'DELETE'
|
|
10406
|
+
? 'var(--owt-widget-table-deleted-row-bg, #FEE2E2)'
|
|
10407
|
+
: undefined,
|
|
10408
|
+
}, children: [visibleColumns.map((col) => {
|
|
10409
|
+
const key = col['column-key'];
|
|
10410
|
+
const widgetType = col.widget || 'text';
|
|
10411
|
+
const displayValue = getDisplayValue(rowIndex, col);
|
|
10412
|
+
if (widgetType === 'select' && displayValue === null) {
|
|
10413
|
+
const displayConfig = {
|
|
10414
|
+
...col,
|
|
10415
|
+
'widget-id': `${widgetConfig['widget-id']}-view-row-${rowIndex}-col-${key}`,
|
|
10416
|
+
'widget-label': '',
|
|
10417
|
+
'widget-readonly': true,
|
|
10418
|
+
'widget-data-path': undefined,
|
|
10419
|
+
};
|
|
10420
|
+
return (jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", children: jsxRuntimeExports.jsx("div", { className: "text-sm", style: cellStyle, children: jsxRuntimeExports.jsx(SelectDisplayValue, { config: displayConfig, value: row?.[key] }) }) }, key));
|
|
10421
|
+
}
|
|
10422
|
+
return (jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", children: jsxRuntimeExports.jsx("div", { className: "text-sm", style: cellStyle, children: displayValue }) }, key));
|
|
10423
|
+
}), ((operations.edit || operations.remove) && !isReadonly) && (jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", style: { minWidth: '120px' }, children: jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [operations.edit && (jsxRuntimeExports.jsx("button", { type: "button", onClick: () => openEditDialog(rowIndex), disabled: !isEnabled, className: "px-3 py-1 text-xs disabled:opacity-50 disabled:cursor-not-allowed", style: {
|
|
10424
|
+
borderRadius: 'var(--owt-btn-border-radius, 10px)',
|
|
10425
|
+
color: 'var(--owt-color-primary-dark, #F07B1A)',
|
|
10426
|
+
backgroundColor: 'transparent',
|
|
10427
|
+
border: 'none',
|
|
10428
|
+
}, children: translate('common.edit') || 'Edit' })), operations.remove && (jsxRuntimeExports.jsx("button", { type: "button", onClick: () => deleteRow(rowIndex), disabled: !isEnabled, className: "px-3 py-1 text-xs disabled:opacity-50 disabled:cursor-not-allowed", style: {
|
|
10429
|
+
borderRadius: 'var(--owt-btn-border-radius, 10px)',
|
|
10430
|
+
color: 'var(--owt-color-error, #B91C1C)',
|
|
10431
|
+
backgroundColor: 'transparent',
|
|
10432
|
+
border: 'none',
|
|
10433
|
+
}, children: translate('common.remove') || 'Delete' }))] }) }))] }, rowIndex));
|
|
10434
|
+
})] })] }) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-sm mt-1", style: { color: 'var(--owt-widget-error-color, #B91C1C)' }, children: error[0] }))] }), dialogOpen && (jsxRuntimeExports.jsx("div", { className: "fixed inset-0 flex items-center justify-center z-50", style: { backgroundColor: 'rgba(0,0,0,0.5)' }, children: jsxRuntimeExports.jsxs("div", { className: "rounded-lg p-6 w-full mx-4", style: {
|
|
9740
10435
|
maxWidth: '900px',
|
|
9741
10436
|
backgroundColor: 'var(--owt-color-bg, #FFFFFF)',
|
|
9742
10437
|
borderRadius: 'var(--owt-widget-card-border-radius, 20px)',
|
|
@@ -9747,22 +10442,10 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9747
10442
|
cursor: 'pointer',
|
|
9748
10443
|
fontSize: '20px',
|
|
9749
10444
|
lineHeight: 1,
|
|
9750
|
-
}, "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:
|
|
10445
|
+
}, "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: visibleDialogColumns.map((col) => {
|
|
9751
10446
|
const key = col['column-key'];
|
|
9752
|
-
const
|
|
9753
|
-
|
|
9754
|
-
const initialValue = formData[key] ?? col['widget-data-default'] ?? '';
|
|
9755
|
-
const fieldConfig = {
|
|
9756
|
-
...col,
|
|
9757
|
-
widget: widgetType,
|
|
9758
|
-
'widget-type': col['widget-type'] || 'input',
|
|
9759
|
-
'widget-id': cellWidgetId,
|
|
9760
|
-
'widget-label': col['widget-label'],
|
|
9761
|
-
'widget-readonly': isReadonly || col['widget-readonly'] === true,
|
|
9762
|
-
'widget-data-path': undefined,
|
|
9763
|
-
'widget-data-default': initialValue,
|
|
9764
|
-
};
|
|
9765
|
-
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}`));
|
|
10447
|
+
const cellWidgetId = dialogFieldWidgetId(dialogSessionId, key);
|
|
10448
|
+
return (jsxRuntimeExports.jsx(DialogTableField, { col: col, cellWidgetId: cellWidgetId, dialogRowValues: dialogRowValues, isReadonly: isReadonly }, `${dialogSessionId}-${key}`));
|
|
9766
10449
|
}) }, `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: {
|
|
9767
10450
|
borderRadius: 'var(--owt-btn-border-radius, 10px)',
|
|
9768
10451
|
border: '1px solid var(--owt-btn-secondary-border, #C4C4C4)',
|
|
@@ -9952,13 +10635,13 @@ const ProfileWidget = ({ config }) => {
|
|
|
9952
10635
|
if (placeholder) {
|
|
9953
10636
|
placeholder.style.display = 'flex';
|
|
9954
10637
|
}
|
|
9955
|
-
} })) : null, jsxRuntimeExports.jsx("div", { className: "profile-avatar-placeholder", style: { display: imageUrl ? 'none' : 'flex' }, children: jsxRuntimeExports.jsx("img", { src: img$
|
|
10638
|
+
} })) : null, jsxRuntimeExports.jsx("div", { className: "profile-avatar-placeholder", style: { display: imageUrl ? 'none' : 'flex' }, children: jsxRuntimeExports.jsx("img", { src: img$h, alt: "Profile Placeholder" }) })] }), jsxRuntimeExports.jsxs("div", { className: "profile-info", children: [displayName && (jsxRuntimeExports.jsx("div", { className: "profile-name", children: displayName })), idValue && (jsxRuntimeExports.jsxs("div", { className: "profile-id", children: [showIdLabel && (jsxRuntimeExports.jsx("span", { className: "profile-id-label", children: "ID :" })), jsxRuntimeExports.jsx("span", { className: "profile-id-value", children: idValue })] }))] })] })] }));
|
|
9956
10639
|
};
|
|
9957
10640
|
|
|
9958
10641
|
const TextAreaWidget = ({ config }) => {
|
|
9959
10642
|
// Check readonly early from original config
|
|
9960
10643
|
const isReadonly = config['widget-readonly'] || false;
|
|
9961
|
-
const { value, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
10644
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
9962
10645
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
9963
10646
|
const formatConfig = widgetConfig['widget-data-format'] || {};
|
|
9964
10647
|
const validationConfig = widgetConfig['widget-data-validation'] || {};
|
|
@@ -10006,7 +10689,6 @@ const TextAreaWidget = ({ config }) => {
|
|
|
10006
10689
|
? translateConfig(widgetConfig['widget-label'])
|
|
10007
10690
|
: '';
|
|
10008
10691
|
// Check if required
|
|
10009
|
-
const isRequired = widgetConfig['widget-required'] || false;
|
|
10010
10692
|
// Error display
|
|
10011
10693
|
const hasError = touched && error && error.length > 0;
|
|
10012
10694
|
const errorMessage = hasError ? error[0] : '';
|
|
@@ -10024,7 +10706,7 @@ const TextAreaWidget = ({ config }) => {
|
|
|
10024
10706
|
border: 'none',
|
|
10025
10707
|
}, children: displayValue }) })] }));
|
|
10026
10708
|
}
|
|
10027
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.
|
|
10709
|
+
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
|
|
10028
10710
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
10029
10711
|
: 'border-gray-300'} ${!isEnabled ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: {
|
|
10030
10712
|
borderRadius: '10px',
|
|
@@ -10170,17 +10852,16 @@ const HeaderSectionWidget = ({ config }) => {
|
|
|
10170
10852
|
result = searchIn(schemaData);
|
|
10171
10853
|
return result;
|
|
10172
10854
|
}, [paths, values, schemaData]);
|
|
10173
|
-
const imageVal = findValue('image');
|
|
10174
10855
|
const imageUrlVal = findValue('imageUrl');
|
|
10175
10856
|
const [previewUrl, setPreviewUrl] = React.useState(null);
|
|
10176
10857
|
React.useEffect(() => {
|
|
10177
|
-
if (
|
|
10178
|
-
const url = URL.createObjectURL(
|
|
10858
|
+
if (imageUrlVal instanceof File) {
|
|
10859
|
+
const url = URL.createObjectURL(imageUrlVal);
|
|
10179
10860
|
setPreviewUrl(url);
|
|
10180
10861
|
return () => URL.revokeObjectURL(url);
|
|
10181
10862
|
}
|
|
10182
10863
|
setPreviewUrl(null);
|
|
10183
|
-
}, [
|
|
10864
|
+
}, [imageUrlVal]);
|
|
10184
10865
|
const displayImageUrl = previewUrl || (typeof imageUrlVal === 'string' && imageUrlVal ? imageUrlVal : null);
|
|
10185
10866
|
const displayName = findValue('name') || '';
|
|
10186
10867
|
const functionalId = findValue('functionalId') || '';
|
|
@@ -10299,14 +10980,16 @@ const HeaderSectionWidget = ({ config }) => {
|
|
|
10299
10980
|
const fileInputRef = React.useRef(null);
|
|
10300
10981
|
const handleImageUpload = React.useCallback((e) => {
|
|
10301
10982
|
const file = e.target.files?.[0];
|
|
10302
|
-
if (!file)
|
|
10983
|
+
if (!file || !paths.imageUrl)
|
|
10303
10984
|
return;
|
|
10304
|
-
|
|
10985
|
+
dispatch(setValues(setValueByPath({ ...values }, paths.imageUrl, file)));
|
|
10305
10986
|
e.target.value = '';
|
|
10306
|
-
}, [
|
|
10987
|
+
}, [paths.imageUrl, values, dispatch]);
|
|
10307
10988
|
const handleImageDelete = React.useCallback(() => {
|
|
10308
|
-
|
|
10309
|
-
|
|
10989
|
+
if (!paths.imageUrl)
|
|
10990
|
+
return;
|
|
10991
|
+
dispatch(setValues(setValueByPath({ ...values }, paths.imageUrl, null)));
|
|
10992
|
+
}, [paths.imageUrl, values, dispatch]);
|
|
10310
10993
|
// ── Scoped class for CSS isolation ────────────────────────────
|
|
10311
10994
|
const cls = `header-section-widget-${widgetConfig['widget-id']}`;
|
|
10312
10995
|
// ── RENDER ────────────────────────────────────────────────────
|
|
@@ -10615,7 +11298,7 @@ const HeaderSectionWidget = ({ config }) => {
|
|
|
10615
11298
|
.parentElement?.querySelector('.hdr-avatar-placeholder');
|
|
10616
11299
|
if (placeholder)
|
|
10617
11300
|
placeholder.style.display = 'flex';
|
|
10618
|
-
} })) : null, jsxRuntimeExports.jsx("div", { className: "hdr-avatar-placeholder", style: { display: displayImageUrl ? 'none' : 'flex' }, children: jsxRuntimeExports.jsx("img", { src: img$
|
|
11301
|
+
} })) : null, jsxRuntimeExports.jsx("div", { className: "hdr-avatar-placeholder", style: { display: displayImageUrl ? 'none' : 'flex' }, children: jsxRuntimeExports.jsx("img", { src: img$h, alt: "Profile Placeholder" }) }), !isReadonly && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsxs("div", { className: "hdr-avatar-overlay", children: [jsxRuntimeExports.jsxs("button", { type: "button", className: "hdr-avatar-action", onClick: () => fileInputRef.current?.click(), children: [jsxRuntimeExports.jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntimeExports.jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }), jsxRuntimeExports.jsx("polyline", { points: "17 8 12 3 7 8" }), jsxRuntimeExports.jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })] }), "Upload"] }), jsxRuntimeExports.jsxs("button", { type: "button", className: "hdr-avatar-action hdr-avatar-action--delete", onClick: handleImageDelete, children: [jsxRuntimeExports.jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntimeExports.jsx("polyline", { points: "3 6 5 6 21 6" }), jsxRuntimeExports.jsx("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })] }), "Delete"] })] }), jsxRuntimeExports.jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", style: { display: 'none' }, onChange: handleImageUpload })] }))] }), jsxRuntimeExports.jsxs("div", { className: "hdr-info", children: [displayName && jsxRuntimeExports.jsx("div", { className: "hdr-name", children: displayName }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-field-label", title: `${getLabel('functionalId')} :`, children: [getLabel('functionalId'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-field-value", title: functionalId || '-', children: functionalId || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsx("span", { className: "hdr-field-label", title: getLabel('status'), children: getLabel('status') }), isReadonly ? (statusLabel ? (jsxRuntimeExports.jsx("span", { className: "hdr-status-badge", style: { backgroundColor: statusColor }, title: statusLabel, children: statusLabel })) : (jsxRuntimeExports.jsx("span", { className: "hdr-field-value", title: "-", children: "-" }))) : (jsxRuntimeExports.jsxs("select", { className: "hdr-select", value: statusValue, onChange: (e) => updateFieldValue('status', e.target.value), children: [jsxRuntimeExports.jsx("option", { value: "", children: getLabel('select') }), statusOptions.map((opt) => (jsxRuntimeExports.jsx("option", { value: opt.value, children: opt.label }, opt.value)))] }))] }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-field-label", title: `${getLabel('statusReason')} :`, children: [getLabel('statusReason'), " :"] }), isReadonly ? (jsxRuntimeExports.jsx("span", { className: "hdr-field-value", title: statusReason || '-', children: statusReason || '-' })) : (jsxRuntimeExports.jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [jsxRuntimeExports.jsx("input", { type: "text", className: `hdr-input ${(!isReadonly && (showReasonRequired || isReasonMissing) && isReasonMissing) ? 'hdr-input--error' : ''}`, value: statusReason, placeholder: getLabel('enterReason'), required: isStatusChanged, "aria-required": isStatusChanged, "aria-invalid": !isReadonly && (showReasonRequired || isReasonMissing) && isReasonMissing, onBlur: () => {
|
|
10619
11302
|
if (isReasonMissing)
|
|
10620
11303
|
setShowReasonRequired(true);
|
|
10621
11304
|
}, onChange: (e) => {
|
|
@@ -11360,6 +12043,621 @@ const IdAuthenticationWidget = ({ config, schemaData: propSchemaData }) => {
|
|
|
11360
12043
|
}, children: "Close" })] }), jsxRuntimeExports.jsx("iframe", { className: "overlay-iframe", src: overlayUrl, title: "Authentication" })] }) })) : null, jsxRuntimeExports.jsx("div", { className: "auth-content", children: jsxRuntimeExports.jsxs("div", { className: "auth-grid", children: [jsxRuntimeExports.jsxs("div", { className: "auth-cell", children: [jsxRuntimeExports.jsx("div", { className: "auth-label", children: "Foundational ID:" }), jsxRuntimeExports.jsx("div", { className: "auth-value auth-value--foundational", children: displayText(foundationalId) })] }), jsxRuntimeExports.jsxs("div", { className: "auth-cell", children: [jsxRuntimeExports.jsx("div", { className: "auth-label", children: "Last authenticated on:" }), jsxRuntimeExports.jsx("div", { className: "auth-value", children: tryFormatDateTime(lastAuthenticatedOn) })] }), jsxRuntimeExports.jsxs("div", { className: "auth-cell", children: [jsxRuntimeExports.jsx("div", { className: "auth-label", children: "Expiry date:" }), jsxRuntimeExports.jsx("div", { className: "auth-value", children: tryFormatDate(expiryDate) })] }), jsxRuntimeExports.jsxs("div", { className: "auth-cell", children: [jsxRuntimeExports.jsx("div", { className: "auth-label", children: "Last authentication status:" }), jsxRuntimeExports.jsx("div", { className: "auth-value", children: jsxRuntimeExports.jsxs("div", { className: "auth-status", "aria-label": `Authentication status: ${statusLabel}`, children: [jsxRuntimeExports.jsx("span", { className: "auth-dot" }), jsxRuntimeExports.jsx("span", { children: statusLabel })] }) })] }), jsxRuntimeExports.jsxs("div", { className: "auth-cell", children: [jsxRuntimeExports.jsx("div", { className: "auth-label", children: "Authentication token (PSUT):" }), jsxRuntimeExports.jsx("div", { className: "auth-value", children: jsxRuntimeExports.jsx("div", { className: "auth-token", children: psut ? String(psut) : '-' }) })] }), jsxRuntimeExports.jsxs("div", { className: "auth-cell auth-cell--action", children: [jsxRuntimeExports.jsx("div", { className: "auth-label", "aria-hidden": true }), jsxRuntimeExports.jsxs("div", { className: "auth-value", children: [jsxRuntimeExports.jsx("button", { type: "button", className: "auth-button", onClick: onAuthenticate, disabled: buttonDisabled, children: buttonBusy ? 'Loading…' : 'Authenticate' }), authError ? (jsxRuntimeExports.jsx("div", { className: "auth-error", style: { marginTop: 8 }, children: authError })) : null] })] })] }) })] })] }));
|
|
11361
12044
|
};
|
|
11362
12045
|
|
|
12046
|
+
/**
|
|
12047
|
+
* Register lookup widget — searchable popup to select a record from any register.
|
|
12048
|
+
* Reads ID from widget-data-path, finds matching register row by internal_record_id, shows display fields.
|
|
12049
|
+
*
|
|
12050
|
+
* Example (individual → household link):
|
|
12051
|
+
*
|
|
12052
|
+
* {
|
|
12053
|
+
* "widget": "register-lookup",
|
|
12054
|
+
* "widget-id": "link_internal_record_id",
|
|
12055
|
+
* "widget-type": "input",
|
|
12056
|
+
* "widget-label": "Household",
|
|
12057
|
+
* "widget-required": true,
|
|
12058
|
+
* "widget-data-path": "<section_register_ids>.link_internal_record_id",
|
|
12059
|
+
* "widget-data-source": {
|
|
12060
|
+
* "type": "api",
|
|
12061
|
+
* "method": "POST",
|
|
12062
|
+
* "params": {
|
|
12063
|
+
* "register_id": "<target_register_id>"
|
|
12064
|
+
* },
|
|
12065
|
+
* "service": "register",
|
|
12066
|
+
* "endpoint": "records"
|
|
12067
|
+
* },
|
|
12068
|
+
* "widget-lookup-config": {
|
|
12069
|
+
* "page_size": 10,
|
|
12070
|
+
* "action_label": "Click to Search Household",
|
|
12071
|
+
* "search_placeholder": "Search by name or ID...",
|
|
12072
|
+
* "select_record_label": "Select Household"
|
|
12073
|
+
* }
|
|
12074
|
+
* }
|
|
12075
|
+
*/
|
|
12076
|
+
const normalizeDisplayFields = (row) => {
|
|
12077
|
+
if (!Array.isArray(row.display_fields))
|
|
12078
|
+
return [];
|
|
12079
|
+
return [...row.display_fields]
|
|
12080
|
+
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
|
|
12081
|
+
.map((f) => ({
|
|
12082
|
+
label: String(f.field_name ?? ''),
|
|
12083
|
+
value: f.value !== null && f.value !== undefined ? String(f.value) : '-',
|
|
12084
|
+
}));
|
|
12085
|
+
};
|
|
12086
|
+
const parsePagination = (pagination, rowCount, size, fallbackPage = 1) => {
|
|
12087
|
+
const totalItems = typeof pagination.number_of_items === 'number' ? pagination.number_of_items : rowCount;
|
|
12088
|
+
const totalPages = typeof pagination.number_of_pages === 'number'
|
|
12089
|
+
? Math.max(1, pagination.number_of_pages)
|
|
12090
|
+
: totalItems > 0
|
|
12091
|
+
? Math.max(1, Math.ceil(totalItems / size))
|
|
12092
|
+
: 1;
|
|
12093
|
+
return { totalItems, totalPages, currentPage: pagination.current_page ?? fallbackPage };
|
|
12094
|
+
};
|
|
12095
|
+
const RecordDisplayPanel = ({ row, widgetIdPrefix, className = '', }) => {
|
|
12096
|
+
const { translateConfig } = useWidgetTranslation();
|
|
12097
|
+
const columnFields = normalizeDisplayFields(row).filter((f) => f.label !== 'record_name' && f.label !== 'functional_record_id' && f.label !== 'internal_record_id');
|
|
12098
|
+
const fieldSlot = (widgetId, label, value) => (jsxRuntimeExports.jsx("div", { className: "min-w-0 overflow-hidden", children: jsxRuntimeExports.jsx(WidgetRenderer, { config: {
|
|
12099
|
+
widget: 'display',
|
|
12100
|
+
'widget-type': 'input',
|
|
12101
|
+
'widget-id': widgetId,
|
|
12102
|
+
'widget-label': label,
|
|
12103
|
+
'widget-readonly': true,
|
|
12104
|
+
'widget-data-default': value,
|
|
12105
|
+
}, schemaData: { [widgetId]: value } }) }, widgetId));
|
|
12106
|
+
const optionalSlot = (field, slot) => field
|
|
12107
|
+
? fieldSlot(`${widgetIdPrefix}-${field.label}`, translateConfig(field.label), field.value)
|
|
12108
|
+
: jsxRuntimeExports.jsx("div", { className: "mb-[10px] invisible text-base", children: "\u00A0" }, slot);
|
|
12109
|
+
const column = (showDivider, isFirst, children) => (jsxRuntimeExports.jsxs("div", { className: "relative min-w-0 overflow-hidden", style: { paddingRight: showDivider ? '40px' : undefined, paddingLeft: isFirst ? undefined : '40px' }, children: [children, showDivider && (jsxRuntimeExports.jsx("div", { className: "absolute right-0 top-0 bottom-[5px] w-px", style: { backgroundColor: 'var(--owt-panel-divider-color, #C4C4C4)' } }))] }));
|
|
12110
|
+
return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("style", { children: `
|
|
12111
|
+
.register-lookup-record-panel .DisplayFieldWidget,
|
|
12112
|
+
.register-lookup-record-panel .widget-container {
|
|
12113
|
+
min-width: 0 !important;
|
|
12114
|
+
overflow: hidden !important;
|
|
12115
|
+
}
|
|
12116
|
+
.register-lookup-record-panel .DisplayFieldWidget > .flex-1 {
|
|
12117
|
+
min-width: 0 !important;
|
|
12118
|
+
overflow: hidden !important;
|
|
12119
|
+
}
|
|
12120
|
+
.register-lookup-record-panel .DisplayFieldWidget > .text-base.text-gray-600 {
|
|
12121
|
+
width: 50% !important;
|
|
12122
|
+
min-width: 50% !important;
|
|
12123
|
+
max-width: 50% !important;
|
|
12124
|
+
flex-shrink: 0 !important;
|
|
12125
|
+
overflow: hidden !important;
|
|
12126
|
+
text-overflow: ellipsis !important;
|
|
12127
|
+
white-space: nowrap !important;
|
|
12128
|
+
}
|
|
12129
|
+
.register-lookup-record-panel .DisplayFieldWidget > .flex-1 > .text-gray-900 {
|
|
12130
|
+
overflow: hidden;
|
|
12131
|
+
text-overflow: ellipsis;
|
|
12132
|
+
white-space: nowrap;
|
|
12133
|
+
}
|
|
12134
|
+
` }), jsxRuntimeExports.jsxs("div", { className: `register-lookup-record-panel grid w-full min-w-0 ${className}`, style: { gridTemplateColumns: 'repeat(3, minmax(200px, 1fr))' }, children: [column(true, true, (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [fieldSlot(`${widgetIdPrefix}-record_name`, translateConfig('record_name'), row.record_name == null || row.record_name === '' ? '-' : String(row.record_name)), fieldSlot(`${widgetIdPrefix}-functional_record_id`, translateConfig('functional_record_id'), row.functional_record_id == null || row.functional_record_id === '' ? '-' : String(row.functional_record_id))] }))), column(true, false, [0, 1, 2].map((i) => optionalSlot(columnFields[i], `mid-${i}`))), column(false, false, [0, 1, 2].map((i) => optionalSlot(columnFields[i + 3], `right-${i}`)))] })] }));
|
|
12135
|
+
};
|
|
12136
|
+
const PaginationFooter = ({ currentPage, totalPages, totalCount, pageSize, onPageChange, onPrev, onNext, translate, embedded, }) => {
|
|
12137
|
+
const [pageInput, setPageInput] = React.useState(String(currentPage));
|
|
12138
|
+
React.useEffect(() => {
|
|
12139
|
+
setPageInput(String(currentPage));
|
|
12140
|
+
}, [currentPage]);
|
|
12141
|
+
const pageStart = totalCount === 0 ? 0 : (currentPage - 1) * pageSize + 1;
|
|
12142
|
+
const pageEnd = totalCount === 0 ? 0 : Math.min(currentPage * pageSize, totalCount);
|
|
12143
|
+
const commitPage = () => {
|
|
12144
|
+
const parsed = parseInt(pageInput, 10);
|
|
12145
|
+
if (!Number.isFinite(parsed)) {
|
|
12146
|
+
setPageInput(String(currentPage));
|
|
12147
|
+
return;
|
|
12148
|
+
}
|
|
12149
|
+
const page = Math.min(Math.max(1, parsed), totalPages);
|
|
12150
|
+
setPageInput(String(page));
|
|
12151
|
+
if (page !== currentPage)
|
|
12152
|
+
onPageChange(page);
|
|
12153
|
+
};
|
|
12154
|
+
return (jsxRuntimeExports.jsxs("div", { className: embedded
|
|
12155
|
+
? 'flex flex-wrap items-center gap-3 flex-1 min-w-0'
|
|
12156
|
+
: 'flex flex-wrap items-center justify-between gap-3 px-5 py-3 flex-shrink-0 border-t border-gray-200', children: [jsxRuntimeExports.jsxs("span", { className: "text-sm text-gray-600", children: [totalCount === 1
|
|
12157
|
+
? translate('common.record', { count: totalCount, defaultValue: `${totalCount} record` })
|
|
12158
|
+
: translate('common.records', { count: totalCount, defaultValue: `${totalCount} records` }), totalCount > 0 && ` · ${pageStart}-${pageEnd}`] }), jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntimeExports.jsx("button", { type: "button", onClick: onPrev, disabled: currentPage <= 1, className: "px-3 h-8 text-sm font-medium rounded-[10px] bg-gray-100 text-gray-700 disabled:opacity-40 disabled:cursor-not-allowed", children: translate('common.previous', { defaultValue: 'Prev' }) }), jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 text-sm text-gray-600", children: [jsxRuntimeExports.jsx("span", { children: translate('common.page', { defaultValue: 'Page' }) }), jsxRuntimeExports.jsx("input", { type: "text", inputMode: "numeric", value: pageInput, onChange: (e) => setPageInput(e.target.value.replace(/\D/g, '')), onKeyDown: (e) => {
|
|
12159
|
+
if (e.key === 'Enter') {
|
|
12160
|
+
e.preventDefault();
|
|
12161
|
+
commitPage();
|
|
12162
|
+
}
|
|
12163
|
+
}, onBlur: commitPage, className: "w-10 h-8 text-center text-sm text-gray-900 outline-none rounded-[10px] border border-gray-300 bg-white", "aria-label": translate('common.pageNumber', { defaultValue: 'Page number' }) }), jsxRuntimeExports.jsx("span", { children: translate('common.ofPages', { total: totalPages, defaultValue: `of ${totalPages}` }) })] }), jsxRuntimeExports.jsx("button", { type: "button", onClick: onNext, disabled: currentPage >= totalPages, className: "px-3 h-8 text-sm font-medium rounded-[10px] bg-gray-100 text-gray-700 disabled:opacity-40 disabled:cursor-not-allowed", children: translate('common.next', { defaultValue: 'Next' }) })] })] }));
|
|
12164
|
+
};
|
|
12165
|
+
const ResultsTable = ({ rows, selectedRowKey, onRowClick, onRowDoubleClick, }) => {
|
|
12166
|
+
const { translateConfig } = useWidgetTranslation();
|
|
12167
|
+
const columns = rows.length === 0
|
|
12168
|
+
? []
|
|
12169
|
+
: [
|
|
12170
|
+
{ key: 'record_name', header: 'record_name' },
|
|
12171
|
+
...normalizeDisplayFields(rows[0]).map((f) => ({ key: f.label, header: f.label })),
|
|
12172
|
+
];
|
|
12173
|
+
return (jsxRuntimeExports.jsx("div", { className: "overflow-auto h-full", children: jsxRuntimeExports.jsxs("table", { className: "w-full text-sm border-collapse", children: [jsxRuntimeExports.jsx("thead", { className: "sticky top-0 z-[1] bg-gray-50", children: jsxRuntimeExports.jsx("tr", { children: columns.map((col) => (jsxRuntimeExports.jsx("th", { className: "text-left px-4 py-2 text-sm font-medium text-gray-600 whitespace-nowrap border-b border-gray-200 bg-gray-50", children: translateConfig(col.header) }, col.key))) }) }), jsxRuntimeExports.jsx("tbody", { children: rows.map((row, idx) => {
|
|
12174
|
+
const tableRowKey = row.internal_record_id ?? idx;
|
|
12175
|
+
const isSelected = selectedRowKey != null && tableRowKey === selectedRowKey;
|
|
12176
|
+
return (jsxRuntimeExports.jsx("tr", { onClick: () => onRowClick(row), onDoubleClick: () => onRowDoubleClick?.(row), className: `cursor-pointer border-b border-gray-100 transition-colors ${isSelected ? 'bg-blue-100' : 'hover:bg-blue-50'}`, children: columns.map((col) => {
|
|
12177
|
+
const cellValue = col.key === 'record_name'
|
|
12178
|
+
? row.record_name != null
|
|
12179
|
+
? String(row.record_name)
|
|
12180
|
+
: '-'
|
|
12181
|
+
: normalizeDisplayFields(row).find((f) => f.label === col.key)?.value ?? '-';
|
|
12182
|
+
return (jsxRuntimeExports.jsx("td", { className: "px-4 py-2 text-sm text-gray-900 whitespace-nowrap", children: cellValue }, col.key));
|
|
12183
|
+
}) }, tableRowKey));
|
|
12184
|
+
}) })] }) }));
|
|
12185
|
+
};
|
|
12186
|
+
const RegisterLookupWidget = ({ config }) => {
|
|
12187
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
12188
|
+
const { translate, translateConfig } = useWidgetTranslation();
|
|
12189
|
+
const { dataSourceRequestHandler } = useWidgetContext();
|
|
12190
|
+
const dataSource = widgetConfig['widget-data-source'];
|
|
12191
|
+
const lookupConfig = widgetConfig['widget-lookup-config'];
|
|
12192
|
+
const pageSize = lookupConfig?.page_size ?? 10;
|
|
12193
|
+
const widgetIdPrefix = `${widgetConfig['widget-id'] || 'register-lookup'}-record`;
|
|
12194
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
12195
|
+
const [searchText, setSearchText] = React.useState('');
|
|
12196
|
+
const [searchResults, setSearchResults] = React.useState([]);
|
|
12197
|
+
const [currentPage, setCurrentPage] = React.useState(1);
|
|
12198
|
+
const [totalPages, setTotalPages] = React.useState(1);
|
|
12199
|
+
const [totalCount, setTotalCount] = React.useState(null);
|
|
12200
|
+
const [pendingRow, setPendingRow] = React.useState(null);
|
|
12201
|
+
const [appliedRecord, setAppliedRecord] = React.useState(null);
|
|
12202
|
+
const [isHydrating, setIsHydrating] = React.useState(false);
|
|
12203
|
+
const [modalPos, setModalPos] = React.useState({ x: 80, y: 80 });
|
|
12204
|
+
const [modalSize, setModalSize] = React.useState({ w: 860, h: 520 });
|
|
12205
|
+
const isDragging = React.useRef(false);
|
|
12206
|
+
const dragOrigin = React.useRef({ mouseX: 0, mouseY: 0, posX: 0, posY: 0 });
|
|
12207
|
+
const searchInputRef = React.useRef(null);
|
|
12208
|
+
const hydratedValueRef = React.useRef(null);
|
|
12209
|
+
const fetchRecords = React.useCallback(async (text, page, size) => {
|
|
12210
|
+
if (!dataSource?.service || !dataSource?.endpoint || !dataSourceRequestHandler) {
|
|
12211
|
+
return { rows: [], pagination: {} };
|
|
12212
|
+
}
|
|
12213
|
+
const result = await dataSourceRequestHandler(dataSource.service, dataSource.endpoint, dataSource.method, {
|
|
12214
|
+
...(dataSource.params || {}),
|
|
12215
|
+
search_text: text,
|
|
12216
|
+
current_page: page,
|
|
12217
|
+
page_size: size,
|
|
12218
|
+
}, { headers: dataSource.headers });
|
|
12219
|
+
return {
|
|
12220
|
+
rows: (result?.records ?? []),
|
|
12221
|
+
pagination: (result?.pagination ?? {}),
|
|
12222
|
+
};
|
|
12223
|
+
}, [dataSource, dataSourceRequestHandler]);
|
|
12224
|
+
const findRecordByValue = React.useCallback(async (recordValue) => {
|
|
12225
|
+
const target = String(recordValue).trim();
|
|
12226
|
+
if (!target)
|
|
12227
|
+
return null;
|
|
12228
|
+
const hydratePageSize = lookupConfig?.hydrate_page_size ?? 50;
|
|
12229
|
+
let page = 1;
|
|
12230
|
+
let totalPages = 1;
|
|
12231
|
+
while (page <= totalPages) {
|
|
12232
|
+
const { rows, pagination } = await fetchRecords('', page, hydratePageSize);
|
|
12233
|
+
const match = rows.find((row) => String(row.internal_record_id ?? '').trim() === target);
|
|
12234
|
+
if (match)
|
|
12235
|
+
return match;
|
|
12236
|
+
totalPages = parsePagination(pagination, rows.length, hydratePageSize).totalPages;
|
|
12237
|
+
if (page >= totalPages)
|
|
12238
|
+
break;
|
|
12239
|
+
page += 1;
|
|
12240
|
+
}
|
|
12241
|
+
return null;
|
|
12242
|
+
}, [fetchRecords, lookupConfig?.hydrate_page_size]);
|
|
12243
|
+
const runSearch = React.useCallback(async (text, page = 1) => {
|
|
12244
|
+
try {
|
|
12245
|
+
const { rows, pagination } = await fetchRecords(text, page, pageSize);
|
|
12246
|
+
const parsed = parsePagination(pagination, rows.length, pageSize, page);
|
|
12247
|
+
setSearchResults(rows);
|
|
12248
|
+
setTotalCount(parsed.totalItems);
|
|
12249
|
+
setTotalPages(parsed.totalPages);
|
|
12250
|
+
setCurrentPage(parsed.currentPage);
|
|
12251
|
+
}
|
|
12252
|
+
catch {
|
|
12253
|
+
setSearchResults([]);
|
|
12254
|
+
setTotalCount(null);
|
|
12255
|
+
setTotalPages(1);
|
|
12256
|
+
setCurrentPage(1);
|
|
12257
|
+
}
|
|
12258
|
+
}, [fetchRecords, pageSize]);
|
|
12259
|
+
React.useEffect(() => {
|
|
12260
|
+
const onMove = (e) => {
|
|
12261
|
+
if (!isDragging.current)
|
|
12262
|
+
return;
|
|
12263
|
+
setModalPos({
|
|
12264
|
+
x: dragOrigin.current.posX + (e.clientX - dragOrigin.current.mouseX),
|
|
12265
|
+
y: dragOrigin.current.posY + (e.clientY - dragOrigin.current.mouseY),
|
|
12266
|
+
});
|
|
12267
|
+
};
|
|
12268
|
+
const onUp = () => { isDragging.current = false; };
|
|
12269
|
+
document.addEventListener('mousemove', onMove);
|
|
12270
|
+
document.addEventListener('mouseup', onUp);
|
|
12271
|
+
return () => {
|
|
12272
|
+
document.removeEventListener('mousemove', onMove);
|
|
12273
|
+
document.removeEventListener('mouseup', onUp);
|
|
12274
|
+
};
|
|
12275
|
+
}, []);
|
|
12276
|
+
const hasValue = value !== null && value !== undefined && value !== '';
|
|
12277
|
+
const applySelection = (row) => {
|
|
12278
|
+
hydratedValueRef.current = row.internal_record_id;
|
|
12279
|
+
onChange(row.internal_record_id);
|
|
12280
|
+
setAppliedRecord(row);
|
|
12281
|
+
setIsOpen(false);
|
|
12282
|
+
};
|
|
12283
|
+
const openLookup = () => {
|
|
12284
|
+
const w = Math.min(Math.round(window.innerWidth * 0.82), 940);
|
|
12285
|
+
const h = Math.min(Math.round(window.innerHeight * 0.72), 560);
|
|
12286
|
+
setModalPos({ x: Math.round((window.innerWidth - w) / 2), y: Math.round((window.innerHeight - h) / 2) });
|
|
12287
|
+
setModalSize({ w, h });
|
|
12288
|
+
setIsOpen(true);
|
|
12289
|
+
setSearchText('');
|
|
12290
|
+
setSearchResults([]);
|
|
12291
|
+
setTotalCount(null);
|
|
12292
|
+
setCurrentPage(1);
|
|
12293
|
+
setTotalPages(1);
|
|
12294
|
+
setPendingRow(appliedRecord);
|
|
12295
|
+
setTimeout(() => searchInputRef.current?.focus(), 50);
|
|
12296
|
+
runSearch('', 1);
|
|
12297
|
+
};
|
|
12298
|
+
React.useEffect(() => {
|
|
12299
|
+
if (!hasValue) {
|
|
12300
|
+
hydratedValueRef.current = null;
|
|
12301
|
+
setAppliedRecord(null);
|
|
12302
|
+
setIsHydrating(false);
|
|
12303
|
+
return;
|
|
12304
|
+
}
|
|
12305
|
+
if (!dataSource?.service || !dataSource?.endpoint || !dataSourceRequestHandler)
|
|
12306
|
+
return;
|
|
12307
|
+
if (hydratedValueRef.current === value)
|
|
12308
|
+
return;
|
|
12309
|
+
let cancelled = false;
|
|
12310
|
+
setIsHydrating(true);
|
|
12311
|
+
setAppliedRecord(null);
|
|
12312
|
+
(async () => {
|
|
12313
|
+
try {
|
|
12314
|
+
const match = await findRecordByValue(value);
|
|
12315
|
+
if (cancelled)
|
|
12316
|
+
return;
|
|
12317
|
+
hydratedValueRef.current = value;
|
|
12318
|
+
setAppliedRecord(match);
|
|
12319
|
+
}
|
|
12320
|
+
catch {
|
|
12321
|
+
if (!cancelled) {
|
|
12322
|
+
hydratedValueRef.current = value;
|
|
12323
|
+
setAppliedRecord(null);
|
|
12324
|
+
}
|
|
12325
|
+
}
|
|
12326
|
+
finally {
|
|
12327
|
+
if (!cancelled)
|
|
12328
|
+
setIsHydrating(false);
|
|
12329
|
+
}
|
|
12330
|
+
})();
|
|
12331
|
+
return () => {
|
|
12332
|
+
cancelled = true;
|
|
12333
|
+
setIsHydrating(false);
|
|
12334
|
+
};
|
|
12335
|
+
}, [hasValue, value, dataSource, dataSourceRequestHandler, findRecordByValue]);
|
|
12336
|
+
const isReadonly = !!widgetConfig['widget-readonly'];
|
|
12337
|
+
const label = translateConfig(widgetConfig['widget-label']);
|
|
12338
|
+
const hasError = (touched && error.length > 0) ||
|
|
12339
|
+
(widgetConfig['widget-required'] && !hasValue);
|
|
12340
|
+
const actionLabel = translateConfig(String(lookupConfig?.action_label ?? `Select ${label}`));
|
|
12341
|
+
const searchPlaceholder = translateConfig(String(lookupConfig?.search_placeholder ?? 'Search...'));
|
|
12342
|
+
const selectRecordLabel = translateConfig(String(lookupConfig?.select_record_label ?? `Select ${label}`));
|
|
12343
|
+
const hydratedPanel = isHydrating ? (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500", children: translate('common.loading', { defaultValue: 'Loading...' }) })) : appliedRecord ? (jsxRuntimeExports.jsx(RecordDisplayPanel, { row: appliedRecord, widgetIdPrefix: `${widgetIdPrefix}-${isReadonly ? 'readonly' : 'applied'}` })) : null;
|
|
12344
|
+
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] w-full", children: [hasValue ? (jsxRuntimeExports.jsxs("div", { className: "w-full", children: [hydratedPanel, !isReadonly && isEnabled && (jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 mt-2", children: [jsxRuntimeExports.jsx("button", { type: "button", onClick: openLookup, className: "text-sm underline p-0 border-0 bg-transparent cursor-pointer focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1 rounded", style: { color: 'var(--owt-color-info, #2563eb)' }, children: translate('common.change', { defaultValue: 'Change' }) }), jsxRuntimeExports.jsx("button", { type: "button", onClick: (e) => {
|
|
12345
|
+
e.stopPropagation();
|
|
12346
|
+
hydratedValueRef.current = null;
|
|
12347
|
+
onChange(null);
|
|
12348
|
+
setAppliedRecord(null);
|
|
12349
|
+
setPendingRow(null);
|
|
12350
|
+
}, 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: {
|
|
12351
|
+
position: 'fixed',
|
|
12352
|
+
top: modalPos.y,
|
|
12353
|
+
left: modalPos.x,
|
|
12354
|
+
width: modalSize.w,
|
|
12355
|
+
height: modalSize.h,
|
|
12356
|
+
zIndex: 51,
|
|
12357
|
+
resize: 'both',
|
|
12358
|
+
minWidth: 340,
|
|
12359
|
+
minHeight: 260,
|
|
12360
|
+
maxWidth: '96vw',
|
|
12361
|
+
maxHeight: '92vh',
|
|
12362
|
+
backgroundColor: 'var(--owt-color-bg, #FFFFFF)',
|
|
12363
|
+
borderRadius: 'var(--owt-widget-card-border-radius, 20px)',
|
|
12364
|
+
boxShadow: '0 24px 64px rgba(0,0,0,0.28)',
|
|
12365
|
+
}, onClick: (e) => e.stopPropagation(), children: [jsxRuntimeExports.jsxs("div", { onMouseDown: (e) => {
|
|
12366
|
+
e.preventDefault();
|
|
12367
|
+
isDragging.current = true;
|
|
12368
|
+
dragOrigin.current = { mouseX: e.clientX, mouseY: e.clientY, posX: modalPos.x, posY: modalPos.y };
|
|
12369
|
+
}, className: "flex items-center justify-between px-5 py-4 flex-shrink-0 select-none border-b border-gray-200 cursor-grab", children: [jsxRuntimeExports.jsx("h3", { className: "text-lg font-semibold text-gray-900", children: translate('common.selectTitle', { label, defaultValue: `Select ${label}` }) }), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => { setIsOpen(false); onBlur(); }, onMouseDown: (e) => e.stopPropagation(), className: "p-0 border-0 bg-transparent cursor-pointer", "aria-label": translate('common.close', { defaultValue: 'Close' }), children: jsxRuntimeExports.jsx("img", { src: img$f, alt: "", className: "w-5 h-5 opacity-60" }) })] }), jsxRuntimeExports.jsx("div", { className: "px-5 py-3 flex-shrink-0 border-b border-gray-200", children: jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 px-3 h-[30px] border border-gray-300 rounded-[10px] bg-white", children: [jsxRuntimeExports.jsx("input", { ref: searchInputRef, type: "text", value: searchText, onChange: (e) => setSearchText(e.target.value), onKeyDown: (e) => {
|
|
12370
|
+
if (e.key === 'Enter') {
|
|
12371
|
+
e.preventDefault();
|
|
12372
|
+
runSearch(searchText, 1);
|
|
12373
|
+
}
|
|
12374
|
+
}, placeholder: searchPlaceholder, className: "flex-1 outline-none text-sm text-gray-900 bg-transparent" }), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => runSearch(searchText, 1), "aria-label": translate('common.search', { defaultValue: 'Search' }), className: "flex-shrink-0 p-0 border-0 bg-transparent cursor-pointer", children: jsxRuntimeExports.jsx("img", { src: img$g, alt: "", className: "w-4 h-4 opacity-40" }) })] }) }), jsxRuntimeExports.jsx("div", { className: "overflow-auto flex-1", children: searchResults.length === 0 ? (jsxRuntimeExports.jsx("p", { className: "text-center text-sm text-gray-500 py-10", children: searchText
|
|
12375
|
+
? translate('common.noResults', { defaultValue: 'No results found' })
|
|
12376
|
+
: 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 })] })] })] }))] }));
|
|
12377
|
+
};
|
|
12378
|
+
|
|
12379
|
+
const MultiSelectWidget = ({ config }) => {
|
|
12380
|
+
const { value, error, touched, isEnabled, isRequired, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
|
|
12381
|
+
const { translate, translateConfig } = useWidgetTranslation();
|
|
12382
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
12383
|
+
const [isListPopupOpen, setIsListPopupOpen] = React.useState(false);
|
|
12384
|
+
const [searchQuery, setSearchQuery] = React.useState('');
|
|
12385
|
+
const [dropdownPosition, setDropdownPosition] = React.useState(null);
|
|
12386
|
+
const [listPopupPosition, setListPopupPosition] = React.useState(null);
|
|
12387
|
+
const [mounted, setMounted] = React.useState(false);
|
|
12388
|
+
const containerRef = React.useRef(null);
|
|
12389
|
+
const triggerRef = React.useRef(null);
|
|
12390
|
+
const dropdownRef = React.useRef(null);
|
|
12391
|
+
const listPopupRef = React.useRef(null);
|
|
12392
|
+
const moreButtonRef = React.useRef(null);
|
|
12393
|
+
const searchInputRef = React.useRef(null);
|
|
12394
|
+
const formatConfig = widgetConfig['widget-data-format'];
|
|
12395
|
+
const sortOptions = formatConfig?.sortOptions ?? false;
|
|
12396
|
+
React.useEffect(() => {
|
|
12397
|
+
setMounted(true);
|
|
12398
|
+
}, []);
|
|
12399
|
+
const updateDropdownPosition = React.useCallback(() => {
|
|
12400
|
+
const trigger = triggerRef.current;
|
|
12401
|
+
if (!trigger)
|
|
12402
|
+
return;
|
|
12403
|
+
const rect = trigger.getBoundingClientRect();
|
|
12404
|
+
const gap = 4;
|
|
12405
|
+
const spaceBelow = window.innerHeight - rect.bottom - gap;
|
|
12406
|
+
const spaceAbove = rect.top - gap;
|
|
12407
|
+
const openDown = spaceBelow >= 160 || spaceBelow >= spaceAbove;
|
|
12408
|
+
const availableSpace = openDown ? spaceBelow : spaceAbove;
|
|
12409
|
+
const maxHeight = Math.min(320, Math.max(160, availableSpace - 8));
|
|
12410
|
+
setDropdownPosition(openDown
|
|
12411
|
+
? {
|
|
12412
|
+
top: rect.bottom + gap,
|
|
12413
|
+
left: rect.left,
|
|
12414
|
+
width: rect.width,
|
|
12415
|
+
maxHeight,
|
|
12416
|
+
placement: 'bottom',
|
|
12417
|
+
}
|
|
12418
|
+
: {
|
|
12419
|
+
bottom: window.innerHeight - rect.top + gap,
|
|
12420
|
+
left: rect.left,
|
|
12421
|
+
width: rect.width,
|
|
12422
|
+
maxHeight,
|
|
12423
|
+
placement: 'top',
|
|
12424
|
+
});
|
|
12425
|
+
}, []);
|
|
12426
|
+
const updateListPopupPosition = React.useCallback(() => {
|
|
12427
|
+
const anchor = moreButtonRef.current;
|
|
12428
|
+
if (!anchor)
|
|
12429
|
+
return;
|
|
12430
|
+
const rect = anchor.getBoundingClientRect();
|
|
12431
|
+
const gap = 4;
|
|
12432
|
+
const spaceBelow = window.innerHeight - rect.bottom - gap;
|
|
12433
|
+
const spaceAbove = rect.top - gap;
|
|
12434
|
+
const openDown = spaceBelow >= 120 || spaceBelow >= spaceAbove;
|
|
12435
|
+
const availableSpace = openDown ? spaceBelow : spaceAbove;
|
|
12436
|
+
const maxHeight = Math.min(280, Math.max(120, availableSpace - 8));
|
|
12437
|
+
setListPopupPosition(openDown
|
|
12438
|
+
? {
|
|
12439
|
+
top: rect.bottom + gap,
|
|
12440
|
+
left: rect.left,
|
|
12441
|
+
width: Math.max(rect.width, 220),
|
|
12442
|
+
maxHeight,
|
|
12443
|
+
placement: 'bottom',
|
|
12444
|
+
}
|
|
12445
|
+
: {
|
|
12446
|
+
bottom: window.innerHeight - rect.top + gap,
|
|
12447
|
+
left: rect.left,
|
|
12448
|
+
width: Math.max(rect.width, 220),
|
|
12449
|
+
maxHeight,
|
|
12450
|
+
placement: 'top',
|
|
12451
|
+
});
|
|
12452
|
+
}, []);
|
|
12453
|
+
React.useEffect(() => {
|
|
12454
|
+
if (!isOpen) {
|
|
12455
|
+
setDropdownPosition(null);
|
|
12456
|
+
setSearchQuery('');
|
|
12457
|
+
return;
|
|
12458
|
+
}
|
|
12459
|
+
updateDropdownPosition();
|
|
12460
|
+
const handleResize = () => updateDropdownPosition();
|
|
12461
|
+
window.addEventListener('resize', handleResize);
|
|
12462
|
+
return () => {
|
|
12463
|
+
window.removeEventListener('resize', handleResize);
|
|
12464
|
+
};
|
|
12465
|
+
}, [isOpen, updateDropdownPosition]);
|
|
12466
|
+
React.useEffect(() => {
|
|
12467
|
+
if (!isListPopupOpen) {
|
|
12468
|
+
setListPopupPosition(null);
|
|
12469
|
+
return;
|
|
12470
|
+
}
|
|
12471
|
+
updateListPopupPosition();
|
|
12472
|
+
const handleResize = () => updateListPopupPosition();
|
|
12473
|
+
window.addEventListener('resize', handleResize);
|
|
12474
|
+
return () => {
|
|
12475
|
+
window.removeEventListener('resize', handleResize);
|
|
12476
|
+
};
|
|
12477
|
+
}, [isListPopupOpen, updateListPopupPosition]);
|
|
12478
|
+
React.useEffect(() => {
|
|
12479
|
+
if (!isOpen && !isListPopupOpen)
|
|
12480
|
+
return;
|
|
12481
|
+
const handleScroll = (event) => {
|
|
12482
|
+
const target = event.target;
|
|
12483
|
+
if (dropdownRef.current?.contains(target))
|
|
12484
|
+
return;
|
|
12485
|
+
if (listPopupRef.current?.contains(target))
|
|
12486
|
+
return;
|
|
12487
|
+
if (isOpen)
|
|
12488
|
+
setIsOpen(false);
|
|
12489
|
+
if (isListPopupOpen)
|
|
12490
|
+
setIsListPopupOpen(false);
|
|
12491
|
+
};
|
|
12492
|
+
window.addEventListener('scroll', handleScroll, true);
|
|
12493
|
+
return () => window.removeEventListener('scroll', handleScroll, true);
|
|
12494
|
+
}, [isOpen, isListPopupOpen]);
|
|
12495
|
+
React.useEffect(() => {
|
|
12496
|
+
if (!isOpen && !isListPopupOpen)
|
|
12497
|
+
return;
|
|
12498
|
+
const handleClickOutside = (event) => {
|
|
12499
|
+
const target = event.target;
|
|
12500
|
+
if (isOpen) {
|
|
12501
|
+
if (containerRef.current?.contains(target))
|
|
12502
|
+
return;
|
|
12503
|
+
if (dropdownRef.current?.contains(target))
|
|
12504
|
+
return;
|
|
12505
|
+
setIsOpen(false);
|
|
12506
|
+
}
|
|
12507
|
+
if (isListPopupOpen) {
|
|
12508
|
+
if (listPopupRef.current?.contains(target))
|
|
12509
|
+
return;
|
|
12510
|
+
if (moreButtonRef.current?.contains(target))
|
|
12511
|
+
return;
|
|
12512
|
+
setIsListPopupOpen(false);
|
|
12513
|
+
}
|
|
12514
|
+
};
|
|
12515
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
12516
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
12517
|
+
}, [isOpen, isListPopupOpen]);
|
|
12518
|
+
React.useEffect(() => {
|
|
12519
|
+
if (isOpen && searchInputRef.current) {
|
|
12520
|
+
searchInputRef.current.focus();
|
|
12521
|
+
}
|
|
12522
|
+
}, [isOpen]);
|
|
12523
|
+
const processedOptions = React.useMemo(() => {
|
|
12524
|
+
let options = dataSourceOptions.map((opt) => {
|
|
12525
|
+
const rawLabel = String(opt.label ?? opt.value ?? '');
|
|
12526
|
+
return {
|
|
12527
|
+
value: opt.value,
|
|
12528
|
+
label: translateConfig(rawLabel),
|
|
12529
|
+
rawLabel,
|
|
12530
|
+
};
|
|
12531
|
+
});
|
|
12532
|
+
if (sortOptions) {
|
|
12533
|
+
options.sort((a, b) => a.label.localeCompare(b.label));
|
|
12534
|
+
}
|
|
12535
|
+
return options;
|
|
12536
|
+
}, [dataSourceOptions, sortOptions, translateConfig]);
|
|
12537
|
+
const filteredOptions = React.useMemo(() => {
|
|
12538
|
+
if (!searchQuery.trim())
|
|
12539
|
+
return processedOptions;
|
|
12540
|
+
const q = searchQuery.trim().toLowerCase();
|
|
12541
|
+
return processedOptions.filter((opt) => opt.label.toLowerCase().includes(q) ||
|
|
12542
|
+
opt.rawLabel.toLowerCase().includes(q));
|
|
12543
|
+
}, [processedOptions, searchQuery]);
|
|
12544
|
+
const selectedValues = React.useMemo(() => {
|
|
12545
|
+
if (value === null || value === undefined)
|
|
12546
|
+
return [];
|
|
12547
|
+
if (Array.isArray(value))
|
|
12548
|
+
return value;
|
|
12549
|
+
return [value];
|
|
12550
|
+
}, [value]);
|
|
12551
|
+
const allFilteredSelected = React.useMemo(() => {
|
|
12552
|
+
if (filteredOptions.length === 0)
|
|
12553
|
+
return false;
|
|
12554
|
+
return filteredOptions.every((opt) => selectedValues.includes(opt.value));
|
|
12555
|
+
}, [filteredOptions, selectedValues]);
|
|
12556
|
+
const handleToggle = React.useCallback((optionValue, checked) => {
|
|
12557
|
+
if (checked) {
|
|
12558
|
+
onChange([...selectedValues, optionValue]);
|
|
12559
|
+
}
|
|
12560
|
+
else {
|
|
12561
|
+
onChange(selectedValues.filter((v) => v !== optionValue));
|
|
12562
|
+
}
|
|
12563
|
+
}, [selectedValues, onChange]);
|
|
12564
|
+
const handleSelectAll = React.useCallback(() => {
|
|
12565
|
+
const filteredVals = filteredOptions.map((o) => o.value);
|
|
12566
|
+
const merged = Array.from(new Set([...selectedValues, ...filteredVals]));
|
|
12567
|
+
onChange(merged);
|
|
12568
|
+
}, [filteredOptions, selectedValues, onChange]);
|
|
12569
|
+
const handleClearAll = React.useCallback(() => {
|
|
12570
|
+
onChange([]);
|
|
12571
|
+
}, [onChange]);
|
|
12572
|
+
const selectedLabels = React.useMemo(() => {
|
|
12573
|
+
return selectedValues.map((val) => {
|
|
12574
|
+
const opt = processedOptions.find((o) => o.value === val);
|
|
12575
|
+
return opt ? opt.label : translateConfig(String(val));
|
|
12576
|
+
});
|
|
12577
|
+
}, [selectedValues, processedOptions, translateConfig]);
|
|
12578
|
+
const fullSelectionText = selectedLabels.join(', ');
|
|
12579
|
+
const visibleLabels = selectedLabels.slice(0, 5);
|
|
12580
|
+
const overflowCount = Math.max(0, selectedLabels.length - 10);
|
|
12581
|
+
const disabled = !isEnabled || loading || widgetConfig['widget-readonly'];
|
|
12582
|
+
const renderSelectedLabels = (options) => {
|
|
12583
|
+
if (selectedLabels.length === 0)
|
|
12584
|
+
return null;
|
|
12585
|
+
const readonly = options?.readonly ?? false;
|
|
12586
|
+
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', {
|
|
12587
|
+
label,
|
|
12588
|
+
defaultValue: `Remove ${label}`,
|
|
12589
|
+
}), 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', {
|
|
12590
|
+
count: overflowCount,
|
|
12591
|
+
defaultValue: `+${overflowCount} more`,
|
|
12592
|
+
}) }))] }));
|
|
12593
|
+
};
|
|
12594
|
+
const listPopupPanel = isListPopupOpen && listPopupPosition && mounted ? (jsxRuntimeExports.jsxs("div", { ref: listPopupRef, className: "fixed z-[201] bg-white border border-gray-300 shadow-lg", style: {
|
|
12595
|
+
...(listPopupPosition.placement === 'bottom'
|
|
12596
|
+
? { top: listPopupPosition.top }
|
|
12597
|
+
: { bottom: listPopupPosition.bottom }),
|
|
12598
|
+
left: listPopupPosition.left,
|
|
12599
|
+
width: listPopupPosition.width,
|
|
12600
|
+
maxWidth: '320px',
|
|
12601
|
+
maxHeight: listPopupPosition.maxHeight,
|
|
12602
|
+
borderRadius: '10px',
|
|
12603
|
+
display: 'flex',
|
|
12604
|
+
flexDirection: 'column',
|
|
12605
|
+
}, 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', {
|
|
12606
|
+
count: selectedLabels.length,
|
|
12607
|
+
defaultValue: `All selected (${selectedLabels.length})`,
|
|
12608
|
+
}) }), 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;
|
|
12609
|
+
if (widgetConfig['widget-readonly']) {
|
|
12610
|
+
const fieldLabel = widgetConfig['widget-label'];
|
|
12611
|
+
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'
|
|
12612
|
+
? reactDom.createPortal(listPopupPanel, document.body)
|
|
12613
|
+
: null] })] }));
|
|
12614
|
+
}
|
|
12615
|
+
const optionsMaxHeight = dropdownPosition
|
|
12616
|
+
? Math.min(280, dropdownPosition.maxHeight - 100)
|
|
12617
|
+
: 280;
|
|
12618
|
+
const dropdownPanel = isOpen && dropdownPosition && mounted ? (jsxRuntimeExports.jsxs("div", { ref: dropdownRef, className: "fixed z-[200] bg-white border border-gray-300 shadow-lg", style: {
|
|
12619
|
+
...(dropdownPosition.placement === 'bottom'
|
|
12620
|
+
? { top: dropdownPosition.top }
|
|
12621
|
+
: { bottom: dropdownPosition.bottom }),
|
|
12622
|
+
left: dropdownPosition.left,
|
|
12623
|
+
width: dropdownPosition.width,
|
|
12624
|
+
maxWidth: '280px',
|
|
12625
|
+
maxHeight: dropdownPosition.maxHeight,
|
|
12626
|
+
borderRadius: '10px',
|
|
12627
|
+
display: 'flex',
|
|
12628
|
+
flexDirection: 'column',
|
|
12629
|
+
}, 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 ? () => {
|
|
12630
|
+
const filteredVals = new Set(filteredOptions.map((o) => o.value));
|
|
12631
|
+
onChange(selectedValues.filter((v) => !filteredVals.has(v)));
|
|
12632
|
+
} : handleSelectAll, className: "text-xs font-medium text-blue-600 hover:text-blue-800 focus:outline-none", children: allFilteredSelected
|
|
12633
|
+
? translate('common.deselectAll', { defaultValue: 'Deselect All' })
|
|
12634
|
+
: 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) => {
|
|
12635
|
+
const isChecked = selectedValues.includes(option.value);
|
|
12636
|
+
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));
|
|
12637
|
+
})) })] })) : null;
|
|
12638
|
+
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: () => {
|
|
12639
|
+
if (!disabled)
|
|
12640
|
+
setIsOpen((prev) => !prev);
|
|
12641
|
+
}, onBlur: () => {
|
|
12642
|
+
if (!isOpen)
|
|
12643
|
+
onBlur();
|
|
12644
|
+
}, 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) ||
|
|
12645
|
+
(widgetConfig['widget-required'] && selectedValues.length === 0)
|
|
12646
|
+
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
12647
|
+
: '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
|
|
12648
|
+
? fullSelectionText
|
|
12649
|
+
: 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
|
|
12650
|
+
? translate('common.select', { defaultValue: 'Select...' })
|
|
12651
|
+
: translate('common.selectedCount', {
|
|
12652
|
+
count: selectedLabels.length,
|
|
12653
|
+
defaultValue: `${selectedLabels.length} selected`,
|
|
12654
|
+
}) }), 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'
|
|
12655
|
+
? reactDom.createPortal(dropdownPanel, document.body)
|
|
12656
|
+
: null, selectedLabels.length > 0 && renderSelectedLabels(), mounted && listPopupPanel && typeof document !== 'undefined'
|
|
12657
|
+
? reactDom.createPortal(listPopupPanel, document.body)
|
|
12658
|
+
: 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') }))] })] }) }));
|
|
12659
|
+
};
|
|
12660
|
+
|
|
11363
12661
|
/**
|
|
11364
12662
|
* Register all default/generic widgets
|
|
11365
12663
|
* This is called automatically when the package is imported
|
|
@@ -11407,6 +12705,10 @@ const registerDefaultWidgets = () => {
|
|
|
11407
12705
|
widgetRegistry.register({ widget: 'scores-display', component: ScoresDisplayWidget });
|
|
11408
12706
|
// ID Authentication widget for OIDC-based foundational ID authentication (view-only + action)
|
|
11409
12707
|
widgetRegistry.register({ widget: 'id-authentication', component: IdAuthenticationWidget });
|
|
12708
|
+
// Register lookup widget — searchable popup to select a record from any register
|
|
12709
|
+
widgetRegistry.register({ widget: 'register-lookup', component: RegisterLookupWidget });
|
|
12710
|
+
// Multi-select widget — searchable dropdown with checkbox-style options, select all, and clear all
|
|
12711
|
+
widgetRegistry.register({ widget: 'multi-select', component: MultiSelectWidget });
|
|
11410
12712
|
};
|
|
11411
12713
|
// Auto-register on import
|
|
11412
12714
|
registerDefaultWidgets();
|
|
@@ -11469,6 +12771,27 @@ var enTranslations = {
|
|
|
11469
12771
|
"common.sectionSaved": "Saved",
|
|
11470
12772
|
"common.sectionModified": "Modified and not saved",
|
|
11471
12773
|
"common.supportedDocuments": "Supported Documents",
|
|
12774
|
+
"common.searchPlaceholder": "Search...",
|
|
12775
|
+
"common.selectAll": "Select All",
|
|
12776
|
+
"common.deselectAll": "Deselect All",
|
|
12777
|
+
"common.clearAll": "Clear All",
|
|
12778
|
+
"common.noOptionsFound": "No options found",
|
|
12779
|
+
"common.allSelected": "All selected ({{count}})",
|
|
12780
|
+
"common.moreSelected": "+{{count}} more",
|
|
12781
|
+
"common.selectedCount": "{{count}} selected",
|
|
12782
|
+
"common.removeItem": "Remove {{label}}",
|
|
12783
|
+
"common.selectAction": "Select {{label}}",
|
|
12784
|
+
"common.selectTitle": "Select {{label}}",
|
|
12785
|
+
"common.change": "Change",
|
|
12786
|
+
"common.noResults": "No results found",
|
|
12787
|
+
"common.searchHint": "Type and press Enter or click search",
|
|
12788
|
+
"common.record": "{{count}} record",
|
|
12789
|
+
"common.records": "{{count}} records",
|
|
12790
|
+
"common.page": "Page",
|
|
12791
|
+
"common.ofPages": "of {{total}}",
|
|
12792
|
+
"common.pageNumber": "Page number",
|
|
12793
|
+
"common.close": "Close",
|
|
12794
|
+
"common.search": "Search",
|
|
11472
12795
|
"table.addRecord": "Add New Record",
|
|
11473
12796
|
"table.confirm": "Confirm Action",
|
|
11474
12797
|
"table.discard": "Discard & Continue",
|
|
@@ -11709,13 +13032,10 @@ const translateWidgetConfig = (widgetConfig, translate) => {
|
|
|
11709
13032
|
...dataSource,
|
|
11710
13033
|
options: dataSource.options.map((option) => {
|
|
11711
13034
|
if (option.label && typeof option.label === 'string') {
|
|
11712
|
-
|
|
11713
|
-
|
|
11714
|
-
|
|
11715
|
-
|
|
11716
|
-
label: translate(optionLabel, { defaultValue: optionLabel }),
|
|
11717
|
-
};
|
|
11718
|
-
}
|
|
13035
|
+
return {
|
|
13036
|
+
...option,
|
|
13037
|
+
label: translate(option.label, { defaultValue: option.label }),
|
|
13038
|
+
};
|
|
11719
13039
|
}
|
|
11720
13040
|
return option;
|
|
11721
13041
|
}),
|
|
@@ -11778,16 +13098,19 @@ exports.DateTimeInputWidget = DateTimeInputWidget;
|
|
|
11778
13098
|
exports.DialogTableWidget = DialogTableWidget;
|
|
11779
13099
|
exports.DisplayWidget = DisplayWidget;
|
|
11780
13100
|
exports.FileInputWidget = FileInputWidget;
|
|
13101
|
+
exports.GEO_LEVEL_CLEARED = GEO_LEVEL_CLEARED;
|
|
11781
13102
|
exports.HeaderSectionWidget = HeaderSectionWidget;
|
|
11782
13103
|
exports.IdAuthenticationWidget = IdAuthenticationWidget;
|
|
11783
13104
|
exports.IterableAccordionWidget = IterableAccordionWidget;
|
|
11784
13105
|
exports.JSONEditorPanel = JSONEditorPanel;
|
|
13106
|
+
exports.MultiSelectWidget = MultiSelectWidget;
|
|
11785
13107
|
exports.NumberInputWidget = NumberInputWidget;
|
|
11786
13108
|
exports.PanelRenderer = PanelRenderer;
|
|
11787
13109
|
exports.PhoneInputWidget = PhoneInputWidget;
|
|
11788
13110
|
exports.ProfileWidget = ProfileWidget;
|
|
11789
13111
|
exports.PropertyEditor = PropertyEditor;
|
|
11790
13112
|
exports.RadioWidget = RadioWidget;
|
|
13113
|
+
exports.RegisterLookupWidget = RegisterLookupWidget;
|
|
11791
13114
|
exports.ScoresDisplayWidget = ScoresDisplayWidget;
|
|
11792
13115
|
exports.SectionBuilder = SectionBuilder;
|
|
11793
13116
|
exports.SectionRenderer = SectionRenderer;
|
|
@@ -11805,10 +13128,14 @@ exports.WidgetRenderer = WidgetRenderer;
|
|
|
11805
13128
|
exports.applyCaseControl = applyCaseControl;
|
|
11806
13129
|
exports.applyDecimalPrecision = applyDecimalPrecision;
|
|
11807
13130
|
exports.applyMask = applyMask;
|
|
13131
|
+
exports.applySharedGeoHierarchyToValues = applySharedGeoHierarchyToValues;
|
|
13132
|
+
exports.collectGeoWidgetRegistrationsFromWidgets = collectGeoWidgetRegistrationsFromWidgets;
|
|
13133
|
+
exports.createGeoLevelMnemonicResolver = createGeoLevelMnemonicResolver;
|
|
11808
13134
|
exports.createWidgetStore = createWidgetStore;
|
|
11809
13135
|
exports.createZodSchema = createZodSchema;
|
|
11810
13136
|
exports.defaultTheme = defaultTheme;
|
|
11811
13137
|
exports.evaluateCondition = evaluateCondition;
|
|
13138
|
+
exports.evaluateWidgetConditions = evaluateWidgetConditions;
|
|
11812
13139
|
exports.filterByCharacterType = filterByCharacterType;
|
|
11813
13140
|
exports.formatCurrency = formatCurrency;
|
|
11814
13141
|
exports.formatDate = formatDate;
|
|
@@ -11817,21 +13144,38 @@ exports.formatPhone = formatPhone;
|
|
|
11817
13144
|
exports.formatValue = formatValue;
|
|
11818
13145
|
exports.geoHierarchyBuilder = geoHierarchyBuilder;
|
|
11819
13146
|
exports.getApiDataSource = getApiDataSource;
|
|
13147
|
+
exports.getCachedApiDataSource = getCachedApiDataSource;
|
|
11820
13148
|
exports.getFormattedNumberLength = getFormattedNumberLength;
|
|
13149
|
+
exports.getGeoDescendantWidgetIds = getGeoDescendantWidgetIds;
|
|
13150
|
+
exports.getGeoGroupId = getGeoGroupId;
|
|
13151
|
+
exports.getGeoWidgetRegistrationsInGroup = getGeoWidgetRegistrationsInGroup;
|
|
11821
13152
|
exports.getSchemaDataSource = getSchemaDataSource;
|
|
11822
13153
|
exports.getStaticDataSource = getStaticDataSource;
|
|
11823
13154
|
exports.getValueByPath = getValueByPath;
|
|
11824
13155
|
exports.getWidgetValue = getWidgetValue;
|
|
13156
|
+
exports.hasVisibilityRules = hasVisibilityRules;
|
|
11825
13157
|
exports.initI18n = initI18n;
|
|
11826
13158
|
exports.isAllowedKey = isAllowedKey;
|
|
13159
|
+
exports.isUpstreamGeoAncestor = isUpstreamGeoAncestor;
|
|
11827
13160
|
exports.normalizeNumericDefault = normalizeNumericDefault;
|
|
13161
|
+
exports.normalizeOptionRules = normalizeOptionRules;
|
|
13162
|
+
exports.orderGeoWidgetRegistrations = orderGeoWidgetRegistrations;
|
|
11828
13163
|
exports.parseDataPath = parseDataPath;
|
|
11829
13164
|
exports.parseNumber = parseNumber;
|
|
13165
|
+
exports.rebuildGeoHierarchyFromRegistrations = rebuildGeoHierarchyFromRegistrations;
|
|
13166
|
+
exports.reconcileGeoHierarchiesInValues = reconcileGeoHierarchiesInValues;
|
|
11830
13167
|
exports.registerDefaultWidgets = registerDefaultWidgets;
|
|
13168
|
+
exports.registerGeoWidget = registerGeoWidget;
|
|
13169
|
+
exports.registerGeoWidgetParent = registerGeoWidgetParent;
|
|
11831
13170
|
exports.removeMask = removeMask;
|
|
11832
13171
|
exports.resetAll = resetAll;
|
|
13172
|
+
exports.resetAndSeedGeoHierarchyFromValues = resetAndSeedGeoHierarchyFromValues;
|
|
11833
13173
|
exports.resetWidget = resetWidget;
|
|
13174
|
+
exports.resolveGeoWidgetLevelLabel = resolveGeoWidgetLevelLabel;
|
|
13175
|
+
exports.resolveGeoWidgetLevelValue = resolveGeoWidgetLevelValue;
|
|
11834
13176
|
exports.resolveTheme = resolveTheme;
|
|
13177
|
+
exports.resolveWidgetIdValue = resolveWidgetIdValue;
|
|
13178
|
+
exports.seedGeoHierarchyFromValues = seedGeoHierarchyFromValues;
|
|
11835
13179
|
exports.setDataSource = setDataSource;
|
|
11836
13180
|
exports.setError = setError;
|
|
11837
13181
|
exports.setLoading = setLoading;
|
|
@@ -11841,11 +13185,14 @@ exports.setValueByPath = setValueByPath;
|
|
|
11841
13185
|
exports.setValues = setValues;
|
|
11842
13186
|
exports.setWidgetValue = setWidgetValue;
|
|
11843
13187
|
exports.shouldEnableWidget = shouldEnableWidget;
|
|
13188
|
+
exports.shouldRequireWidget = shouldRequireWidget;
|
|
11844
13189
|
exports.shouldShowWidget = shouldShowWidget;
|
|
11845
13190
|
exports.transformDataSourceOptions = transformDataSourceOptions;
|
|
11846
13191
|
exports.translatePanelConfig = translatePanelConfig;
|
|
11847
13192
|
exports.translateUISchema = translateUISchema;
|
|
11848
13193
|
exports.translateWidgetConfig = translateWidgetConfig;
|
|
13194
|
+
exports.unregisterGeoWidget = unregisterGeoWidget;
|
|
13195
|
+
exports.unregisterGeoWidgetParent = unregisterGeoWidgetParent;
|
|
11849
13196
|
exports.useBaseWidget = useBaseWidget;
|
|
11850
13197
|
exports.useGeoWidgetCascade = useGeoWidgetCascade;
|
|
11851
13198
|
exports.useWidgetCascade = useWidgetCascade;
|