@openg2p/registry-widgets 0.1.7 → 0.1.9
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.map +1 -1
- package/dist/components/SectionsContainer.d.ts.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.esm.js +607 -269
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +607 -268
- package/dist/index.js.map +1 -1
- package/dist/registry/defaultWidgets.d.ts.map +1 -1
- package/dist/utils/formatting.d.ts.map +1 -1
- package/dist/widgets/HeaderSectionWidget.d.ts +7 -0
- package/dist/widgets/HeaderSectionWidget.d.ts.map +1 -0
- package/dist/widgets/TableWidget.d.ts.map +1 -1
- package/dist/widgets/index.d.ts +1 -0
- package/dist/widgets/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -671,23 +671,235 @@ const getFormattedNumberLength = (value, format) => {
|
|
|
671
671
|
return formatted.length;
|
|
672
672
|
};
|
|
673
673
|
|
|
674
|
+
/**
|
|
675
|
+
* Date input utilities for parsing, formatting, and validation
|
|
676
|
+
*/
|
|
677
|
+
/**
|
|
678
|
+
* Parse date string in various formats to Date object
|
|
679
|
+
*/
|
|
680
|
+
const parseDate = (dateString) => {
|
|
681
|
+
if (!dateString)
|
|
682
|
+
return null;
|
|
683
|
+
// Try ISO format first (YYYY-MM-DD)
|
|
684
|
+
const isoMatch = dateString.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
685
|
+
if (isoMatch) {
|
|
686
|
+
const date = new Date(parseInt(isoMatch[1]), parseInt(isoMatch[2]) - 1, parseInt(isoMatch[3]));
|
|
687
|
+
if (!isNaN(date.getTime())) {
|
|
688
|
+
return date;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
// Try common formats
|
|
692
|
+
const date = new Date(dateString);
|
|
693
|
+
if (!isNaN(date.getTime())) {
|
|
694
|
+
return date;
|
|
695
|
+
}
|
|
696
|
+
return null;
|
|
697
|
+
};
|
|
698
|
+
/**
|
|
699
|
+
* Format date to ISO string (YYYY-MM-DD) for storage
|
|
700
|
+
*/
|
|
701
|
+
const formatDateToISO = (date) => {
|
|
702
|
+
if (!date)
|
|
703
|
+
return '';
|
|
704
|
+
let dateObj;
|
|
705
|
+
if (date instanceof Date) {
|
|
706
|
+
dateObj = date;
|
|
707
|
+
}
|
|
708
|
+
else if (typeof date === 'string') {
|
|
709
|
+
const parsed = parseDate(date);
|
|
710
|
+
if (!parsed)
|
|
711
|
+
return '';
|
|
712
|
+
dateObj = parsed;
|
|
713
|
+
}
|
|
714
|
+
else {
|
|
715
|
+
return '';
|
|
716
|
+
}
|
|
717
|
+
if (isNaN(dateObj.getTime()))
|
|
718
|
+
return '';
|
|
719
|
+
const year = dateObj.getFullYear();
|
|
720
|
+
const month = String(dateObj.getMonth() + 1).padStart(2, '0');
|
|
721
|
+
const day = String(dateObj.getDate()).padStart(2, '0');
|
|
722
|
+
return `${year}-${month}-${day}`;
|
|
723
|
+
};
|
|
724
|
+
/**
|
|
725
|
+
* Format date to custom format string
|
|
726
|
+
*/
|
|
727
|
+
const formatDateToString = (date, format) => {
|
|
728
|
+
if (!date || !format)
|
|
729
|
+
return '';
|
|
730
|
+
let dateObj;
|
|
731
|
+
if (date instanceof Date) {
|
|
732
|
+
dateObj = date;
|
|
733
|
+
}
|
|
734
|
+
else if (typeof date === 'string') {
|
|
735
|
+
const parsed = parseDate(date);
|
|
736
|
+
if (!parsed)
|
|
737
|
+
return '';
|
|
738
|
+
dateObj = parsed;
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
return '';
|
|
742
|
+
}
|
|
743
|
+
if (isNaN(dateObj.getTime()))
|
|
744
|
+
return '';
|
|
745
|
+
const day = String(dateObj.getDate()).padStart(2, '0');
|
|
746
|
+
const month = String(dateObj.getMonth() + 1).padStart(2, '0');
|
|
747
|
+
const year = dateObj.getFullYear();
|
|
748
|
+
const yearShort = year.toString().slice(-2);
|
|
749
|
+
return format
|
|
750
|
+
.replace(/DD/g, day)
|
|
751
|
+
.replace(/MM/g, month)
|
|
752
|
+
.replace(/YYYY/g, year.toString())
|
|
753
|
+
.replace(/YY/g, yearShort);
|
|
754
|
+
};
|
|
755
|
+
/**
|
|
756
|
+
* Parse custom format string to Date object
|
|
757
|
+
*/
|
|
758
|
+
const parseDateFromFormat = (dateString, format) => {
|
|
759
|
+
if (!dateString || !format)
|
|
760
|
+
return null;
|
|
761
|
+
// Extract day, month, year positions from format
|
|
762
|
+
const dayIndex = format.indexOf('DD');
|
|
763
|
+
const monthIndex = format.indexOf('MM');
|
|
764
|
+
const yearIndex = format.indexOf('YYYY');
|
|
765
|
+
const yearShortIndex = format.indexOf('YY');
|
|
766
|
+
if (dayIndex === -1 || monthIndex === -1 || (yearIndex === -1 && yearShortIndex === -1)) {
|
|
767
|
+
// Fallback to standard Date parsing
|
|
768
|
+
return parseDate(dateString);
|
|
769
|
+
}
|
|
770
|
+
try {
|
|
771
|
+
let day = '';
|
|
772
|
+
let month = '';
|
|
773
|
+
let year = '';
|
|
774
|
+
// Extract day (2 digits)
|
|
775
|
+
if (dayIndex !== -1) {
|
|
776
|
+
day = dateString.substring(dayIndex, dayIndex + 2);
|
|
777
|
+
}
|
|
778
|
+
// Extract month (2 digits)
|
|
779
|
+
if (monthIndex !== -1) {
|
|
780
|
+
month = dateString.substring(monthIndex, monthIndex + 2);
|
|
781
|
+
}
|
|
782
|
+
// Extract year (4 digits or 2 digits)
|
|
783
|
+
if (yearIndex !== -1) {
|
|
784
|
+
year = dateString.substring(yearIndex, yearIndex + 4);
|
|
785
|
+
}
|
|
786
|
+
else if (yearShortIndex !== -1) {
|
|
787
|
+
const yearShort = dateString.substring(yearShortIndex, yearShortIndex + 2);
|
|
788
|
+
const currentYear = new Date().getFullYear();
|
|
789
|
+
const currentCentury = Math.floor(currentYear / 100) * 100;
|
|
790
|
+
const parsedYear = parseInt(yearShort);
|
|
791
|
+
// Assume 20xx for years 00-50, 19xx for years 51-99
|
|
792
|
+
year = parsedYear <= 50
|
|
793
|
+
? (currentCentury + parsedYear).toString()
|
|
794
|
+
: (currentCentury - 100 + parsedYear).toString();
|
|
795
|
+
}
|
|
796
|
+
if (day && month && year) {
|
|
797
|
+
const date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
|
|
798
|
+
if (!isNaN(date.getTime())) {
|
|
799
|
+
return date;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
catch {
|
|
804
|
+
// Fallback to standard parsing
|
|
805
|
+
}
|
|
806
|
+
return parseDate(dateString);
|
|
807
|
+
};
|
|
808
|
+
/**
|
|
809
|
+
* Get min date based on constraint type
|
|
810
|
+
*/
|
|
811
|
+
const getMinDate = (constraint, minDate) => {
|
|
812
|
+
if (minDate) {
|
|
813
|
+
// If it's 'today', return today's date
|
|
814
|
+
if (minDate === 'today') {
|
|
815
|
+
return formatDateToISO(new Date());
|
|
816
|
+
}
|
|
817
|
+
return minDate;
|
|
818
|
+
}
|
|
819
|
+
if (constraint === 'past-only') {
|
|
820
|
+
// No minimum for past-only (can select any past date)
|
|
821
|
+
return undefined;
|
|
822
|
+
}
|
|
823
|
+
if (constraint === 'future-only') {
|
|
824
|
+
// Minimum is today for future-only
|
|
825
|
+
return formatDateToISO(new Date());
|
|
826
|
+
}
|
|
827
|
+
return undefined;
|
|
828
|
+
};
|
|
829
|
+
/**
|
|
830
|
+
* Get max date based on constraint type
|
|
831
|
+
*/
|
|
832
|
+
const getMaxDate = (constraint, maxDate) => {
|
|
833
|
+
if (maxDate) {
|
|
834
|
+
// If it's 'today', return today's date
|
|
835
|
+
if (maxDate === 'today') {
|
|
836
|
+
return formatDateToISO(new Date());
|
|
837
|
+
}
|
|
838
|
+
return maxDate;
|
|
839
|
+
}
|
|
840
|
+
if (constraint === 'past-only') {
|
|
841
|
+
// Maximum is today for past-only
|
|
842
|
+
return formatDateToISO(new Date());
|
|
843
|
+
}
|
|
844
|
+
if (constraint === 'future-only') {
|
|
845
|
+
// No maximum for future-only (can select any future date)
|
|
846
|
+
return undefined;
|
|
847
|
+
}
|
|
848
|
+
return undefined;
|
|
849
|
+
};
|
|
850
|
+
/**
|
|
851
|
+
* Validate date constraints
|
|
852
|
+
*/
|
|
853
|
+
const validateDateConstraints = (date, minDate, maxDate, constraint) => {
|
|
854
|
+
if (!date)
|
|
855
|
+
return null;
|
|
856
|
+
const dateObj = date instanceof Date ? date : parseDate(date);
|
|
857
|
+
if (!dateObj)
|
|
858
|
+
return null;
|
|
859
|
+
const dateISO = formatDateToISO(dateObj);
|
|
860
|
+
const todayISO = formatDateToISO(new Date());
|
|
861
|
+
// Check constraint type
|
|
862
|
+
if (constraint === 'past-only') {
|
|
863
|
+
if (dateISO > todayISO) {
|
|
864
|
+
return 'Date must be in the past';
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
else if (constraint === 'future-only') {
|
|
868
|
+
if (dateISO < todayISO) {
|
|
869
|
+
return 'Date must be in the future';
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
// Check minDate
|
|
873
|
+
const effectiveMinDate = getMinDate(constraint, minDate);
|
|
874
|
+
if (effectiveMinDate && dateISO < effectiveMinDate) {
|
|
875
|
+
return `Date must be on or after ${effectiveMinDate}`;
|
|
876
|
+
}
|
|
877
|
+
// Check maxDate
|
|
878
|
+
const effectiveMaxDate = getMaxDate(constraint, maxDate);
|
|
879
|
+
if (effectiveMaxDate && dateISO > effectiveMaxDate) {
|
|
880
|
+
return `Date must be on or before ${effectiveMaxDate}`;
|
|
881
|
+
}
|
|
882
|
+
return null;
|
|
883
|
+
};
|
|
884
|
+
|
|
674
885
|
/**
|
|
675
886
|
* Format date value
|
|
676
887
|
*/
|
|
677
888
|
const formatDate = (value, format) => {
|
|
678
|
-
if (!value
|
|
679
|
-
return
|
|
889
|
+
if (!value) {
|
|
890
|
+
return '';
|
|
680
891
|
}
|
|
892
|
+
const dateFormat = format?.dateFormat || 'DD-MM-YYYY';
|
|
681
893
|
try {
|
|
682
|
-
const date = new Date(value);
|
|
683
|
-
if (isNaN(date.getTime())) {
|
|
894
|
+
const date = typeof value === 'string' ? parseDate(value) : new Date(value);
|
|
895
|
+
if (!date || isNaN(date.getTime())) {
|
|
684
896
|
return value?.toString() || '';
|
|
685
897
|
}
|
|
686
898
|
// Simple date formatting (can be enhanced with date-fns or similar)
|
|
687
899
|
const day = date.getDate().toString().padStart(2, '0');
|
|
688
900
|
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
689
901
|
const year = date.getFullYear();
|
|
690
|
-
return
|
|
902
|
+
return dateFormat
|
|
691
903
|
.replace('DD', day)
|
|
692
904
|
.replace('MM', month)
|
|
693
905
|
.replace('YYYY', year.toString())
|
|
@@ -3623,8 +3835,6 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
3623
3835
|
return [];
|
|
3624
3836
|
const snapshot = {};
|
|
3625
3837
|
let hasTable = false;
|
|
3626
|
-
if (!sectionRegisterId)
|
|
3627
|
-
return [];
|
|
3628
3838
|
const resolvePath = (path) => (pathPrefix ? `${pathPrefix}.${path}` : path);
|
|
3629
3839
|
widgets.forEach(widget => {
|
|
3630
3840
|
const widgetPath = widget['widget-data-path'];
|
|
@@ -3654,9 +3864,11 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
3654
3864
|
: fullPath;
|
|
3655
3865
|
cleanedSnapshot[fieldPath] = value;
|
|
3656
3866
|
});
|
|
3657
|
-
const sectionData =
|
|
3658
|
-
?
|
|
3659
|
-
|
|
3867
|
+
const sectionData = sectionRegisterId
|
|
3868
|
+
? (pathPrefix
|
|
3869
|
+
? getValueByPath(sourceData, resolvePath(sectionRegisterId))
|
|
3870
|
+
: sourceData[sectionRegisterId])
|
|
3871
|
+
: {};
|
|
3660
3872
|
return [
|
|
3661
3873
|
{
|
|
3662
3874
|
...sectionData,
|
|
@@ -3808,7 +4020,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
3808
4020
|
if (JSON.stringify(oldSchemaData) !== JSON.stringify(newSchemaData)) {
|
|
3809
4021
|
try {
|
|
3810
4022
|
const sectionchanges = {
|
|
3811
|
-
section_id: dbSectionId,
|
|
4023
|
+
section_id: dbSectionId ?? originalSection['section-id'],
|
|
3812
4024
|
section_register_id: sectionRegisterId,
|
|
3813
4025
|
records: [...newSchemaData],
|
|
3814
4026
|
files: [...sectionFiles]
|
|
@@ -3823,49 +4035,52 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
3823
4035
|
};
|
|
3824
4036
|
// IntakeForm: save section then collapse current and expand next (or stay on final section)
|
|
3825
4037
|
const handleIntakeFormSave = React.useCallback(async () => {
|
|
3826
|
-
if (
|
|
3827
|
-
return;
|
|
3828
|
-
const sectionWidgets = collectWidgets(originalSection.panels);
|
|
3829
|
-
const currentState = store.getState().widget;
|
|
3830
|
-
const currentSchemaData = currentState.values || {};
|
|
3831
|
-
const isSectionValid = sectionValidate(originalSection, currentSchemaData, dispatch);
|
|
3832
|
-
if (!isSectionValid)
|
|
4038
|
+
if (sectionIndex === undefined)
|
|
3833
4039
|
return;
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
const
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
files: [...sectionFiles],
|
|
4040
|
+
// Only run save/validation logic when in draft mode and handlers are available
|
|
4041
|
+
if (isDraft !== false && store && onSectionSave) {
|
|
4042
|
+
const sectionWidgets = collectWidgets(originalSection.panels);
|
|
4043
|
+
const currentState = store.getState().widget;
|
|
4044
|
+
const currentSchemaData = currentState.values || {};
|
|
4045
|
+
const isSectionValid = sectionValidate(originalSection, currentSchemaData, dispatch);
|
|
4046
|
+
if (!isSectionValid)
|
|
4047
|
+
return;
|
|
4048
|
+
const oldSchemaData = schemaData || contextSchemaData;
|
|
4049
|
+
const newSchemaData = trackSectionChages(sectionWidgets, currentSchemaData, namespace);
|
|
4050
|
+
const sectionFiles = [];
|
|
4051
|
+
if (hasSupportingDocuments) {
|
|
4052
|
+
const originalSupportingDocuments = originalSection['section-supporting-documents'] || [];
|
|
4053
|
+
originalSupportingDocuments.forEach((doc) => {
|
|
4054
|
+
const originalDataPath = doc['document-data-path'];
|
|
4055
|
+
const storeDataPath = namespace && originalDataPath
|
|
4056
|
+
? `${namespace}.${originalDataPath}`
|
|
4057
|
+
: originalDataPath;
|
|
4058
|
+
sectionFiles.push(getValueByPath(currentSchemaData, storeDataPath));
|
|
3854
4059
|
});
|
|
3855
4060
|
}
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
4061
|
+
if (JSON.stringify(oldSchemaData) !== JSON.stringify(newSchemaData)) {
|
|
4062
|
+
try {
|
|
4063
|
+
await onSectionSave({
|
|
4064
|
+
section_id: dbSectionId ?? originalSection['section-id'],
|
|
4065
|
+
section_register_id: sectionRegisterId,
|
|
4066
|
+
records: [...newSchemaData],
|
|
4067
|
+
files: [...sectionFiles],
|
|
4068
|
+
});
|
|
4069
|
+
}
|
|
4070
|
+
catch (error) {
|
|
4071
|
+
console.error('Section Changes Save failed', error);
|
|
4072
|
+
return;
|
|
4073
|
+
}
|
|
3859
4074
|
}
|
|
4075
|
+
if (mode === 'IntakeForm') {
|
|
4076
|
+
baselineSnapshotRef.current = buildSectionSnapshot(currentSchemaData, namespace);
|
|
4077
|
+
setIntakeFormBaselineTrigger((prev) => prev + 1);
|
|
4078
|
+
}
|
|
4079
|
+
onSectionDirtyChange?.(sectionId, false);
|
|
3860
4080
|
}
|
|
3861
|
-
//
|
|
3862
|
-
if (mode === 'IntakeForm') {
|
|
3863
|
-
baselineSnapshotRef.current = buildSectionSnapshot(currentSchemaData, namespace);
|
|
3864
|
-
setIntakeFormBaselineTrigger((prev) => prev + 1);
|
|
3865
|
-
}
|
|
3866
|
-
onSectionDirtyChange?.(sectionId, false);
|
|
4081
|
+
// Always navigate to the next section
|
|
3867
4082
|
onSectionSaveSuccess?.(sectionIndex);
|
|
3868
|
-
}, [store, onSectionSave, onSectionSaveSuccess, sectionIndex, originalSection, schemaData, contextSchemaData, namespace, hasSupportingDocuments, dbSectionId, sectionRegisterId, dispatch, buildSectionSnapshot, sectionId, onSectionDirtyChange, mode]);
|
|
4083
|
+
}, [store, onSectionSave, onSectionSaveSuccess, sectionIndex, originalSection, schemaData, contextSchemaData, namespace, hasSupportingDocuments, dbSectionId, sectionRegisterId, dispatch, buildSectionSnapshot, sectionId, onSectionDirtyChange, mode, isDraft]);
|
|
3869
4084
|
// Handle cancel button click
|
|
3870
4085
|
const handleCancel = () => {
|
|
3871
4086
|
// Revert values in store to original schema data
|
|
@@ -4186,7 +4401,7 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4186
4401
|
gap: '12px',
|
|
4187
4402
|
marginBottom: '20px',
|
|
4188
4403
|
width: '100%',
|
|
4189
|
-
}, children: [jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => onPreviousSection?.(sectionIndex),
|
|
4404
|
+
}, children: [typeof sectionIndex === 'number' && sectionIndex > 0 && (jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => onPreviousSection?.(sectionIndex), className: "intake-form-prev-btn", style: {
|
|
4190
4405
|
fontFamily: 'Roboto, sans-serif',
|
|
4191
4406
|
fontSize: '14px',
|
|
4192
4407
|
fontWeight: 400,
|
|
@@ -4194,25 +4409,25 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
|
|
|
4194
4409
|
borderRadius: '10px',
|
|
4195
4410
|
border: '1px solid #FD8C3E',
|
|
4196
4411
|
background: '#F3F4F6',
|
|
4197
|
-
color:
|
|
4198
|
-
cursor:
|
|
4412
|
+
color: 'rgba(0, 0, 0, 0.5)',
|
|
4413
|
+
cursor: 'pointer',
|
|
4199
4414
|
display: 'inline-flex',
|
|
4200
4415
|
alignItems: 'center',
|
|
4201
4416
|
gap: '8px',
|
|
4202
|
-
}, children: [jsxRuntimeExports.jsx("img", { src: img$5, alt: "", "aria-hidden": true, style: { width: '14px', height: '14px', opacity:
|
|
4417
|
+
}, children: [jsxRuntimeExports.jsx("img", { src: img$5, alt: "", "aria-hidden": true, style: { width: '14px', height: '14px', opacity: 0.5 } }), translate('common.previous') || 'Prev'] })), jsxRuntimeExports.jsxs("button", { type: "button", onClick: handleIntakeFormSave, className: "intake-form-save-btn", style: {
|
|
4203
4418
|
fontFamily: 'Roboto, sans-serif',
|
|
4204
4419
|
fontSize: '14px',
|
|
4205
4420
|
fontWeight: 400,
|
|
4206
4421
|
padding: '8px 24px',
|
|
4207
4422
|
borderRadius: '10px',
|
|
4208
4423
|
border: '1px solid #FD8C3E',
|
|
4209
|
-
background:
|
|
4210
|
-
color:
|
|
4211
|
-
cursor:
|
|
4424
|
+
background: '#F3F4F6',
|
|
4425
|
+
color: 'rgba(0, 0, 0, 0.5)',
|
|
4426
|
+
cursor: 'pointer',
|
|
4212
4427
|
display: 'inline-flex',
|
|
4213
4428
|
alignItems: 'center',
|
|
4214
4429
|
gap: '8px',
|
|
4215
|
-
}, children: [translate('common.
|
|
4430
|
+
}, children: [translate('common.next') || 'Next', jsxRuntimeExports.jsx("img", { src: img$4, alt: "", "aria-hidden": true, style: { width: '14px', height: '14px' } })] })] })] }) }))] })) : (
|
|
4216
4431
|
/* RegistryView / CRView: standard layout */
|
|
4217
4432
|
jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [sectionToRender['section-title'] && (jsxRuntimeExports.jsxs("div", { style: {
|
|
4218
4433
|
marginTop: '35px',
|
|
@@ -4469,7 +4684,9 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
|
|
|
4469
4684
|
if (index + 1 < safeSections.length) {
|
|
4470
4685
|
setExpandedSectionIndex(index + 1);
|
|
4471
4686
|
}
|
|
4472
|
-
|
|
4687
|
+
else {
|
|
4688
|
+
setExpandedSectionIndex(null);
|
|
4689
|
+
}
|
|
4473
4690
|
}, [safeSections.length]);
|
|
4474
4691
|
// IntakeForm mode: called when Previous clicked - collapse current, expand previous
|
|
4475
4692
|
const handlePreviousSection = React.useCallback((index) => {
|
|
@@ -6797,217 +7014,6 @@ const BooleanWidget = ({ config }) => {
|
|
|
6797
7014
|
: '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: falseLabel })] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
|
|
6798
7015
|
};
|
|
6799
7016
|
|
|
6800
|
-
/**
|
|
6801
|
-
* Date input utilities for parsing, formatting, and validation
|
|
6802
|
-
*/
|
|
6803
|
-
/**
|
|
6804
|
-
* Parse date string in various formats to Date object
|
|
6805
|
-
*/
|
|
6806
|
-
const parseDate = (dateString) => {
|
|
6807
|
-
if (!dateString)
|
|
6808
|
-
return null;
|
|
6809
|
-
// Try ISO format first (YYYY-MM-DD)
|
|
6810
|
-
const isoMatch = dateString.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
6811
|
-
if (isoMatch) {
|
|
6812
|
-
const date = new Date(parseInt(isoMatch[1]), parseInt(isoMatch[2]) - 1, parseInt(isoMatch[3]));
|
|
6813
|
-
if (!isNaN(date.getTime())) {
|
|
6814
|
-
return date;
|
|
6815
|
-
}
|
|
6816
|
-
}
|
|
6817
|
-
// Try common formats
|
|
6818
|
-
const date = new Date(dateString);
|
|
6819
|
-
if (!isNaN(date.getTime())) {
|
|
6820
|
-
return date;
|
|
6821
|
-
}
|
|
6822
|
-
return null;
|
|
6823
|
-
};
|
|
6824
|
-
/**
|
|
6825
|
-
* Format date to ISO string (YYYY-MM-DD) for storage
|
|
6826
|
-
*/
|
|
6827
|
-
const formatDateToISO = (date) => {
|
|
6828
|
-
if (!date)
|
|
6829
|
-
return '';
|
|
6830
|
-
let dateObj;
|
|
6831
|
-
if (date instanceof Date) {
|
|
6832
|
-
dateObj = date;
|
|
6833
|
-
}
|
|
6834
|
-
else if (typeof date === 'string') {
|
|
6835
|
-
const parsed = parseDate(date);
|
|
6836
|
-
if (!parsed)
|
|
6837
|
-
return '';
|
|
6838
|
-
dateObj = parsed;
|
|
6839
|
-
}
|
|
6840
|
-
else {
|
|
6841
|
-
return '';
|
|
6842
|
-
}
|
|
6843
|
-
if (isNaN(dateObj.getTime()))
|
|
6844
|
-
return '';
|
|
6845
|
-
const year = dateObj.getFullYear();
|
|
6846
|
-
const month = String(dateObj.getMonth() + 1).padStart(2, '0');
|
|
6847
|
-
const day = String(dateObj.getDate()).padStart(2, '0');
|
|
6848
|
-
return `${year}-${month}-${day}`;
|
|
6849
|
-
};
|
|
6850
|
-
/**
|
|
6851
|
-
* Format date to custom format string
|
|
6852
|
-
*/
|
|
6853
|
-
const formatDateToString = (date, format) => {
|
|
6854
|
-
if (!date || !format)
|
|
6855
|
-
return '';
|
|
6856
|
-
let dateObj;
|
|
6857
|
-
if (date instanceof Date) {
|
|
6858
|
-
dateObj = date;
|
|
6859
|
-
}
|
|
6860
|
-
else if (typeof date === 'string') {
|
|
6861
|
-
const parsed = parseDate(date);
|
|
6862
|
-
if (!parsed)
|
|
6863
|
-
return '';
|
|
6864
|
-
dateObj = parsed;
|
|
6865
|
-
}
|
|
6866
|
-
else {
|
|
6867
|
-
return '';
|
|
6868
|
-
}
|
|
6869
|
-
if (isNaN(dateObj.getTime()))
|
|
6870
|
-
return '';
|
|
6871
|
-
const day = String(dateObj.getDate()).padStart(2, '0');
|
|
6872
|
-
const month = String(dateObj.getMonth() + 1).padStart(2, '0');
|
|
6873
|
-
const year = dateObj.getFullYear();
|
|
6874
|
-
const yearShort = year.toString().slice(-2);
|
|
6875
|
-
return format
|
|
6876
|
-
.replace(/DD/g, day)
|
|
6877
|
-
.replace(/MM/g, month)
|
|
6878
|
-
.replace(/YYYY/g, year.toString())
|
|
6879
|
-
.replace(/YY/g, yearShort);
|
|
6880
|
-
};
|
|
6881
|
-
/**
|
|
6882
|
-
* Parse custom format string to Date object
|
|
6883
|
-
*/
|
|
6884
|
-
const parseDateFromFormat = (dateString, format) => {
|
|
6885
|
-
if (!dateString || !format)
|
|
6886
|
-
return null;
|
|
6887
|
-
// Extract day, month, year positions from format
|
|
6888
|
-
const dayIndex = format.indexOf('DD');
|
|
6889
|
-
const monthIndex = format.indexOf('MM');
|
|
6890
|
-
const yearIndex = format.indexOf('YYYY');
|
|
6891
|
-
const yearShortIndex = format.indexOf('YY');
|
|
6892
|
-
if (dayIndex === -1 || monthIndex === -1 || (yearIndex === -1 && yearShortIndex === -1)) {
|
|
6893
|
-
// Fallback to standard Date parsing
|
|
6894
|
-
return parseDate(dateString);
|
|
6895
|
-
}
|
|
6896
|
-
try {
|
|
6897
|
-
let day = '';
|
|
6898
|
-
let month = '';
|
|
6899
|
-
let year = '';
|
|
6900
|
-
// Extract day (2 digits)
|
|
6901
|
-
if (dayIndex !== -1) {
|
|
6902
|
-
day = dateString.substring(dayIndex, dayIndex + 2);
|
|
6903
|
-
}
|
|
6904
|
-
// Extract month (2 digits)
|
|
6905
|
-
if (monthIndex !== -1) {
|
|
6906
|
-
month = dateString.substring(monthIndex, monthIndex + 2);
|
|
6907
|
-
}
|
|
6908
|
-
// Extract year (4 digits or 2 digits)
|
|
6909
|
-
if (yearIndex !== -1) {
|
|
6910
|
-
year = dateString.substring(yearIndex, yearIndex + 4);
|
|
6911
|
-
}
|
|
6912
|
-
else if (yearShortIndex !== -1) {
|
|
6913
|
-
const yearShort = dateString.substring(yearShortIndex, yearShortIndex + 2);
|
|
6914
|
-
const currentYear = new Date().getFullYear();
|
|
6915
|
-
const currentCentury = Math.floor(currentYear / 100) * 100;
|
|
6916
|
-
const parsedYear = parseInt(yearShort);
|
|
6917
|
-
// Assume 20xx for years 00-50, 19xx for years 51-99
|
|
6918
|
-
year = parsedYear <= 50
|
|
6919
|
-
? (currentCentury + parsedYear).toString()
|
|
6920
|
-
: (currentCentury - 100 + parsedYear).toString();
|
|
6921
|
-
}
|
|
6922
|
-
if (day && month && year) {
|
|
6923
|
-
const date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
|
|
6924
|
-
if (!isNaN(date.getTime())) {
|
|
6925
|
-
return date;
|
|
6926
|
-
}
|
|
6927
|
-
}
|
|
6928
|
-
}
|
|
6929
|
-
catch {
|
|
6930
|
-
// Fallback to standard parsing
|
|
6931
|
-
}
|
|
6932
|
-
return parseDate(dateString);
|
|
6933
|
-
};
|
|
6934
|
-
/**
|
|
6935
|
-
* Get min date based on constraint type
|
|
6936
|
-
*/
|
|
6937
|
-
const getMinDate = (constraint, minDate) => {
|
|
6938
|
-
if (minDate) {
|
|
6939
|
-
// If it's 'today', return today's date
|
|
6940
|
-
if (minDate === 'today') {
|
|
6941
|
-
return formatDateToISO(new Date());
|
|
6942
|
-
}
|
|
6943
|
-
return minDate;
|
|
6944
|
-
}
|
|
6945
|
-
if (constraint === 'past-only') {
|
|
6946
|
-
// No minimum for past-only (can select any past date)
|
|
6947
|
-
return undefined;
|
|
6948
|
-
}
|
|
6949
|
-
if (constraint === 'future-only') {
|
|
6950
|
-
// Minimum is today for future-only
|
|
6951
|
-
return formatDateToISO(new Date());
|
|
6952
|
-
}
|
|
6953
|
-
return undefined;
|
|
6954
|
-
};
|
|
6955
|
-
/**
|
|
6956
|
-
* Get max date based on constraint type
|
|
6957
|
-
*/
|
|
6958
|
-
const getMaxDate = (constraint, maxDate) => {
|
|
6959
|
-
if (maxDate) {
|
|
6960
|
-
// If it's 'today', return today's date
|
|
6961
|
-
if (maxDate === 'today') {
|
|
6962
|
-
return formatDateToISO(new Date());
|
|
6963
|
-
}
|
|
6964
|
-
return maxDate;
|
|
6965
|
-
}
|
|
6966
|
-
if (constraint === 'past-only') {
|
|
6967
|
-
// Maximum is today for past-only
|
|
6968
|
-
return formatDateToISO(new Date());
|
|
6969
|
-
}
|
|
6970
|
-
if (constraint === 'future-only') {
|
|
6971
|
-
// No maximum for future-only (can select any future date)
|
|
6972
|
-
return undefined;
|
|
6973
|
-
}
|
|
6974
|
-
return undefined;
|
|
6975
|
-
};
|
|
6976
|
-
/**
|
|
6977
|
-
* Validate date constraints
|
|
6978
|
-
*/
|
|
6979
|
-
const validateDateConstraints = (date, minDate, maxDate, constraint) => {
|
|
6980
|
-
if (!date)
|
|
6981
|
-
return null;
|
|
6982
|
-
const dateObj = date instanceof Date ? date : parseDate(date);
|
|
6983
|
-
if (!dateObj)
|
|
6984
|
-
return null;
|
|
6985
|
-
const dateISO = formatDateToISO(dateObj);
|
|
6986
|
-
const todayISO = formatDateToISO(new Date());
|
|
6987
|
-
// Check constraint type
|
|
6988
|
-
if (constraint === 'past-only') {
|
|
6989
|
-
if (dateISO > todayISO) {
|
|
6990
|
-
return 'Date must be in the past';
|
|
6991
|
-
}
|
|
6992
|
-
}
|
|
6993
|
-
else if (constraint === 'future-only') {
|
|
6994
|
-
if (dateISO < todayISO) {
|
|
6995
|
-
return 'Date must be in the future';
|
|
6996
|
-
}
|
|
6997
|
-
}
|
|
6998
|
-
// Check minDate
|
|
6999
|
-
const effectiveMinDate = getMinDate(constraint, minDate);
|
|
7000
|
-
if (effectiveMinDate && dateISO < effectiveMinDate) {
|
|
7001
|
-
return `Date must be on or after ${effectiveMinDate}`;
|
|
7002
|
-
}
|
|
7003
|
-
// Check maxDate
|
|
7004
|
-
const effectiveMaxDate = getMaxDate(constraint, maxDate);
|
|
7005
|
-
if (effectiveMaxDate && dateISO > effectiveMaxDate) {
|
|
7006
|
-
return `Date must be on or before ${effectiveMaxDate}`;
|
|
7007
|
-
}
|
|
7008
|
-
return null;
|
|
7009
|
-
};
|
|
7010
|
-
|
|
7011
7017
|
const DateInputWidget = ({ config }) => {
|
|
7012
7018
|
const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
|
|
7013
7019
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
@@ -8096,6 +8102,13 @@ const TableCellNumber = ({ config, value, onValueChange }) => {
|
|
|
8096
8102
|
};
|
|
8097
8103
|
return (jsxRuntimeExports.jsx("input", { type: "number", value: displayValue, onChange: handleChange, disabled: isReadonly, placeholder: placeholder, min: validationConfig?.min, max: validationConfig?.max, step: formatConfig?.decimalPlaces ? Math.pow(0.1, formatConfig.decimalPlaces) : undefined, className: `w-full h-[28px] px-2 text-sm border focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 text-right ${isReadonly ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'} border-gray-300`, style: { borderRadius: '10px' } }));
|
|
8098
8104
|
};
|
|
8105
|
+
const TableCellDate = ({ config, value, onValueChange }) => {
|
|
8106
|
+
const isReadonly = config['widget-readonly'] || false;
|
|
8107
|
+
const placeholder = config['widget-data-placeholder'] || '';
|
|
8108
|
+
// input type="date" requires YYYY-MM-DD format
|
|
8109
|
+
const displayValue = value && typeof value === 'string' ? value.split('T')[0] : '';
|
|
8110
|
+
return (jsxRuntimeExports.jsx("input", { type: "date", value: displayValue, onChange: (e) => onValueChange(e.target.value), disabled: isReadonly, placeholder: placeholder, className: `w-full h-[28px] px-2 text-sm border focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 ${isReadonly ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'} border-gray-300`, style: { borderRadius: '10px' } }));
|
|
8111
|
+
};
|
|
8099
8112
|
const TableWidget = ({ config }) => {
|
|
8100
8113
|
const { value, error, touched, isEnabled, onChange, config: widgetConfig, } = useBaseWidget({ config });
|
|
8101
8114
|
const { translate, translateConfig } = useWidgetTranslation();
|
|
@@ -8479,6 +8492,9 @@ const TableWidget = ({ config }) => {
|
|
|
8479
8492
|
else if (widgetType === 'number') {
|
|
8480
8493
|
return jsxRuntimeExports.jsx(TableCellNumber, { config: cellConfig, value: cellValue, onValueChange: (newValue) => updateCellValue(columnKey, newValue, rowIndex) });
|
|
8481
8494
|
}
|
|
8495
|
+
else if (widgetType === 'date') {
|
|
8496
|
+
return jsxRuntimeExports.jsx(TableCellDate, { config: cellConfig, value: cellValue, onValueChange: (newValue) => updateCellValue(columnKey, newValue, rowIndex) });
|
|
8497
|
+
}
|
|
8482
8498
|
// For other widget types, use WidgetRenderer but with compact styling
|
|
8483
8499
|
return (jsxRuntimeExports.jsx("div", { className: "table-cell-widget", style: { margin: 0, padding: 0 }, children: jsxRuntimeExports.jsx(WidgetRenderer, { config: cellConfig, schemaData: {
|
|
8484
8500
|
[cellWidgetId]: cellValue !== undefined ? cellValue : (column['widget-data-default'] ?? ''),
|
|
@@ -8872,6 +8888,326 @@ const TextAreaWidget = ({ config }) => {
|
|
|
8872
8888
|
}, children: charCounterText }))] }), hasError && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: errorMessage }))] })] }) }));
|
|
8873
8889
|
};
|
|
8874
8890
|
|
|
8891
|
+
const DEFAULT_STATUS_COLORS = {
|
|
8892
|
+
active: '#16A34A',
|
|
8893
|
+
inactive: '#D97706',
|
|
8894
|
+
archived: '#6B7280',
|
|
8895
|
+
};
|
|
8896
|
+
// ── Hook: load data-source options for a single field ────────────
|
|
8897
|
+
// Options are loaded in both view and edit modes because view mode
|
|
8898
|
+
// needs them to resolve display labels (e.g. "active" → "Active").
|
|
8899
|
+
// For API data sources, loading is deferred to edit mode to avoid
|
|
8900
|
+
// unnecessary network calls when only labels are needed.
|
|
8901
|
+
function useFieldDataSource(fieldKey, fieldConfig, isReadonly) {
|
|
8902
|
+
const [options, setOptions] = React.useState([]);
|
|
8903
|
+
const { dataSourceRequestHandler, schemaData } = useWidgetContext();
|
|
8904
|
+
const values = reactRedux.useSelector((state) => state.widget.values);
|
|
8905
|
+
const dataSource = fieldConfig?.['data-source'];
|
|
8906
|
+
const dsType = dataSource?.type;
|
|
8907
|
+
const dsKey = dataSource
|
|
8908
|
+
? `${fieldKey}-${dsType}-${JSON.stringify(dataSource)}`
|
|
8909
|
+
: '';
|
|
8910
|
+
React.useEffect(() => {
|
|
8911
|
+
if (!dataSource) {
|
|
8912
|
+
setOptions([]);
|
|
8913
|
+
return;
|
|
8914
|
+
}
|
|
8915
|
+
// API sources: only fetch when editable to avoid unnecessary calls
|
|
8916
|
+
if (dataSource.type === 'api' && isReadonly) {
|
|
8917
|
+
return;
|
|
8918
|
+
}
|
|
8919
|
+
let cancelled = false;
|
|
8920
|
+
const load = async () => {
|
|
8921
|
+
try {
|
|
8922
|
+
let raw = [];
|
|
8923
|
+
if (dataSource.type === 'static') {
|
|
8924
|
+
raw = getStaticDataSource(dataSource);
|
|
8925
|
+
}
|
|
8926
|
+
else if (dataSource.type === 'api') {
|
|
8927
|
+
if (!dataSourceRequestHandler)
|
|
8928
|
+
return;
|
|
8929
|
+
raw = await getApiDataSource(dataSource, values, dataSourceRequestHandler);
|
|
8930
|
+
}
|
|
8931
|
+
else if (dataSource.type === 'schema') {
|
|
8932
|
+
raw = getSchemaDataSource(dataSource, schemaData || {});
|
|
8933
|
+
}
|
|
8934
|
+
const transformed = transformDataSourceOptions(raw, dataSource.valueKey, dataSource.labelKey);
|
|
8935
|
+
if (!cancelled)
|
|
8936
|
+
setOptions(transformed);
|
|
8937
|
+
}
|
|
8938
|
+
catch (err) {
|
|
8939
|
+
console.error(`[HeaderSectionWidget] Error loading data-source for field "${fieldKey}":`, err);
|
|
8940
|
+
if (!cancelled)
|
|
8941
|
+
setOptions([]);
|
|
8942
|
+
}
|
|
8943
|
+
};
|
|
8944
|
+
load();
|
|
8945
|
+
return () => { cancelled = true; };
|
|
8946
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
8947
|
+
}, [dsKey, isReadonly, dataSourceRequestHandler]);
|
|
8948
|
+
return options;
|
|
8949
|
+
}
|
|
8950
|
+
// ── Main component ───────────────────────────────────────────────
|
|
8951
|
+
const HeaderSectionWidget = ({ config }) => {
|
|
8952
|
+
const { config: widgetConfig, getFieldValue, } = useBaseWidget({ config });
|
|
8953
|
+
const dispatch = reactRedux.useDispatch();
|
|
8954
|
+
const { translateConfig } = useWidgetTranslation();
|
|
8955
|
+
const { schemaData } = useWidgetContext();
|
|
8956
|
+
const values = reactRedux.useSelector((state) => state.widget.values);
|
|
8957
|
+
const isReadonly = widgetConfig['widget-readonly'] !== false;
|
|
8958
|
+
const dataPath = widgetConfig['widget-data-path'];
|
|
8959
|
+
// ── Per-field config map ──────────────────────────────────────
|
|
8960
|
+
const fieldConfigMap = React.useMemo(() => {
|
|
8961
|
+
return widgetConfig['widget-field-config'] || {};
|
|
8962
|
+
}, [widgetConfig['widget-field-config']]);
|
|
8963
|
+
// ── Load data source options for the status field ─────────────
|
|
8964
|
+
const statusOptions = useFieldDataSource('status', fieldConfigMap['status'], isReadonly);
|
|
8965
|
+
// ── Resolve field values ──────────────────────────────────────
|
|
8966
|
+
const paths = React.useMemo(() => {
|
|
8967
|
+
if (!dataPath || typeof dataPath !== 'object')
|
|
8968
|
+
return {};
|
|
8969
|
+
return dataPath;
|
|
8970
|
+
}, [dataPath]);
|
|
8971
|
+
const findValue = React.useCallback((fieldKey) => {
|
|
8972
|
+
const path = paths[fieldKey];
|
|
8973
|
+
if (!path)
|
|
8974
|
+
return undefined;
|
|
8975
|
+
const searchIn = (source) => {
|
|
8976
|
+
if (!source)
|
|
8977
|
+
return undefined;
|
|
8978
|
+
let v = getValueByPath(source, path);
|
|
8979
|
+
if (v !== undefined)
|
|
8980
|
+
return v;
|
|
8981
|
+
for (const obj of Object.values(source)) {
|
|
8982
|
+
if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
|
|
8983
|
+
v = getValueByPath(obj, path);
|
|
8984
|
+
if (v !== undefined)
|
|
8985
|
+
return v;
|
|
8986
|
+
}
|
|
8987
|
+
}
|
|
8988
|
+
return undefined;
|
|
8989
|
+
};
|
|
8990
|
+
let result = searchIn(values);
|
|
8991
|
+
if (result === undefined)
|
|
8992
|
+
result = searchIn(schemaData);
|
|
8993
|
+
return result;
|
|
8994
|
+
}, [paths, values, schemaData]);
|
|
8995
|
+
const imageUrl = findValue('image') || null;
|
|
8996
|
+
const displayName = findValue('name') || '';
|
|
8997
|
+
const functionalId = findValue('functionalId') || '';
|
|
8998
|
+
const statusValue = findValue('status') || '';
|
|
8999
|
+
const statusReason = findValue('statusReason') || '';
|
|
9000
|
+
const createdBy = findValue('createdBy') || '';
|
|
9001
|
+
const createdAt = findValue('createdAt') || '';
|
|
9002
|
+
const lastApprovedBy = findValue('lastApprovedBy') || '';
|
|
9003
|
+
const lastApprovedAt = findValue('lastApprovedAt') || '';
|
|
9004
|
+
// ── Format options ────────────────────────────────────────────
|
|
9005
|
+
const format = (widgetConfig['widget-data-format'] || {});
|
|
9006
|
+
const imageSize = format.imageSize || 90;
|
|
9007
|
+
const nameColor = format.nameColor || '#ED7C22';
|
|
9008
|
+
const statusColors = {
|
|
9009
|
+
...DEFAULT_STATUS_COLORS,
|
|
9010
|
+
...(format.statusColors || {}),
|
|
9011
|
+
};
|
|
9012
|
+
// ── Value change helpers ──────────────────────────────────────
|
|
9013
|
+
const updateFieldValue = React.useCallback((fieldKey, newValue) => {
|
|
9014
|
+
const path = paths[fieldKey];
|
|
9015
|
+
if (!path)
|
|
9016
|
+
return;
|
|
9017
|
+
const updated = setValueByPath({ ...values }, path, newValue);
|
|
9018
|
+
dispatch(setValues(updated));
|
|
9019
|
+
}, [paths, values, dispatch]);
|
|
9020
|
+
// ── Status helpers ────────────────────────────────────────────
|
|
9021
|
+
const statusLabel = React.useMemo(() => {
|
|
9022
|
+
if (!statusValue)
|
|
9023
|
+
return '';
|
|
9024
|
+
const opt = statusOptions.find((o) => String(o.value).toLowerCase() === String(statusValue).toLowerCase());
|
|
9025
|
+
return opt ? opt.label : String(statusValue);
|
|
9026
|
+
}, [statusValue, statusOptions]);
|
|
9027
|
+
const statusColor = statusColors[String(statusValue).toLowerCase()] || '#6B7280';
|
|
9028
|
+
// ── Scoped class for CSS isolation ────────────────────────────
|
|
9029
|
+
const cls = `header-section-widget-${widgetConfig['widget-id']}`;
|
|
9030
|
+
// ── Indicator dot component ───────────────────────────────────
|
|
9031
|
+
const Dot = ({ color }) => (jsxRuntimeExports.jsx("span", { style: {
|
|
9032
|
+
display: 'inline-block',
|
|
9033
|
+
width: 8,
|
|
9034
|
+
height: 8,
|
|
9035
|
+
borderRadius: '50%',
|
|
9036
|
+
backgroundColor: color,
|
|
9037
|
+
flexShrink: 0,
|
|
9038
|
+
marginTop: 6,
|
|
9039
|
+
} }));
|
|
9040
|
+
// ── RENDER ────────────────────────────────────────────────────
|
|
9041
|
+
return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("style", { children: `
|
|
9042
|
+
.${cls} {
|
|
9043
|
+
display: flex;
|
|
9044
|
+
flex-direction: row;
|
|
9045
|
+
gap: 1.5rem;
|
|
9046
|
+
width: 100%;
|
|
9047
|
+
font-family: Roboto, sans-serif;
|
|
9048
|
+
padding: 35px 0 16px 0;
|
|
9049
|
+
}
|
|
9050
|
+
|
|
9051
|
+
.${cls} .hdr-left {
|
|
9052
|
+
display: flex;
|
|
9053
|
+
flex-direction: row;
|
|
9054
|
+
align-items: flex-start;
|
|
9055
|
+
gap: 1rem;
|
|
9056
|
+
flex: 1 1 55%;
|
|
9057
|
+
min-width: 0;
|
|
9058
|
+
}
|
|
9059
|
+
|
|
9060
|
+
.${cls} .hdr-right {
|
|
9061
|
+
display: flex;
|
|
9062
|
+
flex-direction: column;
|
|
9063
|
+
gap: 0.5rem;
|
|
9064
|
+
flex: 0 0 auto;
|
|
9065
|
+
min-width: 220px;
|
|
9066
|
+
}
|
|
9067
|
+
|
|
9068
|
+
.${cls} .hdr-avatar {
|
|
9069
|
+
width: ${imageSize}px;
|
|
9070
|
+
height: ${imageSize}px;
|
|
9071
|
+
border-radius: 8px;
|
|
9072
|
+
object-fit: cover;
|
|
9073
|
+
background-color: #e5e7eb;
|
|
9074
|
+
border: 2px solid #d1d5db;
|
|
9075
|
+
flex-shrink: 0;
|
|
9076
|
+
}
|
|
9077
|
+
|
|
9078
|
+
.${cls} .hdr-avatar-placeholder {
|
|
9079
|
+
width: ${imageSize}px;
|
|
9080
|
+
height: ${imageSize}px;
|
|
9081
|
+
border-radius: 8px;
|
|
9082
|
+
background-color: #e5e7eb;
|
|
9083
|
+
border: 2px solid #d1d5db;
|
|
9084
|
+
display: flex;
|
|
9085
|
+
align-items: center;
|
|
9086
|
+
justify-content: center;
|
|
9087
|
+
flex-shrink: 0;
|
|
9088
|
+
overflow: hidden;
|
|
9089
|
+
}
|
|
9090
|
+
|
|
9091
|
+
.${cls} .hdr-avatar-placeholder img {
|
|
9092
|
+
width: 100%;
|
|
9093
|
+
height: 100%;
|
|
9094
|
+
object-fit: cover;
|
|
9095
|
+
border-radius: 8px;
|
|
9096
|
+
}
|
|
9097
|
+
|
|
9098
|
+
.${cls} .hdr-info {
|
|
9099
|
+
display: flex;
|
|
9100
|
+
flex-direction: column;
|
|
9101
|
+
gap: 0.35rem;
|
|
9102
|
+
min-width: 0;
|
|
9103
|
+
flex: 1;
|
|
9104
|
+
}
|
|
9105
|
+
|
|
9106
|
+
.${cls} .hdr-name {
|
|
9107
|
+
font-size: 1.25rem;
|
|
9108
|
+
font-weight: 600;
|
|
9109
|
+
color: ${nameColor};
|
|
9110
|
+
line-height: 1.4;
|
|
9111
|
+
word-wrap: break-word;
|
|
9112
|
+
}
|
|
9113
|
+
|
|
9114
|
+
.${cls} .hdr-field-row {
|
|
9115
|
+
display: flex;
|
|
9116
|
+
align-items: flex-start;
|
|
9117
|
+
gap: 0.5rem;
|
|
9118
|
+
font-size: 0.875rem;
|
|
9119
|
+
line-height: 1.6;
|
|
9120
|
+
}
|
|
9121
|
+
|
|
9122
|
+
.${cls} .hdr-field-label {
|
|
9123
|
+
color: #6b7280;
|
|
9124
|
+
font-weight: 500;
|
|
9125
|
+
white-space: nowrap;
|
|
9126
|
+
}
|
|
9127
|
+
|
|
9128
|
+
.${cls} .hdr-field-value {
|
|
9129
|
+
color: #111827;
|
|
9130
|
+
font-weight: 600;
|
|
9131
|
+
}
|
|
9132
|
+
|
|
9133
|
+
.${cls} .hdr-status-badge {
|
|
9134
|
+
display: inline-block;
|
|
9135
|
+
padding: 2px 12px;
|
|
9136
|
+
border-radius: 4px;
|
|
9137
|
+
font-size: 0.75rem;
|
|
9138
|
+
font-weight: 600;
|
|
9139
|
+
color: #fff;
|
|
9140
|
+
}
|
|
9141
|
+
|
|
9142
|
+
.${cls} .hdr-meta-row {
|
|
9143
|
+
display: flex;
|
|
9144
|
+
align-items: baseline;
|
|
9145
|
+
gap: 0.35rem;
|
|
9146
|
+
font-size: 0.875rem;
|
|
9147
|
+
line-height: 1.6;
|
|
9148
|
+
}
|
|
9149
|
+
|
|
9150
|
+
.${cls} .hdr-meta-label {
|
|
9151
|
+
color: #6b7280;
|
|
9152
|
+
font-weight: 400;
|
|
9153
|
+
}
|
|
9154
|
+
|
|
9155
|
+
.${cls} .hdr-meta-value {
|
|
9156
|
+
color: #111827;
|
|
9157
|
+
font-weight: 600;
|
|
9158
|
+
}
|
|
9159
|
+
|
|
9160
|
+
.${cls} .hdr-select {
|
|
9161
|
+
height: 32px;
|
|
9162
|
+
padding: 0 8px;
|
|
9163
|
+
border: 1px solid #d1d5db;
|
|
9164
|
+
border-radius: 6px;
|
|
9165
|
+
font-size: 0.875rem;
|
|
9166
|
+
font-family: Roboto, sans-serif;
|
|
9167
|
+
background: #fff;
|
|
9168
|
+
min-width: 140px;
|
|
9169
|
+
color: #374151;
|
|
9170
|
+
}
|
|
9171
|
+
.${cls} .hdr-select:focus {
|
|
9172
|
+
outline: none;
|
|
9173
|
+
border-color: #ED7C22;
|
|
9174
|
+
box-shadow: 0 0 0 2px rgba(237, 124, 34, 0.15);
|
|
9175
|
+
}
|
|
9176
|
+
|
|
9177
|
+
.${cls} .hdr-input {
|
|
9178
|
+
height: 32px;
|
|
9179
|
+
padding: 0 8px;
|
|
9180
|
+
border: 1px solid #d1d5db;
|
|
9181
|
+
border-radius: 6px;
|
|
9182
|
+
font-size: 0.875rem;
|
|
9183
|
+
font-family: Roboto, sans-serif;
|
|
9184
|
+
background: #fff;
|
|
9185
|
+
min-width: 140px;
|
|
9186
|
+
color: #374151;
|
|
9187
|
+
}
|
|
9188
|
+
.${cls} .hdr-input:focus {
|
|
9189
|
+
outline: none;
|
|
9190
|
+
border-color: #ED7C22;
|
|
9191
|
+
box-shadow: 0 0 0 2px rgba(237, 124, 34, 0.15);
|
|
9192
|
+
}
|
|
9193
|
+
|
|
9194
|
+
@media (max-width: 768px) {
|
|
9195
|
+
.${cls} {
|
|
9196
|
+
flex-direction: column;
|
|
9197
|
+
}
|
|
9198
|
+
.${cls} .hdr-right {
|
|
9199
|
+
min-width: 0;
|
|
9200
|
+
}
|
|
9201
|
+
}
|
|
9202
|
+
` }), jsxRuntimeExports.jsxs("div", { className: cls, children: [jsxRuntimeExports.jsxs("div", { className: "hdr-left", children: [jsxRuntimeExports.jsxs("div", { children: [imageUrl ? (jsxRuntimeExports.jsx("img", { src: imageUrl, alt: displayName || 'Profile', className: "hdr-avatar", onError: (e) => {
|
|
9203
|
+
e.target.style.display = 'none';
|
|
9204
|
+
const placeholder = e.target
|
|
9205
|
+
.parentElement?.querySelector('.hdr-avatar-placeholder');
|
|
9206
|
+
if (placeholder)
|
|
9207
|
+
placeholder.style.display = 'flex';
|
|
9208
|
+
} })) : null, jsxRuntimeExports.jsx("div", { className: "hdr-avatar-placeholder", style: { display: imageUrl ? 'none' : 'flex' }, children: jsxRuntimeExports.jsx("img", { src: img$f, alt: "Profile Placeholder" }) })] }), 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.jsx(Dot, { color: "#9CA3AF" }), jsxRuntimeExports.jsxs("span", { className: "hdr-field-label", children: [translateConfig('Functional Record ID') || 'Functional Record ID', " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-field-value", children: functionalId || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsx(Dot, { color: isReadonly ? statusColor : '#F59E0B' }), jsxRuntimeExports.jsx("span", { className: "hdr-field-label", children: translateConfig('Record Status') || 'Record 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: translateConfig('Select') || 'Select' }), statusOptions.map((opt) => (jsxRuntimeExports.jsx("option", { value: opt.value, children: opt.label }, opt.value)))] }))] }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsx(Dot, { color: isReadonly ? '#9CA3AF' : '#F59E0B' }), jsxRuntimeExports.jsxs("span", { className: "hdr-field-label", children: [translateConfig('Status Reason') || 'Status Reason', " :"] }), isReadonly ? (jsxRuntimeExports.jsx("span", { className: "hdr-field-value", children: statusReason || '-' })) : (jsxRuntimeExports.jsx("input", { type: "text", className: "hdr-input", value: statusReason, placeholder: translateConfig('Enter Reason') || 'Enter Reason', 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: [translateConfig('Created by') || 'Created by', " :"] }), 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: [translateConfig('Created at') || 'Created at', " :"] }), 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: [translateConfig('Last Approved by') || 'Last Approved by', " :"] }), 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: [translateConfig('Last Approved at') || 'Last Approved at', " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: lastApprovedAt || '-' })] })] })] })] }));
|
|
9209
|
+
};
|
|
9210
|
+
|
|
8875
9211
|
/**
|
|
8876
9212
|
* Register all default/generic widgets
|
|
8877
9213
|
* This is called automatically when the package is imported
|
|
@@ -8911,6 +9247,8 @@ const registerDefaultWidgets = () => {
|
|
|
8911
9247
|
widgetRegistry.register({ widget: 'display', component: DisplayWidget });
|
|
8912
9248
|
// Profile widget for displaying user identity (image, name, ID)
|
|
8913
9249
|
widgetRegistry.register({ widget: 'profile', component: ProfileWidget });
|
|
9250
|
+
// Header section widget for full-width registry header with profile, status, and metadata
|
|
9251
|
+
widgetRegistry.register({ widget: 'header-section', component: HeaderSectionWidget });
|
|
8914
9252
|
};
|
|
8915
9253
|
// Auto-register on import
|
|
8916
9254
|
registerDefaultWidgets();
|
|
@@ -9276,6 +9614,7 @@ exports.DateInputWidget = DateInputWidget;
|
|
|
9276
9614
|
exports.DateTimeInputWidget = DateTimeInputWidget;
|
|
9277
9615
|
exports.DisplayWidget = DisplayWidget;
|
|
9278
9616
|
exports.FileInputWidget = FileInputWidget;
|
|
9617
|
+
exports.HeaderSectionWidget = HeaderSectionWidget;
|
|
9279
9618
|
exports.IterableAccordionWidget = IterableAccordionWidget;
|
|
9280
9619
|
exports.JSONEditorPanel = JSONEditorPanel;
|
|
9281
9620
|
exports.NumberInputWidget = NumberInputWidget;
|