@openg2p/registry-widgets 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createSlice, configureStore } from '@reduxjs/toolkit';
2
- import React, { useContext, createContext, useMemo, useEffect, useRef, useCallback, useState } from 'react';
2
+ import React, { useContext, createContext, useMemo, useEffect, useRef, useCallback, useState, useId } from 'react';
3
3
  import { Provider, useDispatch, useSelector, useStore } from 'react-redux';
4
4
  import { z } from 'zod';
5
5
  import { createPortal } from 'react-dom';
@@ -807,6 +807,40 @@ const parseDateFromFormat = (dateString, format) => {
807
807
  }
808
808
  return parseDate(dateString);
809
809
  };
810
+ /**
811
+ * Resolve a stored date value (ISO or parseable string) to YYYY-MM-DD for comparisons.
812
+ */
813
+ const resolveDateBoundFromFieldValue = (fieldValue) => {
814
+ if (fieldValue == null || fieldValue === '') {
815
+ return undefined;
816
+ }
817
+ const iso = formatDateToISO(fieldValue);
818
+ return iso || undefined;
819
+ };
820
+ /**
821
+ * Pick the stricter (later) minimum when combining static and field-based bounds.
822
+ */
823
+ const mergeMinDateBounds = (boundA, boundB) => {
824
+ if (!boundA) {
825
+ return boundB;
826
+ }
827
+ if (!boundB) {
828
+ return boundA;
829
+ }
830
+ return boundA > boundB ? boundA : boundB;
831
+ };
832
+ /**
833
+ * Pick the stricter (earlier) maximum when combining static and field-based bounds.
834
+ */
835
+ const mergeMaxDateBounds = (boundA, boundB) => {
836
+ if (!boundA) {
837
+ return boundB;
838
+ }
839
+ if (!boundB) {
840
+ return boundA;
841
+ }
842
+ return boundA < boundB ? boundA : boundB;
843
+ };
810
844
  /**
811
845
  * Get min date based on constraint type
812
846
  */
@@ -849,10 +883,7 @@ const getMaxDate = (constraint, maxDate) => {
849
883
  }
850
884
  return undefined;
851
885
  };
852
- /**
853
- * Validate date constraints
854
- */
855
- const validateDateConstraints = (date, minDate, maxDate, constraint) => {
886
+ const validateDateConstraints = (date, minDate, maxDate, constraint, messages) => {
856
887
  if (!date)
857
888
  return null;
858
889
  const dateObj = date instanceof Date ? date : parseDate(date);
@@ -871,15 +902,12 @@ const validateDateConstraints = (date, minDate, maxDate, constraint) => {
871
902
  return 'Date must be in the future';
872
903
  }
873
904
  }
874
- // Check minDate
875
- const effectiveMinDate = getMinDate(constraint, minDate);
876
- if (effectiveMinDate && dateISO < effectiveMinDate) {
877
- return `Date must be on or after ${effectiveMinDate}`;
905
+ // minDate / maxDate are effective bounds (static + field-based), resolved by the caller
906
+ if (minDate && dateISO < minDate) {
907
+ return messages?.minDateMessage ?? `Date must be on or after ${minDate}`;
878
908
  }
879
- // Check maxDate
880
- const effectiveMaxDate = getMaxDate(constraint, maxDate);
881
- if (effectiveMaxDate && dateISO > effectiveMaxDate) {
882
- return `Date must be on or before ${effectiveMaxDate}`;
909
+ if (maxDate && dateISO > maxDate) {
910
+ return messages?.maxDateMessage ?? `Date must be on or before ${maxDate}`;
883
911
  }
884
912
  return null;
885
913
  };
@@ -3793,6 +3821,116 @@ const namespaceSectionConfig = (section, namespace) => {
3793
3821
  return namespaced;
3794
3822
  };
3795
3823
 
3824
+ /** Table-style widgets that bind to an array path in the store / schema. */
3825
+ function isTableLikeWidget(widget) {
3826
+ const w = widget.widget;
3827
+ /** widget-type union in types omits legacy values like simple-table still used at runtime */
3828
+ const t = widget['widget-type'];
3829
+ return (w === 'table' ||
3830
+ w === 'dialog-table' ||
3831
+ w === 'simple-table' ||
3832
+ t === 'table' ||
3833
+ t === 'simple-table');
3834
+ }
3835
+ /**
3836
+ * Resolve `records` for section save payloads.
3837
+ * - Back-compat: path ending in `.records` (e.g. `regId.records`)
3838
+ * - Else: first array snapshot at a string `widget-data-path` on a table-like widget
3839
+ * (e.g. `household.members` for dialog-table)
3840
+ */
3841
+ function extractTableRecordsFromSnapshot(snapshot, sectionWidgets) {
3842
+ const convention = Object.entries(snapshot).find(([key, value]) => key.endsWith('.records') && Array.isArray(value));
3843
+ if (convention) {
3844
+ return convention[1];
3845
+ }
3846
+ const tablePaths = [];
3847
+ sectionWidgets.forEach((widget) => {
3848
+ if (!isTableLikeWidget(widget))
3849
+ return;
3850
+ const p = widget['widget-data-path'];
3851
+ if (typeof p === 'string' && p.length > 0) {
3852
+ tablePaths.push(p);
3853
+ }
3854
+ else if (p && typeof p === 'object') {
3855
+ Object.values(p).forEach((sub) => {
3856
+ if (typeof sub === 'string' && sub.length > 0)
3857
+ tablePaths.push(sub);
3858
+ });
3859
+ }
3860
+ });
3861
+ for (const path of tablePaths) {
3862
+ const val = snapshot[path];
3863
+ if (Array.isArray(val)) {
3864
+ return val;
3865
+ }
3866
+ }
3867
+ return [];
3868
+ }
3869
+
3870
+ const isColumnRequired = (column, skipRequired) => {
3871
+ if (skipRequired)
3872
+ return false;
3873
+ const validation = column['widget-data-validation'];
3874
+ return !!(column['widget-required'] || validation?.required);
3875
+ };
3876
+ const validateTableLikeWidget = (widget, currentSchemaData, dispatch, skipRequired) => {
3877
+ const widgetId = widget['widget-id'];
3878
+ if (!widgetId)
3879
+ return true;
3880
+ const columns = (widget['widget-data-columns'] || []);
3881
+ const value = getWidgetValue(currentSchemaData, widget['widget-data-path'], widgetId);
3882
+ const rows = Array.isArray(value)
3883
+ ? value
3884
+ : [];
3885
+ const activeRows = rows.filter((row) => row?.edit_action !== 'DELETE');
3886
+ const rowErrors = [];
3887
+ let isValid = true;
3888
+ if (!skipRequired && widget['widget-required'] && activeRows.length === 0) {
3889
+ dispatch(setTouched({ widgetId, touched: true }));
3890
+ dispatch(setError({ widgetId, errors: ['At least one record is required'] }));
3891
+ return false;
3892
+ }
3893
+ const hasRequiredColumns = columns.some((col) => isColumnRequired(col, skipRequired));
3894
+ if (!skipRequired && hasRequiredColumns && activeRows.length === 0) {
3895
+ dispatch(setTouched({ widgetId, touched: true }));
3896
+ dispatch(setError({
3897
+ widgetId,
3898
+ errors: ['Add at least one record and fill all required fields'],
3899
+ }));
3900
+ return false;
3901
+ }
3902
+ activeRows.forEach((row, rowIndex) => {
3903
+ columns.forEach((col) => {
3904
+ if (col['widget-readonly'])
3905
+ return;
3906
+ const key = col['column-key'];
3907
+ if (!key)
3908
+ return;
3909
+ const required = isColumnRequired(col, skipRequired);
3910
+ const cellValue = row[key];
3911
+ const errors = validateWidget(cellValue, col['widget-data-validation'], required, skipRequired);
3912
+ if (errors.length > 0) {
3913
+ isValid = false;
3914
+ const label = col['widget-label'] || key;
3915
+ rowErrors.push(`Row ${rowIndex + 1}, ${label}: ${errors[0]}`);
3916
+ }
3917
+ });
3918
+ });
3919
+ if (!isValid) {
3920
+ dispatch(setTouched({ widgetId, touched: true }));
3921
+ dispatch(setError({
3922
+ widgetId,
3923
+ errors: rowErrors.length > 0
3924
+ ? rowErrors.slice(0, 5)
3925
+ : ['Please fix required fields in the table'],
3926
+ }));
3927
+ }
3928
+ else {
3929
+ dispatch(setTouched({ widgetId, touched: false }));
3930
+ dispatch(setError({ widgetId, errors: [] }));
3931
+ }
3932
+ return isValid;
3933
+ };
3796
3934
  const collectWidgets = (panels) => {
3797
3935
  let widgets = [];
3798
3936
  panels.forEach((panel) => {
@@ -3821,6 +3959,13 @@ const sectionValidate = (section, currentSchemaData, dispatch, skipRequired = fa
3821
3959
  if (!isVisible)
3822
3960
  continue;
3823
3961
  const widgetId = widget['widget-id'];
3962
+ if (isTableLikeWidget(widget)) {
3963
+ const tableValid = validateTableLikeWidget(widget, currentSchemaData, dispatch, skipRequired);
3964
+ if (!tableValid) {
3965
+ isValid = false;
3966
+ }
3967
+ continue;
3968
+ }
3824
3969
  const value = getWidgetValue(currentSchemaData, widget['widget-data-path'], widgetId);
3825
3970
  const errors = validateWidget(value, widget['widget-data-validation'], widget['widget-required'], skipRequired);
3826
3971
  if (errors.length > 0) {
@@ -3855,52 +4000,6 @@ const sectionValidate = (section, currentSchemaData, dispatch, skipRequired = fa
3855
4000
  return isValid;
3856
4001
  };
3857
4002
 
3858
- /** Table-style widgets that bind to an array path in the store / schema. */
3859
- function isTableLikeWidget(widget) {
3860
- const w = widget.widget;
3861
- /** widget-type union in types omits legacy values like simple-table still used at runtime */
3862
- const t = widget['widget-type'];
3863
- return (w === 'table' ||
3864
- w === 'dialog-table' ||
3865
- w === 'simple-table' ||
3866
- t === 'table' ||
3867
- t === 'simple-table');
3868
- }
3869
- /**
3870
- * Resolve `records` for section save payloads.
3871
- * - Back-compat: path ending in `.records` (e.g. `regId.records`)
3872
- * - Else: first array snapshot at a string `widget-data-path` on a table-like widget
3873
- * (e.g. `household.members` for dialog-table)
3874
- */
3875
- function extractTableRecordsFromSnapshot(snapshot, sectionWidgets) {
3876
- const convention = Object.entries(snapshot).find(([key, value]) => key.endsWith('.records') && Array.isArray(value));
3877
- if (convention) {
3878
- return convention[1];
3879
- }
3880
- const tablePaths = [];
3881
- sectionWidgets.forEach((widget) => {
3882
- if (!isTableLikeWidget(widget))
3883
- return;
3884
- const p = widget['widget-data-path'];
3885
- if (typeof p === 'string' && p.length > 0) {
3886
- tablePaths.push(p);
3887
- }
3888
- else if (p && typeof p === 'object') {
3889
- Object.values(p).forEach((sub) => {
3890
- if (typeof sub === 'string' && sub.length > 0)
3891
- tablePaths.push(sub);
3892
- });
3893
- }
3894
- });
3895
- for (const path of tablePaths) {
3896
- const val = snapshot[path];
3897
- if (Array.isArray(val)) {
3898
- return val;
3899
- }
3900
- }
3901
- return [];
3902
- }
3903
-
3904
4003
  /** Root class on readonly label/value rows; SectionRenderer scopes overflow/ellipsis rules here. */
3905
4004
  const READONLY_VALUE_ROW_ROOT_CLASSES = [
3906
4005
  'TextDisplayWidget',
@@ -7510,6 +7609,8 @@ const BooleanWidget = ({ config }) => {
7510
7609
  return labels[representation];
7511
7610
  }, [representation, formatConfig, translateConfig]);
7512
7611
  const { trueLabel, falseLabel } = getLabels();
7612
+ const unsetLabel = useMemo(() => translateConfig(formatConfig?.booleanUnsetLabel || 'Not set'), [formatConfig?.booleanUnsetLabel, translateConfig]);
7613
+ const radioGroupName = `${widgetConfig['widget-id'] ?? 'boolean'}__${useId().replace(/:/g, '')}`;
7513
7614
  // Determine current value (handle null/undefined)
7514
7615
  const currentValue = useMemo(() => {
7515
7616
  if (value === null || value === undefined) {
@@ -7541,7 +7642,7 @@ const BooleanWidget = ({ config }) => {
7541
7642
  const label = translateConfig(widgetConfig['widget-label']);
7542
7643
  let displayValue = '';
7543
7644
  if (currentValue === null) {
7544
- displayValue = '-';
7645
+ displayValue = '';
7545
7646
  }
7546
7647
  else if (currentValue === true) {
7547
7648
  displayValue = trueLabel;
@@ -7553,18 +7654,20 @@ const BooleanWidget = ({ config }) => {
7553
7654
  }
7554
7655
  // Render based on control type
7555
7656
  if (controlType === 'checkbox') {
7556
- return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("label", { className: "flex items-center cursor-pointer", children: [jsxRuntimeExports.jsx("input", { type: "checkbox", checked: currentValue === true, onChange: handleCheckboxChange, onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], className: "mr-2 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-700", children: currentValue === true ? trueLabel : (currentValue === false ? falseLabel : '-') })] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
7657
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium leading-normal text-gray-700 md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("label", { className: "inline-flex items-baseline cursor-pointer gap-2", children: [jsxRuntimeExports.jsx("input", { type: "checkbox", checked: currentValue === true, onChange: handleCheckboxChange, onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), (currentValue === true || currentValue === false) && (jsxRuntimeExports.jsx("span", { className: "text-base text-gray-700 leading-normal", children: currentValue === true ? trueLabel : falseLabel }))] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
7557
7658
  }
7558
7659
  if (controlType === 'radio') {
7559
7660
  const containerClass = orientation === 'horizontal'
7560
- ? 'flex flex-row space-x-4'
7561
- : 'flex flex-col space-y-2';
7562
- return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: containerClass, onBlur: onBlur, children: [allowUnset && (jsxRuntimeExports.jsxs("label", { className: `flex items-center cursor-pointer ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : ''}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: widgetConfig['widget-id'], checked: currentValue === null, onChange: () => handleRadioChange(null), disabled: !isEnabled || widgetConfig['widget-readonly'], className: "mr-2 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-700", children: "-" })] })), jsxRuntimeExports.jsxs("label", { className: `flex items-center cursor-pointer ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : ''}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: widgetConfig['widget-id'], checked: currentValue === true, onChange: () => handleRadioChange(true), disabled: !isEnabled || widgetConfig['widget-readonly'], className: "mr-2 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-700", children: trueLabel })] }), jsxRuntimeExports.jsxs("label", { className: `flex items-center cursor-pointer ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : ''}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: widgetConfig['widget-id'], checked: currentValue === false, onChange: () => handleRadioChange(false), disabled: !isEnabled || widgetConfig['widget-readonly'], className: "mr-2 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-700", children: falseLabel })] })] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
7661
+ ? 'flex flex-row flex-wrap items-baseline gap-x-4 gap-y-2'
7662
+ : 'flex flex-col items-start gap-2';
7663
+ const radioDisabled = !isEnabled || widgetConfig['widget-readonly'];
7664
+ const optionDisabledClass = radioDisabled ? 'opacity-50 cursor-not-allowed' : '';
7665
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium leading-normal text-gray-700 md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: containerClass, onBlur: onBlur, children: [allowUnset && (jsxRuntimeExports.jsxs("label", { className: `inline-flex items-baseline gap-2 cursor-pointer ${optionDisabledClass}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: radioGroupName, checked: currentValue === null, onChange: () => handleRadioChange(null), disabled: radioDisabled, className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-base text-gray-700 leading-normal", children: unsetLabel })] })), jsxRuntimeExports.jsxs("label", { className: `inline-flex items-baseline gap-2 cursor-pointer ${optionDisabledClass}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: radioGroupName, checked: currentValue === true, onChange: () => handleRadioChange(true), disabled: radioDisabled, className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-base text-gray-700 leading-normal", children: trueLabel })] }), jsxRuntimeExports.jsxs("label", { className: `inline-flex items-baseline gap-2 cursor-pointer ${optionDisabledClass}`, children: [jsxRuntimeExports.jsx("input", { type: "radio", name: radioGroupName, checked: currentValue === false, onChange: () => handleRadioChange(false), disabled: radioDisabled, className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300" }), jsxRuntimeExports.jsx("span", { className: "text-base text-gray-700 leading-normal", children: falseLabel })] })] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
7563
7666
  }
7564
7667
  // Toggle/switch control type
7565
- return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium text-gray-700 sm:min-w-[150px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: "flex items-center space-x-3", onBlur: onBlur, children: [allowUnset && (jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleChange(null), disabled: !isEnabled || widgetConfig['widget-readonly'], className: `px-3 py-1 text-sm border ${currentValue === null
7668
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium leading-normal text-gray-700 sm:min-w-[150px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-3", onBlur: onBlur, children: [allowUnset && (jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleChange(null), disabled: !isEnabled || widgetConfig['widget-readonly'], className: `px-3 py-1 text-sm border ${currentValue === null
7566
7669
  ? 'bg-blue-600 text-white border-blue-600'
7567
- : '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: "-" })), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleChange(true), disabled: !isEnabled || widgetConfig['widget-readonly'], className: `px-3 py-1 text-sm border ${currentValue === true
7670
+ : 'bg-white text-gray-700 border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-50'}`, style: { borderRadius: '15px' }, children: unsetLabel })), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleChange(true), disabled: !isEnabled || widgetConfig['widget-readonly'], className: `px-3 py-1 text-sm border ${currentValue === true
7568
7671
  ? 'bg-blue-600 text-white border-blue-600'
7569
7672
  : 'bg-white text-gray-700 border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-50'}`, style: { borderRadius: '15px' }, children: trueLabel }), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleChange(false), disabled: !isEnabled || widgetConfig['widget-readonly'], className: `px-3 py-1 text-sm border ${currentValue === false
7570
7673
  ? 'bg-blue-600 text-white border-blue-600'
@@ -7572,42 +7675,76 @@ const BooleanWidget = ({ config }) => {
7572
7675
  };
7573
7676
 
7574
7677
  const DateInputWidget = ({ config }) => {
7575
- const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
7576
- const { translate, translateConfig } = useWidgetTranslation();
7678
+ const { value, error, touched, isEnabled, onChange, onBlur, setError, config: widgetConfig, } = useBaseWidget({ config });
7679
+ const formValues = useSelector((state) => state.widget.values);
7680
+ const { translateConfig } = useWidgetTranslation();
7577
7681
  const formatConfig = widgetConfig['widget-data-format'];
7578
7682
  const optionsConfig = widgetConfig['widget-data-options'];
7579
7683
  const dateFormat = formatConfig?.dateFormat || 'YYYY-MM-DD';
7580
- const inputMethod = formatConfig?.inputMethod || 'picker'; // Default to picker for better UX
7684
+ const inputMethod = formatConfig?.inputMethod || 'picker';
7581
7685
  const dateConstraint = formatConfig?.dateConstraint || 'any';
7582
7686
  const minDate = optionsConfig?.minDate;
7583
7687
  const maxDate = optionsConfig?.maxDate;
7688
+ const minDateField = optionsConfig?.minDateField;
7689
+ const maxDateField = optionsConfig?.maxDateField;
7690
+ const minDateMessage = optionsConfig?.minDateMessage
7691
+ ? translateConfig(optionsConfig.minDateMessage)
7692
+ : undefined;
7693
+ const maxDateMessage = optionsConfig?.maxDateMessage
7694
+ ? translateConfig(optionsConfig.maxDateMessage)
7695
+ : undefined;
7584
7696
  const defaultToToday = widgetConfig['widget-data-default'] === 'today';
7585
- // Track manual input value (for manual/hybrid modes)
7586
7697
  const [manualInputValue, setManualInputValue] = useState('');
7587
7698
  const [isFocused, setIsFocused] = useState(false);
7588
- // Initialize default value to today if configured
7699
+ const fieldMinDate = useMemo(() => {
7700
+ if (!minDateField) {
7701
+ return undefined;
7702
+ }
7703
+ return resolveDateBoundFromFieldValue(getValueByPath(formValues, minDateField));
7704
+ }, [formValues, minDateField]);
7705
+ const fieldMaxDate = useMemo(() => {
7706
+ if (!maxDateField) {
7707
+ return undefined;
7708
+ }
7709
+ return resolveDateBoundFromFieldValue(getValueByPath(formValues, maxDateField));
7710
+ }, [formValues, maxDateField]);
7711
+ const effectiveMinDate = useMemo(() => {
7712
+ const staticMin = getMinDate(dateConstraint, minDate);
7713
+ return mergeMinDateBounds(staticMin, fieldMinDate);
7714
+ }, [dateConstraint, minDate, fieldMinDate]);
7715
+ const effectiveMaxDate = useMemo(() => {
7716
+ const staticMax = getMaxDate(dateConstraint, maxDate);
7717
+ return mergeMaxDateBounds(staticMax, fieldMaxDate);
7718
+ }, [dateConstraint, maxDate, fieldMaxDate]);
7719
+ const constraintMessages = useMemo(() => ({ minDateMessage, maxDateMessage }), [minDateMessage, maxDateMessage]);
7720
+ const runDateConstraintValidation = useCallback((dateValue) => {
7721
+ if (!dateValue) {
7722
+ return null;
7723
+ }
7724
+ return validateDateConstraints(dateValue, effectiveMinDate, effectiveMaxDate, dateConstraint, constraintMessages);
7725
+ }, [effectiveMinDate, effectiveMaxDate, dateConstraint, constraintMessages]);
7589
7726
  useEffect(() => {
7590
7727
  if (defaultToToday && (value === null || value === undefined || value === '')) {
7591
7728
  const todayISO = formatDateToISO(new Date());
7592
7729
  onChange(todayISO);
7593
7730
  }
7594
7731
  }, [defaultToToday, value, onChange]);
7595
- // Get effective min/max dates
7596
- const effectiveMinDate = useMemo(() => {
7597
- return getMinDate(dateConstraint, minDate);
7598
- }, [dateConstraint, minDate]);
7599
- const effectiveMaxDate = useMemo(() => {
7600
- return getMaxDate(dateConstraint, maxDate);
7601
- }, [dateConstraint, maxDate]);
7602
- // Convert ISO value to display format
7732
+ // Re-validate when a relative bound field changes (e.g. start date set after end date)
7733
+ useEffect(() => {
7734
+ if (!value) {
7735
+ return;
7736
+ }
7737
+ const constraintError = runDateConstraintValidation(value);
7738
+ if (constraintError) {
7739
+ setError([constraintError]);
7740
+ }
7741
+ }, [fieldMinDate, fieldMaxDate, value, runDateConstraintValidation, setError]);
7603
7742
  const getDisplayValue = useCallback(() => {
7604
- // For picker mode, always use YYYY-MM-DD
7605
7743
  if (inputMethod === 'picker') {
7606
7744
  if (!value)
7607
7745
  return '';
7608
7746
  return formatDateToISO(value);
7609
7747
  }
7610
- // For manual/hybrid modes, use custom format
7611
7748
  if (isFocused && manualInputValue) {
7612
7749
  return manualInputValue;
7613
7750
  }
@@ -7618,7 +7755,6 @@ const DateInputWidget = ({ config }) => {
7618
7755
  }
7619
7756
  return formatDateToString(value, dateFormat);
7620
7757
  }, [value, inputMethod, dateFormat, isFocused, manualInputValue]);
7621
- // Initialize manual input value
7622
7758
  useEffect(() => {
7623
7759
  if (!isFocused && value) {
7624
7760
  if (dateFormat === 'YYYY-MM-DD') {
@@ -7629,66 +7765,81 @@ const DateInputWidget = ({ config }) => {
7629
7765
  }
7630
7766
  }
7631
7767
  }, [value, dateFormat, isFocused]);
7632
- // Handle input change
7768
+ const applyConstraintError = useCallback((dateValue) => {
7769
+ const constraintError = runDateConstraintValidation(dateValue);
7770
+ setError(constraintError ? [constraintError] : []);
7771
+ }, [runDateConstraintValidation, setError]);
7633
7772
  const handleChange = useCallback((e) => {
7634
7773
  const inputValue = e.target.value;
7635
7774
  if (inputMethod === 'picker') {
7636
- // Picker mode: input is always YYYY-MM-DD
7637
7775
  if (inputValue) {
7638
7776
  const date = parseDate(inputValue);
7639
7777
  if (date) {
7640
- onChange(formatDateToISO(date));
7778
+ const iso = formatDateToISO(date);
7779
+ onChange(iso);
7780
+ applyConstraintError(iso);
7641
7781
  }
7642
7782
  else {
7643
7783
  onChange('');
7784
+ setError([]);
7644
7785
  }
7645
7786
  }
7646
7787
  else {
7647
7788
  onChange('');
7789
+ setError([]);
7648
7790
  }
7649
7791
  }
7650
7792
  else {
7651
- // Manual/hybrid mode: parse custom format
7652
7793
  setManualInputValue(inputValue);
7653
7794
  if (inputValue) {
7654
7795
  const date = parseDateFromFormat(inputValue, dateFormat);
7655
7796
  if (date) {
7656
- // Validate constraints
7657
- const constraintError = validateDateConstraints(date, minDate, maxDate, dateConstraint);
7658
- if (!constraintError) {
7659
- onChange(formatDateToISO(date));
7660
- }
7661
- else {
7662
- // Still update the value but validation will catch it
7663
- onChange(formatDateToISO(date));
7664
- }
7797
+ const iso = formatDateToISO(date);
7798
+ onChange(iso);
7799
+ applyConstraintError(iso);
7665
7800
  }
7666
7801
  }
7667
7802
  else {
7668
7803
  onChange('');
7804
+ setError([]);
7669
7805
  }
7670
7806
  }
7671
- }, [inputMethod, dateFormat, onChange, minDate, maxDate, dateConstraint]);
7672
- // Handle blur - validate and format
7807
+ }, [inputMethod, dateFormat, onChange, applyConstraintError, setError]);
7673
7808
  const handleBlur = useCallback(() => {
7674
7809
  setIsFocused(false);
7675
7810
  if (inputMethod !== 'picker' && manualInputValue) {
7676
7811
  const date = parseDateFromFormat(manualInputValue, dateFormat);
7677
7812
  if (date) {
7678
- // Format the value according to the format
7679
7813
  const formatted = formatDateToString(date, dateFormat);
7680
7814
  setManualInputValue(formatted);
7681
- onChange(formatDateToISO(date));
7815
+ const iso = formatDateToISO(date);
7816
+ onChange(iso);
7817
+ applyConstraintError(iso);
7682
7818
  }
7683
7819
  else {
7684
- // Invalid date, clear it
7685
7820
  setManualInputValue('');
7686
7821
  onChange('');
7822
+ setError([]);
7687
7823
  }
7688
7824
  }
7689
7825
  onBlur();
7690
- }, [inputMethod, manualInputValue, dateFormat, onChange, onBlur]);
7691
- // Handle focus
7826
+ if (value) {
7827
+ const constraintError = runDateConstraintValidation(value);
7828
+ if (constraintError) {
7829
+ setError([constraintError]);
7830
+ }
7831
+ }
7832
+ }, [
7833
+ inputMethod,
7834
+ manualInputValue,
7835
+ dateFormat,
7836
+ onChange,
7837
+ onBlur,
7838
+ applyConstraintError,
7839
+ value,
7840
+ runDateConstraintValidation,
7841
+ setError,
7842
+ ]);
7692
7843
  const handleFocus = useCallback(() => {
7693
7844
  setIsFocused(true);
7694
7845
  if (value) {
@@ -7700,15 +7851,15 @@ const DateInputWidget = ({ config }) => {
7700
7851
  }
7701
7852
  }
7702
7853
  }, [value, dateFormat]);
7703
- // Determine placeholder
7704
7854
  const placeholder = useMemo(() => {
7705
- const hasValue = getDisplayValue() && getDisplayValue().trim().length > 0;
7855
+ const display = getDisplayValue();
7856
+ const hasValue = display && display.trim().length > 0;
7706
7857
  const placeholderText = translateConfig(widgetConfig['widget-data-placeholder']);
7707
- return hasValue ? undefined : (placeholderText || dateFormat);
7858
+ return hasValue ? undefined : placeholderText || dateFormat;
7708
7859
  }, [getDisplayValue, widgetConfig, translateConfig, dateFormat]);
7709
- // Determine input type
7710
7860
  const inputType = inputMethod === 'picker' ? 'date' : 'text';
7711
- // For readonly mode, render as display text
7861
+ const showRequiredError = widgetConfig['widget-required'] && (!value || value === '');
7862
+ const showValidationError = touched && error.length > 0;
7712
7863
  if (widgetConfig['widget-readonly']) {
7713
7864
  const label = translateConfig(widgetConfig['widget-label']);
7714
7865
  let displayValue = '';
@@ -7725,9 +7876,9 @@ const DateInputWidget = ({ config }) => {
7725
7876
  }
7726
7877
  return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] DateDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
7727
7878
  }
7728
- return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("input", { type: inputType, value: getDisplayValue(), onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, min: inputMethod === 'picker' ? effectiveMinDate : undefined, max: inputMethod === 'picker' ? effectiveMaxDate : undefined, className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${(touched && error.length > 0) || (widgetConfig['widget-required'] && (!value || value === ''))
7879
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" })] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("input", { type: inputType, value: getDisplayValue(), onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, min: inputMethod === 'picker' ? effectiveMinDate : undefined, max: inputMethod === 'picker' ? effectiveMaxDate : undefined, className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${showValidationError || showRequiredError
7729
7880
  ? 'border-red-500 focus:ring-red-500 focus:border-red-500'
7730
- : 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
7881
+ : 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }), showValidationError && jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] })] })] }) }));
7731
7882
  };
7732
7883
 
7733
7884
  /**
@@ -8275,7 +8426,7 @@ const CheckboxWidget = ({ config }) => {
8275
8426
  const displayValue = isChecked ? 'Yes' : 'No';
8276
8427
  return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] CheckboxDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
8277
8428
  }
8278
- return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("label", { className: "flex items-center cursor-pointer", children: [jsxRuntimeExports.jsx("input", { type: "checkbox", checked: isChecked, onChange: (e) => onChange(e.target.checked), onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], className: "mr-2 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-700", children: isChecked ? 'Yes' : 'No' })] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
8429
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium leading-normal text-gray-700 md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("label", { className: "inline-flex cursor-pointer items-baseline gap-2", children: [jsxRuntimeExports.jsx("input", { type: "checkbox", checked: isChecked, onChange: (e) => onChange(e.target.checked), onBlur: onBlur, disabled: !isEnabled || widgetConfig['widget-readonly'], className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), jsxRuntimeExports.jsx("span", { className: "text-base leading-normal text-gray-700", children: isChecked ? 'Yes' : 'No' })] }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
8279
8430
  }
8280
8431
  // Multiple checkboxes (with data source) - for array values
8281
8432
  // Process and sort options if needed
@@ -8318,7 +8469,7 @@ const CheckboxWidget = ({ config }) => {
8318
8469
  switch (layout) {
8319
8470
  case 'horizontal':
8320
8471
  return {
8321
- className: 'flex flex-row flex-wrap gap-4',
8472
+ className: 'flex flex-row flex-wrap items-baseline gap-4',
8322
8473
  style: undefined,
8323
8474
  };
8324
8475
  case 'grid':
@@ -8331,7 +8482,7 @@ const CheckboxWidget = ({ config }) => {
8331
8482
  case 'vertical':
8332
8483
  default:
8333
8484
  return {
8334
- className: 'flex flex-col space-y-2',
8485
+ className: 'flex flex-col gap-2',
8335
8486
  style: undefined,
8336
8487
  };
8337
8488
  }
@@ -8345,7 +8496,7 @@ const CheckboxWidget = ({ config }) => {
8345
8496
  : '-';
8346
8497
  return (jsxRuntimeExports.jsxs("div", { className: "mb-3 CheckboxDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-sm text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
8347
8498
  }
8348
- return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("div", { className: layoutConfig.className, style: layoutConfig.style, onBlur: onBlur, children: loading ? (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500", children: translate('common.loading') })) : (processedOptions.map((option) => (jsxRuntimeExports.jsxs("label", { className: `flex items-center cursor-pointer ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : ''}`, children: [jsxRuntimeExports.jsx("input", { type: "checkbox", value: option.value, checked: selectedValues.includes(option.value), onChange: (e) => handleCheckboxChange(option.value, e.target.checked), disabled: !isEnabled || widgetConfig['widget-readonly'], className: "mr-2 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-700", children: translateConfig(option.label) })] }, option.value)))) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
8499
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-baseline", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium leading-normal text-gray-700 md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0 sm:pt-0.5", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("div", { className: layoutConfig.className, style: layoutConfig.style, onBlur: onBlur, children: loading ? (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500", children: translate('common.loading') })) : (processedOptions.map((option) => (jsxRuntimeExports.jsxs("label", { className: `inline-flex cursor-pointer items-baseline gap-2 ${!isEnabled || widgetConfig['widget-readonly'] ? 'opacity-50 cursor-not-allowed' : ''}`, children: [jsxRuntimeExports.jsx("input", { type: "checkbox", value: option.value, checked: selectedValues.includes(option.value), onChange: (e) => handleCheckboxChange(option.value, e.target.checked), disabled: !isEnabled || widgetConfig['widget-readonly'], className: "relative top-[0.2em] h-4 w-4 shrink-0 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" }), jsxRuntimeExports.jsx("span", { className: "text-base leading-normal text-gray-700", children: translateConfig(option.label) })] }, option.value)))) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
8349
8500
  };
8350
8501
 
8351
8502
  const SimpleTableWidget = ({ config }) => {
@@ -8671,16 +8822,70 @@ const TableCellNumber = ({ config, value, onValueChange }) => {
8671
8822
  backgroundColor: isReadonly ? 'var(--owt-color-bg-alt, #F6F6F6)' : 'var(--owt-color-bg, #FFFFFF)',
8672
8823
  } }));
8673
8824
  };
8674
- const TableCellDate = ({ config, value, onValueChange }) => {
8825
+ const TableCellDate = ({ config, value, rowValues, onValueChange }) => {
8826
+ const { translateConfig } = useWidgetTranslation();
8675
8827
  const isReadonly = config['widget-readonly'] || false;
8676
8828
  const placeholder = config['widget-data-placeholder'] || '';
8829
+ const optionsConfig = config['widget-data-options'];
8830
+ const formatConfig = config['widget-data-format'];
8831
+ const dateConstraint = formatConfig?.dateConstraint || 'any';
8832
+ const minDate = optionsConfig?.minDate;
8833
+ const maxDate = optionsConfig?.maxDate;
8834
+ const minDateField = optionsConfig?.minDateField;
8835
+ const maxDateField = optionsConfig?.maxDateField;
8836
+ const minDateMessage = optionsConfig?.minDateMessage
8837
+ ? translateConfig(optionsConfig.minDateMessage)
8838
+ : undefined;
8839
+ const maxDateMessage = optionsConfig?.maxDateMessage
8840
+ ? translateConfig(optionsConfig.maxDateMessage)
8841
+ : undefined;
8842
+ const [constraintError, setConstraintError] = useState(null);
8843
+ const resolveSiblingDate = (fieldRef) => {
8844
+ if (!fieldRef || !rowValues) {
8845
+ return undefined;
8846
+ }
8847
+ const raw = getValueByPath(rowValues, fieldRef) ?? rowValues[fieldRef];
8848
+ return resolveDateBoundFromFieldValue(raw);
8849
+ };
8850
+ const fieldMinDate = useMemo(() => resolveSiblingDate(minDateField), [minDateField, rowValues]);
8851
+ const fieldMaxDate = useMemo(() => resolveSiblingDate(maxDateField), [maxDateField, rowValues]);
8852
+ const effectiveMinDate = useMemo(() => {
8853
+ const staticMin = getMinDate(dateConstraint, minDate);
8854
+ return mergeMinDateBounds(staticMin, fieldMinDate);
8855
+ }, [dateConstraint, minDate, fieldMinDate]);
8856
+ const effectiveMaxDate = useMemo(() => {
8857
+ const staticMax = getMaxDate(dateConstraint, maxDate);
8858
+ return mergeMaxDateBounds(staticMax, fieldMaxDate);
8859
+ }, [dateConstraint, maxDate, fieldMaxDate]);
8860
+ const constraintMessages = useMemo(() => ({ minDateMessage, maxDateMessage }), [minDateMessage, maxDateMessage]);
8677
8861
  // input type="date" requires YYYY-MM-DD format
8678
8862
  const displayValue = value && typeof value === 'string' ? value.split('T')[0] : '';
8679
- 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 ${isReadonly ? 'cursor-not-allowed' : ''} table-cell-input`, style: {
8680
- borderRadius: '10px',
8681
- borderColor: 'var(--owt-widget-input-border, #C4C4C4)',
8682
- backgroundColor: isReadonly ? 'var(--owt-color-bg-alt, #F6F6F6)' : 'var(--owt-color-bg, #FFFFFF)',
8683
- } }));
8863
+ useEffect(() => {
8864
+ if (!displayValue) {
8865
+ setConstraintError(null);
8866
+ return;
8867
+ }
8868
+ const error = validateDateConstraints(displayValue, effectiveMinDate, effectiveMaxDate, dateConstraint, constraintMessages);
8869
+ setConstraintError(error);
8870
+ }, [displayValue, effectiveMinDate, effectiveMaxDate, dateConstraint, constraintMessages]);
8871
+ const handleChange = (e) => {
8872
+ const nextValue = e.target.value;
8873
+ onValueChange(nextValue);
8874
+ if (!nextValue) {
8875
+ setConstraintError(null);
8876
+ return;
8877
+ }
8878
+ const error = validateDateConstraints(nextValue, effectiveMinDate, effectiveMaxDate, dateConstraint, constraintMessages);
8879
+ setConstraintError(error);
8880
+ };
8881
+ const hasError = Boolean(constraintError);
8882
+ return (jsxRuntimeExports.jsxs("div", { className: "w-full", children: [jsxRuntimeExports.jsx("input", { type: "date", value: displayValue, onChange: handleChange, disabled: isReadonly, placeholder: placeholder, min: effectiveMinDate, max: effectiveMaxDate, title: constraintError || translateConfig(config['widget-data-tooltip']), className: `w-full h-[28px] px-2 text-sm border focus:outline-none ${isReadonly ? 'cursor-not-allowed' : ''} ${hasError ? 'border-red-500' : ''} table-cell-input`, style: {
8883
+ borderRadius: '10px',
8884
+ borderColor: hasError
8885
+ ? 'var(--owt-color-error, #B91C1C)'
8886
+ : 'var(--owt-widget-input-border, #C4C4C4)',
8887
+ backgroundColor: isReadonly ? 'var(--owt-color-bg-alt, #F6F6F6)' : 'var(--owt-color-bg, #FFFFFF)',
8888
+ } }), hasError && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-xs mt-0.5 leading-tight", children: constraintError }))] }));
8684
8889
  };
8685
8890
  const TableWidget = ({ config }) => {
8686
8891
  const { value, error, touched, isEnabled, onChange, config: widgetConfig, } = useBaseWidget({ config });
@@ -9039,11 +9244,21 @@ const TableWidget = ({ config }) => {
9039
9244
  });
9040
9245
  }
9041
9246
  }, [isAdding, newRowData, columns, widgetConfig, rows.length, dispatch]);
9247
+ const getRowValuesForEdit = useCallback((rowIndex) => {
9248
+ if (editingState && editingState.rowIndex === rowIndex) {
9249
+ return editingState.currentValue ?? {};
9250
+ }
9251
+ if (isAdding && rowIndex === rows.length && newRowData) {
9252
+ return newRowData;
9253
+ }
9254
+ return rows[rowIndex] ?? {};
9255
+ }, [editingState, isAdding, rows, newRowData]);
9042
9256
  // Lightweight cell renderer for table cells (no labels, compact)
9043
9257
  const renderTableCell = useCallback((rowIndex, column, cellValue, isReadonly) => {
9044
9258
  const columnKey = column['column-key'];
9045
9259
  const widgetType = column.widget || 'text';
9046
9260
  const cellWidgetId = `${widgetConfig['widget-id']}-row-${rowIndex}-col-${columnKey}`;
9261
+ const rowValues = getRowValuesForEdit(rowIndex);
9047
9262
  // Use lightweight cell config (no label, minimal styling)
9048
9263
  const cellConfig = {
9049
9264
  ...column,
@@ -9066,7 +9281,7 @@ const TableWidget = ({ config }) => {
9066
9281
  return jsxRuntimeExports.jsx(TableCellNumber, { config: cellConfig, value: cellValue, onValueChange: (newValue) => updateCellValue(columnKey, newValue, rowIndex) });
9067
9282
  }
9068
9283
  else if (widgetType === 'date') {
9069
- return jsxRuntimeExports.jsx(TableCellDate, { config: cellConfig, value: cellValue, onValueChange: (newValue) => updateCellValue(columnKey, newValue, rowIndex) });
9284
+ return jsxRuntimeExports.jsx(TableCellDate, { config: cellConfig, value: cellValue, rowValues: rowValues, onValueChange: (newValue) => updateCellValue(columnKey, newValue, rowIndex) });
9070
9285
  }
9071
9286
  // For other widget types, use WidgetRenderer but with compact styling
9072
9287
  return (jsxRuntimeExports.jsx("div", { className: "table-cell-widget", style: { margin: 0, padding: 0 }, children: jsxRuntimeExports.jsx(WidgetRenderer, { config: cellConfig, schemaData: {
@@ -9074,7 +9289,7 @@ const TableWidget = ({ config }) => {
9074
9289
  }, onValueChange: (widgetId, newValue) => {
9075
9290
  updateCellValue(columnKey, newValue, rowIndex);
9076
9291
  } }) }));
9077
- }, [widgetConfig, updateCellValue]);
9292
+ }, [widgetConfig, updateCellValue, getRowValuesForEdit]);
9078
9293
  // Render cell content (widget in edit mode, formatted value in view mode)
9079
9294
  const renderCell = useCallback((rowIndex, column, row) => {
9080
9295
  const columnKey = column['column-key'];
@@ -9384,6 +9599,27 @@ const DialogTableWidget = ({ config }) => {
9384
9599
  }, [formData, columns, storeValues, dialogFieldWidgetId]);
9385
9600
  const saveDialog = useCallback(() => {
9386
9601
  const payload = collectMergedRowPayload();
9602
+ let hasErrors = false;
9603
+ columns.forEach((col) => {
9604
+ const key = col['column-key'];
9605
+ const cellWidgetId = dialogFieldWidgetId(key);
9606
+ const isColReadonly = isReadonly || col['widget-readonly'] === true;
9607
+ if (isColReadonly)
9608
+ return;
9609
+ const cellValue = payload[key];
9610
+ const validationErrors = validateWidget(cellValue, col['widget-data-validation'], col['widget-required']);
9611
+ if (validationErrors && validationErrors.length > 0) {
9612
+ hasErrors = true;
9613
+ dispatch(setError({ widgetId: cellWidgetId, errors: validationErrors }));
9614
+ dispatch(setTouched({ widgetId: cellWidgetId, touched: true }));
9615
+ }
9616
+ else {
9617
+ dispatch(setError({ widgetId: cellWidgetId, errors: [] }));
9618
+ }
9619
+ });
9620
+ if (hasErrors) {
9621
+ return;
9622
+ }
9387
9623
  if (dialogMode === 'add') {
9388
9624
  const savedRow = { ...payload, edit_action: 'ADD' };
9389
9625
  onChange([...rows, savedRow]);
@@ -9399,7 +9635,7 @@ const DialogTableWidget = ({ config }) => {
9399
9635
  onChange(newRows);
9400
9636
  closeDialog();
9401
9637
  }
9402
- }, [collectMergedRowPayload, dialogMode, onChange, rows, closeDialog, activeRowIndex]);
9638
+ }, [collectMergedRowPayload, dialogMode, onChange, rows, closeDialog, activeRowIndex, columns, dialogFieldWidgetId, isReadonly, dispatch]);
9403
9639
  const deleteRow = useCallback((rowIndex) => {
9404
9640
  const newRows = rows.filter((_, i) => i !== rowIndex);
9405
9641
  onChange(newRows);
@@ -10225,20 +10461,29 @@ const HeaderSectionWidget = ({ config }) => {
10225
10461
  .${cls} .hdr-field-row {
10226
10462
  display: flex;
10227
10463
  align-items: flex-start;
10228
- gap: 0.5rem;
10229
10464
  font-size: 1rem;
10230
10465
  line-height: 1.6;
10231
10466
  }
10232
10467
 
10233
10468
  .${cls} .hdr-field-label {
10469
+ width: 50%;
10470
+ flex: 0 0 50%;
10234
10471
  color: rgba(0, 0, 0, 0.5);
10235
10472
  font-weight: 400;
10236
10473
  white-space: nowrap;
10474
+ overflow: hidden;
10475
+ text-overflow: ellipsis;
10476
+ padding-right: 4px;
10237
10477
  }
10238
10478
 
10239
10479
  .${cls} .hdr-field-value {
10480
+ width: 50%;
10481
+ flex: 0 0 50%;
10240
10482
  color: var(--owt-color-text, #111827);
10241
10483
  font-weight: 500;
10484
+ white-space: nowrap;
10485
+ overflow: hidden;
10486
+ text-overflow: ellipsis;
10242
10487
  }
10243
10488
 
10244
10489
  .${cls} .hdr-status-badge {
@@ -10248,24 +10493,38 @@ const HeaderSectionWidget = ({ config }) => {
10248
10493
  font-size: 0.75rem;
10249
10494
  font-weight: 600;
10250
10495
  color: #fff;
10496
+ max-width: 100%;
10497
+ overflow: hidden;
10498
+ text-overflow: ellipsis;
10499
+ white-space: nowrap;
10251
10500
  }
10252
10501
 
10253
10502
  .${cls} .hdr-meta-row {
10254
10503
  display: flex;
10255
10504
  align-items: baseline;
10256
- gap: 0.35rem;
10257
10505
  font-size: 1rem;
10258
10506
  line-height: 1.6;
10259
10507
  }
10260
10508
 
10261
10509
  .${cls} .hdr-meta-label {
10510
+ width: 50%;
10511
+ flex: 0 0 50%;
10262
10512
  color: rgba(0, 0, 0, 0.5);
10263
10513
  font-weight: 400;
10514
+ white-space: nowrap;
10515
+ overflow: hidden;
10516
+ text-overflow: ellipsis;
10517
+ padding-right: 4px;
10264
10518
  }
10265
10519
 
10266
10520
  .${cls} .hdr-meta-value {
10521
+ width: 50%;
10522
+ flex: 0 0 50%;
10267
10523
  color: var(--owt-color-text, #111827);
10268
10524
  font-weight: 500;
10525
+ white-space: nowrap;
10526
+ overflow: hidden;
10527
+ text-overflow: ellipsis;
10269
10528
  }
10270
10529
 
10271
10530
  .${cls} .hdr-select {
@@ -10329,7 +10588,7 @@ const HeaderSectionWidget = ({ config }) => {
10329
10588
  .parentElement?.querySelector('.hdr-avatar-placeholder');
10330
10589
  if (placeholder)
10331
10590
  placeholder.style.display = 'flex';
10332
- } })) : null, jsxRuntimeExports.jsx("div", { className: "hdr-avatar-placeholder", style: { display: displayImageUrl ? 'none' : 'flex' }, children: jsxRuntimeExports.jsx("img", { src: img$f, alt: "Profile Placeholder" }) }), !isReadonly && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsxs("div", { className: "hdr-avatar-overlay", children: [jsxRuntimeExports.jsxs("button", { type: "button", className: "hdr-avatar-action", onClick: () => fileInputRef.current?.click(), children: [jsxRuntimeExports.jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntimeExports.jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }), jsxRuntimeExports.jsx("polyline", { points: "17 8 12 3 7 8" }), jsxRuntimeExports.jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })] }), "Upload"] }), jsxRuntimeExports.jsxs("button", { type: "button", className: "hdr-avatar-action hdr-avatar-action--delete", onClick: handleImageDelete, children: [jsxRuntimeExports.jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntimeExports.jsx("polyline", { points: "3 6 5 6 21 6" }), jsxRuntimeExports.jsx("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })] }), "Delete"] })] }), jsxRuntimeExports.jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", style: { display: 'none' }, onChange: handleImageUpload })] }))] }), jsxRuntimeExports.jsxs("div", { className: "hdr-info", children: [displayName && jsxRuntimeExports.jsx("div", { className: "hdr-name", children: displayName }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-field-label", children: [getLabel('functionalId'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-field-value", children: functionalId || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsx("span", { className: "hdr-field-label", children: getLabel('status') }), isReadonly ? (statusLabel ? (jsxRuntimeExports.jsx("span", { className: "hdr-status-badge", style: { backgroundColor: statusColor }, children: statusLabel })) : (jsxRuntimeExports.jsx("span", { className: "hdr-field-value", children: "-" }))) : (jsxRuntimeExports.jsxs("select", { className: "hdr-select", value: statusValue, onChange: (e) => updateFieldValue('status', e.target.value), children: [jsxRuntimeExports.jsx("option", { value: "", children: getLabel('select') }), statusOptions.map((opt) => (jsxRuntimeExports.jsx("option", { value: opt.value, children: opt.label }, opt.value)))] }))] }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-field-label", children: [getLabel('statusReason'), " :"] }), isReadonly ? (jsxRuntimeExports.jsx("span", { className: "hdr-field-value", children: statusReason || '-' })) : (jsxRuntimeExports.jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [jsxRuntimeExports.jsx("input", { type: "text", className: `hdr-input ${(!isReadonly && (showReasonRequired || isReasonMissing) && isReasonMissing) ? 'hdr-input--error' : ''}`, value: statusReason, placeholder: getLabel('enterReason'), required: isStatusChanged, "aria-required": isStatusChanged, "aria-invalid": !isReadonly && (showReasonRequired || isReasonMissing) && isReasonMissing, onBlur: () => {
10591
+ } })) : null, jsxRuntimeExports.jsx("div", { className: "hdr-avatar-placeholder", style: { display: displayImageUrl ? 'none' : 'flex' }, children: jsxRuntimeExports.jsx("img", { src: img$f, alt: "Profile Placeholder" }) }), !isReadonly && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsxs("div", { className: "hdr-avatar-overlay", children: [jsxRuntimeExports.jsxs("button", { type: "button", className: "hdr-avatar-action", onClick: () => fileInputRef.current?.click(), children: [jsxRuntimeExports.jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntimeExports.jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }), jsxRuntimeExports.jsx("polyline", { points: "17 8 12 3 7 8" }), jsxRuntimeExports.jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })] }), "Upload"] }), jsxRuntimeExports.jsxs("button", { type: "button", className: "hdr-avatar-action hdr-avatar-action--delete", onClick: handleImageDelete, children: [jsxRuntimeExports.jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntimeExports.jsx("polyline", { points: "3 6 5 6 21 6" }), jsxRuntimeExports.jsx("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })] }), "Delete"] })] }), jsxRuntimeExports.jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", style: { display: 'none' }, onChange: handleImageUpload })] }))] }), jsxRuntimeExports.jsxs("div", { className: "hdr-info", children: [displayName && jsxRuntimeExports.jsx("div", { className: "hdr-name", children: displayName }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-field-label", title: `${getLabel('functionalId')} :`, children: [getLabel('functionalId'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-field-value", title: functionalId || '-', children: functionalId || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsx("span", { className: "hdr-field-label", title: getLabel('status'), children: getLabel('status') }), isReadonly ? (statusLabel ? (jsxRuntimeExports.jsx("span", { className: "hdr-status-badge", style: { backgroundColor: statusColor }, title: statusLabel, children: statusLabel })) : (jsxRuntimeExports.jsx("span", { className: "hdr-field-value", title: "-", children: "-" }))) : (jsxRuntimeExports.jsxs("select", { className: "hdr-select", value: statusValue, onChange: (e) => updateFieldValue('status', e.target.value), children: [jsxRuntimeExports.jsx("option", { value: "", children: getLabel('select') }), statusOptions.map((opt) => (jsxRuntimeExports.jsx("option", { value: opt.value, children: opt.label }, opt.value)))] }))] }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-field-label", title: `${getLabel('statusReason')} :`, children: [getLabel('statusReason'), " :"] }), isReadonly ? (jsxRuntimeExports.jsx("span", { className: "hdr-field-value", title: statusReason || '-', children: statusReason || '-' })) : (jsxRuntimeExports.jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [jsxRuntimeExports.jsx("input", { type: "text", className: `hdr-input ${(!isReadonly && (showReasonRequired || isReasonMissing) && isReasonMissing) ? 'hdr-input--error' : ''}`, value: statusReason, placeholder: getLabel('enterReason'), required: isStatusChanged, "aria-required": isStatusChanged, "aria-invalid": !isReadonly && (showReasonRequired || isReasonMissing) && isReasonMissing, onBlur: () => {
10333
10592
  if (isReasonMissing)
10334
10593
  setShowReasonRequired(true);
10335
10594
  }, onChange: (e) => {
@@ -10337,7 +10596,7 @@ const HeaderSectionWidget = ({ config }) => {
10337
10596
  if (showReasonRequired && String(e.target.value || '').trim().length > 0) {
10338
10597
  setShowReasonRequired(false);
10339
10598
  }
10340
- } }), !isReadonly && (showReasonRequired || isReasonMissing) && isReasonMissing ? (jsxRuntimeExports.jsx("div", { className: "hdr-error-text", children: getLabel('enterReason') })) : null] }))] })] })] }), jsxRuntimeExports.jsx("div", { className: "hdr-right", children: jsxRuntimeExports.jsxs("div", { className: "hdr-right-top", children: [jsxRuntimeExports.jsxs("div", { className: "hdr-meta-col", children: [jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", children: [getLabel('createdBy'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: createdBy || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", children: [getLabel('createdAt'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: createdAt || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", children: [getLabel('lastApprovedBy'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: lastApprovedBy || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", children: [getLabel('lastApprovedAt'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: lastApprovedAt || '-' })] })] }), score ? (jsxRuntimeExports.jsx("div", { className: "hdr-score-ring", style: { ['--pct']: score.percent }, "aria-label": `Completion score ${score.completionDisplay} of ${score.idealDisplay} (${score.percent}%)`, title: `${score.completionDisplay} / ${score.idealDisplay} (${score.percent}%)`, children: jsxRuntimeExports.jsx("div", { className: "hdr-score-value", children: String(score.completionDisplay) }) })) : null] }) })] })] }));
10599
+ } }), !isReadonly && (showReasonRequired || isReasonMissing) && isReasonMissing ? (jsxRuntimeExports.jsx("div", { className: "hdr-error-text", children: getLabel('enterReason') })) : null] }))] })] })] }), jsxRuntimeExports.jsx("div", { className: "hdr-right", children: jsxRuntimeExports.jsxs("div", { className: "hdr-right-top", children: [jsxRuntimeExports.jsxs("div", { className: "hdr-meta-col", children: [jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", title: `${getLabel('createdBy')} :`, children: [getLabel('createdBy'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", title: createdBy || '-', children: createdBy || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", title: `${getLabel('createdAt')} :`, children: [getLabel('createdAt'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", title: createdAt || '-', children: createdAt || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", title: `${getLabel('lastApprovedBy')} :`, children: [getLabel('lastApprovedBy'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", title: lastApprovedBy || '-', children: lastApprovedBy || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", title: `${getLabel('lastApprovedAt')} :`, children: [getLabel('lastApprovedAt'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", title: lastApprovedAt || '-', children: lastApprovedAt || '-' })] })] }), score ? (jsxRuntimeExports.jsx("div", { className: "hdr-score-ring", style: { ['--pct']: score.percent }, "aria-label": `Completion score ${score.completionDisplay} of ${score.idealDisplay} (${score.percent}%)`, title: `${score.completionDisplay} / ${score.idealDisplay} (${score.percent}%)`, children: jsxRuntimeExports.jsx("div", { className: "hdr-score-value", children: String(score.completionDisplay) }) })) : null] }) })] })] }));
10341
10600
  };
10342
10601
 
10343
10602
  function getValueByPathOrKey(obj, path) {