@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/components/SectionRenderer/utils/diffSectionChangeRecords.d.ts +3 -1
- package/dist/components/SectionRenderer/utils/diffSectionChangeRecords.d.ts.map +1 -1
- package/dist/components/SectionRenderer/utils/executeSectionSave.d.ts.map +1 -1
- package/dist/hooks/useBaseWidget.d.ts.map +1 -1
- package/dist/hooks/useGeoHierarchy.d.ts +5 -3
- package/dist/hooks/useGeoHierarchy.d.ts.map +1 -1
- package/dist/index.esm.js +439 -195
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +439 -195
- package/dist/index.js.map +1 -1
- package/dist/utils/geoHierarchy.d.ts +43 -5
- package/dist/utils/geoHierarchy.d.ts.map +1 -1
- package/dist/widgets/GeoHierarchyWidget.d.ts.map +1 -1
- package/dist/widgets/ParentLookupWidget.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/components/SectionRenderer/components/CRViewFooter.d.ts +0 -8
- package/dist/components/SectionRenderer/components/CRViewFooter.d.ts.map +0 -1
- package/dist/components/SectionRenderer/hooks/useCrViewData.d.ts +0 -8
- package/dist/components/SectionRenderer/hooks/useCrViewData.d.ts.map +0 -1
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 =
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
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).
|
|
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
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
10570
|
-
const
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
|
|
10574
|
-
|
|
10575
|
-
|
|
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 (!
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
11208
|
-
|
|
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(
|
|
11213
|
-
ordered.push(
|
|
11214
|
-
|
|
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
|
|
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:
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
|
11428
|
-
|
|
11429
|
-
|
|
11430
|
-
|
|
11431
|
-
|
|
11432
|
-
|
|
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
|
|
11482
|
+
return selectedValues[path[path.length - 1].level_id] ?? null;
|
|
11436
11483
|
}
|
|
11437
|
-
/**
|
|
11438
|
-
|
|
11439
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
11484
|
-
|
|
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
|
-
/**
|
|
11491
|
-
function
|
|
11492
|
-
|
|
11493
|
-
|
|
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
|
|
11496
|
-
|
|
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
|
-
|
|
11707
|
-
|
|
11708
|
-
|
|
11709
|
-
|
|
11710
|
-
|
|
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
|
|
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(
|
|
11858
|
-
persistDeepestValue(nextSelectedValues, levels,
|
|
11859
|
-
if (!nextValue
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
11889
|
-
return
|
|
11890
|
-
}, [
|
|
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
|
-
|
|
11905
|
-
|
|
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
|
|
11917
|
-
return option
|
|
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
|
|
11925
|
-
return
|
|
11926
|
-
|
|
11927
|
-
|
|
11928
|
-
|
|
11929
|
-
|
|
11930
|
-
|
|
11931
|
-
|
|
11932
|
-
|
|
11933
|
-
|
|
11934
|
-
|
|
11935
|
-
|
|
11936
|
-
|
|
11937
|
-
|
|
11938
|
-
|
|
11939
|
-
|
|
11940
|
-
|
|
11941
|
-
|
|
11942
|
-
|
|
11943
|
-
|
|
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,
|
|
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
|
|
11954
|
-
|
|
11955
|
-
|
|
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
|
-
|
|
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:
|
|
11989
|
-
|
|
11990
|
-
|
|
11991
|
-
|
|
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
|
-
|
|
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) => {
|