@openg2p/registry-widgets 1.1.2-dev.7 → 1.1.2-dev.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/SectionRenderer.d.ts.map +1 -1
- package/dist/hooks/useBaseWidget.d.ts.map +1 -1
- package/dist/hooks/useGeoWidgetCascade.d.ts.map +1 -1
- package/dist/index.d.ts +42 -4
- package/dist/index.esm.js +285 -181
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +293 -180
- package/dist/index.js.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 +33 -0
- package/dist/utils/geoHierarchy.d.ts.map +1 -1
- package/dist/widgets/DialogTableWidget.d.ts.map +1 -1
- package/dist/widgets/SelectWidget.d.ts.map +1 -1
- package/dist/widgets/TableWidget.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1083,6 +1083,67 @@ const formatValue = (value, format, widgetType) => {
|
|
|
1083
1083
|
return value?.toString() || '';
|
|
1084
1084
|
};
|
|
1085
1085
|
|
|
1086
|
+
const apiDataSourceCache = new Map();
|
|
1087
|
+
const apiDataSourceInflight = new Map();
|
|
1088
|
+
function buildApiRequestContext(dataSource, allValues, levelId) {
|
|
1089
|
+
let depValue = null;
|
|
1090
|
+
if (dataSource.dependsOn) {
|
|
1091
|
+
if (dataSource.dependsOn.includes('.')) {
|
|
1092
|
+
depValue = getValueByPath(allValues, dataSource.dependsOn);
|
|
1093
|
+
}
|
|
1094
|
+
else {
|
|
1095
|
+
depValue = resolveWidgetIdValue(allValues, dataSource.dependsOn);
|
|
1096
|
+
}
|
|
1097
|
+
if (depValue === null || depValue === undefined || depValue === '') {
|
|
1098
|
+
return null;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
const method = dataSource.method || 'GET';
|
|
1102
|
+
const staticParams = { ...dataSource.params };
|
|
1103
|
+
const standardFields = ['type', 'service', 'endpoint', 'url', 'method', 'dependsOn', 'valueKey', 'labelKey', 'headers', 'body', 'params', 'level_id'];
|
|
1104
|
+
for (const [key, value] of Object.entries(dataSource)) {
|
|
1105
|
+
if (!standardFields.includes(key) && value !== undefined && value !== null) {
|
|
1106
|
+
staticParams[key] = value;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
if (levelId) {
|
|
1110
|
+
staticParams.level_id = levelId;
|
|
1111
|
+
}
|
|
1112
|
+
const requestParams = { ...staticParams };
|
|
1113
|
+
if (dataSource.dependsOn && depValue !== null && depValue !== undefined) {
|
|
1114
|
+
const parentValueId = typeof depValue === 'object' && depValue !== null
|
|
1115
|
+
? (depValue.level_value_id || depValue.id || depValue.value || depValue)
|
|
1116
|
+
: depValue;
|
|
1117
|
+
if (staticParams.level_id) {
|
|
1118
|
+
requestParams.parent_level_value_id = parentValueId;
|
|
1119
|
+
}
|
|
1120
|
+
else {
|
|
1121
|
+
const paramKey = dataSource.dependsOn.split('.').pop() || 'filter';
|
|
1122
|
+
requestParams[paramKey] = parentValueId;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
else if (staticParams.level_id) {
|
|
1126
|
+
requestParams.parent_level_value_id = '';
|
|
1127
|
+
}
|
|
1128
|
+
const service = dataSource.service;
|
|
1129
|
+
const endpoint = dataSource.endpoint;
|
|
1130
|
+
if (!service || !endpoint) {
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
return { service, endpoint, method, requestParams };
|
|
1134
|
+
}
|
|
1135
|
+
function buildApiDataSourceCacheKey(service, endpoint, method, requestParams) {
|
|
1136
|
+
return `${service}|${endpoint}|${method}|${JSON.stringify(requestParams)}`;
|
|
1137
|
+
}
|
|
1138
|
+
/** Return cached API options when already fetched (e.g. duplicate table cells). */
|
|
1139
|
+
function getCachedApiDataSource(dataSource, allValues, levelId) {
|
|
1140
|
+
const context = buildApiRequestContext(dataSource, allValues, levelId);
|
|
1141
|
+
if (!context) {
|
|
1142
|
+
return undefined;
|
|
1143
|
+
}
|
|
1144
|
+
const cacheKey = buildApiDataSourceCacheKey(context.service, context.endpoint, context.method, context.requestParams);
|
|
1145
|
+
return apiDataSourceCache.get(cacheKey);
|
|
1146
|
+
}
|
|
1086
1147
|
/**
|
|
1087
1148
|
* Get static data source options
|
|
1088
1149
|
*/
|
|
@@ -1100,98 +1161,33 @@ const getApiDataSource = async (dataSource, allValues, dataSourceRequestHandler,
|
|
|
1100
1161
|
return [];
|
|
1101
1162
|
}
|
|
1102
1163
|
try {
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
let depValue = null;
|
|
1106
|
-
if (dataSource.dependsOn) {
|
|
1107
|
-
if (dataSource.dependsOn.includes('.')) {
|
|
1108
|
-
depValue = getValueByPath(allValues, dataSource.dependsOn);
|
|
1109
|
-
}
|
|
1110
|
-
else {
|
|
1111
|
-
depValue = resolveWidgetIdValue(allValues, dataSource.dependsOn);
|
|
1112
|
-
}
|
|
1113
|
-
if (depValue === null || depValue === undefined || depValue === '') {
|
|
1114
|
-
// If dependency is empty, return empty array
|
|
1115
|
-
return [];
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
// Build request parameters
|
|
1119
|
-
const method = dataSource.method || 'GET';
|
|
1120
|
-
// Extract static params from dataSource
|
|
1121
|
-
// Include explicit params object and any additional fields (like level_id)
|
|
1122
|
-
const staticParams = { ...dataSource.params };
|
|
1123
|
-
// Extract additional fields that aren't part of the standard ApiDataSource interface
|
|
1124
|
-
// These are fields like level_id that might be directly on the dataSource
|
|
1125
|
-
// BUT: level_id should come from widget-geo-config.level, not from dataSource
|
|
1126
|
-
const standardFields = ['type', 'service', 'endpoint', 'url', 'method', 'dependsOn', 'valueKey', 'labelKey', 'headers', 'body', 'params', 'level_id'];
|
|
1127
|
-
for (const [key, value] of Object.entries(dataSource)) {
|
|
1128
|
-
if (!standardFields.includes(key) && value !== undefined && value !== null) {
|
|
1129
|
-
staticParams[key] = value;
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
// If levelId is provided (from widget-geo-config.level), use it instead of any level_id in dataSource
|
|
1133
|
-
if (levelId) {
|
|
1134
|
-
staticParams.level_id = levelId;
|
|
1135
|
-
}
|
|
1136
|
-
// Build request params object
|
|
1137
|
-
const requestParams = { ...staticParams };
|
|
1138
|
-
// Add dependency value to params
|
|
1139
|
-
if (dataSource.dependsOn && depValue !== null && depValue !== undefined) {
|
|
1140
|
-
// Extract the actual value ID if depValue is an object
|
|
1141
|
-
const parentValueId = typeof depValue === 'object' && depValue !== null
|
|
1142
|
-
? (depValue.level_value_id || depValue.id || depValue.value || depValue)
|
|
1143
|
-
: depValue;
|
|
1144
|
-
// For geo APIs, use parent_level_value_id
|
|
1145
|
-
if (staticParams.level_id) {
|
|
1146
|
-
requestParams.parent_level_value_id = parentValueId;
|
|
1147
|
-
}
|
|
1148
|
-
else {
|
|
1149
|
-
// For other APIs, use the dependency field name as param key
|
|
1150
|
-
const paramKey = dataSource.dependsOn.split('.').pop() || 'filter';
|
|
1151
|
-
requestParams[paramKey] = parentValueId;
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
else if (staticParams.level_id) {
|
|
1155
|
-
// First level has no parent, send empty string as many OpenG2P APIs expect it
|
|
1156
|
-
requestParams.parent_level_value_id = "";
|
|
1157
|
-
}
|
|
1158
|
-
// Get service mnemonic and endpoint (required)
|
|
1159
|
-
const service = dataSource.service;
|
|
1160
|
-
const endpoint = dataSource.endpoint;
|
|
1161
|
-
if (!service) {
|
|
1162
|
-
console.error('[getApiDataSource] API data source missing service mnemonic. Use "service" field instead of "url"');
|
|
1163
|
-
return [];
|
|
1164
|
-
}
|
|
1165
|
-
if (!endpoint) {
|
|
1166
|
-
console.error('[getApiDataSource] API data source missing endpoint. Use "endpoint" field to specify the operation (e.g., "get_g2p_geo_level_values")');
|
|
1164
|
+
const context = buildApiRequestContext(dataSource, allValues, levelId);
|
|
1165
|
+
if (!context) {
|
|
1167
1166
|
return [];
|
|
1168
1167
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
if (
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
return
|
|
1168
|
+
const { service, endpoint, method, requestParams } = context;
|
|
1169
|
+
const cacheKey = buildApiDataSourceCacheKey(service, endpoint, method, requestParams);
|
|
1170
|
+
const cached = apiDataSourceCache.get(cacheKey);
|
|
1171
|
+
if (cached) {
|
|
1172
|
+
return cached;
|
|
1173
|
+
}
|
|
1174
|
+
const inflight = apiDataSourceInflight.get(cacheKey);
|
|
1175
|
+
if (inflight) {
|
|
1176
|
+
return inflight;
|
|
1177
|
+
}
|
|
1178
|
+
const fetchPromise = (async () => {
|
|
1179
|
+
const response = await dataSourceRequestHandler(service, endpoint, method, requestParams, { headers: dataSource.headers });
|
|
1180
|
+
const parsed = Array.isArray(response) ? response : [];
|
|
1181
|
+
apiDataSourceCache.set(cacheKey, parsed);
|
|
1182
|
+
return parsed;
|
|
1183
|
+
})();
|
|
1184
|
+
apiDataSourceInflight.set(cacheKey, fetchPromise);
|
|
1185
|
+
try {
|
|
1186
|
+
return await fetchPromise;
|
|
1184
1187
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
if (response.data && Array.isArray(response.data)) {
|
|
1188
|
-
return response.data;
|
|
1189
|
-
}
|
|
1190
|
-
if (response.results && Array.isArray(response.results)) {
|
|
1191
|
-
return response.results;
|
|
1192
|
-
}
|
|
1188
|
+
finally {
|
|
1189
|
+
apiDataSourceInflight.delete(cacheKey);
|
|
1193
1190
|
}
|
|
1194
|
-
return [];
|
|
1195
1191
|
}
|
|
1196
1192
|
catch (error) {
|
|
1197
1193
|
// Rethrow so useBaseWidget's catch can log it with full widget context
|
|
@@ -2248,12 +2244,145 @@ const geoHierarchyBuilder = new GeoHierarchyBuilder();
|
|
|
2248
2244
|
/** Sentinel written to Redux when a geo level is cleared by cascade (distinct from "never set"). */
|
|
2249
2245
|
const GEO_LEVEL_CLEARED = null;
|
|
2250
2246
|
const geoWidgetParentRegistry = new Map();
|
|
2247
|
+
const geoWidgetConfigRegistry = new Map();
|
|
2251
2248
|
function registerGeoWidgetParent(widgetId, parentWidgetId) {
|
|
2252
2249
|
geoWidgetParentRegistry.set(widgetId, parentWidgetId || null);
|
|
2253
2250
|
}
|
|
2254
2251
|
function unregisterGeoWidgetParent(widgetId) {
|
|
2255
2252
|
geoWidgetParentRegistry.delete(widgetId);
|
|
2256
2253
|
}
|
|
2254
|
+
function registerGeoWidget(widgetId, geoConfig, dataPath) {
|
|
2255
|
+
if (typeof dataPath !== 'string') {
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
const parentWidgetId = geoConfig.parentWidgetId?.trim() ? geoConfig.parentWidgetId : null;
|
|
2259
|
+
registerGeoWidgetParent(widgetId, parentWidgetId);
|
|
2260
|
+
geoWidgetConfigRegistry.set(widgetId, {
|
|
2261
|
+
widgetId,
|
|
2262
|
+
parentWidgetId,
|
|
2263
|
+
level: geoConfig.level,
|
|
2264
|
+
geoConfig,
|
|
2265
|
+
dataPath,
|
|
2266
|
+
groupId: getGeoGroupId(dataPath),
|
|
2267
|
+
});
|
|
2268
|
+
}
|
|
2269
|
+
function unregisterGeoWidget(widgetId) {
|
|
2270
|
+
unregisterGeoWidgetParent(widgetId);
|
|
2271
|
+
geoWidgetConfigRegistry.delete(widgetId);
|
|
2272
|
+
}
|
|
2273
|
+
function orderGeoWidgetRegistrations(registrations) {
|
|
2274
|
+
if (registrations.length <= 1) {
|
|
2275
|
+
return registrations;
|
|
2276
|
+
}
|
|
2277
|
+
const roots = registrations.filter((entry) => !entry.parentWidgetId);
|
|
2278
|
+
if (roots.length === 0) {
|
|
2279
|
+
return registrations;
|
|
2280
|
+
}
|
|
2281
|
+
const ordered = [];
|
|
2282
|
+
let current = roots[0];
|
|
2283
|
+
const visited = new Set();
|
|
2284
|
+
while (current && !visited.has(current.widgetId)) {
|
|
2285
|
+
visited.add(current.widgetId);
|
|
2286
|
+
ordered.push(current);
|
|
2287
|
+
current = registrations.find((entry) => entry.parentWidgetId === current.widgetId);
|
|
2288
|
+
}
|
|
2289
|
+
return ordered.length > 0 ? ordered : registrations;
|
|
2290
|
+
}
|
|
2291
|
+
function resolveLevelValueId(rawValue) {
|
|
2292
|
+
if (rawValue === null || rawValue === undefined || rawValue === '') {
|
|
2293
|
+
return null;
|
|
2294
|
+
}
|
|
2295
|
+
if (typeof rawValue === 'string' || typeof rawValue === 'number') {
|
|
2296
|
+
return String(rawValue);
|
|
2297
|
+
}
|
|
2298
|
+
if (typeof rawValue === 'object') {
|
|
2299
|
+
const id = rawValue.level_value_id || rawValue.id || rawValue.value;
|
|
2300
|
+
return id != null && id !== '' ? String(id) : null;
|
|
2301
|
+
}
|
|
2302
|
+
return null;
|
|
2303
|
+
}
|
|
2304
|
+
function resolveStoredMnemonic(values, registration, valueId) {
|
|
2305
|
+
const stored = getWidgetValue(values, registration.dataPath, registration.widgetId);
|
|
2306
|
+
const hierarchy = stored?.hierarchy || stored?.geo_code_hierarchy_json?.hierarchy;
|
|
2307
|
+
if (!Array.isArray(hierarchy)) {
|
|
2308
|
+
return undefined;
|
|
2309
|
+
}
|
|
2310
|
+
const levelData = hierarchy.find((entry) => entry.level === registration.level);
|
|
2311
|
+
if (levelData && String(levelData.level_value_id) === String(valueId)) {
|
|
2312
|
+
return levelData.level_value_mnemonic ? String(levelData.level_value_mnemonic) : undefined;
|
|
2313
|
+
}
|
|
2314
|
+
return undefined;
|
|
2315
|
+
}
|
|
2316
|
+
/** Resolve display mnemonic from cached dropdown options, then stored hierarchy. */
|
|
2317
|
+
function createGeoLevelMnemonicResolver(values, dataSources) {
|
|
2318
|
+
return (registration, valueId) => {
|
|
2319
|
+
const options = dataSources[registration.widgetId];
|
|
2320
|
+
const option = options?.find((entry) => String(entry.value) === String(valueId));
|
|
2321
|
+
if (option?.label) {
|
|
2322
|
+
return option.label;
|
|
2323
|
+
}
|
|
2324
|
+
return resolveStoredMnemonic(values, registration, valueId);
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2327
|
+
/** Rebuild group hierarchy from widget values in parent→child order; stop at first missing level. */
|
|
2328
|
+
function rebuildGeoHierarchyFromRegistrations(groupId, values, registrations, resolveMnemonic) {
|
|
2329
|
+
const ordered = orderGeoWidgetRegistrations(registrations.filter((entry) => entry.groupId === groupId));
|
|
2330
|
+
geoHierarchyBuilder.clear(groupId);
|
|
2331
|
+
for (const registration of ordered) {
|
|
2332
|
+
const rawValue = resolveGeoWidgetLevelValue(values, registration.widgetId, registration.dataPath, registration.geoConfig);
|
|
2333
|
+
const valueId = resolveLevelValueId(rawValue);
|
|
2334
|
+
if (!valueId) {
|
|
2335
|
+
break;
|
|
2336
|
+
}
|
|
2337
|
+
const mnemonic = resolveMnemonic?.(registration, valueId) ??
|
|
2338
|
+
resolveStoredMnemonic(values, registration, valueId) ??
|
|
2339
|
+
valueId;
|
|
2340
|
+
geoHierarchyBuilder.addLevel(registration.level, valueId, mnemonic, groupId);
|
|
2341
|
+
}
|
|
2342
|
+
return geoHierarchyBuilder.buildHierarchyJson(groupId) !== null;
|
|
2343
|
+
}
|
|
2344
|
+
function collectGeoWidgetRegistrationsFromWidgets(widgets, namespace) {
|
|
2345
|
+
return widgets
|
|
2346
|
+
.filter((widget) => widget['widget-geo-config'] && typeof widget['widget-data-path'] === 'string')
|
|
2347
|
+
.map((widget) => {
|
|
2348
|
+
const originalWidgetId = widget['widget-id'];
|
|
2349
|
+
const originalDataPath = widget['widget-data-path'];
|
|
2350
|
+
const geoConfig = widget['widget-geo-config'];
|
|
2351
|
+
const widgetId = namespace ? `${namespace}__${originalWidgetId}` : originalWidgetId;
|
|
2352
|
+
const parentWidgetId = geoConfig.parentWidgetId?.trim()
|
|
2353
|
+
? (namespace ? `${namespace}__${geoConfig.parentWidgetId}` : geoConfig.parentWidgetId)
|
|
2354
|
+
: null;
|
|
2355
|
+
const dataPath = namespace ? `${namespace}.${originalDataPath}` : originalDataPath;
|
|
2356
|
+
return {
|
|
2357
|
+
widgetId,
|
|
2358
|
+
parentWidgetId,
|
|
2359
|
+
level: geoConfig.level,
|
|
2360
|
+
geoConfig,
|
|
2361
|
+
dataPath,
|
|
2362
|
+
groupId: getGeoGroupId(dataPath),
|
|
2363
|
+
};
|
|
2364
|
+
});
|
|
2365
|
+
}
|
|
2366
|
+
/** Reconcile all geo groups in section values before save. */
|
|
2367
|
+
function reconcileGeoHierarchiesInValues(values, registrations, dataSources = {}) {
|
|
2368
|
+
const groupIds = [...new Set(registrations.map((entry) => entry.groupId))];
|
|
2369
|
+
let updatedValues = values;
|
|
2370
|
+
const resolveMnemonic = createGeoLevelMnemonicResolver(updatedValues, dataSources);
|
|
2371
|
+
for (const groupId of groupIds) {
|
|
2372
|
+
const groupRegistrations = registrations.filter((entry) => entry.groupId === groupId);
|
|
2373
|
+
const dataPath = groupRegistrations[0]?.dataPath;
|
|
2374
|
+
const widgetId = groupRegistrations[0]?.widgetId;
|
|
2375
|
+
if (!dataPath || !widgetId) {
|
|
2376
|
+
continue;
|
|
2377
|
+
}
|
|
2378
|
+
rebuildGeoHierarchyFromRegistrations(groupId, updatedValues, groupRegistrations, resolveMnemonic);
|
|
2379
|
+
updatedValues = applySharedGeoHierarchyToValues(updatedValues, groupId, dataPath, widgetId);
|
|
2380
|
+
}
|
|
2381
|
+
return updatedValues;
|
|
2382
|
+
}
|
|
2383
|
+
function getGeoWidgetRegistrationsInGroup(groupId) {
|
|
2384
|
+
return orderGeoWidgetRegistrations([...geoWidgetConfigRegistry.values()].filter((entry) => entry.groupId === groupId));
|
|
2385
|
+
}
|
|
2257
2386
|
/** True when changedWidgetId is any upstream geo parent of widgetId (not only immediate parent). */
|
|
2258
2387
|
function isUpstreamGeoAncestor(changedWidgetId, widgetId, immediateParentWidgetId) {
|
|
2259
2388
|
if (changedWidgetId === widgetId) {
|
|
@@ -2348,7 +2477,7 @@ function resetAndSeedGeoHierarchyFromValues(values, dataPath, widgetId, groupId)
|
|
|
2348
2477
|
|
|
2349
2478
|
// Define stable empty arrays to avoid selector reference issues
|
|
2350
2479
|
const EMPTY_ERRORS = [];
|
|
2351
|
-
const EMPTY_DATA_SOURCE
|
|
2480
|
+
const EMPTY_DATA_SOURCE = [];
|
|
2352
2481
|
const useBaseWidget = (options) => {
|
|
2353
2482
|
const { config, dataSourceRequestHandler: propHandler, schemaData, onValueChange } = options;
|
|
2354
2483
|
const dispatch = reactRedux.useDispatch();
|
|
@@ -2363,7 +2492,7 @@ const useBaseWidget = (options) => {
|
|
|
2363
2492
|
const errors = reactRedux.useSelector((state) => state.widget.errors[widgetId] ?? EMPTY_ERRORS);
|
|
2364
2493
|
const touched = reactRedux.useSelector((state) => state.widget.touched[widgetId] || false);
|
|
2365
2494
|
const loading = reactRedux.useSelector((state) => state.widget.loading[widgetId] || false);
|
|
2366
|
-
const dataSourceOptions = reactRedux.useSelector((state) => state.widget.dataSources[widgetId] ?? EMPTY_DATA_SOURCE
|
|
2495
|
+
const dataSourceOptions = reactRedux.useSelector((state) => state.widget.dataSources[widgetId] ?? EMPTY_DATA_SOURCE);
|
|
2367
2496
|
// Skip value handling for layout widgets (they don't store data values)
|
|
2368
2497
|
// Infer layout from widget-type
|
|
2369
2498
|
const isLayoutWidget = config['widget-type'] === 'layout';
|
|
@@ -2701,9 +2830,9 @@ const useBaseWidget = (options) => {
|
|
|
2701
2830
|
if (!dataSource) {
|
|
2702
2831
|
return;
|
|
2703
2832
|
}
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
if (dataSource.type === 'api' && isReadonly && !
|
|
2833
|
+
const loadApiInReadonly = !!geoConfig ||
|
|
2834
|
+
['select', 'radio', 'checkbox', 'multi-select'].includes(config.widget);
|
|
2835
|
+
if (dataSource.type === 'api' && isReadonly && !loadApiInReadonly) {
|
|
2707
2836
|
return;
|
|
2708
2837
|
}
|
|
2709
2838
|
// For widgets with dependencies, check if dependency value exists
|
|
@@ -2745,6 +2874,30 @@ const useBaseWidget = (options) => {
|
|
|
2745
2874
|
// React will call this effect again when the handler is ready
|
|
2746
2875
|
return;
|
|
2747
2876
|
}
|
|
2877
|
+
const resolveOptionKeys = () => {
|
|
2878
|
+
if (dataSource.type === 'static') {
|
|
2879
|
+
return { valueKey: undefined, labelKey: undefined };
|
|
2880
|
+
}
|
|
2881
|
+
if (geoConfig) {
|
|
2882
|
+
return {
|
|
2883
|
+
valueKey: dataSource.valueKey || 'level_value_id',
|
|
2884
|
+
labelKey: dataSource.labelKey || 'level_value_mnemonic',
|
|
2885
|
+
};
|
|
2886
|
+
}
|
|
2887
|
+
return { valueKey: dataSource.valueKey, labelKey: dataSource.labelKey };
|
|
2888
|
+
};
|
|
2889
|
+
if (dataSource.type === 'api') {
|
|
2890
|
+
const levelId = geoConfig?.level;
|
|
2891
|
+
const cached = getCachedApiDataSource(dataSource, valuesRef.current, levelId);
|
|
2892
|
+
if (cached) {
|
|
2893
|
+
const { valueKey, labelKey } = resolveOptionKeys();
|
|
2894
|
+
dispatch(setDataSource({
|
|
2895
|
+
widgetId,
|
|
2896
|
+
data: transformDataSourceOptions(cached, valueKey, labelKey),
|
|
2897
|
+
}));
|
|
2898
|
+
return;
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2748
2901
|
dispatch(setLoading({ widgetId, loading: true }));
|
|
2749
2902
|
let data = [];
|
|
2750
2903
|
if (dataSource.type === 'static') {
|
|
@@ -2757,31 +2910,13 @@ const useBaseWidget = (options) => {
|
|
|
2757
2910
|
dispatch(setDataSource({ widgetId, data: [] }));
|
|
2758
2911
|
return;
|
|
2759
2912
|
}
|
|
2760
|
-
// Extract level_id from widget-geo-config.level if available
|
|
2761
2913
|
const levelId = geoConfig?.level;
|
|
2762
2914
|
data = await getApiDataSource(dataSource, valuesRef.current, currentHandler, levelId);
|
|
2763
2915
|
}
|
|
2764
2916
|
else if (dataSource.type === 'schema') {
|
|
2765
2917
|
data = getSchemaDataSource(dataSource, schemaData || {});
|
|
2766
2918
|
}
|
|
2767
|
-
|
|
2768
|
-
// For geo widgets, default to level_value_id and level_value_mnemonic
|
|
2769
|
-
let valueKey;
|
|
2770
|
-
let labelKey;
|
|
2771
|
-
if (dataSource.type === 'static') {
|
|
2772
|
-
valueKey = undefined;
|
|
2773
|
-
labelKey = undefined;
|
|
2774
|
-
}
|
|
2775
|
-
else if (geoConfig) {
|
|
2776
|
-
// Geo widgets: default to level_value_id and level_value_mnemonic
|
|
2777
|
-
valueKey = dataSource.valueKey || 'level_value_id';
|
|
2778
|
-
labelKey = dataSource.labelKey || 'level_value_mnemonic';
|
|
2779
|
-
}
|
|
2780
|
-
else {
|
|
2781
|
-
// Non-geo widgets: use specified keys or undefined
|
|
2782
|
-
valueKey = dataSource.valueKey;
|
|
2783
|
-
labelKey = dataSource.labelKey;
|
|
2784
|
-
}
|
|
2919
|
+
const { valueKey, labelKey } = resolveOptionKeys();
|
|
2785
2920
|
const transformed = transformDataSourceOptions(data, valueKey, labelKey);
|
|
2786
2921
|
dispatch(setDataSource({ widgetId, data: transformed }));
|
|
2787
2922
|
}
|
|
@@ -2885,8 +3020,6 @@ const useWidgetCascade = (options) => {
|
|
|
2885
3020
|
}, [cascadeConfig, eventBus, dataSource, widgetId, dispatch]);
|
|
2886
3021
|
};
|
|
2887
3022
|
|
|
2888
|
-
// Define stable empty array to avoid selector reference issues
|
|
2889
|
-
const EMPTY_DATA_SOURCE = [];
|
|
2890
3023
|
/**
|
|
2891
3024
|
* Hook for geo widget cascade functionality
|
|
2892
3025
|
* Handles geo hierarchy building and cascade behavior
|
|
@@ -2916,15 +3049,15 @@ const useGeoWidgetCascade = (options) => {
|
|
|
2916
3049
|
? resolveGeoWidgetLevelValue(state.widget.values, widgetId, dataPath, geoConfig)
|
|
2917
3050
|
: state.widget.values[widgetId]);
|
|
2918
3051
|
// Memoize selector to avoid returning new array reference
|
|
2919
|
-
const
|
|
3052
|
+
const allDataSources = reactRedux.useSelector((state) => state.widget.dataSources);
|
|
2920
3053
|
// Register parent chain for upstream-ancestor detection (grandparent → grandchild reset)
|
|
2921
3054
|
React.useEffect(() => {
|
|
2922
|
-
if (!geoConfig) {
|
|
3055
|
+
if (!geoConfig || typeof dataPath !== 'string') {
|
|
2923
3056
|
return;
|
|
2924
3057
|
}
|
|
2925
|
-
|
|
2926
|
-
return () =>
|
|
2927
|
-
}, [widgetId, geoConfig]);
|
|
3058
|
+
registerGeoWidget(widgetId, geoConfig, dataPath);
|
|
3059
|
+
return () => unregisterGeoWidget(widgetId);
|
|
3060
|
+
}, [widgetId, geoConfig, dataPath]);
|
|
2928
3061
|
// Keep in-memory builder in sync with persisted hierarchy (reload, cancel→re-edit, etc.)
|
|
2929
3062
|
React.useEffect(() => {
|
|
2930
3063
|
if (!geoConfig || typeof dataPath !== 'string') {
|
|
@@ -3016,18 +3149,20 @@ const useGeoWidgetCascade = (options) => {
|
|
|
3016
3149
|
}, [geoConfig, eventBus, dataSource, widgetId, dataPath, dispatch, groupId]);
|
|
3017
3150
|
// Handle value changes to build hierarchy
|
|
3018
3151
|
React.useEffect(() => {
|
|
3019
|
-
if (!geoConfig) {
|
|
3152
|
+
if (!geoConfig || typeof dataPath !== 'string') {
|
|
3020
3153
|
return;
|
|
3021
3154
|
}
|
|
3022
|
-
|
|
3155
|
+
const { level, isLastLevel } = geoConfig;
|
|
3156
|
+
const groupRegistrations = getGeoWidgetRegistrationsInGroup(groupId);
|
|
3157
|
+
const applyGroupRebuild = () => {
|
|
3158
|
+
const resolveMnemonic = createGeoLevelMnemonicResolver(valuesRef.current, allDataSources);
|
|
3159
|
+
rebuildGeoHierarchyFromRegistrations(groupId, valuesRef.current, groupRegistrations, resolveMnemonic);
|
|
3160
|
+
dispatch(setValues(applySharedGeoHierarchyToValues(valuesRef.current, groupId, dataPath, widgetId)));
|
|
3161
|
+
};
|
|
3023
3162
|
// ONLY clear hierarchy if the value is explicitly null or empty string (user action)
|
|
3024
3163
|
if (currentValue === null || currentValue === '') {
|
|
3025
|
-
const { level, isLastLevel } = geoConfig;
|
|
3026
3164
|
geoHierarchyBuilder.removeLevelAndBelow(level, groupId);
|
|
3027
|
-
|
|
3028
|
-
if (dataPath) {
|
|
3029
|
-
dispatch(setValues(applySharedGeoHierarchyToValues(valuesRef.current, groupId, dataPath, widgetId)));
|
|
3030
|
-
}
|
|
3165
|
+
applyGroupRebuild();
|
|
3031
3166
|
if (!isLastLevel && eventBus && lastCascadePublishRef.current !== GEO_LEVEL_CLEARED) {
|
|
3032
3167
|
lastCascadePublishRef.current = GEO_LEVEL_CLEARED;
|
|
3033
3168
|
eventBus.publish({
|
|
@@ -3040,56 +3175,13 @@ const useGeoWidgetCascade = (options) => {
|
|
|
3040
3175
|
return;
|
|
3041
3176
|
}
|
|
3042
3177
|
if (currentValue === undefined) {
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
// Check if hierarchy is already built to prevent endless loops
|
|
3047
|
-
if (dataPath) {
|
|
3048
|
-
const currentHierarchy = getWidgetValue(valuesRef.current, dataPath, widgetId);
|
|
3049
|
-
// If hierarchy JSON is already set and matches current value, skip rebuilding
|
|
3050
|
-
if (currentHierarchy && typeof currentHierarchy === 'object') {
|
|
3051
|
-
// Check if this specific level's value matches the hierarchy
|
|
3052
|
-
const hierarchyArray = currentHierarchy.hierarchy || currentHierarchy.geo_code_hierarchy_json?.hierarchy;
|
|
3053
|
-
if (Array.isArray(hierarchyArray)) {
|
|
3054
|
-
const currentLevelValue = typeof currentValue === 'object'
|
|
3055
|
-
? (currentValue.level_value_id || currentValue.id || currentValue.value)
|
|
3056
|
-
: currentValue;
|
|
3057
|
-
const levelData = hierarchyArray.find((l) => l.level === geoConfig.level);
|
|
3058
|
-
// If this level is already correctly represented in the hierarchy, skip rebuilding
|
|
3059
|
-
// String conversion ensures comparison works for mixed types
|
|
3060
|
-
if (levelData && String(levelData.level_value_id) === String(currentLevelValue)) {
|
|
3061
|
-
return;
|
|
3062
|
-
}
|
|
3063
|
-
}
|
|
3178
|
+
const hasOwnValue = Object.prototype.hasOwnProperty.call(valuesRef.current, widgetId);
|
|
3179
|
+
if (!hasOwnValue) {
|
|
3180
|
+
return;
|
|
3064
3181
|
}
|
|
3065
3182
|
}
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
let level_value_id;
|
|
3069
|
-
let level_value_mnemonic;
|
|
3070
|
-
if (typeof currentValue === 'string' || typeof currentValue === 'number') {
|
|
3071
|
-
// Value is just the ID, need to find mnemonic from data source
|
|
3072
|
-
level_value_id = String(currentValue);
|
|
3073
|
-
// Try to get mnemonic from data source options
|
|
3074
|
-
const option = dataSourceOptions.find((opt) => opt.value === currentValue);
|
|
3075
|
-
level_value_mnemonic = option?.label || String(currentValue);
|
|
3076
|
-
}
|
|
3077
|
-
else if (currentValue && typeof currentValue === 'object') {
|
|
3078
|
-
level_value_id = currentValue.level_value_id || currentValue.id || currentValue.value;
|
|
3079
|
-
level_value_mnemonic = currentValue.level_value_mnemonic || currentValue.name || currentValue.label;
|
|
3080
|
-
}
|
|
3081
|
-
else {
|
|
3082
|
-
return;
|
|
3083
|
-
}
|
|
3084
|
-
// When a widget's own value changes, remove this level and all below from hierarchy first
|
|
3085
|
-
geoHierarchyBuilder.removeLevelAndBelow(level, groupId);
|
|
3086
|
-
// Add level to hierarchy
|
|
3087
|
-
geoHierarchyBuilder.addLevel(level, level_value_id, level_value_mnemonic, groupId);
|
|
3088
|
-
// Build and store hierarchy JSON on every change
|
|
3089
|
-
if (dataPath && geoHierarchyBuilder.buildHierarchyJson(groupId)) {
|
|
3090
|
-
dispatch(setValues(applySharedGeoHierarchyToValues(valuesRef.current, groupId, dataPath, widgetId)));
|
|
3091
|
-
}
|
|
3092
|
-
}, [geoConfig, currentValue, widgetId, dataPath, dispatch, dataSourceOptions, groupId]);
|
|
3183
|
+
applyGroupRebuild();
|
|
3184
|
+
}, [geoConfig, currentValue, widgetId, dataPath, dispatch, allDataSources, groupId, eventBus]);
|
|
3093
3185
|
};
|
|
3094
3186
|
|
|
3095
3187
|
class WidgetRegistry {
|
|
@@ -4991,7 +5083,12 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4991
5083
|
// This ensures we use the original widget IDs and data paths
|
|
4992
5084
|
const sectionWidgets = collectWidgets(originalSection.panels);
|
|
4993
5085
|
const currentState = store.getState().widget;
|
|
4994
|
-
|
|
5086
|
+
let currentSchemaData = currentState.values || {};
|
|
5087
|
+
const geoRegistrations = collectGeoWidgetRegistrationsFromWidgets(sectionWidgets, namespace);
|
|
5088
|
+
if (geoRegistrations.length > 0) {
|
|
5089
|
+
currentSchemaData = reconcileGeoHierarchiesInValues(currentSchemaData, geoRegistrations, currentState.dataSources || {});
|
|
5090
|
+
dispatch(setValues(currentSchemaData));
|
|
5091
|
+
}
|
|
4995
5092
|
const isSectionValid = sectionValidate(originalSection, currentSchemaData, dispatch, true);
|
|
4996
5093
|
if (!isSectionValid) {
|
|
4997
5094
|
return;
|
|
@@ -5057,7 +5154,12 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
5057
5154
|
if (isDraft !== false && store && onSectionSave) {
|
|
5058
5155
|
const sectionWidgets = collectWidgets(originalSection.panels);
|
|
5059
5156
|
const currentState = store.getState().widget;
|
|
5060
|
-
|
|
5157
|
+
let currentSchemaData = currentState.values || {};
|
|
5158
|
+
const geoRegistrations = collectGeoWidgetRegistrationsFromWidgets(sectionWidgets, namespace);
|
|
5159
|
+
if (geoRegistrations.length > 0) {
|
|
5160
|
+
currentSchemaData = reconcileGeoHierarchiesInValues(currentSchemaData, geoRegistrations, currentState.dataSources || {});
|
|
5161
|
+
dispatch(setValues(currentSchemaData));
|
|
5162
|
+
}
|
|
5061
5163
|
const isSectionValid = sectionValidate(originalSection, currentSchemaData, dispatch, true);
|
|
5062
5164
|
if (!isSectionValid)
|
|
5063
5165
|
return;
|
|
@@ -8757,10 +8859,12 @@ const SelectWidget = ({ config }) => {
|
|
|
8757
8859
|
if (widgetConfig['widget-readonly']) {
|
|
8758
8860
|
const label = translateConfig(widgetConfig['widget-label']);
|
|
8759
8861
|
// Find the selected option's label
|
|
8760
|
-
const selectedOption = dataSourceOptions.find((option) => option.value === value);
|
|
8862
|
+
const selectedOption = dataSourceOptions.find((option) => option.value === value || String(option.value) === String(value));
|
|
8761
8863
|
const displayValue = selectedOption
|
|
8762
8864
|
? translateConfig(selectedOption.label)
|
|
8763
|
-
:
|
|
8865
|
+
: loading
|
|
8866
|
+
? (geoDisplayLabel || '-')
|
|
8867
|
+
: (geoDisplayLabel || (value != null && value !== '' ? translateConfig(String(value)) : '-'));
|
|
8764
8868
|
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 }) })] }));
|
|
8765
8869
|
}
|
|
8766
8870
|
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 === ''))
|
|
@@ -9259,7 +9363,7 @@ const SelectDisplayValue$1 = ({ config, value }) => {
|
|
|
9259
9363
|
if (value === null || value === undefined || value === '') {
|
|
9260
9364
|
return jsxRuntimeExports.jsx("span", { children: "-" });
|
|
9261
9365
|
}
|
|
9262
|
-
const selectedOption = dataSourceOptions.find((option) => option.value === value);
|
|
9366
|
+
const selectedOption = dataSourceOptions.find((option) => option.value === value || String(option.value) === String(value));
|
|
9263
9367
|
return jsxRuntimeExports.jsx("span", { children: selectedOption ? selectedOption.label : String(value) });
|
|
9264
9368
|
};
|
|
9265
9369
|
const TableCellText = ({ config, value, onValueChange }) => {
|
|
@@ -9990,7 +10094,7 @@ const SelectDisplayValue = ({ config, value }) => {
|
|
|
9990
10094
|
return jsxRuntimeExports.jsx("span", { children: "-" });
|
|
9991
10095
|
if (value === null || value === undefined || value === '')
|
|
9992
10096
|
return jsxRuntimeExports.jsx("span", { children: "-" });
|
|
9993
|
-
const selectedOption = dataSourceOptions.find((option) => option.value === value);
|
|
10097
|
+
const selectedOption = dataSourceOptions.find((option) => option.value === value || String(option.value) === String(value));
|
|
9994
10098
|
return jsxRuntimeExports.jsx("span", { children: selectedOption ? selectedOption.label : String(value) });
|
|
9995
10099
|
};
|
|
9996
10100
|
/**
|
|
@@ -12984,6 +13088,8 @@ exports.applyCaseControl = applyCaseControl;
|
|
|
12984
13088
|
exports.applyDecimalPrecision = applyDecimalPrecision;
|
|
12985
13089
|
exports.applyMask = applyMask;
|
|
12986
13090
|
exports.applySharedGeoHierarchyToValues = applySharedGeoHierarchyToValues;
|
|
13091
|
+
exports.collectGeoWidgetRegistrationsFromWidgets = collectGeoWidgetRegistrationsFromWidgets;
|
|
13092
|
+
exports.createGeoLevelMnemonicResolver = createGeoLevelMnemonicResolver;
|
|
12987
13093
|
exports.createWidgetStore = createWidgetStore;
|
|
12988
13094
|
exports.createZodSchema = createZodSchema;
|
|
12989
13095
|
exports.defaultTheme = defaultTheme;
|
|
@@ -12997,9 +13103,11 @@ exports.formatPhone = formatPhone;
|
|
|
12997
13103
|
exports.formatValue = formatValue;
|
|
12998
13104
|
exports.geoHierarchyBuilder = geoHierarchyBuilder;
|
|
12999
13105
|
exports.getApiDataSource = getApiDataSource;
|
|
13106
|
+
exports.getCachedApiDataSource = getCachedApiDataSource;
|
|
13000
13107
|
exports.getFormattedNumberLength = getFormattedNumberLength;
|
|
13001
13108
|
exports.getGeoDescendantWidgetIds = getGeoDescendantWidgetIds;
|
|
13002
13109
|
exports.getGeoGroupId = getGeoGroupId;
|
|
13110
|
+
exports.getGeoWidgetRegistrationsInGroup = getGeoWidgetRegistrationsInGroup;
|
|
13003
13111
|
exports.getSchemaDataSource = getSchemaDataSource;
|
|
13004
13112
|
exports.getStaticDataSource = getStaticDataSource;
|
|
13005
13113
|
exports.getValueByPath = getValueByPath;
|
|
@@ -13010,9 +13118,13 @@ exports.isAllowedKey = isAllowedKey;
|
|
|
13010
13118
|
exports.isUpstreamGeoAncestor = isUpstreamGeoAncestor;
|
|
13011
13119
|
exports.normalizeNumericDefault = normalizeNumericDefault;
|
|
13012
13120
|
exports.normalizeOptionRules = normalizeOptionRules;
|
|
13121
|
+
exports.orderGeoWidgetRegistrations = orderGeoWidgetRegistrations;
|
|
13013
13122
|
exports.parseDataPath = parseDataPath;
|
|
13014
13123
|
exports.parseNumber = parseNumber;
|
|
13124
|
+
exports.rebuildGeoHierarchyFromRegistrations = rebuildGeoHierarchyFromRegistrations;
|
|
13125
|
+
exports.reconcileGeoHierarchiesInValues = reconcileGeoHierarchiesInValues;
|
|
13015
13126
|
exports.registerDefaultWidgets = registerDefaultWidgets;
|
|
13127
|
+
exports.registerGeoWidget = registerGeoWidget;
|
|
13016
13128
|
exports.registerGeoWidgetParent = registerGeoWidgetParent;
|
|
13017
13129
|
exports.removeMask = removeMask;
|
|
13018
13130
|
exports.resetAll = resetAll;
|
|
@@ -13038,6 +13150,7 @@ exports.transformDataSourceOptions = transformDataSourceOptions;
|
|
|
13038
13150
|
exports.translatePanelConfig = translatePanelConfig;
|
|
13039
13151
|
exports.translateUISchema = translateUISchema;
|
|
13040
13152
|
exports.translateWidgetConfig = translateWidgetConfig;
|
|
13153
|
+
exports.unregisterGeoWidget = unregisterGeoWidget;
|
|
13041
13154
|
exports.unregisterGeoWidgetParent = unregisterGeoWidgetParent;
|
|
13042
13155
|
exports.useBaseWidget = useBaseWidget;
|
|
13043
13156
|
exports.useGeoWidgetCascade = useGeoWidgetCascade;
|