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