@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/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 || !format?.dateFormat) {
679
- return value?.toString() || '';
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 format.dateFormat
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 = pathPrefix
3658
- ? getValueByPath(sourceData, resolvePath(sectionRegisterId))
3659
- : sourceData[sectionRegisterId];
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 (!store || !onSectionSave || sectionIndex === undefined)
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
- const oldSchemaData = schemaData || contextSchemaData;
3835
- const newSchemaData = trackSectionChages(sectionWidgets, currentSchemaData, namespace);
3836
- const sectionFiles = [];
3837
- if (hasSupportingDocuments) {
3838
- const originalSupportingDocuments = originalSection['section-supporting-documents'] || [];
3839
- originalSupportingDocuments.forEach((doc) => {
3840
- const originalDataPath = doc['document-data-path'];
3841
- const storeDataPath = namespace && originalDataPath
3842
- ? `${namespace}.${originalDataPath}`
3843
- : originalDataPath;
3844
- sectionFiles.push(getValueByPath(currentSchemaData, storeDataPath));
3845
- });
3846
- }
3847
- if (JSON.stringify(oldSchemaData) !== JSON.stringify(newSchemaData)) {
3848
- try {
3849
- await onSectionSave({
3850
- section_id: dbSectionId,
3851
- section_register_id: sectionRegisterId,
3852
- records: [...newSchemaData],
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
- catch (error) {
3857
- console.error('Section Changes Save failed', error);
3858
- return;
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
- // IntakeForm only: update baseline so section is no longer dirty; trigger re-render so badge updates to "Saved"
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), disabled: sectionIndex === 0, className: "intake-form-prev-btn", style: {
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: sectionIndex === 0 ? 'rgba(0, 0, 0, 0.3)' : 'rgba(0, 0, 0, 0.5)',
4198
- cursor: sectionIndex === 0 ? 'not-allowed' : 'pointer',
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: sectionIndex === 0 ? 0.5 : 0.5 } }), translate('common.previous') || 'Prev'] }), jsxRuntimeExports.jsxs("button", { type: "button", onClick: handleIntakeFormSave, disabled: isDraft === false, className: "intake-form-save-btn", style: {
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: isDraft === false ? '#9CA3AF' : '#F3F4F6',
4210
- color: isDraft === false ? 'rgba(0, 0, 0, 0.3)' : 'rgba(0, 0, 0, 0.5)',
4211
- cursor: isDraft === false ? 'not-allowed' : 'pointer',
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.save') || 'Save', jsxRuntimeExports.jsx("img", { src: img$4, alt: "", "aria-hidden": true, style: { width: '14px', height: '14px' } })] })] })] }) }))] })) : (
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
- // Last section: stay expanded (no action)
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;