@bsol-oss/react-datatable5 13.0.1-beta.3 → 13.0.1-beta.5
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.d.ts +24 -18
- package/dist/index.js +661 -209
- package/dist/index.mjs +661 -209
- package/dist/types/components/DatePicker/DatePicker.d.ts +24 -0
- package/dist/types/components/DatePicker/DateTimePicker.d.ts +14 -1
- package/dist/types/components/DatePicker/index.d.ts +1 -1
- package/dist/types/components/Form/components/fields/FilePicker.d.ts +1 -1
- package/dist/types/components/Form/components/viewers/EnumViewer.d.ts +1 -1
- package/dist/types/components/Form/utils/useFormI18n.d.ts +2 -2
- package/dist/types/index.d.ts +0 -1
- package/package.json +1 -1
- package/dist/types/components/DatePicker/DatePickerInput.d.ts +0 -18
package/dist/index.mjs
CHANGED
|
@@ -30,8 +30,8 @@ import addFormats from 'ajv-formats';
|
|
|
30
30
|
import dayjs from 'dayjs';
|
|
31
31
|
import utc from 'dayjs/plugin/utc';
|
|
32
32
|
import timezone from 'dayjs/plugin/timezone';
|
|
33
|
-
import { TiDeleteOutline } from 'react-icons/ti';
|
|
34
33
|
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
|
34
|
+
import { TiDeleteOutline } from 'react-icons/ti';
|
|
35
35
|
import { rankItem } from '@tanstack/match-sorter-utils';
|
|
36
36
|
|
|
37
37
|
const DataTableContext = createContext({
|
|
@@ -4126,6 +4126,22 @@ const convertAjvErrorsToFieldErrors = (errors, schema) => {
|
|
|
4126
4126
|
// Get the schema node for this field to check for custom error messages
|
|
4127
4127
|
const fieldSchema = getSchemaNodeForField(schema, fieldName);
|
|
4128
4128
|
const customMessage = fieldSchema?.errorMessages?.[error.keyword];
|
|
4129
|
+
// Debug log when error message is missing
|
|
4130
|
+
if (!customMessage) {
|
|
4131
|
+
console.debug(`[Form Validation] Missing error message for field '${fieldName}' with keyword '${error.keyword}'. Add errorMessages.${error.keyword} to schema for field '${fieldName}'`, {
|
|
4132
|
+
fieldName,
|
|
4133
|
+
keyword: error.keyword,
|
|
4134
|
+
instancePath: error.instancePath,
|
|
4135
|
+
schemaPath: error.schemaPath,
|
|
4136
|
+
params: error.params,
|
|
4137
|
+
fieldSchema: fieldSchema
|
|
4138
|
+
? {
|
|
4139
|
+
type: fieldSchema.type,
|
|
4140
|
+
errorMessages: fieldSchema.errorMessages,
|
|
4141
|
+
}
|
|
4142
|
+
: undefined,
|
|
4143
|
+
});
|
|
4144
|
+
}
|
|
4129
4145
|
// Provide helpful fallback message if no custom message is provided
|
|
4130
4146
|
const fallbackMessage = customMessage ||
|
|
4131
4147
|
`Missing error message for ${error.keyword}. Add errorMessages.${error.keyword} to schema for field '${fieldName}'`;
|
|
@@ -4368,7 +4384,7 @@ function removeIndex(str) {
|
|
|
4368
4384
|
*
|
|
4369
4385
|
* @param column - The column name
|
|
4370
4386
|
* @param prefix - The prefix for the field (usually empty string or parent path)
|
|
4371
|
-
* @param schema -
|
|
4387
|
+
* @param schema - Required schema object with title property
|
|
4372
4388
|
* @returns Object with label helper functions
|
|
4373
4389
|
*
|
|
4374
4390
|
* @example
|
|
@@ -4401,9 +4417,21 @@ const useFormI18n = (column, prefix = '', schema) => {
|
|
|
4401
4417
|
* Uses schema.title if available, otherwise: translate.t(removeIndex(`${colLabel}.field_label`))
|
|
4402
4418
|
*/
|
|
4403
4419
|
label: (options) => {
|
|
4404
|
-
if (schema
|
|
4420
|
+
if (schema.title) {
|
|
4405
4421
|
return schema.title;
|
|
4406
4422
|
}
|
|
4423
|
+
// Debug log when field title is missing
|
|
4424
|
+
console.debug(`[Form Field Label] Missing title for field '${colLabel}'. Add title property to schema for field '${colLabel}'.`, {
|
|
4425
|
+
fieldName: column,
|
|
4426
|
+
colLabel,
|
|
4427
|
+
prefix,
|
|
4428
|
+
schema: {
|
|
4429
|
+
type: schema.type,
|
|
4430
|
+
errorMessages: schema.errorMessages
|
|
4431
|
+
? Object.keys(schema.errorMessages)
|
|
4432
|
+
: undefined,
|
|
4433
|
+
},
|
|
4434
|
+
});
|
|
4407
4435
|
return translate.t(removeIndex(`${colLabel}.field_label`), options);
|
|
4408
4436
|
},
|
|
4409
4437
|
/**
|
|
@@ -4497,6 +4525,9 @@ const CustomInput = ({ column, schema, prefix }) => {
|
|
|
4497
4525
|
}));
|
|
4498
4526
|
};
|
|
4499
4527
|
|
|
4528
|
+
dayjs.extend(utc);
|
|
4529
|
+
dayjs.extend(timezone);
|
|
4530
|
+
dayjs.extend(customParseFormat);
|
|
4500
4531
|
const Calendar = ({ calendars, getBackProps, getForwardProps, getDateProps, firstDayOfWeek = 0, }) => {
|
|
4501
4532
|
const { labels } = useContext(DatePickerContext);
|
|
4502
4533
|
const { monthNamesShort, weekdayNamesShort, backButtonLabel, forwardButtonLabel, } = labels;
|
|
@@ -4566,6 +4597,9 @@ const DatePickerContext = createContext({
|
|
|
4566
4597
|
weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
|
4567
4598
|
backButtonLabel: 'Back',
|
|
4568
4599
|
forwardButtonLabel: 'Next',
|
|
4600
|
+
todayLabel: 'Today',
|
|
4601
|
+
yesterdayLabel: 'Yesterday',
|
|
4602
|
+
tomorrowLabel: 'Tomorrow',
|
|
4569
4603
|
},
|
|
4570
4604
|
});
|
|
4571
4605
|
const DatePicker$1 = ({ labels = {
|
|
@@ -4586,6 +4620,9 @@ const DatePicker$1 = ({ labels = {
|
|
|
4586
4620
|
weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
|
4587
4621
|
backButtonLabel: 'Back',
|
|
4588
4622
|
forwardButtonLabel: 'Next',
|
|
4623
|
+
todayLabel: 'Today',
|
|
4624
|
+
yesterdayLabel: 'Yesterday',
|
|
4625
|
+
tomorrowLabel: 'Tomorrow',
|
|
4589
4626
|
}, onDateSelected, selected, firstDayOfWeek, showOutsideDays, date, minDate, maxDate, monthsToDisplay, render, }) => {
|
|
4590
4627
|
const calendarData = useCalendar({
|
|
4591
4628
|
onDateSelected,
|
|
@@ -4600,9 +4637,164 @@ const DatePicker$1 = ({ labels = {
|
|
|
4600
4637
|
return (jsx(DatePickerContext.Provider, { value: { labels }, children: render ? (render(calendarData)) : (jsx(Calendar, { ...calendarData,
|
|
4601
4638
|
firstDayOfWeek })) }));
|
|
4602
4639
|
};
|
|
4640
|
+
function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateFormat = 'YYYY-MM-DD', displayFormat = 'YYYY-MM-DD', labels = {
|
|
4641
|
+
monthNamesShort: [
|
|
4642
|
+
'Jan',
|
|
4643
|
+
'Feb',
|
|
4644
|
+
'Mar',
|
|
4645
|
+
'Apr',
|
|
4646
|
+
'May',
|
|
4647
|
+
'Jun',
|
|
4648
|
+
'Jul',
|
|
4649
|
+
'Aug',
|
|
4650
|
+
'Sep',
|
|
4651
|
+
'Oct',
|
|
4652
|
+
'Nov',
|
|
4653
|
+
'Dec',
|
|
4654
|
+
],
|
|
4655
|
+
weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
|
4656
|
+
backButtonLabel: 'Back',
|
|
4657
|
+
forwardButtonLabel: 'Next',
|
|
4658
|
+
todayLabel: 'Today',
|
|
4659
|
+
yesterdayLabel: 'Yesterday',
|
|
4660
|
+
tomorrowLabel: 'Tomorrow',
|
|
4661
|
+
}, timezone = 'Asia/Hong_Kong', minDate, maxDate, firstDayOfWeek, showOutsideDays, monthsToDisplay = 1, insideDialog = false, readOnly = false, showHelperButtons = true, }) {
|
|
4662
|
+
const [open, setOpen] = useState(false);
|
|
4663
|
+
const [inputValue, setInputValue] = useState('');
|
|
4664
|
+
// Update input value when prop value changes
|
|
4665
|
+
useEffect(() => {
|
|
4666
|
+
if (value) {
|
|
4667
|
+
const formatted = typeof value === 'string'
|
|
4668
|
+
? dayjs(value).tz(timezone).isValid()
|
|
4669
|
+
? dayjs(value).tz(timezone).format(displayFormat)
|
|
4670
|
+
: ''
|
|
4671
|
+
: dayjs(value).tz(timezone).format(displayFormat);
|
|
4672
|
+
setInputValue(formatted);
|
|
4673
|
+
}
|
|
4674
|
+
else {
|
|
4675
|
+
setInputValue('');
|
|
4676
|
+
}
|
|
4677
|
+
}, [value, displayFormat, timezone]);
|
|
4678
|
+
// Convert value to Date object for DatePicker
|
|
4679
|
+
const selectedDate = value
|
|
4680
|
+
? typeof value === 'string'
|
|
4681
|
+
? dayjs(value).tz(timezone).isValid()
|
|
4682
|
+
? dayjs(value).tz(timezone).toDate()
|
|
4683
|
+
: new Date()
|
|
4684
|
+
: value
|
|
4685
|
+
: new Date();
|
|
4686
|
+
// Shared function to parse and validate input value
|
|
4687
|
+
const parseAndValidateInput = (inputVal) => {
|
|
4688
|
+
// If empty, clear the value
|
|
4689
|
+
if (!inputVal.trim()) {
|
|
4690
|
+
onChange?.(undefined);
|
|
4691
|
+
setInputValue('');
|
|
4692
|
+
return;
|
|
4693
|
+
}
|
|
4694
|
+
// Try parsing with displayFormat first
|
|
4695
|
+
let parsedDate = dayjs(inputVal, displayFormat, true);
|
|
4696
|
+
// If that fails, try common date formats
|
|
4697
|
+
if (!parsedDate.isValid()) {
|
|
4698
|
+
parsedDate = dayjs(inputVal);
|
|
4699
|
+
}
|
|
4700
|
+
// If still invalid, try parsing with dateFormat
|
|
4701
|
+
if (!parsedDate.isValid()) {
|
|
4702
|
+
parsedDate = dayjs(inputVal, dateFormat, true);
|
|
4703
|
+
}
|
|
4704
|
+
// If valid, check constraints and update
|
|
4705
|
+
if (parsedDate.isValid()) {
|
|
4706
|
+
const dateObj = parsedDate.tz(timezone).toDate();
|
|
4707
|
+
// Check min/max constraints
|
|
4708
|
+
if (minDate && dateObj < minDate) {
|
|
4709
|
+
// Invalid: before minDate, reset to prop value
|
|
4710
|
+
resetToPropValue();
|
|
4711
|
+
return;
|
|
4712
|
+
}
|
|
4713
|
+
if (maxDate && dateObj > maxDate) {
|
|
4714
|
+
// Invalid: after maxDate, reset to prop value
|
|
4715
|
+
resetToPropValue();
|
|
4716
|
+
return;
|
|
4717
|
+
}
|
|
4718
|
+
// Valid date - format and update
|
|
4719
|
+
const formattedDate = parsedDate.tz(timezone).format(dateFormat);
|
|
4720
|
+
const formattedDisplay = parsedDate.tz(timezone).format(displayFormat);
|
|
4721
|
+
onChange?.(formattedDate);
|
|
4722
|
+
setInputValue(formattedDisplay);
|
|
4723
|
+
}
|
|
4724
|
+
else {
|
|
4725
|
+
// Invalid date - reset to prop value
|
|
4726
|
+
resetToPropValue();
|
|
4727
|
+
}
|
|
4728
|
+
};
|
|
4729
|
+
// Helper function to reset input to prop value
|
|
4730
|
+
const resetToPropValue = () => {
|
|
4731
|
+
if (value) {
|
|
4732
|
+
const formatted = typeof value === 'string'
|
|
4733
|
+
? dayjs(value).tz(timezone).isValid()
|
|
4734
|
+
? dayjs(value).tz(timezone).format(displayFormat)
|
|
4735
|
+
: ''
|
|
4736
|
+
: dayjs(value).tz(timezone).format(displayFormat);
|
|
4737
|
+
setInputValue(formatted);
|
|
4738
|
+
}
|
|
4739
|
+
else {
|
|
4740
|
+
setInputValue('');
|
|
4741
|
+
}
|
|
4742
|
+
};
|
|
4743
|
+
const handleInputChange = (e) => {
|
|
4744
|
+
// Only update the input value, don't parse yet
|
|
4745
|
+
setInputValue(e.target.value);
|
|
4746
|
+
};
|
|
4747
|
+
const handleInputBlur = () => {
|
|
4748
|
+
// Parse and validate when input loses focus
|
|
4749
|
+
parseAndValidateInput(inputValue);
|
|
4750
|
+
};
|
|
4751
|
+
const handleKeyDown = (e) => {
|
|
4752
|
+
// Parse and validate when Enter is pressed
|
|
4753
|
+
if (e.key === 'Enter') {
|
|
4754
|
+
e.preventDefault();
|
|
4755
|
+
parseAndValidateInput(inputValue);
|
|
4756
|
+
}
|
|
4757
|
+
};
|
|
4758
|
+
const handleDateSelected = ({ date }) => {
|
|
4759
|
+
const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
|
|
4760
|
+
onChange?.(formattedDate);
|
|
4761
|
+
setOpen(false);
|
|
4762
|
+
};
|
|
4763
|
+
// Helper function to get dates in the correct timezone
|
|
4764
|
+
const getToday = () => dayjs().tz(timezone).startOf('day').toDate();
|
|
4765
|
+
const getYesterday = () => dayjs().tz(timezone).subtract(1, 'day').startOf('day').toDate();
|
|
4766
|
+
const getTomorrow = () => dayjs().tz(timezone).add(1, 'day').startOf('day').toDate();
|
|
4767
|
+
// Check if a date is within min/max constraints
|
|
4768
|
+
const isDateValid = (date) => {
|
|
4769
|
+
if (minDate) {
|
|
4770
|
+
const minDateStart = dayjs(minDate).tz(timezone).startOf('day').toDate();
|
|
4771
|
+
const dateStart = dayjs(date).tz(timezone).startOf('day').toDate();
|
|
4772
|
+
if (dateStart < minDateStart)
|
|
4773
|
+
return false;
|
|
4774
|
+
}
|
|
4775
|
+
if (maxDate) {
|
|
4776
|
+
const maxDateStart = dayjs(maxDate).tz(timezone).startOf('day').toDate();
|
|
4777
|
+
const dateStart = dayjs(date).tz(timezone).startOf('day').toDate();
|
|
4778
|
+
if (dateStart > maxDateStart)
|
|
4779
|
+
return false;
|
|
4780
|
+
}
|
|
4781
|
+
return true;
|
|
4782
|
+
};
|
|
4783
|
+
const handleHelperButtonClick = (date) => {
|
|
4784
|
+
if (isDateValid(date)) {
|
|
4785
|
+
handleDateSelected({ date });
|
|
4786
|
+
}
|
|
4787
|
+
};
|
|
4788
|
+
const today = getToday();
|
|
4789
|
+
const yesterday = getYesterday();
|
|
4790
|
+
const tomorrow = getTomorrow();
|
|
4791
|
+
const datePickerContent = (jsxs(Grid, { gap: 2, children: [showHelperButtons && (jsxs(Grid, { templateColumns: "repeat(3, 1fr)", gap: 2, children: [jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleHelperButtonClick(yesterday), disabled: !isDateValid(yesterday), children: labels.yesterdayLabel ?? 'Yesterday' }), jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleHelperButtonClick(today), disabled: !isDateValid(today), children: labels.todayLabel ?? 'Today' }), jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleHelperButtonClick(tomorrow), disabled: !isDateValid(tomorrow), children: labels.tomorrowLabel ?? 'Tomorrow' })] })), jsx(DatePicker$1, { selected: selectedDate, onDateSelected: handleDateSelected, labels: labels, minDate: minDate, maxDate: maxDate, firstDayOfWeek: firstDayOfWeek, showOutsideDays: showOutsideDays, monthsToDisplay: monthsToDisplay })] }));
|
|
4792
|
+
return (jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(InputGroup, { endElement: jsx(Popover.Trigger, { asChild: true, children: jsx(IconButton, { variant: "ghost", size: "2xs", "aria-label": "Open calendar", onClick: () => setOpen(true), children: jsx(Icon, { children: jsx(MdDateRange, {}) }) }) }), children: jsx(Input, { value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onKeyDown: handleKeyDown, placeholder: placeholder, readOnly: readOnly }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) }) }))] }));
|
|
4793
|
+
}
|
|
4603
4794
|
|
|
4604
4795
|
dayjs.extend(utc);
|
|
4605
4796
|
dayjs.extend(timezone);
|
|
4797
|
+
dayjs.extend(customParseFormat);
|
|
4606
4798
|
const DatePicker = ({ column, schema, prefix }) => {
|
|
4607
4799
|
const { watch, formState: { errors }, setValue, } = useFormContext();
|
|
4608
4800
|
const { timezone, dateTimePickerLabels, insideDialog } = useSchemaContext();
|
|
@@ -4611,15 +4803,29 @@ const DatePicker = ({ column, schema, prefix }) => {
|
|
|
4611
4803
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
4612
4804
|
const colLabel = formI18n.colLabel;
|
|
4613
4805
|
const [open, setOpen] = useState(false);
|
|
4806
|
+
const [inputValue, setInputValue] = useState('');
|
|
4614
4807
|
const selectedDate = watch(colLabel);
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4808
|
+
// Update input value when form value changes
|
|
4809
|
+
useEffect(() => {
|
|
4810
|
+
if (selectedDate) {
|
|
4811
|
+
const parsedDate = dayjs(selectedDate).tz(timezone);
|
|
4812
|
+
if (parsedDate.isValid()) {
|
|
4813
|
+
const formatted = parsedDate.format(displayDateFormat);
|
|
4814
|
+
setInputValue(formatted);
|
|
4815
|
+
}
|
|
4816
|
+
else {
|
|
4817
|
+
setInputValue('');
|
|
4818
|
+
}
|
|
4819
|
+
}
|
|
4820
|
+
else {
|
|
4821
|
+
setInputValue('');
|
|
4822
|
+
}
|
|
4823
|
+
}, [selectedDate, displayDateFormat, timezone]);
|
|
4824
|
+
// Format and validate existing value
|
|
4618
4825
|
useEffect(() => {
|
|
4619
4826
|
try {
|
|
4620
4827
|
if (selectedDate) {
|
|
4621
4828
|
// Parse the selectedDate as UTC or in a specific timezone to avoid +8 hour shift
|
|
4622
|
-
// For example, parse as UTC:
|
|
4623
4829
|
const parsedDate = dayjs(selectedDate).tz(timezone);
|
|
4624
4830
|
if (!parsedDate.isValid())
|
|
4625
4831
|
return;
|
|
@@ -4637,7 +4843,7 @@ const DatePicker = ({ column, schema, prefix }) => {
|
|
|
4637
4843
|
catch (e) {
|
|
4638
4844
|
console.error(e);
|
|
4639
4845
|
}
|
|
4640
|
-
}, [selectedDate, dateFormat, colLabel, setValue]);
|
|
4846
|
+
}, [selectedDate, dateFormat, colLabel, setValue, timezone]);
|
|
4641
4847
|
const datePickerLabels = {
|
|
4642
4848
|
monthNamesShort: dateTimePickerLabels?.monthNamesShort ?? [
|
|
4643
4849
|
'January',
|
|
@@ -4665,14 +4871,92 @@ const DatePicker = ({ column, schema, prefix }) => {
|
|
|
4665
4871
|
backButtonLabel: dateTimePickerLabels?.backButtonLabel ?? 'Back',
|
|
4666
4872
|
forwardButtonLabel: dateTimePickerLabels?.forwardButtonLabel ?? 'Forward',
|
|
4667
4873
|
};
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4874
|
+
// Convert value to Date object for DatePicker
|
|
4875
|
+
const selectedDateObj = selectedDate
|
|
4876
|
+
? dayjs(selectedDate).tz(timezone).isValid()
|
|
4877
|
+
? dayjs(selectedDate).tz(timezone).toDate()
|
|
4878
|
+
: new Date()
|
|
4879
|
+
: new Date();
|
|
4880
|
+
// Shared function to parse and validate input value
|
|
4881
|
+
const parseAndValidateInput = (inputVal) => {
|
|
4882
|
+
// If empty, clear the value
|
|
4883
|
+
if (!inputVal.trim()) {
|
|
4884
|
+
setValue(colLabel, undefined, {
|
|
4885
|
+
shouldValidate: true,
|
|
4886
|
+
shouldDirty: true,
|
|
4887
|
+
});
|
|
4888
|
+
setInputValue('');
|
|
4889
|
+
return;
|
|
4890
|
+
}
|
|
4891
|
+
// Try parsing with displayDateFormat first
|
|
4892
|
+
let parsedDate = dayjs(inputVal, displayDateFormat, true);
|
|
4893
|
+
// If that fails, try common date formats
|
|
4894
|
+
if (!parsedDate.isValid()) {
|
|
4895
|
+
parsedDate = dayjs(inputVal);
|
|
4896
|
+
}
|
|
4897
|
+
// If still invalid, try parsing with dateFormat
|
|
4898
|
+
if (!parsedDate.isValid()) {
|
|
4899
|
+
parsedDate = dayjs(inputVal, dateFormat, true);
|
|
4900
|
+
}
|
|
4901
|
+
// If valid, format and update
|
|
4902
|
+
if (parsedDate.isValid()) {
|
|
4903
|
+
const formattedDate = parsedDate.tz(timezone).format(dateFormat);
|
|
4904
|
+
const formattedDisplay = parsedDate
|
|
4905
|
+
.tz(timezone)
|
|
4906
|
+
.format(displayDateFormat);
|
|
4907
|
+
setValue(colLabel, formattedDate, {
|
|
4908
|
+
shouldValidate: true,
|
|
4909
|
+
shouldDirty: true,
|
|
4910
|
+
});
|
|
4911
|
+
setInputValue(formattedDisplay);
|
|
4912
|
+
}
|
|
4913
|
+
else {
|
|
4914
|
+
// Invalid date - reset to prop value
|
|
4915
|
+
resetToPropValue();
|
|
4916
|
+
}
|
|
4917
|
+
};
|
|
4918
|
+
// Helper function to reset input to prop value
|
|
4919
|
+
const resetToPropValue = () => {
|
|
4920
|
+
if (selectedDate) {
|
|
4921
|
+
const parsedDate = dayjs(selectedDate).tz(timezone);
|
|
4922
|
+
if (parsedDate.isValid()) {
|
|
4923
|
+
const formatted = parsedDate.format(displayDateFormat);
|
|
4924
|
+
setInputValue(formatted);
|
|
4925
|
+
}
|
|
4926
|
+
else {
|
|
4927
|
+
setInputValue('');
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
else {
|
|
4931
|
+
setInputValue('');
|
|
4932
|
+
}
|
|
4933
|
+
};
|
|
4934
|
+
const handleInputChange = (e) => {
|
|
4935
|
+
// Only update the input value, don't parse yet
|
|
4936
|
+
setInputValue(e.target.value);
|
|
4937
|
+
};
|
|
4938
|
+
const handleInputBlur = () => {
|
|
4939
|
+
// Parse and validate when input loses focus
|
|
4940
|
+
parseAndValidateInput(inputValue);
|
|
4941
|
+
};
|
|
4942
|
+
const handleKeyDown = (e) => {
|
|
4943
|
+
// Parse and validate when Enter is pressed
|
|
4944
|
+
if (e.key === 'Enter') {
|
|
4945
|
+
e.preventDefault();
|
|
4946
|
+
parseAndValidateInput(inputValue);
|
|
4947
|
+
}
|
|
4948
|
+
};
|
|
4949
|
+
const handleDateSelected = ({ date }) => {
|
|
4950
|
+
const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
|
|
4951
|
+
setValue(colLabel, formattedDate, {
|
|
4952
|
+
shouldValidate: true,
|
|
4953
|
+
shouldDirty: true,
|
|
4954
|
+
});
|
|
4955
|
+
setOpen(false);
|
|
4956
|
+
};
|
|
4957
|
+
const datePickerContent = (jsx(DatePicker$1, { selected: selectedDateObj, onDateSelected: handleDateSelected, labels: datePickerLabels }));
|
|
4672
4958
|
return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
4673
|
-
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsx(Popover.Trigger, { asChild: true, children:
|
|
4674
|
-
setOpen(true);
|
|
4675
|
-
}, justifyContent: 'start', children: [jsx(MdDateRange, {}), selectedDate !== undefined ? `${displayDate}` : ''] }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) }) }))] }) }));
|
|
4959
|
+
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(InputGroup, { endElement: jsx(Popover.Trigger, { asChild: true, children: jsx(IconButton, { variant: "ghost", size: "2xs", "aria-label": "Open calendar", onClick: () => setOpen(true), children: jsx(Icon, { children: jsx(MdDateRange, {}) }) }) }), children: jsx(Input, { value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onKeyDown: handleKeyDown, placeholder: formI18n.label(), size: "sm" }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) }) }))] }) }));
|
|
4676
4960
|
};
|
|
4677
4961
|
|
|
4678
4962
|
dayjs.extend(utc);
|
|
@@ -4680,7 +4964,7 @@ dayjs.extend(timezone);
|
|
|
4680
4964
|
const DateRangePicker = ({ column, schema, prefix, }) => {
|
|
4681
4965
|
const { watch, formState: { errors }, setValue, } = useFormContext();
|
|
4682
4966
|
const { timezone, insideDialog } = useSchemaContext();
|
|
4683
|
-
const formI18n = useFormI18n(column, prefix);
|
|
4967
|
+
const formI18n = useFormI18n(column, prefix, schema);
|
|
4684
4968
|
const { required, gridColumn = 'span 12', gridRow = 'span 1', displayDateFormat = 'YYYY-MM-DD', dateFormat = 'YYYY-MM-DD', } = schema;
|
|
4685
4969
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
4686
4970
|
const colLabel = formI18n.colLabel;
|
|
@@ -5325,7 +5609,7 @@ const MediaLibraryBrowser = ({ onFetchFiles, filterImageOnly = false, labels, en
|
|
|
5325
5609
|
}) })) }))] }));
|
|
5326
5610
|
};
|
|
5327
5611
|
|
|
5328
|
-
function MediaBrowserDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, onUploadFile, enableUpload = false, labels,
|
|
5612
|
+
function MediaBrowserDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, onUploadFile, enableUpload = false, labels, }) {
|
|
5329
5613
|
const [selectedFile, setSelectedFile] = useState(undefined);
|
|
5330
5614
|
const [activeTab, setActiveTab] = useState('browse');
|
|
5331
5615
|
const [uploadingFiles, setUploadingFiles] = useState(new Set());
|
|
@@ -5409,7 +5693,7 @@ function MediaBrowserDialog({ open, onClose, onSelect, title, filterImageOnly =
|
|
|
5409
5693
|
const FilePicker = ({ column, schema, prefix }) => {
|
|
5410
5694
|
const { setValue, formState: { errors }, watch, } = useFormContext();
|
|
5411
5695
|
const { filePickerLabels } = useSchemaContext();
|
|
5412
|
-
const formI18n = useFormI18n(column, prefix);
|
|
5696
|
+
const formI18n = useFormI18n(column, prefix, schema);
|
|
5413
5697
|
const { required, gridColumn = 'span 12', gridRow = 'span 1', type, } = schema;
|
|
5414
5698
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
5415
5699
|
const isSingleSelect = type === 'string';
|
|
@@ -5485,7 +5769,7 @@ const FilePicker = ({ column, schema, prefix }) => {
|
|
|
5485
5769
|
const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
|
|
5486
5770
|
const { setValue, formState: { errors }, watch, } = useFormContext();
|
|
5487
5771
|
const { filePickerLabels } = useSchemaContext();
|
|
5488
|
-
const formI18n = useFormI18n(column, prefix);
|
|
5772
|
+
const formI18n = useFormI18n(column, prefix, schema);
|
|
5489
5773
|
const { required, gridColumn = 'span 12', gridRow = 'span 1', filePicker, type, } = schema;
|
|
5490
5774
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
5491
5775
|
const isSingleSelect = type === 'string';
|
|
@@ -5580,9 +5864,7 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
|
|
|
5580
5864
|
return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
5581
5865
|
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsx(VStack, { align: "stretch", gap: 2, children: jsx(Button$1, { variant: "outline", onClick: () => setDialogOpen(true), borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: filePickerLabels?.browseLibrary ??
|
|
5582
5866
|
formI18n.t('browse_library') ??
|
|
5583
|
-
'Browse from Library' }) }), jsx(MediaBrowserDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSelect: handleMediaLibrarySelect, title: filePickerLabels?.dialogTitle ??
|
|
5584
|
-
filePickerLabels?.dialogTitle ??
|
|
5585
|
-
'Select File', filterImageOnly: filterImageOnly, onFetchFiles: onFetchFiles, onUploadFile: onUploadFile, enableUpload: enableUpload, labels: filePickerLabels, colLabel: colLabel }), jsx(Flex, { flexFlow: 'column', gap: 1, children: currentFileIds.map((fileId, index) => {
|
|
5867
|
+
'Browse from Library' }) }), jsx(MediaBrowserDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSelect: handleMediaLibrarySelect, title: filePickerLabels?.dialogTitle ?? formI18n.label() ?? 'Select File', filterImageOnly: filterImageOnly, onFetchFiles: onFetchFiles, onUploadFile: onUploadFile, enableUpload: enableUpload, labels: filePickerLabels, colLabel: colLabel }), jsx(Flex, { flexFlow: 'column', gap: 1, children: currentFileIds.map((fileId, index) => {
|
|
5586
5868
|
const file = fileMap.get(fileId);
|
|
5587
5869
|
const isImage = file
|
|
5588
5870
|
? /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name)
|
|
@@ -5673,7 +5955,8 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
|
|
|
5673
5955
|
}
|
|
5674
5956
|
// Use schema's loadInitialValues (required for id-picker)
|
|
5675
5957
|
if (!loadInitialValues) {
|
|
5676
|
-
|
|
5958
|
+
console.warn(`loadInitialValues is required in schema for IdPicker field '${column}'. Returning empty idMap.`);
|
|
5959
|
+
return { data: [], count: 0 };
|
|
5677
5960
|
}
|
|
5678
5961
|
const result = await loadInitialValues({
|
|
5679
5962
|
ids: missingIds,
|
|
@@ -6405,14 +6688,74 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6405
6688
|
}
|
|
6406
6689
|
}
|
|
6407
6690
|
}
|
|
6408
|
-
|
|
6691
|
+
// Sort options by time (convert to 24-hour for proper chronological sorting)
|
|
6692
|
+
return options.sort((a, b) => {
|
|
6693
|
+
// Convert 12-hour to 24-hour for comparison
|
|
6694
|
+
let hour24A = a.hour;
|
|
6695
|
+
if (a.meridiem === 'am' && a.hour === 12)
|
|
6696
|
+
hour24A = 0;
|
|
6697
|
+
else if (a.meridiem === 'pm' && a.hour < 12)
|
|
6698
|
+
hour24A = a.hour + 12;
|
|
6699
|
+
let hour24B = b.hour;
|
|
6700
|
+
if (b.meridiem === 'am' && b.hour === 12)
|
|
6701
|
+
hour24B = 0;
|
|
6702
|
+
else if (b.meridiem === 'pm' && b.hour < 12)
|
|
6703
|
+
hour24B = b.hour + 12;
|
|
6704
|
+
// Compare by hour first, then minute
|
|
6705
|
+
if (hour24A !== hour24B) {
|
|
6706
|
+
return hour24A - hour24B;
|
|
6707
|
+
}
|
|
6708
|
+
return a.minute - b.minute;
|
|
6709
|
+
});
|
|
6409
6710
|
}, [startTime, selectedDate, timezone]);
|
|
6410
|
-
|
|
6711
|
+
// itemToString returns only the clean display text (no metadata)
|
|
6712
|
+
const itemToString = useMemo(() => {
|
|
6713
|
+
return (item) => {
|
|
6714
|
+
return item.searchText; // Clean display text only
|
|
6715
|
+
};
|
|
6716
|
+
}, []);
|
|
6717
|
+
// Custom filter function that filters by time and supports 24-hour format input
|
|
6718
|
+
const customTimeFilter = useMemo(() => {
|
|
6719
|
+
return (itemText, filterText) => {
|
|
6720
|
+
if (!filterText) {
|
|
6721
|
+
return true; // Show all items when no filter
|
|
6722
|
+
}
|
|
6723
|
+
const lowerItemText = itemText.toLowerCase();
|
|
6724
|
+
const lowerFilterText = filterText.toLowerCase();
|
|
6725
|
+
// First, try matching against the display text (12-hour format)
|
|
6726
|
+
if (lowerItemText.includes(lowerFilterText)) {
|
|
6727
|
+
return true;
|
|
6728
|
+
}
|
|
6729
|
+
// Find the corresponding item to check 24-hour format matches
|
|
6730
|
+
const item = timeOptions.find((opt) => opt.searchText.toLowerCase() === lowerItemText);
|
|
6731
|
+
if (!item) {
|
|
6732
|
+
return false;
|
|
6733
|
+
}
|
|
6734
|
+
// Convert item to 24-hour format for matching
|
|
6735
|
+
let hour24 = item.hour;
|
|
6736
|
+
if (item.meridiem === 'am' && item.hour === 12)
|
|
6737
|
+
hour24 = 0;
|
|
6738
|
+
else if (item.meridiem === 'pm' && item.hour < 12)
|
|
6739
|
+
hour24 = item.hour + 12;
|
|
6740
|
+
const hour24Str = hour24.toString().padStart(2, '0');
|
|
6741
|
+
const minuteStr = item.minute.toString().padStart(2, '0');
|
|
6742
|
+
// Check if filterText matches 24-hour format variations
|
|
6743
|
+
const formats = [
|
|
6744
|
+
`${hour24Str}:${minuteStr}`, // "13:30"
|
|
6745
|
+
`${hour24Str}${minuteStr}`, // "1330"
|
|
6746
|
+
hour24Str, // "13"
|
|
6747
|
+
`${hour24}:${minuteStr}`, // "13:30" (without padding)
|
|
6748
|
+
hour24.toString(), // "13" (without padding)
|
|
6749
|
+
];
|
|
6750
|
+
return formats.some((format) => format.toLowerCase().includes(lowerFilterText) ||
|
|
6751
|
+
lowerFilterText.includes(format.toLowerCase()));
|
|
6752
|
+
};
|
|
6753
|
+
}, [timeOptions]);
|
|
6411
6754
|
const { collection, filter } = useListCollection({
|
|
6412
6755
|
initialItems: timeOptions,
|
|
6413
|
-
itemToString:
|
|
6756
|
+
itemToString: itemToString,
|
|
6414
6757
|
itemToValue: (item) => item.value,
|
|
6415
|
-
filter:
|
|
6758
|
+
filter: customTimeFilter,
|
|
6416
6759
|
});
|
|
6417
6760
|
// Get current value string for combobox
|
|
6418
6761
|
const currentValue = useMemo(() => {
|
|
@@ -6502,6 +6845,47 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6502
6845
|
if (!trimmedValue) {
|
|
6503
6846
|
return;
|
|
6504
6847
|
}
|
|
6848
|
+
// Parse 24-hour format first (e.g., "13:30", "14:00", "1330", "1400", "9:05", "905")
|
|
6849
|
+
const timePattern24Hour = /^(\d{1,2}):?(\d{2})$/;
|
|
6850
|
+
const match24Hour = trimmedValue.match(timePattern24Hour);
|
|
6851
|
+
if (match24Hour) {
|
|
6852
|
+
const parsedHour24 = parseInt(match24Hour[1], 10);
|
|
6853
|
+
const parsedMinute = parseInt(match24Hour[2], 10);
|
|
6854
|
+
// Validate 24-hour format ranges
|
|
6855
|
+
if (parsedHour24 >= 0 &&
|
|
6856
|
+
parsedHour24 <= 23 &&
|
|
6857
|
+
parsedMinute >= 0 &&
|
|
6858
|
+
parsedMinute <= 59) {
|
|
6859
|
+
// Convert 24-hour to 12-hour format
|
|
6860
|
+
let hour12;
|
|
6861
|
+
let meridiem;
|
|
6862
|
+
if (parsedHour24 === 0) {
|
|
6863
|
+
hour12 = 12;
|
|
6864
|
+
meridiem = 'am';
|
|
6865
|
+
}
|
|
6866
|
+
else if (parsedHour24 === 12) {
|
|
6867
|
+
hour12 = 12;
|
|
6868
|
+
meridiem = 'pm';
|
|
6869
|
+
}
|
|
6870
|
+
else if (parsedHour24 > 12) {
|
|
6871
|
+
hour12 = parsedHour24 - 12;
|
|
6872
|
+
meridiem = 'pm';
|
|
6873
|
+
}
|
|
6874
|
+
else {
|
|
6875
|
+
hour12 = parsedHour24;
|
|
6876
|
+
meridiem = 'am';
|
|
6877
|
+
}
|
|
6878
|
+
setHour(hour12);
|
|
6879
|
+
setMinute(parsedMinute);
|
|
6880
|
+
setMeridiem(meridiem);
|
|
6881
|
+
onChange({
|
|
6882
|
+
hour: hour12,
|
|
6883
|
+
minute: parsedMinute,
|
|
6884
|
+
meridiem: meridiem,
|
|
6885
|
+
});
|
|
6886
|
+
return;
|
|
6887
|
+
}
|
|
6888
|
+
}
|
|
6505
6889
|
// Parse formats like "1:30 PM", "1:30PM", "1:30 pm", "1:30pm"
|
|
6506
6890
|
const timePattern12Hour = /^(\d{1,2}):(\d{1,2})\s*(am|pm|AM|PM)$/i;
|
|
6507
6891
|
const match12Hour = trimmedValue.match(timePattern12Hour);
|
|
@@ -6667,133 +7051,6 @@ const TimePicker = ({ column, schema, prefix }) => {
|
|
|
6667
7051
|
}, justifyContent: 'start', children: [jsx(IoMdClock, {}), !!value ? `${displayedTime}` : ''] }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { maxH: "70vh", overflowY: "auto", children: jsx(Popover.Body, { overflow: "visible", children: jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, labels: timePickerLabels }) }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { children: jsx(Popover.Body, { children: jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, labels: timePickerLabels }) }) }) }) }))] }) }));
|
|
6668
7052
|
};
|
|
6669
7053
|
|
|
6670
|
-
dayjs.extend(utc);
|
|
6671
|
-
dayjs.extend(timezone);
|
|
6672
|
-
dayjs.extend(customParseFormat);
|
|
6673
|
-
function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateFormat = 'YYYY-MM-DD', displayFormat = 'YYYY-MM-DD', labels = {
|
|
6674
|
-
monthNamesShort: [
|
|
6675
|
-
'Jan',
|
|
6676
|
-
'Feb',
|
|
6677
|
-
'Mar',
|
|
6678
|
-
'Apr',
|
|
6679
|
-
'May',
|
|
6680
|
-
'Jun',
|
|
6681
|
-
'Jul',
|
|
6682
|
-
'Aug',
|
|
6683
|
-
'Sep',
|
|
6684
|
-
'Oct',
|
|
6685
|
-
'Nov',
|
|
6686
|
-
'Dec',
|
|
6687
|
-
],
|
|
6688
|
-
weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
|
6689
|
-
backButtonLabel: 'Back',
|
|
6690
|
-
forwardButtonLabel: 'Next',
|
|
6691
|
-
}, timezone = 'Asia/Hong_Kong', minDate, maxDate, firstDayOfWeek, showOutsideDays, monthsToDisplay = 1, insideDialog = false, readOnly = false, }) {
|
|
6692
|
-
const [open, setOpen] = useState(false);
|
|
6693
|
-
const [inputValue, setInputValue] = useState('');
|
|
6694
|
-
// Update input value when prop value changes
|
|
6695
|
-
useEffect(() => {
|
|
6696
|
-
if (value) {
|
|
6697
|
-
const formatted = typeof value === 'string'
|
|
6698
|
-
? dayjs(value).tz(timezone).isValid()
|
|
6699
|
-
? dayjs(value).tz(timezone).format(displayFormat)
|
|
6700
|
-
: ''
|
|
6701
|
-
: dayjs(value).tz(timezone).format(displayFormat);
|
|
6702
|
-
setInputValue(formatted);
|
|
6703
|
-
}
|
|
6704
|
-
else {
|
|
6705
|
-
setInputValue('');
|
|
6706
|
-
}
|
|
6707
|
-
}, [value, displayFormat, timezone]);
|
|
6708
|
-
// Convert value to Date object for DatePicker
|
|
6709
|
-
const selectedDate = value
|
|
6710
|
-
? typeof value === 'string'
|
|
6711
|
-
? dayjs(value).tz(timezone).isValid()
|
|
6712
|
-
? dayjs(value).tz(timezone).toDate()
|
|
6713
|
-
: new Date()
|
|
6714
|
-
: value
|
|
6715
|
-
: new Date();
|
|
6716
|
-
// Shared function to parse and validate input value
|
|
6717
|
-
const parseAndValidateInput = (inputVal) => {
|
|
6718
|
-
// If empty, clear the value
|
|
6719
|
-
if (!inputVal.trim()) {
|
|
6720
|
-
onChange?.(undefined);
|
|
6721
|
-
setInputValue('');
|
|
6722
|
-
return;
|
|
6723
|
-
}
|
|
6724
|
-
// Try parsing with displayFormat first
|
|
6725
|
-
let parsedDate = dayjs(inputVal, displayFormat, true);
|
|
6726
|
-
// If that fails, try common date formats
|
|
6727
|
-
if (!parsedDate.isValid()) {
|
|
6728
|
-
parsedDate = dayjs(inputVal);
|
|
6729
|
-
}
|
|
6730
|
-
// If still invalid, try parsing with dateFormat
|
|
6731
|
-
if (!parsedDate.isValid()) {
|
|
6732
|
-
parsedDate = dayjs(inputVal, dateFormat, true);
|
|
6733
|
-
}
|
|
6734
|
-
// If valid, check constraints and update
|
|
6735
|
-
if (parsedDate.isValid()) {
|
|
6736
|
-
const dateObj = parsedDate.tz(timezone).toDate();
|
|
6737
|
-
// Check min/max constraints
|
|
6738
|
-
if (minDate && dateObj < minDate) {
|
|
6739
|
-
// Invalid: before minDate, reset to prop value
|
|
6740
|
-
resetToPropValue();
|
|
6741
|
-
return;
|
|
6742
|
-
}
|
|
6743
|
-
if (maxDate && dateObj > maxDate) {
|
|
6744
|
-
// Invalid: after maxDate, reset to prop value
|
|
6745
|
-
resetToPropValue();
|
|
6746
|
-
return;
|
|
6747
|
-
}
|
|
6748
|
-
// Valid date - format and update
|
|
6749
|
-
const formattedDate = parsedDate.tz(timezone).format(dateFormat);
|
|
6750
|
-
const formattedDisplay = parsedDate.tz(timezone).format(displayFormat);
|
|
6751
|
-
onChange?.(formattedDate);
|
|
6752
|
-
setInputValue(formattedDisplay);
|
|
6753
|
-
}
|
|
6754
|
-
else {
|
|
6755
|
-
// Invalid date - reset to prop value
|
|
6756
|
-
resetToPropValue();
|
|
6757
|
-
}
|
|
6758
|
-
};
|
|
6759
|
-
// Helper function to reset input to prop value
|
|
6760
|
-
const resetToPropValue = () => {
|
|
6761
|
-
if (value) {
|
|
6762
|
-
const formatted = typeof value === 'string'
|
|
6763
|
-
? dayjs(value).tz(timezone).isValid()
|
|
6764
|
-
? dayjs(value).tz(timezone).format(displayFormat)
|
|
6765
|
-
: ''
|
|
6766
|
-
: dayjs(value).tz(timezone).format(displayFormat);
|
|
6767
|
-
setInputValue(formatted);
|
|
6768
|
-
}
|
|
6769
|
-
else {
|
|
6770
|
-
setInputValue('');
|
|
6771
|
-
}
|
|
6772
|
-
};
|
|
6773
|
-
const handleInputChange = (e) => {
|
|
6774
|
-
// Only update the input value, don't parse yet
|
|
6775
|
-
setInputValue(e.target.value);
|
|
6776
|
-
};
|
|
6777
|
-
const handleInputBlur = () => {
|
|
6778
|
-
// Parse and validate when input loses focus
|
|
6779
|
-
parseAndValidateInput(inputValue);
|
|
6780
|
-
};
|
|
6781
|
-
const handleKeyDown = (e) => {
|
|
6782
|
-
// Parse and validate when Enter is pressed
|
|
6783
|
-
if (e.key === 'Enter') {
|
|
6784
|
-
e.preventDefault();
|
|
6785
|
-
parseAndValidateInput(inputValue);
|
|
6786
|
-
}
|
|
6787
|
-
};
|
|
6788
|
-
const handleDateSelected = ({ date }) => {
|
|
6789
|
-
const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
|
|
6790
|
-
onChange?.(formattedDate);
|
|
6791
|
-
setOpen(false);
|
|
6792
|
-
};
|
|
6793
|
-
const datePickerContent = (jsx(DatePicker$1, { selected: selectedDate, onDateSelected: handleDateSelected, labels: labels, minDate: minDate, maxDate: maxDate, firstDayOfWeek: firstDayOfWeek, showOutsideDays: showOutsideDays, monthsToDisplay: monthsToDisplay }));
|
|
6794
|
-
return (jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(InputGroup, { endElement: jsx(Popover.Trigger, { asChild: true, children: jsx(IconButton, { variant: "ghost", size: "2xs", "aria-label": "Open calendar", onClick: () => setOpen(true), children: jsx(Icon, { children: jsx(MdDateRange, {}) }) }) }), children: jsx(Input, { value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onKeyDown: handleKeyDown, placeholder: placeholder, readOnly: readOnly }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) }) }))] }));
|
|
6795
|
-
}
|
|
6796
|
-
|
|
6797
7054
|
dayjs.extend(utc);
|
|
6798
7055
|
dayjs.extend(timezone);
|
|
6799
7056
|
function IsoTimePicker({ hour, setHour, minute, setMinute, second, setSecond,
|
|
@@ -7037,7 +7294,20 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
|
|
|
7037
7294
|
});
|
|
7038
7295
|
}
|
|
7039
7296
|
};
|
|
7297
|
+
const [inputValue, setInputValue] = useState('');
|
|
7298
|
+
// Sync inputValue with currentValue when time changes externally
|
|
7299
|
+
useEffect(() => {
|
|
7300
|
+
if (hour !== null && minute !== null && second !== null) {
|
|
7301
|
+
const formattedValue = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${second.toString().padStart(2, '0')}`;
|
|
7302
|
+
setInputValue(formattedValue);
|
|
7303
|
+
}
|
|
7304
|
+
else {
|
|
7305
|
+
setInputValue('');
|
|
7306
|
+
}
|
|
7307
|
+
}, [hour, minute, second]);
|
|
7040
7308
|
const handleInputValueChange = (details) => {
|
|
7309
|
+
// Update local input value state
|
|
7310
|
+
setInputValue(details.inputValue);
|
|
7041
7311
|
// Filter the collection based on input, but don't parse yet
|
|
7042
7312
|
filter(details.inputValue);
|
|
7043
7313
|
};
|
|
@@ -7047,24 +7317,26 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
|
|
|
7047
7317
|
};
|
|
7048
7318
|
const handleBlur = (e) => {
|
|
7049
7319
|
// Parse and commit the input value when losing focus
|
|
7050
|
-
const
|
|
7051
|
-
|
|
7052
|
-
|
|
7320
|
+
const inputVal = e.target.value;
|
|
7321
|
+
setInputValue(inputVal);
|
|
7322
|
+
if (inputVal) {
|
|
7323
|
+
parseAndCommitInput(inputVal);
|
|
7053
7324
|
}
|
|
7054
7325
|
};
|
|
7055
7326
|
const handleKeyDown = (e) => {
|
|
7056
7327
|
// Commit input on Enter key
|
|
7057
7328
|
if (e.key === 'Enter') {
|
|
7058
7329
|
e.preventDefault();
|
|
7059
|
-
const
|
|
7060
|
-
|
|
7061
|
-
|
|
7330
|
+
const inputVal = e.currentTarget.value;
|
|
7331
|
+
setInputValue(inputVal);
|
|
7332
|
+
if (inputVal) {
|
|
7333
|
+
parseAndCommitInput(inputVal);
|
|
7062
7334
|
}
|
|
7063
7335
|
// Blur the input
|
|
7064
7336
|
e.currentTarget?.blur();
|
|
7065
7337
|
}
|
|
7066
7338
|
};
|
|
7067
|
-
return (jsx(Flex, { direction: "column", gap: 3, children: jsxs(Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxs(Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", openOnClick: true, flex: 1, children: [jsxs(Combobox.Control, { children: [jsx(InputGroup$1, { startElement: jsx(BsClock, {}), children: jsx(Combobox.Input, { placeholder: labels.placeholder, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsx(Combobox.IndicatorGroup, { children: jsx(Combobox.Trigger, {}) })] }), jsx(Portal, { disabled: !portalled, children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [jsx(Combobox.Empty, { children: labels.emptyMessage }), collection.items.map((item) => (jsxs(Combobox.Item, { item: item, children: [jsxs(Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsx(Text, { flex: 1, children: item.label }), item.durationText && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: item.durationText }) }))] }), jsx(Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: durationDiff }) })), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "ghost", children: jsx(Icon, { children: jsx(MdCancel, {}) }) })] }) }));
|
|
7339
|
+
return (jsx(Flex, { direction: "column", gap: 3, children: jsxs(Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxs(Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", openOnClick: true, flex: 1, children: [jsxs(Combobox.Control, { children: [jsx(InputGroup$1, { startElement: jsx(BsClock, {}), children: jsx(Combobox.Input, { value: inputValue, placeholder: labels.placeholder, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsx(Combobox.IndicatorGroup, { children: jsx(Combobox.Trigger, {}) })] }), jsx(Portal, { disabled: !portalled, children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [jsx(Combobox.Empty, { children: labels.emptyMessage }), collection.items.map((item) => (jsxs(Combobox.Item, { item: item, children: [jsxs(Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsx(Text, { flex: 1, children: item.label }), item.durationText && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: item.durationText }) }))] }), jsx(Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: durationDiff }) })), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "ghost", children: jsx(Icon, { children: jsx(MdCancel, {}) }) })] }) }));
|
|
7068
7340
|
}
|
|
7069
7341
|
|
|
7070
7342
|
dayjs.extend(utc);
|
|
@@ -7087,7 +7359,7 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
|
|
|
7087
7359
|
weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
|
7088
7360
|
backButtonLabel: 'Back',
|
|
7089
7361
|
forwardButtonLabel: 'Next',
|
|
7090
|
-
}, timePickerLabels, timezone = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, }) {
|
|
7362
|
+
}, timePickerLabels, timezone = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, defaultDate, defaultTime, }) {
|
|
7091
7363
|
console.log('[DateTimePicker] Component initialized with props:', {
|
|
7092
7364
|
value,
|
|
7093
7365
|
format,
|
|
@@ -7162,13 +7434,77 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
|
|
|
7162
7434
|
value,
|
|
7163
7435
|
initialTime,
|
|
7164
7436
|
});
|
|
7437
|
+
// Normalize startTime to ignore milliseconds (needed for effectiveDefaultDate calculation)
|
|
7438
|
+
const normalizedStartTime = startTime
|
|
7439
|
+
? dayjs(startTime).tz(timezone).millisecond(0).toISOString()
|
|
7440
|
+
: undefined;
|
|
7441
|
+
// Calculate effective defaultDate: use prop if provided, otherwise use startTime date, otherwise use today
|
|
7442
|
+
const effectiveDefaultDate = useMemo(() => {
|
|
7443
|
+
if (defaultDate) {
|
|
7444
|
+
return defaultDate;
|
|
7445
|
+
}
|
|
7446
|
+
if (normalizedStartTime &&
|
|
7447
|
+
dayjs(normalizedStartTime).tz(timezone).isValid()) {
|
|
7448
|
+
return dayjs(normalizedStartTime).tz(timezone).format('YYYY-MM-DD');
|
|
7449
|
+
}
|
|
7450
|
+
return dayjs().tz(timezone).format('YYYY-MM-DD');
|
|
7451
|
+
}, [defaultDate, normalizedStartTime, timezone]);
|
|
7452
|
+
// Initialize time with default values if no value is provided
|
|
7453
|
+
const getInitialTimeValues = () => {
|
|
7454
|
+
if (value && initialTime.hour12 !== null) {
|
|
7455
|
+
return initialTime;
|
|
7456
|
+
}
|
|
7457
|
+
// If no value or no time in value, use defaultTime or 00:00
|
|
7458
|
+
if (defaultTime) {
|
|
7459
|
+
if (format === 'iso-date-time') {
|
|
7460
|
+
const defaultTime24 = defaultTime;
|
|
7461
|
+
return {
|
|
7462
|
+
hour12: null,
|
|
7463
|
+
minute: defaultTime24.minute ?? 0,
|
|
7464
|
+
meridiem: null,
|
|
7465
|
+
hour24: defaultTime24.hour ?? 0,
|
|
7466
|
+
second: showSeconds ? defaultTime24.second ?? 0 : null,
|
|
7467
|
+
};
|
|
7468
|
+
}
|
|
7469
|
+
else {
|
|
7470
|
+
const defaultTime12 = defaultTime;
|
|
7471
|
+
return {
|
|
7472
|
+
hour12: defaultTime12.hour ?? 12,
|
|
7473
|
+
minute: defaultTime12.minute ?? 0,
|
|
7474
|
+
meridiem: defaultTime12.meridiem ?? 'am',
|
|
7475
|
+
hour24: null,
|
|
7476
|
+
second: null,
|
|
7477
|
+
};
|
|
7478
|
+
}
|
|
7479
|
+
}
|
|
7480
|
+
// Default to 00:00
|
|
7481
|
+
if (format === 'iso-date-time') {
|
|
7482
|
+
return {
|
|
7483
|
+
hour12: null,
|
|
7484
|
+
minute: 0,
|
|
7485
|
+
meridiem: null,
|
|
7486
|
+
hour24: 0,
|
|
7487
|
+
second: showSeconds ? 0 : null,
|
|
7488
|
+
};
|
|
7489
|
+
}
|
|
7490
|
+
else {
|
|
7491
|
+
return {
|
|
7492
|
+
hour12: 12,
|
|
7493
|
+
minute: 0,
|
|
7494
|
+
meridiem: 'am',
|
|
7495
|
+
hour24: null,
|
|
7496
|
+
second: null,
|
|
7497
|
+
};
|
|
7498
|
+
}
|
|
7499
|
+
};
|
|
7500
|
+
const initialTimeValues = getInitialTimeValues();
|
|
7165
7501
|
// Time state for 12-hour format
|
|
7166
|
-
const [hour12, setHour12] = useState(
|
|
7167
|
-
const [minute, setMinute] = useState(
|
|
7168
|
-
const [meridiem, setMeridiem] = useState(
|
|
7502
|
+
const [hour12, setHour12] = useState(initialTimeValues.hour12);
|
|
7503
|
+
const [minute, setMinute] = useState(initialTimeValues.minute);
|
|
7504
|
+
const [meridiem, setMeridiem] = useState(initialTimeValues.meridiem);
|
|
7169
7505
|
// Time state for 24-hour format
|
|
7170
|
-
const [hour24, setHour24] = useState(
|
|
7171
|
-
const [second, setSecond] = useState(
|
|
7506
|
+
const [hour24, setHour24] = useState(initialTimeValues.hour24);
|
|
7507
|
+
const [second, setSecond] = useState(initialTimeValues.second);
|
|
7172
7508
|
// Sync selectedDate and time states when value prop changes
|
|
7173
7509
|
useEffect(() => {
|
|
7174
7510
|
console.log('[DateTimePicker] useEffect triggered - value changed:', {
|
|
@@ -7176,27 +7512,47 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
|
|
|
7176
7512
|
timezone,
|
|
7177
7513
|
format,
|
|
7178
7514
|
});
|
|
7179
|
-
// If value is null, undefined, or invalid, clear
|
|
7515
|
+
// If value is null, undefined, or invalid, clear date but keep default time values
|
|
7180
7516
|
if (!value || value === null || value === undefined) {
|
|
7181
|
-
console.log('[DateTimePicker] Value is null/undefined, clearing
|
|
7517
|
+
console.log('[DateTimePicker] Value is null/undefined, clearing date but keeping default time');
|
|
7182
7518
|
setSelectedDate('');
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7519
|
+
// Keep default time values instead of clearing them
|
|
7520
|
+
if (format === 'iso-date-time') {
|
|
7521
|
+
setHour24(defaultTime ? defaultTime.hour ?? 0 : 0);
|
|
7522
|
+
setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
|
|
7523
|
+
setSecond(showSeconds
|
|
7524
|
+
? defaultTime
|
|
7525
|
+
? defaultTime.second ?? 0
|
|
7526
|
+
: 0
|
|
7527
|
+
: null);
|
|
7528
|
+
}
|
|
7529
|
+
else {
|
|
7530
|
+
setHour12(defaultTime ? defaultTime.hour ?? 12 : 12);
|
|
7531
|
+
setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
|
|
7532
|
+
setMeridiem(defaultTime ? defaultTime.meridiem ?? 'am' : 'am');
|
|
7533
|
+
}
|
|
7188
7534
|
return;
|
|
7189
7535
|
}
|
|
7190
7536
|
// Check if value is valid
|
|
7191
7537
|
const dateObj = dayjs(value).tz(timezone);
|
|
7192
7538
|
if (!dateObj.isValid()) {
|
|
7193
|
-
console.log('[DateTimePicker] Invalid value, clearing
|
|
7539
|
+
console.log('[DateTimePicker] Invalid value, clearing date but keeping default time');
|
|
7194
7540
|
setSelectedDate('');
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7541
|
+
// Keep default time values instead of clearing them
|
|
7542
|
+
if (format === 'iso-date-time') {
|
|
7543
|
+
setHour24(defaultTime ? defaultTime.hour ?? 0 : 0);
|
|
7544
|
+
setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
|
|
7545
|
+
setSecond(showSeconds
|
|
7546
|
+
? defaultTime
|
|
7547
|
+
? defaultTime.second ?? 0
|
|
7548
|
+
: 0
|
|
7549
|
+
: null);
|
|
7550
|
+
}
|
|
7551
|
+
else {
|
|
7552
|
+
setHour12(defaultTime ? defaultTime.hour ?? 12 : 12);
|
|
7553
|
+
setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
|
|
7554
|
+
setMeridiem(defaultTime ? defaultTime.meridiem ?? 'am' : 'am');
|
|
7555
|
+
}
|
|
7200
7556
|
return;
|
|
7201
7557
|
}
|
|
7202
7558
|
const dateString = getDateString(value);
|
|
@@ -7252,16 +7608,76 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
|
|
|
7252
7608
|
onChange?.(undefined);
|
|
7253
7609
|
return;
|
|
7254
7610
|
}
|
|
7611
|
+
// Check if time values are null - if so, use defaultTime or set to 00:00
|
|
7612
|
+
const hasTimeValues = format === 'iso-date-time'
|
|
7613
|
+
? hour24 !== null || minute !== null
|
|
7614
|
+
: hour12 !== null || minute !== null || meridiem !== null;
|
|
7615
|
+
let timeDataToUse = undefined;
|
|
7616
|
+
if (!hasTimeValues) {
|
|
7617
|
+
// Use defaultTime if provided, otherwise default to 00:00
|
|
7618
|
+
if (defaultTime) {
|
|
7619
|
+
console.log('[DateTimePicker] No time values set, using defaultTime');
|
|
7620
|
+
if (format === 'iso-date-time') {
|
|
7621
|
+
const defaultTime24 = defaultTime;
|
|
7622
|
+
setHour24(defaultTime24.hour ?? 0);
|
|
7623
|
+
setMinute(defaultTime24.minute ?? 0);
|
|
7624
|
+
if (showSeconds) {
|
|
7625
|
+
setSecond(defaultTime24.second ?? 0);
|
|
7626
|
+
}
|
|
7627
|
+
timeDataToUse = {
|
|
7628
|
+
hour: defaultTime24.hour ?? 0,
|
|
7629
|
+
minute: defaultTime24.minute ?? 0,
|
|
7630
|
+
second: showSeconds ? defaultTime24.second ?? 0 : undefined,
|
|
7631
|
+
};
|
|
7632
|
+
}
|
|
7633
|
+
else {
|
|
7634
|
+
const defaultTime12 = defaultTime;
|
|
7635
|
+
setHour12(defaultTime12.hour ?? 12);
|
|
7636
|
+
setMinute(defaultTime12.minute ?? 0);
|
|
7637
|
+
setMeridiem(defaultTime12.meridiem ?? 'am');
|
|
7638
|
+
timeDataToUse = {
|
|
7639
|
+
hour: defaultTime12.hour ?? 12,
|
|
7640
|
+
minute: defaultTime12.minute ?? 0,
|
|
7641
|
+
meridiem: defaultTime12.meridiem ?? 'am',
|
|
7642
|
+
};
|
|
7643
|
+
}
|
|
7644
|
+
}
|
|
7645
|
+
else {
|
|
7646
|
+
console.log('[DateTimePicker] No time values set, defaulting to 00:00');
|
|
7647
|
+
if (format === 'iso-date-time') {
|
|
7648
|
+
setHour24(0);
|
|
7649
|
+
setMinute(0);
|
|
7650
|
+
if (showSeconds) {
|
|
7651
|
+
setSecond(0);
|
|
7652
|
+
}
|
|
7653
|
+
timeDataToUse = {
|
|
7654
|
+
hour: 0,
|
|
7655
|
+
minute: 0,
|
|
7656
|
+
second: showSeconds ? 0 : undefined,
|
|
7657
|
+
};
|
|
7658
|
+
}
|
|
7659
|
+
else {
|
|
7660
|
+
setHour12(12);
|
|
7661
|
+
setMinute(0);
|
|
7662
|
+
setMeridiem('am');
|
|
7663
|
+
timeDataToUse = {
|
|
7664
|
+
hour: 12,
|
|
7665
|
+
minute: 0,
|
|
7666
|
+
meridiem: 'am',
|
|
7667
|
+
};
|
|
7668
|
+
}
|
|
7669
|
+
}
|
|
7670
|
+
}
|
|
7255
7671
|
// When showSeconds is false, ignore seconds from the date
|
|
7256
7672
|
if (!showSeconds) {
|
|
7257
7673
|
const dateWithoutSeconds = dateObj.second(0).millisecond(0).toISOString();
|
|
7258
7674
|
console.log('[DateTimePicker] Updating date without seconds:', dateWithoutSeconds);
|
|
7259
|
-
updateDateTime(dateWithoutSeconds);
|
|
7675
|
+
updateDateTime(dateWithoutSeconds, timeDataToUse);
|
|
7260
7676
|
}
|
|
7261
7677
|
else {
|
|
7262
7678
|
const dateWithSeconds = dateObj.toISOString();
|
|
7263
7679
|
console.log('[DateTimePicker] Updating date with seconds:', dateWithSeconds);
|
|
7264
|
-
updateDateTime(dateWithSeconds);
|
|
7680
|
+
updateDateTime(dateWithSeconds, timeDataToUse);
|
|
7265
7681
|
}
|
|
7266
7682
|
};
|
|
7267
7683
|
const handleTimeChange = (timeData) => {
|
|
@@ -7291,17 +7707,36 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
|
|
|
7291
7707
|
setMinute(data.minute);
|
|
7292
7708
|
setMeridiem(data.meridiem);
|
|
7293
7709
|
}
|
|
7294
|
-
// Use selectedDate if valid, otherwise clear all fields
|
|
7710
|
+
// Use selectedDate if valid, otherwise use effectiveDefaultDate or clear all fields
|
|
7295
7711
|
if (!selectedDate || !dayjs(selectedDate).isValid()) {
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7712
|
+
// If effectiveDefaultDate is available, use it instead of clearing
|
|
7713
|
+
if (effectiveDefaultDate && dayjs(effectiveDefaultDate).isValid()) {
|
|
7714
|
+
console.log('[DateTimePicker] No valid selectedDate, using effectiveDefaultDate:', effectiveDefaultDate);
|
|
7715
|
+
setSelectedDate(effectiveDefaultDate);
|
|
7716
|
+
const dateObj = dayjs(effectiveDefaultDate).tz(timezone);
|
|
7717
|
+
if (dateObj.isValid()) {
|
|
7718
|
+
updateDateTime(dateObj.toISOString(), timeData);
|
|
7719
|
+
}
|
|
7720
|
+
else {
|
|
7721
|
+
console.warn('[DateTimePicker] Invalid effectiveDefaultDate, clearing fields');
|
|
7722
|
+
setSelectedDate('');
|
|
7723
|
+
setHour12(null);
|
|
7724
|
+
setMinute(null);
|
|
7725
|
+
setMeridiem(null);
|
|
7726
|
+
setHour24(null);
|
|
7727
|
+
setSecond(null);
|
|
7728
|
+
onChange?.(undefined);
|
|
7729
|
+
}
|
|
7730
|
+
return;
|
|
7731
|
+
}
|
|
7732
|
+
else {
|
|
7733
|
+
console.log('[DateTimePicker] No valid selectedDate and no effectiveDefaultDate, keeping time values but no date');
|
|
7734
|
+
// Keep the time values that were just set, but don't set a date
|
|
7735
|
+
// This should rarely happen as effectiveDefaultDate always defaults to today
|
|
7736
|
+
setSelectedDate('');
|
|
7737
|
+
onChange?.(undefined);
|
|
7738
|
+
return;
|
|
7739
|
+
}
|
|
7305
7740
|
}
|
|
7306
7741
|
const dateObj = dayjs(selectedDate).tz(timezone);
|
|
7307
7742
|
if (dateObj.isValid()) {
|
|
@@ -7444,18 +7879,24 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
|
|
|
7444
7879
|
};
|
|
7445
7880
|
const handleClear = () => {
|
|
7446
7881
|
setSelectedDate('');
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7882
|
+
// Reset to default time values instead of clearing them
|
|
7883
|
+
if (format === 'iso-date-time') {
|
|
7884
|
+
setHour24(defaultTime ? defaultTime.hour ?? 0 : 0);
|
|
7885
|
+
setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
|
|
7886
|
+
setSecond(showSeconds
|
|
7887
|
+
? defaultTime
|
|
7888
|
+
? defaultTime.second ?? 0
|
|
7889
|
+
: 0
|
|
7890
|
+
: null);
|
|
7891
|
+
}
|
|
7892
|
+
else {
|
|
7893
|
+
setHour12(defaultTime ? defaultTime.hour ?? 12 : 12);
|
|
7894
|
+
setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
|
|
7895
|
+
setMeridiem(defaultTime ? defaultTime.meridiem ?? 'am' : 'am');
|
|
7896
|
+
}
|
|
7452
7897
|
onChange?.(undefined);
|
|
7453
7898
|
};
|
|
7454
7899
|
const isISO = format === 'iso-date-time';
|
|
7455
|
-
// Normalize startTime to ignore milliseconds
|
|
7456
|
-
const normalizedStartTime = startTime
|
|
7457
|
-
? dayjs(startTime).tz(timezone).millisecond(0).toISOString()
|
|
7458
|
-
: undefined;
|
|
7459
7900
|
// Determine minDate: prioritize explicit minDate prop, then fall back to startTime
|
|
7460
7901
|
const effectiveMinDate = minDate
|
|
7461
7902
|
? minDate
|
|
@@ -7533,7 +7974,7 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
|
|
|
7533
7974
|
const dateObj = dayjs.tz(selectedDate, timezone);
|
|
7534
7975
|
return dateObj.isValid() ? dateObj.format('Z') : null;
|
|
7535
7976
|
}, [selectedDate, timezone]);
|
|
7536
|
-
return (jsxs(Flex, { direction: "column", gap:
|
|
7977
|
+
return (jsxs(Flex, { direction: "column", gap: 2, children: [jsx(DatePickerInput, { value: selectedDate || undefined, onChange: (date) => {
|
|
7537
7978
|
if (date) {
|
|
7538
7979
|
handleDateChange(date);
|
|
7539
7980
|
}
|
|
@@ -7541,7 +7982,7 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
|
|
|
7541
7982
|
setSelectedDate('');
|
|
7542
7983
|
onChange?.(undefined);
|
|
7543
7984
|
}
|
|
7544
|
-
}, placeholder: "Select a date", dateFormat: "YYYY-MM-DD", displayFormat: "YYYY-MM-DD", labels: labels, timezone: timezone, minDate: effectiveMinDate, maxDate: maxDate, monthsToDisplay: 1, readOnly:
|
|
7985
|
+
}, placeholder: "Select a date", dateFormat: "YYYY-MM-DD", displayFormat: "YYYY-MM-DD", labels: labels, timezone: timezone, minDate: effectiveMinDate, maxDate: maxDate, monthsToDisplay: 1, readOnly: false }), jsxs(Grid, { templateColumns: "1fr auto", alignItems: "center", gap: 2, children: [isISO ? (jsx(IsoTimePicker, { hour: hour24, setHour: setHour24, minute: minute, setMinute: setMinute, second: showSeconds ? second : null, setSecond: showSeconds ? setSecond : () => { }, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone, portalled: portalled, labels: timePickerLabels })) : (jsx(TimePicker$1, { hour: hour12, setHour: setHour12, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone, portalled: portalled, labels: timePickerLabels })), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "outline", colorScheme: "red", children: jsx(Icon, { as: FaTrash }) })] }), displayText && (jsxs(Flex, { gap: 2, children: [jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: displayText }), timezoneOffset && (jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezoneOffset })), jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezone })] }))] }));
|
|
7545
7986
|
}
|
|
7546
7987
|
|
|
7547
7988
|
dayjs.extend(utc);
|
|
@@ -7603,7 +8044,7 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
|
|
|
7603
8044
|
return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
7604
8045
|
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(Popover.Trigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
|
|
7605
8046
|
setOpen(true);
|
|
7606
|
-
}, justifyContent: 'start', children: [jsx(MdDateRange, {}), displayDate || ''] }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "
|
|
8047
|
+
}, justifyContent: 'start', children: [jsx(MdDateRange, {}), displayDate || ''] }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "350px", minH: "10rem", children: jsx(Popover.Body, { children: dateTimePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "350px", minH: "10rem", children: jsx(Popover.Body, { children: dateTimePickerContent }) }) }) }))] }) }));
|
|
7607
8048
|
};
|
|
7608
8049
|
|
|
7609
8050
|
const SchemaRenderer = ({ schema, prefix, column, }) => {
|
|
@@ -7756,15 +8197,15 @@ const DateViewer = ({ column, schema, prefix }) => {
|
|
|
7756
8197
|
|
|
7757
8198
|
const EnumViewer = ({ column, isMultiple = false, schema, prefix, }) => {
|
|
7758
8199
|
const { watch, formState: { errors }, } = useFormContext();
|
|
7759
|
-
const formI18n = useFormI18n(column, prefix);
|
|
8200
|
+
const formI18n = useFormI18n(column, prefix, schema);
|
|
7760
8201
|
const { required } = schema;
|
|
7761
8202
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
7762
|
-
const { gridColumn =
|
|
8203
|
+
const { gridColumn = 'span 12', gridRow = 'span 1', renderDisplay } = schema;
|
|
7763
8204
|
const colLabel = formI18n.colLabel;
|
|
7764
8205
|
const watchEnum = watch(colLabel);
|
|
7765
8206
|
const watchEnums = (watch(colLabel) ?? []);
|
|
7766
|
-
return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems:
|
|
7767
|
-
gridRow, children: [isMultiple && (jsx(Flex, { flexFlow:
|
|
8207
|
+
return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
8208
|
+
gridRow, children: [isMultiple && (jsx(Flex, { flexFlow: 'wrap', gap: 1, children: watchEnums.map((enumValue) => {
|
|
7768
8209
|
const item = enumValue;
|
|
7769
8210
|
if (item === undefined) {
|
|
7770
8211
|
return jsx(Fragment, { children: "undefined" });
|
|
@@ -7772,7 +8213,7 @@ const EnumViewer = ({ column, isMultiple = false, schema, prefix, }) => {
|
|
|
7772
8213
|
return (jsx(Tag, { size: "lg", children: !!renderDisplay === true
|
|
7773
8214
|
? renderDisplay(item)
|
|
7774
8215
|
: formI18n.t(item) }, item));
|
|
7775
|
-
}) })), !isMultiple && jsx(Text, { children: formI18n.t(watchEnum) }), errors[`${column}`] && (jsx(Text, { color:
|
|
8216
|
+
}) })), !isMultiple && jsx(Text, { children: formI18n.t(watchEnum) }), errors[`${column}`] && (jsx(Text, { color: 'red.400', children: formI18n.required() }))] }));
|
|
7776
8217
|
};
|
|
7777
8218
|
|
|
7778
8219
|
const FileViewer = ({ column, schema, prefix }) => {
|
|
@@ -8185,6 +8626,17 @@ const FormBody = () => {
|
|
|
8185
8626
|
|
|
8186
8627
|
const FormTitle = () => {
|
|
8187
8628
|
const { schema } = useSchemaContext();
|
|
8629
|
+
// Debug log when form title is missing
|
|
8630
|
+
if (!schema.title) {
|
|
8631
|
+
console.debug('[Form Title] Missing title in root schema. Add title property to schema.', {
|
|
8632
|
+
schema: {
|
|
8633
|
+
type: schema.type,
|
|
8634
|
+
properties: schema.properties
|
|
8635
|
+
? Object.keys(schema.properties)
|
|
8636
|
+
: undefined,
|
|
8637
|
+
},
|
|
8638
|
+
});
|
|
8639
|
+
}
|
|
8188
8640
|
return jsx(Heading, { children: schema.title ?? 'Form' });
|
|
8189
8641
|
};
|
|
8190
8642
|
|