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