@bsol-oss/react-datatable5 13.0.1-beta.7 → 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.d.ts +1 -0
- package/dist/index.js +1528 -1111
- package/dist/index.mjs +1531 -1114
- 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/Form/components/fields/useIdPickerData.d.ts +4 -0
- package/dist/types/components/Form/components/types/CustomJSONSchema7.d.ts +1 -0
- package/dist/types/components/TimePicker/TimePicker.d.ts +39 -10
- package/package.json +7 -4
- package/dist/types/components/DatePicker/IsoTimePicker.d.ts +0 -24
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
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, Skeleton, NumberInput,
|
|
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
|
|
9
|
-
import { BiDownArrow, BiUpArrow, BiError } from 'react-icons/bi';
|
|
8
|
+
import { FaUpDown, FaGripLinesVertical } from 'react-icons/fa6';
|
|
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';
|
|
12
12
|
import { IoMdEye, IoMdCheckbox, IoMdClock } from 'react-icons/io';
|
|
@@ -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
|
};
|
|
@@ -5069,7 +5076,7 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
|
|
|
5069
5076
|
const watchEnum = watch(colLabel);
|
|
5070
5077
|
const watchEnums = (watch(colLabel) ?? []);
|
|
5071
5078
|
const dataList = schema.enum ?? [];
|
|
5072
|
-
// Helper function to render enum value
|
|
5079
|
+
// Helper function to render enum value (returns ReactNode)
|
|
5073
5080
|
// If renderDisplay is provided, use it; otherwise show the enum string value directly
|
|
5074
5081
|
const renderEnumValue = (value) => {
|
|
5075
5082
|
if (renderDisplay) {
|
|
@@ -5078,6 +5085,35 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
|
|
|
5078
5085
|
// If no renderDisplay provided, show the enum string value directly
|
|
5079
5086
|
return value;
|
|
5080
5087
|
};
|
|
5088
|
+
// Helper function to get string representation for input display
|
|
5089
|
+
// Converts ReactNode to string for combobox input display
|
|
5090
|
+
const getDisplayString = (value) => {
|
|
5091
|
+
if (renderDisplay) {
|
|
5092
|
+
const rendered = renderDisplay(value);
|
|
5093
|
+
// If renderDisplay returns a string, use it directly
|
|
5094
|
+
if (typeof rendered === 'string') {
|
|
5095
|
+
return rendered;
|
|
5096
|
+
}
|
|
5097
|
+
// If it's a React element, try to extract text content
|
|
5098
|
+
// For now, fallback to the raw value if we can't extract text
|
|
5099
|
+
// In most cases, renderDisplay should return a string or simple element
|
|
5100
|
+
if (typeof rendered === 'object' &&
|
|
5101
|
+
rendered !== null &&
|
|
5102
|
+
'props' in rendered) {
|
|
5103
|
+
const props = rendered.props;
|
|
5104
|
+
// Try to extract text from React element props
|
|
5105
|
+
if (props?.children) {
|
|
5106
|
+
const children = props.children;
|
|
5107
|
+
if (typeof children === 'string') {
|
|
5108
|
+
return children;
|
|
5109
|
+
}
|
|
5110
|
+
}
|
|
5111
|
+
}
|
|
5112
|
+
// Fallback: use raw value if we can't extract string
|
|
5113
|
+
return value;
|
|
5114
|
+
}
|
|
5115
|
+
return value;
|
|
5116
|
+
};
|
|
5081
5117
|
// Debug log when renderDisplay is missing
|
|
5082
5118
|
if (!renderDisplay) {
|
|
5083
5119
|
console.debug(`[EnumPicker] Missing renderDisplay for field '${colLabel}'. Add renderDisplay function to schema for field '${colLabel}' to provide custom UI rendering. Currently showing enum string values directly.`, {
|
|
@@ -5093,19 +5129,29 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
|
|
|
5093
5129
|
: watchEnum
|
|
5094
5130
|
? [watchEnum]
|
|
5095
5131
|
: [];
|
|
5132
|
+
// Track input focus state for single selection
|
|
5133
|
+
const [isInputFocused, setIsInputFocused] = useState(false);
|
|
5134
|
+
// Get the selected value for single selection display
|
|
5135
|
+
const selectedSingleValue = !isMultiple && watchEnum ? watchEnum : null;
|
|
5136
|
+
const selectedSingleRendered = selectedSingleValue
|
|
5137
|
+
? renderEnumValue(selectedSingleValue)
|
|
5138
|
+
: null;
|
|
5139
|
+
const isSelectedSingleValueString = typeof selectedSingleRendered === 'string';
|
|
5096
5140
|
const comboboxItems = useMemo(() => {
|
|
5097
5141
|
return dataList.map((item) => ({
|
|
5098
5142
|
label: item, // Internal: used for search/filtering only
|
|
5099
5143
|
value: item,
|
|
5100
5144
|
raw: item, // Passed to renderEnumValue for UI rendering
|
|
5145
|
+
displayLabel: getDisplayString(item), // Used for input display when selected
|
|
5101
5146
|
}));
|
|
5102
|
-
}, [dataList]);
|
|
5147
|
+
}, [dataList, renderDisplay]);
|
|
5103
5148
|
// Use filter hook for combobox
|
|
5104
5149
|
const { contains } = useFilter({ sensitivity: 'base' });
|
|
5105
5150
|
// Create collection for combobox
|
|
5151
|
+
// itemToString uses displayLabel to show rendered display in input when selected
|
|
5106
5152
|
const { collection, filter } = useListCollection({
|
|
5107
5153
|
initialItems: comboboxItems,
|
|
5108
|
-
itemToString: (item) => item.
|
|
5154
|
+
itemToString: (item) => item.displayLabel, // Use displayLabel for selected value display
|
|
5109
5155
|
itemToValue: (item) => item.value,
|
|
5110
5156
|
filter: contains,
|
|
5111
5157
|
});
|
|
@@ -5143,7 +5189,26 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
|
|
|
5143
5189
|
}, children: renderEnumValue(enumValue) }, enumValue));
|
|
5144
5190
|
}) })), jsxs(Combobox.Root, { collection: collection, value: currentValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, multiple: isMultiple, closeOnSelect: !isMultiple, openOnClick: true, invalid: !!errors[colLabel], width: "100%", positioning: insideDialog
|
|
5145
5191
|
? { strategy: 'fixed', hideWhenDetached: true }
|
|
5146
|
-
: undefined, children: [jsxs(Combobox.Control, {
|
|
5192
|
+
: undefined, children: [jsxs(Combobox.Control, { position: "relative", children: [!isMultiple &&
|
|
5193
|
+
selectedSingleValue &&
|
|
5194
|
+
!isInputFocused &&
|
|
5195
|
+
!isSelectedSingleValueString &&
|
|
5196
|
+
selectedSingleRendered && (jsx(Box, { position: "absolute", left: 3, top: "50%", transform: "translateY(-50%)", pointerEvents: "none", zIndex: 1, maxWidth: "calc(100% - 60px)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: selectedSingleRendered })), jsx(Combobox.Input, { placeholder: !isMultiple && selectedSingleValue && !isInputFocused
|
|
5197
|
+
? undefined
|
|
5198
|
+
: enumPickerLabels?.typeToSearch ?? 'Type to search', onFocus: () => setIsInputFocused(true), onBlur: () => setIsInputFocused(false), style: {
|
|
5199
|
+
color: !isMultiple &&
|
|
5200
|
+
selectedSingleValue &&
|
|
5201
|
+
!isInputFocused &&
|
|
5202
|
+
!isSelectedSingleValueString
|
|
5203
|
+
? 'transparent'
|
|
5204
|
+
: undefined,
|
|
5205
|
+
caretColor: !isMultiple &&
|
|
5206
|
+
selectedSingleValue &&
|
|
5207
|
+
!isInputFocused &&
|
|
5208
|
+
!isSelectedSingleValueString
|
|
5209
|
+
? 'transparent'
|
|
5210
|
+
: undefined,
|
|
5211
|
+
} }), jsxs(Combobox.IndicatorGroup, { children: [!isMultiple && currentValue.length > 0 && (jsx(Combobox.ClearTrigger, { onClick: () => {
|
|
5147
5212
|
setValue(colLabel, '');
|
|
5148
5213
|
} })), jsx(Combobox.Trigger, {})] })] }), insideDialog ? (jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [showTotalAndLimit && (jsx(Text, { p: 2, fontSize: "sm", color: "fg.muted", children: `${enumPickerLabels?.total ?? 'Total'}: ${collection.items.length}` })), collection.items.length === 0 ? (jsx(Combobox.Empty, { children: enumPickerLabels?.emptySearchResult ?? 'No results found' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: renderEnumValue(item.raw) }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) }))] }) })) : (jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [showTotalAndLimit && (jsx(Text, { p: 2, fontSize: "sm", color: "fg.muted", children: `${enumPickerLabels?.total ?? 'Total'}: ${collection.items.length}` })), collection.items.length === 0 ? (jsx(Combobox.Empty, { children: enumPickerLabels?.emptySearchResult ?? 'No results found' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: renderEnumValue(item.raw) }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) }))] }) }) }))] })] }));
|
|
5149
5214
|
};
|
|
@@ -5887,15 +5952,46 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
|
|
|
5887
5952
|
}) })] }));
|
|
5888
5953
|
};
|
|
5889
5954
|
|
|
5890
|
-
// Default renderDisplay function that
|
|
5955
|
+
// Default renderDisplay function that intelligently displays items
|
|
5956
|
+
// If item is an object, tries to find common display fields (name, title, label, etc.)
|
|
5957
|
+
// Otherwise falls back to JSON.stringify
|
|
5891
5958
|
const defaultRenderDisplay = (item) => {
|
|
5959
|
+
// Check if item is an object (not null, not array, not primitive)
|
|
5960
|
+
if (item !== null &&
|
|
5961
|
+
typeof item === 'object' &&
|
|
5962
|
+
!Array.isArray(item) &&
|
|
5963
|
+
!(item instanceof Date)) {
|
|
5964
|
+
const obj = item;
|
|
5965
|
+
// Try common display fields in order of preference
|
|
5966
|
+
const displayFields = [
|
|
5967
|
+
'name',
|
|
5968
|
+
'title',
|
|
5969
|
+
'label',
|
|
5970
|
+
'displayName',
|
|
5971
|
+
'display_name',
|
|
5972
|
+
'text',
|
|
5973
|
+
'value',
|
|
5974
|
+
];
|
|
5975
|
+
for (const field of displayFields) {
|
|
5976
|
+
if (obj[field] !== undefined && obj[field] !== null) {
|
|
5977
|
+
const value = obj[field];
|
|
5978
|
+
// Return the value if it's a string or number, otherwise stringify it
|
|
5979
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
5980
|
+
return String(value);
|
|
5981
|
+
}
|
|
5982
|
+
}
|
|
5983
|
+
}
|
|
5984
|
+
// If no display field found, fall back to JSON.stringify
|
|
5985
|
+
return JSON.stringify(item);
|
|
5986
|
+
}
|
|
5987
|
+
// For non-objects (primitives, arrays, dates), use JSON.stringify
|
|
5892
5988
|
return JSON.stringify(item);
|
|
5893
5989
|
};
|
|
5894
5990
|
|
|
5895
5991
|
const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
|
|
5896
5992
|
const { watch, getValues, formState: { errors }, setValue, } = useFormContext();
|
|
5897
5993
|
const { idMap, setIdMap, idPickerLabels, insideDialog } = useSchemaContext();
|
|
5898
|
-
const { renderDisplay, loadInitialValues, foreign_key, variant } = schema;
|
|
5994
|
+
const { renderDisplay, itemToValue: schemaItemToValue, loadInitialValues, foreign_key, variant, } = schema;
|
|
5899
5995
|
// loadInitialValues should be provided in schema for id-picker fields
|
|
5900
5996
|
// It's used to load the record of the id so the display is human-readable
|
|
5901
5997
|
if (variant === 'id-picker' && !loadInitialValues) {
|
|
@@ -6028,17 +6124,51 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
|
|
|
6028
6124
|
// Depend on idMapKey which only changes when items we care about change
|
|
6029
6125
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
6030
6126
|
}, [currentValueKey, idMapKey]);
|
|
6127
|
+
// Default itemToValue function: extract value from item using column_ref
|
|
6128
|
+
const defaultItemToValue = (item) => String(item[column_ref]);
|
|
6129
|
+
// Use schema's itemToValue if provided, otherwise use default
|
|
6130
|
+
const itemToValueFn = schemaItemToValue
|
|
6131
|
+
? (item) => schemaItemToValue(item)
|
|
6132
|
+
: defaultItemToValue;
|
|
6133
|
+
// itemToString function: convert item to readable string using renderDisplay
|
|
6134
|
+
// This ensures items can always be displayed as readable strings in the combobox
|
|
6135
|
+
const renderFn = renderDisplay || defaultRenderDisplay;
|
|
6136
|
+
const itemToStringFn = (item) => {
|
|
6137
|
+
const rendered = renderFn(item);
|
|
6138
|
+
// If already a string or number, return it
|
|
6139
|
+
if (typeof rendered === 'string')
|
|
6140
|
+
return rendered;
|
|
6141
|
+
if (typeof rendered === 'number')
|
|
6142
|
+
return String(rendered);
|
|
6143
|
+
// For ReactNode, fall back to defaultRenderDisplay which converts to string
|
|
6144
|
+
return String(defaultRenderDisplay(item));
|
|
6145
|
+
};
|
|
6031
6146
|
// Transform data for combobox collection
|
|
6032
6147
|
// label is used for filtering/searching (must be a string)
|
|
6148
|
+
// displayLabel is used for input display when selected (string representation of rendered display)
|
|
6033
6149
|
// raw item is stored for custom rendering
|
|
6034
6150
|
// Also include items from idMap that match currentValue (for initial values display)
|
|
6035
6151
|
const comboboxItems = useMemo(() => {
|
|
6036
6152
|
const renderFn = renderDisplay || defaultRenderDisplay;
|
|
6153
|
+
// Helper to convert rendered display to string for displayLabel
|
|
6154
|
+
// For ReactNodes (non-string/number), we can't safely stringify due to circular refs
|
|
6155
|
+
// So we use the label (which is already a string) as fallback
|
|
6156
|
+
const getDisplayString = (rendered, fallbackLabel) => {
|
|
6157
|
+
if (typeof rendered === 'string')
|
|
6158
|
+
return rendered;
|
|
6159
|
+
if (typeof rendered === 'number')
|
|
6160
|
+
return String(rendered);
|
|
6161
|
+
// For ReactNode, use the fallback label (which is already a string representation)
|
|
6162
|
+
// The actual ReactNode will be rendered in the overlay, not in the input
|
|
6163
|
+
return fallbackLabel;
|
|
6164
|
+
};
|
|
6037
6165
|
const itemsFromDataList = dataList.map((item) => {
|
|
6038
6166
|
const rendered = renderFn(item);
|
|
6167
|
+
const label = typeof rendered === 'string' ? rendered : JSON.stringify(item); // Use string for filtering
|
|
6039
6168
|
return {
|
|
6040
|
-
label
|
|
6041
|
-
|
|
6169
|
+
label, // Use string for filtering
|
|
6170
|
+
displayLabel: getDisplayString(rendered, label), // String representation for input display
|
|
6171
|
+
value: itemToValueFn(item),
|
|
6042
6172
|
raw: item,
|
|
6043
6173
|
};
|
|
6044
6174
|
});
|
|
@@ -6047,25 +6177,28 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
|
|
|
6047
6177
|
const itemsFromIdMap = idMapItems
|
|
6048
6178
|
.map((item) => {
|
|
6049
6179
|
// Check if this item is already in itemsFromDataList
|
|
6050
|
-
const alreadyIncluded = itemsFromDataList.some((i) => i.value ===
|
|
6180
|
+
const alreadyIncluded = itemsFromDataList.some((i) => i.value === itemToValueFn(item));
|
|
6051
6181
|
if (alreadyIncluded)
|
|
6052
6182
|
return null;
|
|
6053
6183
|
const rendered = renderFn(item);
|
|
6184
|
+
const label = typeof rendered === 'string' ? rendered : JSON.stringify(item);
|
|
6054
6185
|
return {
|
|
6055
|
-
label
|
|
6056
|
-
|
|
6186
|
+
label,
|
|
6187
|
+
displayLabel: getDisplayString(rendered, label), // String representation for input display
|
|
6188
|
+
value: itemToValueFn(item),
|
|
6057
6189
|
raw: item,
|
|
6058
6190
|
};
|
|
6059
6191
|
})
|
|
6060
6192
|
.filter((item) => item !== null);
|
|
6061
6193
|
return [...itemsFromIdMap, ...itemsFromDataList];
|
|
6062
|
-
}, [dataList, column_ref, renderDisplay, idMapItems]);
|
|
6194
|
+
}, [dataList, column_ref, renderDisplay, idMapItems, itemToValueFn]);
|
|
6063
6195
|
// Use filter hook for combobox
|
|
6064
6196
|
const { contains } = useFilter({ sensitivity: 'base' });
|
|
6065
6197
|
// Create collection for combobox
|
|
6198
|
+
// itemToString uses displayLabel to show rendered display in input when selected
|
|
6066
6199
|
const { collection, filter, set } = useListCollection({
|
|
6067
6200
|
initialItems: comboboxItems,
|
|
6068
|
-
itemToString: (item) => item.
|
|
6201
|
+
itemToString: (item) => item.displayLabel, // Use displayLabel for selected value display
|
|
6069
6202
|
itemToValue: (item) => item.value,
|
|
6070
6203
|
filter: contains,
|
|
6071
6204
|
});
|
|
@@ -6120,6 +6253,8 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
|
|
|
6120
6253
|
idPickerLabels,
|
|
6121
6254
|
insideDialog: insideDialog ?? false,
|
|
6122
6255
|
renderDisplay,
|
|
6256
|
+
itemToValue: itemToValueFn,
|
|
6257
|
+
itemToString: itemToStringFn,
|
|
6123
6258
|
loadInitialValues: loadInitialValues ??
|
|
6124
6259
|
(async () => ({ data: { data: [], count: 0 }, idMap: {} })), // Fallback if not provided
|
|
6125
6260
|
column_ref,
|
|
@@ -6130,60 +6265,69 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
|
|
|
6130
6265
|
|
|
6131
6266
|
const IdPickerSingle = ({ column, schema, prefix, }) => {
|
|
6132
6267
|
const formI18n = useFormI18n(column, prefix, schema);
|
|
6133
|
-
const { required, gridColumn = 'span 12', gridRow = 'span 1'
|
|
6268
|
+
const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
|
|
6134
6269
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
6135
|
-
const { colLabel, currentValue, searchText, setSearchText, isLoading, isFetching, isPending, isError, isSearching,
|
|
6270
|
+
const { colLabel, currentValue, searchText, setSearchText, isLoading, isFetching, isPending, isError, isSearching, collection, filter, idMap, idPickerLabels, insideDialog, renderDisplay: renderDisplayFn, itemToValue, itemToString, errors, setValue, } = useIdPickerData({
|
|
6136
6271
|
column,
|
|
6137
6272
|
schema,
|
|
6138
6273
|
prefix,
|
|
6139
6274
|
isMultiple: false,
|
|
6140
6275
|
});
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
const
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6276
|
+
// Get the selected value for single selection display
|
|
6277
|
+
const selectedId = currentValue.length > 0 ? currentValue[0] : null;
|
|
6278
|
+
const selectedItem = selectedId
|
|
6279
|
+
? idMap[selectedId]
|
|
6280
|
+
: undefined;
|
|
6281
|
+
// Use itemToValue to get the combobox value from the selected item, or use the ID directly
|
|
6282
|
+
const comboboxValue = selectedItem
|
|
6283
|
+
? itemToString(selectedItem)
|
|
6284
|
+
: selectedId || '';
|
|
6285
|
+
// itemToString is available from the hook and can be used to get a readable string
|
|
6286
|
+
// representation of any item. The collection's itemToString is automatically used
|
|
6287
|
+
// by the combobox to display selected values.
|
|
6288
|
+
// Use useCombobox hook to control input value
|
|
6289
|
+
const combobox = useCombobox({
|
|
6290
|
+
collection,
|
|
6291
|
+
value: [comboboxValue],
|
|
6292
|
+
onInputValueChange(e) {
|
|
6293
|
+
setSearchText(e.inputValue);
|
|
6294
|
+
filter(e.inputValue);
|
|
6295
|
+
},
|
|
6296
|
+
onValueChange(e) {
|
|
6297
|
+
setValue(colLabel, e.value[0] || '');
|
|
6298
|
+
// Clear the input value after selection
|
|
6299
|
+
setSearchText('');
|
|
6300
|
+
},
|
|
6301
|
+
multiple: false,
|
|
6302
|
+
closeOnSelect: true,
|
|
6303
|
+
openOnClick: true,
|
|
6304
|
+
invalid: !!errors[colLabel],
|
|
6305
|
+
});
|
|
6306
|
+
// Use renderDisplay from hook (which comes from schema) or fallback to default
|
|
6307
|
+
const renderDisplayFunction = renderDisplayFn || defaultRenderDisplay;
|
|
6308
|
+
// Get the selected value for single selection display (already computed above)
|
|
6309
|
+
const selectedRendered = selectedItem
|
|
6310
|
+
? renderDisplayFunction(selectedItem)
|
|
6311
|
+
: null;
|
|
6312
|
+
return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
6313
|
+
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(Combobox.RootProvider, { value: combobox, width: "100%", children: [jsx(Show, { when: selectedId && selectedRendered, children: jsxs(HStack, { justifyContent: 'space-between', children: [jsx(Box, { children: selectedRendered }), currentValue.length > 0 && (jsx(Button$1, { variant: "ghost", size: "sm", onClick: () => {
|
|
6314
|
+
setValue(colLabel, '');
|
|
6315
|
+
}, children: jsx(Icon, { children: jsx(BiX, {}) }) }))] }) }), jsx(Show, { when: !selectedId || !selectedRendered, children: jsxs(Combobox.Control, { position: "relative", children: [jsx(Combobox.Input, { placeholder: idPickerLabels?.typeToSearch ?? 'Type to search' }), jsxs(Combobox.IndicatorGroup, { children: [(isFetching || isLoading || isPending) && jsx(Spinner, { size: "xs" }), isError && (jsx(Icon, { color: "fg.error", children: jsx(BiError, {}) })), jsx(Combobox.Trigger, {})] })] }) }), insideDialog ? (jsx(Combobox.Positioner, { children: jsx(Combobox.Content, { children: isError ? (jsx(Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
|
|
6316
|
+
// Show skeleton items to prevent UI shift
|
|
6317
|
+
jsx(Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsx(Flex, { p: 2, align: "center", gap: 2, children: jsx(Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsx(Combobox.Empty, { children: searchText
|
|
6318
|
+
? idPickerLabels?.emptySearchResult ?? 'No results found'
|
|
6319
|
+
: idPickerLabels?.initialResults ??
|
|
6320
|
+
'Start typing to search' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [renderDisplayFunction(item.raw), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsx(Combobox.Content, { children: isError ? (jsx(Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
|
|
6168
6321
|
// Show skeleton items to prevent UI shift
|
|
6169
6322
|
jsx(Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsx(Flex, { p: 2, align: "center", gap: 2, children: jsx(Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsx(Combobox.Empty, { children: searchText
|
|
6170
6323
|
? idPickerLabels?.emptySearchResult ?? 'No results found'
|
|
6171
6324
|
: idPickerLabels?.initialResults ??
|
|
6172
|
-
'Start typing to search' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (
|
|
6173
|
-
? renderDisplayFunction(item.raw)
|
|
6174
|
-
: item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsx(Combobox.Content, { children: isError ? (jsx(Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
|
|
6175
|
-
// Show skeleton items to prevent UI shift
|
|
6176
|
-
jsx(Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsx(Flex, { p: 2, align: "center", gap: 2, children: jsx(Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsx(Combobox.Empty, { children: searchText
|
|
6177
|
-
? idPickerLabels?.emptySearchResult ?? 'No results found'
|
|
6178
|
-
: idPickerLabels?.initialResults ??
|
|
6179
|
-
'Start typing to search' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: !!renderDisplayFunction === true
|
|
6180
|
-
? renderDisplayFunction(item.raw)
|
|
6181
|
-
: item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
|
|
6325
|
+
'Start typing to search' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsx(Combobox.Item, { item: item, children: renderDisplayFunction(item.raw) }, item.value ?? `item-${index}`))) })) }) }) }))] }) }));
|
|
6182
6326
|
};
|
|
6183
6327
|
|
|
6184
6328
|
const IdPickerMultiple = ({ column, schema, prefix, }) => {
|
|
6185
6329
|
const formI18n = useFormI18n(column, prefix, schema);
|
|
6186
|
-
const { required, gridColumn = 'span 12', gridRow = 'span 1'
|
|
6330
|
+
const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
|
|
6187
6331
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
6188
6332
|
const { colLabel, currentValue, searchText, setSearchText, isLoading, isFetching, isPending, isError, isSearching, isLoadingInitialValues, isFetchingInitialValues, missingIds, collection, idMap, idPickerLabels, insideDialog, renderDisplay: renderDisplayFn, errors, setValue, } = useIdPickerData({
|
|
6189
6333
|
column,
|
|
@@ -6197,7 +6341,8 @@ const IdPickerMultiple = ({ column, schema, prefix, }) => {
|
|
|
6197
6341
|
const handleValueChange = (details) => {
|
|
6198
6342
|
setValue(colLabel, details.value);
|
|
6199
6343
|
};
|
|
6200
|
-
|
|
6344
|
+
// Use renderDisplay from hook (which comes from schema) or fallback to default
|
|
6345
|
+
const renderDisplayFunction = renderDisplayFn || defaultRenderDisplay;
|
|
6201
6346
|
return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
6202
6347
|
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [currentValue.length > 0 && (jsx(Flex, { flexFlow: 'wrap', gap: 1, mb: 2, children: currentValue.map((id) => {
|
|
6203
6348
|
const item = idMap[id];
|
|
@@ -6222,16 +6367,12 @@ const IdPickerMultiple = ({ column, schema, prefix, }) => {
|
|
|
6222
6367
|
jsx(Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsx(Flex, { p: 2, align: "center", gap: 2, children: jsx(Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsx(Combobox.Empty, { children: searchText
|
|
6223
6368
|
? idPickerLabels?.emptySearchResult ?? 'No results found'
|
|
6224
6369
|
: idPickerLabels?.initialResults ??
|
|
6225
|
-
'Start typing to search' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.
|
|
6226
|
-
? renderDisplayFunction(item.raw)
|
|
6227
|
-
: item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsx(Combobox.Content, { children: isError ? (jsx(Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
|
|
6370
|
+
'Start typing to search' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [renderDisplayFunction(item.raw), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsx(Combobox.Content, { children: isError ? (jsx(Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
|
|
6228
6371
|
// Show skeleton items to prevent UI shift
|
|
6229
6372
|
jsx(Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsx(Flex, { p: 2, align: "center", gap: 2, children: jsx(Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsx(Combobox.Empty, { children: searchText
|
|
6230
6373
|
? idPickerLabels?.emptySearchResult ?? 'No results found'
|
|
6231
6374
|
: idPickerLabels?.initialResults ??
|
|
6232
|
-
'Start typing to search' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.
|
|
6233
|
-
? renderDisplayFunction(item.raw)
|
|
6234
|
-
: item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
|
|
6375
|
+
'Start typing to search' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [renderDisplayFunction(item.raw), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
|
|
6235
6376
|
};
|
|
6236
6377
|
|
|
6237
6378
|
const NumberInputRoot = React.forwardRef(function NumberInput$1(props, ref) {
|
|
@@ -6608,11 +6749,193 @@ const TextAreaInput = ({ column, schema, prefix, }) => {
|
|
|
6608
6749
|
|
|
6609
6750
|
dayjs.extend(utc);
|
|
6610
6751
|
dayjs.extend(timezone);
|
|
6611
|
-
const TimePicker$1 = (
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
|
|
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
|
|
6616
6939
|
const timeOptions = useMemo(() => {
|
|
6617
6940
|
const options = [];
|
|
6618
6941
|
// Get start time for comparison if provided
|
|
@@ -6623,32 +6946,25 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6623
6946
|
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
6624
6947
|
if (startDateObj.isValid() && selectedDateObj.isValid()) {
|
|
6625
6948
|
startDateTime = startDateObj;
|
|
6626
|
-
// Only filter if dates are the same
|
|
6627
6949
|
shouldFilterByDate =
|
|
6628
6950
|
startDateObj.format('YYYY-MM-DD') ===
|
|
6629
6951
|
selectedDateObj.format('YYYY-MM-DD');
|
|
6630
6952
|
}
|
|
6631
6953
|
}
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
for (let
|
|
6635
|
-
for (
|
|
6636
|
-
//
|
|
6637
|
-
let hour24 = h;
|
|
6638
|
-
if (mer === 'am' && h === 12)
|
|
6639
|
-
hour24 = 0;
|
|
6640
|
-
else if (mer === 'pm' && h < 12)
|
|
6641
|
-
hour24 = h + 12;
|
|
6642
|
-
// 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
|
|
6643
6959
|
if (startDateTime && selectedDate && shouldFilterByDate) {
|
|
6644
6960
|
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
6645
6961
|
const optionDateTime = selectedDateObj
|
|
6646
|
-
.hour(
|
|
6962
|
+
.hour(h)
|
|
6647
6963
|
.minute(m)
|
|
6648
6964
|
.second(0)
|
|
6649
6965
|
.millisecond(0);
|
|
6650
6966
|
if (optionDateTime.isBefore(startDateTime)) {
|
|
6651
|
-
continue;
|
|
6967
|
+
continue;
|
|
6652
6968
|
}
|
|
6653
6969
|
}
|
|
6654
6970
|
// Calculate duration if startTime is provided
|
|
@@ -6656,7 +6972,7 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6656
6972
|
if (startDateTime && selectedDate) {
|
|
6657
6973
|
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
6658
6974
|
const optionDateTime = selectedDateObj
|
|
6659
|
-
.hour(
|
|
6975
|
+
.hour(h)
|
|
6660
6976
|
.minute(m)
|
|
6661
6977
|
.second(0)
|
|
6662
6978
|
.millisecond(0);
|
|
@@ -6681,62 +6997,133 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6681
6997
|
}
|
|
6682
6998
|
}
|
|
6683
6999
|
}
|
|
6684
|
-
const
|
|
6685
|
-
const minuteDisplay = m.toString().padStart(2, '0');
|
|
6686
|
-
const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
|
|
7000
|
+
const timeDisplay = `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:00`;
|
|
6687
7001
|
options.push({
|
|
6688
7002
|
label: timeDisplay,
|
|
6689
|
-
value: `${h}:${m}
|
|
7003
|
+
value: `${h}:${m}:0`,
|
|
6690
7004
|
hour: h,
|
|
6691
7005
|
minute: m,
|
|
6692
|
-
|
|
6693
|
-
searchText: timeDisplay,
|
|
7006
|
+
second: 0,
|
|
7007
|
+
searchText: timeDisplay,
|
|
6694
7008
|
durationText,
|
|
6695
7009
|
});
|
|
6696
7010
|
}
|
|
6697
7011
|
}
|
|
6698
7012
|
}
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
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]);
|
|
6719
7103
|
// itemToString returns only the clean display text (no metadata)
|
|
6720
7104
|
const itemToString = useMemo(() => {
|
|
6721
7105
|
return (item) => {
|
|
6722
|
-
return item.searchText;
|
|
7106
|
+
return item.searchText;
|
|
6723
7107
|
};
|
|
6724
7108
|
}, []);
|
|
6725
|
-
// Custom filter function
|
|
7109
|
+
// Custom filter function
|
|
7110
|
+
const { contains } = useFilter({ sensitivity: 'base' });
|
|
6726
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
|
|
6727
7116
|
return (itemText, filterText) => {
|
|
6728
7117
|
if (!filterText) {
|
|
6729
|
-
return true;
|
|
7118
|
+
return true;
|
|
6730
7119
|
}
|
|
6731
7120
|
const lowerItemText = itemText.toLowerCase();
|
|
6732
7121
|
const lowerFilterText = filterText.toLowerCase();
|
|
6733
|
-
// First, try matching against the display text (12-hour format)
|
|
6734
7122
|
if (lowerItemText.includes(lowerFilterText)) {
|
|
6735
7123
|
return true;
|
|
6736
7124
|
}
|
|
6737
|
-
// Find the corresponding item to check 24-hour format matches
|
|
6738
7125
|
const item = timeOptions.find((opt) => opt.searchText.toLowerCase() === lowerItemText);
|
|
6739
|
-
if (!item) {
|
|
7126
|
+
if (!item || !('meridiem' in item)) {
|
|
6740
7127
|
return false;
|
|
6741
7128
|
}
|
|
6742
7129
|
// Convert item to 24-hour format for matching
|
|
@@ -6747,18 +7134,17 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6747
7134
|
hour24 = item.hour + 12;
|
|
6748
7135
|
const hour24Str = hour24.toString().padStart(2, '0');
|
|
6749
7136
|
const minuteStr = item.minute.toString().padStart(2, '0');
|
|
6750
|
-
// Check if filterText matches 24-hour format variations
|
|
6751
7137
|
const formats = [
|
|
6752
|
-
`${hour24Str}:${minuteStr}`,
|
|
6753
|
-
`${hour24Str}${minuteStr}`,
|
|
6754
|
-
hour24Str,
|
|
6755
|
-
`${hour24}:${minuteStr}`,
|
|
6756
|
-
hour24.toString(),
|
|
7138
|
+
`${hour24Str}:${minuteStr}`,
|
|
7139
|
+
`${hour24Str}${minuteStr}`,
|
|
7140
|
+
hour24Str,
|
|
7141
|
+
`${hour24}:${minuteStr}`,
|
|
7142
|
+
hour24.toString(),
|
|
6757
7143
|
];
|
|
6758
7144
|
return formats.some((format) => format.toLowerCase().includes(lowerFilterText) ||
|
|
6759
7145
|
lowerFilterText.includes(format.toLowerCase()));
|
|
6760
7146
|
};
|
|
6761
|
-
}, [timeOptions]);
|
|
7147
|
+
}, [timeOptions, is24Hour, contains]);
|
|
6762
7148
|
const { collection, filter } = useListCollection({
|
|
6763
7149
|
initialItems: timeOptions,
|
|
6764
7150
|
itemToString: itemToString,
|
|
@@ -6767,32 +7153,48 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6767
7153
|
});
|
|
6768
7154
|
// Get current value string for combobox
|
|
6769
7155
|
const currentValue = useMemo(() => {
|
|
6770
|
-
if (
|
|
6771
|
-
|
|
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}`;
|
|
6772
7167
|
}
|
|
6773
|
-
|
|
6774
|
-
}, [hour, minute, meridiem]);
|
|
7168
|
+
}, [hour, minute, second, meridiem, is24Hour]);
|
|
6775
7169
|
// Calculate duration difference
|
|
6776
7170
|
const durationDiff = useMemo(() => {
|
|
6777
|
-
if (!startTime ||
|
|
6778
|
-
!selectedDate ||
|
|
6779
|
-
hour === null ||
|
|
6780
|
-
minute === null ||
|
|
6781
|
-
meridiem === null) {
|
|
7171
|
+
if (!startTime || !selectedDate || hour === null || minute === null) {
|
|
6782
7172
|
return null;
|
|
6783
7173
|
}
|
|
7174
|
+
if (is24Hour) {
|
|
7175
|
+
if (second === null)
|
|
7176
|
+
return null;
|
|
7177
|
+
}
|
|
7178
|
+
else {
|
|
7179
|
+
if (meridiem === null)
|
|
7180
|
+
return null;
|
|
7181
|
+
}
|
|
6784
7182
|
const startDateObj = dayjs(startTime).tz(timezone);
|
|
6785
7183
|
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
6786
|
-
// Convert
|
|
7184
|
+
// Convert to 24-hour format
|
|
6787
7185
|
let hour24 = hour;
|
|
6788
|
-
if (
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
|
|
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
|
+
}
|
|
6792
7192
|
const currentDateTime = selectedDateObj
|
|
6793
7193
|
.hour(hour24)
|
|
6794
7194
|
.minute(minute)
|
|
6795
|
-
.second(
|
|
7195
|
+
.second(is24Hour && second !== null && second !== undefined
|
|
7196
|
+
? second
|
|
7197
|
+
: 0)
|
|
6796
7198
|
.millisecond(0);
|
|
6797
7199
|
if (!startDateObj.isValid() || !currentDateTime.isValid()) {
|
|
6798
7200
|
return null;
|
|
@@ -6818,13 +7220,28 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6818
7220
|
return `+${diffText}`;
|
|
6819
7221
|
}
|
|
6820
7222
|
return null;
|
|
6821
|
-
}, [
|
|
7223
|
+
}, [
|
|
7224
|
+
hour,
|
|
7225
|
+
minute,
|
|
7226
|
+
second,
|
|
7227
|
+
meridiem,
|
|
7228
|
+
startTime,
|
|
7229
|
+
selectedDate,
|
|
7230
|
+
timezone,
|
|
7231
|
+
is24Hour,
|
|
7232
|
+
]);
|
|
6822
7233
|
const handleClear = () => {
|
|
6823
7234
|
setHour(null);
|
|
6824
7235
|
setMinute(null);
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
|
|
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('');
|
|
6828
7245
|
};
|
|
6829
7246
|
const handleValueChange = (details) => {
|
|
6830
7247
|
if (details.value.length === 0) {
|
|
@@ -6836,112 +7253,165 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6836
7253
|
if (selectedOption) {
|
|
6837
7254
|
setHour(selectedOption.hour);
|
|
6838
7255
|
setMinute(selectedOption.minute);
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
}
|
|
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
|
+
}
|
|
6846
7269
|
}
|
|
6847
7270
|
};
|
|
6848
7271
|
// Parse input value and update state
|
|
6849
7272
|
const parseAndCommitInput = (value) => {
|
|
6850
7273
|
const trimmedValue = value.trim();
|
|
6851
|
-
// Filter the collection based on input
|
|
6852
7274
|
filter(trimmedValue);
|
|
6853
7275
|
if (!trimmedValue) {
|
|
6854
7276
|
return;
|
|
6855
7277
|
}
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
meridiem = 'pm';
|
|
6877
|
-
}
|
|
6878
|
-
else if (parsedHour24 > 12) {
|
|
6879
|
-
hour12 = parsedHour24 - 12;
|
|
6880
|
-
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;
|
|
6881
7298
|
}
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
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;
|
|
6885
7318
|
}
|
|
6886
|
-
setHour(hour12);
|
|
6887
|
-
setMinute(parsedMinute);
|
|
6888
|
-
setMeridiem(meridiem);
|
|
6889
|
-
onChange({
|
|
6890
|
-
hour: hour12,
|
|
6891
|
-
minute: parsedMinute,
|
|
6892
|
-
meridiem: meridiem,
|
|
6893
|
-
});
|
|
6894
|
-
return;
|
|
6895
7319
|
}
|
|
6896
7320
|
}
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
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
|
+
}
|
|
6918
7358
|
}
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
if (numbersOnly.length >= 3) {
|
|
6927
|
-
const parsedHour = parseInt(numbersOnly.slice(0, -2), 10);
|
|
6928
|
-
const parsedMinute = parseInt(numbersOnly.slice(-2), 10);
|
|
6929
|
-
// 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();
|
|
6930
7366
|
if (parsedHour >= 1 &&
|
|
6931
7367
|
parsedHour <= 12 &&
|
|
6932
7368
|
parsedMinute >= 0 &&
|
|
6933
7369
|
parsedMinute <= 59) {
|
|
6934
7370
|
setHour(parsedHour);
|
|
6935
7371
|
setMinute(parsedMinute);
|
|
6936
|
-
setMeridiem
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
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);
|
|
6942
7390
|
return;
|
|
6943
7391
|
}
|
|
6944
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
|
+
}
|
|
6945
7415
|
}
|
|
6946
7416
|
// Parse failed, select first result
|
|
6947
7417
|
selectFirstResult();
|
|
@@ -6952,55 +7422,84 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6952
7422
|
const firstItem = collection.items[0];
|
|
6953
7423
|
setHour(firstItem.hour);
|
|
6954
7424
|
setMinute(firstItem.minute);
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
}
|
|
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
|
+
}
|
|
6962
7438
|
}
|
|
6963
7439
|
};
|
|
6964
7440
|
const handleInputValueChange = (details) => {
|
|
6965
|
-
|
|
7441
|
+
if (is24Hour) {
|
|
7442
|
+
setInputValue(details.inputValue);
|
|
7443
|
+
}
|
|
6966
7444
|
filter(details.inputValue);
|
|
6967
7445
|
};
|
|
6968
7446
|
const handleFocus = (e) => {
|
|
6969
|
-
// Select all text when focusing
|
|
6970
7447
|
e.target.select();
|
|
6971
7448
|
};
|
|
6972
7449
|
const handleBlur = (e) => {
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
7450
|
+
const inputVal = e.target.value;
|
|
7451
|
+
if (is24Hour) {
|
|
7452
|
+
setInputValue(inputVal);
|
|
7453
|
+
}
|
|
7454
|
+
if (inputVal) {
|
|
7455
|
+
parseAndCommitInput(inputVal);
|
|
6977
7456
|
}
|
|
6978
7457
|
};
|
|
6979
7458
|
const handleKeyDown = (e) => {
|
|
6980
|
-
// Commit input on Enter key
|
|
6981
7459
|
if (e.key === 'Enter') {
|
|
6982
7460
|
e.preventDefault();
|
|
6983
|
-
const
|
|
6984
|
-
if (
|
|
6985
|
-
|
|
7461
|
+
const inputVal = e.currentTarget.value;
|
|
7462
|
+
if (is24Hour) {
|
|
7463
|
+
setInputValue(inputVal);
|
|
7464
|
+
}
|
|
7465
|
+
if (inputVal) {
|
|
7466
|
+
parseAndCommitInput(inputVal);
|
|
6986
7467
|
}
|
|
6987
|
-
// Blur the input
|
|
6988
7468
|
e.currentTarget?.blur();
|
|
6989
7469
|
}
|
|
6990
7470
|
};
|
|
6991
|
-
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 }) }))] }) }));
|
|
6992
7472
|
};
|
|
6993
7473
|
|
|
6994
7474
|
dayjs.extend(timezone);
|
|
6995
7475
|
const TimePicker = ({ column, schema, prefix }) => {
|
|
6996
7476
|
const { watch, formState: { errors }, setValue, } = useFormContext();
|
|
6997
7477
|
const { timezone, insideDialog, timePickerLabels } = useSchemaContext();
|
|
6998
|
-
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;
|
|
6999
7479
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
7000
7480
|
const colLabel = `${prefix}${column}`;
|
|
7001
7481
|
const formI18n = useFormI18n(column, prefix, schema);
|
|
7002
7482
|
const [open, setOpen] = useState(false);
|
|
7003
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;
|
|
7004
7503
|
const displayedTime = dayjs(`1970-01-01T${value}`).tz(timezone).isValid()
|
|
7005
7504
|
? dayjs(`1970-01-01T${value}`).tz(timezone).format(displayTimeFormat)
|
|
7006
7505
|
: '';
|
|
@@ -7056,941 +7555,860 @@ const TimePicker = ({ column, schema, prefix }) => {
|
|
|
7056
7555
|
return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
7057
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: () => {
|
|
7058
7557
|
setOpen(true);
|
|
7059
|
-
}, 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 }) }) }) }) }))] }) }));
|
|
7060
7559
|
};
|
|
7061
7560
|
|
|
7062
7561
|
dayjs.extend(utc);
|
|
7063
7562
|
dayjs.extend(timezone);
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
7108
|
-
const optionDateTime = selectedDateObj
|
|
7109
|
-
.hour(h)
|
|
7110
|
-
.minute(m)
|
|
7111
|
-
.second(0)
|
|
7112
|
-
.millisecond(0);
|
|
7113
|
-
if (optionDateTime.isValid() &&
|
|
7114
|
-
optionDateTime.isAfter(startDateTime)) {
|
|
7115
|
-
const diffMs = optionDateTime.diff(startDateTime);
|
|
7116
|
-
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
7117
|
-
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
7118
|
-
const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
|
|
7119
|
-
if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
|
|
7120
|
-
let diffText = '';
|
|
7121
|
-
if (diffHours > 0) {
|
|
7122
|
-
diffText = `${diffHours}h ${diffMinutes}m`;
|
|
7123
|
-
}
|
|
7124
|
-
else if (diffMinutes > 0) {
|
|
7125
|
-
diffText = `${diffMinutes}m ${diffSeconds}s`;
|
|
7126
|
-
}
|
|
7127
|
-
else {
|
|
7128
|
-
diffText = `${diffSeconds}s`;
|
|
7129
|
-
}
|
|
7130
|
-
durationText = `+${diffText}`;
|
|
7131
|
-
}
|
|
7132
|
-
}
|
|
7133
|
-
}
|
|
7134
|
-
options.push({
|
|
7135
|
-
label: timeDisplay,
|
|
7136
|
-
value: `${h}:${m}:0`,
|
|
7137
|
-
hour: h,
|
|
7138
|
-
minute: m,
|
|
7139
|
-
second: 0,
|
|
7140
|
-
searchText: timeDisplay, // Use base time without duration for searching
|
|
7141
|
-
durationText,
|
|
7142
|
-
});
|
|
7143
|
-
}
|
|
7144
|
-
}
|
|
7145
|
-
return options;
|
|
7146
|
-
}, [startTime, selectedDate, timezone]);
|
|
7147
|
-
const { contains } = useFilter({ sensitivity: 'base' });
|
|
7148
|
-
const { collection, filter } = useListCollection({
|
|
7149
|
-
initialItems: timeOptions,
|
|
7150
|
-
itemToString: (item) => item.searchText, // Use searchText (without duration) for filtering
|
|
7151
|
-
itemToValue: (item) => item.value,
|
|
7152
|
-
filter: contains,
|
|
7153
|
-
});
|
|
7154
|
-
// Get current value string for combobox
|
|
7155
|
-
const currentValue = useMemo(() => {
|
|
7156
|
-
if (hour === null || minute === null || second === null) {
|
|
7157
|
-
return '';
|
|
7158
|
-
}
|
|
7159
|
-
return `${hour}:${minute}:${second}`;
|
|
7160
|
-
}, [hour, minute, second]);
|
|
7161
|
-
// Calculate duration difference
|
|
7162
|
-
const durationDiff = useMemo(() => {
|
|
7163
|
-
if (!startTime ||
|
|
7164
|
-
!selectedDate ||
|
|
7165
|
-
hour === null ||
|
|
7166
|
-
minute === null ||
|
|
7167
|
-
second === null) {
|
|
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)
|
|
7168
7606
|
return null;
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
7172
|
-
const currentDateTime = selectedDateObj
|
|
7173
|
-
.hour(hour)
|
|
7174
|
-
.minute(minute)
|
|
7175
|
-
.second(second ?? 0)
|
|
7176
|
-
.millisecond(0);
|
|
7177
|
-
if (!startDateObj.isValid() || !currentDateTime.isValid()) {
|
|
7607
|
+
const dateObj = dayjs(value).tz(tz);
|
|
7608
|
+
if (!dateObj.isValid())
|
|
7178
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();
|
|
7179
7616
|
}
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
return
|
|
7617
|
+
if (defaultDate) {
|
|
7618
|
+
const defaultDateObj = dayjs(defaultDate).tz(tz);
|
|
7619
|
+
return defaultDateObj.isValid() ? defaultDateObj.toDate() : new Date();
|
|
7183
7620
|
}
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
diffText = `${diffMinutes}m ${diffSeconds}s`;
|
|
7194
|
-
}
|
|
7195
|
-
else {
|
|
7196
|
-
diffText = `${diffSeconds}s`;
|
|
7197
|
-
}
|
|
7198
|
-
return `+${diffText}`;
|
|
7621
|
+
return new Date();
|
|
7622
|
+
});
|
|
7623
|
+
// Initialize time state
|
|
7624
|
+
const [hour, setHour] = useState(() => {
|
|
7625
|
+
if (parsedValue) {
|
|
7626
|
+
return parsedValue.hour();
|
|
7627
|
+
}
|
|
7628
|
+
if (defaultTime?.hour !== null && defaultTime?.hour !== undefined) {
|
|
7629
|
+
return defaultTime.hour;
|
|
7199
7630
|
}
|
|
7200
7631
|
return null;
|
|
7201
|
-
}
|
|
7202
|
-
const
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
setSecond(null);
|
|
7206
|
-
filter(''); // Reset filter to show all options
|
|
7207
|
-
onChange({ hour: null, minute: null, second: null });
|
|
7208
|
-
};
|
|
7209
|
-
const handleValueChange = (details) => {
|
|
7210
|
-
if (details.value.length === 0) {
|
|
7211
|
-
handleClear();
|
|
7212
|
-
return;
|
|
7632
|
+
});
|
|
7633
|
+
const [minute, setMinute] = useState(() => {
|
|
7634
|
+
if (parsedValue) {
|
|
7635
|
+
return parsedValue.minute();
|
|
7213
7636
|
}
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
if (selectedOption) {
|
|
7217
|
-
setHour(selectedOption.hour);
|
|
7218
|
-
setMinute(selectedOption.minute);
|
|
7219
|
-
setSecond(selectedOption.second);
|
|
7220
|
-
filter(''); // Reset filter after selection
|
|
7221
|
-
onChange({
|
|
7222
|
-
hour: selectedOption.hour,
|
|
7223
|
-
minute: selectedOption.minute,
|
|
7224
|
-
second: selectedOption.second,
|
|
7225
|
-
});
|
|
7637
|
+
if (defaultTime?.minute !== null && defaultTime?.minute !== undefined) {
|
|
7638
|
+
return defaultTime.minute;
|
|
7226
7639
|
}
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
const
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
if (
|
|
7640
|
+
return null;
|
|
7641
|
+
});
|
|
7642
|
+
const [second, setSecond] = useState(() => {
|
|
7643
|
+
if (parsedValue) {
|
|
7644
|
+
return parsedValue.second();
|
|
7645
|
+
}
|
|
7646
|
+
if (defaultTime?.second !== null && defaultTime?.second !== undefined) {
|
|
7647
|
+
return defaultTime.second;
|
|
7648
|
+
}
|
|
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);
|
|
7234
7711
|
return;
|
|
7235
7712
|
}
|
|
7236
|
-
//
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
if (
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
minute: parsedMinute,
|
|
7256
|
-
second: parsedSecond,
|
|
7257
|
-
});
|
|
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
|
+
}
|
|
7258
7732
|
return;
|
|
7259
7733
|
}
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
const parsedHour = parseInt(numbersOnly.slice(0, 2), 10);
|
|
7266
|
-
const parsedMinute = parseInt(numbersOnly.slice(2, 4), 10);
|
|
7267
|
-
const parsedSecond = numbersOnly.length >= 6 ? parseInt(numbersOnly.slice(4, 6), 10) : 0;
|
|
7268
|
-
// Validate ranges
|
|
7269
|
-
if (parsedHour >= 0 &&
|
|
7270
|
-
parsedHour <= 23 &&
|
|
7271
|
-
parsedMinute >= 0 &&
|
|
7272
|
-
parsedMinute <= 59 &&
|
|
7273
|
-
parsedSecond >= 0 &&
|
|
7274
|
-
parsedSecond <= 59) {
|
|
7275
|
-
setHour(parsedHour);
|
|
7276
|
-
setMinute(parsedMinute);
|
|
7277
|
-
setSecond(parsedSecond);
|
|
7278
|
-
onChange({
|
|
7279
|
-
hour: parsedHour,
|
|
7280
|
-
minute: parsedMinute,
|
|
7281
|
-
second: parsedSecond,
|
|
7282
|
-
});
|
|
7283
|
-
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);
|
|
7284
7739
|
}
|
|
7740
|
+
else {
|
|
7741
|
+
setDateInputValue('');
|
|
7742
|
+
}
|
|
7743
|
+
return;
|
|
7285
7744
|
}
|
|
7286
|
-
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
if (collection.items.length > 0) {
|
|
7293
|
-
const firstItem = collection.items[0];
|
|
7294
|
-
setHour(firstItem.hour);
|
|
7295
|
-
setMinute(firstItem.minute);
|
|
7296
|
-
setSecond(firstItem.second);
|
|
7297
|
-
filter(''); // Reset filter after selection
|
|
7298
|
-
onChange({
|
|
7299
|
-
hour: firstItem.hour,
|
|
7300
|
-
minute: firstItem.minute,
|
|
7301
|
-
second: firstItem.second,
|
|
7302
|
-
});
|
|
7303
|
-
}
|
|
7304
|
-
};
|
|
7305
|
-
const [inputValue, setInputValue] = useState('');
|
|
7306
|
-
// Sync inputValue with currentValue when time changes externally
|
|
7307
|
-
useEffect(() => {
|
|
7308
|
-
if (hour !== null && minute !== null && second !== null) {
|
|
7309
|
-
const formattedValue = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${second.toString().padStart(2, '0')}`;
|
|
7310
|
-
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);
|
|
7311
7751
|
}
|
|
7312
7752
|
else {
|
|
7313
|
-
|
|
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
|
+
}
|
|
7314
7761
|
}
|
|
7315
|
-
}, [hour, minute, second]);
|
|
7316
|
-
const handleInputValueChange = (details) => {
|
|
7317
|
-
// Update local input value state
|
|
7318
|
-
setInputValue(details.inputValue);
|
|
7319
|
-
// Filter the collection based on input, but don't parse yet
|
|
7320
|
-
filter(details.inputValue);
|
|
7321
7762
|
};
|
|
7322
|
-
const
|
|
7323
|
-
|
|
7324
|
-
e.target.select();
|
|
7763
|
+
const handleDateInputChange = (e) => {
|
|
7764
|
+
setDateInputValue(e.target.value);
|
|
7325
7765
|
};
|
|
7326
|
-
const
|
|
7327
|
-
|
|
7328
|
-
const inputVal = e.target.value;
|
|
7329
|
-
setInputValue(inputVal);
|
|
7330
|
-
if (inputVal) {
|
|
7331
|
-
parseAndCommitInput(inputVal);
|
|
7332
|
-
}
|
|
7766
|
+
const handleDateInputBlur = () => {
|
|
7767
|
+
parseAndValidateDateInput(dateInputValue);
|
|
7333
7768
|
};
|
|
7334
|
-
const
|
|
7335
|
-
// Commit input on Enter key
|
|
7769
|
+
const handleDateInputKeyDown = (e) => {
|
|
7336
7770
|
if (e.key === 'Enter') {
|
|
7337
7771
|
e.preventDefault();
|
|
7338
|
-
|
|
7339
|
-
setInputValue(inputVal);
|
|
7340
|
-
if (inputVal) {
|
|
7341
|
-
parseAndCommitInput(inputVal);
|
|
7342
|
-
}
|
|
7343
|
-
// Blur the input
|
|
7344
|
-
e.currentTarget?.blur();
|
|
7772
|
+
parseAndValidateDateInput(dateInputValue);
|
|
7345
7773
|
}
|
|
7346
7774
|
};
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
dayjs.
|
|
7351
|
-
dayjs.
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
'Jun',
|
|
7360
|
-
'Jul',
|
|
7361
|
-
'Aug',
|
|
7362
|
-
'Sep',
|
|
7363
|
-
'Oct',
|
|
7364
|
-
'Nov',
|
|
7365
|
-
'Dec',
|
|
7366
|
-
],
|
|
7367
|
-
weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
|
7368
|
-
backButtonLabel: 'Back',
|
|
7369
|
-
forwardButtonLabel: 'Next',
|
|
7370
|
-
}, timePickerLabels, timezone = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, defaultDate, defaultTime, }) {
|
|
7371
|
-
console.log('[DateTimePicker] Component initialized with props:', {
|
|
7372
|
-
value,
|
|
7373
|
-
format,
|
|
7374
|
-
showSeconds,
|
|
7375
|
-
timezone,
|
|
7376
|
-
startTime,
|
|
7377
|
-
minDate,
|
|
7378
|
-
maxDate,
|
|
7379
|
-
});
|
|
7380
|
-
// Initialize selectedDate from value prop, converting ISO to YYYY-MM-DD format
|
|
7381
|
-
const getDateString = useCallback((val) => {
|
|
7382
|
-
if (!val)
|
|
7383
|
-
return '';
|
|
7384
|
-
const dateObj = dayjs(val).tz(timezone);
|
|
7385
|
-
return dateObj.isValid() ? dateObj.format('YYYY-MM-DD') : '';
|
|
7386
|
-
}, [timezone]);
|
|
7387
|
-
const [selectedDate, setSelectedDate] = useState(getDateString(value));
|
|
7388
|
-
// Helper to get time values from value prop with timezone
|
|
7389
|
-
const getTimeFromValue = useCallback((val) => {
|
|
7390
|
-
console.log('[DateTimePicker] getTimeFromValue called:', {
|
|
7391
|
-
val,
|
|
7392
|
-
timezone,
|
|
7393
|
-
showSeconds,
|
|
7394
|
-
});
|
|
7395
|
-
if (!val) {
|
|
7396
|
-
console.log('[DateTimePicker] No value provided, returning nulls');
|
|
7397
|
-
return {
|
|
7398
|
-
hour12: null,
|
|
7399
|
-
minute: null,
|
|
7400
|
-
meridiem: null,
|
|
7401
|
-
hour24: null,
|
|
7402
|
-
second: null,
|
|
7403
|
-
};
|
|
7404
|
-
}
|
|
7405
|
-
const dateObj = dayjs(val).tz(timezone);
|
|
7406
|
-
console.log('[DateTimePicker] Parsed date object:', {
|
|
7407
|
-
original: val,
|
|
7408
|
-
timezone,
|
|
7409
|
-
isValid: dateObj.isValid(),
|
|
7410
|
-
formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
|
|
7411
|
-
hour24: dateObj.hour(),
|
|
7412
|
-
minute: dateObj.minute(),
|
|
7413
|
-
second: dateObj.second(),
|
|
7414
|
-
});
|
|
7415
|
-
if (!dateObj.isValid()) {
|
|
7416
|
-
console.log('[DateTimePicker] Invalid date object, returning nulls');
|
|
7417
|
-
return {
|
|
7418
|
-
hour12: null,
|
|
7419
|
-
minute: null,
|
|
7420
|
-
meridiem: null,
|
|
7421
|
-
hour24: null,
|
|
7422
|
-
second: null,
|
|
7423
|
-
};
|
|
7424
|
-
}
|
|
7425
|
-
const hour24Value = dateObj.hour();
|
|
7426
|
-
const hour12Value = hour24Value % 12 || 12;
|
|
7427
|
-
const minuteValue = dateObj.minute();
|
|
7428
|
-
const meridiemValue = hour24Value >= 12 ? 'pm' : 'am';
|
|
7429
|
-
const secondValue = showSeconds ? dateObj.second() : null;
|
|
7430
|
-
const result = {
|
|
7431
|
-
hour12: hour12Value,
|
|
7432
|
-
minute: minuteValue,
|
|
7433
|
-
meridiem: meridiemValue,
|
|
7434
|
-
hour24: hour24Value,
|
|
7435
|
-
second: secondValue,
|
|
7436
|
-
};
|
|
7437
|
-
console.log('[DateTimePicker] Extracted time values:', result);
|
|
7438
|
-
return result;
|
|
7439
|
-
}, [timezone, showSeconds]);
|
|
7440
|
-
const initialTime = getTimeFromValue(value);
|
|
7441
|
-
console.log('[DateTimePicker] Initial time from value:', {
|
|
7442
|
-
value,
|
|
7443
|
-
initialTime,
|
|
7444
|
-
});
|
|
7445
|
-
// Normalize startTime to ignore milliseconds (needed for effectiveDefaultDate calculation)
|
|
7446
|
-
const normalizedStartTime = startTime
|
|
7447
|
-
? dayjs(startTime).tz(timezone).millisecond(0).toISOString()
|
|
7448
|
-
: undefined;
|
|
7449
|
-
// Calculate effective defaultDate: use prop if provided, otherwise use startTime date, otherwise use today
|
|
7450
|
-
const effectiveDefaultDate = useMemo(() => {
|
|
7451
|
-
if (defaultDate) {
|
|
7452
|
-
return defaultDate;
|
|
7453
|
-
}
|
|
7454
|
-
if (normalizedStartTime &&
|
|
7455
|
-
dayjs(normalizedStartTime).tz(timezone).isValid()) {
|
|
7456
|
-
return dayjs(normalizedStartTime).tz(timezone).format('YYYY-MM-DD');
|
|
7457
|
-
}
|
|
7458
|
-
return dayjs().tz(timezone).format('YYYY-MM-DD');
|
|
7459
|
-
}, [defaultDate, normalizedStartTime, timezone]);
|
|
7460
|
-
// Initialize time with default values if no value is provided
|
|
7461
|
-
const getInitialTimeValues = () => {
|
|
7462
|
-
if (value && initialTime.hour12 !== null) {
|
|
7463
|
-
return initialTime;
|
|
7464
|
-
}
|
|
7465
|
-
// If no value or no time in value, use defaultTime or 00:00
|
|
7466
|
-
if (defaultTime) {
|
|
7467
|
-
if (format === 'iso-date-time') {
|
|
7468
|
-
const defaultTime24 = defaultTime;
|
|
7469
|
-
return {
|
|
7470
|
-
hour12: null,
|
|
7471
|
-
minute: defaultTime24.minute ?? 0,
|
|
7472
|
-
meridiem: null,
|
|
7473
|
-
hour24: defaultTime24.hour ?? 0,
|
|
7474
|
-
second: showSeconds ? defaultTime24.second ?? 0 : null,
|
|
7475
|
-
};
|
|
7476
|
-
}
|
|
7477
|
-
else {
|
|
7478
|
-
const defaultTime12 = defaultTime;
|
|
7479
|
-
return {
|
|
7480
|
-
hour12: defaultTime12.hour ?? 12,
|
|
7481
|
-
minute: defaultTime12.minute ?? 0,
|
|
7482
|
-
meridiem: defaultTime12.meridiem ?? 'am',
|
|
7483
|
-
hour24: null,
|
|
7484
|
-
second: null,
|
|
7485
|
-
};
|
|
7486
|
-
}
|
|
7487
|
-
}
|
|
7488
|
-
// Default to 00:00
|
|
7489
|
-
if (format === 'iso-date-time') {
|
|
7490
|
-
return {
|
|
7491
|
-
hour12: null,
|
|
7492
|
-
minute: 0,
|
|
7493
|
-
meridiem: null,
|
|
7494
|
-
hour24: 0,
|
|
7495
|
-
second: showSeconds ? 0 : null,
|
|
7496
|
-
};
|
|
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;
|
|
7497
7787
|
}
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
hour24: null,
|
|
7504
|
-
second: null,
|
|
7505
|
-
};
|
|
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;
|
|
7506
7793
|
}
|
|
7794
|
+
return true;
|
|
7507
7795
|
};
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
const [second, setSecond] = useState(initialTimeValues.second);
|
|
7516
|
-
// Sync selectedDate and time states when value prop changes
|
|
7517
|
-
useEffect(() => {
|
|
7518
|
-
console.log('[DateTimePicker] useEffect triggered - value changed:', {
|
|
7519
|
-
value,
|
|
7520
|
-
timezone,
|
|
7521
|
-
format,
|
|
7522
|
-
});
|
|
7523
|
-
// If value is null, undefined, or invalid, clear date but keep default time values
|
|
7524
|
-
if (!value || value === null || value === undefined) {
|
|
7525
|
-
console.log('[DateTimePicker] Value is null/undefined, clearing date but keeping default time');
|
|
7526
|
-
setSelectedDate('');
|
|
7527
|
-
// Keep default time values instead of clearing them
|
|
7528
|
-
if (format === 'iso-date-time') {
|
|
7529
|
-
setHour24(defaultTime ? defaultTime.hour ?? 0 : 0);
|
|
7530
|
-
setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
|
|
7531
|
-
setSecond(showSeconds
|
|
7532
|
-
? defaultTime
|
|
7533
|
-
? defaultTime.second ?? 0
|
|
7534
|
-
: 0
|
|
7535
|
-
: null);
|
|
7536
|
-
}
|
|
7537
|
-
else {
|
|
7538
|
-
setHour12(defaultTime ? defaultTime.hour ?? 12 : 12);
|
|
7539
|
-
setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
|
|
7540
|
-
setMeridiem(defaultTime ? defaultTime.meridiem ?? 'am' : 'am');
|
|
7541
|
-
}
|
|
7542
|
-
return;
|
|
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);
|
|
7543
7803
|
}
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
setHour12(defaultTime ? defaultTime.hour ?? 12 : 12);
|
|
7561
|
-
setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
|
|
7562
|
-
setMeridiem(defaultTime ? defaultTime.meridiem ?? 'am' : 'am');
|
|
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')}`;
|
|
7563
7820
|
}
|
|
7564
|
-
return
|
|
7821
|
+
return `${hour24.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
|
|
7565
7822
|
}
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
7576
|
-
|
|
7577
|
-
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
setSelectedDate(date);
|
|
7599
|
-
// Parse the date string (YYYY-MM-DD) in the specified timezone
|
|
7600
|
-
const dateObj = dayjs.tz(date, timezone);
|
|
7601
|
-
console.log('[DateTimePicker] Parsed date object:', {
|
|
7602
|
-
date,
|
|
7603
|
-
timezone,
|
|
7604
|
-
isValid: dateObj.isValid(),
|
|
7605
|
-
isoString: dateObj.toISOString(),
|
|
7606
|
-
formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
|
|
7607
|
-
});
|
|
7608
|
-
if (!dateObj.isValid()) {
|
|
7609
|
-
console.warn('[DateTimePicker] Invalid date object in handleDateChange, clearing fields');
|
|
7610
|
-
setSelectedDate('');
|
|
7611
|
-
setHour12(null);
|
|
7612
|
-
setMinute(null);
|
|
7613
|
-
setMeridiem(null);
|
|
7614
|
-
setHour24(null);
|
|
7615
|
-
setSecond(null);
|
|
7823
|
+
else {
|
|
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
|
|
7840
|
+
useEffect(() => {
|
|
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');
|
|
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) {
|
|
7616
7855
|
onChange?.(undefined);
|
|
7617
7856
|
return;
|
|
7618
7857
|
}
|
|
7619
|
-
//
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
const defaultTime24 = defaultTime;
|
|
7630
|
-
setHour24(defaultTime24.hour ?? 0);
|
|
7631
|
-
setMinute(defaultTime24.minute ?? 0);
|
|
7632
|
-
if (showSeconds) {
|
|
7633
|
-
setSecond(defaultTime24.second ?? 0);
|
|
7634
|
-
}
|
|
7635
|
-
timeDataToUse = {
|
|
7636
|
-
hour: defaultTime24.hour ?? 0,
|
|
7637
|
-
minute: defaultTime24.minute ?? 0,
|
|
7638
|
-
second: showSeconds ? defaultTime24.second ?? 0 : undefined,
|
|
7639
|
-
};
|
|
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;
|
|
7640
7868
|
}
|
|
7641
7869
|
else {
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
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
|
|
7651
7880
|
}
|
|
7652
7881
|
}
|
|
7653
7882
|
else {
|
|
7654
|
-
|
|
7655
|
-
if (
|
|
7656
|
-
|
|
7657
|
-
setMinute(0);
|
|
7658
|
-
if (showSeconds) {
|
|
7659
|
-
setSecond(0);
|
|
7660
|
-
}
|
|
7661
|
-
timeDataToUse = {
|
|
7662
|
-
hour: 0,
|
|
7663
|
-
minute: 0,
|
|
7664
|
-
second: showSeconds ? 0 : undefined,
|
|
7665
|
-
};
|
|
7883
|
+
// PM
|
|
7884
|
+
if (hour12 === 12) {
|
|
7885
|
+
hour24 = 12; // 12 PM = 12:00
|
|
7666
7886
|
}
|
|
7667
7887
|
else {
|
|
7668
|
-
|
|
7669
|
-
setMinute(0);
|
|
7670
|
-
setMeridiem('am');
|
|
7671
|
-
timeDataToUse = {
|
|
7672
|
-
hour: 12,
|
|
7673
|
-
minute: 0,
|
|
7674
|
-
meridiem: 'am',
|
|
7675
|
-
};
|
|
7888
|
+
hour24 = hour12 + 12; // 1-11 PM = 13-23
|
|
7676
7889
|
}
|
|
7677
7890
|
}
|
|
7678
7891
|
}
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
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}`);
|
|
7911
|
+
return;
|
|
7684
7912
|
}
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
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()) {
|
|
7921
|
+
onChange?.(undefined);
|
|
7922
|
+
return;
|
|
7689
7923
|
}
|
|
7690
|
-
|
|
7691
|
-
const handleTimeChange = (timeData) => {
|
|
7692
|
-
console.log('[DateTimePicker] handleTimeChange called:', {
|
|
7693
|
-
timeData,
|
|
7694
|
-
format,
|
|
7695
|
-
selectedDate,
|
|
7696
|
-
timezone,
|
|
7697
|
-
});
|
|
7924
|
+
// Format based on format prop
|
|
7698
7925
|
if (format === 'iso-date-time') {
|
|
7699
|
-
|
|
7700
|
-
console.log('[DateTimePicker] ISO format - setting 24-hour time:', data);
|
|
7701
|
-
setHour24(data.hour);
|
|
7702
|
-
setMinute(data.minute);
|
|
7703
|
-
if (showSeconds) {
|
|
7704
|
-
setSecond(data.second ?? null);
|
|
7705
|
-
}
|
|
7706
|
-
else {
|
|
7707
|
-
// Ignore seconds - always set to null when showSeconds is false
|
|
7708
|
-
setSecond(null);
|
|
7709
|
-
}
|
|
7926
|
+
onChange?.(dateTime.format('YYYY-MM-DDTHH:mm:ss'));
|
|
7710
7927
|
}
|
|
7711
7928
|
else {
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
setHour12(data.hour);
|
|
7715
|
-
setMinute(data.minute);
|
|
7716
|
-
setMeridiem(data.meridiem);
|
|
7717
|
-
}
|
|
7718
|
-
// Use selectedDate if valid, otherwise use effectiveDefaultDate or clear all fields
|
|
7719
|
-
if (!selectedDate || !dayjs(selectedDate).isValid()) {
|
|
7720
|
-
// If effectiveDefaultDate is available, use it instead of clearing
|
|
7721
|
-
if (effectiveDefaultDate && dayjs(effectiveDefaultDate).isValid()) {
|
|
7722
|
-
console.log('[DateTimePicker] No valid selectedDate, using effectiveDefaultDate:', effectiveDefaultDate);
|
|
7723
|
-
setSelectedDate(effectiveDefaultDate);
|
|
7724
|
-
const dateObj = dayjs(effectiveDefaultDate).tz(timezone);
|
|
7725
|
-
if (dateObj.isValid()) {
|
|
7726
|
-
updateDateTime(dateObj.toISOString(), timeData);
|
|
7727
|
-
}
|
|
7728
|
-
else {
|
|
7729
|
-
console.warn('[DateTimePicker] Invalid effectiveDefaultDate, clearing fields');
|
|
7730
|
-
setSelectedDate('');
|
|
7731
|
-
setHour12(null);
|
|
7732
|
-
setMinute(null);
|
|
7733
|
-
setMeridiem(null);
|
|
7734
|
-
setHour24(null);
|
|
7735
|
-
setSecond(null);
|
|
7736
|
-
onChange?.(undefined);
|
|
7737
|
-
}
|
|
7738
|
-
return;
|
|
7739
|
-
}
|
|
7740
|
-
else {
|
|
7741
|
-
console.log('[DateTimePicker] No valid selectedDate and no effectiveDefaultDate, keeping time values but no date');
|
|
7742
|
-
// Keep the time values that were just set, but don't set a date
|
|
7743
|
-
// This should rarely happen as effectiveDefaultDate always defaults to today
|
|
7744
|
-
setSelectedDate('');
|
|
7745
|
-
onChange?.(undefined);
|
|
7746
|
-
return;
|
|
7747
|
-
}
|
|
7929
|
+
// date-time format with timezone
|
|
7930
|
+
onChange?.(dateTime.format('YYYY-MM-DDTHH:mm:ssZ'));
|
|
7748
7931
|
}
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
7932
|
+
};
|
|
7933
|
+
// Handle date selection
|
|
7934
|
+
const handleDateSelected = ({ date, }) => {
|
|
7935
|
+
setSelectedDate(date);
|
|
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);
|
|
7752
7944
|
}
|
|
7753
7945
|
else {
|
|
7754
|
-
|
|
7755
|
-
setSelectedDate('');
|
|
7756
|
-
setHour12(null);
|
|
7757
|
-
setMinute(null);
|
|
7758
|
-
setMeridiem(null);
|
|
7759
|
-
setHour24(null);
|
|
7760
|
-
setSecond(null);
|
|
7761
|
-
onChange?.(undefined);
|
|
7946
|
+
setMeridiem(newMeridiem);
|
|
7762
7947
|
}
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
console.log('[DateTimePicker] updateDateTime called:', {
|
|
7766
|
-
date,
|
|
7767
|
-
timeData,
|
|
7768
|
-
format,
|
|
7769
|
-
currentStates: { hour12, minute, meridiem, hour24, second },
|
|
7770
|
-
});
|
|
7771
|
-
if (!date || date === null || date === undefined) {
|
|
7772
|
-
console.log('[DateTimePicker] No date provided, clearing all fields and calling onChange(undefined)');
|
|
7773
|
-
setSelectedDate('');
|
|
7774
|
-
setHour12(null);
|
|
7775
|
-
setMinute(null);
|
|
7776
|
-
setMeridiem(null);
|
|
7777
|
-
setHour24(null);
|
|
7778
|
-
setSecond(null);
|
|
7779
|
-
onChange?.(undefined);
|
|
7780
|
-
return;
|
|
7948
|
+
if (selectedDate) {
|
|
7949
|
+
updateDateTime(selectedDate, newHour, newMinute, newSecond, newMeridiem);
|
|
7781
7950
|
}
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
7793
|
-
|
|
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
|
+
}
|
|
7794
7976
|
}
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
7805
|
-
|
|
7806
|
-
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
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
|
+
}
|
|
7992
|
+
}
|
|
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
|
+
});
|
|
8036
|
+
}
|
|
7813
8037
|
}
|
|
7814
|
-
console.log('[DateTimePicker] ISO format - setting time on date:', {
|
|
7815
|
-
h,
|
|
7816
|
-
m,
|
|
7817
|
-
s,
|
|
7818
|
-
showSeconds,
|
|
7819
|
-
});
|
|
7820
|
-
if (h !== null)
|
|
7821
|
-
newDate.setHours(h);
|
|
7822
|
-
if (m !== null)
|
|
7823
|
-
newDate.setMinutes(m);
|
|
7824
|
-
newDate.setSeconds(s ?? 0);
|
|
7825
8038
|
}
|
|
7826
8039
|
else {
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
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
|
+
});
|
|
8104
|
+
}
|
|
8105
|
+
}
|
|
7847
8106
|
}
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
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;
|
|
7852
8125
|
});
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
8126
|
+
}
|
|
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;
|
|
8139
|
+
}
|
|
8140
|
+
return (itemText, filterText) => {
|
|
8141
|
+
if (!filterText) {
|
|
8142
|
+
return true;
|
|
7865
8143
|
}
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
7870
|
-
});
|
|
8144
|
+
const lowerItemText = itemText.toLowerCase();
|
|
8145
|
+
const lowerFilterText = filterText.toLowerCase();
|
|
8146
|
+
if (lowerItemText.includes(lowerFilterText)) {
|
|
8147
|
+
return true;
|
|
7871
8148
|
}
|
|
7872
|
-
|
|
7873
|
-
|
|
8149
|
+
const item = timeOptions.find((opt) => opt.searchText.toLowerCase() === lowerItemText);
|
|
8150
|
+
if (!item || !('meridiem' in item)) {
|
|
8151
|
+
return false;
|
|
7874
8152
|
}
|
|
7875
|
-
|
|
7876
|
-
|
|
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 '';
|
|
7877
8182
|
}
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
const finalISO = dayjs(newDate).tz(timezone).toISOString();
|
|
7881
|
-
console.log('[DateTimePicker] Final ISO string to emit:', {
|
|
7882
|
-
newDate: newDate.toISOString(),
|
|
7883
|
-
timezone,
|
|
7884
|
-
finalISO,
|
|
7885
|
-
});
|
|
7886
|
-
onChange?.(finalISO);
|
|
7887
|
-
};
|
|
7888
|
-
const handleClear = () => {
|
|
7889
|
-
setSelectedDate('');
|
|
7890
|
-
// Reset to default time values instead of clearing them
|
|
7891
|
-
if (format === 'iso-date-time') {
|
|
7892
|
-
setHour24(defaultTime ? defaultTime.hour ?? 0 : 0);
|
|
7893
|
-
setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
|
|
7894
|
-
setSecond(showSeconds
|
|
7895
|
-
? defaultTime
|
|
7896
|
-
? defaultTime.second ?? 0
|
|
7897
|
-
: 0
|
|
7898
|
-
: null);
|
|
8183
|
+
const s = second ?? 0;
|
|
8184
|
+
return `${hour}:${minute}:${s}`;
|
|
7899
8185
|
}
|
|
7900
8186
|
else {
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
};
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
minute
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
// For ISO format, use hour24, minute, second
|
|
7947
|
-
if (hour24 === null || minute === null)
|
|
7948
|
-
return null;
|
|
7949
|
-
const dateTimeObj = dateObj
|
|
7950
|
-
.hour(hour24)
|
|
7951
|
-
.minute(minute)
|
|
7952
|
-
.second(second ?? 0);
|
|
7953
|
-
return dateTimeObj.format(showSeconds ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm');
|
|
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 };
|
|
8208
|
+
}
|
|
8209
|
+
else {
|
|
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 };
|
|
8230
|
+
}
|
|
8231
|
+
}
|
|
7954
8232
|
}
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
else if (meridiem === 'pm' && hour12 < 12)
|
|
7964
|
-
hour24Value = hour12 + 12;
|
|
7965
|
-
const dateTimeObj = dateObj.hour(hour24Value).minute(minute).second(0);
|
|
7966
|
-
return dateTimeObj.format('YYYY-MM-DD hh:mm A');
|
|
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 };
|
|
8240
|
+
}
|
|
7967
8241
|
}
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
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
|
+
}
|
|
8249
|
+
}
|
|
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';
|
|
7988
8281
|
}
|
|
7989
8282
|
else {
|
|
7990
|
-
|
|
7991
|
-
|
|
8283
|
+
hour12 = hour24;
|
|
8284
|
+
meridiem = 'am';
|
|
8285
|
+
}
|
|
8286
|
+
return { hour: hour12, minute, second: null, meridiem };
|
|
8287
|
+
}
|
|
8288
|
+
}
|
|
8289
|
+
}
|
|
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
|
+
}
|
|
8305
|
+
}
|
|
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('');
|
|
8312
|
+
return;
|
|
8313
|
+
}
|
|
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);
|
|
8321
|
+
}
|
|
8322
|
+
else {
|
|
8323
|
+
const opt12 = selectedOption;
|
|
8324
|
+
handleTimeChange(opt12.hour, opt12.minute, null, opt12.meridiem);
|
|
8325
|
+
}
|
|
8326
|
+
}
|
|
8327
|
+
};
|
|
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);
|
|
7992
8347
|
}
|
|
7993
|
-
}
|
|
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
|
+
}
|
|
8355
|
+
}
|
|
8356
|
+
};
|
|
8357
|
+
// Calendar rendering
|
|
8358
|
+
const renderCalendar = () => {
|
|
8359
|
+
const { calendars, getBackProps, getForwardProps, getDateProps } = calendarProps;
|
|
8360
|
+
if (calendars.length === 0)
|
|
8361
|
+
return null;
|
|
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))) }) })] }) }) }) }) }))] }))] }));
|
|
7994
8412
|
}
|
|
7995
8413
|
|
|
7996
8414
|
dayjs.extend(utc);
|
|
@@ -8004,11 +8422,12 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
|
|
|
8004
8422
|
dateFormat = 'YYYY-MM-DD[T]HH:mm:ssZ', } = schema;
|
|
8005
8423
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
8006
8424
|
const colLabel = formI18n.colLabel;
|
|
8007
|
-
|
|
8425
|
+
useState(false);
|
|
8008
8426
|
const selectedDate = watch(colLabel);
|
|
8009
|
-
|
|
8427
|
+
selectedDate && dayjs(selectedDate).tz(timezone).isValid()
|
|
8010
8428
|
? dayjs(selectedDate).tz(timezone).format(displayDateFormat)
|
|
8011
8429
|
: '';
|
|
8430
|
+
// Set default date on mount if no value exists
|
|
8012
8431
|
const dateTimePickerLabelsConfig = {
|
|
8013
8432
|
monthNamesShort: dateTimePickerLabels?.monthNamesShort ?? [
|
|
8014
8433
|
'January',
|
|
@@ -8050,9 +8469,7 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
|
|
|
8050
8469
|
}
|
|
8051
8470
|
}, timezone: timezone, labels: dateTimePickerLabelsConfig, timePickerLabels: timePickerLabels }));
|
|
8052
8471
|
return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
8053
|
-
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children:
|
|
8054
|
-
setOpen(true);
|
|
8055
|
-
}, 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 }));
|
|
8056
8473
|
};
|
|
8057
8474
|
|
|
8058
8475
|
const SchemaRenderer = ({ schema, prefix, column, }) => {
|