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