@bsol-oss/react-datatable5 13.0.1-beta.8 → 13.0.1-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1329 -1046
- package/dist/index.mjs +1331 -1048
- package/dist/types/components/DatePicker/DateTimePicker.d.ts +17 -16
- package/dist/types/components/DatePicker/UniversalPicker.d.ts +2 -3
- package/dist/types/components/DatePicker/index.d.ts +0 -1
- package/dist/types/components/TimePicker/TimePicker.d.ts +39 -10
- package/package.json +1 -1
- package/dist/types/components/DatePicker/IsoTimePicker.d.ts +0 -24
package/dist/index.js
CHANGED
|
@@ -29,9 +29,9 @@ var reactHookForm = require('react-hook-form');
|
|
|
29
29
|
var Ajv = require('ajv');
|
|
30
30
|
var addFormats = require('ajv-formats');
|
|
31
31
|
var dayjs = require('dayjs');
|
|
32
|
-
var utc = require('dayjs/plugin/utc');
|
|
33
|
-
var timezone = require('dayjs/plugin/timezone');
|
|
34
32
|
var customParseFormat = require('dayjs/plugin/customParseFormat');
|
|
33
|
+
var timezone = require('dayjs/plugin/timezone');
|
|
34
|
+
var utc = require('dayjs/plugin/utc');
|
|
35
35
|
var ti = require('react-icons/ti');
|
|
36
36
|
var matchSorterUtils = require('@tanstack/match-sorter-utils');
|
|
37
37
|
|
|
@@ -4680,7 +4680,7 @@ function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateF
|
|
|
4680
4680
|
}, timezone = 'Asia/Hong_Kong', minDate, maxDate, firstDayOfWeek, showOutsideDays, monthsToDisplay = 1, insideDialog = false, readOnly = false, showHelperButtons = true, }) {
|
|
4681
4681
|
const [open, setOpen] = React.useState(false);
|
|
4682
4682
|
const [inputValue, setInputValue] = React.useState('');
|
|
4683
|
-
//
|
|
4683
|
+
// Sync inputValue with value prop changes
|
|
4684
4684
|
React.useEffect(() => {
|
|
4685
4685
|
if (value) {
|
|
4686
4686
|
const formatted = typeof value === 'string'
|
|
@@ -4693,7 +4693,7 @@ function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateF
|
|
|
4693
4693
|
else {
|
|
4694
4694
|
setInputValue('');
|
|
4695
4695
|
}
|
|
4696
|
-
}, [value,
|
|
4696
|
+
}, [value, timezone, displayFormat]);
|
|
4697
4697
|
// Convert value to Date object for DatePicker
|
|
4698
4698
|
const selectedDate = value
|
|
4699
4699
|
? typeof value === 'string'
|
|
@@ -4775,7 +4775,14 @@ function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateF
|
|
|
4775
4775
|
}
|
|
4776
4776
|
};
|
|
4777
4777
|
const handleDateSelected = ({ date }) => {
|
|
4778
|
+
console.debug('[DatePickerInput] handleDateSelected called:', {
|
|
4779
|
+
date: date.toISOString(),
|
|
4780
|
+
timezone,
|
|
4781
|
+
dateFormat,
|
|
4782
|
+
formattedDate: dayjs(date).tz(timezone).format(dateFormat),
|
|
4783
|
+
});
|
|
4778
4784
|
const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
|
|
4785
|
+
console.debug('[DatePickerInput] Calling onChange with formatted date:', formattedDate);
|
|
4779
4786
|
onChange?.(formattedDate);
|
|
4780
4787
|
setOpen(false);
|
|
4781
4788
|
};
|
|
@@ -6762,11 +6769,193 @@ const TextAreaInput = ({ column, schema, prefix, }) => {
|
|
|
6762
6769
|
|
|
6763
6770
|
dayjs.extend(utc);
|
|
6764
6771
|
dayjs.extend(timezone);
|
|
6765
|
-
const TimePicker$1 = (
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6772
|
+
const TimePicker$1 = (props) => {
|
|
6773
|
+
const { format = '12h', value: controlledValue, onChange: controlledOnChange, hour: uncontrolledHour, setHour: uncontrolledSetHour, minute: uncontrolledMinute, setMinute: uncontrolledSetMinute, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, labels = {
|
|
6774
|
+
placeholder: format === '24h' ? 'HH:mm:ss' : 'hh:mm AM/PM',
|
|
6775
|
+
emptyMessage: 'No time found',
|
|
6776
|
+
}, onTimeChange, } = props;
|
|
6777
|
+
const is24Hour = format === '24h';
|
|
6778
|
+
const uncontrolledMeridiem = is24Hour ? undefined : props.meridiem;
|
|
6779
|
+
const uncontrolledSetMeridiem = is24Hour ? undefined : props.setMeridiem;
|
|
6780
|
+
const uncontrolledSecond = is24Hour ? props.second : undefined;
|
|
6781
|
+
const uncontrolledSetSecond = is24Hour ? props.setSecond : undefined;
|
|
6782
|
+
// Determine if we're in controlled mode
|
|
6783
|
+
const isControlled = controlledValue !== undefined;
|
|
6784
|
+
// Parse time string to extract hour, minute, second, meridiem
|
|
6785
|
+
const parseTimeString = (timeStr) => {
|
|
6786
|
+
if (!timeStr || !timeStr.trim()) {
|
|
6787
|
+
return { hour: null, minute: null, second: null, meridiem: null };
|
|
6788
|
+
}
|
|
6789
|
+
// Remove timezone suffix if present (e.g., "14:30:00Z" -> "14:30:00")
|
|
6790
|
+
const timeWithoutTz = timeStr.replace(/[Z+-]\d{2}:?\d{2}$/, '').trim();
|
|
6791
|
+
// Try parsing 24-hour format: "HH:mm:ss" or "HH:mm"
|
|
6792
|
+
const time24Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
|
|
6793
|
+
const match24 = timeWithoutTz.match(time24Pattern);
|
|
6794
|
+
if (match24) {
|
|
6795
|
+
const hour24 = parseInt(match24[1], 10);
|
|
6796
|
+
const minute = parseInt(match24[2], 10);
|
|
6797
|
+
const second = match24[3] ? parseInt(match24[3], 10) : 0;
|
|
6798
|
+
if (hour24 >= 0 &&
|
|
6799
|
+
hour24 <= 23 &&
|
|
6800
|
+
minute >= 0 &&
|
|
6801
|
+
minute <= 59 &&
|
|
6802
|
+
second >= 0 &&
|
|
6803
|
+
second <= 59) {
|
|
6804
|
+
if (is24Hour) {
|
|
6805
|
+
return { hour: hour24, minute, second, meridiem: null };
|
|
6806
|
+
}
|
|
6807
|
+
else {
|
|
6808
|
+
// Convert to 12-hour format
|
|
6809
|
+
let hour12 = hour24;
|
|
6810
|
+
let meridiem;
|
|
6811
|
+
if (hour24 === 0) {
|
|
6812
|
+
hour12 = 12;
|
|
6813
|
+
meridiem = 'am';
|
|
6814
|
+
}
|
|
6815
|
+
else if (hour24 === 12) {
|
|
6816
|
+
hour12 = 12;
|
|
6817
|
+
meridiem = 'pm';
|
|
6818
|
+
}
|
|
6819
|
+
else if (hour24 > 12) {
|
|
6820
|
+
hour12 = hour24 - 12;
|
|
6821
|
+
meridiem = 'pm';
|
|
6822
|
+
}
|
|
6823
|
+
else {
|
|
6824
|
+
hour12 = hour24;
|
|
6825
|
+
meridiem = 'am';
|
|
6826
|
+
}
|
|
6827
|
+
return { hour: hour12, minute, second: null, meridiem };
|
|
6828
|
+
}
|
|
6829
|
+
}
|
|
6830
|
+
}
|
|
6831
|
+
// Try parsing 12-hour format: "hh:mm AM/PM" or "hh:mm:ss AM/PM"
|
|
6832
|
+
const time12Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\s*(am|pm|AM|PM)$/i;
|
|
6833
|
+
const match12 = timeWithoutTz.match(time12Pattern);
|
|
6834
|
+
if (match12 && !is24Hour) {
|
|
6835
|
+
const hour12 = parseInt(match12[1], 10);
|
|
6836
|
+
const minute = parseInt(match12[2], 10);
|
|
6837
|
+
const second = match12[3] ? parseInt(match12[3], 10) : null;
|
|
6838
|
+
const meridiem = match12[4].toLowerCase();
|
|
6839
|
+
if (hour12 >= 1 &&
|
|
6840
|
+
hour12 <= 12 &&
|
|
6841
|
+
minute >= 0 &&
|
|
6842
|
+
minute <= 59 &&
|
|
6843
|
+
(second === null || (second >= 0 && second <= 59))) {
|
|
6844
|
+
return { hour: hour12, minute, second, meridiem };
|
|
6845
|
+
}
|
|
6846
|
+
}
|
|
6847
|
+
return { hour: null, minute: null, second: null, meridiem: null };
|
|
6848
|
+
};
|
|
6849
|
+
// Format time values to time string
|
|
6850
|
+
const formatTimeString = (hour, minute, second, meridiem) => {
|
|
6851
|
+
if (hour === null || minute === null) {
|
|
6852
|
+
return undefined;
|
|
6853
|
+
}
|
|
6854
|
+
if (is24Hour) {
|
|
6855
|
+
const h = hour.toString().padStart(2, '0');
|
|
6856
|
+
const m = minute.toString().padStart(2, '0');
|
|
6857
|
+
const s = (second ?? 0).toString().padStart(2, '0');
|
|
6858
|
+
return `${h}:${m}:${s}`;
|
|
6859
|
+
}
|
|
6860
|
+
else {
|
|
6861
|
+
if (meridiem === null) {
|
|
6862
|
+
return undefined;
|
|
6863
|
+
}
|
|
6864
|
+
const h = hour.toString();
|
|
6865
|
+
const m = minute.toString().padStart(2, '0');
|
|
6866
|
+
return `${h}:${m} ${meridiem.toUpperCase()}`;
|
|
6867
|
+
}
|
|
6868
|
+
};
|
|
6869
|
+
// Internal state for controlled mode
|
|
6870
|
+
const [internalHour, setInternalHour] = React.useState(null);
|
|
6871
|
+
const [internalMinute, setInternalMinute] = React.useState(null);
|
|
6872
|
+
const [internalSecond, setInternalSecond] = React.useState(null);
|
|
6873
|
+
const [internalMeridiem, setInternalMeridiem] = React.useState(null);
|
|
6874
|
+
// Use controlled or uncontrolled values
|
|
6875
|
+
const hour = isControlled ? internalHour : uncontrolledHour ?? null;
|
|
6876
|
+
const minute = isControlled ? internalMinute : uncontrolledMinute ?? null;
|
|
6877
|
+
const second = isControlled ? internalSecond : uncontrolledSecond ?? null;
|
|
6878
|
+
const meridiem = isControlled
|
|
6879
|
+
? internalMeridiem
|
|
6880
|
+
: uncontrolledMeridiem ?? null;
|
|
6881
|
+
// Setters that work for both modes
|
|
6882
|
+
const setHour = isControlled
|
|
6883
|
+
? setInternalHour
|
|
6884
|
+
: uncontrolledSetHour || (() => { });
|
|
6885
|
+
const setMinute = isControlled
|
|
6886
|
+
? setInternalMinute
|
|
6887
|
+
: uncontrolledSetMinute || (() => { });
|
|
6888
|
+
const setSecond = isControlled
|
|
6889
|
+
? setInternalSecond
|
|
6890
|
+
: uncontrolledSetSecond || (() => { });
|
|
6891
|
+
const setMeridiem = isControlled
|
|
6892
|
+
? setInternalMeridiem
|
|
6893
|
+
: uncontrolledSetMeridiem || (() => { });
|
|
6894
|
+
// Sync internal state with controlled value prop
|
|
6895
|
+
const prevValueRef = React.useRef(controlledValue);
|
|
6896
|
+
React.useEffect(() => {
|
|
6897
|
+
if (!isControlled)
|
|
6898
|
+
return;
|
|
6899
|
+
if (prevValueRef.current === controlledValue) {
|
|
6900
|
+
return;
|
|
6901
|
+
}
|
|
6902
|
+
prevValueRef.current = controlledValue;
|
|
6903
|
+
const parsed = parseTimeString(controlledValue);
|
|
6904
|
+
setInternalHour(parsed.hour);
|
|
6905
|
+
setInternalMinute(parsed.minute);
|
|
6906
|
+
if (is24Hour) {
|
|
6907
|
+
setInternalSecond(parsed.second);
|
|
6908
|
+
}
|
|
6909
|
+
else {
|
|
6910
|
+
setInternalMeridiem(parsed.meridiem);
|
|
6911
|
+
}
|
|
6912
|
+
}, [controlledValue, isControlled, is24Hour]);
|
|
6913
|
+
// Wrapper onChange that calls both controlled and uncontrolled onChange
|
|
6914
|
+
const handleTimeChange = (newHour, newMinute, newSecond, newMeridiem) => {
|
|
6915
|
+
if (isControlled) {
|
|
6916
|
+
const timeString = formatTimeString(newHour, newMinute, newSecond, newMeridiem);
|
|
6917
|
+
controlledOnChange?.(timeString);
|
|
6918
|
+
}
|
|
6919
|
+
else {
|
|
6920
|
+
// Call legacy onTimeChange if provided
|
|
6921
|
+
if (onTimeChange) {
|
|
6922
|
+
if (is24Hour) {
|
|
6923
|
+
const timeChange24h = onTimeChange;
|
|
6924
|
+
timeChange24h({
|
|
6925
|
+
hour: newHour,
|
|
6926
|
+
minute: newMinute,
|
|
6927
|
+
second: newSecond,
|
|
6928
|
+
});
|
|
6929
|
+
}
|
|
6930
|
+
else {
|
|
6931
|
+
const timeChange12h = onTimeChange;
|
|
6932
|
+
timeChange12h({
|
|
6933
|
+
hour: newHour,
|
|
6934
|
+
minute: newMinute,
|
|
6935
|
+
meridiem: newMeridiem,
|
|
6936
|
+
});
|
|
6937
|
+
}
|
|
6938
|
+
}
|
|
6939
|
+
}
|
|
6940
|
+
};
|
|
6941
|
+
const [inputValue, setInputValue] = React.useState('');
|
|
6942
|
+
// Sync inputValue with current time
|
|
6943
|
+
React.useEffect(() => {
|
|
6944
|
+
if (is24Hour && second !== undefined) {
|
|
6945
|
+
if (hour !== null && minute !== null && second !== null) {
|
|
6946
|
+
const formatted = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${second.toString().padStart(2, '0')}`;
|
|
6947
|
+
setInputValue(formatted);
|
|
6948
|
+
}
|
|
6949
|
+
else {
|
|
6950
|
+
setInputValue('');
|
|
6951
|
+
}
|
|
6952
|
+
}
|
|
6953
|
+
else {
|
|
6954
|
+
// 12-hour format - input is managed by combobox
|
|
6955
|
+
setInputValue('');
|
|
6956
|
+
}
|
|
6957
|
+
}, [hour, minute, second, is24Hour]);
|
|
6958
|
+
// Generate time options based on format
|
|
6770
6959
|
const timeOptions = React.useMemo(() => {
|
|
6771
6960
|
const options = [];
|
|
6772
6961
|
// Get start time for comparison if provided
|
|
@@ -6777,32 +6966,25 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6777
6966
|
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
6778
6967
|
if (startDateObj.isValid() && selectedDateObj.isValid()) {
|
|
6779
6968
|
startDateTime = startDateObj;
|
|
6780
|
-
// Only filter if dates are the same
|
|
6781
6969
|
shouldFilterByDate =
|
|
6782
6970
|
startDateObj.format('YYYY-MM-DD') ===
|
|
6783
6971
|
selectedDateObj.format('YYYY-MM-DD');
|
|
6784
6972
|
}
|
|
6785
6973
|
}
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
for (let
|
|
6789
|
-
for (
|
|
6790
|
-
//
|
|
6791
|
-
let hour24 = h;
|
|
6792
|
-
if (mer === 'am' && h === 12)
|
|
6793
|
-
hour24 = 0;
|
|
6794
|
-
else if (mer === 'pm' && h < 12)
|
|
6795
|
-
hour24 = h + 12;
|
|
6796
|
-
// Filter out times that would result in negative duration (only when dates are the same)
|
|
6974
|
+
if (is24Hour) {
|
|
6975
|
+
// Generate 24-hour format options (0-23 for hours)
|
|
6976
|
+
for (let h = 0; h < 24; h++) {
|
|
6977
|
+
for (let m = 0; m < 60; m += 15) {
|
|
6978
|
+
// Filter out times that would result in negative duration
|
|
6797
6979
|
if (startDateTime && selectedDate && shouldFilterByDate) {
|
|
6798
6980
|
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
6799
6981
|
const optionDateTime = selectedDateObj
|
|
6800
|
-
.hour(
|
|
6982
|
+
.hour(h)
|
|
6801
6983
|
.minute(m)
|
|
6802
6984
|
.second(0)
|
|
6803
6985
|
.millisecond(0);
|
|
6804
6986
|
if (optionDateTime.isBefore(startDateTime)) {
|
|
6805
|
-
continue;
|
|
6987
|
+
continue;
|
|
6806
6988
|
}
|
|
6807
6989
|
}
|
|
6808
6990
|
// Calculate duration if startTime is provided
|
|
@@ -6810,7 +6992,7 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6810
6992
|
if (startDateTime && selectedDate) {
|
|
6811
6993
|
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
6812
6994
|
const optionDateTime = selectedDateObj
|
|
6813
|
-
.hour(
|
|
6995
|
+
.hour(h)
|
|
6814
6996
|
.minute(m)
|
|
6815
6997
|
.second(0)
|
|
6816
6998
|
.millisecond(0);
|
|
@@ -6835,62 +7017,133 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6835
7017
|
}
|
|
6836
7018
|
}
|
|
6837
7019
|
}
|
|
6838
|
-
const
|
|
6839
|
-
const minuteDisplay = m.toString().padStart(2, '0');
|
|
6840
|
-
const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
|
|
7020
|
+
const timeDisplay = `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:00`;
|
|
6841
7021
|
options.push({
|
|
6842
7022
|
label: timeDisplay,
|
|
6843
|
-
value: `${h}:${m}
|
|
7023
|
+
value: `${h}:${m}:0`,
|
|
6844
7024
|
hour: h,
|
|
6845
7025
|
minute: m,
|
|
6846
|
-
|
|
6847
|
-
searchText: timeDisplay,
|
|
7026
|
+
second: 0,
|
|
7027
|
+
searchText: timeDisplay,
|
|
6848
7028
|
durationText,
|
|
6849
7029
|
});
|
|
6850
7030
|
}
|
|
6851
7031
|
}
|
|
6852
7032
|
}
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
7033
|
+
else {
|
|
7034
|
+
// Generate 12-hour format options (1-12 for hours, AM/PM)
|
|
7035
|
+
for (let h = 1; h <= 12; h++) {
|
|
7036
|
+
for (let m = 0; m < 60; m += 15) {
|
|
7037
|
+
for (const mer of ['am', 'pm']) {
|
|
7038
|
+
// Convert 12-hour to 24-hour for comparison
|
|
7039
|
+
let hour24 = h;
|
|
7040
|
+
if (mer === 'am' && h === 12)
|
|
7041
|
+
hour24 = 0;
|
|
7042
|
+
else if (mer === 'pm' && h < 12)
|
|
7043
|
+
hour24 = h + 12;
|
|
7044
|
+
// Filter out times that would result in negative duration
|
|
7045
|
+
if (startDateTime && selectedDate && shouldFilterByDate) {
|
|
7046
|
+
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
7047
|
+
const optionDateTime = selectedDateObj
|
|
7048
|
+
.hour(hour24)
|
|
7049
|
+
.minute(m)
|
|
7050
|
+
.second(0)
|
|
7051
|
+
.millisecond(0);
|
|
7052
|
+
if (optionDateTime.isBefore(startDateTime)) {
|
|
7053
|
+
continue;
|
|
7054
|
+
}
|
|
7055
|
+
}
|
|
7056
|
+
// Calculate duration if startTime is provided
|
|
7057
|
+
let durationText;
|
|
7058
|
+
if (startDateTime && selectedDate) {
|
|
7059
|
+
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
7060
|
+
const optionDateTime = selectedDateObj
|
|
7061
|
+
.hour(hour24)
|
|
7062
|
+
.minute(m)
|
|
7063
|
+
.second(0)
|
|
7064
|
+
.millisecond(0);
|
|
7065
|
+
if (optionDateTime.isValid() &&
|
|
7066
|
+
optionDateTime.isAfter(startDateTime)) {
|
|
7067
|
+
const diffMs = optionDateTime.diff(startDateTime);
|
|
7068
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
7069
|
+
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
7070
|
+
const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
|
|
7071
|
+
if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
|
|
7072
|
+
let diffText = '';
|
|
7073
|
+
if (diffHours > 0) {
|
|
7074
|
+
diffText = `${diffHours}h ${diffMinutes}m`;
|
|
7075
|
+
}
|
|
7076
|
+
else if (diffMinutes > 0) {
|
|
7077
|
+
diffText = `${diffMinutes}m ${diffSeconds}s`;
|
|
7078
|
+
}
|
|
7079
|
+
else {
|
|
7080
|
+
diffText = `${diffSeconds}s`;
|
|
7081
|
+
}
|
|
7082
|
+
durationText = `+${diffText}`;
|
|
7083
|
+
}
|
|
7084
|
+
}
|
|
7085
|
+
}
|
|
7086
|
+
const hourDisplay = h.toString();
|
|
7087
|
+
const minuteDisplay = m.toString().padStart(2, '0');
|
|
7088
|
+
const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
|
|
7089
|
+
options.push({
|
|
7090
|
+
label: timeDisplay,
|
|
7091
|
+
value: `${h}:${m}:${mer}`,
|
|
7092
|
+
hour: h,
|
|
7093
|
+
minute: m,
|
|
7094
|
+
meridiem: mer,
|
|
7095
|
+
searchText: timeDisplay,
|
|
7096
|
+
durationText,
|
|
7097
|
+
});
|
|
7098
|
+
}
|
|
7099
|
+
}
|
|
7100
|
+
}
|
|
7101
|
+
// Sort 12-hour options by time (convert to 24-hour for proper chronological sorting)
|
|
7102
|
+
return options.sort((a, b) => {
|
|
7103
|
+
const a12 = a;
|
|
7104
|
+
const b12 = b;
|
|
7105
|
+
let hour24A = a12.hour;
|
|
7106
|
+
if (a12.meridiem === 'am' && a12.hour === 12)
|
|
7107
|
+
hour24A = 0;
|
|
7108
|
+
else if (a12.meridiem === 'pm' && a12.hour < 12)
|
|
7109
|
+
hour24A = a12.hour + 12;
|
|
7110
|
+
let hour24B = b12.hour;
|
|
7111
|
+
if (b12.meridiem === 'am' && b12.hour === 12)
|
|
7112
|
+
hour24B = 0;
|
|
7113
|
+
else if (b12.meridiem === 'pm' && b12.hour < 12)
|
|
7114
|
+
hour24B = b12.hour + 12;
|
|
7115
|
+
if (hour24A !== hour24B) {
|
|
7116
|
+
return hour24A - hour24B;
|
|
7117
|
+
}
|
|
7118
|
+
return a12.minute - b12.minute;
|
|
7119
|
+
});
|
|
7120
|
+
}
|
|
7121
|
+
return options;
|
|
7122
|
+
}, [startTime, selectedDate, timezone, is24Hour]);
|
|
6873
7123
|
// itemToString returns only the clean display text (no metadata)
|
|
6874
7124
|
const itemToString = React.useMemo(() => {
|
|
6875
7125
|
return (item) => {
|
|
6876
|
-
return item.searchText;
|
|
7126
|
+
return item.searchText;
|
|
6877
7127
|
};
|
|
6878
7128
|
}, []);
|
|
6879
|
-
// Custom filter function
|
|
7129
|
+
// Custom filter function
|
|
7130
|
+
const { contains } = react.useFilter({ sensitivity: 'base' });
|
|
6880
7131
|
const customTimeFilter = React.useMemo(() => {
|
|
7132
|
+
if (is24Hour) {
|
|
7133
|
+
return contains; // Simple contains filter for 24-hour format
|
|
7134
|
+
}
|
|
7135
|
+
// For 12-hour format, support both 12-hour and 24-hour input
|
|
6881
7136
|
return (itemText, filterText) => {
|
|
6882
7137
|
if (!filterText) {
|
|
6883
|
-
return true;
|
|
7138
|
+
return true;
|
|
6884
7139
|
}
|
|
6885
7140
|
const lowerItemText = itemText.toLowerCase();
|
|
6886
7141
|
const lowerFilterText = filterText.toLowerCase();
|
|
6887
|
-
// First, try matching against the display text (12-hour format)
|
|
6888
7142
|
if (lowerItemText.includes(lowerFilterText)) {
|
|
6889
7143
|
return true;
|
|
6890
7144
|
}
|
|
6891
|
-
// Find the corresponding item to check 24-hour format matches
|
|
6892
7145
|
const item = timeOptions.find((opt) => opt.searchText.toLowerCase() === lowerItemText);
|
|
6893
|
-
if (!item) {
|
|
7146
|
+
if (!item || !('meridiem' in item)) {
|
|
6894
7147
|
return false;
|
|
6895
7148
|
}
|
|
6896
7149
|
// Convert item to 24-hour format for matching
|
|
@@ -6901,18 +7154,17 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6901
7154
|
hour24 = item.hour + 12;
|
|
6902
7155
|
const hour24Str = hour24.toString().padStart(2, '0');
|
|
6903
7156
|
const minuteStr = item.minute.toString().padStart(2, '0');
|
|
6904
|
-
// Check if filterText matches 24-hour format variations
|
|
6905
7157
|
const formats = [
|
|
6906
|
-
`${hour24Str}:${minuteStr}`,
|
|
6907
|
-
`${hour24Str}${minuteStr}`,
|
|
6908
|
-
hour24Str,
|
|
6909
|
-
`${hour24}:${minuteStr}`,
|
|
6910
|
-
hour24.toString(),
|
|
7158
|
+
`${hour24Str}:${minuteStr}`,
|
|
7159
|
+
`${hour24Str}${minuteStr}`,
|
|
7160
|
+
hour24Str,
|
|
7161
|
+
`${hour24}:${minuteStr}`,
|
|
7162
|
+
hour24.toString(),
|
|
6911
7163
|
];
|
|
6912
7164
|
return formats.some((format) => format.toLowerCase().includes(lowerFilterText) ||
|
|
6913
7165
|
lowerFilterText.includes(format.toLowerCase()));
|
|
6914
7166
|
};
|
|
6915
|
-
}, [timeOptions]);
|
|
7167
|
+
}, [timeOptions, is24Hour, contains]);
|
|
6916
7168
|
const { collection, filter } = react.useListCollection({
|
|
6917
7169
|
initialItems: timeOptions,
|
|
6918
7170
|
itemToString: itemToString,
|
|
@@ -6921,32 +7173,48 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6921
7173
|
});
|
|
6922
7174
|
// Get current value string for combobox
|
|
6923
7175
|
const currentValue = React.useMemo(() => {
|
|
6924
|
-
if (
|
|
6925
|
-
|
|
7176
|
+
if (is24Hour) {
|
|
7177
|
+
if (hour === null || minute === null || second === null) {
|
|
7178
|
+
return '';
|
|
7179
|
+
}
|
|
7180
|
+
return `${hour}:${minute}:${second}`;
|
|
7181
|
+
}
|
|
7182
|
+
else {
|
|
7183
|
+
if (hour === null || minute === null || meridiem === null) {
|
|
7184
|
+
return '';
|
|
7185
|
+
}
|
|
7186
|
+
return `${hour}:${minute}:${meridiem}`;
|
|
6926
7187
|
}
|
|
6927
|
-
|
|
6928
|
-
}, [hour, minute, meridiem]);
|
|
7188
|
+
}, [hour, minute, second, meridiem, is24Hour]);
|
|
6929
7189
|
// Calculate duration difference
|
|
6930
7190
|
const durationDiff = React.useMemo(() => {
|
|
6931
|
-
if (!startTime ||
|
|
6932
|
-
!selectedDate ||
|
|
6933
|
-
hour === null ||
|
|
6934
|
-
minute === null ||
|
|
6935
|
-
meridiem === null) {
|
|
7191
|
+
if (!startTime || !selectedDate || hour === null || minute === null) {
|
|
6936
7192
|
return null;
|
|
6937
7193
|
}
|
|
7194
|
+
if (is24Hour) {
|
|
7195
|
+
if (second === null)
|
|
7196
|
+
return null;
|
|
7197
|
+
}
|
|
7198
|
+
else {
|
|
7199
|
+
if (meridiem === null)
|
|
7200
|
+
return null;
|
|
7201
|
+
}
|
|
6938
7202
|
const startDateObj = dayjs(startTime).tz(timezone);
|
|
6939
7203
|
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
6940
|
-
// Convert
|
|
7204
|
+
// Convert to 24-hour format
|
|
6941
7205
|
let hour24 = hour;
|
|
6942
|
-
if (
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
7206
|
+
if (!is24Hour && meridiem) {
|
|
7207
|
+
if (meridiem === 'am' && hour === 12)
|
|
7208
|
+
hour24 = 0;
|
|
7209
|
+
else if (meridiem === 'pm' && hour < 12)
|
|
7210
|
+
hour24 = hour + 12;
|
|
7211
|
+
}
|
|
6946
7212
|
const currentDateTime = selectedDateObj
|
|
6947
7213
|
.hour(hour24)
|
|
6948
7214
|
.minute(minute)
|
|
6949
|
-
.second(
|
|
7215
|
+
.second(is24Hour && second !== null && second !== undefined
|
|
7216
|
+
? second
|
|
7217
|
+
: 0)
|
|
6950
7218
|
.millisecond(0);
|
|
6951
7219
|
if (!startDateObj.isValid() || !currentDateTime.isValid()) {
|
|
6952
7220
|
return null;
|
|
@@ -6972,13 +7240,28 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6972
7240
|
return `+${diffText}`;
|
|
6973
7241
|
}
|
|
6974
7242
|
return null;
|
|
6975
|
-
}, [
|
|
7243
|
+
}, [
|
|
7244
|
+
hour,
|
|
7245
|
+
minute,
|
|
7246
|
+
second,
|
|
7247
|
+
meridiem,
|
|
7248
|
+
startTime,
|
|
7249
|
+
selectedDate,
|
|
7250
|
+
timezone,
|
|
7251
|
+
is24Hour,
|
|
7252
|
+
]);
|
|
6976
7253
|
const handleClear = () => {
|
|
6977
7254
|
setHour(null);
|
|
6978
7255
|
setMinute(null);
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
7256
|
+
if (is24Hour && setSecond) {
|
|
7257
|
+
setSecond(null);
|
|
7258
|
+
handleTimeChange(null, null, null, null);
|
|
7259
|
+
}
|
|
7260
|
+
else if (!is24Hour && setMeridiem) {
|
|
7261
|
+
setMeridiem(null);
|
|
7262
|
+
handleTimeChange(null, null, null, null);
|
|
7263
|
+
}
|
|
7264
|
+
filter('');
|
|
6982
7265
|
};
|
|
6983
7266
|
const handleValueChange = (details) => {
|
|
6984
7267
|
if (details.value.length === 0) {
|
|
@@ -6990,112 +7273,165 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6990
7273
|
if (selectedOption) {
|
|
6991
7274
|
setHour(selectedOption.hour);
|
|
6992
7275
|
setMinute(selectedOption.minute);
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
}
|
|
7276
|
+
filter('');
|
|
7277
|
+
if (is24Hour) {
|
|
7278
|
+
const opt24 = selectedOption;
|
|
7279
|
+
if (setSecond)
|
|
7280
|
+
setSecond(opt24.second);
|
|
7281
|
+
handleTimeChange(opt24.hour, opt24.minute, opt24.second, null);
|
|
7282
|
+
}
|
|
7283
|
+
else {
|
|
7284
|
+
const opt12 = selectedOption;
|
|
7285
|
+
if (setMeridiem)
|
|
7286
|
+
setMeridiem(opt12.meridiem);
|
|
7287
|
+
handleTimeChange(opt12.hour, opt12.minute, null, opt12.meridiem);
|
|
7288
|
+
}
|
|
7000
7289
|
}
|
|
7001
7290
|
};
|
|
7002
7291
|
// Parse input value and update state
|
|
7003
7292
|
const parseAndCommitInput = (value) => {
|
|
7004
7293
|
const trimmedValue = value.trim();
|
|
7005
|
-
// Filter the collection based on input
|
|
7006
7294
|
filter(trimmedValue);
|
|
7007
7295
|
if (!trimmedValue) {
|
|
7008
7296
|
return;
|
|
7009
7297
|
}
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
meridiem = 'pm';
|
|
7031
|
-
}
|
|
7032
|
-
else if (parsedHour24 > 12) {
|
|
7033
|
-
hour12 = parsedHour24 - 12;
|
|
7034
|
-
meridiem = 'pm';
|
|
7298
|
+
if (is24Hour) {
|
|
7299
|
+
// Parse 24-hour format: "HH:mm:ss" or "HH:mm" or "HHmmss" or "HHmm"
|
|
7300
|
+
const timePattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
|
|
7301
|
+
const match = trimmedValue.match(timePattern);
|
|
7302
|
+
if (match) {
|
|
7303
|
+
const parsedHour = parseInt(match[1], 10);
|
|
7304
|
+
const parsedMinute = parseInt(match[2], 10);
|
|
7305
|
+
const parsedSecond = match[3] ? parseInt(match[3], 10) : 0;
|
|
7306
|
+
if (parsedHour >= 0 &&
|
|
7307
|
+
parsedHour <= 23 &&
|
|
7308
|
+
parsedMinute >= 0 &&
|
|
7309
|
+
parsedMinute <= 59 &&
|
|
7310
|
+
parsedSecond >= 0 &&
|
|
7311
|
+
parsedSecond <= 59) {
|
|
7312
|
+
setHour(parsedHour);
|
|
7313
|
+
setMinute(parsedMinute);
|
|
7314
|
+
if (setSecond)
|
|
7315
|
+
setSecond(parsedSecond);
|
|
7316
|
+
handleTimeChange(parsedHour, parsedMinute, parsedSecond, null);
|
|
7317
|
+
return;
|
|
7035
7318
|
}
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7319
|
+
}
|
|
7320
|
+
// Try numbers only format: "123045" or "1230"
|
|
7321
|
+
const numbersOnly = trimmedValue.replace(/[^0-9]/g, '');
|
|
7322
|
+
if (numbersOnly.length >= 4) {
|
|
7323
|
+
const parsedHour = parseInt(numbersOnly.slice(0, 2), 10);
|
|
7324
|
+
const parsedMinute = parseInt(numbersOnly.slice(2, 4), 10);
|
|
7325
|
+
const parsedSecond = numbersOnly.length >= 6 ? parseInt(numbersOnly.slice(4, 6), 10) : 0;
|
|
7326
|
+
if (parsedHour >= 0 &&
|
|
7327
|
+
parsedHour <= 23 &&
|
|
7328
|
+
parsedMinute >= 0 &&
|
|
7329
|
+
parsedMinute <= 59 &&
|
|
7330
|
+
parsedSecond >= 0 &&
|
|
7331
|
+
parsedSecond <= 59) {
|
|
7332
|
+
setHour(parsedHour);
|
|
7333
|
+
setMinute(parsedMinute);
|
|
7334
|
+
if (setSecond)
|
|
7335
|
+
setSecond(parsedSecond);
|
|
7336
|
+
handleTimeChange(parsedHour, parsedMinute, parsedSecond, null);
|
|
7337
|
+
return;
|
|
7039
7338
|
}
|
|
7040
|
-
setHour(hour12);
|
|
7041
|
-
setMinute(parsedMinute);
|
|
7042
|
-
setMeridiem(meridiem);
|
|
7043
|
-
onChange({
|
|
7044
|
-
hour: hour12,
|
|
7045
|
-
minute: parsedMinute,
|
|
7046
|
-
meridiem: meridiem,
|
|
7047
|
-
});
|
|
7048
|
-
return;
|
|
7049
7339
|
}
|
|
7050
7340
|
}
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7341
|
+
else {
|
|
7342
|
+
// Parse 24-hour format first (e.g., "13:30", "14:00", "1330", "1400")
|
|
7343
|
+
const timePattern24Hour = /^(\d{1,2}):?(\d{2})$/;
|
|
7344
|
+
const match24Hour = trimmedValue.match(timePattern24Hour);
|
|
7345
|
+
if (match24Hour) {
|
|
7346
|
+
const parsedHour24 = parseInt(match24Hour[1], 10);
|
|
7347
|
+
const parsedMinute = parseInt(match24Hour[2], 10);
|
|
7348
|
+
if (parsedHour24 >= 0 &&
|
|
7349
|
+
parsedHour24 <= 23 &&
|
|
7350
|
+
parsedMinute >= 0 &&
|
|
7351
|
+
parsedMinute <= 59) {
|
|
7352
|
+
// Convert 24-hour to 12-hour format
|
|
7353
|
+
let hour12;
|
|
7354
|
+
let meridiem;
|
|
7355
|
+
if (parsedHour24 === 0) {
|
|
7356
|
+
hour12 = 12;
|
|
7357
|
+
meridiem = 'am';
|
|
7358
|
+
}
|
|
7359
|
+
else if (parsedHour24 === 12) {
|
|
7360
|
+
hour12 = 12;
|
|
7361
|
+
meridiem = 'pm';
|
|
7362
|
+
}
|
|
7363
|
+
else if (parsedHour24 > 12) {
|
|
7364
|
+
hour12 = parsedHour24 - 12;
|
|
7365
|
+
meridiem = 'pm';
|
|
7366
|
+
}
|
|
7367
|
+
else {
|
|
7368
|
+
hour12 = parsedHour24;
|
|
7369
|
+
meridiem = 'am';
|
|
7370
|
+
}
|
|
7371
|
+
setHour(hour12);
|
|
7372
|
+
setMinute(parsedMinute);
|
|
7373
|
+
if (setMeridiem)
|
|
7374
|
+
setMeridiem(meridiem);
|
|
7375
|
+
handleTimeChange(hour12, parsedMinute, null, meridiem);
|
|
7376
|
+
return;
|
|
7377
|
+
}
|
|
7072
7378
|
}
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
if (numbersOnly.length >= 3) {
|
|
7081
|
-
const parsedHour = parseInt(numbersOnly.slice(0, -2), 10);
|
|
7082
|
-
const parsedMinute = parseInt(numbersOnly.slice(-2), 10);
|
|
7083
|
-
// Validate ranges
|
|
7379
|
+
// Parse formats like "1:30 PM", "1:30PM", "1:30 pm", "1:30pm"
|
|
7380
|
+
const timePattern12Hour = /^(\d{1,2}):(\d{1,2})\s*(am|pm|AM|PM)$/i;
|
|
7381
|
+
const match12Hour = trimmedValue.match(timePattern12Hour);
|
|
7382
|
+
if (match12Hour) {
|
|
7383
|
+
const parsedHour = parseInt(match12Hour[1], 10);
|
|
7384
|
+
const parsedMinute = parseInt(match12Hour[2], 10);
|
|
7385
|
+
const parsedMeridiem = match12Hour[3].toLowerCase();
|
|
7084
7386
|
if (parsedHour >= 1 &&
|
|
7085
7387
|
parsedHour <= 12 &&
|
|
7086
7388
|
parsedMinute >= 0 &&
|
|
7087
7389
|
parsedMinute <= 59) {
|
|
7088
7390
|
setHour(parsedHour);
|
|
7089
7391
|
setMinute(parsedMinute);
|
|
7090
|
-
setMeridiem
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7392
|
+
if (setMeridiem)
|
|
7393
|
+
setMeridiem(parsedMeridiem);
|
|
7394
|
+
handleTimeChange(parsedHour, parsedMinute, null, parsedMeridiem);
|
|
7395
|
+
return;
|
|
7396
|
+
}
|
|
7397
|
+
}
|
|
7398
|
+
// Parse formats like "12am" or "1pm" (hour only with meridiem, no minutes)
|
|
7399
|
+
const timePatternHourOnly = /^(\d{1,2})\s*(am|pm|AM|PM)$/i;
|
|
7400
|
+
const matchHourOnly = trimmedValue.match(timePatternHourOnly);
|
|
7401
|
+
if (matchHourOnly) {
|
|
7402
|
+
const parsedHour = parseInt(matchHourOnly[1], 10);
|
|
7403
|
+
const parsedMeridiem = matchHourOnly[2].toLowerCase();
|
|
7404
|
+
if (parsedHour >= 1 && parsedHour <= 12) {
|
|
7405
|
+
setHour(parsedHour);
|
|
7406
|
+
setMinute(0); // Default to 0 minutes when only hour is provided
|
|
7407
|
+
if (setMeridiem)
|
|
7408
|
+
setMeridiem(parsedMeridiem);
|
|
7409
|
+
handleTimeChange(parsedHour, 0, null, parsedMeridiem);
|
|
7096
7410
|
return;
|
|
7097
7411
|
}
|
|
7098
7412
|
}
|
|
7413
|
+
// Try to parse formats like "130pm" or "130 pm" (without colon, with minutes)
|
|
7414
|
+
const timePatternNoColon = /^(\d{1,4})\s*(am|pm|AM|PM)$/i;
|
|
7415
|
+
const matchNoColon = trimmedValue.match(timePatternNoColon);
|
|
7416
|
+
if (matchNoColon) {
|
|
7417
|
+
const numbersOnly = matchNoColon[1];
|
|
7418
|
+
const parsedMeridiem = matchNoColon[2].toLowerCase();
|
|
7419
|
+
if (numbersOnly.length >= 3) {
|
|
7420
|
+
const parsedHour = parseInt(numbersOnly.slice(0, -2), 10);
|
|
7421
|
+
const parsedMinute = parseInt(numbersOnly.slice(-2), 10);
|
|
7422
|
+
if (parsedHour >= 1 &&
|
|
7423
|
+
parsedHour <= 12 &&
|
|
7424
|
+
parsedMinute >= 0 &&
|
|
7425
|
+
parsedMinute <= 59) {
|
|
7426
|
+
setHour(parsedHour);
|
|
7427
|
+
setMinute(parsedMinute);
|
|
7428
|
+
if (setMeridiem)
|
|
7429
|
+
setMeridiem(parsedMeridiem);
|
|
7430
|
+
handleTimeChange(parsedHour, parsedMinute, null, parsedMeridiem);
|
|
7431
|
+
return;
|
|
7432
|
+
}
|
|
7433
|
+
}
|
|
7434
|
+
}
|
|
7099
7435
|
}
|
|
7100
7436
|
// Parse failed, select first result
|
|
7101
7437
|
selectFirstResult();
|
|
@@ -7106,55 +7442,84 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
7106
7442
|
const firstItem = collection.items[0];
|
|
7107
7443
|
setHour(firstItem.hour);
|
|
7108
7444
|
setMinute(firstItem.minute);
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
}
|
|
7445
|
+
filter('');
|
|
7446
|
+
if (is24Hour) {
|
|
7447
|
+
const opt24 = firstItem;
|
|
7448
|
+
if (setSecond)
|
|
7449
|
+
setSecond(opt24.second);
|
|
7450
|
+
handleTimeChange(opt24.hour, opt24.minute, opt24.second, null);
|
|
7451
|
+
}
|
|
7452
|
+
else {
|
|
7453
|
+
const opt12 = firstItem;
|
|
7454
|
+
if (setMeridiem)
|
|
7455
|
+
setMeridiem(opt12.meridiem);
|
|
7456
|
+
handleTimeChange(opt12.hour, opt12.minute, null, opt12.meridiem);
|
|
7457
|
+
}
|
|
7116
7458
|
}
|
|
7117
7459
|
};
|
|
7118
7460
|
const handleInputValueChange = (details) => {
|
|
7119
|
-
|
|
7461
|
+
if (is24Hour) {
|
|
7462
|
+
setInputValue(details.inputValue);
|
|
7463
|
+
}
|
|
7120
7464
|
filter(details.inputValue);
|
|
7121
7465
|
};
|
|
7122
7466
|
const handleFocus = (e) => {
|
|
7123
|
-
// Select all text when focusing
|
|
7124
7467
|
e.target.select();
|
|
7125
7468
|
};
|
|
7126
7469
|
const handleBlur = (e) => {
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7470
|
+
const inputVal = e.target.value;
|
|
7471
|
+
if (is24Hour) {
|
|
7472
|
+
setInputValue(inputVal);
|
|
7473
|
+
}
|
|
7474
|
+
if (inputVal) {
|
|
7475
|
+
parseAndCommitInput(inputVal);
|
|
7131
7476
|
}
|
|
7132
7477
|
};
|
|
7133
7478
|
const handleKeyDown = (e) => {
|
|
7134
|
-
// Commit input on Enter key
|
|
7135
7479
|
if (e.key === 'Enter') {
|
|
7136
7480
|
e.preventDefault();
|
|
7137
|
-
const
|
|
7138
|
-
if (
|
|
7139
|
-
|
|
7481
|
+
const inputVal = e.currentTarget.value;
|
|
7482
|
+
if (is24Hour) {
|
|
7483
|
+
setInputValue(inputVal);
|
|
7484
|
+
}
|
|
7485
|
+
if (inputVal) {
|
|
7486
|
+
parseAndCommitInput(inputVal);
|
|
7140
7487
|
}
|
|
7141
|
-
// Blur the input
|
|
7142
7488
|
e.currentTarget?.blur();
|
|
7143
7489
|
}
|
|
7144
7490
|
};
|
|
7145
|
-
return (jsxRuntime.jsx(react.Flex, { direction: "column", gap: 3, children: jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace",
|
|
7491
|
+
return (jsxRuntime.jsx(react.Flex, { direction: "column", gap: 3, children: jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", flex: 1, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { value: is24Hour ? inputValue : undefined, placeholder: labels?.placeholder ?? (is24Hour ? 'HH:mm:ss' : 'hh:mm AM/PM'), onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsxRuntime.jsx(react.Combobox.IndicatorGroup, { children: jsxRuntime.jsx(react.Combobox.Trigger, {}) })] }), jsxRuntime.jsx(react.Portal, { disabled: !portalled, children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [jsxRuntime.jsx(react.Combobox.Empty, { children: labels?.emptyMessage ?? 'No time found' }), collection.items.map((item) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsxRuntime.jsx(react.Text, { flex: 1, children: item.label }), item.durationText && (jsxRuntime.jsx(react.Tag.Root, { size: "sm", children: jsxRuntime.jsx(react.Tag.Label, { children: item.durationText }) }))] }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsxRuntime.jsx(react.Tag.Root, { size: "sm", children: jsxRuntime.jsx(react.Tag.Label, { children: durationDiff }) }))] }) }));
|
|
7146
7492
|
};
|
|
7147
7493
|
|
|
7148
7494
|
dayjs.extend(timezone);
|
|
7149
7495
|
const TimePicker = ({ column, schema, prefix }) => {
|
|
7150
7496
|
const { watch, formState: { errors }, setValue, } = reactHookForm.useFormContext();
|
|
7151
7497
|
const { timezone, insideDialog, timePickerLabels } = useSchemaContext();
|
|
7152
|
-
const { required, gridColumn = 'span 12', gridRow = 'span 1', timeFormat = 'HH:mm:ssZ', displayTimeFormat = 'hh:mm A', } = schema;
|
|
7498
|
+
const { required, gridColumn = 'span 12', gridRow = 'span 1', timeFormat = 'HH:mm:ssZ', displayTimeFormat = 'hh:mm A', startTimeField, selectedDateField, } = schema;
|
|
7153
7499
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
7154
7500
|
const colLabel = `${prefix}${column}`;
|
|
7155
7501
|
const formI18n = useFormI18n(column, prefix, schema);
|
|
7156
7502
|
const [open, setOpen] = React.useState(false);
|
|
7157
7503
|
const value = watch(colLabel);
|
|
7504
|
+
// Watch startTime and selectedDate fields for offset calculation
|
|
7505
|
+
const startTimeValue = startTimeField
|
|
7506
|
+
? watch(`${prefix}${startTimeField}`)
|
|
7507
|
+
: undefined;
|
|
7508
|
+
const selectedDateValue = selectedDateField
|
|
7509
|
+
? watch(`${prefix}${selectedDateField}`)
|
|
7510
|
+
: undefined;
|
|
7511
|
+
// Convert to ISO string format for startTime if it's a date-time string
|
|
7512
|
+
const startTime = startTimeValue
|
|
7513
|
+
? dayjs(startTimeValue).tz(timezone).isValid()
|
|
7514
|
+
? dayjs(startTimeValue).tz(timezone).toISOString()
|
|
7515
|
+
: undefined
|
|
7516
|
+
: undefined;
|
|
7517
|
+
// Convert selectedDate to YYYY-MM-DD format
|
|
7518
|
+
const selectedDate = selectedDateValue
|
|
7519
|
+
? dayjs(selectedDateValue).tz(timezone).isValid()
|
|
7520
|
+
? dayjs(selectedDateValue).tz(timezone).format('YYYY-MM-DD')
|
|
7521
|
+
: undefined
|
|
7522
|
+
: undefined;
|
|
7158
7523
|
const displayedTime = dayjs(`1970-01-01T${value}`).tz(timezone).isValid()
|
|
7159
7524
|
? dayjs(`1970-01-01T${value}`).tz(timezone).format(displayTimeFormat)
|
|
7160
7525
|
: '';
|
|
@@ -7210,941 +7575,860 @@ const TimePicker = ({ column, schema, prefix }) => {
|
|
|
7210
7575
|
return (jsxRuntime.jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
7211
7576
|
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxRuntime.jsxs(react.Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
|
|
7212
7577
|
setOpen(true);
|
|
7213
|
-
}, justifyContent: 'start', children: [jsxRuntime.jsx(io.IoMdClock, {}), !!value ? `${displayedTime}` : ''] }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { maxH: "70vh", overflowY: "auto", children: jsxRuntime.jsx(react.Popover.Body, { overflow: "visible", children: jsxRuntime.jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, labels: timePickerLabels }) }) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { children: jsxRuntime.jsx(react.Popover.Body, { children: jsxRuntime.jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, labels: timePickerLabels }) }) }) }) }))] }) }));
|
|
7578
|
+
}, justifyContent: 'start', children: [jsxRuntime.jsx(io.IoMdClock, {}), !!value ? `${displayedTime}` : ''] }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { maxH: "70vh", overflowY: "auto", children: jsxRuntime.jsx(react.Popover.Body, { overflow: "visible", children: jsxRuntime.jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: startTime, selectedDate: selectedDate, timezone: timezone, portalled: false, labels: timePickerLabels }) }) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { children: jsxRuntime.jsx(react.Popover.Body, { children: jsxRuntime.jsx(TimePicker$1, { format: "12h", hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: startTime, selectedDate: selectedDate, timezone: timezone, portalled: false, labels: timePickerLabels }) }) }) }) }))] }) }));
|
|
7214
7579
|
};
|
|
7215
7580
|
|
|
7216
7581
|
dayjs.extend(utc);
|
|
7217
7582
|
dayjs.extend(timezone);
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7583
|
+
dayjs.extend(customParseFormat);
|
|
7584
|
+
function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds = false, labels = {
|
|
7585
|
+
monthNamesShort: [
|
|
7586
|
+
'January',
|
|
7587
|
+
'February',
|
|
7588
|
+
'March',
|
|
7589
|
+
'April',
|
|
7590
|
+
'May',
|
|
7591
|
+
'June',
|
|
7592
|
+
'July',
|
|
7593
|
+
'August',
|
|
7594
|
+
'September',
|
|
7595
|
+
'October',
|
|
7596
|
+
'November',
|
|
7597
|
+
'December',
|
|
7598
|
+
],
|
|
7599
|
+
weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
|
7600
|
+
backButtonLabel: 'Back',
|
|
7601
|
+
forwardButtonLabel: 'Forward',
|
|
7602
|
+
}, timePickerLabels, timezone: tz = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, defaultDate, defaultTime, showQuickActions = false, quickActionLabels = {
|
|
7603
|
+
yesterday: 'Yesterday',
|
|
7604
|
+
today: 'Today',
|
|
7605
|
+
tomorrow: 'Tomorrow',
|
|
7606
|
+
plus7Days: '+7 Days',
|
|
7607
|
+
}, showTimezoneSelector = false, }) {
|
|
7608
|
+
const is24Hour = format === 'iso-date-time' || showSeconds;
|
|
7609
|
+
const { monthNamesShort = [
|
|
7610
|
+
'January',
|
|
7611
|
+
'February',
|
|
7612
|
+
'March',
|
|
7613
|
+
'April',
|
|
7614
|
+
'May',
|
|
7615
|
+
'June',
|
|
7616
|
+
'July',
|
|
7617
|
+
'August',
|
|
7618
|
+
'September',
|
|
7619
|
+
'October',
|
|
7620
|
+
'November',
|
|
7621
|
+
'December',
|
|
7622
|
+
], weekdayNamesShort = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], backButtonLabel = 'Back', forwardButtonLabel = 'Forward', } = labels;
|
|
7623
|
+
// Parse value to get date and time
|
|
7624
|
+
const parsedValue = React.useMemo(() => {
|
|
7625
|
+
if (!value)
|
|
7626
|
+
return null;
|
|
7627
|
+
const dateObj = dayjs(value).tz(tz);
|
|
7628
|
+
if (!dateObj.isValid())
|
|
7629
|
+
return null;
|
|
7630
|
+
return dateObj;
|
|
7631
|
+
}, [value, tz]);
|
|
7632
|
+
// Initialize date state
|
|
7633
|
+
const [selectedDate, setSelectedDate] = React.useState(() => {
|
|
7634
|
+
if (parsedValue) {
|
|
7635
|
+
return parsedValue.toDate();
|
|
7240
7636
|
}
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
.toString()
|
|
7245
|
-
.padStart(2, '0')}:00`;
|
|
7246
|
-
// Filter out times that would result in negative duration (only when dates are the same)
|
|
7247
|
-
if (startDateTime && selectedDate && shouldFilterByDate) {
|
|
7248
|
-
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
7249
|
-
const optionDateTime = selectedDateObj
|
|
7250
|
-
.hour(h)
|
|
7251
|
-
.minute(m)
|
|
7252
|
-
.second(0)
|
|
7253
|
-
.millisecond(0);
|
|
7254
|
-
if (optionDateTime.isBefore(startDateTime)) {
|
|
7255
|
-
continue; // Skip this option as it would result in negative duration
|
|
7256
|
-
}
|
|
7257
|
-
}
|
|
7258
|
-
// Calculate duration if startTime is provided
|
|
7259
|
-
let durationText;
|
|
7260
|
-
if (startDateTime && selectedDate) {
|
|
7261
|
-
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
7262
|
-
const optionDateTime = selectedDateObj
|
|
7263
|
-
.hour(h)
|
|
7264
|
-
.minute(m)
|
|
7265
|
-
.second(0)
|
|
7266
|
-
.millisecond(0);
|
|
7267
|
-
if (optionDateTime.isValid() &&
|
|
7268
|
-
optionDateTime.isAfter(startDateTime)) {
|
|
7269
|
-
const diffMs = optionDateTime.diff(startDateTime);
|
|
7270
|
-
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
7271
|
-
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
7272
|
-
const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
|
|
7273
|
-
if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
|
|
7274
|
-
let diffText = '';
|
|
7275
|
-
if (diffHours > 0) {
|
|
7276
|
-
diffText = `${diffHours}h ${diffMinutes}m`;
|
|
7277
|
-
}
|
|
7278
|
-
else if (diffMinutes > 0) {
|
|
7279
|
-
diffText = `${diffMinutes}m ${diffSeconds}s`;
|
|
7280
|
-
}
|
|
7281
|
-
else {
|
|
7282
|
-
diffText = `${diffSeconds}s`;
|
|
7283
|
-
}
|
|
7284
|
-
durationText = `+${diffText}`;
|
|
7285
|
-
}
|
|
7286
|
-
}
|
|
7287
|
-
}
|
|
7288
|
-
options.push({
|
|
7289
|
-
label: timeDisplay,
|
|
7290
|
-
value: `${h}:${m}:0`,
|
|
7291
|
-
hour: h,
|
|
7292
|
-
minute: m,
|
|
7293
|
-
second: 0,
|
|
7294
|
-
searchText: timeDisplay, // Use base time without duration for searching
|
|
7295
|
-
durationText,
|
|
7296
|
-
});
|
|
7297
|
-
}
|
|
7637
|
+
if (defaultDate) {
|
|
7638
|
+
const defaultDateObj = dayjs(defaultDate).tz(tz);
|
|
7639
|
+
return defaultDateObj.isValid() ? defaultDateObj.toDate() : new Date();
|
|
7298
7640
|
}
|
|
7299
|
-
return
|
|
7300
|
-
}, [startTime, selectedDate, timezone]);
|
|
7301
|
-
const { contains } = react.useFilter({ sensitivity: 'base' });
|
|
7302
|
-
const { collection, filter } = react.useListCollection({
|
|
7303
|
-
initialItems: timeOptions,
|
|
7304
|
-
itemToString: (item) => item.searchText, // Use searchText (without duration) for filtering
|
|
7305
|
-
itemToValue: (item) => item.value,
|
|
7306
|
-
filter: contains,
|
|
7641
|
+
return new Date();
|
|
7307
7642
|
});
|
|
7308
|
-
//
|
|
7309
|
-
const
|
|
7310
|
-
if (
|
|
7311
|
-
return
|
|
7312
|
-
}
|
|
7313
|
-
return `${hour}:${minute}:${second}`;
|
|
7314
|
-
}, [hour, minute, second]);
|
|
7315
|
-
// Calculate duration difference
|
|
7316
|
-
const durationDiff = React.useMemo(() => {
|
|
7317
|
-
if (!startTime ||
|
|
7318
|
-
!selectedDate ||
|
|
7319
|
-
hour === null ||
|
|
7320
|
-
minute === null ||
|
|
7321
|
-
second === null) {
|
|
7322
|
-
return null;
|
|
7643
|
+
// Initialize time state
|
|
7644
|
+
const [hour, setHour] = React.useState(() => {
|
|
7645
|
+
if (parsedValue) {
|
|
7646
|
+
return parsedValue.hour();
|
|
7323
7647
|
}
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
const currentDateTime = selectedDateObj
|
|
7327
|
-
.hour(hour)
|
|
7328
|
-
.minute(minute)
|
|
7329
|
-
.second(second ?? 0)
|
|
7330
|
-
.millisecond(0);
|
|
7331
|
-
if (!startDateObj.isValid() || !currentDateTime.isValid()) {
|
|
7332
|
-
return null;
|
|
7648
|
+
if (defaultTime?.hour !== null && defaultTime?.hour !== undefined) {
|
|
7649
|
+
return defaultTime.hour;
|
|
7333
7650
|
}
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7651
|
+
return null;
|
|
7652
|
+
});
|
|
7653
|
+
const [minute, setMinute] = React.useState(() => {
|
|
7654
|
+
if (parsedValue) {
|
|
7655
|
+
return parsedValue.minute();
|
|
7337
7656
|
}
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
|
|
7341
|
-
if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
|
|
7342
|
-
let diffText = '';
|
|
7343
|
-
if (diffHours > 0) {
|
|
7344
|
-
diffText = `${diffHours}h ${diffMinutes}m`;
|
|
7345
|
-
}
|
|
7346
|
-
else if (diffMinutes > 0) {
|
|
7347
|
-
diffText = `${diffMinutes}m ${diffSeconds}s`;
|
|
7348
|
-
}
|
|
7349
|
-
else {
|
|
7350
|
-
diffText = `${diffSeconds}s`;
|
|
7351
|
-
}
|
|
7352
|
-
return `+${diffText}`;
|
|
7657
|
+
if (defaultTime?.minute !== null && defaultTime?.minute !== undefined) {
|
|
7658
|
+
return defaultTime.minute;
|
|
7353
7659
|
}
|
|
7354
7660
|
return null;
|
|
7355
|
-
}
|
|
7356
|
-
const
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
setSecond(null);
|
|
7360
|
-
filter(''); // Reset filter to show all options
|
|
7361
|
-
onChange({ hour: null, minute: null, second: null });
|
|
7362
|
-
};
|
|
7363
|
-
const handleValueChange = (details) => {
|
|
7364
|
-
if (details.value.length === 0) {
|
|
7365
|
-
handleClear();
|
|
7366
|
-
return;
|
|
7661
|
+
});
|
|
7662
|
+
const [second, setSecond] = React.useState(() => {
|
|
7663
|
+
if (parsedValue) {
|
|
7664
|
+
return parsedValue.second();
|
|
7367
7665
|
}
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
if (selectedOption) {
|
|
7371
|
-
setHour(selectedOption.hour);
|
|
7372
|
-
setMinute(selectedOption.minute);
|
|
7373
|
-
setSecond(selectedOption.second);
|
|
7374
|
-
filter(''); // Reset filter after selection
|
|
7375
|
-
onChange({
|
|
7376
|
-
hour: selectedOption.hour,
|
|
7377
|
-
minute: selectedOption.minute,
|
|
7378
|
-
second: selectedOption.second,
|
|
7379
|
-
});
|
|
7666
|
+
if (defaultTime?.second !== null && defaultTime?.second !== undefined) {
|
|
7667
|
+
return defaultTime.second;
|
|
7380
7668
|
}
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
const
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7669
|
+
return showSeconds ? 0 : null;
|
|
7670
|
+
});
|
|
7671
|
+
const [meridiem, setMeridiem] = React.useState(() => {
|
|
7672
|
+
if (parsedValue) {
|
|
7673
|
+
const h = parsedValue.hour();
|
|
7674
|
+
return h < 12 ? 'am' : 'pm';
|
|
7675
|
+
}
|
|
7676
|
+
if (defaultTime?.meridiem !== null && defaultTime?.meridiem !== undefined) {
|
|
7677
|
+
return defaultTime.meridiem;
|
|
7678
|
+
}
|
|
7679
|
+
return is24Hour ? null : 'am';
|
|
7680
|
+
});
|
|
7681
|
+
// Popover state - separate for date, time, and timezone
|
|
7682
|
+
const [datePopoverOpen, setDatePopoverOpen] = React.useState(false);
|
|
7683
|
+
const [timePopoverOpen, setTimePopoverOpen] = React.useState(false);
|
|
7684
|
+
const [timezonePopoverOpen, setTimezonePopoverOpen] = React.useState(false);
|
|
7685
|
+
const [calendarPopoverOpen, setCalendarPopoverOpen] = React.useState(false);
|
|
7686
|
+
// Timezone offset state
|
|
7687
|
+
const [timezoneOffset, setTimezoneOffset] = React.useState(() => {
|
|
7688
|
+
if (parsedValue) {
|
|
7689
|
+
return parsedValue.format('Z');
|
|
7690
|
+
}
|
|
7691
|
+
// Default to +08:00
|
|
7692
|
+
return '+08:00';
|
|
7693
|
+
});
|
|
7694
|
+
// Sync timezone offset when value changes
|
|
7695
|
+
// Generate timezone offset options (UTC-12 to UTC+14)
|
|
7696
|
+
const timezoneOffsetOptions = React.useMemo(() => {
|
|
7697
|
+
const options = [];
|
|
7698
|
+
for (let offset = -12; offset <= 14; offset++) {
|
|
7699
|
+
const sign = offset >= 0 ? '+' : '-';
|
|
7700
|
+
const hours = Math.abs(offset).toString().padStart(2, '0');
|
|
7701
|
+
const value = `${sign}${hours}:00`;
|
|
7702
|
+
const label = `UTC${sign}${hours}:00`;
|
|
7703
|
+
options.push({ value, label });
|
|
7704
|
+
}
|
|
7705
|
+
return options;
|
|
7706
|
+
}, []);
|
|
7707
|
+
// Create collection for Select
|
|
7708
|
+
const { collection: timezoneCollection } = react.useListCollection({
|
|
7709
|
+
initialItems: timezoneOffsetOptions,
|
|
7710
|
+
itemToString: (item) => item.label,
|
|
7711
|
+
itemToValue: (item) => item.value,
|
|
7712
|
+
});
|
|
7713
|
+
// Date input state
|
|
7714
|
+
const [dateInputValue, setDateInputValue] = React.useState('');
|
|
7715
|
+
// Sync date input value with selected date
|
|
7716
|
+
React.useEffect(() => {
|
|
7717
|
+
if (selectedDate) {
|
|
7718
|
+
const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
|
|
7719
|
+
setDateInputValue(formatted);
|
|
7720
|
+
}
|
|
7721
|
+
else {
|
|
7722
|
+
setDateInputValue('');
|
|
7723
|
+
}
|
|
7724
|
+
}, [selectedDate, tz]);
|
|
7725
|
+
// Parse and validate date input
|
|
7726
|
+
const parseAndValidateDateInput = (inputVal) => {
|
|
7727
|
+
// If empty, clear the value
|
|
7728
|
+
if (!inputVal.trim()) {
|
|
7729
|
+
setSelectedDate(null);
|
|
7730
|
+
updateDateTime(null, hour, minute, second, meridiem);
|
|
7388
7731
|
return;
|
|
7389
7732
|
}
|
|
7390
|
-
//
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
if (
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
minute: parsedMinute,
|
|
7410
|
-
second: parsedSecond,
|
|
7411
|
-
});
|
|
7733
|
+
// Try parsing with common date formats
|
|
7734
|
+
let parsedDate = dayjs(inputVal, 'YYYY-MM-DD', true);
|
|
7735
|
+
// If that fails, try other common formats
|
|
7736
|
+
if (!parsedDate.isValid()) {
|
|
7737
|
+
parsedDate = dayjs(inputVal);
|
|
7738
|
+
}
|
|
7739
|
+
// If valid, check constraints and update
|
|
7740
|
+
if (parsedDate.isValid()) {
|
|
7741
|
+
const dateObj = parsedDate.tz(tz).toDate();
|
|
7742
|
+
// Check min/max constraints
|
|
7743
|
+
if (minDate && dateObj < minDate) {
|
|
7744
|
+
// Invalid: before minDate, reset to current selected date
|
|
7745
|
+
if (selectedDate) {
|
|
7746
|
+
const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
|
|
7747
|
+
setDateInputValue(formatted);
|
|
7748
|
+
}
|
|
7749
|
+
else {
|
|
7750
|
+
setDateInputValue('');
|
|
7751
|
+
}
|
|
7412
7752
|
return;
|
|
7413
7753
|
}
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
const parsedHour = parseInt(numbersOnly.slice(0, 2), 10);
|
|
7420
|
-
const parsedMinute = parseInt(numbersOnly.slice(2, 4), 10);
|
|
7421
|
-
const parsedSecond = numbersOnly.length >= 6 ? parseInt(numbersOnly.slice(4, 6), 10) : 0;
|
|
7422
|
-
// Validate ranges
|
|
7423
|
-
if (parsedHour >= 0 &&
|
|
7424
|
-
parsedHour <= 23 &&
|
|
7425
|
-
parsedMinute >= 0 &&
|
|
7426
|
-
parsedMinute <= 59 &&
|
|
7427
|
-
parsedSecond >= 0 &&
|
|
7428
|
-
parsedSecond <= 59) {
|
|
7429
|
-
setHour(parsedHour);
|
|
7430
|
-
setMinute(parsedMinute);
|
|
7431
|
-
setSecond(parsedSecond);
|
|
7432
|
-
onChange({
|
|
7433
|
-
hour: parsedHour,
|
|
7434
|
-
minute: parsedMinute,
|
|
7435
|
-
second: parsedSecond,
|
|
7436
|
-
});
|
|
7437
|
-
return;
|
|
7754
|
+
if (maxDate && dateObj > maxDate) {
|
|
7755
|
+
// Invalid: after maxDate, reset to current selected date
|
|
7756
|
+
if (selectedDate) {
|
|
7757
|
+
const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
|
|
7758
|
+
setDateInputValue(formatted);
|
|
7438
7759
|
}
|
|
7760
|
+
else {
|
|
7761
|
+
setDateInputValue('');
|
|
7762
|
+
}
|
|
7763
|
+
return;
|
|
7439
7764
|
}
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
if (collection.items.length > 0) {
|
|
7447
|
-
const firstItem = collection.items[0];
|
|
7448
|
-
setHour(firstItem.hour);
|
|
7449
|
-
setMinute(firstItem.minute);
|
|
7450
|
-
setSecond(firstItem.second);
|
|
7451
|
-
filter(''); // Reset filter after selection
|
|
7452
|
-
onChange({
|
|
7453
|
-
hour: firstItem.hour,
|
|
7454
|
-
minute: firstItem.minute,
|
|
7455
|
-
second: firstItem.second,
|
|
7456
|
-
});
|
|
7457
|
-
}
|
|
7458
|
-
};
|
|
7459
|
-
const [inputValue, setInputValue] = React.useState('');
|
|
7460
|
-
// Sync inputValue with currentValue when time changes externally
|
|
7461
|
-
React.useEffect(() => {
|
|
7462
|
-
if (hour !== null && minute !== null && second !== null) {
|
|
7463
|
-
const formattedValue = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${second.toString().padStart(2, '0')}`;
|
|
7464
|
-
setInputValue(formattedValue);
|
|
7765
|
+
// Valid date - update selected date
|
|
7766
|
+
setSelectedDate(dateObj);
|
|
7767
|
+
updateDateTime(dateObj, hour, minute, second, meridiem);
|
|
7768
|
+
// Format and update input value
|
|
7769
|
+
const formatted = parsedDate.tz(tz).format('YYYY-MM-DD');
|
|
7770
|
+
setDateInputValue(formatted);
|
|
7465
7771
|
}
|
|
7466
7772
|
else {
|
|
7467
|
-
|
|
7773
|
+
// Invalid date - reset to current selected date
|
|
7774
|
+
if (selectedDate) {
|
|
7775
|
+
const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
|
|
7776
|
+
setDateInputValue(formatted);
|
|
7777
|
+
}
|
|
7778
|
+
else {
|
|
7779
|
+
setDateInputValue('');
|
|
7780
|
+
}
|
|
7468
7781
|
}
|
|
7469
|
-
}, [hour, minute, second]);
|
|
7470
|
-
const handleInputValueChange = (details) => {
|
|
7471
|
-
// Update local input value state
|
|
7472
|
-
setInputValue(details.inputValue);
|
|
7473
|
-
// Filter the collection based on input, but don't parse yet
|
|
7474
|
-
filter(details.inputValue);
|
|
7475
7782
|
};
|
|
7476
|
-
const
|
|
7477
|
-
|
|
7478
|
-
e.target.select();
|
|
7783
|
+
const handleDateInputChange = (e) => {
|
|
7784
|
+
setDateInputValue(e.target.value);
|
|
7479
7785
|
};
|
|
7480
|
-
const
|
|
7481
|
-
|
|
7482
|
-
const inputVal = e.target.value;
|
|
7483
|
-
setInputValue(inputVal);
|
|
7484
|
-
if (inputVal) {
|
|
7485
|
-
parseAndCommitInput(inputVal);
|
|
7486
|
-
}
|
|
7786
|
+
const handleDateInputBlur = () => {
|
|
7787
|
+
parseAndValidateDateInput(dateInputValue);
|
|
7487
7788
|
};
|
|
7488
|
-
const
|
|
7489
|
-
// Commit input on Enter key
|
|
7789
|
+
const handleDateInputKeyDown = (e) => {
|
|
7490
7790
|
if (e.key === 'Enter') {
|
|
7491
7791
|
e.preventDefault();
|
|
7492
|
-
|
|
7493
|
-
setInputValue(inputVal);
|
|
7494
|
-
if (inputVal) {
|
|
7495
|
-
parseAndCommitInput(inputVal);
|
|
7496
|
-
}
|
|
7497
|
-
// Blur the input
|
|
7498
|
-
e.currentTarget?.blur();
|
|
7792
|
+
parseAndValidateDateInput(dateInputValue);
|
|
7499
7793
|
}
|
|
7500
7794
|
};
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
dayjs.
|
|
7505
|
-
dayjs.
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
'Jun',
|
|
7514
|
-
'Jul',
|
|
7515
|
-
'Aug',
|
|
7516
|
-
'Sep',
|
|
7517
|
-
'Oct',
|
|
7518
|
-
'Nov',
|
|
7519
|
-
'Dec',
|
|
7520
|
-
],
|
|
7521
|
-
weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
|
7522
|
-
backButtonLabel: 'Back',
|
|
7523
|
-
forwardButtonLabel: 'Next',
|
|
7524
|
-
}, timePickerLabels, timezone = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, defaultDate, defaultTime, }) {
|
|
7525
|
-
console.debug('[DateTimePicker] Component initialized with props:', {
|
|
7526
|
-
value,
|
|
7527
|
-
format,
|
|
7528
|
-
showSeconds,
|
|
7529
|
-
timezone,
|
|
7530
|
-
startTime,
|
|
7531
|
-
minDate,
|
|
7532
|
-
maxDate,
|
|
7533
|
-
});
|
|
7534
|
-
// Initialize selectedDate from value prop, converting ISO to YYYY-MM-DD format
|
|
7535
|
-
const getDateString = React.useCallback((val) => {
|
|
7536
|
-
if (!val)
|
|
7537
|
-
return '';
|
|
7538
|
-
const dateObj = dayjs(val).tz(timezone);
|
|
7539
|
-
return dateObj.isValid() ? dateObj.format('YYYY-MM-DD') : '';
|
|
7540
|
-
}, [timezone]);
|
|
7541
|
-
const [selectedDate, setSelectedDate] = React.useState(getDateString(value));
|
|
7542
|
-
// Helper to get time values from value prop with timezone
|
|
7543
|
-
const getTimeFromValue = React.useCallback((val) => {
|
|
7544
|
-
console.debug('[DateTimePicker] getTimeFromValue called:', {
|
|
7545
|
-
val,
|
|
7546
|
-
timezone,
|
|
7547
|
-
showSeconds,
|
|
7548
|
-
});
|
|
7549
|
-
if (!val) {
|
|
7550
|
-
console.debug('[DateTimePicker] No value provided, returning nulls');
|
|
7551
|
-
return {
|
|
7552
|
-
hour12: null,
|
|
7553
|
-
minute: null,
|
|
7554
|
-
meridiem: null,
|
|
7555
|
-
hour24: null,
|
|
7556
|
-
second: null,
|
|
7557
|
-
};
|
|
7795
|
+
// Helper functions to get dates in the correct timezone
|
|
7796
|
+
const getToday = () => dayjs().tz(tz).startOf('day').toDate();
|
|
7797
|
+
const getYesterday = () => dayjs().tz(tz).subtract(1, 'day').startOf('day').toDate();
|
|
7798
|
+
const getTomorrow = () => dayjs().tz(tz).add(1, 'day').startOf('day').toDate();
|
|
7799
|
+
const getPlus7Days = () => dayjs().tz(tz).add(7, 'day').startOf('day').toDate();
|
|
7800
|
+
// Check if a date is within min/max constraints
|
|
7801
|
+
const isDateValid = (date) => {
|
|
7802
|
+
if (minDate) {
|
|
7803
|
+
const minDateStart = dayjs(minDate).tz(tz).startOf('day').toDate();
|
|
7804
|
+
const dateStart = dayjs(date).tz(tz).startOf('day').toDate();
|
|
7805
|
+
if (dateStart < minDateStart)
|
|
7806
|
+
return false;
|
|
7558
7807
|
}
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
|
|
7565
|
-
hour24: dateObj.hour(),
|
|
7566
|
-
minute: dateObj.minute(),
|
|
7567
|
-
second: dateObj.second(),
|
|
7568
|
-
});
|
|
7569
|
-
if (!dateObj.isValid()) {
|
|
7570
|
-
console.debug('[DateTimePicker] Invalid date object, returning nulls');
|
|
7571
|
-
return {
|
|
7572
|
-
hour12: null,
|
|
7573
|
-
minute: null,
|
|
7574
|
-
meridiem: null,
|
|
7575
|
-
hour24: null,
|
|
7576
|
-
second: null,
|
|
7577
|
-
};
|
|
7808
|
+
if (maxDate) {
|
|
7809
|
+
const maxDateStart = dayjs(maxDate).tz(tz).startOf('day').toDate();
|
|
7810
|
+
const dateStart = dayjs(date).tz(tz).startOf('day').toDate();
|
|
7811
|
+
if (dateStart > maxDateStart)
|
|
7812
|
+
return false;
|
|
7578
7813
|
}
|
|
7579
|
-
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
hour24: hour24Value,
|
|
7589
|
-
second: secondValue,
|
|
7590
|
-
};
|
|
7591
|
-
console.debug('[DateTimePicker] Extracted time values:', result);
|
|
7592
|
-
return result;
|
|
7593
|
-
}, [timezone, showSeconds]);
|
|
7594
|
-
const initialTime = getTimeFromValue(value);
|
|
7595
|
-
console.debug('[DateTimePicker] Initial time from value:', {
|
|
7596
|
-
value,
|
|
7597
|
-
initialTime,
|
|
7598
|
-
});
|
|
7599
|
-
// Normalize startTime to ignore milliseconds (needed for effectiveDefaultDate calculation)
|
|
7600
|
-
const normalizedStartTime = startTime
|
|
7601
|
-
? dayjs(startTime).tz(timezone).millisecond(0).toISOString()
|
|
7602
|
-
: undefined;
|
|
7603
|
-
// Calculate effective defaultDate: use prop if provided, otherwise use startTime date, otherwise use today
|
|
7604
|
-
const effectiveDefaultDate = React.useMemo(() => {
|
|
7605
|
-
if (defaultDate) {
|
|
7606
|
-
return defaultDate;
|
|
7607
|
-
}
|
|
7608
|
-
if (normalizedStartTime &&
|
|
7609
|
-
dayjs(normalizedStartTime).tz(timezone).isValid()) {
|
|
7610
|
-
return dayjs(normalizedStartTime).tz(timezone).format('YYYY-MM-DD');
|
|
7611
|
-
}
|
|
7612
|
-
return dayjs().tz(timezone).format('YYYY-MM-DD');
|
|
7613
|
-
}, [defaultDate, normalizedStartTime, timezone]);
|
|
7614
|
-
// Initialize time with default values if no value is provided
|
|
7615
|
-
const getInitialTimeValues = () => {
|
|
7616
|
-
if (value && initialTime.hour12 !== null) {
|
|
7617
|
-
return initialTime;
|
|
7618
|
-
}
|
|
7619
|
-
// If no value or no time in value, use defaultTime or 00:00
|
|
7620
|
-
if (defaultTime) {
|
|
7621
|
-
if (format === 'iso-date-time') {
|
|
7622
|
-
const defaultTime24 = defaultTime;
|
|
7623
|
-
return {
|
|
7624
|
-
hour12: null,
|
|
7625
|
-
minute: defaultTime24.minute ?? 0,
|
|
7626
|
-
meridiem: null,
|
|
7627
|
-
hour24: defaultTime24.hour ?? 0,
|
|
7628
|
-
second: showSeconds ? defaultTime24.second ?? 0 : null,
|
|
7629
|
-
};
|
|
7630
|
-
}
|
|
7631
|
-
else {
|
|
7632
|
-
const defaultTime12 = defaultTime;
|
|
7633
|
-
return {
|
|
7634
|
-
hour12: defaultTime12.hour ?? 12,
|
|
7635
|
-
minute: defaultTime12.minute ?? 0,
|
|
7636
|
-
meridiem: defaultTime12.meridiem ?? 'am',
|
|
7637
|
-
hour24: null,
|
|
7638
|
-
second: null,
|
|
7639
|
-
};
|
|
7640
|
-
}
|
|
7814
|
+
return true;
|
|
7815
|
+
};
|
|
7816
|
+
// Handle quick action button clicks
|
|
7817
|
+
const handleQuickActionClick = (date) => {
|
|
7818
|
+
if (isDateValid(date)) {
|
|
7819
|
+
setSelectedDate(date);
|
|
7820
|
+
updateDateTime(date, hour, minute, second, meridiem);
|
|
7821
|
+
// Close the calendar popover if open
|
|
7822
|
+
setCalendarPopoverOpen(false);
|
|
7641
7823
|
}
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7824
|
+
};
|
|
7825
|
+
// Display text for buttons
|
|
7826
|
+
const dateDisplayText = React.useMemo(() => {
|
|
7827
|
+
if (!selectedDate)
|
|
7828
|
+
return 'Select date';
|
|
7829
|
+
return dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
|
|
7830
|
+
}, [selectedDate, tz]);
|
|
7831
|
+
const timeDisplayText = React.useMemo(() => {
|
|
7832
|
+
if (hour === null || minute === null)
|
|
7833
|
+
return 'Select time';
|
|
7834
|
+
if (is24Hour) {
|
|
7835
|
+
// 24-hour format: never show meridiem, always use 24-hour format (0-23)
|
|
7836
|
+
const hour24 = hour >= 0 && hour <= 23 ? hour : hour % 24;
|
|
7837
|
+
const s = second ?? 0;
|
|
7838
|
+
if (showSeconds) {
|
|
7839
|
+
return `${hour24.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
|
7840
|
+
}
|
|
7841
|
+
return `${hour24.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
|
|
7651
7842
|
}
|
|
7652
7843
|
else {
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
}
|
|
7660
|
-
}
|
|
7661
|
-
};
|
|
7662
|
-
const
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
const [second, setSecond] = React.useState(initialTimeValues.second);
|
|
7670
|
-
// Sync selectedDate and time states when value prop changes
|
|
7844
|
+
// 12-hour format: always show meridiem (AM/PM)
|
|
7845
|
+
const hour12 = hour >= 1 && hour <= 12 ? hour : hour % 12;
|
|
7846
|
+
if (meridiem === null)
|
|
7847
|
+
return 'Select time';
|
|
7848
|
+
const hourDisplay = hour12.toString();
|
|
7849
|
+
const minuteDisplay = minute.toString().padStart(2, '0');
|
|
7850
|
+
return `${hourDisplay}:${minuteDisplay} ${meridiem.toUpperCase()}`;
|
|
7851
|
+
}
|
|
7852
|
+
}, [hour, minute, second, meridiem, is24Hour, showSeconds]);
|
|
7853
|
+
const timezoneDisplayText = React.useMemo(() => {
|
|
7854
|
+
if (!showTimezoneSelector)
|
|
7855
|
+
return '';
|
|
7856
|
+
// Show offset as is (e.g., "+08:00")
|
|
7857
|
+
return timezoneOffset;
|
|
7858
|
+
}, [timezoneOffset, showTimezoneSelector]);
|
|
7859
|
+
// Update selectedDate when value changes externally
|
|
7671
7860
|
React.useEffect(() => {
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
setSelectedDate('');
|
|
7681
|
-
// Keep default time values instead of clearing them
|
|
7682
|
-
if (format === 'iso-date-time') {
|
|
7683
|
-
setHour24(defaultTime ? defaultTime.hour ?? 0 : 0);
|
|
7684
|
-
setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
|
|
7685
|
-
setSecond(showSeconds
|
|
7686
|
-
? defaultTime
|
|
7687
|
-
? defaultTime.second ?? 0
|
|
7688
|
-
: 0
|
|
7689
|
-
: null);
|
|
7690
|
-
}
|
|
7691
|
-
else {
|
|
7692
|
-
setHour12(defaultTime ? defaultTime.hour ?? 12 : 12);
|
|
7693
|
-
setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
|
|
7694
|
-
setMeridiem(defaultTime ? defaultTime.meridiem ?? 'am' : 'am');
|
|
7861
|
+
if (parsedValue) {
|
|
7862
|
+
setSelectedDate(parsedValue.toDate());
|
|
7863
|
+
setHour(parsedValue.hour());
|
|
7864
|
+
setMinute(parsedValue.minute());
|
|
7865
|
+
setSecond(parsedValue.second());
|
|
7866
|
+
if (!is24Hour) {
|
|
7867
|
+
const h = parsedValue.hour();
|
|
7868
|
+
setMeridiem(h < 12 ? 'am' : 'pm');
|
|
7695
7869
|
}
|
|
7870
|
+
}
|
|
7871
|
+
}, [parsedValue, is24Hour]);
|
|
7872
|
+
// Combine date and time and call onChange
|
|
7873
|
+
const updateDateTime = (newDate, newHour, newMinute, newSecond, newMeridiem, timezoneOffsetOverride) => {
|
|
7874
|
+
if (!newDate || newHour === null || newMinute === null) {
|
|
7875
|
+
onChange?.(undefined);
|
|
7696
7876
|
return;
|
|
7697
7877
|
}
|
|
7698
|
-
//
|
|
7699
|
-
|
|
7700
|
-
if (!
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
if (
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7878
|
+
// Convert 12-hour to 24-hour if needed
|
|
7879
|
+
let hour24 = newHour;
|
|
7880
|
+
if (!is24Hour && newMeridiem) {
|
|
7881
|
+
// In 12-hour format, hour should be 1-12
|
|
7882
|
+
// If hour is > 12, it might already be in 24-hour format, convert it first
|
|
7883
|
+
let hour12 = newHour;
|
|
7884
|
+
if (newHour > 12) {
|
|
7885
|
+
// Hour is in 24-hour format, convert to 12-hour first
|
|
7886
|
+
if (newHour === 12) {
|
|
7887
|
+
hour12 = 12;
|
|
7888
|
+
}
|
|
7889
|
+
else {
|
|
7890
|
+
hour12 = newHour - 12;
|
|
7891
|
+
}
|
|
7892
|
+
}
|
|
7893
|
+
// Now convert 12-hour to 24-hour format (0-23)
|
|
7894
|
+
if (newMeridiem === 'am') {
|
|
7895
|
+
if (hour12 === 12) {
|
|
7896
|
+
hour24 = 0; // 12 AM = 0:00
|
|
7897
|
+
}
|
|
7898
|
+
else {
|
|
7899
|
+
hour24 = hour12; // 1-11 AM = 1-11
|
|
7900
|
+
}
|
|
7712
7901
|
}
|
|
7713
7902
|
else {
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7903
|
+
// PM
|
|
7904
|
+
if (hour12 === 12) {
|
|
7905
|
+
hour24 = 12; // 12 PM = 12:00
|
|
7906
|
+
}
|
|
7907
|
+
else {
|
|
7908
|
+
hour24 = hour12 + 12; // 1-11 PM = 13-23
|
|
7909
|
+
}
|
|
7717
7910
|
}
|
|
7911
|
+
}
|
|
7912
|
+
else if (!is24Hour && !newMeridiem) {
|
|
7913
|
+
// If in 12-hour mode but no meridiem, assume the hour is already in 12-hour format
|
|
7914
|
+
// and default to AM (or keep as is if it's a valid 12-hour value)
|
|
7915
|
+
// This shouldn't happen in normal flow, but handle it gracefully
|
|
7916
|
+
hour24 = newHour;
|
|
7917
|
+
}
|
|
7918
|
+
// If timezone selector is enabled, create date-time without timezone conversion
|
|
7919
|
+
// to ensure the selected timestamp matches the picker values exactly
|
|
7920
|
+
if (showTimezoneSelector) {
|
|
7921
|
+
// Use override if provided, otherwise use state value
|
|
7922
|
+
const offsetToUse = timezoneOffsetOverride ?? timezoneOffset;
|
|
7923
|
+
// Create date-time from the Date object without timezone conversion
|
|
7924
|
+
// Extract year, month, day from the date
|
|
7925
|
+
const year = newDate.getFullYear();
|
|
7926
|
+
const month = newDate.getMonth();
|
|
7927
|
+
const day = newDate.getDate();
|
|
7928
|
+
// Create a date-time string with the exact values from the picker
|
|
7929
|
+
const formattedDateTime = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}T${String(hour24).padStart(2, '0')}:${String(newMinute).padStart(2, '0')}:${String(newSecond ?? 0).padStart(2, '0')}`;
|
|
7930
|
+
onChange?.(`${formattedDateTime}${offsetToUse}`);
|
|
7718
7931
|
return;
|
|
7719
7932
|
}
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
setMinute(timeData.minute);
|
|
7729
|
-
setMeridiem(timeData.meridiem);
|
|
7730
|
-
setHour24(timeData.hour24);
|
|
7731
|
-
setSecond(timeData.second);
|
|
7732
|
-
}, [value, getTimeFromValue, getDateString, timezone]);
|
|
7733
|
-
const handleDateChange = (date) => {
|
|
7734
|
-
console.debug('[DateTimePicker] handleDateChange called:', {
|
|
7735
|
-
date,
|
|
7736
|
-
timezone,
|
|
7737
|
-
showSeconds,
|
|
7738
|
-
currentTimeStates: { hour12, minute, meridiem, hour24, second },
|
|
7739
|
-
});
|
|
7740
|
-
// If date is empty or invalid, clear all fields
|
|
7741
|
-
if (!date || date === '') {
|
|
7742
|
-
console.debug('[DateTimePicker] Empty date, clearing all fields');
|
|
7743
|
-
setSelectedDate('');
|
|
7744
|
-
setHour12(null);
|
|
7745
|
-
setMinute(null);
|
|
7746
|
-
setMeridiem(null);
|
|
7747
|
-
setHour24(null);
|
|
7748
|
-
setSecond(null);
|
|
7933
|
+
// Normal mode: use timezone conversion
|
|
7934
|
+
let dateTime = dayjs(newDate)
|
|
7935
|
+
.tz(tz)
|
|
7936
|
+
.hour(hour24)
|
|
7937
|
+
.minute(newMinute)
|
|
7938
|
+
.second(newSecond ?? 0)
|
|
7939
|
+
.millisecond(0);
|
|
7940
|
+
if (!dateTime.isValid()) {
|
|
7749
7941
|
onChange?.(undefined);
|
|
7750
7942
|
return;
|
|
7751
7943
|
}
|
|
7944
|
+
// Format based on format prop
|
|
7945
|
+
if (format === 'iso-date-time') {
|
|
7946
|
+
onChange?.(dateTime.format('YYYY-MM-DDTHH:mm:ss'));
|
|
7947
|
+
}
|
|
7948
|
+
else {
|
|
7949
|
+
// date-time format with timezone
|
|
7950
|
+
onChange?.(dateTime.format('YYYY-MM-DDTHH:mm:ssZ'));
|
|
7951
|
+
}
|
|
7952
|
+
};
|
|
7953
|
+
// Handle date selection
|
|
7954
|
+
const handleDateSelected = ({ date, }) => {
|
|
7752
7955
|
setSelectedDate(date);
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
|
|
7759
|
-
|
|
7760
|
-
|
|
7761
|
-
}
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7956
|
+
updateDateTime(date, hour, minute, second, meridiem);
|
|
7957
|
+
};
|
|
7958
|
+
// Handle time change
|
|
7959
|
+
const handleTimeChange = (newHour, newMinute, newSecond, newMeridiem) => {
|
|
7960
|
+
setHour(newHour);
|
|
7961
|
+
setMinute(newMinute);
|
|
7962
|
+
if (is24Hour) {
|
|
7963
|
+
setSecond(newSecond);
|
|
7964
|
+
}
|
|
7965
|
+
else {
|
|
7966
|
+
setMeridiem(newMeridiem);
|
|
7967
|
+
}
|
|
7968
|
+
if (selectedDate) {
|
|
7969
|
+
updateDateTime(selectedDate, newHour, newMinute, newSecond, newMeridiem);
|
|
7970
|
+
}
|
|
7971
|
+
};
|
|
7972
|
+
// Calendar hook
|
|
7973
|
+
const calendarProps = useCalendar({
|
|
7974
|
+
selected: selectedDate || undefined,
|
|
7975
|
+
date: selectedDate || undefined,
|
|
7976
|
+
minDate,
|
|
7977
|
+
maxDate,
|
|
7978
|
+
monthsToDisplay: 1,
|
|
7979
|
+
onDateSelected: handleDateSelected,
|
|
7980
|
+
});
|
|
7981
|
+
// Generate time options
|
|
7982
|
+
const timeOptions = React.useMemo(() => {
|
|
7983
|
+
const options = [];
|
|
7984
|
+
// Get start time for comparison if provided
|
|
7985
|
+
let startDateTime = null;
|
|
7986
|
+
let shouldFilterByDate = false;
|
|
7987
|
+
if (startTime && selectedDate) {
|
|
7988
|
+
const startDateObj = dayjs(startTime).tz(tz);
|
|
7989
|
+
const selectedDateObj = dayjs(selectedDate).tz(tz);
|
|
7990
|
+
if (startDateObj.isValid() && selectedDateObj.isValid()) {
|
|
7991
|
+
startDateTime = startDateObj;
|
|
7992
|
+
shouldFilterByDate =
|
|
7993
|
+
startDateObj.format('YYYY-MM-DD') ===
|
|
7994
|
+
selectedDateObj.format('YYYY-MM-DD');
|
|
7995
|
+
}
|
|
7772
7996
|
}
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
|
|
7787
|
-
|
|
7997
|
+
if (is24Hour) {
|
|
7998
|
+
// Generate 24-hour format options
|
|
7999
|
+
for (let h = 0; h < 24; h++) {
|
|
8000
|
+
for (let m = 0; m < 60; m += 15) {
|
|
8001
|
+
// Filter out times that would result in negative duration
|
|
8002
|
+
if (startDateTime && selectedDate && shouldFilterByDate) {
|
|
8003
|
+
const selectedDateObj = dayjs(selectedDate).tz(tz);
|
|
8004
|
+
const optionDateTime = selectedDateObj
|
|
8005
|
+
.hour(h)
|
|
8006
|
+
.minute(m)
|
|
8007
|
+
.second(0)
|
|
8008
|
+
.millisecond(0);
|
|
8009
|
+
if (optionDateTime.isBefore(startDateTime)) {
|
|
8010
|
+
continue;
|
|
8011
|
+
}
|
|
7788
8012
|
}
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
8013
|
+
// Calculate duration if startTime is provided
|
|
8014
|
+
let durationText;
|
|
8015
|
+
if (startDateTime && selectedDate) {
|
|
8016
|
+
const selectedDateObj = dayjs(selectedDate).tz(tz);
|
|
8017
|
+
const optionDateTime = selectedDateObj
|
|
8018
|
+
.hour(h)
|
|
8019
|
+
.minute(m)
|
|
8020
|
+
.second(0)
|
|
8021
|
+
.millisecond(0);
|
|
8022
|
+
if (optionDateTime.isValid() &&
|
|
8023
|
+
optionDateTime.isAfter(startDateTime)) {
|
|
8024
|
+
const diffMs = optionDateTime.diff(startDateTime);
|
|
8025
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
8026
|
+
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
8027
|
+
const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
|
|
8028
|
+
if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
|
|
8029
|
+
let diffText = '';
|
|
8030
|
+
if (diffHours > 0) {
|
|
8031
|
+
diffText = `${diffHours}h ${diffMinutes}m`;
|
|
8032
|
+
}
|
|
8033
|
+
else if (diffMinutes > 0) {
|
|
8034
|
+
diffText = `${diffMinutes}m ${diffSeconds}s`;
|
|
8035
|
+
}
|
|
8036
|
+
else {
|
|
8037
|
+
diffText = `${diffSeconds}s`;
|
|
8038
|
+
}
|
|
8039
|
+
durationText = `+${diffText}`;
|
|
8040
|
+
}
|
|
8041
|
+
}
|
|
8042
|
+
}
|
|
8043
|
+
const s = showSeconds ? 0 : 0;
|
|
8044
|
+
const timeDisplay = showSeconds
|
|
8045
|
+
? `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:00`
|
|
8046
|
+
: `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`;
|
|
8047
|
+
options.push({
|
|
8048
|
+
label: timeDisplay,
|
|
8049
|
+
value: `${h}:${m}:${s}`,
|
|
8050
|
+
hour: h,
|
|
8051
|
+
minute: m,
|
|
8052
|
+
second: s,
|
|
8053
|
+
searchText: timeDisplay,
|
|
8054
|
+
durationText,
|
|
8055
|
+
});
|
|
7805
8056
|
}
|
|
7806
8057
|
}
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
8058
|
+
}
|
|
8059
|
+
else {
|
|
8060
|
+
// Generate 12-hour format options
|
|
8061
|
+
for (let h = 1; h <= 12; h++) {
|
|
8062
|
+
for (let m = 0; m < 60; m += 15) {
|
|
8063
|
+
for (const mer of ['am', 'pm']) {
|
|
8064
|
+
// Convert 12-hour to 24-hour for comparison
|
|
8065
|
+
let hour24 = h;
|
|
8066
|
+
if (mer === 'am' && h === 12)
|
|
8067
|
+
hour24 = 0;
|
|
8068
|
+
else if (mer === 'pm' && h < 12)
|
|
8069
|
+
hour24 = h + 12;
|
|
8070
|
+
// Filter out times that would result in negative duration
|
|
8071
|
+
if (startDateTime && selectedDate && shouldFilterByDate) {
|
|
8072
|
+
const selectedDateObj = dayjs(selectedDate).tz(tz);
|
|
8073
|
+
const optionDateTime = selectedDateObj
|
|
8074
|
+
.hour(hour24)
|
|
8075
|
+
.minute(m)
|
|
8076
|
+
.second(0)
|
|
8077
|
+
.millisecond(0);
|
|
8078
|
+
if (optionDateTime.isBefore(startDateTime)) {
|
|
8079
|
+
continue;
|
|
8080
|
+
}
|
|
8081
|
+
}
|
|
8082
|
+
// Calculate duration if startTime is provided
|
|
8083
|
+
let durationText;
|
|
8084
|
+
if (startDateTime && selectedDate) {
|
|
8085
|
+
const selectedDateObj = dayjs(selectedDate).tz(tz);
|
|
8086
|
+
const optionDateTime = selectedDateObj
|
|
8087
|
+
.hour(hour24)
|
|
8088
|
+
.minute(m)
|
|
8089
|
+
.second(0)
|
|
8090
|
+
.millisecond(0);
|
|
8091
|
+
if (optionDateTime.isValid() &&
|
|
8092
|
+
optionDateTime.isAfter(startDateTime)) {
|
|
8093
|
+
const diffMs = optionDateTime.diff(startDateTime);
|
|
8094
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
8095
|
+
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
8096
|
+
const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
|
|
8097
|
+
if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
|
|
8098
|
+
let diffText = '';
|
|
8099
|
+
if (diffHours > 0) {
|
|
8100
|
+
diffText = `${diffHours}h ${diffMinutes}m`;
|
|
8101
|
+
}
|
|
8102
|
+
else if (diffMinutes > 0) {
|
|
8103
|
+
diffText = `${diffMinutes}m ${diffSeconds}s`;
|
|
8104
|
+
}
|
|
8105
|
+
else {
|
|
8106
|
+
diffText = `${diffSeconds}s`;
|
|
8107
|
+
}
|
|
8108
|
+
durationText = `+${diffText}`;
|
|
8109
|
+
}
|
|
8110
|
+
}
|
|
8111
|
+
}
|
|
8112
|
+
const hourDisplay = h.toString();
|
|
8113
|
+
const minuteDisplay = m.toString().padStart(2, '0');
|
|
8114
|
+
const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
|
|
8115
|
+
options.push({
|
|
8116
|
+
label: timeDisplay,
|
|
8117
|
+
value: `${h}:${m}:${mer}`,
|
|
8118
|
+
hour: h,
|
|
8119
|
+
minute: m,
|
|
8120
|
+
meridiem: mer,
|
|
8121
|
+
searchText: timeDisplay,
|
|
8122
|
+
durationText,
|
|
8123
|
+
});
|
|
7814
8124
|
}
|
|
7815
|
-
timeDataToUse = {
|
|
7816
|
-
hour: 0,
|
|
7817
|
-
minute: 0,
|
|
7818
|
-
second: showSeconds ? 0 : undefined,
|
|
7819
|
-
};
|
|
7820
|
-
}
|
|
7821
|
-
else {
|
|
7822
|
-
setHour12(12);
|
|
7823
|
-
setMinute(0);
|
|
7824
|
-
setMeridiem('am');
|
|
7825
|
-
timeDataToUse = {
|
|
7826
|
-
hour: 12,
|
|
7827
|
-
minute: 0,
|
|
7828
|
-
meridiem: 'am',
|
|
7829
|
-
};
|
|
7830
8125
|
}
|
|
7831
8126
|
}
|
|
8127
|
+
// Sort 12-hour options by time
|
|
8128
|
+
return options.sort((a, b) => {
|
|
8129
|
+
const a12 = a;
|
|
8130
|
+
const b12 = b;
|
|
8131
|
+
let hour24A = a12.hour;
|
|
8132
|
+
if (a12.meridiem === 'am' && a12.hour === 12)
|
|
8133
|
+
hour24A = 0;
|
|
8134
|
+
else if (a12.meridiem === 'pm' && a12.hour < 12)
|
|
8135
|
+
hour24A = a12.hour + 12;
|
|
8136
|
+
let hour24B = b12.hour;
|
|
8137
|
+
if (b12.meridiem === 'am' && b12.hour === 12)
|
|
8138
|
+
hour24B = 0;
|
|
8139
|
+
else if (b12.meridiem === 'pm' && b12.hour < 12)
|
|
8140
|
+
hour24B = b12.hour + 12;
|
|
8141
|
+
if (hour24A !== hour24B) {
|
|
8142
|
+
return hour24A - hour24B;
|
|
8143
|
+
}
|
|
8144
|
+
return a12.minute - b12.minute;
|
|
8145
|
+
});
|
|
7832
8146
|
}
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
|
|
7842
|
-
|
|
8147
|
+
return options;
|
|
8148
|
+
}, [startTime, selectedDate, tz, is24Hour, showSeconds]);
|
|
8149
|
+
// Time picker combobox setup
|
|
8150
|
+
const itemToString = React.useMemo(() => {
|
|
8151
|
+
return (item) => {
|
|
8152
|
+
return item.searchText;
|
|
8153
|
+
};
|
|
8154
|
+
}, []);
|
|
8155
|
+
const { contains } = react.useFilter({ sensitivity: 'base' });
|
|
8156
|
+
const customTimeFilter = React.useMemo(() => {
|
|
8157
|
+
if (is24Hour) {
|
|
8158
|
+
return contains;
|
|
7843
8159
|
}
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
timeData,
|
|
7848
|
-
format,
|
|
7849
|
-
selectedDate,
|
|
7850
|
-
timezone,
|
|
7851
|
-
});
|
|
7852
|
-
if (format === 'iso-date-time') {
|
|
7853
|
-
const data = timeData;
|
|
7854
|
-
console.debug('[DateTimePicker] ISO format - setting 24-hour time:', data);
|
|
7855
|
-
setHour24(data.hour);
|
|
7856
|
-
setMinute(data.minute);
|
|
7857
|
-
if (showSeconds) {
|
|
7858
|
-
setSecond(data.second ?? null);
|
|
8160
|
+
return (itemText, filterText) => {
|
|
8161
|
+
if (!filterText) {
|
|
8162
|
+
return true;
|
|
7859
8163
|
}
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
8164
|
+
const lowerItemText = itemText.toLowerCase();
|
|
8165
|
+
const lowerFilterText = filterText.toLowerCase();
|
|
8166
|
+
if (lowerItemText.includes(lowerFilterText)) {
|
|
8167
|
+
return true;
|
|
8168
|
+
}
|
|
8169
|
+
const item = timeOptions.find((opt) => opt.searchText.toLowerCase() === lowerItemText);
|
|
8170
|
+
if (!item || !('meridiem' in item)) {
|
|
8171
|
+
return false;
|
|
7863
8172
|
}
|
|
8173
|
+
let hour24 = item.hour;
|
|
8174
|
+
if (item.meridiem === 'am' && item.hour === 12)
|
|
8175
|
+
hour24 = 0;
|
|
8176
|
+
else if (item.meridiem === 'pm' && item.hour < 12)
|
|
8177
|
+
hour24 = item.hour + 12;
|
|
8178
|
+
const hour24Str = hour24.toString().padStart(2, '0');
|
|
8179
|
+
const minuteStr = item.minute.toString().padStart(2, '0');
|
|
8180
|
+
const formats = [
|
|
8181
|
+
`${hour24Str}:${minuteStr}`,
|
|
8182
|
+
`${hour24Str}${minuteStr}`,
|
|
8183
|
+
hour24Str,
|
|
8184
|
+
`${hour24}:${minuteStr}`,
|
|
8185
|
+
hour24.toString(),
|
|
8186
|
+
];
|
|
8187
|
+
return formats.some((format) => format.toLowerCase().includes(lowerFilterText) ||
|
|
8188
|
+
lowerFilterText.includes(format.toLowerCase()));
|
|
8189
|
+
};
|
|
8190
|
+
}, [timeOptions, is24Hour, contains]);
|
|
8191
|
+
const { collection, filter } = react.useListCollection({
|
|
8192
|
+
initialItems: timeOptions,
|
|
8193
|
+
itemToString: itemToString,
|
|
8194
|
+
itemToValue: (item) => item.value,
|
|
8195
|
+
filter: customTimeFilter,
|
|
8196
|
+
});
|
|
8197
|
+
// Get current value string for combobox (must match option.value format)
|
|
8198
|
+
const currentTimeValue = React.useMemo(() => {
|
|
8199
|
+
if (is24Hour) {
|
|
8200
|
+
if (hour === null || minute === null) {
|
|
8201
|
+
return '';
|
|
8202
|
+
}
|
|
8203
|
+
const s = second ?? 0;
|
|
8204
|
+
return `${hour}:${minute}:${s}`;
|
|
7864
8205
|
}
|
|
7865
8206
|
else {
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
8207
|
+
if (hour === null || minute === null || meridiem === null) {
|
|
8208
|
+
return '';
|
|
8209
|
+
}
|
|
8210
|
+
return `${hour}:${minute}:${meridiem}`;
|
|
8211
|
+
}
|
|
8212
|
+
}, [hour, minute, second, meridiem, is24Hour]);
|
|
8213
|
+
// Parse custom time input formats like "1400", "2pm", "14:00", "2:00 PM"
|
|
8214
|
+
const parseCustomTimeInput = (input) => {
|
|
8215
|
+
if (!input || !input.trim()) {
|
|
8216
|
+
return { hour: null, minute: null, second: null, meridiem: null };
|
|
8217
|
+
}
|
|
8218
|
+
const trimmed = input.trim().toLowerCase();
|
|
8219
|
+
// Try parsing 4-digit format without colon: "1400" -> 14:00
|
|
8220
|
+
const fourDigitMatch = trimmed.match(/^(\d{4})$/);
|
|
8221
|
+
if (fourDigitMatch) {
|
|
8222
|
+
const digits = fourDigitMatch[1];
|
|
8223
|
+
const hour = parseInt(digits.substring(0, 2), 10);
|
|
8224
|
+
const minute = parseInt(digits.substring(2, 4), 10);
|
|
8225
|
+
if (hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59) {
|
|
8226
|
+
if (is24Hour) {
|
|
8227
|
+
return { hour, minute, second: 0, meridiem: null };
|
|
7881
8228
|
}
|
|
7882
8229
|
else {
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
|
|
7890
|
-
|
|
8230
|
+
// Convert to 12-hour format
|
|
8231
|
+
let hour12 = hour;
|
|
8232
|
+
let meridiem;
|
|
8233
|
+
if (hour === 0) {
|
|
8234
|
+
hour12 = 12;
|
|
8235
|
+
meridiem = 'am';
|
|
8236
|
+
}
|
|
8237
|
+
else if (hour === 12) {
|
|
8238
|
+
hour12 = 12;
|
|
8239
|
+
meridiem = 'pm';
|
|
8240
|
+
}
|
|
8241
|
+
else if (hour > 12) {
|
|
8242
|
+
hour12 = hour - 12;
|
|
8243
|
+
meridiem = 'pm';
|
|
8244
|
+
}
|
|
8245
|
+
else {
|
|
8246
|
+
hour12 = hour;
|
|
8247
|
+
meridiem = 'am';
|
|
8248
|
+
}
|
|
8249
|
+
return { hour: hour12, minute, second: null, meridiem };
|
|
7891
8250
|
}
|
|
7892
|
-
return;
|
|
7893
8251
|
}
|
|
7894
|
-
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
8252
|
+
}
|
|
8253
|
+
// Try parsing hour with meridiem: "2pm", "14pm", "2am"
|
|
8254
|
+
const hourMeridiemMatch = trimmed.match(/^(\d{1,2})\s*(am|pm)$/);
|
|
8255
|
+
if (hourMeridiemMatch && !is24Hour) {
|
|
8256
|
+
const hour12 = parseInt(hourMeridiemMatch[1], 10);
|
|
8257
|
+
const meridiem = hourMeridiemMatch[2];
|
|
8258
|
+
if (hour12 >= 1 && hour12 <= 12) {
|
|
8259
|
+
return { hour: hour12, minute: 0, second: null, meridiem };
|
|
7901
8260
|
}
|
|
7902
8261
|
}
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
8262
|
+
// Try parsing 24-hour format with hour only: "14" -> 14:00
|
|
8263
|
+
const hourOnlyMatch = trimmed.match(/^(\d{1,2})$/);
|
|
8264
|
+
if (hourOnlyMatch && is24Hour) {
|
|
8265
|
+
const hour = parseInt(hourOnlyMatch[1], 10);
|
|
8266
|
+
if (hour >= 0 && hour <= 23) {
|
|
8267
|
+
return { hour, minute: 0, second: 0, meridiem: null };
|
|
8268
|
+
}
|
|
7906
8269
|
}
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
8270
|
+
// Try parsing standard formats: "14:00", "2:00 PM"
|
|
8271
|
+
const time24Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
|
|
8272
|
+
const match24 = trimmed.match(time24Pattern);
|
|
8273
|
+
if (match24) {
|
|
8274
|
+
const hour24 = parseInt(match24[1], 10);
|
|
8275
|
+
const minute = parseInt(match24[2], 10);
|
|
8276
|
+
const second = match24[3] ? parseInt(match24[3], 10) : 0;
|
|
8277
|
+
if (hour24 >= 0 &&
|
|
8278
|
+
hour24 <= 23 &&
|
|
8279
|
+
minute >= 0 &&
|
|
8280
|
+
minute <= 59 &&
|
|
8281
|
+
second >= 0 &&
|
|
8282
|
+
second <= 59) {
|
|
8283
|
+
if (is24Hour) {
|
|
8284
|
+
return { hour: hour24, minute, second, meridiem: null };
|
|
8285
|
+
}
|
|
8286
|
+
else {
|
|
8287
|
+
// Convert to 12-hour format
|
|
8288
|
+
let hour12 = hour24;
|
|
8289
|
+
let meridiem;
|
|
8290
|
+
if (hour24 === 0) {
|
|
8291
|
+
hour12 = 12;
|
|
8292
|
+
meridiem = 'am';
|
|
8293
|
+
}
|
|
8294
|
+
else if (hour24 === 12) {
|
|
8295
|
+
hour12 = 12;
|
|
8296
|
+
meridiem = 'pm';
|
|
8297
|
+
}
|
|
8298
|
+
else if (hour24 > 12) {
|
|
8299
|
+
hour12 = hour24 - 12;
|
|
8300
|
+
meridiem = 'pm';
|
|
8301
|
+
}
|
|
8302
|
+
else {
|
|
8303
|
+
hour12 = hour24;
|
|
8304
|
+
meridiem = 'am';
|
|
8305
|
+
}
|
|
8306
|
+
return { hour: hour12, minute, second: null, meridiem };
|
|
8307
|
+
}
|
|
8308
|
+
}
|
|
7916
8309
|
}
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
setSecond(null);
|
|
7933
|
-
onChange?.(undefined);
|
|
7934
|
-
return;
|
|
8310
|
+
// Try parsing 12-hour format: "2:00 PM", "2:00PM"
|
|
8311
|
+
const time12Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\s*(am|pm)$/;
|
|
8312
|
+
const match12 = trimmed.match(time12Pattern);
|
|
8313
|
+
if (match12 && !is24Hour) {
|
|
8314
|
+
const hour12 = parseInt(match12[1], 10);
|
|
8315
|
+
const minute = parseInt(match12[2], 10);
|
|
8316
|
+
const second = match12[3] ? parseInt(match12[3], 10) : null;
|
|
8317
|
+
const meridiem = match12[4];
|
|
8318
|
+
if (hour12 >= 1 &&
|
|
8319
|
+
hour12 <= 12 &&
|
|
8320
|
+
minute >= 0 &&
|
|
8321
|
+
minute <= 59 &&
|
|
8322
|
+
(second === null || (second >= 0 && second <= 59))) {
|
|
8323
|
+
return { hour: hour12, minute, second, meridiem };
|
|
8324
|
+
}
|
|
7935
8325
|
}
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
setMinute(null);
|
|
7943
|
-
setMeridiem(null);
|
|
7944
|
-
setHour24(null);
|
|
7945
|
-
setSecond(null);
|
|
7946
|
-
onChange?.(undefined);
|
|
8326
|
+
return { hour: null, minute: null, second: null, meridiem: null };
|
|
8327
|
+
};
|
|
8328
|
+
const handleTimeValueChange = (details) => {
|
|
8329
|
+
if (details.value.length === 0) {
|
|
8330
|
+
handleTimeChange(null, null, null, null);
|
|
8331
|
+
filter('');
|
|
7947
8332
|
return;
|
|
7948
8333
|
}
|
|
7949
|
-
const
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
// Always ignore seconds when showSeconds is false - set to 0
|
|
7957
|
-
const s = showSeconds
|
|
7958
|
-
? data !== undefined
|
|
7959
|
-
? data.second ?? null
|
|
7960
|
-
: second ?? 0
|
|
7961
|
-
: 0;
|
|
7962
|
-
// If all time values are null, clear the value
|
|
7963
|
-
if (h === null && m === null && (showSeconds ? s === null : true)) {
|
|
7964
|
-
console.debug('[DateTimePicker] All time values are null, clearing value');
|
|
7965
|
-
onChange?.(undefined);
|
|
7966
|
-
return;
|
|
7967
|
-
}
|
|
7968
|
-
console.debug('[DateTimePicker] ISO format - setting time on date:', {
|
|
7969
|
-
h,
|
|
7970
|
-
m,
|
|
7971
|
-
s,
|
|
7972
|
-
showSeconds,
|
|
7973
|
-
});
|
|
7974
|
-
if (h !== null)
|
|
7975
|
-
newDate.setHours(h);
|
|
7976
|
-
if (m !== null)
|
|
7977
|
-
newDate.setMinutes(m);
|
|
7978
|
-
newDate.setSeconds(s ?? 0);
|
|
7979
|
-
}
|
|
7980
|
-
else {
|
|
7981
|
-
const data = timeData;
|
|
7982
|
-
console.debug('[DateTimePicker] Processing 12-hour format:', {
|
|
7983
|
-
'data !== undefined': data !== undefined,
|
|
7984
|
-
'data?.hour': data?.hour,
|
|
7985
|
-
'data?.minute': data?.minute,
|
|
7986
|
-
'data?.meridiem': data?.meridiem,
|
|
7987
|
-
'current hour12': hour12,
|
|
7988
|
-
'current minute': minute,
|
|
7989
|
-
'current meridiem': meridiem,
|
|
7990
|
-
});
|
|
7991
|
-
// Use timeData values if provided, otherwise fall back to current state
|
|
7992
|
-
const h = data !== undefined ? data.hour : hour12;
|
|
7993
|
-
const m = data !== undefined ? data.minute : minute;
|
|
7994
|
-
const mer = data !== undefined ? data.meridiem : meridiem;
|
|
7995
|
-
console.debug('[DateTimePicker] Resolved time values:', { h, m, mer });
|
|
7996
|
-
// If all time values are null, clear the value
|
|
7997
|
-
if (h === null && m === null && mer === null) {
|
|
7998
|
-
console.debug('[DateTimePicker] All time values are null, clearing value');
|
|
7999
|
-
onChange?.(undefined);
|
|
8000
|
-
return;
|
|
8001
|
-
}
|
|
8002
|
-
console.debug('[DateTimePicker] 12-hour format - converting time:', {
|
|
8003
|
-
h,
|
|
8004
|
-
m,
|
|
8005
|
-
mer,
|
|
8006
|
-
});
|
|
8007
|
-
if (h !== null && mer !== null) {
|
|
8008
|
-
let hour24 = h;
|
|
8009
|
-
if (mer === 'am' && h === 12)
|
|
8010
|
-
hour24 = 0;
|
|
8011
|
-
else if (mer === 'pm' && h < 12)
|
|
8012
|
-
hour24 = h + 12;
|
|
8013
|
-
console.debug('[DateTimePicker] Converted to 24-hour:', {
|
|
8014
|
-
h,
|
|
8015
|
-
mer,
|
|
8016
|
-
hour24,
|
|
8017
|
-
});
|
|
8018
|
-
newDate.setHours(hour24);
|
|
8019
|
-
}
|
|
8020
|
-
else {
|
|
8021
|
-
console.debug('[DateTimePicker] Skipping hour update - h or mer is null:', {
|
|
8022
|
-
h,
|
|
8023
|
-
mer,
|
|
8024
|
-
});
|
|
8025
|
-
}
|
|
8026
|
-
if (m !== null) {
|
|
8027
|
-
newDate.setMinutes(m);
|
|
8334
|
+
const selectedValue = details.value[0];
|
|
8335
|
+
const selectedOption = timeOptions.find((opt) => opt.value === selectedValue);
|
|
8336
|
+
if (selectedOption) {
|
|
8337
|
+
filter('');
|
|
8338
|
+
if (is24Hour) {
|
|
8339
|
+
const opt24 = selectedOption;
|
|
8340
|
+
handleTimeChange(opt24.hour, opt24.minute, opt24.second, null);
|
|
8028
8341
|
}
|
|
8029
8342
|
else {
|
|
8030
|
-
|
|
8343
|
+
const opt12 = selectedOption;
|
|
8344
|
+
handleTimeChange(opt12.hour, opt12.minute, null, opt12.meridiem);
|
|
8031
8345
|
}
|
|
8032
|
-
newDate.setSeconds(0);
|
|
8033
8346
|
}
|
|
8034
|
-
const finalISO = dayjs(newDate).tz(timezone).toISOString();
|
|
8035
|
-
console.debug('[DateTimePicker] Final ISO string to emit:', {
|
|
8036
|
-
newDate: newDate.toISOString(),
|
|
8037
|
-
timezone,
|
|
8038
|
-
finalISO,
|
|
8039
|
-
});
|
|
8040
|
-
onChange?.(finalISO);
|
|
8041
8347
|
};
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
React.useEffect(() => {
|
|
8070
|
-
console.debug('[DateTimePicker] Current state before render:', {
|
|
8071
|
-
isISO,
|
|
8072
|
-
hour12,
|
|
8073
|
-
minute,
|
|
8074
|
-
meridiem,
|
|
8075
|
-
hour24,
|
|
8076
|
-
second,
|
|
8077
|
-
selectedDate,
|
|
8078
|
-
normalizedStartTime,
|
|
8079
|
-
timezone,
|
|
8080
|
-
});
|
|
8081
|
-
}, [
|
|
8082
|
-
isISO,
|
|
8083
|
-
hour12,
|
|
8084
|
-
minute,
|
|
8085
|
-
meridiem,
|
|
8086
|
-
hour24,
|
|
8087
|
-
second,
|
|
8088
|
-
selectedDate,
|
|
8089
|
-
normalizedStartTime,
|
|
8090
|
-
timezone,
|
|
8091
|
-
]);
|
|
8092
|
-
// Compute display text from current state
|
|
8093
|
-
const displayText = React.useMemo(() => {
|
|
8094
|
-
if (!selectedDate)
|
|
8095
|
-
return null;
|
|
8096
|
-
const dateObj = dayjs.tz(selectedDate, timezone);
|
|
8097
|
-
if (!dateObj.isValid())
|
|
8098
|
-
return null;
|
|
8099
|
-
if (isISO) {
|
|
8100
|
-
// For ISO format, use hour24, minute, second
|
|
8101
|
-
if (hour24 === null || minute === null)
|
|
8102
|
-
return null;
|
|
8103
|
-
const dateTimeObj = dateObj
|
|
8104
|
-
.hour(hour24)
|
|
8105
|
-
.minute(minute)
|
|
8106
|
-
.second(second ?? 0);
|
|
8107
|
-
return dateTimeObj.format(showSeconds ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm');
|
|
8108
|
-
}
|
|
8109
|
-
else {
|
|
8110
|
-
// For 12-hour format, use hour12, minute, meridiem
|
|
8111
|
-
if (hour12 === null || minute === null || meridiem === null)
|
|
8112
|
-
return null;
|
|
8113
|
-
// Convert to 24-hour format for dayjs
|
|
8114
|
-
let hour24Value = hour12;
|
|
8115
|
-
if (meridiem === 'am' && hour12 === 12)
|
|
8116
|
-
hour24Value = 0;
|
|
8117
|
-
else if (meridiem === 'pm' && hour12 < 12)
|
|
8118
|
-
hour24Value = hour12 + 12;
|
|
8119
|
-
const dateTimeObj = dateObj.hour(hour24Value).minute(minute).second(0);
|
|
8120
|
-
return dateTimeObj.format('YYYY-MM-DD hh:mm A');
|
|
8348
|
+
// Track the current input value for Enter key handling
|
|
8349
|
+
const [timeInputValue, setTimeInputValue] = React.useState('');
|
|
8350
|
+
const handleTimeInputChange = (details) => {
|
|
8351
|
+
// Store the input value and filter
|
|
8352
|
+
setTimeInputValue(details.inputValue);
|
|
8353
|
+
filter(details.inputValue);
|
|
8354
|
+
};
|
|
8355
|
+
const handleTimeInputKeyDown = (e) => {
|
|
8356
|
+
if (e.key === 'Enter') {
|
|
8357
|
+
e.preventDefault();
|
|
8358
|
+
// Use the stored input value
|
|
8359
|
+
const parsed = parseCustomTimeInput(timeInputValue);
|
|
8360
|
+
if (parsed.hour !== null && parsed.minute !== null) {
|
|
8361
|
+
if (is24Hour) {
|
|
8362
|
+
handleTimeChange(parsed.hour, parsed.minute, parsed.second, null);
|
|
8363
|
+
}
|
|
8364
|
+
else {
|
|
8365
|
+
if (parsed.meridiem !== null) {
|
|
8366
|
+
handleTimeChange(parsed.hour, parsed.minute, null, parsed.meridiem);
|
|
8367
|
+
}
|
|
8368
|
+
}
|
|
8369
|
+
// Clear the filter and input value after applying
|
|
8370
|
+
filter('');
|
|
8371
|
+
setTimeInputValue('');
|
|
8372
|
+
// Close the popover if value is valid
|
|
8373
|
+
setTimePopoverOpen(false);
|
|
8374
|
+
}
|
|
8121
8375
|
}
|
|
8122
|
-
}
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
meridiem,
|
|
8128
|
-
hour24,
|
|
8129
|
-
second,
|
|
8130
|
-
showSeconds,
|
|
8131
|
-
timezone,
|
|
8132
|
-
]);
|
|
8133
|
-
const timezoneOffset = React.useMemo(() => {
|
|
8134
|
-
if (!selectedDate)
|
|
8376
|
+
};
|
|
8377
|
+
// Calendar rendering
|
|
8378
|
+
const renderCalendar = () => {
|
|
8379
|
+
const { calendars, getBackProps, getForwardProps, getDateProps } = calendarProps;
|
|
8380
|
+
if (calendars.length === 0)
|
|
8135
8381
|
return null;
|
|
8136
|
-
const
|
|
8137
|
-
return
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8382
|
+
const calendar = calendars[0];
|
|
8383
|
+
return (jsxRuntime.jsxs(react.Grid, { gap: 4, children: [jsxRuntime.jsxs(react.Grid, { templateColumns: 'repeat(4, auto)', justifyContent: 'center', children: [jsxRuntime.jsx(react.Button, { variant: 'ghost', ...getBackProps({ offset: 12 }), children: '<<' }), jsxRuntime.jsx(react.Button, { variant: 'ghost', ...getBackProps(), children: backButtonLabel }), jsxRuntime.jsx(react.Button, { variant: 'ghost', ...getForwardProps(), children: forwardButtonLabel }), jsxRuntime.jsx(react.Button, { variant: 'ghost', ...getForwardProps({ offset: 12 }), children: '>>' })] }), jsxRuntime.jsx(react.Grid, { justifyContent: 'center', children: jsxRuntime.jsxs(react.Text, { children: [monthNamesShort[calendar.month], " ", calendar.year] }) }), jsxRuntime.jsx(react.Grid, { templateColumns: 'repeat(7, auto)', justifyContent: 'center', children: [0, 1, 2, 3, 4, 5, 6].map((weekdayNum) => {
|
|
8384
|
+
return (jsxRuntime.jsx(react.Text, { textAlign: 'center', fontWeight: "semibold", minW: "40px", children: weekdayNamesShort[weekdayNum] }, `header-${weekdayNum}`));
|
|
8385
|
+
}) }), calendar.weeks.map((week, weekIndex) => (jsxRuntime.jsx(react.Grid, { templateColumns: 'repeat(7, auto)', justifyContent: 'center', children: week.map((dateObj, dayIndex) => {
|
|
8386
|
+
if (!dateObj) {
|
|
8387
|
+
return (jsxRuntime.jsx("div", { style: { minWidth: '40px' } }, `empty-${dayIndex}`));
|
|
8388
|
+
}
|
|
8389
|
+
const { date, selected, selectable, isCurrentMonth } = dateObj;
|
|
8390
|
+
const dateProps = getDateProps({
|
|
8391
|
+
dateObj,
|
|
8392
|
+
});
|
|
8393
|
+
return (jsxRuntime.jsx(react.Button, { variant: selected ? 'solid' : 'ghost', colorPalette: selected ? 'blue' : undefined, size: "sm", minW: "40px", disabled: !selectable, opacity: isCurrentMonth ? 1 : 0.4, ...dateProps, children: date.getDate() }, `${date.getTime()}`));
|
|
8394
|
+
}) }, `week-${weekIndex}`)))] }));
|
|
8395
|
+
};
|
|
8396
|
+
return (jsxRuntime.jsxs(react.Flex, { direction: "row", gap: 2, align: "center", children: [jsxRuntime.jsxs(react.Popover.Root, { open: datePopoverOpen, onOpenChange: (e) => setDatePopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsxs(react.Button, { size: "sm", variant: "outline", onClick: () => setDatePopoverOpen(true), justifyContent: "start", children: [jsxRuntime.jsx(md.MdDateRange, {}), dateDisplayText] }) }), portalled ? (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "350px", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsxs(react.Grid, { gap: 4, children: [jsxRuntime.jsx(react.InputGroup, { endElement: jsxRuntime.jsxs(react.Popover.Root, { open: calendarPopoverOpen, onOpenChange: (e) => setCalendarPopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.Button, { variant: "ghost", size: "xs", "aria-label": "Open calendar", onClick: () => setCalendarPopoverOpen(true), children: jsxRuntime.jsx(md.MdDateRange, {}) }) }), jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "350px", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: renderCalendar() }) }) })] }), children: jsxRuntime.jsx(react.Input, { value: dateInputValue, onChange: handleDateInputChange, onBlur: handleDateInputBlur, onKeyDown: handleDateInputKeyDown, placeholder: "YYYY-MM-DD" }) }), showQuickActions && (jsxRuntime.jsxs(react.Grid, { templateColumns: "repeat(4, 1fr)", gap: 2, children: [jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getYesterday()), disabled: !isDateValid(getYesterday()), children: quickActionLabels.yesterday }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getToday()), disabled: !isDateValid(getToday()), children: quickActionLabels.today }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getTomorrow()), disabled: !isDateValid(getTomorrow()), children: quickActionLabels.tomorrow }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getPlus7Days()), disabled: !isDateValid(getPlus7Days()), children: quickActionLabels.plus7Days })] }))] }) }) }) }) })) : (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsxs(react.Grid, { gap: 4, children: [jsxRuntime.jsx(react.InputGroup, { endElement: jsxRuntime.jsxs(react.Popover.Root, { open: calendarPopoverOpen, onOpenChange: (e) => setCalendarPopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.Button, { variant: "ghost", size: "xs", "aria-label": "Open calendar", onClick: () => setCalendarPopoverOpen(true), children: jsxRuntime.jsx(md.MdDateRange, {}) }) }), jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "350px", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: renderCalendar() }) }) })] }), children: jsxRuntime.jsx(react.Input, { value: dateInputValue, onChange: handleDateInputChange, onBlur: handleDateInputBlur, onKeyDown: handleDateInputKeyDown, placeholder: "YYYY-MM-DD" }) }), showQuickActions && (jsxRuntime.jsxs(react.Grid, { templateColumns: "repeat(4, 1fr)", gap: 2, children: [jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getYesterday()), disabled: !isDateValid(getYesterday()), children: quickActionLabels.yesterday }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getToday()), disabled: !isDateValid(getToday()), children: quickActionLabels.today }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getTomorrow()), disabled: !isDateValid(getTomorrow()), children: quickActionLabels.tomorrow }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getPlus7Days()), disabled: !isDateValid(getPlus7Days()), children: quickActionLabels.plus7Days })] }))] }) }) }) }))] }), jsxRuntime.jsxs(react.Popover.Root, { open: timePopoverOpen, onOpenChange: (e) => setTimePopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsxs(react.Button, { size: "sm", variant: "outline", onClick: () => setTimePopoverOpen(true), justifyContent: "start", children: [jsxRuntime.jsx(bs.BsClock, {}), timeDisplayText] }) }), portalled ? (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "300px", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsx(react.Grid, { gap: 2, children: jsxRuntime.jsxs(react.Combobox.Root, { value: currentTimeValue ? [currentTimeValue] : [], onValueChange: handleTimeValueChange, onInputValueChange: handleTimeInputChange, collection: collection, allowCustomValue: true, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { placeholder: timePickerLabels?.placeholder ??
|
|
8397
|
+
(is24Hour ? 'HH:mm' : 'hh:mm AM/PM'), onKeyDown: handleTimeInputKeyDown }) }), jsxRuntime.jsx(react.Combobox.IndicatorGroup, { children: jsxRuntime.jsx(react.Combobox.Trigger, {}) })] }), jsxRuntime.jsx(react.Portal, { disabled: true, children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [jsxRuntime.jsx(react.Combobox.Empty, { children: timePickerLabels?.emptyMessage ??
|
|
8398
|
+
'No time found' }), collection.items.map((item) => {
|
|
8399
|
+
const option = item;
|
|
8400
|
+
return (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsxs(react.Flex, { justify: "space-between", align: "center", w: "100%", children: [jsxRuntime.jsx(react.Text, { children: option.label }), option.durationText && (jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.500", children: option.durationText }))] }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, option.value));
|
|
8401
|
+
})] }) }) })] }) }) }) }) }) })) : (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "300px", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsx(react.Grid, { gap: 2, children: jsxRuntime.jsxs(react.Combobox.Root, { value: currentTimeValue ? [currentTimeValue] : [], onValueChange: handleTimeValueChange, onInputValueChange: handleTimeInputChange, collection: collection, allowCustomValue: true, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { placeholder: timePickerLabels?.placeholder ??
|
|
8402
|
+
(is24Hour ? 'HH:mm' : 'hh:mm AM/PM'), onKeyDown: handleTimeInputKeyDown }) }), jsxRuntime.jsx(react.Combobox.IndicatorGroup, { children: jsxRuntime.jsx(react.Combobox.Trigger, {}) })] }), jsxRuntime.jsx(react.Portal, { disabled: true, children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [jsxRuntime.jsx(react.Combobox.Empty, { children: timePickerLabels?.emptyMessage ?? 'No time found' }), collection.items.map((item) => {
|
|
8403
|
+
const option = item;
|
|
8404
|
+
return (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsxs(react.Flex, { justify: "space-between", align: "center", w: "100%", children: [jsxRuntime.jsx(react.Text, { children: option.label }), option.durationText && (jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.500", children: option.durationText }))] }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, option.value));
|
|
8405
|
+
})] }) }) })] }) }) }) }) }))] }), showTimezoneSelector && (jsxRuntime.jsxs(react.Popover.Root, { open: timezonePopoverOpen, onOpenChange: (e) => setTimezonePopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => setTimezonePopoverOpen(true), justifyContent: "start", children: timezoneDisplayText || 'Select timezone' }) }), portalled ? (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "250px", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsx(react.Grid, { gap: 2, children: jsxRuntime.jsxs(react.Select.Root, { size: "sm", collection: timezoneCollection, value: timezoneOffset ? [timezoneOffset] : [], onValueChange: (e) => {
|
|
8406
|
+
const newOffset = e.value[0];
|
|
8407
|
+
if (newOffset) {
|
|
8408
|
+
setTimezoneOffset(newOffset);
|
|
8409
|
+
// Update date-time with new offset
|
|
8410
|
+
if (selectedDate &&
|
|
8411
|
+
hour !== null &&
|
|
8412
|
+
minute !== null) {
|
|
8413
|
+
updateDateTime(selectedDate, hour, minute, second, meridiem);
|
|
8414
|
+
}
|
|
8415
|
+
// Close popover after selection
|
|
8416
|
+
setTimezonePopoverOpen(false);
|
|
8417
|
+
}
|
|
8418
|
+
}, children: [jsxRuntime.jsxs(react.Select.Control, { children: [jsxRuntime.jsx(react.Select.Trigger, {}), jsxRuntime.jsx(react.Select.IndicatorGroup, { children: jsxRuntime.jsx(react.Select.Indicator, {}) })] }), jsxRuntime.jsx(react.Select.Positioner, { children: jsxRuntime.jsx(react.Select.Content, { children: timezoneCollection.items.map((item) => (jsxRuntime.jsxs(react.Select.Item, { item: item, children: [jsxRuntime.jsx(react.Select.ItemText, { children: item.label }), jsxRuntime.jsx(react.Select.ItemIndicator, {})] }, item.value))) }) })] }) }) }) }) }) })) : (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "250px", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsx(react.Grid, { gap: 2, children: jsxRuntime.jsxs(react.Select.Root, { size: "sm", collection: timezoneCollection, value: timezoneOffset ? [timezoneOffset] : [], onValueChange: (e) => {
|
|
8419
|
+
const newOffset = e.value[0];
|
|
8420
|
+
if (newOffset) {
|
|
8421
|
+
setTimezoneOffset(newOffset);
|
|
8422
|
+
// Update date-time with new offset (pass it directly to avoid stale state)
|
|
8423
|
+
if (selectedDate &&
|
|
8424
|
+
hour !== null &&
|
|
8425
|
+
minute !== null) {
|
|
8426
|
+
updateDateTime(selectedDate, hour, minute, second, meridiem, newOffset);
|
|
8427
|
+
}
|
|
8428
|
+
// Close popover after selection
|
|
8429
|
+
setTimezonePopoverOpen(false);
|
|
8430
|
+
}
|
|
8431
|
+
}, children: [jsxRuntime.jsxs(react.Select.Control, { children: [jsxRuntime.jsx(react.Select.Trigger, {}), jsxRuntime.jsx(react.Select.IndicatorGroup, { children: jsxRuntime.jsx(react.Select.Indicator, {}) })] }), jsxRuntime.jsx(react.Select.Positioner, { children: jsxRuntime.jsx(react.Select.Content, { children: timezoneCollection.items.map((item) => (jsxRuntime.jsxs(react.Select.Item, { item: item, children: [jsxRuntime.jsx(react.Select.ItemText, { children: item.label }), jsxRuntime.jsx(react.Select.ItemIndicator, {})] }, item.value))) }) })] }) }) }) }) }))] }))] }));
|
|
8148
8432
|
}
|
|
8149
8433
|
|
|
8150
8434
|
dayjs.extend(utc);
|
|
@@ -8158,11 +8442,12 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
|
|
|
8158
8442
|
dateFormat = 'YYYY-MM-DD[T]HH:mm:ssZ', } = schema;
|
|
8159
8443
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
8160
8444
|
const colLabel = formI18n.colLabel;
|
|
8161
|
-
|
|
8445
|
+
React.useState(false);
|
|
8162
8446
|
const selectedDate = watch(colLabel);
|
|
8163
|
-
|
|
8447
|
+
selectedDate && dayjs(selectedDate).tz(timezone).isValid()
|
|
8164
8448
|
? dayjs(selectedDate).tz(timezone).format(displayDateFormat)
|
|
8165
8449
|
: '';
|
|
8450
|
+
// Set default date on mount if no value exists
|
|
8166
8451
|
const dateTimePickerLabelsConfig = {
|
|
8167
8452
|
monthNamesShort: dateTimePickerLabels?.monthNamesShort ?? [
|
|
8168
8453
|
'January',
|
|
@@ -8204,9 +8489,7 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
|
|
|
8204
8489
|
}
|
|
8205
8490
|
}, timezone: timezone, labels: dateTimePickerLabelsConfig, timePickerLabels: timePickerLabels }));
|
|
8206
8491
|
return (jsxRuntime.jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
8207
|
-
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children:
|
|
8208
|
-
setOpen(true);
|
|
8209
|
-
}, justifyContent: 'start', children: [jsxRuntime.jsx(md.MdDateRange, {}), displayDate || ''] }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "350px", minH: "10rem", children: jsxRuntime.jsx(react.Popover.Body, { children: dateTimePickerContent }) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "350px", minH: "10rem", children: jsxRuntime.jsx(react.Popover.Body, { children: dateTimePickerContent }) }) }) }))] }) }));
|
|
8492
|
+
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: dateTimePickerContent }));
|
|
8210
8493
|
};
|
|
8211
8494
|
|
|
8212
8495
|
const SchemaRenderer = ({ schema, prefix, column, }) => {
|