@openg2p/registry-widgets 1.1.6-dev.6 → 1.1.7

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/index.js CHANGED
@@ -1970,15 +1970,11 @@ const useBaseWidget = (options) => {
1970
1970
  }
1971
1971
  return docsValue !== undefined ? docsValue : config['widget-data-default'];
1972
1972
  }
1973
- let value = values[widgetId];
1974
- if (value !== null && value !== undefined && typeof value === 'object' && !Array.isArray(value)) {
1975
- value = extractValueFromObject(value);
1976
- }
1977
- if (value === undefined && config['widget-data-path']) {
1978
- value = getWidgetValue(values, config['widget-data-path'], widgetId);
1979
- if (value !== null && value !== undefined && typeof value === 'object' && !Array.isArray(value)) {
1980
- value = extractValueFromObject(value);
1981
- }
1973
+ let value = config['widget-data-path']
1974
+ ? getWidgetValue(values, config['widget-data-path'], widgetId)
1975
+ : values[widgetId];
1976
+ if (value === undefined) {
1977
+ value = values[widgetId];
1982
1978
  }
1983
1979
  if (value === undefined && userHasSetValueRef.current && values[widgetId] !== undefined) {
1984
1980
  value = values[widgetId];
@@ -2173,6 +2169,10 @@ const useBaseWidget = (options) => {
2173
2169
  if (!dataSource) {
2174
2170
  return;
2175
2171
  }
2172
+ if (dataSource.type === 'api' &&
2173
+ (config.widget === 'parent-lookup' || config.widget === 'register-lookup')) {
2174
+ return;
2175
+ }
2176
2176
  const loadApiInReadonly = ['select', 'radio', 'checkbox', 'multi-select'].includes(config.widget);
2177
2177
  if (dataSource.type === 'api' && isReadonly && !loadApiInReadonly) {
2178
2178
  return;
@@ -2920,17 +2920,6 @@ function buildSectionChanges(section, storeValues, options) {
2920
2920
  };
2921
2921
  }
2922
2922
 
2923
- const IDENTITY_AND_META_KEYS = new Set([
2924
- 'edit_action',
2925
- 'internal_record_id',
2926
- 'link_internal_record_id',
2927
- 'functional_record_id',
2928
- 'created_at',
2929
- 'created_by',
2930
- 'last_approved_at',
2931
- 'last_approved_by',
2932
- 'search_text',
2933
- ]);
2934
2923
  const isPlainRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
2935
2924
  const valuesEqual = (baselineValue, currentValue) => {
2936
2925
  if (Object.is(baselineValue, currentValue))
@@ -2952,10 +2941,7 @@ const getRowId = (row) => {
2952
2941
  const id = row.internal_record_id;
2953
2942
  return typeof id === 'string' && id.length > 0 ? id : null;
2954
2943
  };
2955
- const sectionFieldKeys = (baseline, current) => {
2956
- const keys = new Set([...Object.keys(baseline), ...Object.keys(current)]);
2957
- return [...keys].filter((key) => !IDENTITY_AND_META_KEYS.has(key));
2958
- };
2944
+ const sectionFieldKeys = (baseline, current) => [...new Set([...Object.keys(baseline), ...Object.keys(current)])];
2959
2945
  const pickChangedFields = (baseline, current) => {
2960
2946
  const changed = {};
2961
2947
  for (const key of sectionFieldKeys(baseline, current)) {
@@ -2965,7 +2951,7 @@ const pickChangedFields = (baseline, current) => {
2965
2951
  }
2966
2952
  return changed;
2967
2953
  };
2968
- /** All section fields (changed or not). Identity / meta keys stay out of the CR payload. */
2954
+ /** All widget-bound section fields (changed or not). */
2969
2955
  const pickAllSectionFields = (baseline, current) => {
2970
2956
  const fields = {};
2971
2957
  for (const key of sectionFieldKeys(baseline, current)) {
@@ -2976,29 +2962,64 @@ const pickAllSectionFields = (baseline, current) => {
2976
2962
  return fields;
2977
2963
  };
2978
2964
  const toRowList = (records) => records.filter(isPlainRecord);
2979
- const diffFormRecord = (baselineRecords, currentRecords) => {
2965
+ const diffFormRecord = (baselineRecords, currentRecords, internalRecordId) => {
2980
2966
  const baseline = toRowList(baselineRecords)[0] ?? {};
2981
2967
  const current = toRowList(currentRecords)[0] ?? {};
2982
2968
  const changedFields = pickChangedFields(baseline, current);
2983
2969
  if (Object.keys(changedFields).length === 0)
2984
2970
  return [];
2985
- return [{ ...pickAllSectionFields(baseline, current), edit_action: 'UPDATE' }];
2986
- };
2987
- const diffTableRows = (baselineRecords, currentRecords) => {
2971
+ const payload = {
2972
+ ...pickAllSectionFields(baseline, current),
2973
+ edit_action: 'UPDATE',
2974
+ };
2975
+ const recordId = getRowId(current) ??
2976
+ getRowId(baseline) ??
2977
+ (typeof internalRecordId === 'string' && internalRecordId.length > 0
2978
+ ? internalRecordId
2979
+ : null);
2980
+ if (recordId) {
2981
+ payload.internal_record_id = recordId;
2982
+ }
2983
+ return [payload];
2984
+ };
2985
+ const pickSectionFields = (row, sectionKeys) => {
2986
+ const out = {};
2987
+ for (const key of sectionKeys) {
2988
+ if (Object.prototype.hasOwnProperty.call(row, key)) {
2989
+ out[key] = row[key];
2990
+ }
2991
+ }
2992
+ if (row.internal_record_id !== undefined)
2993
+ out.internal_record_id = row.internal_record_id;
2994
+ if (row.link_internal_record_id !== undefined)
2995
+ out.link_internal_record_id = row.link_internal_record_id;
2996
+ return out;
2997
+ };
2998
+ const diffTableRows = (baselineRecords, currentRecords, tableColumnKeys) => {
2988
2999
  const baselineRows = toRowList(baselineRecords);
2989
3000
  const currentRows = toRowList(currentRecords);
3001
+ let sectionKeys;
3002
+ if (tableColumnKeys && tableColumnKeys.length > 0) {
3003
+ sectionKeys = new Set(tableColumnKeys);
3004
+ }
3005
+ else {
3006
+ sectionKeys = new Set();
3007
+ for (const row of baselineRows) {
3008
+ for (const key of Object.keys(row)) {
3009
+ sectionKeys.add(key);
3010
+ }
3011
+ }
3012
+ }
2990
3013
  const baselineById = new Map();
2991
3014
  for (const row of baselineRows) {
2992
3015
  const id = getRowId(row);
2993
- if (id) {
3016
+ if (id)
2994
3017
  baselineById.set(id, row);
2995
- }
2996
3018
  }
2997
3019
  const result = [];
2998
3020
  currentRows.forEach((row) => {
2999
3021
  const editAction = typeof row.edit_action === 'string' ? row.edit_action : undefined;
3000
3022
  const rowId = getRowId(row);
3001
- // Deleted row
3002
3023
  if (editAction === 'DELETE') {
3003
3024
  if (!rowId)
3004
3025
  return;
@@ -3009,7 +3030,6 @@ const diffTableRows = (baselineRecords, currentRecords) => {
3009
3030
  });
3010
3031
  return;
3011
3032
  }
3012
- // New row
3013
3033
  if (editAction === 'ADD' || !rowId) {
3014
3034
  result.push({
3015
3035
  ...row,
@@ -3017,20 +3037,17 @@ const diffTableRows = (baselineRecords, currentRecords) => {
3017
3037
  });
3018
3038
  return;
3019
3039
  }
3020
- // Existing row:
3021
- // Always include the complete row with UPDATE action,
3022
- // whether it was actually modified or not.
3023
3040
  result.push({
3024
- ...row,
3041
+ ...pickSectionFields(row, sectionKeys),
3025
3042
  edit_action: 'UPDATE',
3026
3043
  });
3027
3044
  });
3028
3045
  return result;
3029
3046
  };
3030
- function diffSectionChangeRecords(baselineRecords, currentRecords, { isTable }) {
3047
+ function diffSectionChangeRecords(baselineRecords, currentRecords, { isTable, internalRecordId, tableColumnKeys }) {
3031
3048
  return isTable
3032
- ? diffTableRows(baselineRecords, currentRecords)
3033
- : diffFormRecord(baselineRecords, currentRecords);
3049
+ ? diffTableRows(baselineRecords, currentRecords, tableColumnKeys)
3050
+ : diffFormRecord(baselineRecords, currentRecords, internalRecordId);
3034
3051
  }
3035
3052
 
3036
3053
  const cloneValue = (value) => {
@@ -3248,6 +3265,16 @@ const stripDocsWidgetFields = (records, panels) => {
3248
3265
  return copy;
3249
3266
  });
3250
3267
  };
3268
+ const readSectionInternalRecordId = (source, sectionRegisterId) => {
3269
+ if (!sectionRegisterId)
3270
+ return undefined;
3271
+ const sectionData = source[sectionRegisterId];
3272
+ if (!sectionData || typeof sectionData !== 'object' || Array.isArray(sectionData)) {
3273
+ return undefined;
3274
+ }
3275
+ const id = sectionData.internal_record_id;
3276
+ return typeof id === 'string' && id.length > 0 ? id : undefined;
3277
+ };
3251
3278
  const extractProfileImage = (records) => {
3252
3279
  let profileImage = null;
3253
3280
  const clonedRecords = records.map((record) => {
@@ -3303,7 +3330,23 @@ const executeSectionSave = async ({ store, dispatch, section, schemaData, contex
3303
3330
  if (sectionFieldsOnly) {
3304
3331
  const baselineStripped = stripDocsWidgetFields([...baselineRecords], section.panels);
3305
3332
  const isTable = sectionWidgets.some((widget) => isTableLikeWidget(widget));
3306
- records = diffSectionChangeRecords(baselineStripped, records, { isTable });
3333
+ const internalRecordId = readSectionInternalRecordId(baselineSource, sectionRegisterId) ??
3334
+ readSectionInternalRecordId(currentSchemaData, sectionRegisterId);
3335
+ let tableColumnKeys;
3336
+ if (isTable) {
3337
+ const tableWidget = sectionWidgets.find(isTableLikeWidget);
3338
+ const columns = tableWidget?.['widget-data-columns'];
3339
+ if (Array.isArray(columns)) {
3340
+ tableColumnKeys = columns
3341
+ .map((column) => column['column-key'] ?? column['widget-data-path'])
3342
+ .filter((key) => typeof key === 'string' && key.length > 0);
3343
+ }
3344
+ }
3345
+ records = diffSectionChangeRecords(baselineStripped, records, {
3346
+ isTable,
3347
+ internalRecordId,
3348
+ tableColumnKeys,
3349
+ });
3307
3350
  if (records.length === 0 && sectionFiles.length === 0 && !profileImage) {
3308
3351
  return { validated: true, saved: false, currentSchemaData };
3309
3352
  }
@@ -10538,8 +10581,6 @@ const RegisterLookupWidget = ({ config }) => {
10538
10581
  : t?.('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) })), 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 })] })] })] }))] }));
10539
10582
  };
10540
10583
 
10541
- const parentLookupPageCache = new Map();
10542
- const parentLookupPageInflight = new Map();
10543
10584
  const parentLookupRecordCache = new Map();
10544
10585
  const parsePagination = (pagination, rowCount, size, fallbackPage = 1) => {
10545
10586
  const totalItems = typeof pagination.number_of_items === 'number' ? pagination.number_of_items : rowCount;
@@ -10566,35 +10607,22 @@ const indexParentRecords = (rows) => {
10566
10607
  }
10567
10608
  }
10568
10609
  };
10569
- const buildParentLookupCacheKey = (service, endpoint, method, params) => `${service}|${endpoint}|${method}|${JSON.stringify(params)}`;
10610
+ const hasFilledParams = (params) => {
10611
+ if (!params)
10612
+ return false;
10613
+ const values = Object.values(params);
10614
+ if (values.length === 0)
10615
+ return false;
10616
+ return values.every((value) => value !== null && value !== undefined && String(value).trim() !== '');
10617
+ };
10570
10618
  const fetchParentLookupPage = async (handler, service, endpoint, method, params, headers) => {
10571
- const resolvedMethod = method || 'POST';
10572
- const cacheKey = buildParentLookupCacheKey(service, endpoint, resolvedMethod, params);
10573
- const cached = parentLookupPageCache.get(cacheKey);
10574
- if (cached) {
10575
- return cached;
10576
- }
10577
- const inflight = parentLookupPageInflight.get(cacheKey);
10578
- if (inflight) {
10579
- return inflight;
10580
- }
10581
- const fetchPromise = (async () => {
10582
- const result = await handler(service, endpoint, resolvedMethod, params, { headers });
10583
- const parsed = {
10584
- rows: (result?.records ?? []),
10585
- pagination: (result?.pagination ?? {}),
10586
- };
10587
- indexParentRecords(parsed.rows);
10588
- parentLookupPageCache.set(cacheKey, parsed);
10589
- return parsed;
10590
- })();
10591
- parentLookupPageInflight.set(cacheKey, fetchPromise);
10592
- try {
10593
- return await fetchPromise;
10594
- }
10595
- finally {
10596
- parentLookupPageInflight.delete(cacheKey);
10597
- }
10619
+ const result = await handler(service, endpoint, method || 'POST', params, { headers });
10620
+ const parsed = {
10621
+ rows: (result?.records ?? []),
10622
+ pagination: (result?.pagination ?? {}),
10623
+ };
10624
+ indexParentRecords(parsed.rows);
10625
+ return parsed;
10598
10626
  };
10599
10627
  const ParentLookupWidget = ({ config, value: valueProp, error: errorProp, touched: touchedProp, isEnabled: isEnabledProp, isRequired: isRequiredProp, onChange: onChangeProp, onBlur: onBlurProp, }) => {
10600
10628
  const hook = useBaseWidget({ config });
@@ -10624,6 +10652,11 @@ const ParentLookupWidget = ({ config, value: valueProp, error: errorProp, touche
10624
10652
  }
10625
10653
  return merged;
10626
10654
  }, [hostContext, dataSource?.params]);
10655
+ const canFetch = React.useMemo(() => hasFilledParams(hostContext) &&
10656
+ hasFilledParams(dataSource?.params) &&
10657
+ !!dataSource?.service &&
10658
+ !!dataSource?.endpoint &&
10659
+ !!dataSourceRequestHandler, [hostContext, dataSource, dataSourceRequestHandler]);
10627
10660
  const [isOpen, setIsOpen] = React.useState(false);
10628
10661
  const [searchText, setSearchText] = React.useState('');
10629
10662
  const [searchResults, setSearchResults] = React.useState([]);
@@ -10640,7 +10673,7 @@ const ParentLookupWidget = ({ config, value: valueProp, error: errorProp, touche
10640
10673
  const searchInputRef = React.useRef(null);
10641
10674
  const hydratedValueRef = React.useRef(null);
10642
10675
  const fetchRecords = React.useCallback(async (text, page, size) => {
10643
- if (!dataSource?.service || !dataSource?.endpoint || !dataSourceRequestHandler) {
10676
+ if (!canFetch || !dataSource?.service || !dataSource?.endpoint || !dataSourceRequestHandler) {
10644
10677
  return { rows: [], pagination: {} };
10645
10678
  }
10646
10679
  return fetchParentLookupPage(dataSourceRequestHandler, dataSource.service, dataSource.endpoint, dataSource.method, {
@@ -10649,7 +10682,7 @@ const ParentLookupWidget = ({ config, value: valueProp, error: errorProp, touche
10649
10682
  current_page: page,
10650
10683
  page_size: size,
10651
10684
  }, dataSource.headers);
10652
- }, [dataSource, dataSourceRequestHandler, requestParams]);
10685
+ }, [canFetch, dataSource, dataSourceRequestHandler, requestParams]);
10653
10686
  const findRecordByValue = React.useCallback(async (recordValue) => {
10654
10687
  const target = String(recordValue).trim();
10655
10688
  if (!target)
@@ -10747,7 +10780,7 @@ const ParentLookupWidget = ({ config, value: valueProp, error: errorProp, touche
10747
10780
  setIsHydrating(false);
10748
10781
  return;
10749
10782
  }
10750
- if (!dataSource?.service || !dataSource?.endpoint || !dataSourceRequestHandler)
10783
+ if (!canFetch)
10751
10784
  return;
10752
10785
  if (hydratedValueRef.current === value)
10753
10786
  return;
@@ -10777,7 +10810,7 @@ const ParentLookupWidget = ({ config, value: valueProp, error: errorProp, touche
10777
10810
  cancelled = true;
10778
10811
  setIsHydrating(false);
10779
10812
  };
10780
- }, [hasValue, value, dataSource, dataSourceRequestHandler, findRecordByValue]);
10813
+ }, [hasValue, value, canFetch, findRecordByValue]);
10781
10814
  const isReadonly = !!widgetConfig['widget-readonly'];
10782
10815
  const rawLabel = widgetConfig['widget-label'];
10783
10816
  const label = tSchema(t, rawLabel || 'Parent');
@@ -10829,7 +10862,7 @@ const ParentLookupWidget = ({ config, value: valueProp, error: errorProp, touche
10829
10862
  return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] flex flex-col sm:flex-row sm:items-start", children: [rawLabel && (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 text-base text-gray-900 font-medium", children: hasValue ? displayName : '-' })] }));
10830
10863
  }
10831
10864
  if (isReadonly && isCompact) {
10832
- return (jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-900 truncate block", children: hasValue ? displayName : '-' }));
10865
+ return (jsxRuntimeExports.jsx("span", { className: "text-sm truncate block", style: { color: 'inherit' }, children: hasValue ? displayName : '-' }));
10833
10866
  }
10834
10867
  return (jsxRuntimeExports.jsxs("div", { className: isCompact ? 'table-cell-field w-full' : 'mb-[10px]', children: [isCompact ? (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [selectTrigger, jsxRuntimeExports.jsx("p", { className: "table-cell-field-error", "aria-hidden": "true", children: '\u00a0' })] })) : (jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [rawLabel && (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: rawLabel, required: isRequired })), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [selectTrigger, touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] })), isOpen && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-[100]", style: { backgroundColor: 'rgba(0,0,0,0.5)' }, onClick: () => {
10835
10868
  setIsOpen(false);
@@ -11193,12 +11226,18 @@ function normalizeApiPayload(response) {
11193
11226
  }
11194
11227
  return [];
11195
11228
  }
11196
- /** Order flat levels root → leaf using parent_level_id. */
11229
+ function getRootLevels(levels) {
11230
+ return levels.filter((level) => !level.parent_level_id);
11231
+ }
11232
+ function getChildLevels(levels, parentLevelId) {
11233
+ return levels.filter((level) => level.parent_level_id === parentLevelId);
11234
+ }
11235
+ /** Depth-first from the single root so forked child levels are all included. */
11197
11236
  function buildOrderedLevels(flat) {
11198
11237
  if (flat.length === 0) {
11199
11238
  return [];
11200
11239
  }
11201
- const roots = flat.filter((level) => level.parent_level_id == null || level.parent_level_id === '');
11240
+ const roots = getRootLevels(flat);
11202
11241
  if (roots.length !== 1) {
11203
11242
  throw new Error(roots.length === 0
11204
11243
  ? 'Geo hierarchy has no root level'
@@ -11206,15 +11245,17 @@ function buildOrderedLevels(flat) {
11206
11245
  }
11207
11246
  const ordered = [];
11208
11247
  const visited = new Set();
11209
- let current = roots[0];
11210
- while (current) {
11211
- if (visited.has(current.level_id)) {
11248
+ const walk = (level) => {
11249
+ if (visited.has(level.level_id)) {
11212
11250
  throw new Error('Geo hierarchy contains a cycle');
11213
11251
  }
11214
- visited.add(current.level_id);
11215
- ordered.push(current);
11216
- current = flat.find((level) => level.parent_level_id === current?.level_id);
11217
- }
11252
+ visited.add(level.level_id);
11253
+ ordered.push(level);
11254
+ for (const child of getChildLevels(flat, level.level_id)) {
11255
+ walk(child);
11256
+ }
11257
+ };
11258
+ walk(roots[0]);
11218
11259
  if (ordered.length !== flat.length) {
11219
11260
  throw new Error('Geo hierarchy contains disconnected levels');
11220
11261
  }
@@ -11359,46 +11400,29 @@ function geoIdsMatch(left, right) {
11359
11400
  }
11360
11401
  return normalizeGeoId(left) === normalizeGeoId(right);
11361
11402
  }
11362
- function buildChainEntry(orderedLevels, chain, index, entry) {
11363
- const level = orderedLevels[index];
11403
+ function buildChainEntryFromLevel(level, chain, entry) {
11364
11404
  return {
11365
11405
  level_value_id: entry.level_value_id,
11366
11406
  level_id: level.level_id,
11367
11407
  level_value_mnemonic: entry.level_value_mnemonic || '',
11368
- parent_level_value_id: index > 0 ? chain[index - 1].level_value_id : null,
11408
+ parent_level_value_id: chain.length > 0 ? chain[chain.length - 1].level_value_id : null,
11369
11409
  };
11370
11410
  }
11371
- /** Map geo_code_hierarchy_json entries onto ordered API levels (root leaf). */
11411
+ /** Map geo_code_hierarchy_json entries onto levels by id/mnemonic (path may stop before a fork's unused branch). */
11372
11412
  function mapHierarchyToChain(orderedLevels, hierarchy) {
11373
11413
  if (orderedLevels.length === 0 || hierarchy.length === 0) {
11374
11414
  return [];
11375
11415
  }
11376
- if (hierarchy.length === orderedLevels.length) {
11377
- const indexChain = [];
11378
- let isComplete = true;
11379
- for (let index = 0; index < orderedLevels.length; index += 1) {
11380
- const entry = hierarchy[index];
11381
- if (!entry?.level_value_id) {
11382
- isComplete = false;
11383
- break;
11384
- }
11385
- indexChain.push(buildChainEntry(orderedLevels, indexChain, index, entry));
11386
- }
11387
- if (isComplete && indexChain.length === orderedLevels.length) {
11388
- return indexChain;
11389
- }
11390
- }
11391
11416
  const chain = [];
11392
- for (let index = 0; index < orderedLevels.length; index += 1) {
11393
- const level = orderedLevels[index];
11394
- const entry = hierarchy[index]?.level_value_id &&
11395
- matchesGeoLevel(hierarchy[index], level)
11396
- ? hierarchy[index]
11397
- : hierarchy.find((item) => matchesGeoLevel(item, level));
11417
+ for (const entry of hierarchy) {
11398
11418
  if (!entry?.level_value_id) {
11399
11419
  break;
11400
11420
  }
11401
- chain.push(buildChainEntry(orderedLevels, chain, index, entry));
11421
+ const level = orderedLevels.find((item) => matchesGeoLevel(entry, item));
11422
+ if (!level) {
11423
+ break;
11424
+ }
11425
+ chain.push(buildChainEntryFromLevel(level, chain, entry));
11402
11426
  }
11403
11427
  return chain;
11404
11428
  }
@@ -11426,27 +11450,156 @@ function matchesGeoLevel(entry, level) {
11426
11450
  entry.level === level.level_mnemonic ||
11427
11451
  entry.level === level.level_id);
11428
11452
  }
11429
- function getDeepestSelectedValue(orderedLevels, selectedValues) {
11430
- let deepest = null;
11431
- for (const level of orderedLevels) {
11432
- const value = selectedValues[level.level_id];
11433
- if (value) {
11434
- deepest = value;
11453
+ function getSelectedPath(orderedLevels, selectedValues) {
11454
+ const roots = getRootLevels(orderedLevels);
11455
+ if (roots.length !== 1) {
11456
+ return [];
11457
+ }
11458
+ const path = [];
11459
+ let current = roots[0];
11460
+ while (current && selectedValues[current.level_id]) {
11461
+ path.push(current);
11462
+ const children = getChildLevels(orderedLevels, current.level_id);
11463
+ const next = children.find((child) => selectedValues[child.level_id]);
11464
+ current = next;
11465
+ }
11466
+ return path;
11467
+ }
11468
+ function collectDescendantLevelIds(orderedLevels, parentLevelId) {
11469
+ const ids = [];
11470
+ const walk = (levelId) => {
11471
+ for (const child of getChildLevels(orderedLevels, levelId)) {
11472
+ ids.push(child.level_id);
11473
+ walk(child.level_id);
11435
11474
  }
11475
+ };
11476
+ walk(parentLevelId);
11477
+ return ids;
11478
+ }
11479
+ function getDeepestSelectedValue(orderedLevels, selectedValues) {
11480
+ const path = getSelectedPath(orderedLevels, selectedValues);
11481
+ if (path.length === 0) {
11482
+ return null;
11436
11483
  }
11437
- return deepest;
11484
+ return selectedValues[path[path.length - 1].level_id] ?? null;
11438
11485
  }
11439
- /** True when every hierarchy level has a selected value. */
11440
- function isGeoHierarchyComplete(orderedLevels, selectedValues) {
11441
- if (orderedLevels.length === 0) {
11486
+ /**
11487
+ * Complete when the selected path reaches a leaf:
11488
+ * no child levels, or every child level has an empty option list for this parent
11489
+ * (city with no subdistricts). Sibling forks require exactly one filled branch.
11490
+ */
11491
+ function isGeoHierarchyComplete(orderedLevels, selectedValues, options = {}) {
11492
+ const roots = getRootLevels(orderedLevels);
11493
+ if (roots.length !== 1) {
11442
11494
  return false;
11443
11495
  }
11444
- return orderedLevels.every((level) => Boolean(selectedValues[level.level_id]));
11496
+ const isCompleteAt = (level) => {
11497
+ if (!selectedValues[level.level_id]) {
11498
+ return false;
11499
+ }
11500
+ const children = getChildLevels(orderedLevels, level.level_id);
11501
+ if (children.length === 0) {
11502
+ return true;
11503
+ }
11504
+ if (children.some((child) => options[child.level_id] === undefined)) {
11505
+ return false;
11506
+ }
11507
+ const withOptions = children.filter((child) => (options[child.level_id]?.length ?? 0) > 0);
11508
+ if (withOptions.length === 0) {
11509
+ return true;
11510
+ }
11511
+ const chosen = children.filter((child) => selectedValues[child.level_id]);
11512
+ if (chosen.length !== 1) {
11513
+ return false;
11514
+ }
11515
+ return isCompleteAt(chosen[0]);
11516
+ };
11517
+ return isCompleteAt(roots[0]);
11518
+ }
11519
+ function childLevelsToShow(children, selectedValues, options) {
11520
+ if (children.length === 1) {
11521
+ return children;
11522
+ }
11523
+ return children.filter((child) => selectedValues[child.level_id] ||
11524
+ options[child.level_id] === undefined ||
11525
+ (options[child.level_id]?.length ?? 0) > 0);
11526
+ }
11527
+ /**
11528
+ * One form control per hop. A parent with several child levels (city vs
11529
+ * subdistrict) is a single grouped dropdown, not parallel fields.
11530
+ * Linear children are always included so the full chain is visible at once;
11531
+ * options stay empty until the parent is selected.
11532
+ */
11533
+ function buildGeoFormSteps(orderedLevels, selectedValues, options = {}) {
11534
+ const roots = getRootLevels(orderedLevels);
11535
+ if (roots.length !== 1) {
11536
+ return orderedLevels.map((level) => ({
11537
+ kind: 'single',
11538
+ key: level.level_id,
11539
+ level,
11540
+ parentValueId: '',
11541
+ }));
11542
+ }
11543
+ const steps = [
11544
+ { kind: 'single', key: roots[0].level_id, level: roots[0], parentValueId: '' },
11545
+ ];
11546
+ let current = roots[0];
11547
+ while (true) {
11548
+ const children = getChildLevels(orderedLevels, current.level_id);
11549
+ if (children.length === 0) {
11550
+ break;
11551
+ }
11552
+ const toShow = childLevelsToShow(children, selectedValues, options);
11553
+ if (toShow.length === 0) {
11554
+ break;
11555
+ }
11556
+ const parentValueId = selectedValues[current.level_id] || '';
11557
+ if (toShow.length === 1) {
11558
+ const next = toShow[0];
11559
+ steps.push({
11560
+ kind: 'single',
11561
+ key: next.level_id,
11562
+ level: next,
11563
+ parentValueId,
11564
+ });
11565
+ current = next;
11566
+ continue;
11567
+ }
11568
+ steps.push({
11569
+ kind: 'fork',
11570
+ key: `fork:${current.level_id}`,
11571
+ levels: toShow,
11572
+ parentLevel: current,
11573
+ parentValueId,
11574
+ });
11575
+ const chosen = toShow.find((child) => selectedValues[child.level_id]);
11576
+ if (!chosen) {
11577
+ break;
11578
+ }
11579
+ current = chosen;
11580
+ }
11581
+ return steps;
11582
+ }
11583
+ function encodeGeoSelectValue(levelId, valueId) {
11584
+ return `${levelId}::${valueId}`;
11585
+ }
11586
+ function parseGeoSelectValue(raw) {
11587
+ const separator = raw.indexOf('::');
11588
+ if (separator <= 0) {
11589
+ return null;
11590
+ }
11591
+ const levelId = raw.slice(0, separator);
11592
+ const valueId = raw.slice(separator + 2);
11593
+ if (!levelId || !valueId) {
11594
+ return null;
11595
+ }
11596
+ return { levelId, valueId };
11445
11597
  }
11446
- /** Build geo_code_hierarchy_json document from current selections (for save payload). */
11598
+ /** Build geo_code_hierarchy_json document from the selected path (not unused forks). */
11447
11599
  function buildHierarchyJson(orderedLevels, selectedValues, options, resolvedLabels = {}) {
11448
11600
  const hierarchy = [];
11449
- for (const level of orderedLevels) {
11601
+ const path = getSelectedPath(orderedLevels, selectedValues);
11602
+ for (const level of path) {
11450
11603
  const levelValueId = selectedValues[level.level_id];
11451
11604
  if (!levelValueId) {
11452
11605
  break;
@@ -11480,22 +11633,37 @@ function formatHierarchyForPersist(document, previous) {
11480
11633
  return document;
11481
11634
  }
11482
11635
  function clearDescendants(orderedLevels, fromIndex, selectedValues, options) {
11636
+ const fromLevel = orderedLevels[fromIndex];
11483
11637
  const nextSelected = { ...selectedValues };
11484
11638
  const nextOptions = { ...options };
11485
- for (let index = fromIndex + 1; index < orderedLevels.length; index += 1) {
11486
- const levelId = orderedLevels[index].level_id;
11639
+ if (!fromLevel) {
11640
+ return { selectedValues: nextSelected, options: nextOptions };
11641
+ }
11642
+ for (const levelId of collectDescendantLevelIds(orderedLevels, fromLevel.level_id)) {
11487
11643
  delete nextSelected[levelId];
11488
11644
  delete nextOptions[levelId];
11489
11645
  }
11490
11646
  return { selectedValues: nextSelected, options: nextOptions };
11491
11647
  }
11492
- /** Level is enabled when it is the root, or its parent already has a selection. */
11493
- function isLevelEnabled(orderedLevels, levelIndex, selectedValues) {
11494
- if (levelIndex === 0) {
11495
- return true;
11648
+ /** After choosing one sibling fork, drop the other siblings and their descendants. */
11649
+ function clearUnselectedSiblingBranches(orderedLevels, chosenLevelId, selectedValues, options) {
11650
+ const chosen = orderedLevels.find((level) => level.level_id === chosenLevelId);
11651
+ const nextSelected = { ...selectedValues };
11652
+ const nextOptions = { ...options };
11653
+ if (!chosen?.parent_level_id) {
11654
+ return { selectedValues: nextSelected, options: nextOptions };
11496
11655
  }
11497
- const parent = orderedLevels[levelIndex - 1];
11498
- return Boolean(selectedValues[parent.level_id]);
11656
+ for (const sibling of getChildLevels(orderedLevels, chosen.parent_level_id)) {
11657
+ if (sibling.level_id === chosenLevelId) {
11658
+ continue;
11659
+ }
11660
+ delete nextSelected[sibling.level_id];
11661
+ for (const levelId of collectDescendantLevelIds(orderedLevels, sibling.level_id)) {
11662
+ delete nextSelected[levelId];
11663
+ delete nextOptions[levelId];
11664
+ }
11665
+ }
11666
+ return { selectedValues: nextSelected, options: nextOptions };
11499
11667
  }
11500
11668
  /** Map a root→leaf chain onto level_id → level_value_id selections. */
11501
11669
  function mapChainToSelections(orderedLevels, chain) {
@@ -11510,7 +11678,7 @@ function mapChainToSelections(orderedLevels, chain) {
11510
11678
  }
11511
11679
  function buildReadonlyPath(orderedLevels, selectedValues, options, resolvedLabels) {
11512
11680
  const parts = [];
11513
- for (const level of orderedLevels) {
11681
+ for (const level of getSelectedPath(orderedLevels, selectedValues)) {
11514
11682
  const selected = selectedValues[level.level_id];
11515
11683
  if (!selected) {
11516
11684
  break;
@@ -11670,7 +11838,7 @@ function useGeoHierarchy({ config }) {
11670
11838
  return;
11671
11839
  }
11672
11840
  const deepest = getDeepestSelectedValue(orderedLevels, nextSelectedValues);
11673
- const complete = isGeoHierarchyComplete(orderedLevels, nextSelectedValues);
11841
+ const complete = isGeoHierarchyComplete(orderedLevels, nextSelectedValues, nextOptions);
11674
11842
  // When required, only persist the leaf once every level is filled so submit validation fails for partial chains.
11675
11843
  const nextValue = base.isRequired && !complete ? null : (deepest ?? null);
11676
11844
  selfPersistedValueRef.current = nextValue ? String(nextValue) : '';
@@ -11705,11 +11873,15 @@ function useGeoHierarchy({ config }) {
11705
11873
  }, [fetchValues]);
11706
11874
  const loadOptionsAlongChain = React.useCallback(async (orderedLevels, chain) => {
11707
11875
  const nextOptions = {};
11708
- let parentValueId = '';
11709
- for (let index = 0; index < orderedLevels.length; index += 1) {
11710
- const level = orderedLevels[index];
11711
- nextOptions[level.level_id] = await fetchValues(level.level_id, parentValueId);
11712
- parentValueId = chain[index]?.level_value_id || parentValueId;
11876
+ const root = orderedLevels.find((level) => !level.parent_level_id);
11877
+ if (root) {
11878
+ nextOptions[root.level_id] = await fetchValues(root.level_id, '');
11879
+ }
11880
+ for (const entry of chain) {
11881
+ const children = getChildLevels(orderedLevels, entry.level_id);
11882
+ await Promise.all(children.map(async (child) => {
11883
+ nextOptions[child.level_id] = await fetchValues(child.level_id, entry.level_value_id);
11884
+ }));
11713
11885
  }
11714
11886
  return nextOptions;
11715
11887
  }, [fetchValues]);
@@ -11841,10 +12013,14 @@ function useGeoHierarchy({ config }) {
11841
12013
  lastInitializedKeyRef.current = initKey;
11842
12014
  void initialize();
11843
12015
  }, [baseHierarchyJson, baseStoredValue, initialize, isReadonly]);
11844
- const handleLevelChange = React.useCallback(async (levelIndex, nextValue) => {
12016
+ const handleValueChange = React.useCallback(async (levelId, nextValue) => {
11845
12017
  if (!levels.length || initializingRef.current || hydratingRef.current || isReadonly) {
11846
12018
  return;
11847
12019
  }
12020
+ const levelIndex = levels.findIndex((item) => item.level_id === levelId);
12021
+ if (levelIndex < 0) {
12022
+ return;
12023
+ }
11848
12024
  const level = levels[levelIndex];
11849
12025
  let nextSelectedValues = { ...selectedValues };
11850
12026
  if (!nextValue) {
@@ -11855,15 +12031,26 @@ function useGeoHierarchy({ config }) {
11855
12031
  }
11856
12032
  const cleared = clearDescendants(levels, levelIndex, nextSelectedValues, options);
11857
12033
  nextSelectedValues = cleared.selectedValues;
12034
+ const siblingsCleared = clearUnselectedSiblingBranches(levels, level.level_id, nextSelectedValues, cleared.options);
12035
+ nextSelectedValues = siblingsCleared.selectedValues;
11858
12036
  setSelectedValues(nextSelectedValues);
11859
- setOptions(cleared.options);
11860
- persistDeepestValue(nextSelectedValues, levels, cleared.options, resolvedLabels);
11861
- if (!nextValue || levelIndex >= levels.length - 1) {
12037
+ setOptions(siblingsCleared.options);
12038
+ persistDeepestValue(nextSelectedValues, levels, siblingsCleared.options, resolvedLabels);
12039
+ if (!nextValue) {
11862
12040
  return;
11863
12041
  }
11864
12042
  try {
11865
12043
  setGeoError(null);
11866
- await loadOptionsForLevel(levels, levelIndex + 1, nextValue);
12044
+ const children = getChildLevels(levels, level.level_id);
12045
+ let nextOptions = siblingsCleared.options;
12046
+ for (const child of children) {
12047
+ const childIndex = levels.findIndex((item) => item.level_id === child.level_id);
12048
+ if (childIndex < 0)
12049
+ continue;
12050
+ const childOptions = await loadOptionsForLevel(levels, childIndex, nextValue);
12051
+ nextOptions = { ...nextOptions, [child.level_id]: childOptions };
12052
+ }
12053
+ persistDeepestValue(nextSelectedValues, levels, nextOptions, resolvedLabels);
11867
12054
  }
11868
12055
  catch (error) {
11869
12056
  const message = error instanceof Error ? error.message : 'Failed to load child geo level values';
@@ -11879,17 +12066,31 @@ function useGeoHierarchy({ config }) {
11879
12066
  loadOptionsForLevel,
11880
12067
  ]);
11881
12068
  const readonlyPath = React.useMemo(() => buildReadonlyPath(levels, selectedValues, options, resolvedLabels), [levels, selectedValues, options, resolvedLabels]);
11882
- const { columnCounts, columns: levelColumns, columnSpan } = React.useMemo(() => resolveGeoLevelColumns(levels, geoLayout), [levels, geoLayout]);
12069
+ const selectedPath = React.useMemo(() => getSelectedPath(levels, selectedValues), [levels, selectedValues]);
12070
+ const formSteps = React.useMemo(() => buildGeoFormSteps(levels, selectedValues, options), [levels, options, selectedValues]);
12071
+ const isComplete = React.useMemo(() => isGeoHierarchyComplete(levels, selectedValues, options), [levels, options, selectedValues]);
12072
+ const { columnCounts, columns: stepColumns, columnSpan } = React.useMemo(() => resolveGeoLevelColumns(formSteps.map((step) => step.kind === 'single'
12073
+ ? step.level
12074
+ : {
12075
+ level_id: step.key,
12076
+ level_mnemonic: step.levels.map((level) => level.level_mnemonic).join(' / '),
12077
+ parent_level_id: step.parentLevel.level_id,
12078
+ }), geoLayout), [formSteps, geoLayout]);
11883
12079
  const visibleColumns = React.useMemo(() => {
12080
+ const stepsByKey = new Map(formSteps.map((step) => [step.key, step]));
11884
12081
  const columnIndex = geoLayout?.columnIndex;
12082
+ const mapped = stepColumns.map((columnLevels, index) => ({
12083
+ index,
12084
+ steps: columnLevels
12085
+ .map((level) => stepsByKey.get(level.level_id))
12086
+ .filter((step) => Boolean(step)),
12087
+ }));
11885
12088
  if (columnIndex === undefined || columnIndex === null) {
11886
- return levelColumns
11887
- .map((columnLevels, index) => ({ index, levels: columnLevels }))
11888
- .filter((column) => column.levels.length > 0);
12089
+ return mapped.filter((column) => column.steps.length > 0);
11889
12090
  }
11890
- const columnLevels = levelColumns[columnIndex] ?? [];
11891
- return columnLevels.length > 0 ? [{ index: columnIndex, levels: columnLevels }] : [];
11892
- }, [levelColumns, geoLayout?.columnIndex]);
12091
+ const column = mapped[columnIndex];
12092
+ return column?.steps.length ? [column] : [];
12093
+ }, [formSteps, geoLayout?.columnIndex, stepColumns]);
11893
12094
  return {
11894
12095
  ...base,
11895
12096
  levels,
@@ -11903,77 +12104,106 @@ function useGeoHierarchy({ config }) {
11903
12104
  loadingLevelId,
11904
12105
  geoError,
11905
12106
  readonlyPath,
11906
- handleLevelChange,
11907
- isLevelEnabled: (levelIndex) => isLevelEnabled(levels, levelIndex, selectedValues),
12107
+ handleValueChange,
12108
+ isComplete,
12109
+ selectedPath,
12110
+ formSteps,
11908
12111
  formatLevelLabel,
11909
12112
  };
11910
12113
  }
11911
12114
 
12115
+ const selectClassName = (showError, disabled) => `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 ${showError
12116
+ ? 'border-red-500 focus:ring-red-500 focus:border-red-500'
12117
+ : 'border-gray-300'} ${disabled ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`;
11912
12118
  function resolveDisplayValue(levelId, selectedValues, options, resolvedLabels, loading) {
11913
12119
  const selected = selectedValues[levelId];
11914
12120
  if (!selected) {
11915
12121
  return '-';
11916
12122
  }
11917
- const option = options[levelId]?.find((item) => item.value === selected);
11918
- if (option?.label) {
11919
- return option.label;
12123
+ const option = options[levelId]?.find((item) => item.value === selected)?.label;
12124
+ if (option) {
12125
+ return option;
11920
12126
  }
11921
12127
  if (resolvedLabels[selected]) {
11922
12128
  return resolvedLabels[selected];
11923
12129
  }
11924
12130
  return loading ? '-' : selected;
11925
12131
  }
11926
- function renderLevelRows({ columnLevels, levels, isReadonly, selectedValues, options, resolvedLabels, loadingLevels, loadingLevelId, isEnabled, isRequired, touched, hasError, t, onBlur, handleLevelChange, isLevelEnabled, formatLevelLabel, }) {
11927
- return columnLevels.map((level) => {
11928
- const levelIndex = levels.findIndex((item) => item.level_id === level.level_id);
11929
- const levelLabel = formatLevelLabel(level.level_mnemonic);
11930
- if (isReadonly) {
11931
- const displayValue = resolveDisplayValue(level.level_id, selectedValues, options, resolvedLabels, loadingLevels);
11932
- return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] SelectDisplayWidget 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: levelLabel, children: [levelLabel, ":"] }), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", style: { fontFamily: 'Roboto, sans-serif' }, title: displayValue, children: tSchema(t, displayValue) }) })] }, level.level_id));
11933
- }
11934
- const levelOptions = options[level.level_id] || [];
11935
- const isLoading = loadingLevelId === level.level_id;
11936
- const levelEnabled = isLevelEnabled(levelIndex);
11937
- const disabled = !isEnabled || loadingLevels || isLoading || !levelEnabled;
11938
- const levelHasValue = Boolean(selectedValues[level.level_id]);
11939
- const showLevelError = (isRequired && !levelHasValue) || (touched && hasError && !levelHasValue);
11940
- 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: levelLabel, required: isRequired }), jsxRuntimeExports.jsx("div", { className: "flex-1 min-w-0", children: jsxRuntimeExports.jsxs("select", { value: selectedValues[level.level_id] || '', onChange: (event) => {
11941
- const nextValue = event.target.value;
11942
- void handleLevelChange(levelIndex, nextValue === '' ? undefined : nextValue);
11943
- }, onBlur: onBlur, disabled: disabled, 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 ${showLevelError
11944
- ? 'border-red-500 focus:ring-red-500 focus:border-red-500'
11945
- : 'border-gray-300'} ${disabled ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, children: [jsxRuntimeExports.jsx("option", { value: "", children: t?.('common.select') }), levelOptions.map((option) => (jsxRuntimeExports.jsx("option", { value: option.value, children: tSchema(t, option.label) }, option.value)))] }) })] }) }, level.level_id));
12132
+ function ReadonlyLevelRow({ level, displayValue, }) {
12133
+ return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] SelectDisplayWidget 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: level.level_mnemonic, children: [level.level_mnemonic, ":"] }), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", style: { fontFamily: 'Roboto, sans-serif' }, title: displayValue, children: displayValue }) })] }));
12134
+ }
12135
+ function renderFormSteps({ columnSteps, selectedValues, options, loadingLevels, loadingLevelId, isEnabled, isRequired, isComplete, touched, hasError, t, onBlur, handleValueChange, formatLevelLabel, }) {
12136
+ return columnSteps.map((step) => {
12137
+ if (step.kind === 'single') {
12138
+ const level = step.level;
12139
+ const levelOptions = options[level.level_id] || [];
12140
+ const isLoading = loadingLevelId === level.level_id;
12141
+ const parentId = level.parent_level_id;
12142
+ const parentUnselected = Boolean(parentId) && !selectedValues[parentId ?? ''];
12143
+ const disabled = !isEnabled || loadingLevels || isLoading || parentUnselected;
12144
+ const levelHasValue = Boolean(selectedValues[level.level_id]);
12145
+ const showLevelError = !parentUnselected &&
12146
+ ((isRequired && !isComplete && !levelHasValue) ||
12147
+ (touched && hasError && !levelHasValue));
12148
+ const label = formatLevelLabel(level.level_mnemonic);
12149
+ 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.jsx("div", { className: "flex-1 min-w-0", children: jsxRuntimeExports.jsxs("select", { value: selectedValues[level.level_id] || '', onChange: (event) => {
12150
+ const nextValue = event.target.value;
12151
+ void handleValueChange(level.level_id, nextValue === '' ? undefined : nextValue);
12152
+ }, onBlur: onBlur, disabled: disabled, className: selectClassName(showLevelError, disabled), style: { borderRadius: '10px' }, children: [jsxRuntimeExports.jsx("option", { value: "", children: t?.('common.select') }), levelOptions.map((option) => (jsxRuntimeExports.jsx("option", { value: option.value, children: tSchema(t, option.label) }, option.value)))] }) })] }) }, step.key));
12153
+ }
12154
+ const chosen = step.levels.find((level) => selectedValues[level.level_id]);
12155
+ const activeLevel = chosen ?? step.levels[0];
12156
+ const encodedValue = chosen
12157
+ ? encodeGeoSelectValue(chosen.level_id, selectedValues[chosen.level_id])
12158
+ : '';
12159
+ const forkLoading = step.levels.some((level) => loadingLevelId === level.level_id);
12160
+ const parentUnselected = !selectedValues[step.parentLevel.level_id];
12161
+ const disabled = !isEnabled || loadingLevels || forkLoading || parentUnselected;
12162
+ const showLevelError = !parentUnselected &&
12163
+ ((isRequired && !isComplete && !encodedValue) ||
12164
+ (touched && hasError && !encodedValue));
12165
+ 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: formatLevelLabel(activeLevel.level_mnemonic), required: isRequired }), jsxRuntimeExports.jsx("div", { className: "flex-1 min-w-0", children: jsxRuntimeExports.jsxs("select", { value: encodedValue, onChange: (event) => {
12166
+ const raw = event.target.value;
12167
+ if (!raw) {
12168
+ const selectedLevel = chosen ?? step.levels[0];
12169
+ void handleValueChange(selectedLevel.level_id, undefined);
12170
+ return;
12171
+ }
12172
+ const parsed = parseGeoSelectValue(raw);
12173
+ if (!parsed) {
12174
+ return;
12175
+ }
12176
+ void handleValueChange(parsed.levelId, parsed.valueId);
12177
+ }, onBlur: onBlur, disabled: disabled, className: selectClassName(showLevelError, disabled), style: { borderRadius: '10px' }, children: [jsxRuntimeExports.jsx("option", { value: "", children: t?.('common.select') }), step.levels.map((level) => {
12178
+ const levelOptions = options[level.level_id] || [];
12179
+ if (levelOptions.length === 0 && !selectedValues[level.level_id]) {
12180
+ return null;
12181
+ }
12182
+ return (jsxRuntimeExports.jsx("optgroup", { label: formatLevelLabel(level.level_mnemonic), children: levelOptions.map((option) => (jsxRuntimeExports.jsx("option", { value: encodeGeoSelectValue(level.level_id, option.value), children: tSchema(t, option.label) }, option.value))) }, level.level_id));
12183
+ })] }) })] }) }, step.key));
11946
12184
  });
11947
12185
  }
11948
12186
  const GeoHierarchyWidget = ({ config }) => {
11949
- const { isEnabled, isRequired, error, touched, onBlur, config: widgetConfig, levels, selectedValues, options, resolvedLabels, visibleColumns, loadingLevels, loadingLevelId, geoError, handleLevelChange, isLevelEnabled, formatLevelLabel, } = useGeoHierarchy({ config });
12187
+ const { isEnabled, isRequired, error, touched, onBlur, config: widgetConfig, levels, selectedValues, options, resolvedLabels, visibleColumns, loadingLevels, loadingLevelId, geoError, handleValueChange, isComplete, selectedPath, formatLevelLabel, } = useGeoHierarchy({ config });
11950
12188
  const { t } = useWidgetContext();
11951
- const isComplete = levels.length > 0 &&
11952
- levels.every((level) => Boolean(selectedValues[level.level_id]));
11953
12189
  const hasError = error.length > 0 || (isRequired && levels.length > 0 && !isComplete);
11954
12190
  const isReadonly = Boolean(widgetConfig['widget-readonly']);
11955
- const rowProps = {
11956
- levels,
11957
- isReadonly,
12191
+ const layoutColumnCount = Math.max(visibleColumns.length, 1);
12192
+ const editContent = visibleColumns.length <= 1 ? (renderFormSteps({
12193
+ columnSteps: visibleColumns[0]?.steps ?? [],
11958
12194
  selectedValues,
11959
12195
  options,
11960
- resolvedLabels,
11961
12196
  loadingLevels,
11962
12197
  loadingLevelId,
11963
12198
  isEnabled,
11964
12199
  isRequired,
12200
+ isComplete,
11965
12201
  touched,
11966
12202
  hasError,
11967
12203
  t,
11968
12204
  onBlur,
11969
- handleLevelChange,
11970
- isLevelEnabled,
12205
+ handleValueChange,
11971
12206
  formatLevelLabel,
11972
- };
11973
- const layoutColumnCount = Math.max(visibleColumns.length, 1);
11974
- const content = visibleColumns.length <= 1 ? (renderLevelRows({
11975
- ...rowProps,
11976
- columnLevels: visibleColumns[0]?.levels ?? levels,
11977
12207
  })) : (jsxRuntimeExports.jsx("div", { className: "flex flex-col lg:grid w-full", style: {
11978
12208
  gridTemplateColumns: `repeat(${layoutColumnCount}, minmax(200px, 1fr))`,
11979
12209
  }, children: visibleColumns.map((column, position) => {
@@ -11987,12 +12217,26 @@ const GeoHierarchyWidget = ({ config }) => {
11987
12217
  .join(' ');
11988
12218
  return (jsxRuntimeExports.jsxs("div", { className: columnClassName, children: [!isLast && (jsxRuntimeExports.jsx("div", { className: "hidden lg:block absolute right-0 top-0 w-px", style: {
11989
12219
  bottom: '5px',
11990
- backgroundColor: isReadonly
11991
- ? 'var(--owt-panel-divider-color, #C4C4C4)'
11992
- : 'var(--owt-color-primary, #F5BB1A)',
11993
- } })), renderLevelRows({ ...rowProps, columnLevels: column.levels })] }, `geo-column-${column.index}`));
12220
+ backgroundColor: 'var(--owt-color-primary, #F5BB1A)',
12221
+ } })), renderFormSteps({
12222
+ columnSteps: column.steps,
12223
+ selectedValues,
12224
+ options,
12225
+ loadingLevels,
12226
+ loadingLevelId,
12227
+ isEnabled,
12228
+ isRequired,
12229
+ isComplete,
12230
+ touched,
12231
+ hasError,
12232
+ t,
12233
+ onBlur,
12234
+ handleValueChange,
12235
+ formatLevelLabel,
12236
+ })] }, `geo-column-${column.index}`));
11994
12237
  }) }));
11995
- return (jsxRuntimeExports.jsxs("div", { className: isReadonly ? 'GeoHierarchyDisplayWidget' : 'GeoHierarchyWidget', children: [content, loadingLevels && levels.length === 0 && (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500 mb-[10px]", children: t?.('common.loading') })), geoError && jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mb-[10px]", children: geoError }), !isReadonly && touched && hasError && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mb-[10px]", children: error[0] || 'This field is required' }))] }));
12238
+ const readonlyContent = selectedPath.map((level) => (jsxRuntimeExports.jsx(ReadonlyLevelRow, { level: { ...level, level_mnemonic: formatLevelLabel(level.level_mnemonic) }, displayValue: tSchema(t, resolveDisplayValue(level.level_id, selectedValues, options, resolvedLabels, loadingLevels)) }, level.level_id)));
12239
+ return (jsxRuntimeExports.jsxs("div", { className: isReadonly ? 'GeoHierarchyDisplayWidget' : 'GeoHierarchyWidget', children: [isReadonly ? readonlyContent : editContent, loadingLevels && levels.length === 0 && (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500 mb-[10px]", children: t?.('common.loading') })), geoError && jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mb-[10px]", children: geoError }), !isReadonly && touched && hasError && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mb-[10px]", children: error[0] || 'This field is required' }))] }));
11996
12240
  };
11997
12241
 
11998
12242
  const distributeDocsToColumns = (docs, totalDocs) => {