@openg2p/registry-widgets 1.1.0-dev.9 → 1.1.1
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/SectionBuilder/schemas.d.ts +48 -0
- package/dist/components/SectionBuilder/schemas.d.ts.map +1 -1
- package/dist/components/SectionRenderer.d.ts +7 -1
- package/dist/components/SectionRenderer.d.ts.map +1 -1
- package/dist/components/SectionsContainer.d.ts.map +1 -1
- package/dist/hooks/useBaseWidget.d.ts.map +1 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.esm.js +438 -156
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +437 -155
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/dataSource.d.ts.map +1 -1
- package/dist/utils/dateInput.d.ts +17 -1
- package/dist/utils/dateInput.d.ts.map +1 -1
- package/dist/utils/sectionValidate.d.ts.map +1 -1
- package/dist/widgets/BooleanWidget.d.ts.map +1 -1
- package/dist/widgets/DateInputWidget.d.ts +5 -0
- package/dist/widgets/DateInputWidget.d.ts.map +1 -1
- package/dist/widgets/DialogTableWidget.d.ts.map +1 -1
- package/dist/widgets/HeaderSectionWidget.d.ts.map +1 -1
- package/dist/widgets/TableWidget.d.ts.map +1 -1
- package/dist/widgets/TextAreaWidget.d.ts.map +1 -1
- package/package.json +6 -1
- package/LICENSE +0 -373
- package/README.md +0 -433
- package/dist/events/types.d.ts +0 -27
- package/dist/events/types.d.ts.map +0 -1
- package/dist/index.css +0 -4
- package/dist/index.css.map +0 -1
package/dist/index.js
CHANGED
|
@@ -808,6 +808,40 @@ const parseDateFromFormat = (dateString, format) => {
|
|
|
808
808
|
}
|
|
809
809
|
return parseDate(dateString);
|
|
810
810
|
};
|
|
811
|
+
/**
|
|
812
|
+
* Resolve a stored date value (ISO or parseable string) to YYYY-MM-DD for comparisons.
|
|
813
|
+
*/
|
|
814
|
+
const resolveDateBoundFromFieldValue = (fieldValue) => {
|
|
815
|
+
if (fieldValue == null || fieldValue === '') {
|
|
816
|
+
return undefined;
|
|
817
|
+
}
|
|
818
|
+
const iso = formatDateToISO(fieldValue);
|
|
819
|
+
return iso || undefined;
|
|
820
|
+
};
|
|
821
|
+
/**
|
|
822
|
+
* Pick the stricter (later) minimum when combining static and field-based bounds.
|
|
823
|
+
*/
|
|
824
|
+
const mergeMinDateBounds = (boundA, boundB) => {
|
|
825
|
+
if (!boundA) {
|
|
826
|
+
return boundB;
|
|
827
|
+
}
|
|
828
|
+
if (!boundB) {
|
|
829
|
+
return boundA;
|
|
830
|
+
}
|
|
831
|
+
return boundA > boundB ? boundA : boundB;
|
|
832
|
+
};
|
|
833
|
+
/**
|
|
834
|
+
* Pick the stricter (earlier) maximum when combining static and field-based bounds.
|
|
835
|
+
*/
|
|
836
|
+
const mergeMaxDateBounds = (boundA, boundB) => {
|
|
837
|
+
if (!boundA) {
|
|
838
|
+
return boundB;
|
|
839
|
+
}
|
|
840
|
+
if (!boundB) {
|
|
841
|
+
return boundA;
|
|
842
|
+
}
|
|
843
|
+
return boundA < boundB ? boundA : boundB;
|
|
844
|
+
};
|
|
811
845
|
/**
|
|
812
846
|
* Get min date based on constraint type
|
|
813
847
|
*/
|
|
@@ -850,10 +884,7 @@ const getMaxDate = (constraint, maxDate) => {
|
|
|
850
884
|
}
|
|
851
885
|
return undefined;
|
|
852
886
|
};
|
|
853
|
-
|
|
854
|
-
* Validate date constraints
|
|
855
|
-
*/
|
|
856
|
-
const validateDateConstraints = (date, minDate, maxDate, constraint) => {
|
|
887
|
+
const validateDateConstraints = (date, minDate, maxDate, constraint, messages) => {
|
|
857
888
|
if (!date)
|
|
858
889
|
return null;
|
|
859
890
|
const dateObj = date instanceof Date ? date : parseDate(date);
|
|
@@ -872,15 +903,12 @@ const validateDateConstraints = (date, minDate, maxDate, constraint) => {
|
|
|
872
903
|
return 'Date must be in the future';
|
|
873
904
|
}
|
|
874
905
|
}
|
|
875
|
-
//
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
return `Date must be on or after ${effectiveMinDate}`;
|
|
906
|
+
// minDate / maxDate are effective bounds (static + field-based), resolved by the caller
|
|
907
|
+
if (minDate && dateISO < minDate) {
|
|
908
|
+
return messages?.minDateMessage ?? `Date must be on or after ${minDate}`;
|
|
879
909
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
if (effectiveMaxDate && dateISO > effectiveMaxDate) {
|
|
883
|
-
return `Date must be on or before ${effectiveMaxDate}`;
|
|
910
|
+
if (maxDate && dateISO > maxDate) {
|
|
911
|
+
return messages?.maxDateMessage ?? `Date must be on or before ${maxDate}`;
|
|
884
912
|
}
|
|
885
913
|
return null;
|
|
886
914
|
};
|
|
@@ -1078,16 +1106,12 @@ const getApiDataSource = async (dataSource, allValues, dataSourceRequestHandler,
|
|
|
1078
1106
|
console.error('[getApiDataSource] API data source missing endpoint. Use "endpoint" field to specify the operation (e.g., "get_g2p_geo_level_values")');
|
|
1079
1107
|
return [];
|
|
1080
1108
|
}
|
|
1081
|
-
let
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
}
|
|
1087
|
-
catch (error) {
|
|
1088
|
-
console.error('[getApiDataSource] Handler error:', error);
|
|
1089
|
-
throw error;
|
|
1090
|
-
}
|
|
1109
|
+
// Call handler — let any throw propagate to the outer catch so it is logged once
|
|
1110
|
+
// by useBaseWidget rather than double-logged here (which can cascade when
|
|
1111
|
+
// intercept-console-error.js converts console.error calls into thrown errors).
|
|
1112
|
+
const response = await dataSourceRequestHandler(service, endpoint, method, requestParams, {
|
|
1113
|
+
headers: dataSource.headers,
|
|
1114
|
+
});
|
|
1091
1115
|
// Handle OpenG2P response format (response_body.response_payload)
|
|
1092
1116
|
if (response && typeof response === 'object') {
|
|
1093
1117
|
if (response.response_body?.response_payload && Array.isArray(response.response_body.response_payload)) {
|
|
@@ -1110,8 +1134,8 @@ const getApiDataSource = async (dataSource, allValues, dataSourceRequestHandler,
|
|
|
1110
1134
|
return [];
|
|
1111
1135
|
}
|
|
1112
1136
|
catch (error) {
|
|
1113
|
-
|
|
1114
|
-
|
|
1137
|
+
// Rethrow so useBaseWidget's catch can log it with full widget context
|
|
1138
|
+
throw error;
|
|
1115
1139
|
}
|
|
1116
1140
|
};
|
|
1117
1141
|
/**
|
|
@@ -2305,7 +2329,7 @@ const useBaseWidget = (options) => {
|
|
|
2305
2329
|
dispatch(setDataSource({ widgetId, data: transformed }));
|
|
2306
2330
|
}
|
|
2307
2331
|
catch (error) {
|
|
2308
|
-
console.error(`[useBaseWidget] ERROR loading data source for ${widgetId}:`, error);
|
|
2332
|
+
console.error(`[useBaseWidget] ERROR loading data source for widget "${widgetId}" (type="${dataSource.type}"):`, error, '\nWidget config:', config, '\ndataSourceRequestHandler provided:', Boolean(dataSourceRequestHandler));
|
|
2309
2333
|
dispatch(setDataSource({ widgetId, data: [] }));
|
|
2310
2334
|
}
|
|
2311
2335
|
finally {
|
|
@@ -3798,6 +3822,116 @@ const namespaceSectionConfig = (section, namespace) => {
|
|
|
3798
3822
|
return namespaced;
|
|
3799
3823
|
};
|
|
3800
3824
|
|
|
3825
|
+
/** Table-style widgets that bind to an array path in the store / schema. */
|
|
3826
|
+
function isTableLikeWidget(widget) {
|
|
3827
|
+
const w = widget.widget;
|
|
3828
|
+
/** widget-type union in types omits legacy values like simple-table still used at runtime */
|
|
3829
|
+
const t = widget['widget-type'];
|
|
3830
|
+
return (w === 'table' ||
|
|
3831
|
+
w === 'dialog-table' ||
|
|
3832
|
+
w === 'simple-table' ||
|
|
3833
|
+
t === 'table' ||
|
|
3834
|
+
t === 'simple-table');
|
|
3835
|
+
}
|
|
3836
|
+
/**
|
|
3837
|
+
* Resolve `records` for section save payloads.
|
|
3838
|
+
* - Back-compat: path ending in `.records` (e.g. `regId.records`)
|
|
3839
|
+
* - Else: first array snapshot at a string `widget-data-path` on a table-like widget
|
|
3840
|
+
* (e.g. `household.members` for dialog-table)
|
|
3841
|
+
*/
|
|
3842
|
+
function extractTableRecordsFromSnapshot(snapshot, sectionWidgets) {
|
|
3843
|
+
const convention = Object.entries(snapshot).find(([key, value]) => key.endsWith('.records') && Array.isArray(value));
|
|
3844
|
+
if (convention) {
|
|
3845
|
+
return convention[1];
|
|
3846
|
+
}
|
|
3847
|
+
const tablePaths = [];
|
|
3848
|
+
sectionWidgets.forEach((widget) => {
|
|
3849
|
+
if (!isTableLikeWidget(widget))
|
|
3850
|
+
return;
|
|
3851
|
+
const p = widget['widget-data-path'];
|
|
3852
|
+
if (typeof p === 'string' && p.length > 0) {
|
|
3853
|
+
tablePaths.push(p);
|
|
3854
|
+
}
|
|
3855
|
+
else if (p && typeof p === 'object') {
|
|
3856
|
+
Object.values(p).forEach((sub) => {
|
|
3857
|
+
if (typeof sub === 'string' && sub.length > 0)
|
|
3858
|
+
tablePaths.push(sub);
|
|
3859
|
+
});
|
|
3860
|
+
}
|
|
3861
|
+
});
|
|
3862
|
+
for (const path of tablePaths) {
|
|
3863
|
+
const val = snapshot[path];
|
|
3864
|
+
if (Array.isArray(val)) {
|
|
3865
|
+
return val;
|
|
3866
|
+
}
|
|
3867
|
+
}
|
|
3868
|
+
return [];
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3871
|
+
const isColumnRequired = (column, skipRequired) => {
|
|
3872
|
+
if (skipRequired)
|
|
3873
|
+
return false;
|
|
3874
|
+
const validation = column['widget-data-validation'];
|
|
3875
|
+
return !!(column['widget-required'] || validation?.required);
|
|
3876
|
+
};
|
|
3877
|
+
const validateTableLikeWidget = (widget, currentSchemaData, dispatch, skipRequired) => {
|
|
3878
|
+
const widgetId = widget['widget-id'];
|
|
3879
|
+
if (!widgetId)
|
|
3880
|
+
return true;
|
|
3881
|
+
const columns = (widget['widget-data-columns'] || []);
|
|
3882
|
+
const value = getWidgetValue(currentSchemaData, widget['widget-data-path'], widgetId);
|
|
3883
|
+
const rows = Array.isArray(value)
|
|
3884
|
+
? value
|
|
3885
|
+
: [];
|
|
3886
|
+
const activeRows = rows.filter((row) => row?.edit_action !== 'DELETE');
|
|
3887
|
+
const rowErrors = [];
|
|
3888
|
+
let isValid = true;
|
|
3889
|
+
if (!skipRequired && widget['widget-required'] && activeRows.length === 0) {
|
|
3890
|
+
dispatch(setTouched({ widgetId, touched: true }));
|
|
3891
|
+
dispatch(setError({ widgetId, errors: ['At least one record is required'] }));
|
|
3892
|
+
return false;
|
|
3893
|
+
}
|
|
3894
|
+
const hasRequiredColumns = columns.some((col) => isColumnRequired(col, skipRequired));
|
|
3895
|
+
if (!skipRequired && hasRequiredColumns && activeRows.length === 0) {
|
|
3896
|
+
dispatch(setTouched({ widgetId, touched: true }));
|
|
3897
|
+
dispatch(setError({
|
|
3898
|
+
widgetId,
|
|
3899
|
+
errors: ['Add at least one record and fill all required fields'],
|
|
3900
|
+
}));
|
|
3901
|
+
return false;
|
|
3902
|
+
}
|
|
3903
|
+
activeRows.forEach((row, rowIndex) => {
|
|
3904
|
+
columns.forEach((col) => {
|
|
3905
|
+
if (col['widget-readonly'])
|
|
3906
|
+
return;
|
|
3907
|
+
const key = col['column-key'];
|
|
3908
|
+
if (!key)
|
|
3909
|
+
return;
|
|
3910
|
+
const required = isColumnRequired(col, skipRequired);
|
|
3911
|
+
const cellValue = row[key];
|
|
3912
|
+
const errors = validateWidget(cellValue, col['widget-data-validation'], required, skipRequired);
|
|
3913
|
+
if (errors.length > 0) {
|
|
3914
|
+
isValid = false;
|
|
3915
|
+
const label = col['widget-label'] || key;
|
|
3916
|
+
rowErrors.push(`Row ${rowIndex + 1}, ${label}: ${errors[0]}`);
|
|
3917
|
+
}
|
|
3918
|
+
});
|
|
3919
|
+
});
|
|
3920
|
+
if (!isValid) {
|
|
3921
|
+
dispatch(setTouched({ widgetId, touched: true }));
|
|
3922
|
+
dispatch(setError({
|
|
3923
|
+
widgetId,
|
|
3924
|
+
errors: rowErrors.length > 0
|
|
3925
|
+
? rowErrors.slice(0, 5)
|
|
3926
|
+
: ['Please fix required fields in the table'],
|
|
3927
|
+
}));
|
|
3928
|
+
}
|
|
3929
|
+
else {
|
|
3930
|
+
dispatch(setTouched({ widgetId, touched: false }));
|
|
3931
|
+
dispatch(setError({ widgetId, errors: [] }));
|
|
3932
|
+
}
|
|
3933
|
+
return isValid;
|
|
3934
|
+
};
|
|
3801
3935
|
const collectWidgets = (panels) => {
|
|
3802
3936
|
let widgets = [];
|
|
3803
3937
|
panels.forEach((panel) => {
|
|
@@ -3826,6 +3960,13 @@ const sectionValidate = (section, currentSchemaData, dispatch, skipRequired = fa
|
|
|
3826
3960
|
if (!isVisible)
|
|
3827
3961
|
continue;
|
|
3828
3962
|
const widgetId = widget['widget-id'];
|
|
3963
|
+
if (isTableLikeWidget(widget)) {
|
|
3964
|
+
const tableValid = validateTableLikeWidget(widget, currentSchemaData, dispatch, skipRequired);
|
|
3965
|
+
if (!tableValid) {
|
|
3966
|
+
isValid = false;
|
|
3967
|
+
}
|
|
3968
|
+
continue;
|
|
3969
|
+
}
|
|
3829
3970
|
const value = getWidgetValue(currentSchemaData, widget['widget-data-path'], widgetId);
|
|
3830
3971
|
const errors = validateWidget(value, widget['widget-data-validation'], widget['widget-required'], skipRequired);
|
|
3831
3972
|
if (errors.length > 0) {
|
|
@@ -3860,52 +4001,6 @@ const sectionValidate = (section, currentSchemaData, dispatch, skipRequired = fa
|
|
|
3860
4001
|
return isValid;
|
|
3861
4002
|
};
|
|
3862
4003
|
|
|
3863
|
-
/** Table-style widgets that bind to an array path in the store / schema. */
|
|
3864
|
-
function isTableLikeWidget(widget) {
|
|
3865
|
-
const w = widget.widget;
|
|
3866
|
-
/** widget-type union in types omits legacy values like simple-table still used at runtime */
|
|
3867
|
-
const t = widget['widget-type'];
|
|
3868
|
-
return (w === 'table' ||
|
|
3869
|
-
w === 'dialog-table' ||
|
|
3870
|
-
w === 'simple-table' ||
|
|
3871
|
-
t === 'table' ||
|
|
3872
|
-
t === 'simple-table');
|
|
3873
|
-
}
|
|
3874
|
-
/**
|
|
3875
|
-
* Resolve `records` for section save payloads.
|
|
3876
|
-
* - Back-compat: path ending in `.records` (e.g. `regId.records`)
|
|
3877
|
-
* - Else: first array snapshot at a string `widget-data-path` on a table-like widget
|
|
3878
|
-
* (e.g. `household.members` for dialog-table)
|
|
3879
|
-
*/
|
|
3880
|
-
function extractTableRecordsFromSnapshot(snapshot, sectionWidgets) {
|
|
3881
|
-
const convention = Object.entries(snapshot).find(([key, value]) => key.endsWith('.records') && Array.isArray(value));
|
|
3882
|
-
if (convention) {
|
|
3883
|
-
return convention[1];
|
|
3884
|
-
}
|
|
3885
|
-
const tablePaths = [];
|
|
3886
|
-
sectionWidgets.forEach((widget) => {
|
|
3887
|
-
if (!isTableLikeWidget(widget))
|
|
3888
|
-
return;
|
|
3889
|
-
const p = widget['widget-data-path'];
|
|
3890
|
-
if (typeof p === 'string' && p.length > 0) {
|
|
3891
|
-
tablePaths.push(p);
|
|
3892
|
-
}
|
|
3893
|
-
else if (p && typeof p === 'object') {
|
|
3894
|
-
Object.values(p).forEach((sub) => {
|
|
3895
|
-
if (typeof sub === 'string' && sub.length > 0)
|
|
3896
|
-
tablePaths.push(sub);
|
|
3897
|
-
});
|
|
3898
|
-
}
|
|
3899
|
-
});
|
|
3900
|
-
for (const path of tablePaths) {
|
|
3901
|
-
const val = snapshot[path];
|
|
3902
|
-
if (Array.isArray(val)) {
|
|
3903
|
-
return val;
|
|
3904
|
-
}
|
|
3905
|
-
}
|
|
3906
|
-
return [];
|
|
3907
|
-
}
|
|
3908
|
-
|
|
3909
4004
|
/** Root class on readonly label/value rows; SectionRenderer scopes overflow/ellipsis rules here. */
|
|
3910
4005
|
const READONLY_VALUE_ROW_ROOT_CLASSES = [
|
|
3911
4006
|
'TextDisplayWidget',
|
|
@@ -3947,7 +4042,7 @@ function scopedClassSelectors(sectionClassId, classNames) {
|
|
|
3947
4042
|
* - Panels wrap when they exceed available width
|
|
3948
4043
|
* - Sections can sit side-by-side if there's space
|
|
3949
4044
|
*/
|
|
3950
|
-
const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequestHandler, schemaData, onValueChange, gridColumnSpan, onSectionSave, hideEditButton = false, mode = 'RegistryView', namespace, changeRequestType, showChangeRequestLabel = true, dbSectionId, sectionRegisterId, onSectionDirtyChange, sectionIndex, sectionCount, expandedSectionIndex, onExpandSection, onSectionSaveSuccess, onPreviousSection, isDraft, onEditModeChange, forceExitEdit, }) => {
|
|
4045
|
+
const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequestHandler, schemaData, onValueChange, gridColumnSpan, onSectionSave, hideEditButton = false, mode = 'RegistryView', namespace, changeRequestType, showChangeRequestLabel = true, dbSectionId, sectionRegisterId, onSectionDirtyChange, sectionIndex, sectionCount, expandedSectionIndex, onExpandSection, onSectionSaveSuccess, onPreviousSection, isDraft, isAccessible = false, onEditModeChange, forceExitEdit, }) => {
|
|
3951
4046
|
const { translateConfig, translate } = useWidgetTranslation();
|
|
3952
4047
|
const resolvedTheme = useWidgetTheme();
|
|
3953
4048
|
const portalCSSVariables = React.useMemo(() => themeToCSSVariables(resolvedTheme), [resolvedTheme]);
|
|
@@ -4013,16 +4108,25 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4013
4108
|
const isExpandedFromContainer = typeof sectionIndex === 'number' && expandedSectionIndex === sectionIndex;
|
|
4014
4109
|
const isExpandedStandalone = sectionIndex === undefined && standaloneExpanded;
|
|
4015
4110
|
const isExpanded = mode === 'IntakeForm' && (isExpandedFromContainer || isExpandedStandalone);
|
|
4111
|
+
// IntakeForm only: tracks whether the user has clicked Next on this section at least once.
|
|
4112
|
+
// Used to unlock the accordion header so the user can navigate back to a visited section.
|
|
4113
|
+
const [hasBeenSavedByUser, setHasBeenSavedByUser] = React.useState(false);
|
|
4114
|
+
// Accordion header click behaviour in IntakeForm mode:
|
|
4115
|
+
// - Standalone (no sectionIndex): always toggleable.
|
|
4116
|
+
// - Managed by SectionsContainer: toggleable only when isAccessible is true
|
|
4117
|
+
// (i.e. the section has been visited OR is the immediate next one).
|
|
4118
|
+
// Sections beyond that remain locked.
|
|
4016
4119
|
const handleAccordionToggle = React.useCallback(() => {
|
|
4017
4120
|
if (mode !== 'IntakeForm')
|
|
4018
4121
|
return;
|
|
4019
|
-
if (
|
|
4020
|
-
onExpandSection(sectionIndex);
|
|
4021
|
-
}
|
|
4022
|
-
else if (sectionIndex === undefined) {
|
|
4122
|
+
if (sectionIndex === undefined) {
|
|
4023
4123
|
setStandaloneExpanded(prev => !prev);
|
|
4024
4124
|
}
|
|
4025
|
-
|
|
4125
|
+
else if (isAccessible && onExpandSection) {
|
|
4126
|
+
onExpandSection(sectionIndex);
|
|
4127
|
+
}
|
|
4128
|
+
// Intentionally no-op for locked sections (isAccessible === false)
|
|
4129
|
+
}, [mode, sectionIndex, isAccessible, onExpandSection]);
|
|
4026
4130
|
// Recursively count all vertical panels, especially those nested inside horizontal panels
|
|
4027
4131
|
// Typically: horizontal panels at first level contain vertical panels at second level
|
|
4028
4132
|
// Accounts for panel-column-span: a panel with column-span 3 counts as 3 columns
|
|
@@ -4357,8 +4461,6 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4357
4461
|
const baselineSnapshotRef = React.useRef(null);
|
|
4358
4462
|
// IntakeForm only: increment when baseline is updated after save - forces badge to update (refs don't trigger re-renders)
|
|
4359
4463
|
const [intakeFormBaselineTrigger, setIntakeFormBaselineTrigger] = React.useState(0);
|
|
4360
|
-
// IntakeForm only: tracks whether the user has actually saved this section (prevents "Saved" badge on initial load)
|
|
4361
|
-
const [hasBeenSavedByUser, setHasBeenSavedByUser] = React.useState(false);
|
|
4362
4464
|
// IntakeForm: treat as edit mode for dirty tracking when isDraft. RegistryView: use isEditMode.
|
|
4363
4465
|
const effectiveEditModeForDirty = mode === 'IntakeForm' ? (isDraft !== false) : isEditMode;
|
|
4364
4466
|
// Compute isDirty: compare current store state to baseline (only when in edit mode)
|
|
@@ -4591,6 +4693,11 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4591
4693
|
}
|
|
4592
4694
|
onSectionDirtyChange?.(sectionId, false);
|
|
4593
4695
|
}
|
|
4696
|
+
else if (mode === 'IntakeForm') {
|
|
4697
|
+
// No onSectionSave provided, but still mark section as visited so the
|
|
4698
|
+
// user can navigate back to it by clicking the accordion header.
|
|
4699
|
+
setHasBeenSavedByUser(true);
|
|
4700
|
+
}
|
|
4594
4701
|
// Always navigate to the next section
|
|
4595
4702
|
onSectionSaveSuccess?.(sectionIndex);
|
|
4596
4703
|
}, [store, onSectionSave, onSectionSaveSuccess, sectionIndex, originalSection, schemaData, contextSchemaData, namespace, hasSupportingDocuments, dbSectionId, sectionRegisterId, dispatch, buildSectionSnapshot, sectionId, onSectionDirtyChange, mode, isDraft]);
|
|
@@ -4809,13 +4916,17 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4809
4916
|
.${sectionClassId}.intake-form-accordion-item .intake-form-accordion-header h2 {
|
|
4810
4917
|
color: var(--owt-color-primary-dark, #F07B1A);
|
|
4811
4918
|
}
|
|
4812
|
-
|
|
4919
|
+
/* Hover / focus only shown when the header is actually interactive (standalone mode) */
|
|
4920
|
+
.${sectionClassId}.intake-form-accordion-item .intake-form-accordion-header[data-interactive="true"]:hover {
|
|
4813
4921
|
opacity: 0.85;
|
|
4814
4922
|
}
|
|
4815
|
-
.${sectionClassId}.intake-form-accordion-item .intake-form-accordion-header:focus-visible {
|
|
4923
|
+
.${sectionClassId}.intake-form-accordion-item .intake-form-accordion-header[data-interactive="true"]:focus-visible {
|
|
4816
4924
|
outline: 2px solid var(--owt-color-primary, #F5BB1A);
|
|
4817
4925
|
outline-offset: 2px;
|
|
4818
4926
|
}
|
|
4927
|
+
.${sectionClassId}.intake-form-accordion-item .intake-form-accordion-header[data-interactive="false"]:focus-visible {
|
|
4928
|
+
outline: none;
|
|
4929
|
+
}
|
|
4819
4930
|
.${sectionClassId}.intake-form-accordion-item .intake-form-accordion-content {
|
|
4820
4931
|
padding-top: 8px;
|
|
4821
4932
|
padding-bottom: 0px;
|
|
@@ -4858,7 +4969,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4858
4969
|
}),
|
|
4859
4970
|
}, children: mode === 'IntakeForm' ? (
|
|
4860
4971
|
/* IntakeForm: accordion layout - header always visible, content only when expanded */
|
|
4861
|
-
jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsxs("button", { type: "button", id: `intake-form-accordion-header-${sectionId}`, className: "intake-form-accordion-header", onClick: handleAccordionToggle, "aria-expanded": isExpanded, "aria-controls": isExpanded ? `intake-form-accordion-content-${sectionId}` : undefined, style: {
|
|
4972
|
+
jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsxs("button", { type: "button", id: `intake-form-accordion-header-${sectionId}`, className: "intake-form-accordion-header", onClick: handleAccordionToggle, "aria-expanded": isExpanded, "aria-controls": isExpanded ? `intake-form-accordion-content-${sectionId}` : undefined, "data-interactive": sectionIndex === undefined || isAccessible ? 'true' : 'false', style: {
|
|
4862
4973
|
width: '100%',
|
|
4863
4974
|
display: 'flex',
|
|
4864
4975
|
alignItems: 'flex-start',
|
|
@@ -4868,7 +4979,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4868
4979
|
marginBottom: 0,
|
|
4869
4980
|
background: 'none',
|
|
4870
4981
|
border: 'none',
|
|
4871
|
-
cursor: 'pointer',
|
|
4982
|
+
cursor: sectionIndex === undefined || isAccessible ? 'pointer' : 'default',
|
|
4872
4983
|
textAlign: 'left',
|
|
4873
4984
|
fontFamily: 'Roboto, sans-serif',
|
|
4874
4985
|
}, children: [jsxRuntimeExports.jsxs("div", { style: { flex: 1, display: 'flex', alignItems: 'center', gap: '12px', minWidth: 0 }, children: [jsxRuntimeExports.jsx("h2", { className: "text-xl font-semibold", style: { margin: 0 }, children: sectionToRender['section-title']
|
|
@@ -5166,6 +5277,11 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
|
|
|
5166
5277
|
const dataSourceRequestHandler = propDataSourceRequestHandler || contextDataSourceRequestHandler;
|
|
5167
5278
|
// IntakeForm mode: accordion state - which section is expanded (null = none; first expanded by default)
|
|
5168
5279
|
const [expandedSectionIndex, setExpandedSectionIndex] = React.useState(0);
|
|
5280
|
+
// IntakeForm mode: high-water mark of the furthest section the user has clicked Next on.
|
|
5281
|
+
// A section at index i is accessible when i <= maxVisitedIndex + 1
|
|
5282
|
+
// (i.e. every visited section plus the one immediately after it).
|
|
5283
|
+
// Starts at -1 so only section 0 is accessible before any Next is clicked.
|
|
5284
|
+
const [maxVisitedIndex, setMaxVisitedIndex] = React.useState(-1);
|
|
5169
5285
|
// RegistryView: track which section is currently in edit mode (by section-id); null = none
|
|
5170
5286
|
const [editingSectionId, setEditingSectionId] = React.useState(null);
|
|
5171
5287
|
const handleEditModeChange = React.useCallback((sectionId, editing) => {
|
|
@@ -5190,8 +5306,9 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
|
|
|
5190
5306
|
const handleExpandSection = React.useCallback((index) => {
|
|
5191
5307
|
setExpandedSectionIndex(prev => (prev === index ? null : index));
|
|
5192
5308
|
}, []);
|
|
5193
|
-
// IntakeForm mode: called after section save - collapse current, expand next
|
|
5309
|
+
// IntakeForm mode: called after section save - advance high-water mark, collapse current, expand next
|
|
5194
5310
|
const handleSectionSaveSuccess = React.useCallback((index) => {
|
|
5311
|
+
setMaxVisitedIndex(prev => Math.max(prev, index));
|
|
5195
5312
|
if (index + 1 < safeSections.length) {
|
|
5196
5313
|
setExpandedSectionIndex(index + 1);
|
|
5197
5314
|
}
|
|
@@ -5385,6 +5502,8 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
|
|
|
5385
5502
|
onSectionSaveSuccess: handleSectionSaveSuccess,
|
|
5386
5503
|
onPreviousSection: handlePreviousSection,
|
|
5387
5504
|
isDraft,
|
|
5505
|
+
// Accessible = every visited section + the one immediately after
|
|
5506
|
+
isAccessible: index <= maxVisitedIndex + 1,
|
|
5388
5507
|
}
|
|
5389
5508
|
: {};
|
|
5390
5509
|
// RegistryView: single-edit coordination props
|
|
@@ -7367,7 +7486,7 @@ const NumberInputWidget = ({ config }) => {
|
|
|
7367
7486
|
if (parsed === null) {
|
|
7368
7487
|
// Allow empty input or partial input (e.g., "-", ".")
|
|
7369
7488
|
if (inputValue === '' || inputValue === '-' || inputValue === '.') {
|
|
7370
|
-
onChange(
|
|
7489
|
+
onChange(null);
|
|
7371
7490
|
}
|
|
7372
7491
|
// Don't update if invalid - let user continue typing
|
|
7373
7492
|
return;
|
|
@@ -7491,6 +7610,8 @@ const BooleanWidget = ({ config }) => {
|
|
|
7491
7610
|
return labels[representation];
|
|
7492
7611
|
}, [representation, formatConfig, translateConfig]);
|
|
7493
7612
|
const { trueLabel, falseLabel } = getLabels();
|
|
7613
|
+
const unsetLabel = React.useMemo(() => translateConfig(formatConfig?.booleanUnsetLabel || 'Not set'), [formatConfig?.booleanUnsetLabel, translateConfig]);
|
|
7614
|
+
const radioGroupName = `${widgetConfig['widget-id'] ?? 'boolean'}__${React.useId().replace(/:/g, '')}`;
|
|
7494
7615
|
// Determine current value (handle null/undefined)
|
|
7495
7616
|
const currentValue = React.useMemo(() => {
|
|
7496
7617
|
if (value === null || value === undefined) {
|
|
@@ -7522,7 +7643,7 @@ const BooleanWidget = ({ config }) => {
|
|
|
7522
7643
|
const label = translateConfig(widgetConfig['widget-label']);
|
|
7523
7644
|
let displayValue = '';
|
|
7524
7645
|
if (currentValue === null) {
|
|
7525
|
-
displayValue = '
|
|
7646
|
+
displayValue = '';
|
|
7526
7647
|
}
|
|
7527
7648
|
else if (currentValue === true) {
|
|
7528
7649
|
displayValue = trueLabel;
|
|
@@ -7534,18 +7655,20 @@ const BooleanWidget = ({ config }) => {
|
|
|
7534
7655
|
}
|
|
7535
7656
|
// Render based on control type
|
|
7536
7657
|
if (controlType === 'checkbox') {
|
|
7537
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-
|
|
7658
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium leading-normal text-gray-700 md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("label", { className: "inline-flex items-baseline cursor-pointer gap-2", children: [jsxRuntimeExports.jsx("input", { type: "checkbox", checked: currentValue === true, onChange: handleCheckboxChange, onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), (currentValue === true || currentValue === false) && (jsxRuntimeExports.jsx("span", { className: "text-base text-gray-700 leading-normal", children: currentValue === true ? trueLabel : falseLabel }))] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
7538
7659
|
}
|
|
7539
7660
|
if (controlType === 'radio') {
|
|
7540
7661
|
const containerClass = orientation === 'horizontal'
|
|
7541
|
-
? 'flex flex-row
|
|
7542
|
-
: 'flex flex-col
|
|
7543
|
-
|
|
7662
|
+
? 'flex flex-row flex-wrap items-baseline gap-x-4 gap-y-2'
|
|
7663
|
+
: 'flex flex-col items-start gap-2';
|
|
7664
|
+
const radioDisabled = !isEnabled || widgetConfig['widget-readonly'];
|
|
7665
|
+
const optionDisabledClass = radioDisabled ? 'opacity-50 cursor-not-allowed' : '';
|
|
7666
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium leading-normal text-gray-700 md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: containerClass, onBlur: onBlur, children: [allowUnset && (jsxRuntimeExports.jsxs("label", { className: `inline-flex items-baseline gap-2 cursor-pointer ${optionDisabledClass}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: radioGroupName, checked: currentValue === null, onChange: () => handleRadioChange(null), disabled: radioDisabled, className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-base text-gray-700 leading-normal", children: unsetLabel })] })), jsxRuntimeExports.jsxs("label", { className: `inline-flex items-baseline gap-2 cursor-pointer ${optionDisabledClass}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: radioGroupName, checked: currentValue === true, onChange: () => handleRadioChange(true), disabled: radioDisabled, className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-base text-gray-700 leading-normal", children: trueLabel })] }), jsxRuntimeExports.jsxs("label", { className: `inline-flex items-baseline gap-2 cursor-pointer ${optionDisabledClass}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: radioGroupName, checked: currentValue === false, onChange: () => handleRadioChange(false), disabled: radioDisabled, className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-base text-gray-700 leading-normal", children: falseLabel })] })] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
7544
7667
|
}
|
|
7545
7668
|
// Toggle/switch control type
|
|
7546
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-
|
|
7669
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium leading-normal text-gray-700 sm:min-w-[150px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-3", onBlur: onBlur, children: [allowUnset && (jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleChange(null), disabled: !isEnabled || widgetConfig['widget-readonly'], className: `px-3 py-1 text-sm border ${currentValue === null
|
|
7547
7670
|
? 'bg-blue-600 text-white border-blue-600'
|
|
7548
|
-
: 'bg-white text-gray-700 border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-50'}`, style: { borderRadius: '15px' }, children:
|
|
7671
|
+
: 'bg-white text-gray-700 border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-50'}`, style: { borderRadius: '15px' }, children: unsetLabel })), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleChange(true), disabled: !isEnabled || widgetConfig['widget-readonly'], className: `px-3 py-1 text-sm border ${currentValue === true
|
|
7549
7672
|
? 'bg-blue-600 text-white border-blue-600'
|
|
7550
7673
|
: 'bg-white text-gray-700 border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-50'}`, style: { borderRadius: '15px' }, children: trueLabel }), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleChange(false), disabled: !isEnabled || widgetConfig['widget-readonly'], className: `px-3 py-1 text-sm border ${currentValue === false
|
|
7551
7674
|
? 'bg-blue-600 text-white border-blue-600'
|
|
@@ -7553,42 +7676,76 @@ const BooleanWidget = ({ config }) => {
|
|
|
7553
7676
|
};
|
|
7554
7677
|
|
|
7555
7678
|
const DateInputWidget = ({ config }) => {
|
|
7556
|
-
const { value,
|
|
7557
|
-
const
|
|
7679
|
+
const { value, error, touched, isEnabled, onChange, onBlur, setError, config: widgetConfig, } = useBaseWidget({ config });
|
|
7680
|
+
const formValues = reactRedux.useSelector((state) => state.widget.values);
|
|
7681
|
+
const { translateConfig } = useWidgetTranslation();
|
|
7558
7682
|
const formatConfig = widgetConfig['widget-data-format'];
|
|
7559
7683
|
const optionsConfig = widgetConfig['widget-data-options'];
|
|
7560
7684
|
const dateFormat = formatConfig?.dateFormat || 'YYYY-MM-DD';
|
|
7561
|
-
const inputMethod = formatConfig?.inputMethod || 'picker';
|
|
7685
|
+
const inputMethod = formatConfig?.inputMethod || 'picker';
|
|
7562
7686
|
const dateConstraint = formatConfig?.dateConstraint || 'any';
|
|
7563
7687
|
const minDate = optionsConfig?.minDate;
|
|
7564
7688
|
const maxDate = optionsConfig?.maxDate;
|
|
7689
|
+
const minDateField = optionsConfig?.minDateField;
|
|
7690
|
+
const maxDateField = optionsConfig?.maxDateField;
|
|
7691
|
+
const minDateMessage = optionsConfig?.minDateMessage
|
|
7692
|
+
? translateConfig(optionsConfig.minDateMessage)
|
|
7693
|
+
: undefined;
|
|
7694
|
+
const maxDateMessage = optionsConfig?.maxDateMessage
|
|
7695
|
+
? translateConfig(optionsConfig.maxDateMessage)
|
|
7696
|
+
: undefined;
|
|
7565
7697
|
const defaultToToday = widgetConfig['widget-data-default'] === 'today';
|
|
7566
|
-
// Track manual input value (for manual/hybrid modes)
|
|
7567
7698
|
const [manualInputValue, setManualInputValue] = React.useState('');
|
|
7568
7699
|
const [isFocused, setIsFocused] = React.useState(false);
|
|
7569
|
-
|
|
7700
|
+
const fieldMinDate = React.useMemo(() => {
|
|
7701
|
+
if (!minDateField) {
|
|
7702
|
+
return undefined;
|
|
7703
|
+
}
|
|
7704
|
+
return resolveDateBoundFromFieldValue(getValueByPath(formValues, minDateField));
|
|
7705
|
+
}, [formValues, minDateField]);
|
|
7706
|
+
const fieldMaxDate = React.useMemo(() => {
|
|
7707
|
+
if (!maxDateField) {
|
|
7708
|
+
return undefined;
|
|
7709
|
+
}
|
|
7710
|
+
return resolveDateBoundFromFieldValue(getValueByPath(formValues, maxDateField));
|
|
7711
|
+
}, [formValues, maxDateField]);
|
|
7712
|
+
const effectiveMinDate = React.useMemo(() => {
|
|
7713
|
+
const staticMin = getMinDate(dateConstraint, minDate);
|
|
7714
|
+
return mergeMinDateBounds(staticMin, fieldMinDate);
|
|
7715
|
+
}, [dateConstraint, minDate, fieldMinDate]);
|
|
7716
|
+
const effectiveMaxDate = React.useMemo(() => {
|
|
7717
|
+
const staticMax = getMaxDate(dateConstraint, maxDate);
|
|
7718
|
+
return mergeMaxDateBounds(staticMax, fieldMaxDate);
|
|
7719
|
+
}, [dateConstraint, maxDate, fieldMaxDate]);
|
|
7720
|
+
const constraintMessages = React.useMemo(() => ({ minDateMessage, maxDateMessage }), [minDateMessage, maxDateMessage]);
|
|
7721
|
+
const runDateConstraintValidation = React.useCallback((dateValue) => {
|
|
7722
|
+
if (!dateValue) {
|
|
7723
|
+
return null;
|
|
7724
|
+
}
|
|
7725
|
+
return validateDateConstraints(dateValue, effectiveMinDate, effectiveMaxDate, dateConstraint, constraintMessages);
|
|
7726
|
+
}, [effectiveMinDate, effectiveMaxDate, dateConstraint, constraintMessages]);
|
|
7570
7727
|
React.useEffect(() => {
|
|
7571
7728
|
if (defaultToToday && (value === null || value === undefined || value === '')) {
|
|
7572
7729
|
const todayISO = formatDateToISO(new Date());
|
|
7573
7730
|
onChange(todayISO);
|
|
7574
7731
|
}
|
|
7575
7732
|
}, [defaultToToday, value, onChange]);
|
|
7576
|
-
//
|
|
7577
|
-
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7733
|
+
// Re-validate when a relative bound field changes (e.g. start date set after end date)
|
|
7734
|
+
React.useEffect(() => {
|
|
7735
|
+
if (!value) {
|
|
7736
|
+
return;
|
|
7737
|
+
}
|
|
7738
|
+
const constraintError = runDateConstraintValidation(value);
|
|
7739
|
+
if (constraintError) {
|
|
7740
|
+
setError([constraintError]);
|
|
7741
|
+
}
|
|
7742
|
+
}, [fieldMinDate, fieldMaxDate, value, runDateConstraintValidation, setError]);
|
|
7584
7743
|
const getDisplayValue = React.useCallback(() => {
|
|
7585
|
-
// For picker mode, always use YYYY-MM-DD
|
|
7586
7744
|
if (inputMethod === 'picker') {
|
|
7587
7745
|
if (!value)
|
|
7588
7746
|
return '';
|
|
7589
7747
|
return formatDateToISO(value);
|
|
7590
7748
|
}
|
|
7591
|
-
// For manual/hybrid modes, use custom format
|
|
7592
7749
|
if (isFocused && manualInputValue) {
|
|
7593
7750
|
return manualInputValue;
|
|
7594
7751
|
}
|
|
@@ -7599,7 +7756,6 @@ const DateInputWidget = ({ config }) => {
|
|
|
7599
7756
|
}
|
|
7600
7757
|
return formatDateToString(value, dateFormat);
|
|
7601
7758
|
}, [value, inputMethod, dateFormat, isFocused, manualInputValue]);
|
|
7602
|
-
// Initialize manual input value
|
|
7603
7759
|
React.useEffect(() => {
|
|
7604
7760
|
if (!isFocused && value) {
|
|
7605
7761
|
if (dateFormat === 'YYYY-MM-DD') {
|
|
@@ -7610,66 +7766,81 @@ const DateInputWidget = ({ config }) => {
|
|
|
7610
7766
|
}
|
|
7611
7767
|
}
|
|
7612
7768
|
}, [value, dateFormat, isFocused]);
|
|
7613
|
-
|
|
7769
|
+
const applyConstraintError = React.useCallback((dateValue) => {
|
|
7770
|
+
const constraintError = runDateConstraintValidation(dateValue);
|
|
7771
|
+
setError(constraintError ? [constraintError] : []);
|
|
7772
|
+
}, [runDateConstraintValidation, setError]);
|
|
7614
7773
|
const handleChange = React.useCallback((e) => {
|
|
7615
7774
|
const inputValue = e.target.value;
|
|
7616
7775
|
if (inputMethod === 'picker') {
|
|
7617
|
-
// Picker mode: input is always YYYY-MM-DD
|
|
7618
7776
|
if (inputValue) {
|
|
7619
7777
|
const date = parseDate(inputValue);
|
|
7620
7778
|
if (date) {
|
|
7621
|
-
|
|
7779
|
+
const iso = formatDateToISO(date);
|
|
7780
|
+
onChange(iso);
|
|
7781
|
+
applyConstraintError(iso);
|
|
7622
7782
|
}
|
|
7623
7783
|
else {
|
|
7624
7784
|
onChange('');
|
|
7785
|
+
setError([]);
|
|
7625
7786
|
}
|
|
7626
7787
|
}
|
|
7627
7788
|
else {
|
|
7628
7789
|
onChange('');
|
|
7790
|
+
setError([]);
|
|
7629
7791
|
}
|
|
7630
7792
|
}
|
|
7631
7793
|
else {
|
|
7632
|
-
// Manual/hybrid mode: parse custom format
|
|
7633
7794
|
setManualInputValue(inputValue);
|
|
7634
7795
|
if (inputValue) {
|
|
7635
7796
|
const date = parseDateFromFormat(inputValue, dateFormat);
|
|
7636
7797
|
if (date) {
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
onChange(formatDateToISO(date));
|
|
7641
|
-
}
|
|
7642
|
-
else {
|
|
7643
|
-
// Still update the value but validation will catch it
|
|
7644
|
-
onChange(formatDateToISO(date));
|
|
7645
|
-
}
|
|
7798
|
+
const iso = formatDateToISO(date);
|
|
7799
|
+
onChange(iso);
|
|
7800
|
+
applyConstraintError(iso);
|
|
7646
7801
|
}
|
|
7647
7802
|
}
|
|
7648
7803
|
else {
|
|
7649
7804
|
onChange('');
|
|
7805
|
+
setError([]);
|
|
7650
7806
|
}
|
|
7651
7807
|
}
|
|
7652
|
-
}, [inputMethod, dateFormat, onChange,
|
|
7653
|
-
// Handle blur - validate and format
|
|
7808
|
+
}, [inputMethod, dateFormat, onChange, applyConstraintError, setError]);
|
|
7654
7809
|
const handleBlur = React.useCallback(() => {
|
|
7655
7810
|
setIsFocused(false);
|
|
7656
7811
|
if (inputMethod !== 'picker' && manualInputValue) {
|
|
7657
7812
|
const date = parseDateFromFormat(manualInputValue, dateFormat);
|
|
7658
7813
|
if (date) {
|
|
7659
|
-
// Format the value according to the format
|
|
7660
7814
|
const formatted = formatDateToString(date, dateFormat);
|
|
7661
7815
|
setManualInputValue(formatted);
|
|
7662
|
-
|
|
7816
|
+
const iso = formatDateToISO(date);
|
|
7817
|
+
onChange(iso);
|
|
7818
|
+
applyConstraintError(iso);
|
|
7663
7819
|
}
|
|
7664
7820
|
else {
|
|
7665
|
-
// Invalid date, clear it
|
|
7666
7821
|
setManualInputValue('');
|
|
7667
7822
|
onChange('');
|
|
7823
|
+
setError([]);
|
|
7668
7824
|
}
|
|
7669
7825
|
}
|
|
7670
7826
|
onBlur();
|
|
7671
|
-
|
|
7672
|
-
|
|
7827
|
+
if (value) {
|
|
7828
|
+
const constraintError = runDateConstraintValidation(value);
|
|
7829
|
+
if (constraintError) {
|
|
7830
|
+
setError([constraintError]);
|
|
7831
|
+
}
|
|
7832
|
+
}
|
|
7833
|
+
}, [
|
|
7834
|
+
inputMethod,
|
|
7835
|
+
manualInputValue,
|
|
7836
|
+
dateFormat,
|
|
7837
|
+
onChange,
|
|
7838
|
+
onBlur,
|
|
7839
|
+
applyConstraintError,
|
|
7840
|
+
value,
|
|
7841
|
+
runDateConstraintValidation,
|
|
7842
|
+
setError,
|
|
7843
|
+
]);
|
|
7673
7844
|
const handleFocus = React.useCallback(() => {
|
|
7674
7845
|
setIsFocused(true);
|
|
7675
7846
|
if (value) {
|
|
@@ -7681,15 +7852,15 @@ const DateInputWidget = ({ config }) => {
|
|
|
7681
7852
|
}
|
|
7682
7853
|
}
|
|
7683
7854
|
}, [value, dateFormat]);
|
|
7684
|
-
// Determine placeholder
|
|
7685
7855
|
const placeholder = React.useMemo(() => {
|
|
7686
|
-
const
|
|
7856
|
+
const display = getDisplayValue();
|
|
7857
|
+
const hasValue = display && display.trim().length > 0;
|
|
7687
7858
|
const placeholderText = translateConfig(widgetConfig['widget-data-placeholder']);
|
|
7688
|
-
return hasValue ? undefined :
|
|
7859
|
+
return hasValue ? undefined : placeholderText || dateFormat;
|
|
7689
7860
|
}, [getDisplayValue, widgetConfig, translateConfig, dateFormat]);
|
|
7690
|
-
// Determine input type
|
|
7691
7861
|
const inputType = inputMethod === 'picker' ? 'date' : 'text';
|
|
7692
|
-
|
|
7862
|
+
const showRequiredError = widgetConfig['widget-required'] && (!value || value === '');
|
|
7863
|
+
const showValidationError = touched && error.length > 0;
|
|
7693
7864
|
if (widgetConfig['widget-readonly']) {
|
|
7694
7865
|
const label = translateConfig(widgetConfig['widget-label']);
|
|
7695
7866
|
let displayValue = '';
|
|
@@ -7706,9 +7877,9 @@ const DateInputWidget = ({ config }) => {
|
|
|
7706
7877
|
}
|
|
7707
7878
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] DateDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
|
|
7708
7879
|
}
|
|
7709
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] &&
|
|
7880
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" })] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("input", { type: inputType, value: getDisplayValue(), onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, min: inputMethod === 'picker' ? effectiveMinDate : undefined, max: inputMethod === 'picker' ? effectiveMaxDate : undefined, className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${showValidationError || showRequiredError
|
|
7710
7881
|
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
|
|
7711
|
-
: 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }),
|
|
7882
|
+
: 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }), showValidationError && jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] })] })] }) }));
|
|
7712
7883
|
};
|
|
7713
7884
|
|
|
7714
7885
|
/**
|
|
@@ -8256,7 +8427,7 @@ const CheckboxWidget = ({ config }) => {
|
|
|
8256
8427
|
const displayValue = isChecked ? 'Yes' : 'No';
|
|
8257
8428
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] CheckboxDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
|
|
8258
8429
|
}
|
|
8259
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-
|
|
8430
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium leading-normal text-gray-700 md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("label", { className: "inline-flex cursor-pointer items-baseline gap-2", children: [jsxRuntimeExports.jsx("input", { type: "checkbox", checked: isChecked, onChange: (e) => onChange(e.target.checked), onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), jsxRuntimeExports.jsx("span", { className: "text-base leading-normal text-gray-700", children: isChecked ? 'Yes' : 'No' })] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
8260
8431
|
}
|
|
8261
8432
|
// Multiple checkboxes (with data source) - for array values
|
|
8262
8433
|
// Process and sort options if needed
|
|
@@ -8299,7 +8470,7 @@ const CheckboxWidget = ({ config }) => {
|
|
|
8299
8470
|
switch (layout) {
|
|
8300
8471
|
case 'horizontal':
|
|
8301
8472
|
return {
|
|
8302
|
-
className: 'flex flex-row flex-wrap gap-4',
|
|
8473
|
+
className: 'flex flex-row flex-wrap items-baseline gap-4',
|
|
8303
8474
|
style: undefined,
|
|
8304
8475
|
};
|
|
8305
8476
|
case 'grid':
|
|
@@ -8312,7 +8483,7 @@ const CheckboxWidget = ({ config }) => {
|
|
|
8312
8483
|
case 'vertical':
|
|
8313
8484
|
default:
|
|
8314
8485
|
return {
|
|
8315
|
-
className: 'flex flex-col
|
|
8486
|
+
className: 'flex flex-col gap-2',
|
|
8316
8487
|
style: undefined,
|
|
8317
8488
|
};
|
|
8318
8489
|
}
|
|
@@ -8326,7 +8497,7 @@ const CheckboxWidget = ({ config }) => {
|
|
|
8326
8497
|
: '-';
|
|
8327
8498
|
return (jsxRuntimeExports.jsxs("div", { className: "mb-3 CheckboxDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-sm text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
|
|
8328
8499
|
}
|
|
8329
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-
|
|
8500
|
+
return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium leading-normal text-gray-700 md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("div", { className: layoutConfig.className, style: layoutConfig.style, onBlur: onBlur, children: loading ? (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500", children: translate('common.loading') })) : (processedOptions.map((option) => (jsxRuntimeExports.jsxs("label", { className: `inline-flex cursor-pointer items-baseline gap-2 ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : ''}`, children: [jsxRuntimeExports.jsx("input", { type: "checkbox", value: option.value, checked: selectedValues.includes(option.value), onChange: (e) => handleCheckboxChange(option.value, e.target.checked), disabled: !isEnabled || widgetConfig['widget-readonly'], className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), jsxRuntimeExports.jsx("span", { className: "text-base leading-normal text-gray-700", children: translateConfig(option.label) })] }, option.value)))) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
8330
8501
|
};
|
|
8331
8502
|
|
|
8332
8503
|
const SimpleTableWidget = ({ config }) => {
|
|
@@ -8652,16 +8823,70 @@ const TableCellNumber = ({ config, value, onValueChange }) => {
|
|
|
8652
8823
|
backgroundColor: isReadonly ? 'var(--owt-color-bg-alt, #F6F6F6)' : 'var(--owt-color-bg, #FFFFFF)',
|
|
8653
8824
|
} }));
|
|
8654
8825
|
};
|
|
8655
|
-
const TableCellDate = ({ config, value, onValueChange }) => {
|
|
8826
|
+
const TableCellDate = ({ config, value, rowValues, onValueChange }) => {
|
|
8827
|
+
const { translateConfig } = useWidgetTranslation();
|
|
8656
8828
|
const isReadonly = config['widget-readonly'] || false;
|
|
8657
8829
|
const placeholder = config['widget-data-placeholder'] || '';
|
|
8830
|
+
const optionsConfig = config['widget-data-options'];
|
|
8831
|
+
const formatConfig = config['widget-data-format'];
|
|
8832
|
+
const dateConstraint = formatConfig?.dateConstraint || 'any';
|
|
8833
|
+
const minDate = optionsConfig?.minDate;
|
|
8834
|
+
const maxDate = optionsConfig?.maxDate;
|
|
8835
|
+
const minDateField = optionsConfig?.minDateField;
|
|
8836
|
+
const maxDateField = optionsConfig?.maxDateField;
|
|
8837
|
+
const minDateMessage = optionsConfig?.minDateMessage
|
|
8838
|
+
? translateConfig(optionsConfig.minDateMessage)
|
|
8839
|
+
: undefined;
|
|
8840
|
+
const maxDateMessage = optionsConfig?.maxDateMessage
|
|
8841
|
+
? translateConfig(optionsConfig.maxDateMessage)
|
|
8842
|
+
: undefined;
|
|
8843
|
+
const [constraintError, setConstraintError] = React.useState(null);
|
|
8844
|
+
const resolveSiblingDate = (fieldRef) => {
|
|
8845
|
+
if (!fieldRef || !rowValues) {
|
|
8846
|
+
return undefined;
|
|
8847
|
+
}
|
|
8848
|
+
const raw = getValueByPath(rowValues, fieldRef) ?? rowValues[fieldRef];
|
|
8849
|
+
return resolveDateBoundFromFieldValue(raw);
|
|
8850
|
+
};
|
|
8851
|
+
const fieldMinDate = React.useMemo(() => resolveSiblingDate(minDateField), [minDateField, rowValues]);
|
|
8852
|
+
const fieldMaxDate = React.useMemo(() => resolveSiblingDate(maxDateField), [maxDateField, rowValues]);
|
|
8853
|
+
const effectiveMinDate = React.useMemo(() => {
|
|
8854
|
+
const staticMin = getMinDate(dateConstraint, minDate);
|
|
8855
|
+
return mergeMinDateBounds(staticMin, fieldMinDate);
|
|
8856
|
+
}, [dateConstraint, minDate, fieldMinDate]);
|
|
8857
|
+
const effectiveMaxDate = React.useMemo(() => {
|
|
8858
|
+
const staticMax = getMaxDate(dateConstraint, maxDate);
|
|
8859
|
+
return mergeMaxDateBounds(staticMax, fieldMaxDate);
|
|
8860
|
+
}, [dateConstraint, maxDate, fieldMaxDate]);
|
|
8861
|
+
const constraintMessages = React.useMemo(() => ({ minDateMessage, maxDateMessage }), [minDateMessage, maxDateMessage]);
|
|
8658
8862
|
// input type="date" requires YYYY-MM-DD format
|
|
8659
8863
|
const displayValue = value && typeof value === 'string' ? value.split('T')[0] : '';
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
}
|
|
8864
|
+
React.useEffect(() => {
|
|
8865
|
+
if (!displayValue) {
|
|
8866
|
+
setConstraintError(null);
|
|
8867
|
+
return;
|
|
8868
|
+
}
|
|
8869
|
+
const error = validateDateConstraints(displayValue, effectiveMinDate, effectiveMaxDate, dateConstraint, constraintMessages);
|
|
8870
|
+
setConstraintError(error);
|
|
8871
|
+
}, [displayValue, effectiveMinDate, effectiveMaxDate, dateConstraint, constraintMessages]);
|
|
8872
|
+
const handleChange = (e) => {
|
|
8873
|
+
const nextValue = e.target.value;
|
|
8874
|
+
onValueChange(nextValue);
|
|
8875
|
+
if (!nextValue) {
|
|
8876
|
+
setConstraintError(null);
|
|
8877
|
+
return;
|
|
8878
|
+
}
|
|
8879
|
+
const error = validateDateConstraints(nextValue, effectiveMinDate, effectiveMaxDate, dateConstraint, constraintMessages);
|
|
8880
|
+
setConstraintError(error);
|
|
8881
|
+
};
|
|
8882
|
+
const hasError = Boolean(constraintError);
|
|
8883
|
+
return (jsxRuntimeExports.jsxs("div", { className: "w-full", children: [jsxRuntimeExports.jsx("input", { type: "date", value: displayValue, onChange: handleChange, disabled: isReadonly, placeholder: placeholder, min: effectiveMinDate, max: effectiveMaxDate, title: constraintError || translateConfig(config['widget-data-tooltip']), className: `w-full h-[28px] px-2 text-sm border focus:outline-none ${isReadonly ? 'cursor-not-allowed' : ''} ${hasError ? 'border-red-500' : ''} table-cell-input`, style: {
|
|
8884
|
+
borderRadius: '10px',
|
|
8885
|
+
borderColor: hasError
|
|
8886
|
+
? 'var(--owt-color-error, #B91C1C)'
|
|
8887
|
+
: 'var(--owt-widget-input-border, #C4C4C4)',
|
|
8888
|
+
backgroundColor: isReadonly ? 'var(--owt-color-bg-alt, #F6F6F6)' : 'var(--owt-color-bg, #FFFFFF)',
|
|
8889
|
+
} }), hasError && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-xs mt-0.5 leading-tight", children: constraintError }))] }));
|
|
8665
8890
|
};
|
|
8666
8891
|
const TableWidget = ({ config }) => {
|
|
8667
8892
|
const { value, error, touched, isEnabled, onChange, config: widgetConfig, } = useBaseWidget({ config });
|
|
@@ -9020,11 +9245,21 @@ const TableWidget = ({ config }) => {
|
|
|
9020
9245
|
});
|
|
9021
9246
|
}
|
|
9022
9247
|
}, [isAdding, newRowData, columns, widgetConfig, rows.length, dispatch]);
|
|
9248
|
+
const getRowValuesForEdit = React.useCallback((rowIndex) => {
|
|
9249
|
+
if (editingState && editingState.rowIndex === rowIndex) {
|
|
9250
|
+
return editingState.currentValue ?? {};
|
|
9251
|
+
}
|
|
9252
|
+
if (isAdding && rowIndex === rows.length && newRowData) {
|
|
9253
|
+
return newRowData;
|
|
9254
|
+
}
|
|
9255
|
+
return rows[rowIndex] ?? {};
|
|
9256
|
+
}, [editingState, isAdding, rows, newRowData]);
|
|
9023
9257
|
// Lightweight cell renderer for table cells (no labels, compact)
|
|
9024
9258
|
const renderTableCell = React.useCallback((rowIndex, column, cellValue, isReadonly) => {
|
|
9025
9259
|
const columnKey = column['column-key'];
|
|
9026
9260
|
const widgetType = column.widget || 'text';
|
|
9027
9261
|
const cellWidgetId = `${widgetConfig['widget-id']}-row-${rowIndex}-col-${columnKey}`;
|
|
9262
|
+
const rowValues = getRowValuesForEdit(rowIndex);
|
|
9028
9263
|
// Use lightweight cell config (no label, minimal styling)
|
|
9029
9264
|
const cellConfig = {
|
|
9030
9265
|
...column,
|
|
@@ -9047,7 +9282,7 @@ const TableWidget = ({ config }) => {
|
|
|
9047
9282
|
return jsxRuntimeExports.jsx(TableCellNumber, { config: cellConfig, value: cellValue, onValueChange: (newValue) => updateCellValue(columnKey, newValue, rowIndex) });
|
|
9048
9283
|
}
|
|
9049
9284
|
else if (widgetType === 'date') {
|
|
9050
|
-
return jsxRuntimeExports.jsx(TableCellDate, { config: cellConfig, value: cellValue, onValueChange: (newValue) => updateCellValue(columnKey, newValue, rowIndex) });
|
|
9285
|
+
return jsxRuntimeExports.jsx(TableCellDate, { config: cellConfig, value: cellValue, rowValues: rowValues, onValueChange: (newValue) => updateCellValue(columnKey, newValue, rowIndex) });
|
|
9051
9286
|
}
|
|
9052
9287
|
// For other widget types, use WidgetRenderer but with compact styling
|
|
9053
9288
|
return (jsxRuntimeExports.jsx("div", { className: "table-cell-widget", style: { margin: 0, padding: 0 }, children: jsxRuntimeExports.jsx(WidgetRenderer, { config: cellConfig, schemaData: {
|
|
@@ -9055,7 +9290,7 @@ const TableWidget = ({ config }) => {
|
|
|
9055
9290
|
}, onValueChange: (widgetId, newValue) => {
|
|
9056
9291
|
updateCellValue(columnKey, newValue, rowIndex);
|
|
9057
9292
|
} }) }));
|
|
9058
|
-
}, [widgetConfig, updateCellValue]);
|
|
9293
|
+
}, [widgetConfig, updateCellValue, getRowValuesForEdit]);
|
|
9059
9294
|
// Render cell content (widget in edit mode, formatted value in view mode)
|
|
9060
9295
|
const renderCell = React.useCallback((rowIndex, column, row) => {
|
|
9061
9296
|
const columnKey = column['column-key'];
|
|
@@ -9365,6 +9600,27 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9365
9600
|
}, [formData, columns, storeValues, dialogFieldWidgetId]);
|
|
9366
9601
|
const saveDialog = React.useCallback(() => {
|
|
9367
9602
|
const payload = collectMergedRowPayload();
|
|
9603
|
+
let hasErrors = false;
|
|
9604
|
+
columns.forEach((col) => {
|
|
9605
|
+
const key = col['column-key'];
|
|
9606
|
+
const cellWidgetId = dialogFieldWidgetId(key);
|
|
9607
|
+
const isColReadonly = isReadonly || col['widget-readonly'] === true;
|
|
9608
|
+
if (isColReadonly)
|
|
9609
|
+
return;
|
|
9610
|
+
const cellValue = payload[key];
|
|
9611
|
+
const validationErrors = validateWidget(cellValue, col['widget-data-validation'], col['widget-required']);
|
|
9612
|
+
if (validationErrors && validationErrors.length > 0) {
|
|
9613
|
+
hasErrors = true;
|
|
9614
|
+
dispatch(setError({ widgetId: cellWidgetId, errors: validationErrors }));
|
|
9615
|
+
dispatch(setTouched({ widgetId: cellWidgetId, touched: true }));
|
|
9616
|
+
}
|
|
9617
|
+
else {
|
|
9618
|
+
dispatch(setError({ widgetId: cellWidgetId, errors: [] }));
|
|
9619
|
+
}
|
|
9620
|
+
});
|
|
9621
|
+
if (hasErrors) {
|
|
9622
|
+
return;
|
|
9623
|
+
}
|
|
9368
9624
|
if (dialogMode === 'add') {
|
|
9369
9625
|
const savedRow = { ...payload, edit_action: 'ADD' };
|
|
9370
9626
|
onChange([...rows, savedRow]);
|
|
@@ -9380,7 +9636,7 @@ const DialogTableWidget = ({ config }) => {
|
|
|
9380
9636
|
onChange(newRows);
|
|
9381
9637
|
closeDialog();
|
|
9382
9638
|
}
|
|
9383
|
-
}, [collectMergedRowPayload, dialogMode, onChange, rows, closeDialog, activeRowIndex]);
|
|
9639
|
+
}, [collectMergedRowPayload, dialogMode, onChange, rows, closeDialog, activeRowIndex, columns, dialogFieldWidgetId, isReadonly, dispatch]);
|
|
9384
9640
|
const deleteRow = React.useCallback((rowIndex) => {
|
|
9385
9641
|
const newRows = rows.filter((_, i) => i !== rowIndex);
|
|
9386
9642
|
onChange(newRows);
|
|
@@ -9731,10 +9987,13 @@ const TextAreaWidget = ({ config }) => {
|
|
|
9731
9987
|
// For readonly mode, render as preformatted text using <pre> tag
|
|
9732
9988
|
if (isReadonly) {
|
|
9733
9989
|
const displayValue = getStringValue() || '-';
|
|
9734
|
-
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] TextAreaDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("
|
|
9990
|
+
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] TextAreaDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { title: String(displayValue), className: "text-base text-gray-900 font-medium overflow-y-auto whitespace-pre-wrap break-words", style: {
|
|
9735
9991
|
fontFamily: 'Roboto, sans-serif',
|
|
9736
|
-
|
|
9737
|
-
|
|
9992
|
+
height: '56px',
|
|
9993
|
+
minHeight: '56px',
|
|
9994
|
+
maxHeight: '56px',
|
|
9995
|
+
lineHeight: '20px',
|
|
9996
|
+
padding: '8px 0',
|
|
9738
9997
|
backgroundColor: 'transparent',
|
|
9739
9998
|
border: 'none',
|
|
9740
9999
|
}, children: displayValue }) })] }));
|
|
@@ -10203,20 +10462,29 @@ const HeaderSectionWidget = ({ config }) => {
|
|
|
10203
10462
|
.${cls} .hdr-field-row {
|
|
10204
10463
|
display: flex;
|
|
10205
10464
|
align-items: flex-start;
|
|
10206
|
-
gap: 0.5rem;
|
|
10207
10465
|
font-size: 1rem;
|
|
10208
10466
|
line-height: 1.6;
|
|
10209
10467
|
}
|
|
10210
10468
|
|
|
10211
10469
|
.${cls} .hdr-field-label {
|
|
10470
|
+
width: 50%;
|
|
10471
|
+
flex: 0 0 50%;
|
|
10212
10472
|
color: rgba(0, 0, 0, 0.5);
|
|
10213
10473
|
font-weight: 400;
|
|
10214
10474
|
white-space: nowrap;
|
|
10475
|
+
overflow: hidden;
|
|
10476
|
+
text-overflow: ellipsis;
|
|
10477
|
+
padding-right: 4px;
|
|
10215
10478
|
}
|
|
10216
10479
|
|
|
10217
10480
|
.${cls} .hdr-field-value {
|
|
10481
|
+
width: 50%;
|
|
10482
|
+
flex: 0 0 50%;
|
|
10218
10483
|
color: var(--owt-color-text, #111827);
|
|
10219
10484
|
font-weight: 500;
|
|
10485
|
+
white-space: nowrap;
|
|
10486
|
+
overflow: hidden;
|
|
10487
|
+
text-overflow: ellipsis;
|
|
10220
10488
|
}
|
|
10221
10489
|
|
|
10222
10490
|
.${cls} .hdr-status-badge {
|
|
@@ -10226,24 +10494,38 @@ const HeaderSectionWidget = ({ config }) => {
|
|
|
10226
10494
|
font-size: 0.75rem;
|
|
10227
10495
|
font-weight: 600;
|
|
10228
10496
|
color: #fff;
|
|
10497
|
+
max-width: 100%;
|
|
10498
|
+
overflow: hidden;
|
|
10499
|
+
text-overflow: ellipsis;
|
|
10500
|
+
white-space: nowrap;
|
|
10229
10501
|
}
|
|
10230
10502
|
|
|
10231
10503
|
.${cls} .hdr-meta-row {
|
|
10232
10504
|
display: flex;
|
|
10233
10505
|
align-items: baseline;
|
|
10234
|
-
gap: 0.35rem;
|
|
10235
10506
|
font-size: 1rem;
|
|
10236
10507
|
line-height: 1.6;
|
|
10237
10508
|
}
|
|
10238
10509
|
|
|
10239
10510
|
.${cls} .hdr-meta-label {
|
|
10511
|
+
width: 50%;
|
|
10512
|
+
flex: 0 0 50%;
|
|
10240
10513
|
color: rgba(0, 0, 0, 0.5);
|
|
10241
10514
|
font-weight: 400;
|
|
10515
|
+
white-space: nowrap;
|
|
10516
|
+
overflow: hidden;
|
|
10517
|
+
text-overflow: ellipsis;
|
|
10518
|
+
padding-right: 4px;
|
|
10242
10519
|
}
|
|
10243
10520
|
|
|
10244
10521
|
.${cls} .hdr-meta-value {
|
|
10522
|
+
width: 50%;
|
|
10523
|
+
flex: 0 0 50%;
|
|
10245
10524
|
color: var(--owt-color-text, #111827);
|
|
10246
10525
|
font-weight: 500;
|
|
10526
|
+
white-space: nowrap;
|
|
10527
|
+
overflow: hidden;
|
|
10528
|
+
text-overflow: ellipsis;
|
|
10247
10529
|
}
|
|
10248
10530
|
|
|
10249
10531
|
.${cls} .hdr-select {
|
|
@@ -10307,7 +10589,7 @@ const HeaderSectionWidget = ({ config }) => {
|
|
|
10307
10589
|
.parentElement?.querySelector('.hdr-avatar-placeholder');
|
|
10308
10590
|
if (placeholder)
|
|
10309
10591
|
placeholder.style.display = 'flex';
|
|
10310
|
-
} })) : null, jsxRuntimeExports.jsx("div", { className: "hdr-avatar-placeholder", style: { display: displayImageUrl ? 'none' : 'flex' }, children: jsxRuntimeExports.jsx("img", { src: img$f, alt: "Profile Placeholder" }) }), !isReadonly && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsxs("div", { className: "hdr-avatar-overlay", children: [jsxRuntimeExports.jsxs("button", { type: "button", className: "hdr-avatar-action", onClick: () => fileInputRef.current?.click(), children: [jsxRuntimeExports.jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntimeExports.jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }), jsxRuntimeExports.jsx("polyline", { points: "17 8 12 3 7 8" }), jsxRuntimeExports.jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })] }), "Upload"] }), jsxRuntimeExports.jsxs("button", { type: "button", className: "hdr-avatar-action hdr-avatar-action--delete", onClick: handleImageDelete, children: [jsxRuntimeExports.jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntimeExports.jsx("polyline", { points: "3 6 5 6 21 6" }), jsxRuntimeExports.jsx("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })] }), "Delete"] })] }), jsxRuntimeExports.jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", style: { display: 'none' }, onChange: handleImageUpload })] }))] }), jsxRuntimeExports.jsxs("div", { className: "hdr-info", children: [displayName && jsxRuntimeExports.jsx("div", { className: "hdr-name", children: displayName }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-field-label", children: [getLabel('functionalId'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-field-value", children: functionalId || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsx("span", { className: "hdr-field-label", children: getLabel('status') }), isReadonly ? (statusLabel ? (jsxRuntimeExports.jsx("span", { className: "hdr-status-badge", style: { backgroundColor: statusColor }, children: statusLabel })) : (jsxRuntimeExports.jsx("span", { className: "hdr-field-value", children: "-" }))) : (jsxRuntimeExports.jsxs("select", { className: "hdr-select", value: statusValue, onChange: (e) => updateFieldValue('status', e.target.value), children: [jsxRuntimeExports.jsx("option", { value: "", children: getLabel('select') }), statusOptions.map((opt) => (jsxRuntimeExports.jsx("option", { value: opt.value, children: opt.label }, opt.value)))] }))] }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-field-label", children: [getLabel('statusReason'), " :"] }), isReadonly ? (jsxRuntimeExports.jsx("span", { className: "hdr-field-value", children: statusReason || '-' })) : (jsxRuntimeExports.jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [jsxRuntimeExports.jsx("input", { type: "text", className: `hdr-input ${(!isReadonly && (showReasonRequired || isReasonMissing) && isReasonMissing) ? 'hdr-input--error' : ''}`, value: statusReason, placeholder: getLabel('enterReason'), required: isStatusChanged, "aria-required": isStatusChanged, "aria-invalid": !isReadonly && (showReasonRequired || isReasonMissing) && isReasonMissing, onBlur: () => {
|
|
10592
|
+
} })) : null, jsxRuntimeExports.jsx("div", { className: "hdr-avatar-placeholder", style: { display: displayImageUrl ? 'none' : 'flex' }, children: jsxRuntimeExports.jsx("img", { src: img$f, alt: "Profile Placeholder" }) }), !isReadonly && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsxs("div", { className: "hdr-avatar-overlay", children: [jsxRuntimeExports.jsxs("button", { type: "button", className: "hdr-avatar-action", onClick: () => fileInputRef.current?.click(), children: [jsxRuntimeExports.jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntimeExports.jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }), jsxRuntimeExports.jsx("polyline", { points: "17 8 12 3 7 8" }), jsxRuntimeExports.jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })] }), "Upload"] }), jsxRuntimeExports.jsxs("button", { type: "button", className: "hdr-avatar-action hdr-avatar-action--delete", onClick: handleImageDelete, children: [jsxRuntimeExports.jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntimeExports.jsx("polyline", { points: "3 6 5 6 21 6" }), jsxRuntimeExports.jsx("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })] }), "Delete"] })] }), jsxRuntimeExports.jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", style: { display: 'none' }, onChange: handleImageUpload })] }))] }), jsxRuntimeExports.jsxs("div", { className: "hdr-info", children: [displayName && jsxRuntimeExports.jsx("div", { className: "hdr-name", children: displayName }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-field-label", title: `${getLabel('functionalId')} :`, children: [getLabel('functionalId'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-field-value", title: functionalId || '-', children: functionalId || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsx("span", { className: "hdr-field-label", title: getLabel('status'), children: getLabel('status') }), isReadonly ? (statusLabel ? (jsxRuntimeExports.jsx("span", { className: "hdr-status-badge", style: { backgroundColor: statusColor }, title: statusLabel, children: statusLabel })) : (jsxRuntimeExports.jsx("span", { className: "hdr-field-value", title: "-", children: "-" }))) : (jsxRuntimeExports.jsxs("select", { className: "hdr-select", value: statusValue, onChange: (e) => updateFieldValue('status', e.target.value), children: [jsxRuntimeExports.jsx("option", { value: "", children: getLabel('select') }), statusOptions.map((opt) => (jsxRuntimeExports.jsx("option", { value: opt.value, children: opt.label }, opt.value)))] }))] }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-field-label", title: `${getLabel('statusReason')} :`, children: [getLabel('statusReason'), " :"] }), isReadonly ? (jsxRuntimeExports.jsx("span", { className: "hdr-field-value", title: statusReason || '-', children: statusReason || '-' })) : (jsxRuntimeExports.jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [jsxRuntimeExports.jsx("input", { type: "text", className: `hdr-input ${(!isReadonly && (showReasonRequired || isReasonMissing) && isReasonMissing) ? 'hdr-input--error' : ''}`, value: statusReason, placeholder: getLabel('enterReason'), required: isStatusChanged, "aria-required": isStatusChanged, "aria-invalid": !isReadonly && (showReasonRequired || isReasonMissing) && isReasonMissing, onBlur: () => {
|
|
10311
10593
|
if (isReasonMissing)
|
|
10312
10594
|
setShowReasonRequired(true);
|
|
10313
10595
|
}, onChange: (e) => {
|
|
@@ -10315,7 +10597,7 @@ const HeaderSectionWidget = ({ config }) => {
|
|
|
10315
10597
|
if (showReasonRequired && String(e.target.value || '').trim().length > 0) {
|
|
10316
10598
|
setShowReasonRequired(false);
|
|
10317
10599
|
}
|
|
10318
|
-
} }), !isReadonly && (showReasonRequired || isReasonMissing) && isReasonMissing ? (jsxRuntimeExports.jsx("div", { className: "hdr-error-text", children: getLabel('enterReason') })) : null] }))] })] })] }), jsxRuntimeExports.jsx("div", { className: "hdr-right", children: jsxRuntimeExports.jsxs("div", { className: "hdr-right-top", children: [jsxRuntimeExports.jsxs("div", { className: "hdr-meta-col", children: [jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", children: [getLabel('createdBy'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: createdBy || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", children: [getLabel('createdAt'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: createdAt || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", children: [getLabel('lastApprovedBy'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: lastApprovedBy || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", children: [getLabel('lastApprovedAt'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: lastApprovedAt || '-' })] })] }), score ? (jsxRuntimeExports.jsx("div", { className: "hdr-score-ring", style: { ['--pct']: score.percent }, "aria-label": `Completion score ${score.completionDisplay} of ${score.idealDisplay} (${score.percent}%)`, title: `${score.completionDisplay} / ${score.idealDisplay} (${score.percent}%)`, children: jsxRuntimeExports.jsx("div", { className: "hdr-score-value", children: String(score.completionDisplay) }) })) : null] }) })] })] }));
|
|
10600
|
+
} }), !isReadonly && (showReasonRequired || isReasonMissing) && isReasonMissing ? (jsxRuntimeExports.jsx("div", { className: "hdr-error-text", children: getLabel('enterReason') })) : null] }))] })] })] }), jsxRuntimeExports.jsx("div", { className: "hdr-right", children: jsxRuntimeExports.jsxs("div", { className: "hdr-right-top", children: [jsxRuntimeExports.jsxs("div", { className: "hdr-meta-col", children: [jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", title: `${getLabel('createdBy')} :`, children: [getLabel('createdBy'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", title: createdBy || '-', children: createdBy || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", title: `${getLabel('createdAt')} :`, children: [getLabel('createdAt'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", title: createdAt || '-', children: createdAt || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", title: `${getLabel('lastApprovedBy')} :`, children: [getLabel('lastApprovedBy'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", title: lastApprovedBy || '-', children: lastApprovedBy || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", title: `${getLabel('lastApprovedAt')} :`, children: [getLabel('lastApprovedAt'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", title: lastApprovedAt || '-', children: lastApprovedAt || '-' })] })] }), score ? (jsxRuntimeExports.jsx("div", { className: "hdr-score-ring", style: { ['--pct']: score.percent }, "aria-label": `Completion score ${score.completionDisplay} of ${score.idealDisplay} (${score.percent}%)`, title: `${score.completionDisplay} / ${score.idealDisplay} (${score.percent}%)`, children: jsxRuntimeExports.jsx("div", { className: "hdr-score-value", children: String(score.completionDisplay) }) })) : null] }) })] })] }));
|
|
10319
10601
|
};
|
|
10320
10602
|
|
|
10321
10603
|
function getValueByPathOrKey(obj, path) {
|