@openg2p/registry-widgets 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/SectionRenderer.d.ts +1 -0
- package/dist/components/SectionRenderer.d.ts.map +1 -1
- package/dist/components/SectionsContainer.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +242 -123
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +242 -123
- package/dist/index.js.map +1 -1
- package/dist/theme/index.d.ts +35 -9
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/widgets/DisplayWidget.d.ts.map +1 -1
- package/dist/widgets/HeaderSectionWidget.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3516,6 +3516,39 @@ const sectionValidate = (section, currentSchemaData, dispatch) => {
|
|
|
3516
3516
|
return isValid;
|
|
3517
3517
|
};
|
|
3518
3518
|
|
|
3519
|
+
/** Root class on readonly label/value rows; SectionRenderer scopes overflow/ellipsis rules here. */
|
|
3520
|
+
const READONLY_VALUE_ROW_ROOT_CLASSES = [
|
|
3521
|
+
'TextDisplayWidget',
|
|
3522
|
+
'TextAreaDisplayWidget',
|
|
3523
|
+
'SelectDisplayWidget',
|
|
3524
|
+
'PhoneDisplayWidget',
|
|
3525
|
+
'NumberDisplayWidget',
|
|
3526
|
+
'CurrencyDisplayWidget',
|
|
3527
|
+
'RadioDisplayWidget',
|
|
3528
|
+
'DateDisplayWidget',
|
|
3529
|
+
'DateTimeDisplayWidget',
|
|
3530
|
+
'CheckboxDisplayWidget',
|
|
3531
|
+
'BooleanDisplayWidget',
|
|
3532
|
+
'FileDisplayWidget',
|
|
3533
|
+
'DisplayFieldWidget',
|
|
3534
|
+
];
|
|
3535
|
+
/** Rows whose value is one line in .flex-1 > .text-gray-900 (ellipsis; full string via title on the element). */
|
|
3536
|
+
const READONLY_SINGLE_LINE_VALUE_ROW_CLASSES = [
|
|
3537
|
+
'TextDisplayWidget',
|
|
3538
|
+
'SelectDisplayWidget',
|
|
3539
|
+
'PhoneDisplayWidget',
|
|
3540
|
+
'NumberDisplayWidget',
|
|
3541
|
+
'CurrencyDisplayWidget',
|
|
3542
|
+
'RadioDisplayWidget',
|
|
3543
|
+
'DateDisplayWidget',
|
|
3544
|
+
'DateTimeDisplayWidget',
|
|
3545
|
+
'CheckboxDisplayWidget',
|
|
3546
|
+
'BooleanDisplayWidget',
|
|
3547
|
+
'DisplayFieldWidget',
|
|
3548
|
+
];
|
|
3549
|
+
function scopedClassSelectors(sectionClassId, classNames) {
|
|
3550
|
+
return classNames.map((c) => `.${sectionClassId} .${c}`).join(',\n ');
|
|
3551
|
+
}
|
|
3519
3552
|
/**
|
|
3520
3553
|
* Renders a section with its panels
|
|
3521
3554
|
*
|
|
@@ -3542,41 +3575,22 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
3542
3575
|
}
|
|
3543
3576
|
return section;
|
|
3544
3577
|
}, [section, namespace]);
|
|
3545
|
-
// Create namespaced schemaData if namespace is provided
|
|
3546
|
-
//
|
|
3578
|
+
// Create namespaced schemaData if namespace is provided.
|
|
3579
|
+
// Widgets with namespaced data-paths (e.g. "rv-section-0.a1a4d25a.birth_date")
|
|
3580
|
+
// need a nested object at values[namespace] so getValueByPath can traverse it.
|
|
3547
3581
|
const namespacedSchemaData = React.useMemo(() => {
|
|
3548
3582
|
if (!namespace || !currentSchemaData) {
|
|
3549
3583
|
return schemaData;
|
|
3550
3584
|
}
|
|
3551
|
-
|
|
3552
|
-
const namespaced = { ...currentSchemaData };
|
|
3553
|
-
// Copy all top-level keys to namespaced paths
|
|
3554
|
-
Object.keys(currentSchemaData).forEach(key => {
|
|
3555
|
-
const namespacedKey = `${namespace}.${key}`;
|
|
3556
|
-
if (!(namespacedKey in namespaced)) {
|
|
3557
|
-
namespaced[namespacedKey] = currentSchemaData[key];
|
|
3558
|
-
}
|
|
3559
|
-
});
|
|
3560
|
-
// Also handle nested objects - copy nested values to namespaced paths
|
|
3561
|
-
const copyNestedValues = (obj, prefix = '') => {
|
|
3562
|
-
if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
|
|
3563
|
-
Object.keys(obj).forEach(key => {
|
|
3564
|
-
const fullPath = prefix ? `${prefix}.${key}` : key;
|
|
3565
|
-
const namespacedPath = `${namespace}.${fullPath}`;
|
|
3566
|
-
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
|
|
3567
|
-
copyNestedValues(obj[key], fullPath);
|
|
3568
|
-
// Also set the nested object at the namespaced path
|
|
3569
|
-
setValueByPath(namespaced, namespacedPath, obj[key]);
|
|
3570
|
-
}
|
|
3571
|
-
else {
|
|
3572
|
-
setValueByPath(namespaced, namespacedPath, obj[key]);
|
|
3573
|
-
}
|
|
3574
|
-
});
|
|
3575
|
-
}
|
|
3576
|
-
};
|
|
3577
|
-
copyNestedValues(currentSchemaData);
|
|
3578
|
-
return namespaced;
|
|
3585
|
+
return { ...currentSchemaData, [namespace]: currentSchemaData };
|
|
3579
3586
|
}, [namespace, schemaData, currentSchemaData]);
|
|
3587
|
+
// Populate the store with namespaced schema data so that namespaced widgets
|
|
3588
|
+
// can read their initial values via getValueByPath on the namespaced paths.
|
|
3589
|
+
React.useEffect(() => {
|
|
3590
|
+
if (namespace && namespacedSchemaData) {
|
|
3591
|
+
dispatch(setValues(namespacedSchemaData));
|
|
3592
|
+
}
|
|
3593
|
+
}, [namespace, namespacedSchemaData, dispatch]);
|
|
3580
3594
|
const crViewData = React.useMemo(() => {
|
|
3581
3595
|
if (mode !== 'CRView')
|
|
3582
3596
|
return null;
|
|
@@ -3599,6 +3613,9 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
3599
3613
|
const sectionId = sectionToRender['section-id'];
|
|
3600
3614
|
const gridId = `section-panels-${sectionId}`;
|
|
3601
3615
|
const sectionClassId = `section-${sectionId}`;
|
|
3616
|
+
const readonlyValueRowRootsCss = React.useMemo(() => scopedClassSelectors(sectionClassId, READONLY_VALUE_ROW_ROOT_CLASSES), [sectionClassId]);
|
|
3617
|
+
const readonlyValueRowFlex1Css = React.useMemo(() => READONLY_VALUE_ROW_ROOT_CLASSES.map((c) => `.${sectionClassId} .${c} > .flex-1`).join(',\n '), [sectionClassId]);
|
|
3618
|
+
const readonlySingleLineValueTextCss = React.useMemo(() => READONLY_SINGLE_LINE_VALUE_ROW_CLASSES.map((c) => `.${sectionClassId} .${c} > .flex-1 > .text-gray-900`).join(',\n '), [sectionClassId]);
|
|
3602
3619
|
// IntakeForm mode: accordion expand/collapse state (supports toggle)
|
|
3603
3620
|
const [standaloneExpanded, setStandaloneExpanded] = React.useState(true); // For sectionIndex undefined (standalone use)
|
|
3604
3621
|
const isExpandedFromContainer = typeof sectionIndex === 'number' && expandedSectionIndex === sectionIndex;
|
|
@@ -3938,6 +3955,8 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
3938
3955
|
const baselineSnapshotRef = React.useRef(null);
|
|
3939
3956
|
// IntakeForm only: increment when baseline is updated after save - forces badge to update (refs don't trigger re-renders)
|
|
3940
3957
|
const [intakeFormBaselineTrigger, setIntakeFormBaselineTrigger] = React.useState(0);
|
|
3958
|
+
// IntakeForm only: tracks whether the user has actually saved this section (prevents "Saved" badge on initial load)
|
|
3959
|
+
const [hasBeenSavedByUser, setHasBeenSavedByUser] = React.useState(false);
|
|
3941
3960
|
// IntakeForm: treat as edit mode for dirty tracking when isDraft. RegistryView: use isEditMode.
|
|
3942
3961
|
const effectiveEditModeForDirty = mode === 'IntakeForm' ? (isDraft !== false) : isEditMode;
|
|
3943
3962
|
// Compute isDirty: compare current store state to baseline (only when in edit mode)
|
|
@@ -3980,18 +3999,68 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
3980
3999
|
const intakeFormSectionStatus = React.useMemo(() => {
|
|
3981
4000
|
if (mode !== 'IntakeForm' || isDraft === false)
|
|
3982
4001
|
return null;
|
|
3983
|
-
const hasValue = (v) => v !== undefined && v !== null && (typeof v !== 'string' || v.trim().length > 0);
|
|
3984
|
-
const currentSnapshot = buildSectionSnapshot(storeValues, namespace);
|
|
3985
|
-
const record = currentSnapshot.records?.[0];
|
|
3986
|
-
const hasData = record &&
|
|
3987
|
-
typeof record === 'object' &&
|
|
3988
|
-
Object.values(record).some((v) => hasValue(v));
|
|
3989
4002
|
if (isDirty)
|
|
3990
4003
|
return 'modified';
|
|
3991
|
-
if (
|
|
4004
|
+
if (hasBeenSavedByUser)
|
|
3992
4005
|
return 'saved';
|
|
3993
4006
|
return null;
|
|
3994
|
-
}, [mode, isDirty,
|
|
4007
|
+
}, [mode, isDirty, hasBeenSavedByUser]);
|
|
4008
|
+
// Revert store values to the original schemaData for this section's widgets.
|
|
4009
|
+
// Used by both handleSave (RegistryView raises a CR, so values should not persist)
|
|
4010
|
+
// and handleCancel.
|
|
4011
|
+
const revertToOriginalValues = React.useCallback(() => {
|
|
4012
|
+
const sectionWidgets = collectWidgets(originalSection.panels);
|
|
4013
|
+
const oldSchemaData = schemaData || contextSchemaData;
|
|
4014
|
+
const currentStoreValues = store.getState().widget.values;
|
|
4015
|
+
let newStoreValues = currentStoreValues;
|
|
4016
|
+
sectionWidgets.forEach(widget => {
|
|
4017
|
+
const originalWidgetId = widget['widget-id'];
|
|
4018
|
+
const namespacedWidgetId = namespace ? `${namespace}__${originalWidgetId}` : originalWidgetId;
|
|
4019
|
+
const widgetId = namespacedWidgetId;
|
|
4020
|
+
const originalDataPath = widget['widget-data-path'];
|
|
4021
|
+
const storeDataPath = namespace && originalDataPath
|
|
4022
|
+
? (typeof originalDataPath === 'string'
|
|
4023
|
+
? `${namespace}.${originalDataPath}`
|
|
4024
|
+
: Object.fromEntries(Object.entries(originalDataPath).map(([key, path]) => [key, `${namespace}.${path}`])))
|
|
4025
|
+
: originalDataPath;
|
|
4026
|
+
if (widgetId && originalDataPath) {
|
|
4027
|
+
let oldValue;
|
|
4028
|
+
if (typeof originalDataPath === 'object') {
|
|
4029
|
+
oldValue = {};
|
|
4030
|
+
Object.entries(originalDataPath).forEach(([key, path]) => {
|
|
4031
|
+
if (typeof path === 'string') {
|
|
4032
|
+
oldValue[key] = getValueByPath(oldSchemaData, path);
|
|
4033
|
+
}
|
|
4034
|
+
});
|
|
4035
|
+
}
|
|
4036
|
+
else if (typeof originalDataPath === 'string') {
|
|
4037
|
+
oldValue = getValueByPath(oldSchemaData, originalDataPath);
|
|
4038
|
+
}
|
|
4039
|
+
if (oldValue !== undefined) {
|
|
4040
|
+
newStoreValues = setWidgetValue(newStoreValues, storeDataPath, widgetId, oldValue);
|
|
4041
|
+
// Also revert the widgetId-based entry — useBaseWidget.handleChange
|
|
4042
|
+
// sets values[widgetId] during editing, and useBaseWidget.currentValue
|
|
4043
|
+
// reads values[widgetId] first before falling through to the dataPath.
|
|
4044
|
+
newStoreValues = { ...newStoreValues, [widgetId]: oldValue };
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
});
|
|
4048
|
+
if (hasSupportingDocuments) {
|
|
4049
|
+
const originalSupportingDocuments = originalSection['section-supporting-documents'] || [];
|
|
4050
|
+
originalSupportingDocuments.forEach((doc, index) => {
|
|
4051
|
+
const widgetId = `supporting-doc-${sectionId}-${index}`;
|
|
4052
|
+
const originalDataPath = doc['document-data-path'];
|
|
4053
|
+
const storeDataPath = namespace && originalDataPath
|
|
4054
|
+
? `${namespace}.${originalDataPath}`
|
|
4055
|
+
: originalDataPath;
|
|
4056
|
+
const oldValue = getValueByPath(oldSchemaData, originalDataPath);
|
|
4057
|
+
newStoreValues = setWidgetValue(newStoreValues, storeDataPath, widgetId, oldValue);
|
|
4058
|
+
});
|
|
4059
|
+
}
|
|
4060
|
+
if (newStoreValues !== currentStoreValues) {
|
|
4061
|
+
dispatch(setValues(newStoreValues));
|
|
4062
|
+
}
|
|
4063
|
+
}, [originalSection, schemaData, contextSchemaData, store, namespace, hasSupportingDocuments, sectionId, dispatch]);
|
|
3995
4064
|
// Handle save button click
|
|
3996
4065
|
const handleSave = async () => {
|
|
3997
4066
|
if (!store || !onSectionSave) {
|
|
@@ -4028,12 +4097,24 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4028
4097
|
});
|
|
4029
4098
|
}
|
|
4030
4099
|
if (JSON.stringify(oldSchemaData) !== JSON.stringify(newSchemaData)) {
|
|
4100
|
+
let profileImage = null;
|
|
4101
|
+
for (const record of newSchemaData) {
|
|
4102
|
+
if (typeof record === 'object' && record !== null) {
|
|
4103
|
+
for (const [key, value] of Object.entries(record)) {
|
|
4104
|
+
if (value instanceof File) {
|
|
4105
|
+
profileImage = value;
|
|
4106
|
+
record[key] = '';
|
|
4107
|
+
}
|
|
4108
|
+
}
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
4031
4111
|
try {
|
|
4032
4112
|
const sectionchanges = {
|
|
4033
4113
|
section_id: dbSectionId ?? originalSection['section-id'],
|
|
4034
4114
|
section_register_id: sectionRegisterId,
|
|
4035
4115
|
records: [...newSchemaData],
|
|
4036
|
-
files: [...sectionFiles]
|
|
4116
|
+
files: [...sectionFiles],
|
|
4117
|
+
...(profileImage ? { image: profileImage } : {}),
|
|
4037
4118
|
};
|
|
4038
4119
|
await onSectionSave(sectionchanges);
|
|
4039
4120
|
}
|
|
@@ -4041,6 +4122,12 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4041
4122
|
console.error('Section Changes Save failed', error);
|
|
4042
4123
|
}
|
|
4043
4124
|
}
|
|
4125
|
+
// In RegistryView, save raises a CR — the actual data update follows a
|
|
4126
|
+
// separate approval workflow, so revert the displayed values to the
|
|
4127
|
+
// originals so the view doesn't show unapproved edits.
|
|
4128
|
+
if (mode === 'RegistryView') {
|
|
4129
|
+
revertToOriginalValues();
|
|
4130
|
+
}
|
|
4044
4131
|
setIsEditMode(false);
|
|
4045
4132
|
onEditModeChange?.(originalSectionId, false);
|
|
4046
4133
|
};
|
|
@@ -4070,12 +4157,24 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4070
4157
|
});
|
|
4071
4158
|
}
|
|
4072
4159
|
if (JSON.stringify(oldSchemaData) !== JSON.stringify(newSchemaData)) {
|
|
4160
|
+
let profileImage = null;
|
|
4161
|
+
for (const record of newSchemaData) {
|
|
4162
|
+
if (typeof record === 'object' && record !== null) {
|
|
4163
|
+
for (const [key, value] of Object.entries(record)) {
|
|
4164
|
+
if (value instanceof File) {
|
|
4165
|
+
profileImage = value;
|
|
4166
|
+
record[key] = '';
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
4169
|
+
}
|
|
4170
|
+
}
|
|
4073
4171
|
try {
|
|
4074
4172
|
await onSectionSave({
|
|
4075
4173
|
section_id: dbSectionId ?? originalSection['section-id'],
|
|
4076
4174
|
section_register_id: sectionRegisterId,
|
|
4077
4175
|
records: [...newSchemaData],
|
|
4078
4176
|
files: [...sectionFiles],
|
|
4177
|
+
...(profileImage ? { image: profileImage } : {}),
|
|
4079
4178
|
});
|
|
4080
4179
|
}
|
|
4081
4180
|
catch (error) {
|
|
@@ -4086,6 +4185,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4086
4185
|
if (mode === 'IntakeForm') {
|
|
4087
4186
|
baselineSnapshotRef.current = buildSectionSnapshot(currentSchemaData, namespace);
|
|
4088
4187
|
setIntakeFormBaselineTrigger((prev) => prev + 1);
|
|
4188
|
+
setHasBeenSavedByUser(true);
|
|
4089
4189
|
}
|
|
4090
4190
|
onSectionDirtyChange?.(sectionId, false);
|
|
4091
4191
|
}
|
|
@@ -4094,64 +4194,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4094
4194
|
}, [store, onSectionSave, onSectionSaveSuccess, sectionIndex, originalSection, schemaData, contextSchemaData, namespace, hasSupportingDocuments, dbSectionId, sectionRegisterId, dispatch, buildSectionSnapshot, sectionId, onSectionDirtyChange, mode, isDraft]);
|
|
4095
4195
|
// Handle cancel button click
|
|
4096
4196
|
const handleCancel = () => {
|
|
4097
|
-
|
|
4098
|
-
// Use original section (without namespace) for collecting widgets
|
|
4099
|
-
const sectionWidgets = collectWidgets(originalSection.panels);
|
|
4100
|
-
const oldSchemaData = schemaData || contextSchemaData;
|
|
4101
|
-
const currentStoreValues = store.getState().widget.values;
|
|
4102
|
-
let newStoreValues = currentStoreValues;
|
|
4103
|
-
sectionWidgets.forEach(widget => {
|
|
4104
|
-
const originalWidgetId = widget['widget-id'];
|
|
4105
|
-
// If namespace was used, we need to use namespaced widget ID and data path
|
|
4106
|
-
const namespacedWidgetId = namespace ? `${namespace}__${originalWidgetId}` : originalWidgetId;
|
|
4107
|
-
const widgetId = namespacedWidgetId;
|
|
4108
|
-
const originalDataPath = widget['widget-data-path'];
|
|
4109
|
-
// If namespace was used, data path in store is namespaced, but we read from original schema using original path
|
|
4110
|
-
const storeDataPath = namespace && originalDataPath
|
|
4111
|
-
? (typeof originalDataPath === 'string'
|
|
4112
|
-
? `${namespace}.${originalDataPath}`
|
|
4113
|
-
: Object.fromEntries(Object.entries(originalDataPath).map(([key, path]) => [key, `${namespace}.${path}`])))
|
|
4114
|
-
: originalDataPath;
|
|
4115
|
-
if (widgetId && originalDataPath) {
|
|
4116
|
-
// Handle multi-path (object) or single path (string)
|
|
4117
|
-
// Read from original schema data using original paths
|
|
4118
|
-
let oldValue;
|
|
4119
|
-
if (typeof originalDataPath === 'object') {
|
|
4120
|
-
// Multi-path: get values for each path
|
|
4121
|
-
oldValue = {};
|
|
4122
|
-
Object.entries(originalDataPath).forEach(([key, path]) => {
|
|
4123
|
-
if (typeof path === 'string') {
|
|
4124
|
-
oldValue[key] = getValueByPath(oldSchemaData, path);
|
|
4125
|
-
}
|
|
4126
|
-
});
|
|
4127
|
-
}
|
|
4128
|
-
else if (typeof originalDataPath === 'string') {
|
|
4129
|
-
oldValue = getValueByPath(oldSchemaData, originalDataPath);
|
|
4130
|
-
}
|
|
4131
|
-
// Set in store using namespaced data path (if namespace was used)
|
|
4132
|
-
if (oldValue !== undefined) {
|
|
4133
|
-
newStoreValues = setWidgetValue(newStoreValues, storeDataPath, widgetId, oldValue);
|
|
4134
|
-
}
|
|
4135
|
-
}
|
|
4136
|
-
});
|
|
4137
|
-
// Also revert supporting documents if any
|
|
4138
|
-
if (hasSupportingDocuments) {
|
|
4139
|
-
// Use original section's supporting documents to get original data paths
|
|
4140
|
-
const originalSupportingDocuments = originalSection['section-supporting-documents'] || [];
|
|
4141
|
-
originalSupportingDocuments.forEach((doc, index) => {
|
|
4142
|
-
const widgetId = `supporting-doc-${sectionId}-${index}`;
|
|
4143
|
-
const originalDataPath = doc['document-data-path'];
|
|
4144
|
-
// If namespace was used, data path in store is namespaced
|
|
4145
|
-
const storeDataPath = namespace && originalDataPath
|
|
4146
|
-
? `${namespace}.${originalDataPath}`
|
|
4147
|
-
: originalDataPath;
|
|
4148
|
-
const oldValue = getValueByPath(oldSchemaData, originalDataPath);
|
|
4149
|
-
newStoreValues = setWidgetValue(newStoreValues, storeDataPath, widgetId, oldValue);
|
|
4150
|
-
});
|
|
4151
|
-
}
|
|
4152
|
-
if (newStoreValues !== currentStoreValues) {
|
|
4153
|
-
dispatch(setValues(newStoreValues));
|
|
4154
|
-
}
|
|
4197
|
+
revertToOriginalValues();
|
|
4155
4198
|
setIsEditMode(false);
|
|
4156
4199
|
onEditModeChange?.(originalSectionId, false);
|
|
4157
4200
|
};
|
|
@@ -4202,20 +4245,27 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4202
4245
|
white-space: nowrap !important;
|
|
4203
4246
|
}
|
|
4204
4247
|
/* Readonly: prevent flex row from overflowing panel */
|
|
4205
|
-
|
|
4248
|
+
${readonlyValueRowRootsCss} {
|
|
4206
4249
|
min-width: 0 !important;
|
|
4207
4250
|
overflow: hidden !important;
|
|
4208
4251
|
}
|
|
4209
|
-
|
|
4252
|
+
${readonlyValueRowFlex1Css} {
|
|
4210
4253
|
min-width: 0 !important;
|
|
4211
4254
|
overflow: hidden !important;
|
|
4212
4255
|
}
|
|
4213
|
-
/* Readonly value
|
|
4214
|
-
|
|
4256
|
+
/* Readonly value: single-line ellipsis; full value via title on the value node */
|
|
4257
|
+
${readonlySingleLineValueTextCss} {
|
|
4215
4258
|
overflow: hidden;
|
|
4216
4259
|
text-overflow: ellipsis;
|
|
4217
4260
|
white-space: nowrap;
|
|
4218
4261
|
}
|
|
4262
|
+
/* Readonly textarea: break unbroken long tokens; title on pre keeps full text on hover */
|
|
4263
|
+
.${sectionClassId} .TextAreaDisplayWidget > .flex-1 > pre {
|
|
4264
|
+
min-width: 0;
|
|
4265
|
+
max-width: 100%;
|
|
4266
|
+
overflow-wrap: anywhere;
|
|
4267
|
+
word-break: break-word;
|
|
4268
|
+
}
|
|
4219
4269
|
|
|
4220
4270
|
/* Only apply fixed height when in edit mode */
|
|
4221
4271
|
.${sectionClassId}[data-edit-mode="true"] {
|
|
@@ -4717,6 +4767,9 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
|
|
|
4717
4767
|
}, []);
|
|
4718
4768
|
const safeSections = sections ?? [];
|
|
4719
4769
|
const prevSectionsLengthRef = React.useRef(safeSections.length);
|
|
4770
|
+
// Stable ref for namespace so formHandle useMemo doesn't depend on the (possibly inline) function identity
|
|
4771
|
+
const namespaceRef = React.useRef(namespace);
|
|
4772
|
+
namespaceRef.current = namespace;
|
|
4720
4773
|
// Track dirty (unsaved changes) per section for form handle validation
|
|
4721
4774
|
const sectionDirtyMapRef = React.useRef({});
|
|
4722
4775
|
const handleSectionDirtyChange = React.useCallback((sectionId, isDirty) => {
|
|
@@ -4762,11 +4815,14 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
|
|
|
4762
4815
|
// Form handle for onFormReady - allows host to validate and get all section data from its own Submit button
|
|
4763
4816
|
const formHandle = React.useMemo(() => {
|
|
4764
4817
|
const getValues = () => store.getState().widget?.values || {};
|
|
4765
|
-
const getNamespace = (section, index) =>
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4818
|
+
const getNamespace = (section, index) => {
|
|
4819
|
+
const ns = namespaceRef.current;
|
|
4820
|
+
return ns
|
|
4821
|
+
? typeof ns === 'string'
|
|
4822
|
+
? ns
|
|
4823
|
+
: ns(section['section-id'], index)
|
|
4824
|
+
: undefined;
|
|
4825
|
+
};
|
|
4770
4826
|
const checkNoUnsavedChanges = () => {
|
|
4771
4827
|
const hasDirty = Object.values(sectionDirtyMapRef.current).some(Boolean);
|
|
4772
4828
|
if (hasDirty) {
|
|
@@ -4816,7 +4872,7 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
|
|
|
4816
4872
|
return results;
|
|
4817
4873
|
},
|
|
4818
4874
|
};
|
|
4819
|
-
}, [store, dispatch, safeSections
|
|
4875
|
+
}, [store, dispatch, safeSections]);
|
|
4820
4876
|
// Call onFormReady when form is ready (sections loaded)
|
|
4821
4877
|
React.useEffect(() => {
|
|
4822
4878
|
if (onFormReady && safeSections.length > 0) {
|
|
@@ -8099,10 +8155,10 @@ const DisplayWidget = ({ config }) => {
|
|
|
8099
8155
|
const label = translateConfig(widgetConfig['widget-label']);
|
|
8100
8156
|
// If no label, render as paragraph text
|
|
8101
8157
|
if (!label || label.trim() === '') {
|
|
8102
|
-
return (jsxRuntimeExports.jsx("div", { className: "mb-3 text-base text-gray-700", title: String(displayValue ?? ''), children: displayValue }));
|
|
8158
|
+
return (jsxRuntimeExports.jsx("div", { className: "DisplayFieldWidget mb-3 min-w-0 w-full overflow-hidden text-ellipsis whitespace-nowrap text-base text-gray-700", title: String(displayValue ?? ''), children: displayValue }));
|
|
8103
8159
|
}
|
|
8104
|
-
// With label, render as key-value pair
|
|
8105
|
-
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] }), jsxRuntimeExports.jsx("div", { className: "flex-1 text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue })] }));
|
|
8160
|
+
// With label, render as key-value pair (structure matches other readonly widgets for SectionRenderer ellipsis)
|
|
8161
|
+
return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] DisplayFieldWidget flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] }), jsxRuntimeExports.jsx("div", { className: "flex-1 min-w-0", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
|
|
8106
8162
|
};
|
|
8107
8163
|
|
|
8108
8164
|
const TableCellSelect = ({ config, value, onValueChange }) => {
|
|
@@ -9070,7 +9126,18 @@ const HeaderSectionWidget = ({ config }) => {
|
|
|
9070
9126
|
result = searchIn(schemaData);
|
|
9071
9127
|
return result;
|
|
9072
9128
|
}, [paths, values, schemaData]);
|
|
9073
|
-
const
|
|
9129
|
+
const imageVal = findValue('image');
|
|
9130
|
+
const imageUrlVal = findValue('imageUrl');
|
|
9131
|
+
const [previewUrl, setPreviewUrl] = React.useState(null);
|
|
9132
|
+
React.useEffect(() => {
|
|
9133
|
+
if (imageVal instanceof File) {
|
|
9134
|
+
const url = URL.createObjectURL(imageVal);
|
|
9135
|
+
setPreviewUrl(url);
|
|
9136
|
+
return () => URL.revokeObjectURL(url);
|
|
9137
|
+
}
|
|
9138
|
+
setPreviewUrl(null);
|
|
9139
|
+
}, [imageVal]);
|
|
9140
|
+
const displayImageUrl = previewUrl || (typeof imageUrlVal === 'string' && imageUrlVal ? imageUrlVal : null);
|
|
9074
9141
|
const displayName = findValue('name') || '';
|
|
9075
9142
|
const functionalId = findValue('functionalId') || '';
|
|
9076
9143
|
const statusValue = findValue('status') || '';
|
|
@@ -9103,18 +9170,20 @@ const HeaderSectionWidget = ({ config }) => {
|
|
|
9103
9170
|
return opt ? opt.label : String(statusValue);
|
|
9104
9171
|
}, [statusValue, statusOptions]);
|
|
9105
9172
|
const statusColor = statusColors[String(statusValue).toLowerCase()] || '#6B7280';
|
|
9173
|
+
// ── Image edit helpers ───────────────────────────────────────
|
|
9174
|
+
const fileInputRef = React.useRef(null);
|
|
9175
|
+
const handleImageUpload = React.useCallback((e) => {
|
|
9176
|
+
const file = e.target.files?.[0];
|
|
9177
|
+
if (!file)
|
|
9178
|
+
return;
|
|
9179
|
+
updateFieldValue('image', file);
|
|
9180
|
+
e.target.value = '';
|
|
9181
|
+
}, [updateFieldValue]);
|
|
9182
|
+
const handleImageDelete = React.useCallback(() => {
|
|
9183
|
+
updateFieldValue('image', '');
|
|
9184
|
+
}, [updateFieldValue]);
|
|
9106
9185
|
// ── Scoped class for CSS isolation ────────────────────────────
|
|
9107
9186
|
const cls = `header-section-widget-${widgetConfig['widget-id']}`;
|
|
9108
|
-
// ── Indicator dot component ───────────────────────────────────
|
|
9109
|
-
const Dot = ({ color }) => (jsxRuntimeExports.jsx("span", { style: {
|
|
9110
|
-
display: 'inline-block',
|
|
9111
|
-
width: 8,
|
|
9112
|
-
height: 8,
|
|
9113
|
-
borderRadius: '50%',
|
|
9114
|
-
backgroundColor: color,
|
|
9115
|
-
flexShrink: 0,
|
|
9116
|
-
marginTop: 6,
|
|
9117
|
-
} }));
|
|
9118
9187
|
// ── RENDER ────────────────────────────────────────────────────
|
|
9119
9188
|
return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("style", { children: `
|
|
9120
9189
|
.${cls} {
|
|
@@ -9173,6 +9242,56 @@ const HeaderSectionWidget = ({ config }) => {
|
|
|
9173
9242
|
border-radius: 8px;
|
|
9174
9243
|
}
|
|
9175
9244
|
|
|
9245
|
+
.${cls} .hdr-avatar-wrapper {
|
|
9246
|
+
position: relative;
|
|
9247
|
+
width: ${imageSize}px;
|
|
9248
|
+
height: ${imageSize}px;
|
|
9249
|
+
flex-shrink: 0;
|
|
9250
|
+
}
|
|
9251
|
+
|
|
9252
|
+
.${cls} .hdr-avatar-overlay {
|
|
9253
|
+
position: absolute;
|
|
9254
|
+
inset: 0;
|
|
9255
|
+
border-radius: 8px;
|
|
9256
|
+
background: rgba(0, 0, 0, 0.55);
|
|
9257
|
+
display: flex;
|
|
9258
|
+
flex-direction: column;
|
|
9259
|
+
align-items: center;
|
|
9260
|
+
justify-content: center;
|
|
9261
|
+
gap: 6px;
|
|
9262
|
+
opacity: 0;
|
|
9263
|
+
transition: opacity 0.2s;
|
|
9264
|
+
}
|
|
9265
|
+
|
|
9266
|
+
.${cls} .hdr-avatar-wrapper:hover .hdr-avatar-overlay {
|
|
9267
|
+
opacity: 1;
|
|
9268
|
+
}
|
|
9269
|
+
|
|
9270
|
+
.${cls} .hdr-avatar-action {
|
|
9271
|
+
display: flex;
|
|
9272
|
+
align-items: center;
|
|
9273
|
+
gap: 5px;
|
|
9274
|
+
padding: 5px 14px;
|
|
9275
|
+
border: none;
|
|
9276
|
+
border-radius: 4px;
|
|
9277
|
+
background: rgba(255, 255, 255, 0.92);
|
|
9278
|
+
color: #374151;
|
|
9279
|
+
font-size: 0.7rem;
|
|
9280
|
+
font-weight: 500;
|
|
9281
|
+
cursor: pointer;
|
|
9282
|
+
font-family: Roboto, sans-serif;
|
|
9283
|
+
transition: background 0.15s;
|
|
9284
|
+
white-space: nowrap;
|
|
9285
|
+
}
|
|
9286
|
+
|
|
9287
|
+
.${cls} .hdr-avatar-action:hover {
|
|
9288
|
+
background: #fff;
|
|
9289
|
+
}
|
|
9290
|
+
|
|
9291
|
+
.${cls} .hdr-avatar-action--delete {
|
|
9292
|
+
color: #DC2626;
|
|
9293
|
+
}
|
|
9294
|
+
|
|
9176
9295
|
.${cls} .hdr-info {
|
|
9177
9296
|
display: flex;
|
|
9178
9297
|
flex-direction: column;
|
|
@@ -9277,13 +9396,13 @@ const HeaderSectionWidget = ({ config }) => {
|
|
|
9277
9396
|
min-width: 0;
|
|
9278
9397
|
}
|
|
9279
9398
|
}
|
|
9280
|
-
` }), jsxRuntimeExports.jsxs("div", { className: cls, children: [jsxRuntimeExports.jsxs("div", { className: "hdr-left", children: [jsxRuntimeExports.jsxs("div", { children: [
|
|
9399
|
+
` }), jsxRuntimeExports.jsxs("div", { className: cls, children: [jsxRuntimeExports.jsxs("div", { className: "hdr-left", children: [jsxRuntimeExports.jsxs("div", { className: "hdr-avatar-wrapper", children: [displayImageUrl ? (jsxRuntimeExports.jsx("img", { src: displayImageUrl, alt: displayName || 'Profile', className: "hdr-avatar", onError: (e) => {
|
|
9281
9400
|
e.target.style.display = 'none';
|
|
9282
9401
|
const placeholder = e.target
|
|
9283
9402
|
.parentElement?.querySelector('.hdr-avatar-placeholder');
|
|
9284
9403
|
if (placeholder)
|
|
9285
9404
|
placeholder.style.display = 'flex';
|
|
9286
|
-
} })) : null, jsxRuntimeExports.jsx("div", { className: "hdr-avatar-placeholder", style: { display:
|
|
9405
|
+
} })) : 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.jsx("input", { type: "text", className: "hdr-input", value: statusReason, placeholder: getLabel('enterReason'), onChange: (e) => updateFieldValue('statusReason', e.target.value) }))] })] })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-right", 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 || '-' })] })] })] })] }));
|
|
9287
9406
|
};
|
|
9288
9407
|
|
|
9289
9408
|
/**
|