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