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