@bsol-oss/react-datatable5 12.0.0-beta.56 → 12.0.0-beta.58

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
@@ -29,8 +29,13 @@ var gr = require('react-icons/gr');
29
29
  var reactI18next = require('react-i18next');
30
30
  var axios = require('axios');
31
31
  var reactHookForm = require('react-hook-form');
32
+ var Ajv = require('ajv');
33
+ var addFormats = require('ajv-formats');
34
+ var zh_TW = require('ajv-i18n/localize/zh-TW');
35
+ var zh_CN = require('ajv-i18n/localize/zh');
32
36
  var dayjs = require('dayjs');
33
37
  var utc = require('dayjs/plugin/utc');
38
+ var timezone = require('dayjs/plugin/timezone');
34
39
  var ti = require('react-icons/ti');
35
40
 
36
41
  function _interopNamespaceDefault(e) {
@@ -197,7 +202,7 @@ const monthNamesFull = [
197
202
  "November",
198
203
  "December",
199
204
  ];
200
- const weekdayNamesShort$1 = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
205
+ const weekdayNamesShort = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
201
206
  function Calendar$1({ calendars, getBackProps, getForwardProps, getDateProps, selected = [], firstDayOfWeek = 0, }) {
202
207
  const [hoveredDate, setHoveredDate] = React.useState();
203
208
  const onMouseLeave = () => {
@@ -232,7 +237,7 @@ function Calendar$1({ calendars, getBackProps, getForwardProps, getDateProps, se
232
237
  offset: 12,
233
238
  }), children: ">>" })] }), jsxRuntime.jsx(react.Grid, { templateColumns: "repeat(2, auto)", justifyContent: "center", gap: 4, children: calendars.map((calendar) => (jsxRuntime.jsxs(react.Grid, { gap: 4, children: [jsxRuntime.jsxs(react.Grid, { justifyContent: "center", children: [monthNamesFull[calendar.month], " ", calendar.year] }), jsxRuntime.jsx(react.Grid, { templateColumns: "repeat(7, auto)", justifyContent: "center", children: [0, 1, 2, 3, 4, 5, 6].map((weekdayNum) => {
234
239
  const weekday = (weekdayNum + firstDayOfWeek) % 7;
235
- return (jsxRuntime.jsx(react.Box, { minWidth: "48px", textAlign: "center", children: weekdayNamesShort$1[weekday] }, `${calendar.month}${calendar.year}${weekday}`));
240
+ return (jsxRuntime.jsx(react.Box, { minWidth: "48px", textAlign: "center", children: weekdayNamesShort[weekday] }, `${calendar.month}${calendar.year}${weekday}`));
236
241
  }) }), jsxRuntime.jsx(react.Grid, { templateColumns: "repeat(7, auto)", justifyContent: "center", children: calendar.weeks.map((week, windex) => week.map((dateObj, index) => {
237
242
  const key = `${calendar.month}${calendar.year}${windex}${index}`;
238
243
  if (!dateObj) {
@@ -3688,6 +3693,8 @@ const SchemaFormContext = React.createContext({
3688
3693
  onSubmit: async () => { },
3689
3694
  rowNumber: 0,
3690
3695
  requestOptions: {},
3696
+ validationLocale: 'en',
3697
+ timezone: 'Asia/Hong_Kong',
3691
3698
  });
3692
3699
 
3693
3700
  const useSchemaContext = () => {
@@ -3698,6 +3705,179 @@ const clearEmptyString = (object) => {
3698
3705
  return Object.fromEntries(Object.entries(object).filter(([, value]) => value !== ""));
3699
3706
  };
3700
3707
 
3708
+ // AJV i18n support
3709
+ const localize = {
3710
+ en: () => { }, // English is default, no localization needed
3711
+ 'zh-HK': zh_TW, // Use zh-TW for Hong Kong Traditional Chinese
3712
+ 'zh-TW': zh_TW, // Traditional Chinese (Taiwan)
3713
+ 'zh-CN': zh_CN, // Simplified Chinese
3714
+ 'zh': zh_CN, // Simplified Chinese (short form)
3715
+ };
3716
+ // Create AJV instance with format support
3717
+ const createValidator = () => {
3718
+ const ajv = new Ajv({
3719
+ allErrors: true,
3720
+ verbose: true,
3721
+ removeAdditional: false,
3722
+ strict: false,
3723
+ messages: false, // Disable default messages for i18n
3724
+ });
3725
+ // Add format validation support (date, time, email, etc.)
3726
+ addFormats(ajv);
3727
+ return ajv;
3728
+ };
3729
+ /**
3730
+ * Validates data against a JSON Schema using AJV with i18n support
3731
+ * @param data - The data to validate
3732
+ * @param schema - The JSON Schema to validate against
3733
+ * @param options - Validation options including locale
3734
+ * @returns ValidationResult containing validation status and errors
3735
+ */
3736
+ const validateData = (data, schema, options = {}) => {
3737
+ const { locale = 'en' } = options;
3738
+ const ajv = createValidator();
3739
+ try {
3740
+ const validate = ajv.compile(schema);
3741
+ const isValid = validate(data);
3742
+ if (isValid) {
3743
+ return {
3744
+ isValid: true,
3745
+ errors: [],
3746
+ };
3747
+ }
3748
+ // Apply localization if not English
3749
+ if (locale !== 'en' && validate.errors && localize[locale]) {
3750
+ try {
3751
+ localize[locale](validate.errors);
3752
+ }
3753
+ catch (error) {
3754
+ console.warn(`Failed to localize validation errors to ${locale}:`, error);
3755
+ }
3756
+ }
3757
+ const errors = (validate.errors || []).map((error) => {
3758
+ const field = error.instancePath?.replace(/^\//, '') || error.schemaPath?.split('/').pop() || 'root';
3759
+ let message = error.message || 'Validation error';
3760
+ // Enhanced error messages for better UX (only if using English or localization failed)
3761
+ if (locale === 'en' || !error.message) {
3762
+ switch (error.keyword) {
3763
+ case 'required':
3764
+ message = `${error.params?.missingProperty || 'Field'} is required`;
3765
+ break;
3766
+ case 'format':
3767
+ message = `Invalid ${error.params?.format} format`;
3768
+ break;
3769
+ case 'type':
3770
+ message = `Expected ${error.params?.type}, got ${typeof error.data}`;
3771
+ break;
3772
+ case 'minLength':
3773
+ message = `Must be at least ${error.params?.limit} characters`;
3774
+ break;
3775
+ case 'maxLength':
3776
+ message = `Must be no more than ${error.params?.limit} characters`;
3777
+ break;
3778
+ case 'minimum':
3779
+ message = `Must be at least ${error.params?.limit}`;
3780
+ break;
3781
+ case 'maximum':
3782
+ message = `Must be no more than ${error.params?.limit}`;
3783
+ break;
3784
+ case 'pattern':
3785
+ message = `Does not match required pattern`;
3786
+ break;
3787
+ case 'enum':
3788
+ message = `Must be one of: ${error.params?.allowedValues?.join(', ')}`;
3789
+ break;
3790
+ default:
3791
+ message = error.message || 'Validation error';
3792
+ }
3793
+ }
3794
+ return {
3795
+ field: field || error.instancePath || 'unknown',
3796
+ message,
3797
+ value: error.data,
3798
+ schemaPath: error.schemaPath,
3799
+ };
3800
+ });
3801
+ return {
3802
+ isValid: false,
3803
+ errors,
3804
+ };
3805
+ }
3806
+ catch (error) {
3807
+ // Handle AJV compilation errors
3808
+ const errorMessage = locale === 'zh-HK' || locale === 'zh-TW'
3809
+ ? `架構驗證錯誤: ${error instanceof Error ? error.message : '未知錯誤'}`
3810
+ : locale === 'zh-CN' || locale === 'zh'
3811
+ ? `模式验证错误: ${error instanceof Error ? error.message : '未知错误'}`
3812
+ : `Schema validation error: ${error instanceof Error ? error.message : 'Unknown error'}`;
3813
+ return {
3814
+ isValid: false,
3815
+ errors: [
3816
+ {
3817
+ field: 'schema',
3818
+ message: errorMessage,
3819
+ },
3820
+ ],
3821
+ };
3822
+ }
3823
+ };
3824
+ /**
3825
+ * Creates a reusable validator function for a specific schema with i18n support
3826
+ * @param schema - The JSON Schema to create validator for
3827
+ * @param locale - The locale to use for error messages
3828
+ * @returns A function that validates data against the schema
3829
+ */
3830
+ const createSchemaValidator = (schema, locale = 'en') => {
3831
+ const ajv = createValidator();
3832
+ const validate = ajv.compile(schema);
3833
+ return (data) => {
3834
+ const isValid = validate(data);
3835
+ if (isValid) {
3836
+ return {
3837
+ isValid: true,
3838
+ errors: [],
3839
+ };
3840
+ }
3841
+ // Apply localization if not English
3842
+ if (locale !== 'en' && validate.errors && localize[locale]) {
3843
+ try {
3844
+ localize[locale](validate.errors);
3845
+ }
3846
+ catch (error) {
3847
+ console.warn(`Failed to localize validation errors to ${locale}:`, error);
3848
+ }
3849
+ }
3850
+ const errors = (validate.errors || []).map((error) => {
3851
+ const field = error.instancePath?.replace(/^\//, '') || 'root';
3852
+ return {
3853
+ field,
3854
+ message: error.message || 'Validation error',
3855
+ value: error.data,
3856
+ schemaPath: error.schemaPath,
3857
+ };
3858
+ });
3859
+ return {
3860
+ isValid: false,
3861
+ errors,
3862
+ };
3863
+ };
3864
+ };
3865
+ /**
3866
+ * Get available locales for validation error messages
3867
+ * @returns Array of supported locale codes
3868
+ */
3869
+ const getSupportedLocales = () => {
3870
+ return Object.keys(localize);
3871
+ };
3872
+ /**
3873
+ * Check if a locale is supported
3874
+ * @param locale - The locale to check
3875
+ * @returns Boolean indicating if the locale is supported
3876
+ */
3877
+ const isLocaleSupported = (locale) => {
3878
+ return locale in localize;
3879
+ };
3880
+
3701
3881
  const idPickerSanityCheck = (column, foreign_key) => {
3702
3882
  if (!!foreign_key == false) {
3703
3883
  throw new Error(`The key foreign_key does not exist in properties of column ${column} when using id-picker.`);
@@ -3713,7 +3893,7 @@ const idPickerSanityCheck = (column, foreign_key) => {
3713
3893
  throw new Error(`The key column does not exist in properties of column ${column} when using id-picker.`);
3714
3894
  }
3715
3895
  };
3716
- const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, children, order = [], ignore = [], include = [], onSubmit = undefined, rowNumber = undefined, requestOptions = {}, getUpdatedData = () => { }, customErrorRenderer, }) => {
3896
+ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, children, order = [], ignore = [], include = [], onSubmit = undefined, rowNumber = undefined, requestOptions = {}, getUpdatedData = () => { }, customErrorRenderer, validationLocale = 'en', }) => {
3717
3897
  const [isSuccess, setIsSuccess] = React.useState(false);
3718
3898
  const [isError, setIsError] = React.useState(false);
3719
3899
  const [isSubmiting, setIsSubmiting] = React.useState(false);
@@ -3747,6 +3927,7 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
3747
3927
  setError,
3748
3928
  getUpdatedData,
3749
3929
  customErrorRenderer,
3930
+ validationLocale,
3750
3931
  }, children: jsxRuntime.jsx(reactHookForm.FormProvider, { ...form, children: children }) }));
3751
3932
  };
3752
3933
 
@@ -3820,27 +4001,14 @@ const CustomInput = ({ column, schema, prefix }) => {
3820
4001
  }));
3821
4002
  };
3822
4003
 
3823
- const monthNamesShort = [
3824
- "Jan",
3825
- "Feb",
3826
- "Mar",
3827
- "Apr",
3828
- "May",
3829
- "Jun",
3830
- "Jul",
3831
- "Aug",
3832
- "Sep",
3833
- "Oct",
3834
- "Nov",
3835
- "Dec",
3836
- ];
3837
- const weekdayNamesShort = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
3838
4004
  const Calendar = ({ calendars, getBackProps, getForwardProps, getDateProps, firstDayOfWeek = 0, }) => {
4005
+ const { labels } = React.useContext(DatePickerContext);
4006
+ const { monthNamesShort, weekdayNamesShort, backButtonLabel, forwardButtonLabel } = labels;
3839
4007
  if (calendars.length) {
3840
4008
  return (jsxRuntime.jsxs(react.Grid, { children: [jsxRuntime.jsxs(react.Grid, { templateColumns: "repeat(4, auto)", justifyContent: "center", children: [jsxRuntime.jsx(react.Button, { variant: "ghost", ...getBackProps({
3841
4009
  calendars,
3842
4010
  offset: 12,
3843
- }), children: "<<" }), jsxRuntime.jsx(react.Button, { variant: "ghost", ...getBackProps({ calendars }), children: "Back" }), jsxRuntime.jsx(react.Button, { variant: "ghost", ...getForwardProps({ calendars }), children: "Next" }), jsxRuntime.jsx(react.Button, { variant: "ghost", ...getForwardProps({
4011
+ }), children: "<<" }), jsxRuntime.jsx(react.Button, { variant: "ghost", ...getBackProps({ calendars }), children: backButtonLabel }), jsxRuntime.jsx(react.Button, { variant: "ghost", ...getForwardProps({ calendars }), children: forwardButtonLabel }), jsxRuntime.jsx(react.Button, { variant: "ghost", ...getForwardProps({
3844
4012
  calendars,
3845
4013
  offset: 12,
3846
4014
  }), children: ">>" })] }), jsxRuntime.jsx(react.Grid, { templateColumns: "repeat(2, auto)", justifyContent: "center", children: calendars.map((calendar) => (jsxRuntime.jsxs(react.Grid, { gap: 4, children: [jsxRuntime.jsxs(react.Grid, { justifyContent: "center", children: [monthNamesShort[calendar.month], " ", calendar.year] }), jsxRuntime.jsxs(react.Grid, { templateColumns: "repeat(7, auto)", justifyContent: "center", children: [[0, 1, 2, 3, 4, 5, 6].map((weekdayNum) => {
@@ -3883,9 +4051,52 @@ const Calendar = ({ calendars, getBackProps, getForwardProps, getDateProps, firs
3883
4051
  }
3884
4052
  return null;
3885
4053
  };
4054
+ const DatePickerContext = React.createContext({
4055
+ labels: {
4056
+ monthNamesShort: [
4057
+ "Jan",
4058
+ "Feb",
4059
+ "Mar",
4060
+ "Apr",
4061
+ "May",
4062
+ "Jun",
4063
+ "Jul",
4064
+ "Aug",
4065
+ "Sep",
4066
+ "Oct",
4067
+ "Nov",
4068
+ "Dec",
4069
+ ],
4070
+ weekdayNamesShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
4071
+ backButtonLabel: "Back",
4072
+ forwardButtonLabel: "Next",
4073
+ },
4074
+ });
3886
4075
  let DatePicker$1 = class DatePicker extends React.Component {
3887
4076
  render() {
3888
- return (jsxRuntime.jsx(Dayzed, { onDateSelected: this.props.onDateSelected, selected: this.props.selected, firstDayOfWeek: this.props.firstDayOfWeek, showOutsideDays: this.props.showOutsideDays, date: this.props.date, minDate: this.props.minDate, maxDate: this.props.maxDate, monthsToDisplay: this.props.monthsToDisplay, render: (dayzedData) => (jsxRuntime.jsx(Calendar, { ...dayzedData, firstDayOfWeek: this.props.firstDayOfWeek })) }));
4077
+ const { labels = {
4078
+ monthNamesShort: [
4079
+ "Jan",
4080
+ "Feb",
4081
+ "Mar",
4082
+ "Apr",
4083
+ "May",
4084
+ "Jun",
4085
+ "Jul",
4086
+ "Aug",
4087
+ "Sep",
4088
+ "Oct",
4089
+ "Nov",
4090
+ "Dec",
4091
+ ],
4092
+ weekdayNamesShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
4093
+ backButtonLabel: "Back",
4094
+ forwardButtonLabel: "Next",
4095
+ }, } = this.props;
4096
+ return (jsxRuntime.jsx(DatePickerContext.Provider, { value: { labels }, children: jsxRuntime.jsx(Dayzed, { onDateSelected: this.props.onDateSelected, selected: this.props.selected, firstDayOfWeek: this.props.firstDayOfWeek, showOutsideDays: this.props.showOutsideDays, date: this.props.date, minDate: this.props.minDate, maxDate: this.props.maxDate, monthsToDisplay: this.props.monthsToDisplay, render:
4097
+ // @ts-expect-error - Dayzed types need to be fixed
4098
+ (dayzedData) => (jsxRuntime.jsx(Calendar, { ...dayzedData,
4099
+ firstDayOfWeek: this.props.firstDayOfWeek })) }) }));
3889
4100
  }
3890
4101
  };
3891
4102
 
@@ -3907,24 +4118,27 @@ const PopoverRoot = react.Popover.Root;
3907
4118
  const PopoverBody = react.Popover.Body;
3908
4119
  const PopoverTrigger = react.Popover.Trigger;
3909
4120
 
4121
+ function translateWrapper({ prefix, column, label, translate, }) {
4122
+ return translate.t(removeIndex(`${prefix}${column}.${label}`));
4123
+ }
4124
+
3910
4125
  dayjs.extend(utc);
4126
+ dayjs.extend(timezone);
3911
4127
  const DatePicker = ({ column, schema, prefix }) => {
3912
4128
  const { watch, formState: { errors }, setValue, } = reactHookForm.useFormContext();
3913
- const { translate } = useSchemaContext();
3914
- const { required, gridColumn = "span 4", gridRow = "span 1", displayDateFormat = "YYYY-MM-DD", dateFormat = "YYYY-MM-DD[T]HH:mm:ss[Z]", } = schema;
4129
+ const { translate, timezone } = useSchemaContext();
4130
+ const { required, gridColumn = "span 4", gridRow = "span 1", displayDateFormat = "YYYY-MM-DD", dateFormat = "YYYY-MM-DD", } = schema;
3915
4131
  const isRequired = required?.some((columnId) => columnId === column);
3916
4132
  const colLabel = `${prefix}${column}`;
3917
4133
  const [open, setOpen] = React.useState(false);
3918
4134
  const selectedDate = watch(colLabel);
3919
- const displayDate = dayjs.utc(selectedDate).format(displayDateFormat);
4135
+ const displayDate = dayjs(selectedDate).tz(timezone).format(displayDateFormat);
3920
4136
  React.useEffect(() => {
3921
4137
  try {
3922
4138
  if (selectedDate) {
3923
4139
  // Parse the selectedDate as UTC or in a specific timezone to avoid +8 hour shift
3924
4140
  // For example, parse as UTC:
3925
- const parsedDate = dayjs.utc(selectedDate);
3926
- // Or if you want to parse in local timezone without shifting:
3927
- // const parsedDate = dayjs.tz(selectedDate, dayjs.tz.guess());
4141
+ const parsedDate = dayjs(selectedDate).tz(timezone);
3928
4142
  if (!parsedDate.isValid())
3929
4143
  return;
3930
4144
  // Format according to dateFormat from schema
@@ -3942,19 +4156,48 @@ const DatePicker = ({ column, schema, prefix }) => {
3942
4156
  console.error(e);
3943
4157
  }
3944
4158
  }, [selectedDate, dateFormat, colLabel, setValue]);
3945
- return (jsxRuntime.jsxs(Field, { label: `${translate.t(removeIndex(`${colLabel}.field_label`))}`, required: isRequired, alignItems: "stretch", gridColumn,
4159
+ const customTranslate = (label) => {
4160
+ return translateWrapper({ prefix, column, label, translate });
4161
+ };
4162
+ return (jsxRuntime.jsxs(Field, { label: `${customTranslate(`field_label`)}`, required: isRequired, alignItems: "stretch", gridColumn,
3946
4163
  gridRow, children: [jsxRuntime.jsxs(PopoverRoot, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: jsxRuntime.jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
3947
4164
  setOpen(true);
3948
- }, justifyContent: "start", children: [jsxRuntime.jsx(md.MdDateRange, {}), selectedDate !== undefined ? `${displayDate}` : ""] }) }), jsxRuntime.jsx(PopoverContent, { children: jsxRuntime.jsxs(PopoverBody, { children: [jsxRuntime.jsx(PopoverTitle, {}), jsxRuntime.jsx(DatePicker$1
3949
- // @ts-expect-error TODO: find appropriate types
3950
- , {
3951
- // @ts-expect-error TODO: find appropriate types
3952
- selected: new Date(selectedDate),
3953
- // @ts-expect-error TODO: find appropriate types
3954
- onDateSelected: ({ date }) => {
4165
+ }, justifyContent: "start", children: [jsxRuntime.jsx(md.MdDateRange, {}), selectedDate !== undefined ? `${displayDate}` : ""] }) }), jsxRuntime.jsx(PopoverContent, { children: jsxRuntime.jsxs(PopoverBody, { children: [jsxRuntime.jsx(PopoverTitle, {}), jsxRuntime.jsx(DatePicker$1, { selected: new Date(selectedDate), onDateSelected: ({ date }) => {
3955
4166
  setValue(colLabel, dayjs(date).format(dateFormat));
3956
4167
  setOpen(false);
3957
- } })] }) })] }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }));
4168
+ }, labels: {
4169
+ monthNamesShort: [
4170
+ translate.t(`common.month_1`, { defaultValue: "January" }),
4171
+ translate.t(`common.month_2`, { defaultValue: "February" }),
4172
+ translate.t(`common.month_3`, { defaultValue: "March" }),
4173
+ translate.t(`common.month_4`, { defaultValue: "April" }),
4174
+ translate.t(`common.month_5`, { defaultValue: "May" }),
4175
+ translate.t(`common.month_6`, { defaultValue: "June" }),
4176
+ translate.t(`common.month_7`, { defaultValue: "July" }),
4177
+ translate.t(`common.month_8`, { defaultValue: "August" }),
4178
+ translate.t(`common.month_9`, { defaultValue: "September" }),
4179
+ translate.t(`common.month_10`, { defaultValue: "October" }),
4180
+ translate.t(`common.month_11`, { defaultValue: "November" }),
4181
+ translate.t(`common.month_12`, { defaultValue: "December" }),
4182
+ ],
4183
+ weekdayNamesShort: [
4184
+ translate.t(`common.weekday_1`, { defaultValue: "Sun" }),
4185
+ translate.t(`common.weekday_2`, { defaultValue: "Mon" }),
4186
+ translate.t(`common.weekday_3`, { defaultValue: "Tue" }),
4187
+ translate.t(`common.weekday_4`, {
4188
+ defaultValue: "Wed",
4189
+ }),
4190
+ translate.t(`common.weekday_5`, { defaultValue: "Thu" }),
4191
+ translate.t(`common.weekday_6`, { defaultValue: "Fri" }),
4192
+ translate.t(`common.weekday_7`, { defaultValue: "Sat" }),
4193
+ ],
4194
+ backButtonLabel: translate.t(`common.back_button`, {
4195
+ defaultValue: "Back",
4196
+ }),
4197
+ forwardButtonLabel: translate.t(`common.forward_button`, {
4198
+ defaultValue: "Forward",
4199
+ }),
4200
+ } })] }) })] }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: "red.400", children: customTranslate(`field_required`) }))] }));
3958
4201
  };
3959
4202
 
3960
4203
  function filterArray(array, searchTerm) {
@@ -4642,7 +4885,7 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
4642
4885
 
4643
4886
  const NumberInputRoot = React__namespace.forwardRef(function NumberInput(props, ref) {
4644
4887
  const { children, ...rest } = props;
4645
- return (jsxRuntime.jsxs(react.NumberInput.Root, { ref: ref, variant: "outline", ...rest, children: [children, jsxRuntime.jsxs(react.NumberInput.Control, { children: [jsxRuntime.jsx(react.NumberInput.IncrementTrigger, {}), jsxRuntime.jsx(react.NumberInput.DecrementTrigger, {})] })] }));
4888
+ return (jsxRuntime.jsx(react.NumberInput.Root, { ref: ref, variant: "outline", ...rest, children: children }));
4646
4889
  });
4647
4890
  const NumberInputField$1 = react.NumberInput.Input;
4648
4891
  react.NumberInput.Scrubber;
@@ -4937,22 +5180,23 @@ function TimePicker$1({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
4937
5180
  return (jsxRuntime.jsx(react.Flex, { direction: "column", gap: 3, children: jsxRuntime.jsxs(react.Grid, { justifyContent: "center", alignItems: "center", templateColumns: "60px 10px 60px 90px auto", gap: "2", width: "auto", minWidth: "250px", children: [jsxRuntime.jsx(react.Input, { ref: hourInputRef, type: "text", value: hour === null ? "" : hour.toString().padStart(2, "0"), onKeyDown: (e) => handleKeyDown(e, "hour"), placeholder: "HH", maxLength: 2, textAlign: "center" }), jsxRuntime.jsx(react.Text, { children: ":" }), jsxRuntime.jsx(react.Input, { ref: minuteInputRef, type: "text", value: minute === null ? "" : minute.toString().padStart(2, "0"), onKeyDown: (e) => handleKeyDown(e, "minute"), placeholder: "MM", maxLength: 2, textAlign: "center" }), jsxRuntime.jsxs(react.Flex, { gap: "1", children: [jsxRuntime.jsx(react.Button, { size: "sm", colorScheme: meridiem === "am" ? "blue" : "gray", variant: meridiem === "am" ? "solid" : "outline", onClick: () => handleMeridiemClick("am"), width: "40px", children: meridiemLabel.am }), jsxRuntime.jsx(react.Button, { size: "sm", colorScheme: meridiem === "pm" ? "blue" : "gray", variant: meridiem === "pm" ? "solid" : "outline", onClick: () => handleMeridiemClick("pm"), width: "40px", children: meridiemLabel.pm })] }), jsxRuntime.jsx(react.Button, { onClick: handleClear, size: "sm", variant: "ghost", children: jsxRuntime.jsx(md.MdCancel, {}) })] }) }));
4938
5181
  }
4939
5182
 
5183
+ dayjs.extend(timezone);
4940
5184
  const TimePicker = ({ column, schema, prefix }) => {
4941
5185
  const { watch, formState: { errors }, setValue, } = reactHookForm.useFormContext();
4942
- const { translate } = useSchemaContext();
4943
- const { required, gridColumn = "span 4", gridRow = "span 1", timeFormat = "HH:mm:ss", displayTimeFormat = "hh:mm A", } = schema;
5186
+ const { translate, timezone } = useSchemaContext();
5187
+ const { required, gridColumn = "span 4", gridRow = "span 1", timeFormat = "HH:mm:ssZ", displayTimeFormat = "hh:mm A", } = schema;
4944
5188
  const isRequired = required?.some((columnId) => columnId === column);
4945
5189
  const colLabel = `${prefix}${column}`;
4946
5190
  const [open, setOpen] = React.useState(false);
4947
5191
  const value = watch(colLabel);
4948
- const displayedTime = dayjs(`1970-01-01T${value}Z`).isValid()
4949
- ? dayjs(`1970-01-01T${value}Z`).utc().format(displayTimeFormat)
5192
+ const displayedTime = dayjs(`1970-01-01T${value}`).tz(timezone).isValid()
5193
+ ? dayjs(`1970-01-01T${value}`).tz(timezone).format(displayTimeFormat)
4950
5194
  : "";
4951
5195
  // Parse the initial time parts from the ISO time string (HH:mm:ss)
4952
5196
  const parseTime = (isoTime) => {
4953
5197
  if (!isoTime)
4954
5198
  return { hour: 12, minute: 0, meridiem: "am" };
4955
- const parsed = dayjs(`1970-01-01T${isoTime}Z`).utc();
5199
+ const parsed = dayjs(`1970-01-01T${isoTime}`).tz(timezone);
4956
5200
  if (!parsed.isValid())
4957
5201
  return { hour: 12, minute: 0, meridiem: "am" };
4958
5202
  let hour = parsed.hour();
@@ -4983,8 +5227,8 @@ const TimePicker = ({ column, schema, prefix }) => {
4983
5227
  h = 0;
4984
5228
  else if (meridiem === "pm" && hour < 12)
4985
5229
  h = hour + 12;
4986
- return dayjs(`1970-01-01T${h.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}:00Z`)
4987
- .utc()
5230
+ return dayjs(`1970-01-01T${h.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}:00`)
5231
+ .tz(timezone)
4988
5232
  .format(timeFormat);
4989
5233
  };
4990
5234
  // Handle changes to time parts
@@ -5000,11 +5244,289 @@ const TimePicker = ({ column, schema, prefix }) => {
5000
5244
  gridRow, children: [jsxRuntime.jsxs(react.Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
5001
5245
  setOpen(true);
5002
5246
  }, justifyContent: "start", children: [jsxRuntime.jsx(io.IoMdClock, {}), !!value ? `${displayedTime}` : ""] }) }), jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { ref: containerRef, children: jsxRuntime.jsx(react.Popover.Body, { children: jsxRuntime.jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, meridiemLabel: {
5003
- am: translate.t(removeIndex(`common.am`)),
5004
- pm: translate.t(removeIndex(`common.pm`)),
5247
+ am: translate.t(`common.am`, { defaultValue: "AM" }),
5248
+ pm: translate.t(`common.pm`, { defaultValue: "PM" }),
5005
5249
  } }) }) }) }) })] }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }));
5006
5250
  };
5007
5251
 
5252
+ function IsoTimePicker({ hour, setHour, minute, setMinute, second, setSecond, onChange = (_newValue) => { }, }) {
5253
+ // Refs for focus management
5254
+ const hourInputRef = React.useRef(null);
5255
+ const minuteInputRef = React.useRef(null);
5256
+ const secondInputRef = React.useRef(null);
5257
+ // Centralized handler for key events, value changes, and focus management
5258
+ const handleKeyDown = (e, field) => {
5259
+ const input = e.target;
5260
+ const value = input.value;
5261
+ // Handle navigation between fields
5262
+ if (e.key === "Tab") {
5263
+ return;
5264
+ }
5265
+ if (e.key === ":" && field === "hour") {
5266
+ e.preventDefault();
5267
+ minuteInputRef.current?.focus();
5268
+ return;
5269
+ }
5270
+ if (e.key === ":" && field === "minute") {
5271
+ e.preventDefault();
5272
+ secondInputRef.current?.focus();
5273
+ return;
5274
+ }
5275
+ if (e.key === "Backspace" && value === "") {
5276
+ e.preventDefault();
5277
+ if (field === "minute") {
5278
+ hourInputRef.current?.focus();
5279
+ }
5280
+ else if (field === "second") {
5281
+ minuteInputRef.current?.focus();
5282
+ }
5283
+ return;
5284
+ }
5285
+ // Handle number inputs
5286
+ if (field === "hour") {
5287
+ if (e.key.match(/^[0-9]$/)) {
5288
+ const newValue = value + e.key;
5289
+ const numValue = parseInt(newValue, 10);
5290
+ if (numValue > 23) {
5291
+ const digitValue = parseInt(e.key, 10);
5292
+ setHour(digitValue);
5293
+ onChange({ hour: digitValue, minute, second });
5294
+ return;
5295
+ }
5296
+ if (numValue >= 0 && numValue <= 23) {
5297
+ setHour(numValue);
5298
+ onChange({ hour: numValue, minute, second });
5299
+ e.preventDefault();
5300
+ minuteInputRef.current?.focus();
5301
+ }
5302
+ }
5303
+ }
5304
+ else if (field === "minute") {
5305
+ if (e.key.match(/^[0-9]$/)) {
5306
+ const newValue = value + e.key;
5307
+ const numValue = parseInt(newValue, 10);
5308
+ if (numValue > 59) {
5309
+ const digitValue = parseInt(e.key, 10);
5310
+ setMinute(digitValue);
5311
+ onChange({ hour, minute: digitValue, second });
5312
+ return;
5313
+ }
5314
+ if (numValue >= 0 && numValue <= 59) {
5315
+ setMinute(numValue);
5316
+ onChange({ hour, minute: numValue, second });
5317
+ e.preventDefault();
5318
+ secondInputRef.current?.focus();
5319
+ }
5320
+ }
5321
+ }
5322
+ else if (field === "second") {
5323
+ if (e.key.match(/^[0-9]$/)) {
5324
+ const newValue = value + e.key;
5325
+ const numValue = parseInt(newValue, 10);
5326
+ if (numValue > 59) {
5327
+ const digitValue = parseInt(e.key, 10);
5328
+ setSecond(digitValue);
5329
+ onChange({ hour, minute, second: digitValue });
5330
+ return;
5331
+ }
5332
+ if (numValue >= 0 && numValue <= 59) {
5333
+ setSecond(numValue);
5334
+ onChange({ hour, minute, second: numValue });
5335
+ }
5336
+ }
5337
+ }
5338
+ };
5339
+ const handleClear = () => {
5340
+ setHour(null);
5341
+ setMinute(null);
5342
+ setSecond(null);
5343
+ onChange({ hour: null, minute: null, second: null });
5344
+ hourInputRef.current?.focus();
5345
+ };
5346
+ return (jsxRuntime.jsx(react.Flex, { direction: "column", gap: 3, children: jsxRuntime.jsxs(react.Grid, { justifyContent: "center", alignItems: "center", templateColumns: "60px 10px 60px 10px 60px auto", gap: "2", width: "auto", minWidth: "300px", children: [jsxRuntime.jsx(react.Input, { ref: hourInputRef, type: "text", value: hour === null ? "" : hour.toString().padStart(2, "0"), onKeyDown: (e) => handleKeyDown(e, "hour"), placeholder: "HH", maxLength: 2, textAlign: "center" }), jsxRuntime.jsx(react.Text, { children: ":" }), jsxRuntime.jsx(react.Input, { ref: minuteInputRef, type: "text", value: minute === null ? "" : minute.toString().padStart(2, "0"), onKeyDown: (e) => handleKeyDown(e, "minute"), placeholder: "MM", maxLength: 2, textAlign: "center" }), jsxRuntime.jsx(react.Text, { children: ":" }), jsxRuntime.jsx(react.Input, { ref: secondInputRef, type: "text", value: second === null ? "" : second.toString().padStart(2, "0"), onKeyDown: (e) => handleKeyDown(e, "second"), placeholder: "SS", maxLength: 2, textAlign: "center" }), jsxRuntime.jsx(react.Button, { onClick: handleClear, size: "sm", variant: "ghost", children: jsxRuntime.jsx(md.MdCancel, {}) })] }) }));
5347
+ }
5348
+
5349
+ function DateTimePicker$1({ value, onChange, format = "date-time", showSeconds = false, labels = {
5350
+ monthNamesShort: [
5351
+ "Jan",
5352
+ "Feb",
5353
+ "Mar",
5354
+ "Apr",
5355
+ "May",
5356
+ "Jun",
5357
+ "Jul",
5358
+ "Aug",
5359
+ "Sep",
5360
+ "Oct",
5361
+ "Nov",
5362
+ "Dec",
5363
+ ],
5364
+ weekdayNamesShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
5365
+ backButtonLabel: "Back",
5366
+ forwardButtonLabel: "Next",
5367
+ }, timezone = "Asia/Hong_Kong", }) {
5368
+ const [selectedDate, setSelectedDate] = React.useState(value || "");
5369
+ // Time state for 12-hour format
5370
+ const [hour12, setHour12] = React.useState(value ? dayjs(value).hour() % 12 || 12 : null);
5371
+ const [minute, setMinute] = React.useState(value ? dayjs(value).minute() : null);
5372
+ const [meridiem, setMeridiem] = React.useState(value ? (dayjs(value).hour() >= 12 ? "pm" : "am") : null);
5373
+ // Time state for 24-hour format
5374
+ const [hour24, setHour24] = React.useState(value ? dayjs(value).hour() : null);
5375
+ const [second, setSecond] = React.useState(value ? dayjs(value).second() : null);
5376
+ const handleDateChange = (date) => {
5377
+ setSelectedDate(date);
5378
+ updateDateTime(dayjs(date).tz(timezone).toISOString());
5379
+ };
5380
+ const handleTimeChange = (timeData) => {
5381
+ if (format === "iso-date-time") {
5382
+ setHour24(timeData.hour);
5383
+ setMinute(timeData.minute);
5384
+ if (showSeconds)
5385
+ setSecond(timeData.second);
5386
+ }
5387
+ else {
5388
+ setHour12(timeData.hour);
5389
+ setMinute(timeData.minute);
5390
+ setMeridiem(timeData.meridiem);
5391
+ }
5392
+ updateDateTime(dayjs(selectedDate).tz(timezone).toISOString(), timeData);
5393
+ };
5394
+ const updateDateTime = (date, timeData) => {
5395
+ if (!date) {
5396
+ onChange?.(undefined);
5397
+ return;
5398
+ }
5399
+ // use dayjs to convert the date to the timezone
5400
+ const newDate = dayjs(date).tz(timezone).toDate();
5401
+ if (format === "iso-date-time") {
5402
+ const h = timeData?.hour ?? hour24;
5403
+ const m = timeData?.minute ?? minute;
5404
+ const s = showSeconds ? timeData?.second ?? second : 0;
5405
+ if (h !== null)
5406
+ newDate.setHours(h);
5407
+ if (m !== null)
5408
+ newDate.setMinutes(m);
5409
+ if (s !== null)
5410
+ newDate.setSeconds(s);
5411
+ }
5412
+ else {
5413
+ const h = timeData?.hour ?? hour12;
5414
+ const m = timeData?.minute ?? minute;
5415
+ const mer = timeData?.meridiem ?? meridiem;
5416
+ if (h !== null && mer !== null) {
5417
+ let hour24 = h;
5418
+ if (mer === "am" && h === 12)
5419
+ hour24 = 0;
5420
+ else if (mer === "pm" && h < 12)
5421
+ hour24 = h + 12;
5422
+ newDate.setHours(hour24);
5423
+ }
5424
+ if (m !== null)
5425
+ newDate.setMinutes(m);
5426
+ newDate.setSeconds(0);
5427
+ }
5428
+ onChange?.(dayjs(newDate).tz(timezone).toISOString());
5429
+ };
5430
+ const handleClear = () => {
5431
+ setSelectedDate("");
5432
+ setHour12(null);
5433
+ setHour24(null);
5434
+ setMinute(null);
5435
+ setSecond(null);
5436
+ setMeridiem(null);
5437
+ onChange?.(undefined);
5438
+ };
5439
+ const isISO = format === "iso-date-time";
5440
+ return (jsxRuntime.jsxs(react.Flex, { direction: "column", gap: 4, p: 4, border: "1px solid", borderColor: "gray.200", borderRadius: "md", children: [jsxRuntime.jsx(DatePicker$1, { selected: selectedDate
5441
+ ? dayjs(selectedDate).tz(timezone).toDate()
5442
+ : new Date(), onDateSelected: ({ date }) => handleDateChange(dayjs(date).tz(timezone).toISOString()), monthsToDisplay: 1, labels: labels }), jsxRuntime.jsxs(react.Grid, { templateColumns: "1fr auto", alignItems: "center", gap: 4, children: [isISO ? (jsxRuntime.jsx(IsoTimePicker, { hour: hour24, setHour: setHour24, minute: minute, setMinute: setMinute, second: second, setSecond: setSecond, onChange: handleTimeChange })) : (jsxRuntime.jsx(TimePicker$1, { hour: hour12, setHour: setHour12, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange })), jsxRuntime.jsx(react.Button, { onClick: handleClear, size: "sm", variant: "outline", colorScheme: "red", children: jsxRuntime.jsx(react.Icon, { as: fa6.FaTrash }) })] }), selectedDate && (jsxRuntime.jsxs(react.Flex, { gap: 2, children: [jsxRuntime.jsx(react.Text, { fontSize: "sm", color: { base: "gray.600", _dark: "gray.600" }, children: dayjs(value).format(isISO
5443
+ ? showSeconds
5444
+ ? "YYYY-MM-DD HH:mm:ss"
5445
+ : "YYYY-MM-DD HH:mm"
5446
+ : "YYYY-MM-DD hh:mm A ") }), jsxRuntime.jsx(react.Text, { fontSize: "sm", color: { base: "gray.600", _dark: "gray.600" }, children: dayjs(value).tz(timezone).format("Z") }), jsxRuntime.jsx(react.Text, { fontSize: "sm", color: { base: "gray.600", _dark: "gray.600" }, children: timezone })] }))] }));
5447
+ }
5448
+
5449
+ dayjs.extend(utc);
5450
+ dayjs.extend(timezone);
5451
+ const DateTimePicker = ({ column, schema, prefix, }) => {
5452
+ const { watch, formState: { errors }, setValue, } = reactHookForm.useFormContext();
5453
+ const { translate, timezone } = useSchemaContext();
5454
+ const { required, gridColumn = "span 4", gridRow = "span 1", displayDateFormat = "YYYY-MM-DD HH:mm:ss",
5455
+ // with timezone
5456
+ dateFormat = "YYYY-MM-DD[T]HH:mm:ssZ", } = schema;
5457
+ const isRequired = required?.some((columnId) => columnId === column);
5458
+ const colLabel = `${prefix}${column}`;
5459
+ const [open, setOpen] = React.useState(false);
5460
+ const selectedDate = watch(colLabel);
5461
+ const displayDate = dayjs(selectedDate)
5462
+ .tz(timezone)
5463
+ .format(displayDateFormat);
5464
+ React.useEffect(() => {
5465
+ try {
5466
+ if (selectedDate) {
5467
+ // Parse the selectedDate as UTC or in a specific timezone to avoid +8 hour shift
5468
+ // For example, parse as UTC:
5469
+ const parsedDate = dayjs(selectedDate).tz(timezone);
5470
+ if (!parsedDate.isValid())
5471
+ return;
5472
+ // Format according to dateFormat from schema
5473
+ const formatted = parsedDate.format(dateFormat);
5474
+ // Update the form value only if different to avoid loops
5475
+ if (formatted !== selectedDate) {
5476
+ setValue(colLabel, formatted, {
5477
+ shouldValidate: true,
5478
+ shouldDirty: true,
5479
+ });
5480
+ }
5481
+ }
5482
+ }
5483
+ catch (e) {
5484
+ console.error(e);
5485
+ }
5486
+ }, [selectedDate, dateFormat, colLabel, setValue]);
5487
+ const customTranslate = (label) => {
5488
+ return translateWrapper({ prefix, column, label, translate });
5489
+ };
5490
+ return (jsxRuntime.jsxs(Field, { label: `${customTranslate(`field_label`)}`, required: isRequired, alignItems: "stretch", gridColumn,
5491
+ gridRow, children: [jsxRuntime.jsxs(PopoverRoot, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: jsxRuntime.jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
5492
+ setOpen(true);
5493
+ }, justifyContent: "start", children: [jsxRuntime.jsx(md.MdDateRange, {}), selectedDate !== undefined ? `${displayDate}` : ""] }) }), jsxRuntime.jsx(PopoverContent, { minW: "450px", children: jsxRuntime.jsxs(PopoverBody, { children: [jsxRuntime.jsx(PopoverTitle, {}), jsxRuntime.jsx(DateTimePicker$1, { value: selectedDate, onChange: (date) => {
5494
+ setValue(colLabel, dayjs(date).tz(timezone).format(dateFormat));
5495
+ }, timezone: timezone, labels: {
5496
+ monthNamesShort: [
5497
+ translate.t(`common.month_1`, { defaultValue: "January" }),
5498
+ translate.t(`common.month_2`, { defaultValue: "February" }),
5499
+ translate.t(`common.month_3`, { defaultValue: "March" }),
5500
+ translate.t(`common.month_4`, { defaultValue: "April" }),
5501
+ translate.t(`common.month_5`, { defaultValue: "May" }),
5502
+ translate.t(`common.month_6`, { defaultValue: "June" }),
5503
+ translate.t(`common.month_7`, { defaultValue: "July" }),
5504
+ translate.t(`common.month_8`, { defaultValue: "August" }),
5505
+ translate.t(`common.month_9`, { defaultValue: "September" }),
5506
+ translate.t(`common.month_10`, { defaultValue: "October" }),
5507
+ translate.t(`common.month_11`, { defaultValue: "November" }),
5508
+ translate.t(`common.month_12`, { defaultValue: "December" }),
5509
+ ],
5510
+ weekdayNamesShort: [
5511
+ translate.t(`common.weekday_1`, { defaultValue: "Sun" }),
5512
+ translate.t(`common.weekday_2`, { defaultValue: "Mon" }),
5513
+ translate.t(`common.weekday_3`, { defaultValue: "Tue" }),
5514
+ translate.t(`common.weekday_4`, {
5515
+ defaultValue: "Wed",
5516
+ }),
5517
+ translate.t(`common.weekday_5`, { defaultValue: "Thu" }),
5518
+ translate.t(`common.weekday_6`, { defaultValue: "Fri" }),
5519
+ translate.t(`common.weekday_7`, { defaultValue: "Sat" }),
5520
+ ],
5521
+ backButtonLabel: translate.t(`common.back_button`, {
5522
+ defaultValue: "Back",
5523
+ }),
5524
+ forwardButtonLabel: translate.t(`common.forward_button`, {
5525
+ defaultValue: "Forward",
5526
+ }),
5527
+ } })] }) })] }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: "red.400", children: customTranslate(`field_required`) }))] }));
5528
+ };
5529
+
5008
5530
  const SchemaRenderer = ({ schema, prefix, column, }) => {
5009
5531
  const colSchema = schema;
5010
5532
  const { type, variant, properties: innerProperties, foreign_key, format, items, } = schema;
@@ -5025,6 +5547,9 @@ const SchemaRenderer = ({ schema, prefix, column, }) => {
5025
5547
  if (format === "time") {
5026
5548
  return jsxRuntime.jsx(TimePicker, { schema: colSchema, prefix, column });
5027
5549
  }
5550
+ if (format === "date-time") {
5551
+ return jsxRuntime.jsx(DateTimePicker, { schema: colSchema, prefix, column });
5552
+ }
5028
5553
  if (variant === "text-area") {
5029
5554
  return jsxRuntime.jsx(TextAreaInput, { schema: colSchema, prefix, column });
5030
5555
  }
@@ -5116,20 +5641,16 @@ const CustomViewer = ({ column, schema, prefix }) => {
5116
5641
 
5117
5642
  const DateViewer = ({ column, schema, prefix }) => {
5118
5643
  const { watch, formState: { errors }, } = reactHookForm.useFormContext();
5119
- const { translate } = useSchemaContext();
5644
+ const { translate, timezone } = useSchemaContext();
5120
5645
  const { required, gridColumn = "span 4", gridRow = "span 1", displayDateFormat = "YYYY-MM-DD", } = schema;
5121
5646
  const isRequired = required?.some((columnId) => columnId === column);
5122
5647
  const colLabel = `${prefix}${column}`;
5123
5648
  const selectedDate = watch(colLabel);
5124
- const displayDate = dayjs.utc(selectedDate).format(displayDateFormat);
5649
+ const displayDate = dayjs(selectedDate).tz(timezone).format(displayDateFormat);
5125
5650
  return (jsxRuntime.jsxs(Field, { label: `${translate.t(removeIndex(`${column}.field_label`))}`, required: isRequired, alignItems: "stretch", gridColumn,
5126
5651
  gridRow, children: [jsxRuntime.jsxs(react.Text, { children: [" ", selectedDate !== undefined ? displayDate : ""] }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: "red.400", children: translate.t(`${column}.field_required`) }))] }));
5127
5652
  };
5128
5653
 
5129
- function translateWrapper({ prefix, column, label, translate, }) {
5130
- return translate.t(removeIndex(`${prefix}${column}.${label}`));
5131
- }
5132
-
5133
5654
  const EnumViewer = ({ column, isMultiple = false, schema, prefix, }) => {
5134
5655
  const { watch, formState: { errors }, } = reactHookForm.useFormContext();
5135
5656
  const { translate } = useSchemaContext();
@@ -5385,20 +5906,34 @@ const TextAreaViewer = ({ column, schema, prefix, }) => {
5385
5906
  return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsxs(Field, { label: `${translate.t(removeIndex(`${colLabel}.field_label`))}`, required: isRequired, gridColumn: gridColumn, gridRow: gridRow, children: [jsxRuntime.jsx(react.Text, { whiteSpace: "pre-wrap", children: value }), " ", errors[colLabel] && (jsxRuntime.jsx(react.Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }) }));
5386
5907
  };
5387
5908
 
5388
- const TimeViewer = ({ column, schema, prefix, }) => {
5909
+ const TimeViewer = ({ column, schema, prefix }) => {
5389
5910
  const { watch, formState: { errors }, } = reactHookForm.useFormContext();
5390
- const { translate } = useSchemaContext();
5391
- const { required, gridColumn = "span 4", gridRow = "span 1", displayTimeFormat = "hh:mm A" } = schema;
5911
+ const { translate, timezone } = useSchemaContext();
5912
+ const { required, gridColumn = "span 4", gridRow = "span 1", displayTimeFormat = "hh:mm A", } = schema;
5392
5913
  const isRequired = required?.some((columnId) => columnId === column);
5393
5914
  const colLabel = `${prefix}${column}`;
5394
5915
  const selectedDate = watch(colLabel);
5395
- const displayedTime = dayjs(`1970-01-01T${selectedDate}Z`).isValid()
5396
- ? dayjs(`1970-01-01T${selectedDate}Z`).utc().format(displayTimeFormat)
5916
+ const displayedTime = dayjs(`1970-01-01T${selectedDate}`)
5917
+ .tz(timezone)
5918
+ .isValid()
5919
+ ? dayjs(`1970-01-01T${selectedDate}`).tz(timezone).format(displayTimeFormat)
5397
5920
  : "";
5398
5921
  return (jsxRuntime.jsxs(Field, { label: `${translate.t(removeIndex(`${column}.field_label`))}`, required: isRequired, alignItems: "stretch", gridColumn,
5399
5922
  gridRow, children: [jsxRuntime.jsx(react.Text, { children: displayedTime }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: "red.400", children: translate.t(`${column}.field_required`) }))] }));
5400
5923
  };
5401
5924
 
5925
+ const DateTimeViewer = ({ column, schema, prefix }) => {
5926
+ const { watch, formState: { errors }, } = reactHookForm.useFormContext();
5927
+ const { translate, timezone } = useSchemaContext();
5928
+ const { required, gridColumn = "span 4", gridRow = "span 1", displayDateFormat = "YYYY-MM-DD HH:mm:ss", } = schema;
5929
+ const isRequired = required?.some((columnId) => columnId === column);
5930
+ const colLabel = `${prefix}${column}`;
5931
+ const selectedDate = watch(colLabel);
5932
+ const displayDate = dayjs(selectedDate).tz(timezone).format(displayDateFormat);
5933
+ return (jsxRuntime.jsxs(Field, { label: `${translate.t(removeIndex(`${column}.field_label`))}`, required: isRequired, alignItems: "stretch", gridColumn,
5934
+ gridRow, children: [jsxRuntime.jsxs(react.Text, { children: [" ", selectedDate !== undefined ? displayDate : ""] }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: "red.400", children: translate.t(`${column}.field_required`) }))] }));
5935
+ };
5936
+
5402
5937
  const SchemaViewer = ({ schema, prefix, column, }) => {
5403
5938
  const colSchema = schema;
5404
5939
  const { type, variant, properties: innerProperties, foreign_key, items, format, } = schema;
@@ -5419,6 +5954,9 @@ const SchemaViewer = ({ schema, prefix, column, }) => {
5419
5954
  if (format === "date") {
5420
5955
  return jsxRuntime.jsx(DateViewer, { schema: colSchema, prefix, column });
5421
5956
  }
5957
+ if (format === "date-time") {
5958
+ return jsxRuntime.jsx(DateTimeViewer, { schema: colSchema, prefix, column });
5959
+ }
5422
5960
  if (variant === "text-area") {
5423
5961
  return jsxRuntime.jsx(TextAreaViewer, { schema: colSchema, prefix, column });
5424
5962
  }
@@ -5470,10 +6008,28 @@ const ColumnViewer = ({ column, properties, prefix, }) => {
5470
6008
  };
5471
6009
 
5472
6010
  const SubmitButton = () => {
5473
- const { translate, setValidatedData, setIsError, setIsConfirming } = useSchemaContext();
6011
+ const { translate, setValidatedData, setIsError, setIsConfirming, setError, schema, validationLocale } = useSchemaContext();
5474
6012
  const methods = reactHookForm.useFormContext();
5475
6013
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5476
6014
  const onValid = (data) => {
6015
+ // Validate data using AJV before proceeding to confirmation
6016
+ const validationResult = validateData(data, schema, { locale: validationLocale });
6017
+ if (!validationResult.isValid) {
6018
+ // Set validation errors with i18n support
6019
+ const validationErrorMessage = {
6020
+ type: 'validation',
6021
+ errors: validationResult.errors,
6022
+ message: validationLocale === 'zh-HK' || validationLocale === 'zh-TW'
6023
+ ? '表單驗證失敗'
6024
+ : validationLocale === 'zh-CN' || validationLocale === 'zh'
6025
+ ? '表单验证失败'
6026
+ : 'Form validation failed'
6027
+ };
6028
+ setError(validationErrorMessage);
6029
+ setIsError(true);
6030
+ return;
6031
+ }
6032
+ // If validation passes, proceed to confirmation
5477
6033
  setValidatedData(data);
5478
6034
  setIsError(false);
5479
6035
  setIsConfirming(true);
@@ -5484,7 +6040,7 @@ const SubmitButton = () => {
5484
6040
  };
5485
6041
 
5486
6042
  const FormBody = () => {
5487
- const { schema, requestUrl, order, ignore, include, onSubmit, rowNumber, translate, requestOptions, isSuccess, setIsSuccess, isError, setIsError, isSubmiting, setIsSubmiting, isConfirming, setIsConfirming, validatedData, setValidatedData, error, setError, getUpdatedData, customErrorRenderer, } = useSchemaContext();
6043
+ const { schema, requestUrl, order, ignore, include, onSubmit, rowNumber, translate, requestOptions, isSuccess, setIsSuccess, isError, setIsError, isSubmiting, setIsSubmiting, isConfirming, setIsConfirming, validatedData, setValidatedData, error, setError, getUpdatedData, customErrorRenderer, validationLocale, } = useSchemaContext();
5488
6044
  const methods = reactHookForm.useFormContext();
5489
6045
  const { properties } = schema;
5490
6046
  const onBeforeSubmit = () => {
@@ -5500,6 +6056,27 @@ const FormBody = () => {
5500
6056
  const onSubmitSuccess = () => {
5501
6057
  setIsSuccess(true);
5502
6058
  };
6059
+ // Enhanced validation function using AJV with i18n support
6060
+ const validateFormData = (data) => {
6061
+ try {
6062
+ const validationResult = validateData(data, schema, { locale: validationLocale });
6063
+ return validationResult;
6064
+ }
6065
+ catch (error) {
6066
+ const errorMessage = validationLocale === 'zh-HK' || validationLocale === 'zh-TW'
6067
+ ? `驗證錯誤: ${error instanceof Error ? error.message : '未知驗證錯誤'}`
6068
+ : validationLocale === 'zh-CN' || validationLocale === 'zh'
6069
+ ? `验证错误: ${error instanceof Error ? error.message : '未知验证错误'}`
6070
+ : `Validation error: ${error instanceof Error ? error.message : 'Unknown validation error'}`;
6071
+ return {
6072
+ isValid: false,
6073
+ errors: [{
6074
+ field: 'validation',
6075
+ message: errorMessage
6076
+ }]
6077
+ };
6078
+ }
6079
+ };
5503
6080
  const defaultOnSubmit = async (promise) => {
5504
6081
  try {
5505
6082
  onBeforeSubmit();
@@ -5524,12 +6101,47 @@ const FormBody = () => {
5524
6101
  };
5525
6102
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5526
6103
  const onFormSubmit = async (data) => {
6104
+ // Validate data using AJV before submission
6105
+ const validationResult = validateFormData(data);
6106
+ if (!validationResult.isValid) {
6107
+ // Set validation errors
6108
+ const validationErrorMessage = {
6109
+ type: 'validation',
6110
+ errors: validationResult.errors,
6111
+ message: validationLocale === 'zh-HK' || validationLocale === 'zh-TW'
6112
+ ? '表單驗證失敗'
6113
+ : validationLocale === 'zh-CN' || validationLocale === 'zh'
6114
+ ? '表单验证失败'
6115
+ : 'Form validation failed'
6116
+ };
6117
+ onSubmitError(validationErrorMessage);
6118
+ return;
6119
+ }
5527
6120
  if (onSubmit === undefined) {
5528
6121
  await defaultOnSubmit(defaultSubmitPromise(data));
5529
6122
  return;
5530
6123
  }
5531
6124
  await defaultOnSubmit(onSubmit(data));
5532
6125
  };
6126
+ // Custom error renderer for validation errors with i18n support
6127
+ const renderValidationErrors = (validationErrors) => {
6128
+ const title = validationLocale === 'zh-HK' || validationLocale === 'zh-TW'
6129
+ ? `表單驗證失敗 (${validationErrors.length} 個錯誤${validationErrors.length > 1 ? '' : ''})`
6130
+ : validationLocale === 'zh-CN' || validationLocale === 'zh'
6131
+ ? `表单验证失败 (${validationErrors.length} 个错误${validationErrors.length > 1 ? '' : ''})`
6132
+ : `Form Validation Failed (${validationErrors.length} error${validationErrors.length > 1 ? 's' : ''})`;
6133
+ const formLabel = validationLocale === 'zh-HK' || validationLocale === 'zh-TW'
6134
+ ? '表單'
6135
+ : validationLocale === 'zh-CN' || validationLocale === 'zh'
6136
+ ? '表单'
6137
+ : 'Form';
6138
+ const currentValueLabel = validationLocale === 'zh-HK' || validationLocale === 'zh-TW'
6139
+ ? '目前值:'
6140
+ : validationLocale === 'zh-CN' || validationLocale === 'zh'
6141
+ ? '当前值:'
6142
+ : 'Current value:';
6143
+ return (jsxRuntime.jsxs(react.Alert.Root, { status: "error", children: [jsxRuntime.jsx(react.Alert.Indicator, {}), jsxRuntime.jsx(react.Alert.Title, { children: jsxRuntime.jsx(AccordionRoot, { collapsible: true, defaultValue: [], children: jsxRuntime.jsxs(AccordionItem, { value: "validation-errors", children: [jsxRuntime.jsx(AccordionItemTrigger, { children: title }), jsxRuntime.jsx(AccordionItemContent, { children: jsxRuntime.jsx(react.Box, { mt: 2, children: validationErrors.map((err, index) => (jsxRuntime.jsxs(react.Box, { mb: 2, p: 2, bg: "red.50", borderLeft: "4px solid", borderColor: "red.500", children: [jsxRuntime.jsxs(react.Text, { fontWeight: "bold", color: "red.700", children: [err.field === 'root' ? formLabel : err.field, ":"] }), jsxRuntime.jsx(react.Text, { color: "red.600", children: err.message }), err.value !== undefined && (jsxRuntime.jsxs(react.Text, { fontSize: "sm", color: "red.500", mt: 1, children: [currentValueLabel, " ", JSON.stringify(err.value)] }))] }, index))) }) })] }) }) })] }));
6144
+ };
5533
6145
  const renderColumns = ({ order, keys, ignore, include, }) => {
5534
6146
  const included = include.length > 0 ? include : keys;
5535
6147
  const not_exist = included.filter((columnA) => !order.some((columnB) => columnA === columnB));
@@ -5565,7 +6177,7 @@ const FormBody = () => {
5565
6177
  setIsConfirming(false);
5566
6178
  }, variant: "subtle", children: translate.t("cancel") }), jsxRuntime.jsx(react.Button, { onClick: () => {
5567
6179
  onFormSubmit(validatedData);
5568
- }, children: translate.t("confirm") })] }), isSubmiting && (jsxRuntime.jsx(react.Box, { pos: "absolute", inset: "0", bg: "bg/80", children: jsxRuntime.jsx(react.Center, { h: "full", children: jsxRuntime.jsx(react.Spinner, { color: "teal.500" }) }) })), isError && (jsxRuntime.jsx(jsxRuntime.Fragment, { children: customErrorRenderer ? (customErrorRenderer(error)) : (jsxRuntime.jsx(react.Alert.Root, { status: "error", children: jsxRuntime.jsx(react.Alert.Title, { children: jsxRuntime.jsx(AccordionRoot, { collapsible: true, defaultValue: [], children: jsxRuntime.jsxs(AccordionItem, { value: "b", children: [jsxRuntime.jsxs(AccordionItemTrigger, { children: [jsxRuntime.jsx(react.Alert.Indicator, {}), `${error}`] }), jsxRuntime.jsx(AccordionItemContent, { children: `${JSON.stringify(error)}` })] }) }) }) })) }))] }));
6180
+ }, children: translate.t("confirm") })] }), isSubmiting && (jsxRuntime.jsx(react.Box, { pos: "absolute", inset: "0", bg: "bg/80", children: jsxRuntime.jsx(react.Center, { h: "full", children: jsxRuntime.jsx(react.Spinner, { color: "teal.500" }) }) })), isError && (jsxRuntime.jsx(jsxRuntime.Fragment, { children: customErrorRenderer ? (customErrorRenderer(error)) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: error?.type === 'validation' && error?.errors ? (renderValidationErrors(error.errors)) : (jsxRuntime.jsx(react.Alert.Root, { status: "error", children: jsxRuntime.jsx(react.Alert.Title, { children: jsxRuntime.jsx(AccordionRoot, { collapsible: true, defaultValue: [], children: jsxRuntime.jsxs(AccordionItem, { value: "b", children: [jsxRuntime.jsxs(AccordionItemTrigger, { children: [jsxRuntime.jsx(react.Alert.Indicator, {}), `${error}`] }), jsxRuntime.jsx(AccordionItemContent, { children: `${JSON.stringify(error)}` })] }) }) }) })) })) }))] }));
5569
6181
  }
5570
6182
  return (jsxRuntime.jsxs(react.Flex, { flexFlow: "column", gap: "2", children: [jsxRuntime.jsx(react.Grid, { gap: "4", gridTemplateColumns: "repeat(12, 1fr)", autoFlow: "row", children: ordered.map((column) => {
5571
6183
  return (jsxRuntime.jsx(ColumnRenderer
@@ -5575,7 +6187,7 @@ const FormBody = () => {
5575
6187
  properties: properties, prefix: ``, column }, `form-input-${column}`));
5576
6188
  }) }), jsxRuntime.jsxs(react.Flex, { justifyContent: "end", gap: "2", children: [jsxRuntime.jsx(react.Button, { onClick: () => {
5577
6189
  methods.reset();
5578
- }, variant: "subtle", children: translate.t("reset") }), jsxRuntime.jsx(SubmitButton, {})] })] }));
6190
+ }, variant: "subtle", children: translate.t("reset") }), jsxRuntime.jsx(SubmitButton, {})] }), isError && error?.type === 'validation' && (jsxRuntime.jsx(react.Box, { mt: 4, children: error?.errors && renderValidationErrors(error.errors) }))] }));
5579
6191
  };
5580
6192
 
5581
6193
  const FormTitle = () => {
@@ -5658,12 +6270,16 @@ exports.TableSorter = TableSorter;
5658
6270
  exports.TableViewer = TableViewer;
5659
6271
  exports.TextCell = TextCell;
5660
6272
  exports.ViewDialog = ViewDialog;
6273
+ exports.createSchemaValidator = createSchemaValidator;
5661
6274
  exports.getColumns = getColumns;
5662
6275
  exports.getMultiDates = getMultiDates;
5663
6276
  exports.getRangeDates = getRangeDates;
6277
+ exports.getSupportedLocales = getSupportedLocales;
5664
6278
  exports.idPickerSanityCheck = idPickerSanityCheck;
6279
+ exports.isLocaleSupported = isLocaleSupported;
5665
6280
  exports.useDataTable = useDataTable;
5666
6281
  exports.useDataTableContext = useDataTableContext;
5667
6282
  exports.useDataTableServer = useDataTableServer;
5668
6283
  exports.useForm = useForm;
6284
+ exports.validateData = validateData;
5669
6285
  exports.widthSanityCheck = widthSanityCheck;