@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.mjs CHANGED
@@ -5,7 +5,7 @@ import * as React from 'react';
5
5
  import React__default, { createContext, useContext, useState, useEffect, useRef } from 'react';
6
6
  import { LuX, LuCheck, LuChevronRight, LuChevronDown } from 'react-icons/lu';
7
7
  import { MdOutlineSort, MdFilterAlt, MdSearch, MdOutlineViewColumn, MdFilterListAlt, MdPushPin, MdCancel, MdClear, MdOutlineChecklist, MdDateRange } from 'react-icons/md';
8
- import { FaUpDown, FaGripLinesVertical } from 'react-icons/fa6';
8
+ import { FaUpDown, FaGripLinesVertical, FaTrash } from 'react-icons/fa6';
9
9
  import { BiDownArrow, BiUpArrow, BiError } from 'react-icons/bi';
10
10
  import { CgClose, CgTrash } from 'react-icons/cg';
11
11
  import Dayzed from '@bsol-oss/dayzed-react19';
@@ -28,8 +28,13 @@ import { GrAscend, GrDescend } from 'react-icons/gr';
28
28
  import { useTranslation } from 'react-i18next';
29
29
  import axios from 'axios';
30
30
  import { FormProvider, useFormContext, useForm as useForm$1 } from 'react-hook-form';
31
+ import Ajv from 'ajv';
32
+ import addFormats from 'ajv-formats';
33
+ import zh_TW from 'ajv-i18n/localize/zh-TW';
34
+ import zh_CN from 'ajv-i18n/localize/zh';
31
35
  import dayjs from 'dayjs';
32
36
  import utc from 'dayjs/plugin/utc';
37
+ import timezone from 'dayjs/plugin/timezone';
33
38
  import { TiDeleteOutline } from 'react-icons/ti';
34
39
 
35
40
  const DataTableContext = createContext({
@@ -177,7 +182,7 @@ const monthNamesFull = [
177
182
  "November",
178
183
  "December",
179
184
  ];
180
- const weekdayNamesShort$1 = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
185
+ const weekdayNamesShort = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
181
186
  function Calendar$1({ calendars, getBackProps, getForwardProps, getDateProps, selected = [], firstDayOfWeek = 0, }) {
182
187
  const [hoveredDate, setHoveredDate] = useState();
183
188
  const onMouseLeave = () => {
@@ -212,7 +217,7 @@ function Calendar$1({ calendars, getBackProps, getForwardProps, getDateProps, se
212
217
  offset: 12,
213
218
  }), children: ">>" })] }), jsx(Grid, { templateColumns: "repeat(2, auto)", justifyContent: "center", gap: 4, children: calendars.map((calendar) => (jsxs(Grid, { gap: 4, children: [jsxs(Grid, { justifyContent: "center", children: [monthNamesFull[calendar.month], " ", calendar.year] }), jsx(Grid, { templateColumns: "repeat(7, auto)", justifyContent: "center", children: [0, 1, 2, 3, 4, 5, 6].map((weekdayNum) => {
214
219
  const weekday = (weekdayNum + firstDayOfWeek) % 7;
215
- return (jsx(Box, { minWidth: "48px", textAlign: "center", children: weekdayNamesShort$1[weekday] }, `${calendar.month}${calendar.year}${weekday}`));
220
+ return (jsx(Box, { minWidth: "48px", textAlign: "center", children: weekdayNamesShort[weekday] }, `${calendar.month}${calendar.year}${weekday}`));
216
221
  }) }), jsx(Grid, { templateColumns: "repeat(7, auto)", justifyContent: "center", children: calendar.weeks.map((week, windex) => week.map((dateObj, index) => {
217
222
  const key = `${calendar.month}${calendar.year}${windex}${index}`;
218
223
  if (!dateObj) {
@@ -3668,6 +3673,8 @@ const SchemaFormContext = createContext({
3668
3673
  onSubmit: async () => { },
3669
3674
  rowNumber: 0,
3670
3675
  requestOptions: {},
3676
+ validationLocale: 'en',
3677
+ timezone: 'Asia/Hong_Kong',
3671
3678
  });
3672
3679
 
3673
3680
  const useSchemaContext = () => {
@@ -3678,6 +3685,179 @@ const clearEmptyString = (object) => {
3678
3685
  return Object.fromEntries(Object.entries(object).filter(([, value]) => value !== ""));
3679
3686
  };
3680
3687
 
3688
+ // AJV i18n support
3689
+ const localize = {
3690
+ en: () => { }, // English is default, no localization needed
3691
+ 'zh-HK': zh_TW, // Use zh-TW for Hong Kong Traditional Chinese
3692
+ 'zh-TW': zh_TW, // Traditional Chinese (Taiwan)
3693
+ 'zh-CN': zh_CN, // Simplified Chinese
3694
+ 'zh': zh_CN, // Simplified Chinese (short form)
3695
+ };
3696
+ // Create AJV instance with format support
3697
+ const createValidator = () => {
3698
+ const ajv = new Ajv({
3699
+ allErrors: true,
3700
+ verbose: true,
3701
+ removeAdditional: false,
3702
+ strict: false,
3703
+ messages: false, // Disable default messages for i18n
3704
+ });
3705
+ // Add format validation support (date, time, email, etc.)
3706
+ addFormats(ajv);
3707
+ return ajv;
3708
+ };
3709
+ /**
3710
+ * Validates data against a JSON Schema using AJV with i18n support
3711
+ * @param data - The data to validate
3712
+ * @param schema - The JSON Schema to validate against
3713
+ * @param options - Validation options including locale
3714
+ * @returns ValidationResult containing validation status and errors
3715
+ */
3716
+ const validateData = (data, schema, options = {}) => {
3717
+ const { locale = 'en' } = options;
3718
+ const ajv = createValidator();
3719
+ try {
3720
+ const validate = ajv.compile(schema);
3721
+ const isValid = validate(data);
3722
+ if (isValid) {
3723
+ return {
3724
+ isValid: true,
3725
+ errors: [],
3726
+ };
3727
+ }
3728
+ // Apply localization if not English
3729
+ if (locale !== 'en' && validate.errors && localize[locale]) {
3730
+ try {
3731
+ localize[locale](validate.errors);
3732
+ }
3733
+ catch (error) {
3734
+ console.warn(`Failed to localize validation errors to ${locale}:`, error);
3735
+ }
3736
+ }
3737
+ const errors = (validate.errors || []).map((error) => {
3738
+ const field = error.instancePath?.replace(/^\//, '') || error.schemaPath?.split('/').pop() || 'root';
3739
+ let message = error.message || 'Validation error';
3740
+ // Enhanced error messages for better UX (only if using English or localization failed)
3741
+ if (locale === 'en' || !error.message) {
3742
+ switch (error.keyword) {
3743
+ case 'required':
3744
+ message = `${error.params?.missingProperty || 'Field'} is required`;
3745
+ break;
3746
+ case 'format':
3747
+ message = `Invalid ${error.params?.format} format`;
3748
+ break;
3749
+ case 'type':
3750
+ message = `Expected ${error.params?.type}, got ${typeof error.data}`;
3751
+ break;
3752
+ case 'minLength':
3753
+ message = `Must be at least ${error.params?.limit} characters`;
3754
+ break;
3755
+ case 'maxLength':
3756
+ message = `Must be no more than ${error.params?.limit} characters`;
3757
+ break;
3758
+ case 'minimum':
3759
+ message = `Must be at least ${error.params?.limit}`;
3760
+ break;
3761
+ case 'maximum':
3762
+ message = `Must be no more than ${error.params?.limit}`;
3763
+ break;
3764
+ case 'pattern':
3765
+ message = `Does not match required pattern`;
3766
+ break;
3767
+ case 'enum':
3768
+ message = `Must be one of: ${error.params?.allowedValues?.join(', ')}`;
3769
+ break;
3770
+ default:
3771
+ message = error.message || 'Validation error';
3772
+ }
3773
+ }
3774
+ return {
3775
+ field: field || error.instancePath || 'unknown',
3776
+ message,
3777
+ value: error.data,
3778
+ schemaPath: error.schemaPath,
3779
+ };
3780
+ });
3781
+ return {
3782
+ isValid: false,
3783
+ errors,
3784
+ };
3785
+ }
3786
+ catch (error) {
3787
+ // Handle AJV compilation errors
3788
+ const errorMessage = locale === 'zh-HK' || locale === 'zh-TW'
3789
+ ? `架構驗證錯誤: ${error instanceof Error ? error.message : '未知錯誤'}`
3790
+ : locale === 'zh-CN' || locale === 'zh'
3791
+ ? `模式验证错误: ${error instanceof Error ? error.message : '未知错误'}`
3792
+ : `Schema validation error: ${error instanceof Error ? error.message : 'Unknown error'}`;
3793
+ return {
3794
+ isValid: false,
3795
+ errors: [
3796
+ {
3797
+ field: 'schema',
3798
+ message: errorMessage,
3799
+ },
3800
+ ],
3801
+ };
3802
+ }
3803
+ };
3804
+ /**
3805
+ * Creates a reusable validator function for a specific schema with i18n support
3806
+ * @param schema - The JSON Schema to create validator for
3807
+ * @param locale - The locale to use for error messages
3808
+ * @returns A function that validates data against the schema
3809
+ */
3810
+ const createSchemaValidator = (schema, locale = 'en') => {
3811
+ const ajv = createValidator();
3812
+ const validate = ajv.compile(schema);
3813
+ return (data) => {
3814
+ const isValid = validate(data);
3815
+ if (isValid) {
3816
+ return {
3817
+ isValid: true,
3818
+ errors: [],
3819
+ };
3820
+ }
3821
+ // Apply localization if not English
3822
+ if (locale !== 'en' && validate.errors && localize[locale]) {
3823
+ try {
3824
+ localize[locale](validate.errors);
3825
+ }
3826
+ catch (error) {
3827
+ console.warn(`Failed to localize validation errors to ${locale}:`, error);
3828
+ }
3829
+ }
3830
+ const errors = (validate.errors || []).map((error) => {
3831
+ const field = error.instancePath?.replace(/^\//, '') || 'root';
3832
+ return {
3833
+ field,
3834
+ message: error.message || 'Validation error',
3835
+ value: error.data,
3836
+ schemaPath: error.schemaPath,
3837
+ };
3838
+ });
3839
+ return {
3840
+ isValid: false,
3841
+ errors,
3842
+ };
3843
+ };
3844
+ };
3845
+ /**
3846
+ * Get available locales for validation error messages
3847
+ * @returns Array of supported locale codes
3848
+ */
3849
+ const getSupportedLocales = () => {
3850
+ return Object.keys(localize);
3851
+ };
3852
+ /**
3853
+ * Check if a locale is supported
3854
+ * @param locale - The locale to check
3855
+ * @returns Boolean indicating if the locale is supported
3856
+ */
3857
+ const isLocaleSupported = (locale) => {
3858
+ return locale in localize;
3859
+ };
3860
+
3681
3861
  const idPickerSanityCheck = (column, foreign_key) => {
3682
3862
  if (!!foreign_key == false) {
3683
3863
  throw new Error(`The key foreign_key does not exist in properties of column ${column} when using id-picker.`);
@@ -3693,7 +3873,7 @@ const idPickerSanityCheck = (column, foreign_key) => {
3693
3873
  throw new Error(`The key column does not exist in properties of column ${column} when using id-picker.`);
3694
3874
  }
3695
3875
  };
3696
- const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, children, order = [], ignore = [], include = [], onSubmit = undefined, rowNumber = undefined, requestOptions = {}, getUpdatedData = () => { }, customErrorRenderer, }) => {
3876
+ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, children, order = [], ignore = [], include = [], onSubmit = undefined, rowNumber = undefined, requestOptions = {}, getUpdatedData = () => { }, customErrorRenderer, validationLocale = 'en', }) => {
3697
3877
  const [isSuccess, setIsSuccess] = useState(false);
3698
3878
  const [isError, setIsError] = useState(false);
3699
3879
  const [isSubmiting, setIsSubmiting] = useState(false);
@@ -3727,6 +3907,7 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
3727
3907
  setError,
3728
3908
  getUpdatedData,
3729
3909
  customErrorRenderer,
3910
+ validationLocale,
3730
3911
  }, children: jsx(FormProvider, { ...form, children: children }) }));
3731
3912
  };
3732
3913
 
@@ -3800,27 +3981,14 @@ const CustomInput = ({ column, schema, prefix }) => {
3800
3981
  }));
3801
3982
  };
3802
3983
 
3803
- const monthNamesShort = [
3804
- "Jan",
3805
- "Feb",
3806
- "Mar",
3807
- "Apr",
3808
- "May",
3809
- "Jun",
3810
- "Jul",
3811
- "Aug",
3812
- "Sep",
3813
- "Oct",
3814
- "Nov",
3815
- "Dec",
3816
- ];
3817
- const weekdayNamesShort = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
3818
3984
  const Calendar = ({ calendars, getBackProps, getForwardProps, getDateProps, firstDayOfWeek = 0, }) => {
3985
+ const { labels } = useContext(DatePickerContext);
3986
+ const { monthNamesShort, weekdayNamesShort, backButtonLabel, forwardButtonLabel } = labels;
3819
3987
  if (calendars.length) {
3820
3988
  return (jsxs(Grid, { children: [jsxs(Grid, { templateColumns: "repeat(4, auto)", justifyContent: "center", children: [jsx(Button$1, { variant: "ghost", ...getBackProps({
3821
3989
  calendars,
3822
3990
  offset: 12,
3823
- }), children: "<<" }), jsx(Button$1, { variant: "ghost", ...getBackProps({ calendars }), children: "Back" }), jsx(Button$1, { variant: "ghost", ...getForwardProps({ calendars }), children: "Next" }), jsx(Button$1, { variant: "ghost", ...getForwardProps({
3991
+ }), children: "<<" }), jsx(Button$1, { variant: "ghost", ...getBackProps({ calendars }), children: backButtonLabel }), jsx(Button$1, { variant: "ghost", ...getForwardProps({ calendars }), children: forwardButtonLabel }), jsx(Button$1, { variant: "ghost", ...getForwardProps({
3824
3992
  calendars,
3825
3993
  offset: 12,
3826
3994
  }), children: ">>" })] }), jsx(Grid, { templateColumns: "repeat(2, auto)", justifyContent: "center", children: calendars.map((calendar) => (jsxs(Grid, { gap: 4, children: [jsxs(Grid, { justifyContent: "center", children: [monthNamesShort[calendar.month], " ", calendar.year] }), jsxs(Grid, { templateColumns: "repeat(7, auto)", justifyContent: "center", children: [[0, 1, 2, 3, 4, 5, 6].map((weekdayNum) => {
@@ -3863,9 +4031,52 @@ const Calendar = ({ calendars, getBackProps, getForwardProps, getDateProps, firs
3863
4031
  }
3864
4032
  return null;
3865
4033
  };
4034
+ const DatePickerContext = createContext({
4035
+ labels: {
4036
+ monthNamesShort: [
4037
+ "Jan",
4038
+ "Feb",
4039
+ "Mar",
4040
+ "Apr",
4041
+ "May",
4042
+ "Jun",
4043
+ "Jul",
4044
+ "Aug",
4045
+ "Sep",
4046
+ "Oct",
4047
+ "Nov",
4048
+ "Dec",
4049
+ ],
4050
+ weekdayNamesShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
4051
+ backButtonLabel: "Back",
4052
+ forwardButtonLabel: "Next",
4053
+ },
4054
+ });
3866
4055
  let DatePicker$1 = class DatePicker extends React__default.Component {
3867
4056
  render() {
3868
- return (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) => (jsx(Calendar, { ...dayzedData, firstDayOfWeek: this.props.firstDayOfWeek })) }));
4057
+ const { labels = {
4058
+ monthNamesShort: [
4059
+ "Jan",
4060
+ "Feb",
4061
+ "Mar",
4062
+ "Apr",
4063
+ "May",
4064
+ "Jun",
4065
+ "Jul",
4066
+ "Aug",
4067
+ "Sep",
4068
+ "Oct",
4069
+ "Nov",
4070
+ "Dec",
4071
+ ],
4072
+ weekdayNamesShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
4073
+ backButtonLabel: "Back",
4074
+ forwardButtonLabel: "Next",
4075
+ }, } = this.props;
4076
+ return (jsx(DatePickerContext.Provider, { value: { labels }, children: 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:
4077
+ // @ts-expect-error - Dayzed types need to be fixed
4078
+ (dayzedData) => (jsx(Calendar, { ...dayzedData,
4079
+ firstDayOfWeek: this.props.firstDayOfWeek })) }) }));
3869
4080
  }
3870
4081
  };
3871
4082
 
@@ -3887,24 +4098,27 @@ const PopoverRoot = Popover.Root;
3887
4098
  const PopoverBody = Popover.Body;
3888
4099
  const PopoverTrigger = Popover.Trigger;
3889
4100
 
4101
+ function translateWrapper({ prefix, column, label, translate, }) {
4102
+ return translate.t(removeIndex(`${prefix}${column}.${label}`));
4103
+ }
4104
+
3890
4105
  dayjs.extend(utc);
4106
+ dayjs.extend(timezone);
3891
4107
  const DatePicker = ({ column, schema, prefix }) => {
3892
4108
  const { watch, formState: { errors }, setValue, } = useFormContext();
3893
- const { translate } = useSchemaContext();
3894
- const { required, gridColumn = "span 4", gridRow = "span 1", displayDateFormat = "YYYY-MM-DD", dateFormat = "YYYY-MM-DD[T]HH:mm:ss[Z]", } = schema;
4109
+ const { translate, timezone } = useSchemaContext();
4110
+ const { required, gridColumn = "span 4", gridRow = "span 1", displayDateFormat = "YYYY-MM-DD", dateFormat = "YYYY-MM-DD", } = schema;
3895
4111
  const isRequired = required?.some((columnId) => columnId === column);
3896
4112
  const colLabel = `${prefix}${column}`;
3897
4113
  const [open, setOpen] = useState(false);
3898
4114
  const selectedDate = watch(colLabel);
3899
- const displayDate = dayjs.utc(selectedDate).format(displayDateFormat);
4115
+ const displayDate = dayjs(selectedDate).tz(timezone).format(displayDateFormat);
3900
4116
  useEffect(() => {
3901
4117
  try {
3902
4118
  if (selectedDate) {
3903
4119
  // Parse the selectedDate as UTC or in a specific timezone to avoid +8 hour shift
3904
4120
  // For example, parse as UTC:
3905
- const parsedDate = dayjs.utc(selectedDate);
3906
- // Or if you want to parse in local timezone without shifting:
3907
- // const parsedDate = dayjs.tz(selectedDate, dayjs.tz.guess());
4121
+ const parsedDate = dayjs(selectedDate).tz(timezone);
3908
4122
  if (!parsedDate.isValid())
3909
4123
  return;
3910
4124
  // Format according to dateFormat from schema
@@ -3922,19 +4136,48 @@ const DatePicker = ({ column, schema, prefix }) => {
3922
4136
  console.error(e);
3923
4137
  }
3924
4138
  }, [selectedDate, dateFormat, colLabel, setValue]);
3925
- return (jsxs(Field, { label: `${translate.t(removeIndex(`${colLabel}.field_label`))}`, required: isRequired, alignItems: "stretch", gridColumn,
4139
+ const customTranslate = (label) => {
4140
+ return translateWrapper({ prefix, column, label, translate });
4141
+ };
4142
+ return (jsxs(Field, { label: `${customTranslate(`field_label`)}`, required: isRequired, alignItems: "stretch", gridColumn,
3926
4143
  gridRow, children: [jsxs(PopoverRoot, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsx(PopoverTrigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
3927
4144
  setOpen(true);
3928
- }, justifyContent: "start", children: [jsx(MdDateRange, {}), selectedDate !== undefined ? `${displayDate}` : ""] }) }), jsx(PopoverContent, { children: jsxs(PopoverBody, { children: [jsx(PopoverTitle, {}), jsx(DatePicker$1
3929
- // @ts-expect-error TODO: find appropriate types
3930
- , {
3931
- // @ts-expect-error TODO: find appropriate types
3932
- selected: new Date(selectedDate),
3933
- // @ts-expect-error TODO: find appropriate types
3934
- onDateSelected: ({ date }) => {
4145
+ }, justifyContent: "start", children: [jsx(MdDateRange, {}), selectedDate !== undefined ? `${displayDate}` : ""] }) }), jsx(PopoverContent, { children: jsxs(PopoverBody, { children: [jsx(PopoverTitle, {}), jsx(DatePicker$1, { selected: new Date(selectedDate), onDateSelected: ({ date }) => {
3935
4146
  setValue(colLabel, dayjs(date).format(dateFormat));
3936
4147
  setOpen(false);
3937
- } })] }) })] }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }));
4148
+ }, labels: {
4149
+ monthNamesShort: [
4150
+ translate.t(`common.month_1`, { defaultValue: "January" }),
4151
+ translate.t(`common.month_2`, { defaultValue: "February" }),
4152
+ translate.t(`common.month_3`, { defaultValue: "March" }),
4153
+ translate.t(`common.month_4`, { defaultValue: "April" }),
4154
+ translate.t(`common.month_5`, { defaultValue: "May" }),
4155
+ translate.t(`common.month_6`, { defaultValue: "June" }),
4156
+ translate.t(`common.month_7`, { defaultValue: "July" }),
4157
+ translate.t(`common.month_8`, { defaultValue: "August" }),
4158
+ translate.t(`common.month_9`, { defaultValue: "September" }),
4159
+ translate.t(`common.month_10`, { defaultValue: "October" }),
4160
+ translate.t(`common.month_11`, { defaultValue: "November" }),
4161
+ translate.t(`common.month_12`, { defaultValue: "December" }),
4162
+ ],
4163
+ weekdayNamesShort: [
4164
+ translate.t(`common.weekday_1`, { defaultValue: "Sun" }),
4165
+ translate.t(`common.weekday_2`, { defaultValue: "Mon" }),
4166
+ translate.t(`common.weekday_3`, { defaultValue: "Tue" }),
4167
+ translate.t(`common.weekday_4`, {
4168
+ defaultValue: "Wed",
4169
+ }),
4170
+ translate.t(`common.weekday_5`, { defaultValue: "Thu" }),
4171
+ translate.t(`common.weekday_6`, { defaultValue: "Fri" }),
4172
+ translate.t(`common.weekday_7`, { defaultValue: "Sat" }),
4173
+ ],
4174
+ backButtonLabel: translate.t(`common.back_button`, {
4175
+ defaultValue: "Back",
4176
+ }),
4177
+ forwardButtonLabel: translate.t(`common.forward_button`, {
4178
+ defaultValue: "Forward",
4179
+ }),
4180
+ } })] }) })] }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: customTranslate(`field_required`) }))] }));
3938
4181
  };
3939
4182
 
3940
4183
  function filterArray(array, searchTerm) {
@@ -4622,7 +4865,7 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
4622
4865
 
4623
4866
  const NumberInputRoot = React.forwardRef(function NumberInput$1(props, ref) {
4624
4867
  const { children, ...rest } = props;
4625
- return (jsxs(NumberInput.Root, { ref: ref, variant: "outline", ...rest, children: [children, jsxs(NumberInput.Control, { children: [jsx(NumberInput.IncrementTrigger, {}), jsx(NumberInput.DecrementTrigger, {})] })] }));
4868
+ return (jsx(NumberInput.Root, { ref: ref, variant: "outline", ...rest, children: children }));
4626
4869
  });
4627
4870
  const NumberInputField$1 = NumberInput.Input;
4628
4871
  NumberInput.Scrubber;
@@ -4917,22 +5160,23 @@ function TimePicker$1({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
4917
5160
  return (jsx(Flex, { direction: "column", gap: 3, children: jsxs(Grid, { justifyContent: "center", alignItems: "center", templateColumns: "60px 10px 60px 90px auto", gap: "2", width: "auto", minWidth: "250px", children: [jsx(Input, { ref: hourInputRef, type: "text", value: hour === null ? "" : hour.toString().padStart(2, "0"), onKeyDown: (e) => handleKeyDown(e, "hour"), placeholder: "HH", maxLength: 2, textAlign: "center" }), jsx(Text, { children: ":" }), jsx(Input, { ref: minuteInputRef, type: "text", value: minute === null ? "" : minute.toString().padStart(2, "0"), onKeyDown: (e) => handleKeyDown(e, "minute"), placeholder: "MM", maxLength: 2, textAlign: "center" }), jsxs(Flex, { gap: "1", children: [jsx(Button$1, { size: "sm", colorScheme: meridiem === "am" ? "blue" : "gray", variant: meridiem === "am" ? "solid" : "outline", onClick: () => handleMeridiemClick("am"), width: "40px", children: meridiemLabel.am }), jsx(Button$1, { size: "sm", colorScheme: meridiem === "pm" ? "blue" : "gray", variant: meridiem === "pm" ? "solid" : "outline", onClick: () => handleMeridiemClick("pm"), width: "40px", children: meridiemLabel.pm })] }), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "ghost", children: jsx(MdCancel, {}) })] }) }));
4918
5161
  }
4919
5162
 
5163
+ dayjs.extend(timezone);
4920
5164
  const TimePicker = ({ column, schema, prefix }) => {
4921
5165
  const { watch, formState: { errors }, setValue, } = useFormContext();
4922
- const { translate } = useSchemaContext();
4923
- const { required, gridColumn = "span 4", gridRow = "span 1", timeFormat = "HH:mm:ss", displayTimeFormat = "hh:mm A", } = schema;
5166
+ const { translate, timezone } = useSchemaContext();
5167
+ const { required, gridColumn = "span 4", gridRow = "span 1", timeFormat = "HH:mm:ssZ", displayTimeFormat = "hh:mm A", } = schema;
4924
5168
  const isRequired = required?.some((columnId) => columnId === column);
4925
5169
  const colLabel = `${prefix}${column}`;
4926
5170
  const [open, setOpen] = useState(false);
4927
5171
  const value = watch(colLabel);
4928
- const displayedTime = dayjs(`1970-01-01T${value}Z`).isValid()
4929
- ? dayjs(`1970-01-01T${value}Z`).utc().format(displayTimeFormat)
5172
+ const displayedTime = dayjs(`1970-01-01T${value}`).tz(timezone).isValid()
5173
+ ? dayjs(`1970-01-01T${value}`).tz(timezone).format(displayTimeFormat)
4930
5174
  : "";
4931
5175
  // Parse the initial time parts from the ISO time string (HH:mm:ss)
4932
5176
  const parseTime = (isoTime) => {
4933
5177
  if (!isoTime)
4934
5178
  return { hour: 12, minute: 0, meridiem: "am" };
4935
- const parsed = dayjs(`1970-01-01T${isoTime}Z`).utc();
5179
+ const parsed = dayjs(`1970-01-01T${isoTime}`).tz(timezone);
4936
5180
  if (!parsed.isValid())
4937
5181
  return { hour: 12, minute: 0, meridiem: "am" };
4938
5182
  let hour = parsed.hour();
@@ -4963,8 +5207,8 @@ const TimePicker = ({ column, schema, prefix }) => {
4963
5207
  h = 0;
4964
5208
  else if (meridiem === "pm" && hour < 12)
4965
5209
  h = hour + 12;
4966
- return dayjs(`1970-01-01T${h.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}:00Z`)
4967
- .utc()
5210
+ return dayjs(`1970-01-01T${h.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}:00`)
5211
+ .tz(timezone)
4968
5212
  .format(timeFormat);
4969
5213
  };
4970
5214
  // Handle changes to time parts
@@ -4980,11 +5224,289 @@ const TimePicker = ({ column, schema, prefix }) => {
4980
5224
  gridRow, children: [jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsx(Popover.Trigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
4981
5225
  setOpen(true);
4982
5226
  }, justifyContent: "start", children: [jsx(IoMdClock, {}), !!value ? `${displayedTime}` : ""] }) }), jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { ref: containerRef, children: jsx(Popover.Body, { children: jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, meridiemLabel: {
4983
- am: translate.t(removeIndex(`common.am`)),
4984
- pm: translate.t(removeIndex(`common.pm`)),
5227
+ am: translate.t(`common.am`, { defaultValue: "AM" }),
5228
+ pm: translate.t(`common.pm`, { defaultValue: "PM" }),
4985
5229
  } }) }) }) }) })] }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }));
4986
5230
  };
4987
5231
 
5232
+ function IsoTimePicker({ hour, setHour, minute, setMinute, second, setSecond, onChange = (_newValue) => { }, }) {
5233
+ // Refs for focus management
5234
+ const hourInputRef = useRef(null);
5235
+ const minuteInputRef = useRef(null);
5236
+ const secondInputRef = useRef(null);
5237
+ // Centralized handler for key events, value changes, and focus management
5238
+ const handleKeyDown = (e, field) => {
5239
+ const input = e.target;
5240
+ const value = input.value;
5241
+ // Handle navigation between fields
5242
+ if (e.key === "Tab") {
5243
+ return;
5244
+ }
5245
+ if (e.key === ":" && field === "hour") {
5246
+ e.preventDefault();
5247
+ minuteInputRef.current?.focus();
5248
+ return;
5249
+ }
5250
+ if (e.key === ":" && field === "minute") {
5251
+ e.preventDefault();
5252
+ secondInputRef.current?.focus();
5253
+ return;
5254
+ }
5255
+ if (e.key === "Backspace" && value === "") {
5256
+ e.preventDefault();
5257
+ if (field === "minute") {
5258
+ hourInputRef.current?.focus();
5259
+ }
5260
+ else if (field === "second") {
5261
+ minuteInputRef.current?.focus();
5262
+ }
5263
+ return;
5264
+ }
5265
+ // Handle number inputs
5266
+ if (field === "hour") {
5267
+ if (e.key.match(/^[0-9]$/)) {
5268
+ const newValue = value + e.key;
5269
+ const numValue = parseInt(newValue, 10);
5270
+ if (numValue > 23) {
5271
+ const digitValue = parseInt(e.key, 10);
5272
+ setHour(digitValue);
5273
+ onChange({ hour: digitValue, minute, second });
5274
+ return;
5275
+ }
5276
+ if (numValue >= 0 && numValue <= 23) {
5277
+ setHour(numValue);
5278
+ onChange({ hour: numValue, minute, second });
5279
+ e.preventDefault();
5280
+ minuteInputRef.current?.focus();
5281
+ }
5282
+ }
5283
+ }
5284
+ else if (field === "minute") {
5285
+ if (e.key.match(/^[0-9]$/)) {
5286
+ const newValue = value + e.key;
5287
+ const numValue = parseInt(newValue, 10);
5288
+ if (numValue > 59) {
5289
+ const digitValue = parseInt(e.key, 10);
5290
+ setMinute(digitValue);
5291
+ onChange({ hour, minute: digitValue, second });
5292
+ return;
5293
+ }
5294
+ if (numValue >= 0 && numValue <= 59) {
5295
+ setMinute(numValue);
5296
+ onChange({ hour, minute: numValue, second });
5297
+ e.preventDefault();
5298
+ secondInputRef.current?.focus();
5299
+ }
5300
+ }
5301
+ }
5302
+ else if (field === "second") {
5303
+ if (e.key.match(/^[0-9]$/)) {
5304
+ const newValue = value + e.key;
5305
+ const numValue = parseInt(newValue, 10);
5306
+ if (numValue > 59) {
5307
+ const digitValue = parseInt(e.key, 10);
5308
+ setSecond(digitValue);
5309
+ onChange({ hour, minute, second: digitValue });
5310
+ return;
5311
+ }
5312
+ if (numValue >= 0 && numValue <= 59) {
5313
+ setSecond(numValue);
5314
+ onChange({ hour, minute, second: numValue });
5315
+ }
5316
+ }
5317
+ }
5318
+ };
5319
+ const handleClear = () => {
5320
+ setHour(null);
5321
+ setMinute(null);
5322
+ setSecond(null);
5323
+ onChange({ hour: null, minute: null, second: null });
5324
+ hourInputRef.current?.focus();
5325
+ };
5326
+ return (jsx(Flex, { direction: "column", gap: 3, children: jsxs(Grid, { justifyContent: "center", alignItems: "center", templateColumns: "60px 10px 60px 10px 60px auto", gap: "2", width: "auto", minWidth: "300px", children: [jsx(Input, { ref: hourInputRef, type: "text", value: hour === null ? "" : hour.toString().padStart(2, "0"), onKeyDown: (e) => handleKeyDown(e, "hour"), placeholder: "HH", maxLength: 2, textAlign: "center" }), jsx(Text, { children: ":" }), jsx(Input, { ref: minuteInputRef, type: "text", value: minute === null ? "" : minute.toString().padStart(2, "0"), onKeyDown: (e) => handleKeyDown(e, "minute"), placeholder: "MM", maxLength: 2, textAlign: "center" }), jsx(Text, { children: ":" }), jsx(Input, { ref: secondInputRef, type: "text", value: second === null ? "" : second.toString().padStart(2, "0"), onKeyDown: (e) => handleKeyDown(e, "second"), placeholder: "SS", maxLength: 2, textAlign: "center" }), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "ghost", children: jsx(MdCancel, {}) })] }) }));
5327
+ }
5328
+
5329
+ function DateTimePicker$1({ value, onChange, format = "date-time", showSeconds = false, labels = {
5330
+ monthNamesShort: [
5331
+ "Jan",
5332
+ "Feb",
5333
+ "Mar",
5334
+ "Apr",
5335
+ "May",
5336
+ "Jun",
5337
+ "Jul",
5338
+ "Aug",
5339
+ "Sep",
5340
+ "Oct",
5341
+ "Nov",
5342
+ "Dec",
5343
+ ],
5344
+ weekdayNamesShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
5345
+ backButtonLabel: "Back",
5346
+ forwardButtonLabel: "Next",
5347
+ }, timezone = "Asia/Hong_Kong", }) {
5348
+ const [selectedDate, setSelectedDate] = useState(value || "");
5349
+ // Time state for 12-hour format
5350
+ const [hour12, setHour12] = useState(value ? dayjs(value).hour() % 12 || 12 : null);
5351
+ const [minute, setMinute] = useState(value ? dayjs(value).minute() : null);
5352
+ const [meridiem, setMeridiem] = useState(value ? (dayjs(value).hour() >= 12 ? "pm" : "am") : null);
5353
+ // Time state for 24-hour format
5354
+ const [hour24, setHour24] = useState(value ? dayjs(value).hour() : null);
5355
+ const [second, setSecond] = useState(value ? dayjs(value).second() : null);
5356
+ const handleDateChange = (date) => {
5357
+ setSelectedDate(date);
5358
+ updateDateTime(dayjs(date).tz(timezone).toISOString());
5359
+ };
5360
+ const handleTimeChange = (timeData) => {
5361
+ if (format === "iso-date-time") {
5362
+ setHour24(timeData.hour);
5363
+ setMinute(timeData.minute);
5364
+ if (showSeconds)
5365
+ setSecond(timeData.second);
5366
+ }
5367
+ else {
5368
+ setHour12(timeData.hour);
5369
+ setMinute(timeData.minute);
5370
+ setMeridiem(timeData.meridiem);
5371
+ }
5372
+ updateDateTime(dayjs(selectedDate).tz(timezone).toISOString(), timeData);
5373
+ };
5374
+ const updateDateTime = (date, timeData) => {
5375
+ if (!date) {
5376
+ onChange?.(undefined);
5377
+ return;
5378
+ }
5379
+ // use dayjs to convert the date to the timezone
5380
+ const newDate = dayjs(date).tz(timezone).toDate();
5381
+ if (format === "iso-date-time") {
5382
+ const h = timeData?.hour ?? hour24;
5383
+ const m = timeData?.minute ?? minute;
5384
+ const s = showSeconds ? timeData?.second ?? second : 0;
5385
+ if (h !== null)
5386
+ newDate.setHours(h);
5387
+ if (m !== null)
5388
+ newDate.setMinutes(m);
5389
+ if (s !== null)
5390
+ newDate.setSeconds(s);
5391
+ }
5392
+ else {
5393
+ const h = timeData?.hour ?? hour12;
5394
+ const m = timeData?.minute ?? minute;
5395
+ const mer = timeData?.meridiem ?? meridiem;
5396
+ if (h !== null && mer !== null) {
5397
+ let hour24 = h;
5398
+ if (mer === "am" && h === 12)
5399
+ hour24 = 0;
5400
+ else if (mer === "pm" && h < 12)
5401
+ hour24 = h + 12;
5402
+ newDate.setHours(hour24);
5403
+ }
5404
+ if (m !== null)
5405
+ newDate.setMinutes(m);
5406
+ newDate.setSeconds(0);
5407
+ }
5408
+ onChange?.(dayjs(newDate).tz(timezone).toISOString());
5409
+ };
5410
+ const handleClear = () => {
5411
+ setSelectedDate("");
5412
+ setHour12(null);
5413
+ setHour24(null);
5414
+ setMinute(null);
5415
+ setSecond(null);
5416
+ setMeridiem(null);
5417
+ onChange?.(undefined);
5418
+ };
5419
+ const isISO = format === "iso-date-time";
5420
+ return (jsxs(Flex, { direction: "column", gap: 4, p: 4, border: "1px solid", borderColor: "gray.200", borderRadius: "md", children: [jsx(DatePicker$1, { selected: selectedDate
5421
+ ? dayjs(selectedDate).tz(timezone).toDate()
5422
+ : new Date(), onDateSelected: ({ date }) => handleDateChange(dayjs(date).tz(timezone).toISOString()), monthsToDisplay: 1, labels: labels }), jsxs(Grid, { templateColumns: "1fr auto", alignItems: "center", gap: 4, children: [isISO ? (jsx(IsoTimePicker, { hour: hour24, setHour: setHour24, minute: minute, setMinute: setMinute, second: second, setSecond: setSecond, onChange: handleTimeChange })) : (jsx(TimePicker$1, { hour: hour12, setHour: setHour12, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange })), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "outline", colorScheme: "red", children: jsx(Icon, { as: FaTrash }) })] }), selectedDate && (jsxs(Flex, { gap: 2, children: [jsx(Text, { fontSize: "sm", color: { base: "gray.600", _dark: "gray.600" }, children: dayjs(value).format(isISO
5423
+ ? showSeconds
5424
+ ? "YYYY-MM-DD HH:mm:ss"
5425
+ : "YYYY-MM-DD HH:mm"
5426
+ : "YYYY-MM-DD hh:mm A ") }), jsx(Text, { fontSize: "sm", color: { base: "gray.600", _dark: "gray.600" }, children: dayjs(value).tz(timezone).format("Z") }), jsx(Text, { fontSize: "sm", color: { base: "gray.600", _dark: "gray.600" }, children: timezone })] }))] }));
5427
+ }
5428
+
5429
+ dayjs.extend(utc);
5430
+ dayjs.extend(timezone);
5431
+ const DateTimePicker = ({ column, schema, prefix, }) => {
5432
+ const { watch, formState: { errors }, setValue, } = useFormContext();
5433
+ const { translate, timezone } = useSchemaContext();
5434
+ const { required, gridColumn = "span 4", gridRow = "span 1", displayDateFormat = "YYYY-MM-DD HH:mm:ss",
5435
+ // with timezone
5436
+ dateFormat = "YYYY-MM-DD[T]HH:mm:ssZ", } = schema;
5437
+ const isRequired = required?.some((columnId) => columnId === column);
5438
+ const colLabel = `${prefix}${column}`;
5439
+ const [open, setOpen] = useState(false);
5440
+ const selectedDate = watch(colLabel);
5441
+ const displayDate = dayjs(selectedDate)
5442
+ .tz(timezone)
5443
+ .format(displayDateFormat);
5444
+ useEffect(() => {
5445
+ try {
5446
+ if (selectedDate) {
5447
+ // Parse the selectedDate as UTC or in a specific timezone to avoid +8 hour shift
5448
+ // For example, parse as UTC:
5449
+ const parsedDate = dayjs(selectedDate).tz(timezone);
5450
+ if (!parsedDate.isValid())
5451
+ return;
5452
+ // Format according to dateFormat from schema
5453
+ const formatted = parsedDate.format(dateFormat);
5454
+ // Update the form value only if different to avoid loops
5455
+ if (formatted !== selectedDate) {
5456
+ setValue(colLabel, formatted, {
5457
+ shouldValidate: true,
5458
+ shouldDirty: true,
5459
+ });
5460
+ }
5461
+ }
5462
+ }
5463
+ catch (e) {
5464
+ console.error(e);
5465
+ }
5466
+ }, [selectedDate, dateFormat, colLabel, setValue]);
5467
+ const customTranslate = (label) => {
5468
+ return translateWrapper({ prefix, column, label, translate });
5469
+ };
5470
+ return (jsxs(Field, { label: `${customTranslate(`field_label`)}`, required: isRequired, alignItems: "stretch", gridColumn,
5471
+ gridRow, children: [jsxs(PopoverRoot, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsx(PopoverTrigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
5472
+ setOpen(true);
5473
+ }, justifyContent: "start", children: [jsx(MdDateRange, {}), selectedDate !== undefined ? `${displayDate}` : ""] }) }), jsx(PopoverContent, { minW: "450px", children: jsxs(PopoverBody, { children: [jsx(PopoverTitle, {}), jsx(DateTimePicker$1, { value: selectedDate, onChange: (date) => {
5474
+ setValue(colLabel, dayjs(date).tz(timezone).format(dateFormat));
5475
+ }, timezone: timezone, labels: {
5476
+ monthNamesShort: [
5477
+ translate.t(`common.month_1`, { defaultValue: "January" }),
5478
+ translate.t(`common.month_2`, { defaultValue: "February" }),
5479
+ translate.t(`common.month_3`, { defaultValue: "March" }),
5480
+ translate.t(`common.month_4`, { defaultValue: "April" }),
5481
+ translate.t(`common.month_5`, { defaultValue: "May" }),
5482
+ translate.t(`common.month_6`, { defaultValue: "June" }),
5483
+ translate.t(`common.month_7`, { defaultValue: "July" }),
5484
+ translate.t(`common.month_8`, { defaultValue: "August" }),
5485
+ translate.t(`common.month_9`, { defaultValue: "September" }),
5486
+ translate.t(`common.month_10`, { defaultValue: "October" }),
5487
+ translate.t(`common.month_11`, { defaultValue: "November" }),
5488
+ translate.t(`common.month_12`, { defaultValue: "December" }),
5489
+ ],
5490
+ weekdayNamesShort: [
5491
+ translate.t(`common.weekday_1`, { defaultValue: "Sun" }),
5492
+ translate.t(`common.weekday_2`, { defaultValue: "Mon" }),
5493
+ translate.t(`common.weekday_3`, { defaultValue: "Tue" }),
5494
+ translate.t(`common.weekday_4`, {
5495
+ defaultValue: "Wed",
5496
+ }),
5497
+ translate.t(`common.weekday_5`, { defaultValue: "Thu" }),
5498
+ translate.t(`common.weekday_6`, { defaultValue: "Fri" }),
5499
+ translate.t(`common.weekday_7`, { defaultValue: "Sat" }),
5500
+ ],
5501
+ backButtonLabel: translate.t(`common.back_button`, {
5502
+ defaultValue: "Back",
5503
+ }),
5504
+ forwardButtonLabel: translate.t(`common.forward_button`, {
5505
+ defaultValue: "Forward",
5506
+ }),
5507
+ } })] }) })] }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: customTranslate(`field_required`) }))] }));
5508
+ };
5509
+
4988
5510
  const SchemaRenderer = ({ schema, prefix, column, }) => {
4989
5511
  const colSchema = schema;
4990
5512
  const { type, variant, properties: innerProperties, foreign_key, format, items, } = schema;
@@ -5005,6 +5527,9 @@ const SchemaRenderer = ({ schema, prefix, column, }) => {
5005
5527
  if (format === "time") {
5006
5528
  return jsx(TimePicker, { schema: colSchema, prefix, column });
5007
5529
  }
5530
+ if (format === "date-time") {
5531
+ return jsx(DateTimePicker, { schema: colSchema, prefix, column });
5532
+ }
5008
5533
  if (variant === "text-area") {
5009
5534
  return jsx(TextAreaInput, { schema: colSchema, prefix, column });
5010
5535
  }
@@ -5096,20 +5621,16 @@ const CustomViewer = ({ column, schema, prefix }) => {
5096
5621
 
5097
5622
  const DateViewer = ({ column, schema, prefix }) => {
5098
5623
  const { watch, formState: { errors }, } = useFormContext();
5099
- const { translate } = useSchemaContext();
5624
+ const { translate, timezone } = useSchemaContext();
5100
5625
  const { required, gridColumn = "span 4", gridRow = "span 1", displayDateFormat = "YYYY-MM-DD", } = schema;
5101
5626
  const isRequired = required?.some((columnId) => columnId === column);
5102
5627
  const colLabel = `${prefix}${column}`;
5103
5628
  const selectedDate = watch(colLabel);
5104
- const displayDate = dayjs.utc(selectedDate).format(displayDateFormat);
5629
+ const displayDate = dayjs(selectedDate).tz(timezone).format(displayDateFormat);
5105
5630
  return (jsxs(Field, { label: `${translate.t(removeIndex(`${column}.field_label`))}`, required: isRequired, alignItems: "stretch", gridColumn,
5106
5631
  gridRow, children: [jsxs(Text, { children: [" ", selectedDate !== undefined ? displayDate : ""] }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(`${column}.field_required`) }))] }));
5107
5632
  };
5108
5633
 
5109
- function translateWrapper({ prefix, column, label, translate, }) {
5110
- return translate.t(removeIndex(`${prefix}${column}.${label}`));
5111
- }
5112
-
5113
5634
  const EnumViewer = ({ column, isMultiple = false, schema, prefix, }) => {
5114
5635
  const { watch, formState: { errors }, } = useFormContext();
5115
5636
  const { translate } = useSchemaContext();
@@ -5365,20 +5886,34 @@ const TextAreaViewer = ({ column, schema, prefix, }) => {
5365
5886
  return (jsx(Fragment, { children: jsxs(Field, { label: `${translate.t(removeIndex(`${colLabel}.field_label`))}`, required: isRequired, gridColumn: gridColumn, gridRow: gridRow, children: [jsx(Text, { whiteSpace: "pre-wrap", children: value }), " ", errors[colLabel] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }) }));
5366
5887
  };
5367
5888
 
5368
- const TimeViewer = ({ column, schema, prefix, }) => {
5889
+ const TimeViewer = ({ column, schema, prefix }) => {
5369
5890
  const { watch, formState: { errors }, } = useFormContext();
5370
- const { translate } = useSchemaContext();
5371
- const { required, gridColumn = "span 4", gridRow = "span 1", displayTimeFormat = "hh:mm A" } = schema;
5891
+ const { translate, timezone } = useSchemaContext();
5892
+ const { required, gridColumn = "span 4", gridRow = "span 1", displayTimeFormat = "hh:mm A", } = schema;
5372
5893
  const isRequired = required?.some((columnId) => columnId === column);
5373
5894
  const colLabel = `${prefix}${column}`;
5374
5895
  const selectedDate = watch(colLabel);
5375
- const displayedTime = dayjs(`1970-01-01T${selectedDate}Z`).isValid()
5376
- ? dayjs(`1970-01-01T${selectedDate}Z`).utc().format(displayTimeFormat)
5896
+ const displayedTime = dayjs(`1970-01-01T${selectedDate}`)
5897
+ .tz(timezone)
5898
+ .isValid()
5899
+ ? dayjs(`1970-01-01T${selectedDate}`).tz(timezone).format(displayTimeFormat)
5377
5900
  : "";
5378
5901
  return (jsxs(Field, { label: `${translate.t(removeIndex(`${column}.field_label`))}`, required: isRequired, alignItems: "stretch", gridColumn,
5379
5902
  gridRow, children: [jsx(Text, { children: displayedTime }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(`${column}.field_required`) }))] }));
5380
5903
  };
5381
5904
 
5905
+ const DateTimeViewer = ({ column, schema, prefix }) => {
5906
+ const { watch, formState: { errors }, } = useFormContext();
5907
+ const { translate, timezone } = useSchemaContext();
5908
+ const { required, gridColumn = "span 4", gridRow = "span 1", displayDateFormat = "YYYY-MM-DD HH:mm:ss", } = schema;
5909
+ const isRequired = required?.some((columnId) => columnId === column);
5910
+ const colLabel = `${prefix}${column}`;
5911
+ const selectedDate = watch(colLabel);
5912
+ const displayDate = dayjs(selectedDate).tz(timezone).format(displayDateFormat);
5913
+ return (jsxs(Field, { label: `${translate.t(removeIndex(`${column}.field_label`))}`, required: isRequired, alignItems: "stretch", gridColumn,
5914
+ gridRow, children: [jsxs(Text, { children: [" ", selectedDate !== undefined ? displayDate : ""] }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(`${column}.field_required`) }))] }));
5915
+ };
5916
+
5382
5917
  const SchemaViewer = ({ schema, prefix, column, }) => {
5383
5918
  const colSchema = schema;
5384
5919
  const { type, variant, properties: innerProperties, foreign_key, items, format, } = schema;
@@ -5399,6 +5934,9 @@ const SchemaViewer = ({ schema, prefix, column, }) => {
5399
5934
  if (format === "date") {
5400
5935
  return jsx(DateViewer, { schema: colSchema, prefix, column });
5401
5936
  }
5937
+ if (format === "date-time") {
5938
+ return jsx(DateTimeViewer, { schema: colSchema, prefix, column });
5939
+ }
5402
5940
  if (variant === "text-area") {
5403
5941
  return jsx(TextAreaViewer, { schema: colSchema, prefix, column });
5404
5942
  }
@@ -5450,10 +5988,28 @@ const ColumnViewer = ({ column, properties, prefix, }) => {
5450
5988
  };
5451
5989
 
5452
5990
  const SubmitButton = () => {
5453
- const { translate, setValidatedData, setIsError, setIsConfirming } = useSchemaContext();
5991
+ const { translate, setValidatedData, setIsError, setIsConfirming, setError, schema, validationLocale } = useSchemaContext();
5454
5992
  const methods = useFormContext();
5455
5993
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5456
5994
  const onValid = (data) => {
5995
+ // Validate data using AJV before proceeding to confirmation
5996
+ const validationResult = validateData(data, schema, { locale: validationLocale });
5997
+ if (!validationResult.isValid) {
5998
+ // Set validation errors with i18n support
5999
+ const validationErrorMessage = {
6000
+ type: 'validation',
6001
+ errors: validationResult.errors,
6002
+ message: validationLocale === 'zh-HK' || validationLocale === 'zh-TW'
6003
+ ? '表單驗證失敗'
6004
+ : validationLocale === 'zh-CN' || validationLocale === 'zh'
6005
+ ? '表单验证失败'
6006
+ : 'Form validation failed'
6007
+ };
6008
+ setError(validationErrorMessage);
6009
+ setIsError(true);
6010
+ return;
6011
+ }
6012
+ // If validation passes, proceed to confirmation
5457
6013
  setValidatedData(data);
5458
6014
  setIsError(false);
5459
6015
  setIsConfirming(true);
@@ -5464,7 +6020,7 @@ const SubmitButton = () => {
5464
6020
  };
5465
6021
 
5466
6022
  const FormBody = () => {
5467
- 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();
6023
+ 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();
5468
6024
  const methods = useFormContext();
5469
6025
  const { properties } = schema;
5470
6026
  const onBeforeSubmit = () => {
@@ -5480,6 +6036,27 @@ const FormBody = () => {
5480
6036
  const onSubmitSuccess = () => {
5481
6037
  setIsSuccess(true);
5482
6038
  };
6039
+ // Enhanced validation function using AJV with i18n support
6040
+ const validateFormData = (data) => {
6041
+ try {
6042
+ const validationResult = validateData(data, schema, { locale: validationLocale });
6043
+ return validationResult;
6044
+ }
6045
+ catch (error) {
6046
+ const errorMessage = validationLocale === 'zh-HK' || validationLocale === 'zh-TW'
6047
+ ? `驗證錯誤: ${error instanceof Error ? error.message : '未知驗證錯誤'}`
6048
+ : validationLocale === 'zh-CN' || validationLocale === 'zh'
6049
+ ? `验证错误: ${error instanceof Error ? error.message : '未知验证错误'}`
6050
+ : `Validation error: ${error instanceof Error ? error.message : 'Unknown validation error'}`;
6051
+ return {
6052
+ isValid: false,
6053
+ errors: [{
6054
+ field: 'validation',
6055
+ message: errorMessage
6056
+ }]
6057
+ };
6058
+ }
6059
+ };
5483
6060
  const defaultOnSubmit = async (promise) => {
5484
6061
  try {
5485
6062
  onBeforeSubmit();
@@ -5504,12 +6081,47 @@ const FormBody = () => {
5504
6081
  };
5505
6082
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5506
6083
  const onFormSubmit = async (data) => {
6084
+ // Validate data using AJV before submission
6085
+ const validationResult = validateFormData(data);
6086
+ if (!validationResult.isValid) {
6087
+ // Set validation errors
6088
+ const validationErrorMessage = {
6089
+ type: 'validation',
6090
+ errors: validationResult.errors,
6091
+ message: validationLocale === 'zh-HK' || validationLocale === 'zh-TW'
6092
+ ? '表單驗證失敗'
6093
+ : validationLocale === 'zh-CN' || validationLocale === 'zh'
6094
+ ? '表单验证失败'
6095
+ : 'Form validation failed'
6096
+ };
6097
+ onSubmitError(validationErrorMessage);
6098
+ return;
6099
+ }
5507
6100
  if (onSubmit === undefined) {
5508
6101
  await defaultOnSubmit(defaultSubmitPromise(data));
5509
6102
  return;
5510
6103
  }
5511
6104
  await defaultOnSubmit(onSubmit(data));
5512
6105
  };
6106
+ // Custom error renderer for validation errors with i18n support
6107
+ const renderValidationErrors = (validationErrors) => {
6108
+ const title = validationLocale === 'zh-HK' || validationLocale === 'zh-TW'
6109
+ ? `表單驗證失敗 (${validationErrors.length} 個錯誤${validationErrors.length > 1 ? '' : ''})`
6110
+ : validationLocale === 'zh-CN' || validationLocale === 'zh'
6111
+ ? `表单验证失败 (${validationErrors.length} 个错误${validationErrors.length > 1 ? '' : ''})`
6112
+ : `Form Validation Failed (${validationErrors.length} error${validationErrors.length > 1 ? 's' : ''})`;
6113
+ const formLabel = validationLocale === 'zh-HK' || validationLocale === 'zh-TW'
6114
+ ? '表單'
6115
+ : validationLocale === 'zh-CN' || validationLocale === 'zh'
6116
+ ? '表单'
6117
+ : 'Form';
6118
+ const currentValueLabel = validationLocale === 'zh-HK' || validationLocale === 'zh-TW'
6119
+ ? '目前值:'
6120
+ : validationLocale === 'zh-CN' || validationLocale === 'zh'
6121
+ ? '当前值:'
6122
+ : 'Current value:';
6123
+ return (jsxs(Alert.Root, { status: "error", children: [jsx(Alert.Indicator, {}), jsx(Alert.Title, { children: jsx(AccordionRoot, { collapsible: true, defaultValue: [], children: jsxs(AccordionItem, { value: "validation-errors", children: [jsx(AccordionItemTrigger, { children: title }), jsx(AccordionItemContent, { children: jsx(Box, { mt: 2, children: validationErrors.map((err, index) => (jsxs(Box, { mb: 2, p: 2, bg: "red.50", borderLeft: "4px solid", borderColor: "red.500", children: [jsxs(Text, { fontWeight: "bold", color: "red.700", children: [err.field === 'root' ? formLabel : err.field, ":"] }), jsx(Text, { color: "red.600", children: err.message }), err.value !== undefined && (jsxs(Text, { fontSize: "sm", color: "red.500", mt: 1, children: [currentValueLabel, " ", JSON.stringify(err.value)] }))] }, index))) }) })] }) }) })] }));
6124
+ };
5513
6125
  const renderColumns = ({ order, keys, ignore, include, }) => {
5514
6126
  const included = include.length > 0 ? include : keys;
5515
6127
  const not_exist = included.filter((columnA) => !order.some((columnB) => columnA === columnB));
@@ -5545,7 +6157,7 @@ const FormBody = () => {
5545
6157
  setIsConfirming(false);
5546
6158
  }, variant: "subtle", children: translate.t("cancel") }), jsx(Button$1, { onClick: () => {
5547
6159
  onFormSubmit(validatedData);
5548
- }, children: translate.t("confirm") })] }), isSubmiting && (jsx(Box, { pos: "absolute", inset: "0", bg: "bg/80", children: jsx(Center, { h: "full", children: jsx(Spinner, { color: "teal.500" }) }) })), isError && (jsx(Fragment, { children: customErrorRenderer ? (customErrorRenderer(error)) : (jsx(Alert.Root, { status: "error", children: jsx(Alert.Title, { children: jsx(AccordionRoot, { collapsible: true, defaultValue: [], children: jsxs(AccordionItem, { value: "b", children: [jsxs(AccordionItemTrigger, { children: [jsx(Alert.Indicator, {}), `${error}`] }), jsx(AccordionItemContent, { children: `${JSON.stringify(error)}` })] }) }) }) })) }))] }));
6160
+ }, children: translate.t("confirm") })] }), isSubmiting && (jsx(Box, { pos: "absolute", inset: "0", bg: "bg/80", children: jsx(Center, { h: "full", children: jsx(Spinner, { color: "teal.500" }) }) })), isError && (jsx(Fragment, { children: customErrorRenderer ? (customErrorRenderer(error)) : (jsx(Fragment, { children: error?.type === 'validation' && error?.errors ? (renderValidationErrors(error.errors)) : (jsx(Alert.Root, { status: "error", children: jsx(Alert.Title, { children: jsx(AccordionRoot, { collapsible: true, defaultValue: [], children: jsxs(AccordionItem, { value: "b", children: [jsxs(AccordionItemTrigger, { children: [jsx(Alert.Indicator, {}), `${error}`] }), jsx(AccordionItemContent, { children: `${JSON.stringify(error)}` })] }) }) }) })) })) }))] }));
5549
6161
  }
5550
6162
  return (jsxs(Flex, { flexFlow: "column", gap: "2", children: [jsx(Grid, { gap: "4", gridTemplateColumns: "repeat(12, 1fr)", autoFlow: "row", children: ordered.map((column) => {
5551
6163
  return (jsx(ColumnRenderer
@@ -5555,7 +6167,7 @@ const FormBody = () => {
5555
6167
  properties: properties, prefix: ``, column }, `form-input-${column}`));
5556
6168
  }) }), jsxs(Flex, { justifyContent: "end", gap: "2", children: [jsx(Button$1, { onClick: () => {
5557
6169
  methods.reset();
5558
- }, variant: "subtle", children: translate.t("reset") }), jsx(SubmitButton, {})] })] }));
6170
+ }, variant: "subtle", children: translate.t("reset") }), jsx(SubmitButton, {})] }), isError && error?.type === 'validation' && (jsx(Box, { mt: 4, children: error?.errors && renderValidationErrors(error.errors) }))] }));
5559
6171
  };
5560
6172
 
5561
6173
  const FormTitle = () => {
@@ -5597,4 +6209,4 @@ const getMultiDates = ({ selected, selectedDate, selectedDates, selectable, }) =
5597
6209
  }
5598
6210
  };
5599
6211
 
5600
- export { CardHeader, DataDisplay, DataTable, DataTableServer, DefaultCardTitle, DefaultForm, DefaultTable, DensityToggleButton, EditSortingButton, EmptyState$1 as EmptyState, ErrorAlert, FilterDialog, FormBody, FormRoot, FormTitle, GlobalFilter, PageSizeControl, Pagination, RecordDisplay, ReloadButton, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, RowCountText, Table, TableBody, TableCardContainer, TableCards, TableComponent, TableControls, TableDataDisplay, TableFilter, TableFilterTags, TableFooter, TableHeader, TableLoadingComponent, TableSelector, TableSorter, TableViewer, TextCell, ViewDialog, getColumns, getMultiDates, getRangeDates, idPickerSanityCheck, useDataTable, useDataTableContext, useDataTableServer, useForm, widthSanityCheck };
6212
+ export { CardHeader, DataDisplay, DataTable, DataTableServer, DefaultCardTitle, DefaultForm, DefaultTable, DensityToggleButton, EditSortingButton, EmptyState$1 as EmptyState, ErrorAlert, FilterDialog, FormBody, FormRoot, FormTitle, GlobalFilter, PageSizeControl, Pagination, RecordDisplay, ReloadButton, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, RowCountText, Table, TableBody, TableCardContainer, TableCards, TableComponent, TableControls, TableDataDisplay, TableFilter, TableFilterTags, TableFooter, TableHeader, TableLoadingComponent, TableSelector, TableSorter, TableViewer, TextCell, ViewDialog, createSchemaValidator, getColumns, getMultiDates, getRangeDates, getSupportedLocales, idPickerSanityCheck, isLocaleSupported, useDataTable, useDataTableContext, useDataTableServer, useForm, validateData, widthSanityCheck };