@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.js
CHANGED
|
@@ -29,9 +29,9 @@ var reactHookForm = require('react-hook-form');
|
|
|
29
29
|
var Ajv = require('ajv');
|
|
30
30
|
var addFormats = require('ajv-formats');
|
|
31
31
|
var dayjs = require('dayjs');
|
|
32
|
-
var utc = require('dayjs/plugin/utc');
|
|
33
|
-
var timezone = require('dayjs/plugin/timezone');
|
|
34
32
|
var customParseFormat = require('dayjs/plugin/customParseFormat');
|
|
33
|
+
var timezone = require('dayjs/plugin/timezone');
|
|
34
|
+
var utc = require('dayjs/plugin/utc');
|
|
35
35
|
var ti = require('react-icons/ti');
|
|
36
36
|
var matchSorterUtils = require('@tanstack/match-sorter-utils');
|
|
37
37
|
|
|
@@ -4680,7 +4680,7 @@ function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateF
|
|
|
4680
4680
|
}, timezone = 'Asia/Hong_Kong', minDate, maxDate, firstDayOfWeek, showOutsideDays, monthsToDisplay = 1, insideDialog = false, readOnly = false, showHelperButtons = true, }) {
|
|
4681
4681
|
const [open, setOpen] = React.useState(false);
|
|
4682
4682
|
const [inputValue, setInputValue] = React.useState('');
|
|
4683
|
-
//
|
|
4683
|
+
// Sync inputValue with value prop changes
|
|
4684
4684
|
React.useEffect(() => {
|
|
4685
4685
|
if (value) {
|
|
4686
4686
|
const formatted = typeof value === 'string'
|
|
@@ -4693,7 +4693,7 @@ function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateF
|
|
|
4693
4693
|
else {
|
|
4694
4694
|
setInputValue('');
|
|
4695
4695
|
}
|
|
4696
|
-
}, [value,
|
|
4696
|
+
}, [value, timezone, displayFormat]);
|
|
4697
4697
|
// Convert value to Date object for DatePicker
|
|
4698
4698
|
const selectedDate = value
|
|
4699
4699
|
? typeof value === 'string'
|
|
@@ -4775,7 +4775,14 @@ function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateF
|
|
|
4775
4775
|
}
|
|
4776
4776
|
};
|
|
4777
4777
|
const handleDateSelected = ({ date }) => {
|
|
4778
|
+
console.debug('[DatePickerInput] handleDateSelected called:', {
|
|
4779
|
+
date: date.toISOString(),
|
|
4780
|
+
timezone,
|
|
4781
|
+
dateFormat,
|
|
4782
|
+
formattedDate: dayjs(date).tz(timezone).format(dateFormat),
|
|
4783
|
+
});
|
|
4778
4784
|
const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
|
|
4785
|
+
console.debug('[DatePickerInput] Calling onChange with formatted date:', formattedDate);
|
|
4779
4786
|
onChange?.(formattedDate);
|
|
4780
4787
|
setOpen(false);
|
|
4781
4788
|
};
|
|
@@ -5089,7 +5096,7 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
|
|
|
5089
5096
|
const watchEnum = watch(colLabel);
|
|
5090
5097
|
const watchEnums = (watch(colLabel) ?? []);
|
|
5091
5098
|
const dataList = schema.enum ?? [];
|
|
5092
|
-
// Helper function to render enum value
|
|
5099
|
+
// Helper function to render enum value (returns ReactNode)
|
|
5093
5100
|
// If renderDisplay is provided, use it; otherwise show the enum string value directly
|
|
5094
5101
|
const renderEnumValue = (value) => {
|
|
5095
5102
|
if (renderDisplay) {
|
|
@@ -5098,6 +5105,35 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
|
|
|
5098
5105
|
// If no renderDisplay provided, show the enum string value directly
|
|
5099
5106
|
return value;
|
|
5100
5107
|
};
|
|
5108
|
+
// Helper function to get string representation for input display
|
|
5109
|
+
// Converts ReactNode to string for combobox input display
|
|
5110
|
+
const getDisplayString = (value) => {
|
|
5111
|
+
if (renderDisplay) {
|
|
5112
|
+
const rendered = renderDisplay(value);
|
|
5113
|
+
// If renderDisplay returns a string, use it directly
|
|
5114
|
+
if (typeof rendered === 'string') {
|
|
5115
|
+
return rendered;
|
|
5116
|
+
}
|
|
5117
|
+
// If it's a React element, try to extract text content
|
|
5118
|
+
// For now, fallback to the raw value if we can't extract text
|
|
5119
|
+
// In most cases, renderDisplay should return a string or simple element
|
|
5120
|
+
if (typeof rendered === 'object' &&
|
|
5121
|
+
rendered !== null &&
|
|
5122
|
+
'props' in rendered) {
|
|
5123
|
+
const props = rendered.props;
|
|
5124
|
+
// Try to extract text from React element props
|
|
5125
|
+
if (props?.children) {
|
|
5126
|
+
const children = props.children;
|
|
5127
|
+
if (typeof children === 'string') {
|
|
5128
|
+
return children;
|
|
5129
|
+
}
|
|
5130
|
+
}
|
|
5131
|
+
}
|
|
5132
|
+
// Fallback: use raw value if we can't extract string
|
|
5133
|
+
return value;
|
|
5134
|
+
}
|
|
5135
|
+
return value;
|
|
5136
|
+
};
|
|
5101
5137
|
// Debug log when renderDisplay is missing
|
|
5102
5138
|
if (!renderDisplay) {
|
|
5103
5139
|
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.`, {
|
|
@@ -5113,19 +5149,29 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
|
|
|
5113
5149
|
: watchEnum
|
|
5114
5150
|
? [watchEnum]
|
|
5115
5151
|
: [];
|
|
5152
|
+
// Track input focus state for single selection
|
|
5153
|
+
const [isInputFocused, setIsInputFocused] = React.useState(false);
|
|
5154
|
+
// Get the selected value for single selection display
|
|
5155
|
+
const selectedSingleValue = !isMultiple && watchEnum ? watchEnum : null;
|
|
5156
|
+
const selectedSingleRendered = selectedSingleValue
|
|
5157
|
+
? renderEnumValue(selectedSingleValue)
|
|
5158
|
+
: null;
|
|
5159
|
+
const isSelectedSingleValueString = typeof selectedSingleRendered === 'string';
|
|
5116
5160
|
const comboboxItems = React.useMemo(() => {
|
|
5117
5161
|
return dataList.map((item) => ({
|
|
5118
5162
|
label: item, // Internal: used for search/filtering only
|
|
5119
5163
|
value: item,
|
|
5120
5164
|
raw: item, // Passed to renderEnumValue for UI rendering
|
|
5165
|
+
displayLabel: getDisplayString(item), // Used for input display when selected
|
|
5121
5166
|
}));
|
|
5122
|
-
}, [dataList]);
|
|
5167
|
+
}, [dataList, renderDisplay]);
|
|
5123
5168
|
// Use filter hook for combobox
|
|
5124
5169
|
const { contains } = react.useFilter({ sensitivity: 'base' });
|
|
5125
5170
|
// Create collection for combobox
|
|
5171
|
+
// itemToString uses displayLabel to show rendered display in input when selected
|
|
5126
5172
|
const { collection, filter } = react.useListCollection({
|
|
5127
5173
|
initialItems: comboboxItems,
|
|
5128
|
-
itemToString: (item) => item.
|
|
5174
|
+
itemToString: (item) => item.displayLabel, // Use displayLabel for selected value display
|
|
5129
5175
|
itemToValue: (item) => item.value,
|
|
5130
5176
|
filter: contains,
|
|
5131
5177
|
});
|
|
@@ -5163,7 +5209,26 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
|
|
|
5163
5209
|
}, children: renderEnumValue(enumValue) }, enumValue));
|
|
5164
5210
|
}) })), jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, multiple: isMultiple, closeOnSelect: !isMultiple, openOnClick: true, invalid: !!errors[colLabel], width: "100%", positioning: insideDialog
|
|
5165
5211
|
? { strategy: 'fixed', hideWhenDetached: true }
|
|
5166
|
-
: undefined, children: [jsxRuntime.jsxs(react.Combobox.Control, {
|
|
5212
|
+
: undefined, children: [jsxRuntime.jsxs(react.Combobox.Control, { position: "relative", children: [!isMultiple &&
|
|
5213
|
+
selectedSingleValue &&
|
|
5214
|
+
!isInputFocused &&
|
|
5215
|
+
!isSelectedSingleValueString &&
|
|
5216
|
+
selectedSingleRendered && (jsxRuntime.jsx(react.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 })), jsxRuntime.jsx(react.Combobox.Input, { placeholder: !isMultiple && selectedSingleValue && !isInputFocused
|
|
5217
|
+
? undefined
|
|
5218
|
+
: enumPickerLabels?.typeToSearch ?? 'Type to search', onFocus: () => setIsInputFocused(true), onBlur: () => setIsInputFocused(false), style: {
|
|
5219
|
+
color: !isMultiple &&
|
|
5220
|
+
selectedSingleValue &&
|
|
5221
|
+
!isInputFocused &&
|
|
5222
|
+
!isSelectedSingleValueString
|
|
5223
|
+
? 'transparent'
|
|
5224
|
+
: undefined,
|
|
5225
|
+
caretColor: !isMultiple &&
|
|
5226
|
+
selectedSingleValue &&
|
|
5227
|
+
!isInputFocused &&
|
|
5228
|
+
!isSelectedSingleValueString
|
|
5229
|
+
? 'transparent'
|
|
5230
|
+
: undefined,
|
|
5231
|
+
} }), jsxRuntime.jsxs(react.Combobox.IndicatorGroup, { children: [!isMultiple && currentValue.length > 0 && (jsxRuntime.jsx(react.Combobox.ClearTrigger, { onClick: () => {
|
|
5167
5232
|
setValue(colLabel, '');
|
|
5168
5233
|
} })), jsxRuntime.jsx(react.Combobox.Trigger, {})] })] }), insideDialog ? (jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [showTotalAndLimit && (jsxRuntime.jsx(react.Text, { p: 2, fontSize: "sm", color: "fg.muted", children: `${enumPickerLabels?.total ?? 'Total'}: ${collection.items.length}` })), collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: enumPickerLabels?.emptySearchResult ?? 'No results found' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: renderEnumValue(item.raw) }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) }))] }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [showTotalAndLimit && (jsxRuntime.jsx(react.Text, { p: 2, fontSize: "sm", color: "fg.muted", children: `${enumPickerLabels?.total ?? 'Total'}: ${collection.items.length}` })), collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: enumPickerLabels?.emptySearchResult ?? 'No results found' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: renderEnumValue(item.raw) }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) }))] }) }) }))] })] }));
|
|
5169
5234
|
};
|
|
@@ -5907,15 +5972,46 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
|
|
|
5907
5972
|
}) })] }));
|
|
5908
5973
|
};
|
|
5909
5974
|
|
|
5910
|
-
// Default renderDisplay function that
|
|
5975
|
+
// Default renderDisplay function that intelligently displays items
|
|
5976
|
+
// If item is an object, tries to find common display fields (name, title, label, etc.)
|
|
5977
|
+
// Otherwise falls back to JSON.stringify
|
|
5911
5978
|
const defaultRenderDisplay = (item) => {
|
|
5979
|
+
// Check if item is an object (not null, not array, not primitive)
|
|
5980
|
+
if (item !== null &&
|
|
5981
|
+
typeof item === 'object' &&
|
|
5982
|
+
!Array.isArray(item) &&
|
|
5983
|
+
!(item instanceof Date)) {
|
|
5984
|
+
const obj = item;
|
|
5985
|
+
// Try common display fields in order of preference
|
|
5986
|
+
const displayFields = [
|
|
5987
|
+
'name',
|
|
5988
|
+
'title',
|
|
5989
|
+
'label',
|
|
5990
|
+
'displayName',
|
|
5991
|
+
'display_name',
|
|
5992
|
+
'text',
|
|
5993
|
+
'value',
|
|
5994
|
+
];
|
|
5995
|
+
for (const field of displayFields) {
|
|
5996
|
+
if (obj[field] !== undefined && obj[field] !== null) {
|
|
5997
|
+
const value = obj[field];
|
|
5998
|
+
// Return the value if it's a string or number, otherwise stringify it
|
|
5999
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
6000
|
+
return String(value);
|
|
6001
|
+
}
|
|
6002
|
+
}
|
|
6003
|
+
}
|
|
6004
|
+
// If no display field found, fall back to JSON.stringify
|
|
6005
|
+
return JSON.stringify(item);
|
|
6006
|
+
}
|
|
6007
|
+
// For non-objects (primitives, arrays, dates), use JSON.stringify
|
|
5912
6008
|
return JSON.stringify(item);
|
|
5913
6009
|
};
|
|
5914
6010
|
|
|
5915
6011
|
const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
|
|
5916
6012
|
const { watch, getValues, formState: { errors }, setValue, } = reactHookForm.useFormContext();
|
|
5917
6013
|
const { idMap, setIdMap, idPickerLabels, insideDialog } = useSchemaContext();
|
|
5918
|
-
const { renderDisplay, loadInitialValues, foreign_key, variant } = schema;
|
|
6014
|
+
const { renderDisplay, itemToValue: schemaItemToValue, loadInitialValues, foreign_key, variant, } = schema;
|
|
5919
6015
|
// loadInitialValues should be provided in schema for id-picker fields
|
|
5920
6016
|
// It's used to load the record of the id so the display is human-readable
|
|
5921
6017
|
if (variant === 'id-picker' && !loadInitialValues) {
|
|
@@ -6048,17 +6144,51 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
|
|
|
6048
6144
|
// Depend on idMapKey which only changes when items we care about change
|
|
6049
6145
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
6050
6146
|
}, [currentValueKey, idMapKey]);
|
|
6147
|
+
// Default itemToValue function: extract value from item using column_ref
|
|
6148
|
+
const defaultItemToValue = (item) => String(item[column_ref]);
|
|
6149
|
+
// Use schema's itemToValue if provided, otherwise use default
|
|
6150
|
+
const itemToValueFn = schemaItemToValue
|
|
6151
|
+
? (item) => schemaItemToValue(item)
|
|
6152
|
+
: defaultItemToValue;
|
|
6153
|
+
// itemToString function: convert item to readable string using renderDisplay
|
|
6154
|
+
// This ensures items can always be displayed as readable strings in the combobox
|
|
6155
|
+
const renderFn = renderDisplay || defaultRenderDisplay;
|
|
6156
|
+
const itemToStringFn = (item) => {
|
|
6157
|
+
const rendered = renderFn(item);
|
|
6158
|
+
// If already a string or number, return it
|
|
6159
|
+
if (typeof rendered === 'string')
|
|
6160
|
+
return rendered;
|
|
6161
|
+
if (typeof rendered === 'number')
|
|
6162
|
+
return String(rendered);
|
|
6163
|
+
// For ReactNode, fall back to defaultRenderDisplay which converts to string
|
|
6164
|
+
return String(defaultRenderDisplay(item));
|
|
6165
|
+
};
|
|
6051
6166
|
// Transform data for combobox collection
|
|
6052
6167
|
// label is used for filtering/searching (must be a string)
|
|
6168
|
+
// displayLabel is used for input display when selected (string representation of rendered display)
|
|
6053
6169
|
// raw item is stored for custom rendering
|
|
6054
6170
|
// Also include items from idMap that match currentValue (for initial values display)
|
|
6055
6171
|
const comboboxItems = React.useMemo(() => {
|
|
6056
6172
|
const renderFn = renderDisplay || defaultRenderDisplay;
|
|
6173
|
+
// Helper to convert rendered display to string for displayLabel
|
|
6174
|
+
// For ReactNodes (non-string/number), we can't safely stringify due to circular refs
|
|
6175
|
+
// So we use the label (which is already a string) as fallback
|
|
6176
|
+
const getDisplayString = (rendered, fallbackLabel) => {
|
|
6177
|
+
if (typeof rendered === 'string')
|
|
6178
|
+
return rendered;
|
|
6179
|
+
if (typeof rendered === 'number')
|
|
6180
|
+
return String(rendered);
|
|
6181
|
+
// For ReactNode, use the fallback label (which is already a string representation)
|
|
6182
|
+
// The actual ReactNode will be rendered in the overlay, not in the input
|
|
6183
|
+
return fallbackLabel;
|
|
6184
|
+
};
|
|
6057
6185
|
const itemsFromDataList = dataList.map((item) => {
|
|
6058
6186
|
const rendered = renderFn(item);
|
|
6187
|
+
const label = typeof rendered === 'string' ? rendered : JSON.stringify(item); // Use string for filtering
|
|
6059
6188
|
return {
|
|
6060
|
-
label
|
|
6061
|
-
|
|
6189
|
+
label, // Use string for filtering
|
|
6190
|
+
displayLabel: getDisplayString(rendered, label), // String representation for input display
|
|
6191
|
+
value: itemToValueFn(item),
|
|
6062
6192
|
raw: item,
|
|
6063
6193
|
};
|
|
6064
6194
|
});
|
|
@@ -6067,25 +6197,28 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
|
|
|
6067
6197
|
const itemsFromIdMap = idMapItems
|
|
6068
6198
|
.map((item) => {
|
|
6069
6199
|
// Check if this item is already in itemsFromDataList
|
|
6070
|
-
const alreadyIncluded = itemsFromDataList.some((i) => i.value ===
|
|
6200
|
+
const alreadyIncluded = itemsFromDataList.some((i) => i.value === itemToValueFn(item));
|
|
6071
6201
|
if (alreadyIncluded)
|
|
6072
6202
|
return null;
|
|
6073
6203
|
const rendered = renderFn(item);
|
|
6204
|
+
const label = typeof rendered === 'string' ? rendered : JSON.stringify(item);
|
|
6074
6205
|
return {
|
|
6075
|
-
label
|
|
6076
|
-
|
|
6206
|
+
label,
|
|
6207
|
+
displayLabel: getDisplayString(rendered, label), // String representation for input display
|
|
6208
|
+
value: itemToValueFn(item),
|
|
6077
6209
|
raw: item,
|
|
6078
6210
|
};
|
|
6079
6211
|
})
|
|
6080
6212
|
.filter((item) => item !== null);
|
|
6081
6213
|
return [...itemsFromIdMap, ...itemsFromDataList];
|
|
6082
|
-
}, [dataList, column_ref, renderDisplay, idMapItems]);
|
|
6214
|
+
}, [dataList, column_ref, renderDisplay, idMapItems, itemToValueFn]);
|
|
6083
6215
|
// Use filter hook for combobox
|
|
6084
6216
|
const { contains } = react.useFilter({ sensitivity: 'base' });
|
|
6085
6217
|
// Create collection for combobox
|
|
6218
|
+
// itemToString uses displayLabel to show rendered display in input when selected
|
|
6086
6219
|
const { collection, filter, set } = react.useListCollection({
|
|
6087
6220
|
initialItems: comboboxItems,
|
|
6088
|
-
itemToString: (item) => item.
|
|
6221
|
+
itemToString: (item) => item.displayLabel, // Use displayLabel for selected value display
|
|
6089
6222
|
itemToValue: (item) => item.value,
|
|
6090
6223
|
filter: contains,
|
|
6091
6224
|
});
|
|
@@ -6140,6 +6273,8 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
|
|
|
6140
6273
|
idPickerLabels,
|
|
6141
6274
|
insideDialog: insideDialog ?? false,
|
|
6142
6275
|
renderDisplay,
|
|
6276
|
+
itemToValue: itemToValueFn,
|
|
6277
|
+
itemToString: itemToStringFn,
|
|
6143
6278
|
loadInitialValues: loadInitialValues ??
|
|
6144
6279
|
(async () => ({ data: { data: [], count: 0 }, idMap: {} })), // Fallback if not provided
|
|
6145
6280
|
column_ref,
|
|
@@ -6150,60 +6285,69 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
|
|
|
6150
6285
|
|
|
6151
6286
|
const IdPickerSingle = ({ column, schema, prefix, }) => {
|
|
6152
6287
|
const formI18n = useFormI18n(column, prefix, schema);
|
|
6153
|
-
const { required, gridColumn = 'span 12', gridRow = 'span 1'
|
|
6288
|
+
const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
|
|
6154
6289
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
6155
|
-
const { colLabel, currentValue, searchText, setSearchText, isLoading, isFetching, isPending, isError, isSearching,
|
|
6290
|
+
const { colLabel, currentValue, searchText, setSearchText, isLoading, isFetching, isPending, isError, isSearching, collection, filter, idMap, idPickerLabels, insideDialog, renderDisplay: renderDisplayFn, itemToValue, itemToString, errors, setValue, } = useIdPickerData({
|
|
6156
6291
|
column,
|
|
6157
6292
|
schema,
|
|
6158
6293
|
prefix,
|
|
6159
6294
|
isMultiple: false,
|
|
6160
6295
|
});
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
const
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6296
|
+
// Get the selected value for single selection display
|
|
6297
|
+
const selectedId = currentValue.length > 0 ? currentValue[0] : null;
|
|
6298
|
+
const selectedItem = selectedId
|
|
6299
|
+
? idMap[selectedId]
|
|
6300
|
+
: undefined;
|
|
6301
|
+
// Use itemToValue to get the combobox value from the selected item, or use the ID directly
|
|
6302
|
+
const comboboxValue = selectedItem
|
|
6303
|
+
? itemToString(selectedItem)
|
|
6304
|
+
: selectedId || '';
|
|
6305
|
+
// itemToString is available from the hook and can be used to get a readable string
|
|
6306
|
+
// representation of any item. The collection's itemToString is automatically used
|
|
6307
|
+
// by the combobox to display selected values.
|
|
6308
|
+
// Use useCombobox hook to control input value
|
|
6309
|
+
const combobox = react.useCombobox({
|
|
6310
|
+
collection,
|
|
6311
|
+
value: [comboboxValue],
|
|
6312
|
+
onInputValueChange(e) {
|
|
6313
|
+
setSearchText(e.inputValue);
|
|
6314
|
+
filter(e.inputValue);
|
|
6315
|
+
},
|
|
6316
|
+
onValueChange(e) {
|
|
6317
|
+
setValue(colLabel, e.value[0] || '');
|
|
6318
|
+
// Clear the input value after selection
|
|
6319
|
+
setSearchText('');
|
|
6320
|
+
},
|
|
6321
|
+
multiple: false,
|
|
6322
|
+
closeOnSelect: true,
|
|
6323
|
+
openOnClick: true,
|
|
6324
|
+
invalid: !!errors[colLabel],
|
|
6325
|
+
});
|
|
6326
|
+
// Use renderDisplay from hook (which comes from schema) or fallback to default
|
|
6327
|
+
const renderDisplayFunction = renderDisplayFn || defaultRenderDisplay;
|
|
6328
|
+
// Get the selected value for single selection display (already computed above)
|
|
6329
|
+
const selectedRendered = selectedItem
|
|
6330
|
+
? renderDisplayFunction(selectedItem)
|
|
6331
|
+
: null;
|
|
6332
|
+
return (jsxRuntime.jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
6333
|
+
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxRuntime.jsxs(react.Combobox.RootProvider, { value: combobox, width: "100%", children: [jsxRuntime.jsx(react.Show, { when: selectedId && selectedRendered, children: jsxRuntime.jsxs(react.HStack, { justifyContent: 'space-between', children: [jsxRuntime.jsx(react.Box, { children: selectedRendered }), currentValue.length > 0 && (jsxRuntime.jsx(react.Button, { variant: "ghost", size: "sm", onClick: () => {
|
|
6334
|
+
setValue(colLabel, '');
|
|
6335
|
+
}, children: jsxRuntime.jsx(react.Icon, { children: jsxRuntime.jsx(bi.BiX, {}) }) }))] }) }), jsxRuntime.jsx(react.Show, { when: !selectedId || !selectedRendered, children: jsxRuntime.jsxs(react.Combobox.Control, { position: "relative", children: [jsxRuntime.jsx(react.Combobox.Input, { placeholder: idPickerLabels?.typeToSearch ?? 'Type to search' }), jsxRuntime.jsxs(react.Combobox.IndicatorGroup, { children: [(isFetching || isLoading || isPending) && jsxRuntime.jsx(react.Spinner, { size: "xs" }), isError && (jsxRuntime.jsx(react.Icon, { color: "fg.error", children: jsxRuntime.jsx(bi.BiError, {}) })), jsxRuntime.jsx(react.Combobox.Trigger, {})] })] }) }), insideDialog ? (jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
|
|
6336
|
+
// Show skeleton items to prevent UI shift
|
|
6337
|
+
jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsxRuntime.jsx(react.Flex, { p: 2, align: "center", gap: 2, children: jsxRuntime.jsx(react.Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: searchText
|
|
6338
|
+
? idPickerLabels?.emptySearchResult ?? 'No results found'
|
|
6339
|
+
: idPickerLabels?.initialResults ??
|
|
6340
|
+
'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [renderDisplayFunction(item.raw), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
|
|
6188
6341
|
// Show skeleton items to prevent UI shift
|
|
6189
6342
|
jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsxRuntime.jsx(react.Flex, { p: 2, align: "center", gap: 2, children: jsxRuntime.jsx(react.Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: searchText
|
|
6190
6343
|
? idPickerLabels?.emptySearchResult ?? 'No results found'
|
|
6191
6344
|
: idPickerLabels?.initialResults ??
|
|
6192
|
-
'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.
|
|
6193
|
-
? renderDisplayFunction(item.raw)
|
|
6194
|
-
: item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
|
|
6195
|
-
// Show skeleton items to prevent UI shift
|
|
6196
|
-
jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsxRuntime.jsx(react.Flex, { p: 2, align: "center", gap: 2, children: jsxRuntime.jsx(react.Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: searchText
|
|
6197
|
-
? idPickerLabels?.emptySearchResult ?? 'No results found'
|
|
6198
|
-
: idPickerLabels?.initialResults ??
|
|
6199
|
-
'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: !!renderDisplayFunction === true
|
|
6200
|
-
? renderDisplayFunction(item.raw)
|
|
6201
|
-
: item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
|
|
6345
|
+
'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsx(react.Combobox.Item, { item: item, children: renderDisplayFunction(item.raw) }, item.value ?? `item-${index}`))) })) }) }) }))] }) }));
|
|
6202
6346
|
};
|
|
6203
6347
|
|
|
6204
6348
|
const IdPickerMultiple = ({ column, schema, prefix, }) => {
|
|
6205
6349
|
const formI18n = useFormI18n(column, prefix, schema);
|
|
6206
|
-
const { required, gridColumn = 'span 12', gridRow = 'span 1'
|
|
6350
|
+
const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
|
|
6207
6351
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
6208
6352
|
const { colLabel, currentValue, searchText, setSearchText, isLoading, isFetching, isPending, isError, isSearching, isLoadingInitialValues, isFetchingInitialValues, missingIds, collection, idMap, idPickerLabels, insideDialog, renderDisplay: renderDisplayFn, errors, setValue, } = useIdPickerData({
|
|
6209
6353
|
column,
|
|
@@ -6217,7 +6361,8 @@ const IdPickerMultiple = ({ column, schema, prefix, }) => {
|
|
|
6217
6361
|
const handleValueChange = (details) => {
|
|
6218
6362
|
setValue(colLabel, details.value);
|
|
6219
6363
|
};
|
|
6220
|
-
|
|
6364
|
+
// Use renderDisplay from hook (which comes from schema) or fallback to default
|
|
6365
|
+
const renderDisplayFunction = renderDisplayFn || defaultRenderDisplay;
|
|
6221
6366
|
return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
6222
6367
|
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [currentValue.length > 0 && (jsxRuntime.jsx(react.Flex, { flexFlow: 'wrap', gap: 1, mb: 2, children: currentValue.map((id) => {
|
|
6223
6368
|
const item = idMap[id];
|
|
@@ -6242,16 +6387,12 @@ const IdPickerMultiple = ({ column, schema, prefix, }) => {
|
|
|
6242
6387
|
jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsxRuntime.jsx(react.Flex, { p: 2, align: "center", gap: 2, children: jsxRuntime.jsx(react.Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: searchText
|
|
6243
6388
|
? idPickerLabels?.emptySearchResult ?? 'No results found'
|
|
6244
6389
|
: idPickerLabels?.initialResults ??
|
|
6245
|
-
'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.
|
|
6246
|
-
? renderDisplayFunction(item.raw)
|
|
6247
|
-
: item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
|
|
6390
|
+
'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [renderDisplayFunction(item.raw), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
|
|
6248
6391
|
// Show skeleton items to prevent UI shift
|
|
6249
6392
|
jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsxRuntime.jsx(react.Flex, { p: 2, align: "center", gap: 2, children: jsxRuntime.jsx(react.Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: searchText
|
|
6250
6393
|
? idPickerLabels?.emptySearchResult ?? 'No results found'
|
|
6251
6394
|
: idPickerLabels?.initialResults ??
|
|
6252
|
-
'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.
|
|
6253
|
-
? renderDisplayFunction(item.raw)
|
|
6254
|
-
: item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
|
|
6395
|
+
'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [renderDisplayFunction(item.raw), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
|
|
6255
6396
|
};
|
|
6256
6397
|
|
|
6257
6398
|
const NumberInputRoot = React__namespace.forwardRef(function NumberInput(props, ref) {
|
|
@@ -6628,11 +6769,193 @@ const TextAreaInput = ({ column, schema, prefix, }) => {
|
|
|
6628
6769
|
|
|
6629
6770
|
dayjs.extend(utc);
|
|
6630
6771
|
dayjs.extend(timezone);
|
|
6631
|
-
const TimePicker$1 = (
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6772
|
+
const TimePicker$1 = (props) => {
|
|
6773
|
+
const { format = '12h', value: controlledValue, onChange: controlledOnChange, hour: uncontrolledHour, setHour: uncontrolledSetHour, minute: uncontrolledMinute, setMinute: uncontrolledSetMinute, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, labels = {
|
|
6774
|
+
placeholder: format === '24h' ? 'HH:mm:ss' : 'hh:mm AM/PM',
|
|
6775
|
+
emptyMessage: 'No time found',
|
|
6776
|
+
}, onTimeChange, } = props;
|
|
6777
|
+
const is24Hour = format === '24h';
|
|
6778
|
+
const uncontrolledMeridiem = is24Hour ? undefined : props.meridiem;
|
|
6779
|
+
const uncontrolledSetMeridiem = is24Hour ? undefined : props.setMeridiem;
|
|
6780
|
+
const uncontrolledSecond = is24Hour ? props.second : undefined;
|
|
6781
|
+
const uncontrolledSetSecond = is24Hour ? props.setSecond : undefined;
|
|
6782
|
+
// Determine if we're in controlled mode
|
|
6783
|
+
const isControlled = controlledValue !== undefined;
|
|
6784
|
+
// Parse time string to extract hour, minute, second, meridiem
|
|
6785
|
+
const parseTimeString = (timeStr) => {
|
|
6786
|
+
if (!timeStr || !timeStr.trim()) {
|
|
6787
|
+
return { hour: null, minute: null, second: null, meridiem: null };
|
|
6788
|
+
}
|
|
6789
|
+
// Remove timezone suffix if present (e.g., "14:30:00Z" -> "14:30:00")
|
|
6790
|
+
const timeWithoutTz = timeStr.replace(/[Z+-]\d{2}:?\d{2}$/, '').trim();
|
|
6791
|
+
// Try parsing 24-hour format: "HH:mm:ss" or "HH:mm"
|
|
6792
|
+
const time24Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
|
|
6793
|
+
const match24 = timeWithoutTz.match(time24Pattern);
|
|
6794
|
+
if (match24) {
|
|
6795
|
+
const hour24 = parseInt(match24[1], 10);
|
|
6796
|
+
const minute = parseInt(match24[2], 10);
|
|
6797
|
+
const second = match24[3] ? parseInt(match24[3], 10) : 0;
|
|
6798
|
+
if (hour24 >= 0 &&
|
|
6799
|
+
hour24 <= 23 &&
|
|
6800
|
+
minute >= 0 &&
|
|
6801
|
+
minute <= 59 &&
|
|
6802
|
+
second >= 0 &&
|
|
6803
|
+
second <= 59) {
|
|
6804
|
+
if (is24Hour) {
|
|
6805
|
+
return { hour: hour24, minute, second, meridiem: null };
|
|
6806
|
+
}
|
|
6807
|
+
else {
|
|
6808
|
+
// Convert to 12-hour format
|
|
6809
|
+
let hour12 = hour24;
|
|
6810
|
+
let meridiem;
|
|
6811
|
+
if (hour24 === 0) {
|
|
6812
|
+
hour12 = 12;
|
|
6813
|
+
meridiem = 'am';
|
|
6814
|
+
}
|
|
6815
|
+
else if (hour24 === 12) {
|
|
6816
|
+
hour12 = 12;
|
|
6817
|
+
meridiem = 'pm';
|
|
6818
|
+
}
|
|
6819
|
+
else if (hour24 > 12) {
|
|
6820
|
+
hour12 = hour24 - 12;
|
|
6821
|
+
meridiem = 'pm';
|
|
6822
|
+
}
|
|
6823
|
+
else {
|
|
6824
|
+
hour12 = hour24;
|
|
6825
|
+
meridiem = 'am';
|
|
6826
|
+
}
|
|
6827
|
+
return { hour: hour12, minute, second: null, meridiem };
|
|
6828
|
+
}
|
|
6829
|
+
}
|
|
6830
|
+
}
|
|
6831
|
+
// Try parsing 12-hour format: "hh:mm AM/PM" or "hh:mm:ss AM/PM"
|
|
6832
|
+
const time12Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\s*(am|pm|AM|PM)$/i;
|
|
6833
|
+
const match12 = timeWithoutTz.match(time12Pattern);
|
|
6834
|
+
if (match12 && !is24Hour) {
|
|
6835
|
+
const hour12 = parseInt(match12[1], 10);
|
|
6836
|
+
const minute = parseInt(match12[2], 10);
|
|
6837
|
+
const second = match12[3] ? parseInt(match12[3], 10) : null;
|
|
6838
|
+
const meridiem = match12[4].toLowerCase();
|
|
6839
|
+
if (hour12 >= 1 &&
|
|
6840
|
+
hour12 <= 12 &&
|
|
6841
|
+
minute >= 0 &&
|
|
6842
|
+
minute <= 59 &&
|
|
6843
|
+
(second === null || (second >= 0 && second <= 59))) {
|
|
6844
|
+
return { hour: hour12, minute, second, meridiem };
|
|
6845
|
+
}
|
|
6846
|
+
}
|
|
6847
|
+
return { hour: null, minute: null, second: null, meridiem: null };
|
|
6848
|
+
};
|
|
6849
|
+
// Format time values to time string
|
|
6850
|
+
const formatTimeString = (hour, minute, second, meridiem) => {
|
|
6851
|
+
if (hour === null || minute === null) {
|
|
6852
|
+
return undefined;
|
|
6853
|
+
}
|
|
6854
|
+
if (is24Hour) {
|
|
6855
|
+
const h = hour.toString().padStart(2, '0');
|
|
6856
|
+
const m = minute.toString().padStart(2, '0');
|
|
6857
|
+
const s = (second ?? 0).toString().padStart(2, '0');
|
|
6858
|
+
return `${h}:${m}:${s}`;
|
|
6859
|
+
}
|
|
6860
|
+
else {
|
|
6861
|
+
if (meridiem === null) {
|
|
6862
|
+
return undefined;
|
|
6863
|
+
}
|
|
6864
|
+
const h = hour.toString();
|
|
6865
|
+
const m = minute.toString().padStart(2, '0');
|
|
6866
|
+
return `${h}:${m} ${meridiem.toUpperCase()}`;
|
|
6867
|
+
}
|
|
6868
|
+
};
|
|
6869
|
+
// Internal state for controlled mode
|
|
6870
|
+
const [internalHour, setInternalHour] = React.useState(null);
|
|
6871
|
+
const [internalMinute, setInternalMinute] = React.useState(null);
|
|
6872
|
+
const [internalSecond, setInternalSecond] = React.useState(null);
|
|
6873
|
+
const [internalMeridiem, setInternalMeridiem] = React.useState(null);
|
|
6874
|
+
// Use controlled or uncontrolled values
|
|
6875
|
+
const hour = isControlled ? internalHour : uncontrolledHour ?? null;
|
|
6876
|
+
const minute = isControlled ? internalMinute : uncontrolledMinute ?? null;
|
|
6877
|
+
const second = isControlled ? internalSecond : uncontrolledSecond ?? null;
|
|
6878
|
+
const meridiem = isControlled
|
|
6879
|
+
? internalMeridiem
|
|
6880
|
+
: uncontrolledMeridiem ?? null;
|
|
6881
|
+
// Setters that work for both modes
|
|
6882
|
+
const setHour = isControlled
|
|
6883
|
+
? setInternalHour
|
|
6884
|
+
: uncontrolledSetHour || (() => { });
|
|
6885
|
+
const setMinute = isControlled
|
|
6886
|
+
? setInternalMinute
|
|
6887
|
+
: uncontrolledSetMinute || (() => { });
|
|
6888
|
+
const setSecond = isControlled
|
|
6889
|
+
? setInternalSecond
|
|
6890
|
+
: uncontrolledSetSecond || (() => { });
|
|
6891
|
+
const setMeridiem = isControlled
|
|
6892
|
+
? setInternalMeridiem
|
|
6893
|
+
: uncontrolledSetMeridiem || (() => { });
|
|
6894
|
+
// Sync internal state with controlled value prop
|
|
6895
|
+
const prevValueRef = React.useRef(controlledValue);
|
|
6896
|
+
React.useEffect(() => {
|
|
6897
|
+
if (!isControlled)
|
|
6898
|
+
return;
|
|
6899
|
+
if (prevValueRef.current === controlledValue) {
|
|
6900
|
+
return;
|
|
6901
|
+
}
|
|
6902
|
+
prevValueRef.current = controlledValue;
|
|
6903
|
+
const parsed = parseTimeString(controlledValue);
|
|
6904
|
+
setInternalHour(parsed.hour);
|
|
6905
|
+
setInternalMinute(parsed.minute);
|
|
6906
|
+
if (is24Hour) {
|
|
6907
|
+
setInternalSecond(parsed.second);
|
|
6908
|
+
}
|
|
6909
|
+
else {
|
|
6910
|
+
setInternalMeridiem(parsed.meridiem);
|
|
6911
|
+
}
|
|
6912
|
+
}, [controlledValue, isControlled, is24Hour]);
|
|
6913
|
+
// Wrapper onChange that calls both controlled and uncontrolled onChange
|
|
6914
|
+
const handleTimeChange = (newHour, newMinute, newSecond, newMeridiem) => {
|
|
6915
|
+
if (isControlled) {
|
|
6916
|
+
const timeString = formatTimeString(newHour, newMinute, newSecond, newMeridiem);
|
|
6917
|
+
controlledOnChange?.(timeString);
|
|
6918
|
+
}
|
|
6919
|
+
else {
|
|
6920
|
+
// Call legacy onTimeChange if provided
|
|
6921
|
+
if (onTimeChange) {
|
|
6922
|
+
if (is24Hour) {
|
|
6923
|
+
const timeChange24h = onTimeChange;
|
|
6924
|
+
timeChange24h({
|
|
6925
|
+
hour: newHour,
|
|
6926
|
+
minute: newMinute,
|
|
6927
|
+
second: newSecond,
|
|
6928
|
+
});
|
|
6929
|
+
}
|
|
6930
|
+
else {
|
|
6931
|
+
const timeChange12h = onTimeChange;
|
|
6932
|
+
timeChange12h({
|
|
6933
|
+
hour: newHour,
|
|
6934
|
+
minute: newMinute,
|
|
6935
|
+
meridiem: newMeridiem,
|
|
6936
|
+
});
|
|
6937
|
+
}
|
|
6938
|
+
}
|
|
6939
|
+
}
|
|
6940
|
+
};
|
|
6941
|
+
const [inputValue, setInputValue] = React.useState('');
|
|
6942
|
+
// Sync inputValue with current time
|
|
6943
|
+
React.useEffect(() => {
|
|
6944
|
+
if (is24Hour && second !== undefined) {
|
|
6945
|
+
if (hour !== null && minute !== null && second !== null) {
|
|
6946
|
+
const formatted = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${second.toString().padStart(2, '0')}`;
|
|
6947
|
+
setInputValue(formatted);
|
|
6948
|
+
}
|
|
6949
|
+
else {
|
|
6950
|
+
setInputValue('');
|
|
6951
|
+
}
|
|
6952
|
+
}
|
|
6953
|
+
else {
|
|
6954
|
+
// 12-hour format - input is managed by combobox
|
|
6955
|
+
setInputValue('');
|
|
6956
|
+
}
|
|
6957
|
+
}, [hour, minute, second, is24Hour]);
|
|
6958
|
+
// Generate time options based on format
|
|
6636
6959
|
const timeOptions = React.useMemo(() => {
|
|
6637
6960
|
const options = [];
|
|
6638
6961
|
// Get start time for comparison if provided
|
|
@@ -6643,32 +6966,25 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6643
6966
|
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
6644
6967
|
if (startDateObj.isValid() && selectedDateObj.isValid()) {
|
|
6645
6968
|
startDateTime = startDateObj;
|
|
6646
|
-
// Only filter if dates are the same
|
|
6647
6969
|
shouldFilterByDate =
|
|
6648
6970
|
startDateObj.format('YYYY-MM-DD') ===
|
|
6649
6971
|
selectedDateObj.format('YYYY-MM-DD');
|
|
6650
6972
|
}
|
|
6651
6973
|
}
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
for (let
|
|
6655
|
-
for (
|
|
6656
|
-
//
|
|
6657
|
-
let hour24 = h;
|
|
6658
|
-
if (mer === 'am' && h === 12)
|
|
6659
|
-
hour24 = 0;
|
|
6660
|
-
else if (mer === 'pm' && h < 12)
|
|
6661
|
-
hour24 = h + 12;
|
|
6662
|
-
// Filter out times that would result in negative duration (only when dates are the same)
|
|
6974
|
+
if (is24Hour) {
|
|
6975
|
+
// Generate 24-hour format options (0-23 for hours)
|
|
6976
|
+
for (let h = 0; h < 24; h++) {
|
|
6977
|
+
for (let m = 0; m < 60; m += 15) {
|
|
6978
|
+
// Filter out times that would result in negative duration
|
|
6663
6979
|
if (startDateTime && selectedDate && shouldFilterByDate) {
|
|
6664
6980
|
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
6665
6981
|
const optionDateTime = selectedDateObj
|
|
6666
|
-
.hour(
|
|
6982
|
+
.hour(h)
|
|
6667
6983
|
.minute(m)
|
|
6668
6984
|
.second(0)
|
|
6669
6985
|
.millisecond(0);
|
|
6670
6986
|
if (optionDateTime.isBefore(startDateTime)) {
|
|
6671
|
-
continue;
|
|
6987
|
+
continue;
|
|
6672
6988
|
}
|
|
6673
6989
|
}
|
|
6674
6990
|
// Calculate duration if startTime is provided
|
|
@@ -6676,7 +6992,7 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6676
6992
|
if (startDateTime && selectedDate) {
|
|
6677
6993
|
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
6678
6994
|
const optionDateTime = selectedDateObj
|
|
6679
|
-
.hour(
|
|
6995
|
+
.hour(h)
|
|
6680
6996
|
.minute(m)
|
|
6681
6997
|
.second(0)
|
|
6682
6998
|
.millisecond(0);
|
|
@@ -6701,62 +7017,133 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6701
7017
|
}
|
|
6702
7018
|
}
|
|
6703
7019
|
}
|
|
6704
|
-
const
|
|
6705
|
-
const minuteDisplay = m.toString().padStart(2, '0');
|
|
6706
|
-
const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
|
|
7020
|
+
const timeDisplay = `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:00`;
|
|
6707
7021
|
options.push({
|
|
6708
7022
|
label: timeDisplay,
|
|
6709
|
-
value: `${h}:${m}
|
|
7023
|
+
value: `${h}:${m}:0`,
|
|
6710
7024
|
hour: h,
|
|
6711
7025
|
minute: m,
|
|
6712
|
-
|
|
6713
|
-
searchText: timeDisplay,
|
|
7026
|
+
second: 0,
|
|
7027
|
+
searchText: timeDisplay,
|
|
6714
7028
|
durationText,
|
|
6715
7029
|
});
|
|
6716
7030
|
}
|
|
6717
7031
|
}
|
|
6718
7032
|
}
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
7033
|
+
else {
|
|
7034
|
+
// Generate 12-hour format options (1-12 for hours, AM/PM)
|
|
7035
|
+
for (let h = 1; h <= 12; h++) {
|
|
7036
|
+
for (let m = 0; m < 60; m += 15) {
|
|
7037
|
+
for (const mer of ['am', 'pm']) {
|
|
7038
|
+
// Convert 12-hour to 24-hour for comparison
|
|
7039
|
+
let hour24 = h;
|
|
7040
|
+
if (mer === 'am' && h === 12)
|
|
7041
|
+
hour24 = 0;
|
|
7042
|
+
else if (mer === 'pm' && h < 12)
|
|
7043
|
+
hour24 = h + 12;
|
|
7044
|
+
// Filter out times that would result in negative duration
|
|
7045
|
+
if (startDateTime && selectedDate && shouldFilterByDate) {
|
|
7046
|
+
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
7047
|
+
const optionDateTime = selectedDateObj
|
|
7048
|
+
.hour(hour24)
|
|
7049
|
+
.minute(m)
|
|
7050
|
+
.second(0)
|
|
7051
|
+
.millisecond(0);
|
|
7052
|
+
if (optionDateTime.isBefore(startDateTime)) {
|
|
7053
|
+
continue;
|
|
7054
|
+
}
|
|
7055
|
+
}
|
|
7056
|
+
// Calculate duration if startTime is provided
|
|
7057
|
+
let durationText;
|
|
7058
|
+
if (startDateTime && selectedDate) {
|
|
7059
|
+
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
7060
|
+
const optionDateTime = selectedDateObj
|
|
7061
|
+
.hour(hour24)
|
|
7062
|
+
.minute(m)
|
|
7063
|
+
.second(0)
|
|
7064
|
+
.millisecond(0);
|
|
7065
|
+
if (optionDateTime.isValid() &&
|
|
7066
|
+
optionDateTime.isAfter(startDateTime)) {
|
|
7067
|
+
const diffMs = optionDateTime.diff(startDateTime);
|
|
7068
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
7069
|
+
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
7070
|
+
const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
|
|
7071
|
+
if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
|
|
7072
|
+
let diffText = '';
|
|
7073
|
+
if (diffHours > 0) {
|
|
7074
|
+
diffText = `${diffHours}h ${diffMinutes}m`;
|
|
7075
|
+
}
|
|
7076
|
+
else if (diffMinutes > 0) {
|
|
7077
|
+
diffText = `${diffMinutes}m ${diffSeconds}s`;
|
|
7078
|
+
}
|
|
7079
|
+
else {
|
|
7080
|
+
diffText = `${diffSeconds}s`;
|
|
7081
|
+
}
|
|
7082
|
+
durationText = `+${diffText}`;
|
|
7083
|
+
}
|
|
7084
|
+
}
|
|
7085
|
+
}
|
|
7086
|
+
const hourDisplay = h.toString();
|
|
7087
|
+
const minuteDisplay = m.toString().padStart(2, '0');
|
|
7088
|
+
const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
|
|
7089
|
+
options.push({
|
|
7090
|
+
label: timeDisplay,
|
|
7091
|
+
value: `${h}:${m}:${mer}`,
|
|
7092
|
+
hour: h,
|
|
7093
|
+
minute: m,
|
|
7094
|
+
meridiem: mer,
|
|
7095
|
+
searchText: timeDisplay,
|
|
7096
|
+
durationText,
|
|
7097
|
+
});
|
|
7098
|
+
}
|
|
7099
|
+
}
|
|
7100
|
+
}
|
|
7101
|
+
// Sort 12-hour options by time (convert to 24-hour for proper chronological sorting)
|
|
7102
|
+
return options.sort((a, b) => {
|
|
7103
|
+
const a12 = a;
|
|
7104
|
+
const b12 = b;
|
|
7105
|
+
let hour24A = a12.hour;
|
|
7106
|
+
if (a12.meridiem === 'am' && a12.hour === 12)
|
|
7107
|
+
hour24A = 0;
|
|
7108
|
+
else if (a12.meridiem === 'pm' && a12.hour < 12)
|
|
7109
|
+
hour24A = a12.hour + 12;
|
|
7110
|
+
let hour24B = b12.hour;
|
|
7111
|
+
if (b12.meridiem === 'am' && b12.hour === 12)
|
|
7112
|
+
hour24B = 0;
|
|
7113
|
+
else if (b12.meridiem === 'pm' && b12.hour < 12)
|
|
7114
|
+
hour24B = b12.hour + 12;
|
|
7115
|
+
if (hour24A !== hour24B) {
|
|
7116
|
+
return hour24A - hour24B;
|
|
7117
|
+
}
|
|
7118
|
+
return a12.minute - b12.minute;
|
|
7119
|
+
});
|
|
7120
|
+
}
|
|
7121
|
+
return options;
|
|
7122
|
+
}, [startTime, selectedDate, timezone, is24Hour]);
|
|
6739
7123
|
// itemToString returns only the clean display text (no metadata)
|
|
6740
7124
|
const itemToString = React.useMemo(() => {
|
|
6741
7125
|
return (item) => {
|
|
6742
|
-
return item.searchText;
|
|
7126
|
+
return item.searchText;
|
|
6743
7127
|
};
|
|
6744
7128
|
}, []);
|
|
6745
|
-
// Custom filter function
|
|
7129
|
+
// Custom filter function
|
|
7130
|
+
const { contains } = react.useFilter({ sensitivity: 'base' });
|
|
6746
7131
|
const customTimeFilter = React.useMemo(() => {
|
|
7132
|
+
if (is24Hour) {
|
|
7133
|
+
return contains; // Simple contains filter for 24-hour format
|
|
7134
|
+
}
|
|
7135
|
+
// For 12-hour format, support both 12-hour and 24-hour input
|
|
6747
7136
|
return (itemText, filterText) => {
|
|
6748
7137
|
if (!filterText) {
|
|
6749
|
-
return true;
|
|
7138
|
+
return true;
|
|
6750
7139
|
}
|
|
6751
7140
|
const lowerItemText = itemText.toLowerCase();
|
|
6752
7141
|
const lowerFilterText = filterText.toLowerCase();
|
|
6753
|
-
// First, try matching against the display text (12-hour format)
|
|
6754
7142
|
if (lowerItemText.includes(lowerFilterText)) {
|
|
6755
7143
|
return true;
|
|
6756
7144
|
}
|
|
6757
|
-
// Find the corresponding item to check 24-hour format matches
|
|
6758
7145
|
const item = timeOptions.find((opt) => opt.searchText.toLowerCase() === lowerItemText);
|
|
6759
|
-
if (!item) {
|
|
7146
|
+
if (!item || !('meridiem' in item)) {
|
|
6760
7147
|
return false;
|
|
6761
7148
|
}
|
|
6762
7149
|
// Convert item to 24-hour format for matching
|
|
@@ -6767,18 +7154,17 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6767
7154
|
hour24 = item.hour + 12;
|
|
6768
7155
|
const hour24Str = hour24.toString().padStart(2, '0');
|
|
6769
7156
|
const minuteStr = item.minute.toString().padStart(2, '0');
|
|
6770
|
-
// Check if filterText matches 24-hour format variations
|
|
6771
7157
|
const formats = [
|
|
6772
|
-
`${hour24Str}:${minuteStr}`,
|
|
6773
|
-
`${hour24Str}${minuteStr}`,
|
|
6774
|
-
hour24Str,
|
|
6775
|
-
`${hour24}:${minuteStr}`,
|
|
6776
|
-
hour24.toString(),
|
|
7158
|
+
`${hour24Str}:${minuteStr}`,
|
|
7159
|
+
`${hour24Str}${minuteStr}`,
|
|
7160
|
+
hour24Str,
|
|
7161
|
+
`${hour24}:${minuteStr}`,
|
|
7162
|
+
hour24.toString(),
|
|
6777
7163
|
];
|
|
6778
7164
|
return formats.some((format) => format.toLowerCase().includes(lowerFilterText) ||
|
|
6779
7165
|
lowerFilterText.includes(format.toLowerCase()));
|
|
6780
7166
|
};
|
|
6781
|
-
}, [timeOptions]);
|
|
7167
|
+
}, [timeOptions, is24Hour, contains]);
|
|
6782
7168
|
const { collection, filter } = react.useListCollection({
|
|
6783
7169
|
initialItems: timeOptions,
|
|
6784
7170
|
itemToString: itemToString,
|
|
@@ -6787,32 +7173,48 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6787
7173
|
});
|
|
6788
7174
|
// Get current value string for combobox
|
|
6789
7175
|
const currentValue = React.useMemo(() => {
|
|
6790
|
-
if (
|
|
6791
|
-
|
|
7176
|
+
if (is24Hour) {
|
|
7177
|
+
if (hour === null || minute === null || second === null) {
|
|
7178
|
+
return '';
|
|
7179
|
+
}
|
|
7180
|
+
return `${hour}:${minute}:${second}`;
|
|
7181
|
+
}
|
|
7182
|
+
else {
|
|
7183
|
+
if (hour === null || minute === null || meridiem === null) {
|
|
7184
|
+
return '';
|
|
7185
|
+
}
|
|
7186
|
+
return `${hour}:${minute}:${meridiem}`;
|
|
6792
7187
|
}
|
|
6793
|
-
|
|
6794
|
-
}, [hour, minute, meridiem]);
|
|
7188
|
+
}, [hour, minute, second, meridiem, is24Hour]);
|
|
6795
7189
|
// Calculate duration difference
|
|
6796
7190
|
const durationDiff = React.useMemo(() => {
|
|
6797
|
-
if (!startTime ||
|
|
6798
|
-
!selectedDate ||
|
|
6799
|
-
hour === null ||
|
|
6800
|
-
minute === null ||
|
|
6801
|
-
meridiem === null) {
|
|
7191
|
+
if (!startTime || !selectedDate || hour === null || minute === null) {
|
|
6802
7192
|
return null;
|
|
6803
7193
|
}
|
|
7194
|
+
if (is24Hour) {
|
|
7195
|
+
if (second === null)
|
|
7196
|
+
return null;
|
|
7197
|
+
}
|
|
7198
|
+
else {
|
|
7199
|
+
if (meridiem === null)
|
|
7200
|
+
return null;
|
|
7201
|
+
}
|
|
6804
7202
|
const startDateObj = dayjs(startTime).tz(timezone);
|
|
6805
7203
|
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
6806
|
-
// Convert
|
|
7204
|
+
// Convert to 24-hour format
|
|
6807
7205
|
let hour24 = hour;
|
|
6808
|
-
if (
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
7206
|
+
if (!is24Hour && meridiem) {
|
|
7207
|
+
if (meridiem === 'am' && hour === 12)
|
|
7208
|
+
hour24 = 0;
|
|
7209
|
+
else if (meridiem === 'pm' && hour < 12)
|
|
7210
|
+
hour24 = hour + 12;
|
|
7211
|
+
}
|
|
6812
7212
|
const currentDateTime = selectedDateObj
|
|
6813
7213
|
.hour(hour24)
|
|
6814
7214
|
.minute(minute)
|
|
6815
|
-
.second(
|
|
7215
|
+
.second(is24Hour && second !== null && second !== undefined
|
|
7216
|
+
? second
|
|
7217
|
+
: 0)
|
|
6816
7218
|
.millisecond(0);
|
|
6817
7219
|
if (!startDateObj.isValid() || !currentDateTime.isValid()) {
|
|
6818
7220
|
return null;
|
|
@@ -6838,13 +7240,28 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6838
7240
|
return `+${diffText}`;
|
|
6839
7241
|
}
|
|
6840
7242
|
return null;
|
|
6841
|
-
}, [
|
|
7243
|
+
}, [
|
|
7244
|
+
hour,
|
|
7245
|
+
minute,
|
|
7246
|
+
second,
|
|
7247
|
+
meridiem,
|
|
7248
|
+
startTime,
|
|
7249
|
+
selectedDate,
|
|
7250
|
+
timezone,
|
|
7251
|
+
is24Hour,
|
|
7252
|
+
]);
|
|
6842
7253
|
const handleClear = () => {
|
|
6843
7254
|
setHour(null);
|
|
6844
7255
|
setMinute(null);
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
7256
|
+
if (is24Hour && setSecond) {
|
|
7257
|
+
setSecond(null);
|
|
7258
|
+
handleTimeChange(null, null, null, null);
|
|
7259
|
+
}
|
|
7260
|
+
else if (!is24Hour && setMeridiem) {
|
|
7261
|
+
setMeridiem(null);
|
|
7262
|
+
handleTimeChange(null, null, null, null);
|
|
7263
|
+
}
|
|
7264
|
+
filter('');
|
|
6848
7265
|
};
|
|
6849
7266
|
const handleValueChange = (details) => {
|
|
6850
7267
|
if (details.value.length === 0) {
|
|
@@ -6856,112 +7273,165 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6856
7273
|
if (selectedOption) {
|
|
6857
7274
|
setHour(selectedOption.hour);
|
|
6858
7275
|
setMinute(selectedOption.minute);
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
}
|
|
7276
|
+
filter('');
|
|
7277
|
+
if (is24Hour) {
|
|
7278
|
+
const opt24 = selectedOption;
|
|
7279
|
+
if (setSecond)
|
|
7280
|
+
setSecond(opt24.second);
|
|
7281
|
+
handleTimeChange(opt24.hour, opt24.minute, opt24.second, null);
|
|
7282
|
+
}
|
|
7283
|
+
else {
|
|
7284
|
+
const opt12 = selectedOption;
|
|
7285
|
+
if (setMeridiem)
|
|
7286
|
+
setMeridiem(opt12.meridiem);
|
|
7287
|
+
handleTimeChange(opt12.hour, opt12.minute, null, opt12.meridiem);
|
|
7288
|
+
}
|
|
6866
7289
|
}
|
|
6867
7290
|
};
|
|
6868
7291
|
// Parse input value and update state
|
|
6869
7292
|
const parseAndCommitInput = (value) => {
|
|
6870
7293
|
const trimmedValue = value.trim();
|
|
6871
|
-
// Filter the collection based on input
|
|
6872
7294
|
filter(trimmedValue);
|
|
6873
7295
|
if (!trimmedValue) {
|
|
6874
7296
|
return;
|
|
6875
7297
|
}
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
meridiem = 'pm';
|
|
6897
|
-
}
|
|
6898
|
-
else if (parsedHour24 > 12) {
|
|
6899
|
-
hour12 = parsedHour24 - 12;
|
|
6900
|
-
meridiem = 'pm';
|
|
7298
|
+
if (is24Hour) {
|
|
7299
|
+
// Parse 24-hour format: "HH:mm:ss" or "HH:mm" or "HHmmss" or "HHmm"
|
|
7300
|
+
const timePattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
|
|
7301
|
+
const match = trimmedValue.match(timePattern);
|
|
7302
|
+
if (match) {
|
|
7303
|
+
const parsedHour = parseInt(match[1], 10);
|
|
7304
|
+
const parsedMinute = parseInt(match[2], 10);
|
|
7305
|
+
const parsedSecond = match[3] ? parseInt(match[3], 10) : 0;
|
|
7306
|
+
if (parsedHour >= 0 &&
|
|
7307
|
+
parsedHour <= 23 &&
|
|
7308
|
+
parsedMinute >= 0 &&
|
|
7309
|
+
parsedMinute <= 59 &&
|
|
7310
|
+
parsedSecond >= 0 &&
|
|
7311
|
+
parsedSecond <= 59) {
|
|
7312
|
+
setHour(parsedHour);
|
|
7313
|
+
setMinute(parsedMinute);
|
|
7314
|
+
if (setSecond)
|
|
7315
|
+
setSecond(parsedSecond);
|
|
7316
|
+
handleTimeChange(parsedHour, parsedMinute, parsedSecond, null);
|
|
7317
|
+
return;
|
|
6901
7318
|
}
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
7319
|
+
}
|
|
7320
|
+
// Try numbers only format: "123045" or "1230"
|
|
7321
|
+
const numbersOnly = trimmedValue.replace(/[^0-9]/g, '');
|
|
7322
|
+
if (numbersOnly.length >= 4) {
|
|
7323
|
+
const parsedHour = parseInt(numbersOnly.slice(0, 2), 10);
|
|
7324
|
+
const parsedMinute = parseInt(numbersOnly.slice(2, 4), 10);
|
|
7325
|
+
const parsedSecond = numbersOnly.length >= 6 ? parseInt(numbersOnly.slice(4, 6), 10) : 0;
|
|
7326
|
+
if (parsedHour >= 0 &&
|
|
7327
|
+
parsedHour <= 23 &&
|
|
7328
|
+
parsedMinute >= 0 &&
|
|
7329
|
+
parsedMinute <= 59 &&
|
|
7330
|
+
parsedSecond >= 0 &&
|
|
7331
|
+
parsedSecond <= 59) {
|
|
7332
|
+
setHour(parsedHour);
|
|
7333
|
+
setMinute(parsedMinute);
|
|
7334
|
+
if (setSecond)
|
|
7335
|
+
setSecond(parsedSecond);
|
|
7336
|
+
handleTimeChange(parsedHour, parsedMinute, parsedSecond, null);
|
|
7337
|
+
return;
|
|
6905
7338
|
}
|
|
6906
|
-
setHour(hour12);
|
|
6907
|
-
setMinute(parsedMinute);
|
|
6908
|
-
setMeridiem(meridiem);
|
|
6909
|
-
onChange({
|
|
6910
|
-
hour: hour12,
|
|
6911
|
-
minute: parsedMinute,
|
|
6912
|
-
meridiem: meridiem,
|
|
6913
|
-
});
|
|
6914
|
-
return;
|
|
6915
7339
|
}
|
|
6916
7340
|
}
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
7341
|
+
else {
|
|
7342
|
+
// Parse 24-hour format first (e.g., "13:30", "14:00", "1330", "1400")
|
|
7343
|
+
const timePattern24Hour = /^(\d{1,2}):?(\d{2})$/;
|
|
7344
|
+
const match24Hour = trimmedValue.match(timePattern24Hour);
|
|
7345
|
+
if (match24Hour) {
|
|
7346
|
+
const parsedHour24 = parseInt(match24Hour[1], 10);
|
|
7347
|
+
const parsedMinute = parseInt(match24Hour[2], 10);
|
|
7348
|
+
if (parsedHour24 >= 0 &&
|
|
7349
|
+
parsedHour24 <= 23 &&
|
|
7350
|
+
parsedMinute >= 0 &&
|
|
7351
|
+
parsedMinute <= 59) {
|
|
7352
|
+
// Convert 24-hour to 12-hour format
|
|
7353
|
+
let hour12;
|
|
7354
|
+
let meridiem;
|
|
7355
|
+
if (parsedHour24 === 0) {
|
|
7356
|
+
hour12 = 12;
|
|
7357
|
+
meridiem = 'am';
|
|
7358
|
+
}
|
|
7359
|
+
else if (parsedHour24 === 12) {
|
|
7360
|
+
hour12 = 12;
|
|
7361
|
+
meridiem = 'pm';
|
|
7362
|
+
}
|
|
7363
|
+
else if (parsedHour24 > 12) {
|
|
7364
|
+
hour12 = parsedHour24 - 12;
|
|
7365
|
+
meridiem = 'pm';
|
|
7366
|
+
}
|
|
7367
|
+
else {
|
|
7368
|
+
hour12 = parsedHour24;
|
|
7369
|
+
meridiem = 'am';
|
|
7370
|
+
}
|
|
7371
|
+
setHour(hour12);
|
|
7372
|
+
setMinute(parsedMinute);
|
|
7373
|
+
if (setMeridiem)
|
|
7374
|
+
setMeridiem(meridiem);
|
|
7375
|
+
handleTimeChange(hour12, parsedMinute, null, meridiem);
|
|
7376
|
+
return;
|
|
7377
|
+
}
|
|
6938
7378
|
}
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
if (numbersOnly.length >= 3) {
|
|
6947
|
-
const parsedHour = parseInt(numbersOnly.slice(0, -2), 10);
|
|
6948
|
-
const parsedMinute = parseInt(numbersOnly.slice(-2), 10);
|
|
6949
|
-
// Validate ranges
|
|
7379
|
+
// Parse formats like "1:30 PM", "1:30PM", "1:30 pm", "1:30pm"
|
|
7380
|
+
const timePattern12Hour = /^(\d{1,2}):(\d{1,2})\s*(am|pm|AM|PM)$/i;
|
|
7381
|
+
const match12Hour = trimmedValue.match(timePattern12Hour);
|
|
7382
|
+
if (match12Hour) {
|
|
7383
|
+
const parsedHour = parseInt(match12Hour[1], 10);
|
|
7384
|
+
const parsedMinute = parseInt(match12Hour[2], 10);
|
|
7385
|
+
const parsedMeridiem = match12Hour[3].toLowerCase();
|
|
6950
7386
|
if (parsedHour >= 1 &&
|
|
6951
7387
|
parsedHour <= 12 &&
|
|
6952
7388
|
parsedMinute >= 0 &&
|
|
6953
7389
|
parsedMinute <= 59) {
|
|
6954
7390
|
setHour(parsedHour);
|
|
6955
7391
|
setMinute(parsedMinute);
|
|
6956
|
-
setMeridiem
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
7392
|
+
if (setMeridiem)
|
|
7393
|
+
setMeridiem(parsedMeridiem);
|
|
7394
|
+
handleTimeChange(parsedHour, parsedMinute, null, parsedMeridiem);
|
|
7395
|
+
return;
|
|
7396
|
+
}
|
|
7397
|
+
}
|
|
7398
|
+
// Parse formats like "12am" or "1pm" (hour only with meridiem, no minutes)
|
|
7399
|
+
const timePatternHourOnly = /^(\d{1,2})\s*(am|pm|AM|PM)$/i;
|
|
7400
|
+
const matchHourOnly = trimmedValue.match(timePatternHourOnly);
|
|
7401
|
+
if (matchHourOnly) {
|
|
7402
|
+
const parsedHour = parseInt(matchHourOnly[1], 10);
|
|
7403
|
+
const parsedMeridiem = matchHourOnly[2].toLowerCase();
|
|
7404
|
+
if (parsedHour >= 1 && parsedHour <= 12) {
|
|
7405
|
+
setHour(parsedHour);
|
|
7406
|
+
setMinute(0); // Default to 0 minutes when only hour is provided
|
|
7407
|
+
if (setMeridiem)
|
|
7408
|
+
setMeridiem(parsedMeridiem);
|
|
7409
|
+
handleTimeChange(parsedHour, 0, null, parsedMeridiem);
|
|
6962
7410
|
return;
|
|
6963
7411
|
}
|
|
6964
7412
|
}
|
|
7413
|
+
// Try to parse formats like "130pm" or "130 pm" (without colon, with minutes)
|
|
7414
|
+
const timePatternNoColon = /^(\d{1,4})\s*(am|pm|AM|PM)$/i;
|
|
7415
|
+
const matchNoColon = trimmedValue.match(timePatternNoColon);
|
|
7416
|
+
if (matchNoColon) {
|
|
7417
|
+
const numbersOnly = matchNoColon[1];
|
|
7418
|
+
const parsedMeridiem = matchNoColon[2].toLowerCase();
|
|
7419
|
+
if (numbersOnly.length >= 3) {
|
|
7420
|
+
const parsedHour = parseInt(numbersOnly.slice(0, -2), 10);
|
|
7421
|
+
const parsedMinute = parseInt(numbersOnly.slice(-2), 10);
|
|
7422
|
+
if (parsedHour >= 1 &&
|
|
7423
|
+
parsedHour <= 12 &&
|
|
7424
|
+
parsedMinute >= 0 &&
|
|
7425
|
+
parsedMinute <= 59) {
|
|
7426
|
+
setHour(parsedHour);
|
|
7427
|
+
setMinute(parsedMinute);
|
|
7428
|
+
if (setMeridiem)
|
|
7429
|
+
setMeridiem(parsedMeridiem);
|
|
7430
|
+
handleTimeChange(parsedHour, parsedMinute, null, parsedMeridiem);
|
|
7431
|
+
return;
|
|
7432
|
+
}
|
|
7433
|
+
}
|
|
7434
|
+
}
|
|
6965
7435
|
}
|
|
6966
7436
|
// Parse failed, select first result
|
|
6967
7437
|
selectFirstResult();
|
|
@@ -6972,55 +7442,84 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
|
|
|
6972
7442
|
const firstItem = collection.items[0];
|
|
6973
7443
|
setHour(firstItem.hour);
|
|
6974
7444
|
setMinute(firstItem.minute);
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
}
|
|
7445
|
+
filter('');
|
|
7446
|
+
if (is24Hour) {
|
|
7447
|
+
const opt24 = firstItem;
|
|
7448
|
+
if (setSecond)
|
|
7449
|
+
setSecond(opt24.second);
|
|
7450
|
+
handleTimeChange(opt24.hour, opt24.minute, opt24.second, null);
|
|
7451
|
+
}
|
|
7452
|
+
else {
|
|
7453
|
+
const opt12 = firstItem;
|
|
7454
|
+
if (setMeridiem)
|
|
7455
|
+
setMeridiem(opt12.meridiem);
|
|
7456
|
+
handleTimeChange(opt12.hour, opt12.minute, null, opt12.meridiem);
|
|
7457
|
+
}
|
|
6982
7458
|
}
|
|
6983
7459
|
};
|
|
6984
7460
|
const handleInputValueChange = (details) => {
|
|
6985
|
-
|
|
7461
|
+
if (is24Hour) {
|
|
7462
|
+
setInputValue(details.inputValue);
|
|
7463
|
+
}
|
|
6986
7464
|
filter(details.inputValue);
|
|
6987
7465
|
};
|
|
6988
7466
|
const handleFocus = (e) => {
|
|
6989
|
-
// Select all text when focusing
|
|
6990
7467
|
e.target.select();
|
|
6991
7468
|
};
|
|
6992
7469
|
const handleBlur = (e) => {
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
7470
|
+
const inputVal = e.target.value;
|
|
7471
|
+
if (is24Hour) {
|
|
7472
|
+
setInputValue(inputVal);
|
|
7473
|
+
}
|
|
7474
|
+
if (inputVal) {
|
|
7475
|
+
parseAndCommitInput(inputVal);
|
|
6997
7476
|
}
|
|
6998
7477
|
};
|
|
6999
7478
|
const handleKeyDown = (e) => {
|
|
7000
|
-
// Commit input on Enter key
|
|
7001
7479
|
if (e.key === 'Enter') {
|
|
7002
7480
|
e.preventDefault();
|
|
7003
|
-
const
|
|
7004
|
-
if (
|
|
7005
|
-
|
|
7481
|
+
const inputVal = e.currentTarget.value;
|
|
7482
|
+
if (is24Hour) {
|
|
7483
|
+
setInputValue(inputVal);
|
|
7484
|
+
}
|
|
7485
|
+
if (inputVal) {
|
|
7486
|
+
parseAndCommitInput(inputVal);
|
|
7006
7487
|
}
|
|
7007
|
-
// Blur the input
|
|
7008
7488
|
e.currentTarget?.blur();
|
|
7009
7489
|
}
|
|
7010
7490
|
};
|
|
7011
|
-
return (jsxRuntime.jsx(react.Flex, { direction: "column", gap: 3, children: jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace",
|
|
7491
|
+
return (jsxRuntime.jsx(react.Flex, { direction: "column", gap: 3, children: jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", flex: 1, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { value: is24Hour ? inputValue : undefined, placeholder: labels?.placeholder ?? (is24Hour ? 'HH:mm:ss' : 'hh:mm AM/PM'), onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsxRuntime.jsx(react.Combobox.IndicatorGroup, { children: jsxRuntime.jsx(react.Combobox.Trigger, {}) })] }), jsxRuntime.jsx(react.Portal, { disabled: !portalled, children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [jsxRuntime.jsx(react.Combobox.Empty, { children: labels?.emptyMessage ?? 'No time found' }), collection.items.map((item) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsxRuntime.jsx(react.Text, { flex: 1, children: item.label }), item.durationText && (jsxRuntime.jsx(react.Tag.Root, { size: "sm", children: jsxRuntime.jsx(react.Tag.Label, { children: item.durationText }) }))] }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsxRuntime.jsx(react.Tag.Root, { size: "sm", children: jsxRuntime.jsx(react.Tag.Label, { children: durationDiff }) }))] }) }));
|
|
7012
7492
|
};
|
|
7013
7493
|
|
|
7014
7494
|
dayjs.extend(timezone);
|
|
7015
7495
|
const TimePicker = ({ column, schema, prefix }) => {
|
|
7016
7496
|
const { watch, formState: { errors }, setValue, } = reactHookForm.useFormContext();
|
|
7017
7497
|
const { timezone, insideDialog, timePickerLabels } = useSchemaContext();
|
|
7018
|
-
const { required, gridColumn = 'span 12', gridRow = 'span 1', timeFormat = 'HH:mm:ssZ', displayTimeFormat = 'hh:mm A', } = schema;
|
|
7498
|
+
const { required, gridColumn = 'span 12', gridRow = 'span 1', timeFormat = 'HH:mm:ssZ', displayTimeFormat = 'hh:mm A', startTimeField, selectedDateField, } = schema;
|
|
7019
7499
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
7020
7500
|
const colLabel = `${prefix}${column}`;
|
|
7021
7501
|
const formI18n = useFormI18n(column, prefix, schema);
|
|
7022
7502
|
const [open, setOpen] = React.useState(false);
|
|
7023
7503
|
const value = watch(colLabel);
|
|
7504
|
+
// Watch startTime and selectedDate fields for offset calculation
|
|
7505
|
+
const startTimeValue = startTimeField
|
|
7506
|
+
? watch(`${prefix}${startTimeField}`)
|
|
7507
|
+
: undefined;
|
|
7508
|
+
const selectedDateValue = selectedDateField
|
|
7509
|
+
? watch(`${prefix}${selectedDateField}`)
|
|
7510
|
+
: undefined;
|
|
7511
|
+
// Convert to ISO string format for startTime if it's a date-time string
|
|
7512
|
+
const startTime = startTimeValue
|
|
7513
|
+
? dayjs(startTimeValue).tz(timezone).isValid()
|
|
7514
|
+
? dayjs(startTimeValue).tz(timezone).toISOString()
|
|
7515
|
+
: undefined
|
|
7516
|
+
: undefined;
|
|
7517
|
+
// Convert selectedDate to YYYY-MM-DD format
|
|
7518
|
+
const selectedDate = selectedDateValue
|
|
7519
|
+
? dayjs(selectedDateValue).tz(timezone).isValid()
|
|
7520
|
+
? dayjs(selectedDateValue).tz(timezone).format('YYYY-MM-DD')
|
|
7521
|
+
: undefined
|
|
7522
|
+
: undefined;
|
|
7024
7523
|
const displayedTime = dayjs(`1970-01-01T${value}`).tz(timezone).isValid()
|
|
7025
7524
|
? dayjs(`1970-01-01T${value}`).tz(timezone).format(displayTimeFormat)
|
|
7026
7525
|
: '';
|
|
@@ -7076,941 +7575,860 @@ const TimePicker = ({ column, schema, prefix }) => {
|
|
|
7076
7575
|
return (jsxRuntime.jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
7077
7576
|
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxRuntime.jsxs(react.Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
|
|
7078
7577
|
setOpen(true);
|
|
7079
|
-
}, justifyContent: 'start', children: [jsxRuntime.jsx(io.IoMdClock, {}), !!value ? `${displayedTime}` : ''] }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { maxH: "70vh", overflowY: "auto", children: jsxRuntime.jsx(react.Popover.Body, { overflow: "visible", children: jsxRuntime.jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, labels: timePickerLabels }) }) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { children: jsxRuntime.jsx(react.Popover.Body, { children: jsxRuntime.jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, labels: timePickerLabels }) }) }) }) }))] }) }));
|
|
7578
|
+
}, justifyContent: 'start', children: [jsxRuntime.jsx(io.IoMdClock, {}), !!value ? `${displayedTime}` : ''] }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { maxH: "70vh", overflowY: "auto", children: jsxRuntime.jsx(react.Popover.Body, { overflow: "visible", children: jsxRuntime.jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: startTime, selectedDate: selectedDate, timezone: timezone, portalled: false, labels: timePickerLabels }) }) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { children: jsxRuntime.jsx(react.Popover.Body, { children: jsxRuntime.jsx(TimePicker$1, { format: "12h", hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: startTime, selectedDate: selectedDate, timezone: timezone, portalled: false, labels: timePickerLabels }) }) }) }) }))] }) }));
|
|
7080
7579
|
};
|
|
7081
7580
|
|
|
7082
7581
|
dayjs.extend(utc);
|
|
7083
7582
|
dayjs.extend(timezone);
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
7128
|
-
const optionDateTime = selectedDateObj
|
|
7129
|
-
.hour(h)
|
|
7130
|
-
.minute(m)
|
|
7131
|
-
.second(0)
|
|
7132
|
-
.millisecond(0);
|
|
7133
|
-
if (optionDateTime.isValid() &&
|
|
7134
|
-
optionDateTime.isAfter(startDateTime)) {
|
|
7135
|
-
const diffMs = optionDateTime.diff(startDateTime);
|
|
7136
|
-
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
7137
|
-
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
7138
|
-
const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
|
|
7139
|
-
if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
|
|
7140
|
-
let diffText = '';
|
|
7141
|
-
if (diffHours > 0) {
|
|
7142
|
-
diffText = `${diffHours}h ${diffMinutes}m`;
|
|
7143
|
-
}
|
|
7144
|
-
else if (diffMinutes > 0) {
|
|
7145
|
-
diffText = `${diffMinutes}m ${diffSeconds}s`;
|
|
7146
|
-
}
|
|
7147
|
-
else {
|
|
7148
|
-
diffText = `${diffSeconds}s`;
|
|
7149
|
-
}
|
|
7150
|
-
durationText = `+${diffText}`;
|
|
7151
|
-
}
|
|
7152
|
-
}
|
|
7153
|
-
}
|
|
7154
|
-
options.push({
|
|
7155
|
-
label: timeDisplay,
|
|
7156
|
-
value: `${h}:${m}:0`,
|
|
7157
|
-
hour: h,
|
|
7158
|
-
minute: m,
|
|
7159
|
-
second: 0,
|
|
7160
|
-
searchText: timeDisplay, // Use base time without duration for searching
|
|
7161
|
-
durationText,
|
|
7162
|
-
});
|
|
7163
|
-
}
|
|
7164
|
-
}
|
|
7165
|
-
return options;
|
|
7166
|
-
}, [startTime, selectedDate, timezone]);
|
|
7167
|
-
const { contains } = react.useFilter({ sensitivity: 'base' });
|
|
7168
|
-
const { collection, filter } = react.useListCollection({
|
|
7169
|
-
initialItems: timeOptions,
|
|
7170
|
-
itemToString: (item) => item.searchText, // Use searchText (without duration) for filtering
|
|
7171
|
-
itemToValue: (item) => item.value,
|
|
7172
|
-
filter: contains,
|
|
7173
|
-
});
|
|
7174
|
-
// Get current value string for combobox
|
|
7175
|
-
const currentValue = React.useMemo(() => {
|
|
7176
|
-
if (hour === null || minute === null || second === null) {
|
|
7177
|
-
return '';
|
|
7178
|
-
}
|
|
7179
|
-
return `${hour}:${minute}:${second}`;
|
|
7180
|
-
}, [hour, minute, second]);
|
|
7181
|
-
// Calculate duration difference
|
|
7182
|
-
const durationDiff = React.useMemo(() => {
|
|
7183
|
-
if (!startTime ||
|
|
7184
|
-
!selectedDate ||
|
|
7185
|
-
hour === null ||
|
|
7186
|
-
minute === null ||
|
|
7187
|
-
second === null) {
|
|
7583
|
+
dayjs.extend(customParseFormat);
|
|
7584
|
+
function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds = false, labels = {
|
|
7585
|
+
monthNamesShort: [
|
|
7586
|
+
'January',
|
|
7587
|
+
'February',
|
|
7588
|
+
'March',
|
|
7589
|
+
'April',
|
|
7590
|
+
'May',
|
|
7591
|
+
'June',
|
|
7592
|
+
'July',
|
|
7593
|
+
'August',
|
|
7594
|
+
'September',
|
|
7595
|
+
'October',
|
|
7596
|
+
'November',
|
|
7597
|
+
'December',
|
|
7598
|
+
],
|
|
7599
|
+
weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
|
7600
|
+
backButtonLabel: 'Back',
|
|
7601
|
+
forwardButtonLabel: 'Forward',
|
|
7602
|
+
}, timePickerLabels, timezone: tz = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, defaultDate, defaultTime, showQuickActions = false, quickActionLabels = {
|
|
7603
|
+
yesterday: 'Yesterday',
|
|
7604
|
+
today: 'Today',
|
|
7605
|
+
tomorrow: 'Tomorrow',
|
|
7606
|
+
plus7Days: '+7 Days',
|
|
7607
|
+
}, showTimezoneSelector = false, }) {
|
|
7608
|
+
const is24Hour = format === 'iso-date-time' || showSeconds;
|
|
7609
|
+
const { monthNamesShort = [
|
|
7610
|
+
'January',
|
|
7611
|
+
'February',
|
|
7612
|
+
'March',
|
|
7613
|
+
'April',
|
|
7614
|
+
'May',
|
|
7615
|
+
'June',
|
|
7616
|
+
'July',
|
|
7617
|
+
'August',
|
|
7618
|
+
'September',
|
|
7619
|
+
'October',
|
|
7620
|
+
'November',
|
|
7621
|
+
'December',
|
|
7622
|
+
], weekdayNamesShort = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], backButtonLabel = 'Back', forwardButtonLabel = 'Forward', } = labels;
|
|
7623
|
+
// Parse value to get date and time
|
|
7624
|
+
const parsedValue = React.useMemo(() => {
|
|
7625
|
+
if (!value)
|
|
7188
7626
|
return null;
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
7192
|
-
const currentDateTime = selectedDateObj
|
|
7193
|
-
.hour(hour)
|
|
7194
|
-
.minute(minute)
|
|
7195
|
-
.second(second ?? 0)
|
|
7196
|
-
.millisecond(0);
|
|
7197
|
-
if (!startDateObj.isValid() || !currentDateTime.isValid()) {
|
|
7627
|
+
const dateObj = dayjs(value).tz(tz);
|
|
7628
|
+
if (!dateObj.isValid())
|
|
7198
7629
|
return null;
|
|
7630
|
+
return dateObj;
|
|
7631
|
+
}, [value, tz]);
|
|
7632
|
+
// Initialize date state
|
|
7633
|
+
const [selectedDate, setSelectedDate] = React.useState(() => {
|
|
7634
|
+
if (parsedValue) {
|
|
7635
|
+
return parsedValue.toDate();
|
|
7199
7636
|
}
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
return
|
|
7637
|
+
if (defaultDate) {
|
|
7638
|
+
const defaultDateObj = dayjs(defaultDate).tz(tz);
|
|
7639
|
+
return defaultDateObj.isValid() ? defaultDateObj.toDate() : new Date();
|
|
7203
7640
|
}
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
diffText = `${diffMinutes}m ${diffSeconds}s`;
|
|
7214
|
-
}
|
|
7215
|
-
else {
|
|
7216
|
-
diffText = `${diffSeconds}s`;
|
|
7217
|
-
}
|
|
7218
|
-
return `+${diffText}`;
|
|
7641
|
+
return new Date();
|
|
7642
|
+
});
|
|
7643
|
+
// Initialize time state
|
|
7644
|
+
const [hour, setHour] = React.useState(() => {
|
|
7645
|
+
if (parsedValue) {
|
|
7646
|
+
return parsedValue.hour();
|
|
7647
|
+
}
|
|
7648
|
+
if (defaultTime?.hour !== null && defaultTime?.hour !== undefined) {
|
|
7649
|
+
return defaultTime.hour;
|
|
7219
7650
|
}
|
|
7220
7651
|
return null;
|
|
7221
|
-
}
|
|
7222
|
-
const
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
setSecond(null);
|
|
7226
|
-
filter(''); // Reset filter to show all options
|
|
7227
|
-
onChange({ hour: null, minute: null, second: null });
|
|
7228
|
-
};
|
|
7229
|
-
const handleValueChange = (details) => {
|
|
7230
|
-
if (details.value.length === 0) {
|
|
7231
|
-
handleClear();
|
|
7232
|
-
return;
|
|
7652
|
+
});
|
|
7653
|
+
const [minute, setMinute] = React.useState(() => {
|
|
7654
|
+
if (parsedValue) {
|
|
7655
|
+
return parsedValue.minute();
|
|
7233
7656
|
}
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
if (selectedOption) {
|
|
7237
|
-
setHour(selectedOption.hour);
|
|
7238
|
-
setMinute(selectedOption.minute);
|
|
7239
|
-
setSecond(selectedOption.second);
|
|
7240
|
-
filter(''); // Reset filter after selection
|
|
7241
|
-
onChange({
|
|
7242
|
-
hour: selectedOption.hour,
|
|
7243
|
-
minute: selectedOption.minute,
|
|
7244
|
-
second: selectedOption.second,
|
|
7245
|
-
});
|
|
7657
|
+
if (defaultTime?.minute !== null && defaultTime?.minute !== undefined) {
|
|
7658
|
+
return defaultTime.minute;
|
|
7246
7659
|
}
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
const
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
if (
|
|
7660
|
+
return null;
|
|
7661
|
+
});
|
|
7662
|
+
const [second, setSecond] = React.useState(() => {
|
|
7663
|
+
if (parsedValue) {
|
|
7664
|
+
return parsedValue.second();
|
|
7665
|
+
}
|
|
7666
|
+
if (defaultTime?.second !== null && defaultTime?.second !== undefined) {
|
|
7667
|
+
return defaultTime.second;
|
|
7668
|
+
}
|
|
7669
|
+
return showSeconds ? 0 : null;
|
|
7670
|
+
});
|
|
7671
|
+
const [meridiem, setMeridiem] = React.useState(() => {
|
|
7672
|
+
if (parsedValue) {
|
|
7673
|
+
const h = parsedValue.hour();
|
|
7674
|
+
return h < 12 ? 'am' : 'pm';
|
|
7675
|
+
}
|
|
7676
|
+
if (defaultTime?.meridiem !== null && defaultTime?.meridiem !== undefined) {
|
|
7677
|
+
return defaultTime.meridiem;
|
|
7678
|
+
}
|
|
7679
|
+
return is24Hour ? null : 'am';
|
|
7680
|
+
});
|
|
7681
|
+
// Popover state - separate for date, time, and timezone
|
|
7682
|
+
const [datePopoverOpen, setDatePopoverOpen] = React.useState(false);
|
|
7683
|
+
const [timePopoverOpen, setTimePopoverOpen] = React.useState(false);
|
|
7684
|
+
const [timezonePopoverOpen, setTimezonePopoverOpen] = React.useState(false);
|
|
7685
|
+
const [calendarPopoverOpen, setCalendarPopoverOpen] = React.useState(false);
|
|
7686
|
+
// Timezone offset state
|
|
7687
|
+
const [timezoneOffset, setTimezoneOffset] = React.useState(() => {
|
|
7688
|
+
if (parsedValue) {
|
|
7689
|
+
return parsedValue.format('Z');
|
|
7690
|
+
}
|
|
7691
|
+
// Default to +08:00
|
|
7692
|
+
return '+08:00';
|
|
7693
|
+
});
|
|
7694
|
+
// Sync timezone offset when value changes
|
|
7695
|
+
// Generate timezone offset options (UTC-12 to UTC+14)
|
|
7696
|
+
const timezoneOffsetOptions = React.useMemo(() => {
|
|
7697
|
+
const options = [];
|
|
7698
|
+
for (let offset = -12; offset <= 14; offset++) {
|
|
7699
|
+
const sign = offset >= 0 ? '+' : '-';
|
|
7700
|
+
const hours = Math.abs(offset).toString().padStart(2, '0');
|
|
7701
|
+
const value = `${sign}${hours}:00`;
|
|
7702
|
+
const label = `UTC${sign}${hours}:00`;
|
|
7703
|
+
options.push({ value, label });
|
|
7704
|
+
}
|
|
7705
|
+
return options;
|
|
7706
|
+
}, []);
|
|
7707
|
+
// Create collection for Select
|
|
7708
|
+
const { collection: timezoneCollection } = react.useListCollection({
|
|
7709
|
+
initialItems: timezoneOffsetOptions,
|
|
7710
|
+
itemToString: (item) => item.label,
|
|
7711
|
+
itemToValue: (item) => item.value,
|
|
7712
|
+
});
|
|
7713
|
+
// Date input state
|
|
7714
|
+
const [dateInputValue, setDateInputValue] = React.useState('');
|
|
7715
|
+
// Sync date input value with selected date
|
|
7716
|
+
React.useEffect(() => {
|
|
7717
|
+
if (selectedDate) {
|
|
7718
|
+
const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
|
|
7719
|
+
setDateInputValue(formatted);
|
|
7720
|
+
}
|
|
7721
|
+
else {
|
|
7722
|
+
setDateInputValue('');
|
|
7723
|
+
}
|
|
7724
|
+
}, [selectedDate, tz]);
|
|
7725
|
+
// Parse and validate date input
|
|
7726
|
+
const parseAndValidateDateInput = (inputVal) => {
|
|
7727
|
+
// If empty, clear the value
|
|
7728
|
+
if (!inputVal.trim()) {
|
|
7729
|
+
setSelectedDate(null);
|
|
7730
|
+
updateDateTime(null, hour, minute, second, meridiem);
|
|
7254
7731
|
return;
|
|
7255
7732
|
}
|
|
7256
|
-
//
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
if (
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
minute: parsedMinute,
|
|
7276
|
-
second: parsedSecond,
|
|
7277
|
-
});
|
|
7733
|
+
// Try parsing with common date formats
|
|
7734
|
+
let parsedDate = dayjs(inputVal, 'YYYY-MM-DD', true);
|
|
7735
|
+
// If that fails, try other common formats
|
|
7736
|
+
if (!parsedDate.isValid()) {
|
|
7737
|
+
parsedDate = dayjs(inputVal);
|
|
7738
|
+
}
|
|
7739
|
+
// If valid, check constraints and update
|
|
7740
|
+
if (parsedDate.isValid()) {
|
|
7741
|
+
const dateObj = parsedDate.tz(tz).toDate();
|
|
7742
|
+
// Check min/max constraints
|
|
7743
|
+
if (minDate && dateObj < minDate) {
|
|
7744
|
+
// Invalid: before minDate, reset to current selected date
|
|
7745
|
+
if (selectedDate) {
|
|
7746
|
+
const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
|
|
7747
|
+
setDateInputValue(formatted);
|
|
7748
|
+
}
|
|
7749
|
+
else {
|
|
7750
|
+
setDateInputValue('');
|
|
7751
|
+
}
|
|
7278
7752
|
return;
|
|
7279
7753
|
}
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
const parsedHour = parseInt(numbersOnly.slice(0, 2), 10);
|
|
7286
|
-
const parsedMinute = parseInt(numbersOnly.slice(2, 4), 10);
|
|
7287
|
-
const parsedSecond = numbersOnly.length >= 6 ? parseInt(numbersOnly.slice(4, 6), 10) : 0;
|
|
7288
|
-
// Validate ranges
|
|
7289
|
-
if (parsedHour >= 0 &&
|
|
7290
|
-
parsedHour <= 23 &&
|
|
7291
|
-
parsedMinute >= 0 &&
|
|
7292
|
-
parsedMinute <= 59 &&
|
|
7293
|
-
parsedSecond >= 0 &&
|
|
7294
|
-
parsedSecond <= 59) {
|
|
7295
|
-
setHour(parsedHour);
|
|
7296
|
-
setMinute(parsedMinute);
|
|
7297
|
-
setSecond(parsedSecond);
|
|
7298
|
-
onChange({
|
|
7299
|
-
hour: parsedHour,
|
|
7300
|
-
minute: parsedMinute,
|
|
7301
|
-
second: parsedSecond,
|
|
7302
|
-
});
|
|
7303
|
-
return;
|
|
7754
|
+
if (maxDate && dateObj > maxDate) {
|
|
7755
|
+
// Invalid: after maxDate, reset to current selected date
|
|
7756
|
+
if (selectedDate) {
|
|
7757
|
+
const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
|
|
7758
|
+
setDateInputValue(formatted);
|
|
7304
7759
|
}
|
|
7760
|
+
else {
|
|
7761
|
+
setDateInputValue('');
|
|
7762
|
+
}
|
|
7763
|
+
return;
|
|
7305
7764
|
}
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
if (collection.items.length > 0) {
|
|
7313
|
-
const firstItem = collection.items[0];
|
|
7314
|
-
setHour(firstItem.hour);
|
|
7315
|
-
setMinute(firstItem.minute);
|
|
7316
|
-
setSecond(firstItem.second);
|
|
7317
|
-
filter(''); // Reset filter after selection
|
|
7318
|
-
onChange({
|
|
7319
|
-
hour: firstItem.hour,
|
|
7320
|
-
minute: firstItem.minute,
|
|
7321
|
-
second: firstItem.second,
|
|
7322
|
-
});
|
|
7323
|
-
}
|
|
7324
|
-
};
|
|
7325
|
-
const [inputValue, setInputValue] = React.useState('');
|
|
7326
|
-
// Sync inputValue with currentValue when time changes externally
|
|
7327
|
-
React.useEffect(() => {
|
|
7328
|
-
if (hour !== null && minute !== null && second !== null) {
|
|
7329
|
-
const formattedValue = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${second.toString().padStart(2, '0')}`;
|
|
7330
|
-
setInputValue(formattedValue);
|
|
7765
|
+
// Valid date - update selected date
|
|
7766
|
+
setSelectedDate(dateObj);
|
|
7767
|
+
updateDateTime(dateObj, hour, minute, second, meridiem);
|
|
7768
|
+
// Format and update input value
|
|
7769
|
+
const formatted = parsedDate.tz(tz).format('YYYY-MM-DD');
|
|
7770
|
+
setDateInputValue(formatted);
|
|
7331
7771
|
}
|
|
7332
7772
|
else {
|
|
7333
|
-
|
|
7773
|
+
// Invalid date - reset to current selected date
|
|
7774
|
+
if (selectedDate) {
|
|
7775
|
+
const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
|
|
7776
|
+
setDateInputValue(formatted);
|
|
7777
|
+
}
|
|
7778
|
+
else {
|
|
7779
|
+
setDateInputValue('');
|
|
7780
|
+
}
|
|
7334
7781
|
}
|
|
7335
|
-
}, [hour, minute, second]);
|
|
7336
|
-
const handleInputValueChange = (details) => {
|
|
7337
|
-
// Update local input value state
|
|
7338
|
-
setInputValue(details.inputValue);
|
|
7339
|
-
// Filter the collection based on input, but don't parse yet
|
|
7340
|
-
filter(details.inputValue);
|
|
7341
7782
|
};
|
|
7342
|
-
const
|
|
7343
|
-
|
|
7344
|
-
e.target.select();
|
|
7783
|
+
const handleDateInputChange = (e) => {
|
|
7784
|
+
setDateInputValue(e.target.value);
|
|
7345
7785
|
};
|
|
7346
|
-
const
|
|
7347
|
-
|
|
7348
|
-
const inputVal = e.target.value;
|
|
7349
|
-
setInputValue(inputVal);
|
|
7350
|
-
if (inputVal) {
|
|
7351
|
-
parseAndCommitInput(inputVal);
|
|
7352
|
-
}
|
|
7786
|
+
const handleDateInputBlur = () => {
|
|
7787
|
+
parseAndValidateDateInput(dateInputValue);
|
|
7353
7788
|
};
|
|
7354
|
-
const
|
|
7355
|
-
// Commit input on Enter key
|
|
7789
|
+
const handleDateInputKeyDown = (e) => {
|
|
7356
7790
|
if (e.key === 'Enter') {
|
|
7357
7791
|
e.preventDefault();
|
|
7358
|
-
|
|
7359
|
-
setInputValue(inputVal);
|
|
7360
|
-
if (inputVal) {
|
|
7361
|
-
parseAndCommitInput(inputVal);
|
|
7362
|
-
}
|
|
7363
|
-
// Blur the input
|
|
7364
|
-
e.currentTarget?.blur();
|
|
7792
|
+
parseAndValidateDateInput(dateInputValue);
|
|
7365
7793
|
}
|
|
7366
7794
|
};
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
dayjs.
|
|
7371
|
-
dayjs.
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
'Jun',
|
|
7380
|
-
'Jul',
|
|
7381
|
-
'Aug',
|
|
7382
|
-
'Sep',
|
|
7383
|
-
'Oct',
|
|
7384
|
-
'Nov',
|
|
7385
|
-
'Dec',
|
|
7386
|
-
],
|
|
7387
|
-
weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
|
7388
|
-
backButtonLabel: 'Back',
|
|
7389
|
-
forwardButtonLabel: 'Next',
|
|
7390
|
-
}, timePickerLabels, timezone = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, defaultDate, defaultTime, }) {
|
|
7391
|
-
console.log('[DateTimePicker] Component initialized with props:', {
|
|
7392
|
-
value,
|
|
7393
|
-
format,
|
|
7394
|
-
showSeconds,
|
|
7395
|
-
timezone,
|
|
7396
|
-
startTime,
|
|
7397
|
-
minDate,
|
|
7398
|
-
maxDate,
|
|
7399
|
-
});
|
|
7400
|
-
// Initialize selectedDate from value prop, converting ISO to YYYY-MM-DD format
|
|
7401
|
-
const getDateString = React.useCallback((val) => {
|
|
7402
|
-
if (!val)
|
|
7403
|
-
return '';
|
|
7404
|
-
const dateObj = dayjs(val).tz(timezone);
|
|
7405
|
-
return dateObj.isValid() ? dateObj.format('YYYY-MM-DD') : '';
|
|
7406
|
-
}, [timezone]);
|
|
7407
|
-
const [selectedDate, setSelectedDate] = React.useState(getDateString(value));
|
|
7408
|
-
// Helper to get time values from value prop with timezone
|
|
7409
|
-
const getTimeFromValue = React.useCallback((val) => {
|
|
7410
|
-
console.log('[DateTimePicker] getTimeFromValue called:', {
|
|
7411
|
-
val,
|
|
7412
|
-
timezone,
|
|
7413
|
-
showSeconds,
|
|
7414
|
-
});
|
|
7415
|
-
if (!val) {
|
|
7416
|
-
console.log('[DateTimePicker] No value provided, returning nulls');
|
|
7417
|
-
return {
|
|
7418
|
-
hour12: null,
|
|
7419
|
-
minute: null,
|
|
7420
|
-
meridiem: null,
|
|
7421
|
-
hour24: null,
|
|
7422
|
-
second: null,
|
|
7423
|
-
};
|
|
7424
|
-
}
|
|
7425
|
-
const dateObj = dayjs(val).tz(timezone);
|
|
7426
|
-
console.log('[DateTimePicker] Parsed date object:', {
|
|
7427
|
-
original: val,
|
|
7428
|
-
timezone,
|
|
7429
|
-
isValid: dateObj.isValid(),
|
|
7430
|
-
formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
|
|
7431
|
-
hour24: dateObj.hour(),
|
|
7432
|
-
minute: dateObj.minute(),
|
|
7433
|
-
second: dateObj.second(),
|
|
7434
|
-
});
|
|
7435
|
-
if (!dateObj.isValid()) {
|
|
7436
|
-
console.log('[DateTimePicker] Invalid date object, returning nulls');
|
|
7437
|
-
return {
|
|
7438
|
-
hour12: null,
|
|
7439
|
-
minute: null,
|
|
7440
|
-
meridiem: null,
|
|
7441
|
-
hour24: null,
|
|
7442
|
-
second: null,
|
|
7443
|
-
};
|
|
7444
|
-
}
|
|
7445
|
-
const hour24Value = dateObj.hour();
|
|
7446
|
-
const hour12Value = hour24Value % 12 || 12;
|
|
7447
|
-
const minuteValue = dateObj.minute();
|
|
7448
|
-
const meridiemValue = hour24Value >= 12 ? 'pm' : 'am';
|
|
7449
|
-
const secondValue = showSeconds ? dateObj.second() : null;
|
|
7450
|
-
const result = {
|
|
7451
|
-
hour12: hour12Value,
|
|
7452
|
-
minute: minuteValue,
|
|
7453
|
-
meridiem: meridiemValue,
|
|
7454
|
-
hour24: hour24Value,
|
|
7455
|
-
second: secondValue,
|
|
7456
|
-
};
|
|
7457
|
-
console.log('[DateTimePicker] Extracted time values:', result);
|
|
7458
|
-
return result;
|
|
7459
|
-
}, [timezone, showSeconds]);
|
|
7460
|
-
const initialTime = getTimeFromValue(value);
|
|
7461
|
-
console.log('[DateTimePicker] Initial time from value:', {
|
|
7462
|
-
value,
|
|
7463
|
-
initialTime,
|
|
7464
|
-
});
|
|
7465
|
-
// Normalize startTime to ignore milliseconds (needed for effectiveDefaultDate calculation)
|
|
7466
|
-
const normalizedStartTime = startTime
|
|
7467
|
-
? dayjs(startTime).tz(timezone).millisecond(0).toISOString()
|
|
7468
|
-
: undefined;
|
|
7469
|
-
// Calculate effective defaultDate: use prop if provided, otherwise use startTime date, otherwise use today
|
|
7470
|
-
const effectiveDefaultDate = React.useMemo(() => {
|
|
7471
|
-
if (defaultDate) {
|
|
7472
|
-
return defaultDate;
|
|
7473
|
-
}
|
|
7474
|
-
if (normalizedStartTime &&
|
|
7475
|
-
dayjs(normalizedStartTime).tz(timezone).isValid()) {
|
|
7476
|
-
return dayjs(normalizedStartTime).tz(timezone).format('YYYY-MM-DD');
|
|
7477
|
-
}
|
|
7478
|
-
return dayjs().tz(timezone).format('YYYY-MM-DD');
|
|
7479
|
-
}, [defaultDate, normalizedStartTime, timezone]);
|
|
7480
|
-
// Initialize time with default values if no value is provided
|
|
7481
|
-
const getInitialTimeValues = () => {
|
|
7482
|
-
if (value && initialTime.hour12 !== null) {
|
|
7483
|
-
return initialTime;
|
|
7484
|
-
}
|
|
7485
|
-
// If no value or no time in value, use defaultTime or 00:00
|
|
7486
|
-
if (defaultTime) {
|
|
7487
|
-
if (format === 'iso-date-time') {
|
|
7488
|
-
const defaultTime24 = defaultTime;
|
|
7489
|
-
return {
|
|
7490
|
-
hour12: null,
|
|
7491
|
-
minute: defaultTime24.minute ?? 0,
|
|
7492
|
-
meridiem: null,
|
|
7493
|
-
hour24: defaultTime24.hour ?? 0,
|
|
7494
|
-
second: showSeconds ? defaultTime24.second ?? 0 : null,
|
|
7495
|
-
};
|
|
7496
|
-
}
|
|
7497
|
-
else {
|
|
7498
|
-
const defaultTime12 = defaultTime;
|
|
7499
|
-
return {
|
|
7500
|
-
hour12: defaultTime12.hour ?? 12,
|
|
7501
|
-
minute: defaultTime12.minute ?? 0,
|
|
7502
|
-
meridiem: defaultTime12.meridiem ?? 'am',
|
|
7503
|
-
hour24: null,
|
|
7504
|
-
second: null,
|
|
7505
|
-
};
|
|
7506
|
-
}
|
|
7507
|
-
}
|
|
7508
|
-
// Default to 00:00
|
|
7509
|
-
if (format === 'iso-date-time') {
|
|
7510
|
-
return {
|
|
7511
|
-
hour12: null,
|
|
7512
|
-
minute: 0,
|
|
7513
|
-
meridiem: null,
|
|
7514
|
-
hour24: 0,
|
|
7515
|
-
second: showSeconds ? 0 : null,
|
|
7516
|
-
};
|
|
7795
|
+
// Helper functions to get dates in the correct timezone
|
|
7796
|
+
const getToday = () => dayjs().tz(tz).startOf('day').toDate();
|
|
7797
|
+
const getYesterday = () => dayjs().tz(tz).subtract(1, 'day').startOf('day').toDate();
|
|
7798
|
+
const getTomorrow = () => dayjs().tz(tz).add(1, 'day').startOf('day').toDate();
|
|
7799
|
+
const getPlus7Days = () => dayjs().tz(tz).add(7, 'day').startOf('day').toDate();
|
|
7800
|
+
// Check if a date is within min/max constraints
|
|
7801
|
+
const isDateValid = (date) => {
|
|
7802
|
+
if (minDate) {
|
|
7803
|
+
const minDateStart = dayjs(minDate).tz(tz).startOf('day').toDate();
|
|
7804
|
+
const dateStart = dayjs(date).tz(tz).startOf('day').toDate();
|
|
7805
|
+
if (dateStart < minDateStart)
|
|
7806
|
+
return false;
|
|
7517
7807
|
}
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
hour24: null,
|
|
7524
|
-
second: null,
|
|
7525
|
-
};
|
|
7808
|
+
if (maxDate) {
|
|
7809
|
+
const maxDateStart = dayjs(maxDate).tz(tz).startOf('day').toDate();
|
|
7810
|
+
const dateStart = dayjs(date).tz(tz).startOf('day').toDate();
|
|
7811
|
+
if (dateStart > maxDateStart)
|
|
7812
|
+
return false;
|
|
7526
7813
|
}
|
|
7814
|
+
return true;
|
|
7527
7815
|
};
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
const [second, setSecond] = React.useState(initialTimeValues.second);
|
|
7536
|
-
// Sync selectedDate and time states when value prop changes
|
|
7537
|
-
React.useEffect(() => {
|
|
7538
|
-
console.log('[DateTimePicker] useEffect triggered - value changed:', {
|
|
7539
|
-
value,
|
|
7540
|
-
timezone,
|
|
7541
|
-
format,
|
|
7542
|
-
});
|
|
7543
|
-
// If value is null, undefined, or invalid, clear date but keep default time values
|
|
7544
|
-
if (!value || value === null || value === undefined) {
|
|
7545
|
-
console.log('[DateTimePicker] Value is null/undefined, clearing date but keeping default time');
|
|
7546
|
-
setSelectedDate('');
|
|
7547
|
-
// Keep default time values instead of clearing them
|
|
7548
|
-
if (format === 'iso-date-time') {
|
|
7549
|
-
setHour24(defaultTime ? defaultTime.hour ?? 0 : 0);
|
|
7550
|
-
setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
|
|
7551
|
-
setSecond(showSeconds
|
|
7552
|
-
? defaultTime
|
|
7553
|
-
? defaultTime.second ?? 0
|
|
7554
|
-
: 0
|
|
7555
|
-
: null);
|
|
7556
|
-
}
|
|
7557
|
-
else {
|
|
7558
|
-
setHour12(defaultTime ? defaultTime.hour ?? 12 : 12);
|
|
7559
|
-
setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
|
|
7560
|
-
setMeridiem(defaultTime ? defaultTime.meridiem ?? 'am' : 'am');
|
|
7561
|
-
}
|
|
7562
|
-
return;
|
|
7816
|
+
// Handle quick action button clicks
|
|
7817
|
+
const handleQuickActionClick = (date) => {
|
|
7818
|
+
if (isDateValid(date)) {
|
|
7819
|
+
setSelectedDate(date);
|
|
7820
|
+
updateDateTime(date, hour, minute, second, meridiem);
|
|
7821
|
+
// Close the calendar popover if open
|
|
7822
|
+
setCalendarPopoverOpen(false);
|
|
7563
7823
|
}
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
7576
|
-
|
|
7577
|
-
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
setHour12(defaultTime ? defaultTime.hour ?? 12 : 12);
|
|
7581
|
-
setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
|
|
7582
|
-
setMeridiem(defaultTime ? defaultTime.meridiem ?? 'am' : 'am');
|
|
7824
|
+
};
|
|
7825
|
+
// Display text for buttons
|
|
7826
|
+
const dateDisplayText = React.useMemo(() => {
|
|
7827
|
+
if (!selectedDate)
|
|
7828
|
+
return 'Select date';
|
|
7829
|
+
return dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
|
|
7830
|
+
}, [selectedDate, tz]);
|
|
7831
|
+
const timeDisplayText = React.useMemo(() => {
|
|
7832
|
+
if (hour === null || minute === null)
|
|
7833
|
+
return 'Select time';
|
|
7834
|
+
if (is24Hour) {
|
|
7835
|
+
// 24-hour format: never show meridiem, always use 24-hour format (0-23)
|
|
7836
|
+
const hour24 = hour >= 0 && hour <= 23 ? hour : hour % 24;
|
|
7837
|
+
const s = second ?? 0;
|
|
7838
|
+
if (showSeconds) {
|
|
7839
|
+
return `${hour24.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
|
7583
7840
|
}
|
|
7584
|
-
return
|
|
7841
|
+
return `${hour24.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
|
|
7585
7842
|
}
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7605
|
-
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
setSelectedDate(date);
|
|
7619
|
-
// Parse the date string (YYYY-MM-DD) in the specified timezone
|
|
7620
|
-
const dateObj = dayjs.tz(date, timezone);
|
|
7621
|
-
console.log('[DateTimePicker] Parsed date object:', {
|
|
7622
|
-
date,
|
|
7623
|
-
timezone,
|
|
7624
|
-
isValid: dateObj.isValid(),
|
|
7625
|
-
isoString: dateObj.toISOString(),
|
|
7626
|
-
formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
|
|
7627
|
-
});
|
|
7628
|
-
if (!dateObj.isValid()) {
|
|
7629
|
-
console.warn('[DateTimePicker] Invalid date object in handleDateChange, clearing fields');
|
|
7630
|
-
setSelectedDate('');
|
|
7631
|
-
setHour12(null);
|
|
7632
|
-
setMinute(null);
|
|
7633
|
-
setMeridiem(null);
|
|
7634
|
-
setHour24(null);
|
|
7635
|
-
setSecond(null);
|
|
7843
|
+
else {
|
|
7844
|
+
// 12-hour format: always show meridiem (AM/PM)
|
|
7845
|
+
const hour12 = hour >= 1 && hour <= 12 ? hour : hour % 12;
|
|
7846
|
+
if (meridiem === null)
|
|
7847
|
+
return 'Select time';
|
|
7848
|
+
const hourDisplay = hour12.toString();
|
|
7849
|
+
const minuteDisplay = minute.toString().padStart(2, '0');
|
|
7850
|
+
return `${hourDisplay}:${minuteDisplay} ${meridiem.toUpperCase()}`;
|
|
7851
|
+
}
|
|
7852
|
+
}, [hour, minute, second, meridiem, is24Hour, showSeconds]);
|
|
7853
|
+
const timezoneDisplayText = React.useMemo(() => {
|
|
7854
|
+
if (!showTimezoneSelector)
|
|
7855
|
+
return '';
|
|
7856
|
+
// Show offset as is (e.g., "+08:00")
|
|
7857
|
+
return timezoneOffset;
|
|
7858
|
+
}, [timezoneOffset, showTimezoneSelector]);
|
|
7859
|
+
// Update selectedDate when value changes externally
|
|
7860
|
+
React.useEffect(() => {
|
|
7861
|
+
if (parsedValue) {
|
|
7862
|
+
setSelectedDate(parsedValue.toDate());
|
|
7863
|
+
setHour(parsedValue.hour());
|
|
7864
|
+
setMinute(parsedValue.minute());
|
|
7865
|
+
setSecond(parsedValue.second());
|
|
7866
|
+
if (!is24Hour) {
|
|
7867
|
+
const h = parsedValue.hour();
|
|
7868
|
+
setMeridiem(h < 12 ? 'am' : 'pm');
|
|
7869
|
+
}
|
|
7870
|
+
}
|
|
7871
|
+
}, [parsedValue, is24Hour]);
|
|
7872
|
+
// Combine date and time and call onChange
|
|
7873
|
+
const updateDateTime = (newDate, newHour, newMinute, newSecond, newMeridiem, timezoneOffsetOverride) => {
|
|
7874
|
+
if (!newDate || newHour === null || newMinute === null) {
|
|
7636
7875
|
onChange?.(undefined);
|
|
7637
7876
|
return;
|
|
7638
7877
|
}
|
|
7639
|
-
//
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
const defaultTime24 = defaultTime;
|
|
7650
|
-
setHour24(defaultTime24.hour ?? 0);
|
|
7651
|
-
setMinute(defaultTime24.minute ?? 0);
|
|
7652
|
-
if (showSeconds) {
|
|
7653
|
-
setSecond(defaultTime24.second ?? 0);
|
|
7654
|
-
}
|
|
7655
|
-
timeDataToUse = {
|
|
7656
|
-
hour: defaultTime24.hour ?? 0,
|
|
7657
|
-
minute: defaultTime24.minute ?? 0,
|
|
7658
|
-
second: showSeconds ? defaultTime24.second ?? 0 : undefined,
|
|
7659
|
-
};
|
|
7878
|
+
// Convert 12-hour to 24-hour if needed
|
|
7879
|
+
let hour24 = newHour;
|
|
7880
|
+
if (!is24Hour && newMeridiem) {
|
|
7881
|
+
// In 12-hour format, hour should be 1-12
|
|
7882
|
+
// If hour is > 12, it might already be in 24-hour format, convert it first
|
|
7883
|
+
let hour12 = newHour;
|
|
7884
|
+
if (newHour > 12) {
|
|
7885
|
+
// Hour is in 24-hour format, convert to 12-hour first
|
|
7886
|
+
if (newHour === 12) {
|
|
7887
|
+
hour12 = 12;
|
|
7660
7888
|
}
|
|
7661
7889
|
else {
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
7670
|
-
|
|
7890
|
+
hour12 = newHour - 12;
|
|
7891
|
+
}
|
|
7892
|
+
}
|
|
7893
|
+
// Now convert 12-hour to 24-hour format (0-23)
|
|
7894
|
+
if (newMeridiem === 'am') {
|
|
7895
|
+
if (hour12 === 12) {
|
|
7896
|
+
hour24 = 0; // 12 AM = 0:00
|
|
7897
|
+
}
|
|
7898
|
+
else {
|
|
7899
|
+
hour24 = hour12; // 1-11 AM = 1-11
|
|
7671
7900
|
}
|
|
7672
7901
|
}
|
|
7673
7902
|
else {
|
|
7674
|
-
|
|
7675
|
-
if (
|
|
7676
|
-
|
|
7677
|
-
setMinute(0);
|
|
7678
|
-
if (showSeconds) {
|
|
7679
|
-
setSecond(0);
|
|
7680
|
-
}
|
|
7681
|
-
timeDataToUse = {
|
|
7682
|
-
hour: 0,
|
|
7683
|
-
minute: 0,
|
|
7684
|
-
second: showSeconds ? 0 : undefined,
|
|
7685
|
-
};
|
|
7903
|
+
// PM
|
|
7904
|
+
if (hour12 === 12) {
|
|
7905
|
+
hour24 = 12; // 12 PM = 12:00
|
|
7686
7906
|
}
|
|
7687
7907
|
else {
|
|
7688
|
-
|
|
7689
|
-
setMinute(0);
|
|
7690
|
-
setMeridiem('am');
|
|
7691
|
-
timeDataToUse = {
|
|
7692
|
-
hour: 12,
|
|
7693
|
-
minute: 0,
|
|
7694
|
-
meridiem: 'am',
|
|
7695
|
-
};
|
|
7908
|
+
hour24 = hour12 + 12; // 1-11 PM = 13-23
|
|
7696
7909
|
}
|
|
7697
7910
|
}
|
|
7698
7911
|
}
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7912
|
+
else if (!is24Hour && !newMeridiem) {
|
|
7913
|
+
// If in 12-hour mode but no meridiem, assume the hour is already in 12-hour format
|
|
7914
|
+
// and default to AM (or keep as is if it's a valid 12-hour value)
|
|
7915
|
+
// This shouldn't happen in normal flow, but handle it gracefully
|
|
7916
|
+
hour24 = newHour;
|
|
7917
|
+
}
|
|
7918
|
+
// If timezone selector is enabled, create date-time without timezone conversion
|
|
7919
|
+
// to ensure the selected timestamp matches the picker values exactly
|
|
7920
|
+
if (showTimezoneSelector) {
|
|
7921
|
+
// Use override if provided, otherwise use state value
|
|
7922
|
+
const offsetToUse = timezoneOffsetOverride ?? timezoneOffset;
|
|
7923
|
+
// Create date-time from the Date object without timezone conversion
|
|
7924
|
+
// Extract year, month, day from the date
|
|
7925
|
+
const year = newDate.getFullYear();
|
|
7926
|
+
const month = newDate.getMonth();
|
|
7927
|
+
const day = newDate.getDate();
|
|
7928
|
+
// Create a date-time string with the exact values from the picker
|
|
7929
|
+
const formattedDateTime = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}T${String(hour24).padStart(2, '0')}:${String(newMinute).padStart(2, '0')}:${String(newSecond ?? 0).padStart(2, '0')}`;
|
|
7930
|
+
onChange?.(`${formattedDateTime}${offsetToUse}`);
|
|
7931
|
+
return;
|
|
7704
7932
|
}
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7933
|
+
// Normal mode: use timezone conversion
|
|
7934
|
+
let dateTime = dayjs(newDate)
|
|
7935
|
+
.tz(tz)
|
|
7936
|
+
.hour(hour24)
|
|
7937
|
+
.minute(newMinute)
|
|
7938
|
+
.second(newSecond ?? 0)
|
|
7939
|
+
.millisecond(0);
|
|
7940
|
+
if (!dateTime.isValid()) {
|
|
7941
|
+
onChange?.(undefined);
|
|
7942
|
+
return;
|
|
7709
7943
|
}
|
|
7710
|
-
|
|
7711
|
-
const handleTimeChange = (timeData) => {
|
|
7712
|
-
console.log('[DateTimePicker] handleTimeChange called:', {
|
|
7713
|
-
timeData,
|
|
7714
|
-
format,
|
|
7715
|
-
selectedDate,
|
|
7716
|
-
timezone,
|
|
7717
|
-
});
|
|
7944
|
+
// Format based on format prop
|
|
7718
7945
|
if (format === 'iso-date-time') {
|
|
7719
|
-
|
|
7720
|
-
console.log('[DateTimePicker] ISO format - setting 24-hour time:', data);
|
|
7721
|
-
setHour24(data.hour);
|
|
7722
|
-
setMinute(data.minute);
|
|
7723
|
-
if (showSeconds) {
|
|
7724
|
-
setSecond(data.second ?? null);
|
|
7725
|
-
}
|
|
7726
|
-
else {
|
|
7727
|
-
// Ignore seconds - always set to null when showSeconds is false
|
|
7728
|
-
setSecond(null);
|
|
7729
|
-
}
|
|
7946
|
+
onChange?.(dateTime.format('YYYY-MM-DDTHH:mm:ss'));
|
|
7730
7947
|
}
|
|
7731
7948
|
else {
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
setHour12(data.hour);
|
|
7735
|
-
setMinute(data.minute);
|
|
7736
|
-
setMeridiem(data.meridiem);
|
|
7737
|
-
}
|
|
7738
|
-
// Use selectedDate if valid, otherwise use effectiveDefaultDate or clear all fields
|
|
7739
|
-
if (!selectedDate || !dayjs(selectedDate).isValid()) {
|
|
7740
|
-
// If effectiveDefaultDate is available, use it instead of clearing
|
|
7741
|
-
if (effectiveDefaultDate && dayjs(effectiveDefaultDate).isValid()) {
|
|
7742
|
-
console.log('[DateTimePicker] No valid selectedDate, using effectiveDefaultDate:', effectiveDefaultDate);
|
|
7743
|
-
setSelectedDate(effectiveDefaultDate);
|
|
7744
|
-
const dateObj = dayjs(effectiveDefaultDate).tz(timezone);
|
|
7745
|
-
if (dateObj.isValid()) {
|
|
7746
|
-
updateDateTime(dateObj.toISOString(), timeData);
|
|
7747
|
-
}
|
|
7748
|
-
else {
|
|
7749
|
-
console.warn('[DateTimePicker] Invalid effectiveDefaultDate, clearing fields');
|
|
7750
|
-
setSelectedDate('');
|
|
7751
|
-
setHour12(null);
|
|
7752
|
-
setMinute(null);
|
|
7753
|
-
setMeridiem(null);
|
|
7754
|
-
setHour24(null);
|
|
7755
|
-
setSecond(null);
|
|
7756
|
-
onChange?.(undefined);
|
|
7757
|
-
}
|
|
7758
|
-
return;
|
|
7759
|
-
}
|
|
7760
|
-
else {
|
|
7761
|
-
console.log('[DateTimePicker] No valid selectedDate and no effectiveDefaultDate, keeping time values but no date');
|
|
7762
|
-
// Keep the time values that were just set, but don't set a date
|
|
7763
|
-
// This should rarely happen as effectiveDefaultDate always defaults to today
|
|
7764
|
-
setSelectedDate('');
|
|
7765
|
-
onChange?.(undefined);
|
|
7766
|
-
return;
|
|
7767
|
-
}
|
|
7949
|
+
// date-time format with timezone
|
|
7950
|
+
onChange?.(dateTime.format('YYYY-MM-DDTHH:mm:ssZ'));
|
|
7768
7951
|
}
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7952
|
+
};
|
|
7953
|
+
// Handle date selection
|
|
7954
|
+
const handleDateSelected = ({ date, }) => {
|
|
7955
|
+
setSelectedDate(date);
|
|
7956
|
+
updateDateTime(date, hour, minute, second, meridiem);
|
|
7957
|
+
};
|
|
7958
|
+
// Handle time change
|
|
7959
|
+
const handleTimeChange = (newHour, newMinute, newSecond, newMeridiem) => {
|
|
7960
|
+
setHour(newHour);
|
|
7961
|
+
setMinute(newMinute);
|
|
7962
|
+
if (is24Hour) {
|
|
7963
|
+
setSecond(newSecond);
|
|
7772
7964
|
}
|
|
7773
7965
|
else {
|
|
7774
|
-
|
|
7775
|
-
setSelectedDate('');
|
|
7776
|
-
setHour12(null);
|
|
7777
|
-
setMinute(null);
|
|
7778
|
-
setMeridiem(null);
|
|
7779
|
-
setHour24(null);
|
|
7780
|
-
setSecond(null);
|
|
7781
|
-
onChange?.(undefined);
|
|
7966
|
+
setMeridiem(newMeridiem);
|
|
7782
7967
|
}
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
console.log('[DateTimePicker] updateDateTime called:', {
|
|
7786
|
-
date,
|
|
7787
|
-
timeData,
|
|
7788
|
-
format,
|
|
7789
|
-
currentStates: { hour12, minute, meridiem, hour24, second },
|
|
7790
|
-
});
|
|
7791
|
-
if (!date || date === null || date === undefined) {
|
|
7792
|
-
console.log('[DateTimePicker] No date provided, clearing all fields and calling onChange(undefined)');
|
|
7793
|
-
setSelectedDate('');
|
|
7794
|
-
setHour12(null);
|
|
7795
|
-
setMinute(null);
|
|
7796
|
-
setMeridiem(null);
|
|
7797
|
-
setHour24(null);
|
|
7798
|
-
setSecond(null);
|
|
7799
|
-
onChange?.(undefined);
|
|
7800
|
-
return;
|
|
7968
|
+
if (selectedDate) {
|
|
7969
|
+
updateDateTime(selectedDate, newHour, newMinute, newSecond, newMeridiem);
|
|
7801
7970
|
}
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
7805
|
-
|
|
7806
|
-
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7971
|
+
};
|
|
7972
|
+
// Calendar hook
|
|
7973
|
+
const calendarProps = useCalendar({
|
|
7974
|
+
selected: selectedDate || undefined,
|
|
7975
|
+
date: selectedDate || undefined,
|
|
7976
|
+
minDate,
|
|
7977
|
+
maxDate,
|
|
7978
|
+
monthsToDisplay: 1,
|
|
7979
|
+
onDateSelected: handleDateSelected,
|
|
7980
|
+
});
|
|
7981
|
+
// Generate time options
|
|
7982
|
+
const timeOptions = React.useMemo(() => {
|
|
7983
|
+
const options = [];
|
|
7984
|
+
// Get start time for comparison if provided
|
|
7985
|
+
let startDateTime = null;
|
|
7986
|
+
let shouldFilterByDate = false;
|
|
7987
|
+
if (startTime && selectedDate) {
|
|
7988
|
+
const startDateObj = dayjs(startTime).tz(tz);
|
|
7989
|
+
const selectedDateObj = dayjs(selectedDate).tz(tz);
|
|
7990
|
+
if (startDateObj.isValid() && selectedDateObj.isValid()) {
|
|
7991
|
+
startDateTime = startDateObj;
|
|
7992
|
+
shouldFilterByDate =
|
|
7993
|
+
startDateObj.format('YYYY-MM-DD') ===
|
|
7994
|
+
selectedDateObj.format('YYYY-MM-DD');
|
|
7995
|
+
}
|
|
7814
7996
|
}
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7997
|
+
if (is24Hour) {
|
|
7998
|
+
// Generate 24-hour format options
|
|
7999
|
+
for (let h = 0; h < 24; h++) {
|
|
8000
|
+
for (let m = 0; m < 60; m += 15) {
|
|
8001
|
+
// Filter out times that would result in negative duration
|
|
8002
|
+
if (startDateTime && selectedDate && shouldFilterByDate) {
|
|
8003
|
+
const selectedDateObj = dayjs(selectedDate).tz(tz);
|
|
8004
|
+
const optionDateTime = selectedDateObj
|
|
8005
|
+
.hour(h)
|
|
8006
|
+
.minute(m)
|
|
8007
|
+
.second(0)
|
|
8008
|
+
.millisecond(0);
|
|
8009
|
+
if (optionDateTime.isBefore(startDateTime)) {
|
|
8010
|
+
continue;
|
|
8011
|
+
}
|
|
8012
|
+
}
|
|
8013
|
+
// Calculate duration if startTime is provided
|
|
8014
|
+
let durationText;
|
|
8015
|
+
if (startDateTime && selectedDate) {
|
|
8016
|
+
const selectedDateObj = dayjs(selectedDate).tz(tz);
|
|
8017
|
+
const optionDateTime = selectedDateObj
|
|
8018
|
+
.hour(h)
|
|
8019
|
+
.minute(m)
|
|
8020
|
+
.second(0)
|
|
8021
|
+
.millisecond(0);
|
|
8022
|
+
if (optionDateTime.isValid() &&
|
|
8023
|
+
optionDateTime.isAfter(startDateTime)) {
|
|
8024
|
+
const diffMs = optionDateTime.diff(startDateTime);
|
|
8025
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
8026
|
+
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
8027
|
+
const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
|
|
8028
|
+
if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
|
|
8029
|
+
let diffText = '';
|
|
8030
|
+
if (diffHours > 0) {
|
|
8031
|
+
diffText = `${diffHours}h ${diffMinutes}m`;
|
|
8032
|
+
}
|
|
8033
|
+
else if (diffMinutes > 0) {
|
|
8034
|
+
diffText = `${diffMinutes}m ${diffSeconds}s`;
|
|
8035
|
+
}
|
|
8036
|
+
else {
|
|
8037
|
+
diffText = `${diffSeconds}s`;
|
|
8038
|
+
}
|
|
8039
|
+
durationText = `+${diffText}`;
|
|
8040
|
+
}
|
|
8041
|
+
}
|
|
8042
|
+
}
|
|
8043
|
+
const s = showSeconds ? 0 : 0;
|
|
8044
|
+
const timeDisplay = showSeconds
|
|
8045
|
+
? `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:00`
|
|
8046
|
+
: `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`;
|
|
8047
|
+
options.push({
|
|
8048
|
+
label: timeDisplay,
|
|
8049
|
+
value: `${h}:${m}:${s}`,
|
|
8050
|
+
hour: h,
|
|
8051
|
+
minute: m,
|
|
8052
|
+
second: s,
|
|
8053
|
+
searchText: timeDisplay,
|
|
8054
|
+
durationText,
|
|
8055
|
+
});
|
|
8056
|
+
}
|
|
7833
8057
|
}
|
|
7834
|
-
console.log('[DateTimePicker] ISO format - setting time on date:', {
|
|
7835
|
-
h,
|
|
7836
|
-
m,
|
|
7837
|
-
s,
|
|
7838
|
-
showSeconds,
|
|
7839
|
-
});
|
|
7840
|
-
if (h !== null)
|
|
7841
|
-
newDate.setHours(h);
|
|
7842
|
-
if (m !== null)
|
|
7843
|
-
newDate.setMinutes(m);
|
|
7844
|
-
newDate.setSeconds(s ?? 0);
|
|
7845
8058
|
}
|
|
7846
8059
|
else {
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
8060
|
+
// Generate 12-hour format options
|
|
8061
|
+
for (let h = 1; h <= 12; h++) {
|
|
8062
|
+
for (let m = 0; m < 60; m += 15) {
|
|
8063
|
+
for (const mer of ['am', 'pm']) {
|
|
8064
|
+
// Convert 12-hour to 24-hour for comparison
|
|
8065
|
+
let hour24 = h;
|
|
8066
|
+
if (mer === 'am' && h === 12)
|
|
8067
|
+
hour24 = 0;
|
|
8068
|
+
else if (mer === 'pm' && h < 12)
|
|
8069
|
+
hour24 = h + 12;
|
|
8070
|
+
// Filter out times that would result in negative duration
|
|
8071
|
+
if (startDateTime && selectedDate && shouldFilterByDate) {
|
|
8072
|
+
const selectedDateObj = dayjs(selectedDate).tz(tz);
|
|
8073
|
+
const optionDateTime = selectedDateObj
|
|
8074
|
+
.hour(hour24)
|
|
8075
|
+
.minute(m)
|
|
8076
|
+
.second(0)
|
|
8077
|
+
.millisecond(0);
|
|
8078
|
+
if (optionDateTime.isBefore(startDateTime)) {
|
|
8079
|
+
continue;
|
|
8080
|
+
}
|
|
8081
|
+
}
|
|
8082
|
+
// Calculate duration if startTime is provided
|
|
8083
|
+
let durationText;
|
|
8084
|
+
if (startDateTime && selectedDate) {
|
|
8085
|
+
const selectedDateObj = dayjs(selectedDate).tz(tz);
|
|
8086
|
+
const optionDateTime = selectedDateObj
|
|
8087
|
+
.hour(hour24)
|
|
8088
|
+
.minute(m)
|
|
8089
|
+
.second(0)
|
|
8090
|
+
.millisecond(0);
|
|
8091
|
+
if (optionDateTime.isValid() &&
|
|
8092
|
+
optionDateTime.isAfter(startDateTime)) {
|
|
8093
|
+
const diffMs = optionDateTime.diff(startDateTime);
|
|
8094
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
8095
|
+
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
8096
|
+
const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
|
|
8097
|
+
if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
|
|
8098
|
+
let diffText = '';
|
|
8099
|
+
if (diffHours > 0) {
|
|
8100
|
+
diffText = `${diffHours}h ${diffMinutes}m`;
|
|
8101
|
+
}
|
|
8102
|
+
else if (diffMinutes > 0) {
|
|
8103
|
+
diffText = `${diffMinutes}m ${diffSeconds}s`;
|
|
8104
|
+
}
|
|
8105
|
+
else {
|
|
8106
|
+
diffText = `${diffSeconds}s`;
|
|
8107
|
+
}
|
|
8108
|
+
durationText = `+${diffText}`;
|
|
8109
|
+
}
|
|
8110
|
+
}
|
|
8111
|
+
}
|
|
8112
|
+
const hourDisplay = h.toString();
|
|
8113
|
+
const minuteDisplay = m.toString().padStart(2, '0');
|
|
8114
|
+
const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
|
|
8115
|
+
options.push({
|
|
8116
|
+
label: timeDisplay,
|
|
8117
|
+
value: `${h}:${m}:${mer}`,
|
|
8118
|
+
hour: h,
|
|
8119
|
+
minute: m,
|
|
8120
|
+
meridiem: mer,
|
|
8121
|
+
searchText: timeDisplay,
|
|
8122
|
+
durationText,
|
|
8123
|
+
});
|
|
8124
|
+
}
|
|
8125
|
+
}
|
|
7867
8126
|
}
|
|
7868
|
-
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
8127
|
+
// Sort 12-hour options by time
|
|
8128
|
+
return options.sort((a, b) => {
|
|
8129
|
+
const a12 = a;
|
|
8130
|
+
const b12 = b;
|
|
8131
|
+
let hour24A = a12.hour;
|
|
8132
|
+
if (a12.meridiem === 'am' && a12.hour === 12)
|
|
8133
|
+
hour24A = 0;
|
|
8134
|
+
else if (a12.meridiem === 'pm' && a12.hour < 12)
|
|
8135
|
+
hour24A = a12.hour + 12;
|
|
8136
|
+
let hour24B = b12.hour;
|
|
8137
|
+
if (b12.meridiem === 'am' && b12.hour === 12)
|
|
8138
|
+
hour24B = 0;
|
|
8139
|
+
else if (b12.meridiem === 'pm' && b12.hour < 12)
|
|
8140
|
+
hour24B = b12.hour + 12;
|
|
8141
|
+
if (hour24A !== hour24B) {
|
|
8142
|
+
return hour24A - hour24B;
|
|
8143
|
+
}
|
|
8144
|
+
return a12.minute - b12.minute;
|
|
7872
8145
|
});
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
8146
|
+
}
|
|
8147
|
+
return options;
|
|
8148
|
+
}, [startTime, selectedDate, tz, is24Hour, showSeconds]);
|
|
8149
|
+
// Time picker combobox setup
|
|
8150
|
+
const itemToString = React.useMemo(() => {
|
|
8151
|
+
return (item) => {
|
|
8152
|
+
return item.searchText;
|
|
8153
|
+
};
|
|
8154
|
+
}, []);
|
|
8155
|
+
const { contains } = react.useFilter({ sensitivity: 'base' });
|
|
8156
|
+
const customTimeFilter = React.useMemo(() => {
|
|
8157
|
+
if (is24Hour) {
|
|
8158
|
+
return contains;
|
|
8159
|
+
}
|
|
8160
|
+
return (itemText, filterText) => {
|
|
8161
|
+
if (!filterText) {
|
|
8162
|
+
return true;
|
|
7885
8163
|
}
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
|
|
7890
|
-
});
|
|
8164
|
+
const lowerItemText = itemText.toLowerCase();
|
|
8165
|
+
const lowerFilterText = filterText.toLowerCase();
|
|
8166
|
+
if (lowerItemText.includes(lowerFilterText)) {
|
|
8167
|
+
return true;
|
|
7891
8168
|
}
|
|
7892
|
-
|
|
7893
|
-
|
|
8169
|
+
const item = timeOptions.find((opt) => opt.searchText.toLowerCase() === lowerItemText);
|
|
8170
|
+
if (!item || !('meridiem' in item)) {
|
|
8171
|
+
return false;
|
|
7894
8172
|
}
|
|
7895
|
-
|
|
7896
|
-
|
|
8173
|
+
let hour24 = item.hour;
|
|
8174
|
+
if (item.meridiem === 'am' && item.hour === 12)
|
|
8175
|
+
hour24 = 0;
|
|
8176
|
+
else if (item.meridiem === 'pm' && item.hour < 12)
|
|
8177
|
+
hour24 = item.hour + 12;
|
|
8178
|
+
const hour24Str = hour24.toString().padStart(2, '0');
|
|
8179
|
+
const minuteStr = item.minute.toString().padStart(2, '0');
|
|
8180
|
+
const formats = [
|
|
8181
|
+
`${hour24Str}:${minuteStr}`,
|
|
8182
|
+
`${hour24Str}${minuteStr}`,
|
|
8183
|
+
hour24Str,
|
|
8184
|
+
`${hour24}:${minuteStr}`,
|
|
8185
|
+
hour24.toString(),
|
|
8186
|
+
];
|
|
8187
|
+
return formats.some((format) => format.toLowerCase().includes(lowerFilterText) ||
|
|
8188
|
+
lowerFilterText.includes(format.toLowerCase()));
|
|
8189
|
+
};
|
|
8190
|
+
}, [timeOptions, is24Hour, contains]);
|
|
8191
|
+
const { collection, filter } = react.useListCollection({
|
|
8192
|
+
initialItems: timeOptions,
|
|
8193
|
+
itemToString: itemToString,
|
|
8194
|
+
itemToValue: (item) => item.value,
|
|
8195
|
+
filter: customTimeFilter,
|
|
8196
|
+
});
|
|
8197
|
+
// Get current value string for combobox (must match option.value format)
|
|
8198
|
+
const currentTimeValue = React.useMemo(() => {
|
|
8199
|
+
if (is24Hour) {
|
|
8200
|
+
if (hour === null || minute === null) {
|
|
8201
|
+
return '';
|
|
7897
8202
|
}
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
const finalISO = dayjs(newDate).tz(timezone).toISOString();
|
|
7901
|
-
console.log('[DateTimePicker] Final ISO string to emit:', {
|
|
7902
|
-
newDate: newDate.toISOString(),
|
|
7903
|
-
timezone,
|
|
7904
|
-
finalISO,
|
|
7905
|
-
});
|
|
7906
|
-
onChange?.(finalISO);
|
|
7907
|
-
};
|
|
7908
|
-
const handleClear = () => {
|
|
7909
|
-
setSelectedDate('');
|
|
7910
|
-
// Reset to default time values instead of clearing them
|
|
7911
|
-
if (format === 'iso-date-time') {
|
|
7912
|
-
setHour24(defaultTime ? defaultTime.hour ?? 0 : 0);
|
|
7913
|
-
setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
|
|
7914
|
-
setSecond(showSeconds
|
|
7915
|
-
? defaultTime
|
|
7916
|
-
? defaultTime.second ?? 0
|
|
7917
|
-
: 0
|
|
7918
|
-
: null);
|
|
8203
|
+
const s = second ?? 0;
|
|
8204
|
+
return `${hour}:${minute}:${s}`;
|
|
7919
8205
|
}
|
|
7920
8206
|
else {
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
};
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
minute
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
// For ISO format, use hour24, minute, second
|
|
7967
|
-
if (hour24 === null || minute === null)
|
|
7968
|
-
return null;
|
|
7969
|
-
const dateTimeObj = dateObj
|
|
7970
|
-
.hour(hour24)
|
|
7971
|
-
.minute(minute)
|
|
7972
|
-
.second(second ?? 0);
|
|
7973
|
-
return dateTimeObj.format(showSeconds ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm');
|
|
8207
|
+
if (hour === null || minute === null || meridiem === null) {
|
|
8208
|
+
return '';
|
|
8209
|
+
}
|
|
8210
|
+
return `${hour}:${minute}:${meridiem}`;
|
|
8211
|
+
}
|
|
8212
|
+
}, [hour, minute, second, meridiem, is24Hour]);
|
|
8213
|
+
// Parse custom time input formats like "1400", "2pm", "14:00", "2:00 PM"
|
|
8214
|
+
const parseCustomTimeInput = (input) => {
|
|
8215
|
+
if (!input || !input.trim()) {
|
|
8216
|
+
return { hour: null, minute: null, second: null, meridiem: null };
|
|
8217
|
+
}
|
|
8218
|
+
const trimmed = input.trim().toLowerCase();
|
|
8219
|
+
// Try parsing 4-digit format without colon: "1400" -> 14:00
|
|
8220
|
+
const fourDigitMatch = trimmed.match(/^(\d{4})$/);
|
|
8221
|
+
if (fourDigitMatch) {
|
|
8222
|
+
const digits = fourDigitMatch[1];
|
|
8223
|
+
const hour = parseInt(digits.substring(0, 2), 10);
|
|
8224
|
+
const minute = parseInt(digits.substring(2, 4), 10);
|
|
8225
|
+
if (hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59) {
|
|
8226
|
+
if (is24Hour) {
|
|
8227
|
+
return { hour, minute, second: 0, meridiem: null };
|
|
8228
|
+
}
|
|
8229
|
+
else {
|
|
8230
|
+
// Convert to 12-hour format
|
|
8231
|
+
let hour12 = hour;
|
|
8232
|
+
let meridiem;
|
|
8233
|
+
if (hour === 0) {
|
|
8234
|
+
hour12 = 12;
|
|
8235
|
+
meridiem = 'am';
|
|
8236
|
+
}
|
|
8237
|
+
else if (hour === 12) {
|
|
8238
|
+
hour12 = 12;
|
|
8239
|
+
meridiem = 'pm';
|
|
8240
|
+
}
|
|
8241
|
+
else if (hour > 12) {
|
|
8242
|
+
hour12 = hour - 12;
|
|
8243
|
+
meridiem = 'pm';
|
|
8244
|
+
}
|
|
8245
|
+
else {
|
|
8246
|
+
hour12 = hour;
|
|
8247
|
+
meridiem = 'am';
|
|
8248
|
+
}
|
|
8249
|
+
return { hour: hour12, minute, second: null, meridiem };
|
|
8250
|
+
}
|
|
8251
|
+
}
|
|
7974
8252
|
}
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
else if (meridiem === 'pm' && hour12 < 12)
|
|
7984
|
-
hour24Value = hour12 + 12;
|
|
7985
|
-
const dateTimeObj = dateObj.hour(hour24Value).minute(minute).second(0);
|
|
7986
|
-
return dateTimeObj.format('YYYY-MM-DD hh:mm A');
|
|
8253
|
+
// Try parsing hour with meridiem: "2pm", "14pm", "2am"
|
|
8254
|
+
const hourMeridiemMatch = trimmed.match(/^(\d{1,2})\s*(am|pm)$/);
|
|
8255
|
+
if (hourMeridiemMatch && !is24Hour) {
|
|
8256
|
+
const hour12 = parseInt(hourMeridiemMatch[1], 10);
|
|
8257
|
+
const meridiem = hourMeridiemMatch[2];
|
|
8258
|
+
if (hour12 >= 1 && hour12 <= 12) {
|
|
8259
|
+
return { hour: hour12, minute: 0, second: null, meridiem };
|
|
8260
|
+
}
|
|
7987
8261
|
}
|
|
7988
|
-
|
|
7989
|
-
|
|
7990
|
-
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8262
|
+
// Try parsing 24-hour format with hour only: "14" -> 14:00
|
|
8263
|
+
const hourOnlyMatch = trimmed.match(/^(\d{1,2})$/);
|
|
8264
|
+
if (hourOnlyMatch && is24Hour) {
|
|
8265
|
+
const hour = parseInt(hourOnlyMatch[1], 10);
|
|
8266
|
+
if (hour >= 0 && hour <= 23) {
|
|
8267
|
+
return { hour, minute: 0, second: 0, meridiem: null };
|
|
8268
|
+
}
|
|
8269
|
+
}
|
|
8270
|
+
// Try parsing standard formats: "14:00", "2:00 PM"
|
|
8271
|
+
const time24Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
|
|
8272
|
+
const match24 = trimmed.match(time24Pattern);
|
|
8273
|
+
if (match24) {
|
|
8274
|
+
const hour24 = parseInt(match24[1], 10);
|
|
8275
|
+
const minute = parseInt(match24[2], 10);
|
|
8276
|
+
const second = match24[3] ? parseInt(match24[3], 10) : 0;
|
|
8277
|
+
if (hour24 >= 0 &&
|
|
8278
|
+
hour24 <= 23 &&
|
|
8279
|
+
minute >= 0 &&
|
|
8280
|
+
minute <= 59 &&
|
|
8281
|
+
second >= 0 &&
|
|
8282
|
+
second <= 59) {
|
|
8283
|
+
if (is24Hour) {
|
|
8284
|
+
return { hour: hour24, minute, second, meridiem: null };
|
|
8285
|
+
}
|
|
8286
|
+
else {
|
|
8287
|
+
// Convert to 12-hour format
|
|
8288
|
+
let hour12 = hour24;
|
|
8289
|
+
let meridiem;
|
|
8290
|
+
if (hour24 === 0) {
|
|
8291
|
+
hour12 = 12;
|
|
8292
|
+
meridiem = 'am';
|
|
8293
|
+
}
|
|
8294
|
+
else if (hour24 === 12) {
|
|
8295
|
+
hour12 = 12;
|
|
8296
|
+
meridiem = 'pm';
|
|
8297
|
+
}
|
|
8298
|
+
else if (hour24 > 12) {
|
|
8299
|
+
hour12 = hour24 - 12;
|
|
8300
|
+
meridiem = 'pm';
|
|
8008
8301
|
}
|
|
8009
8302
|
else {
|
|
8010
|
-
|
|
8011
|
-
|
|
8303
|
+
hour12 = hour24;
|
|
8304
|
+
meridiem = 'am';
|
|
8305
|
+
}
|
|
8306
|
+
return { hour: hour12, minute, second: null, meridiem };
|
|
8307
|
+
}
|
|
8308
|
+
}
|
|
8309
|
+
}
|
|
8310
|
+
// Try parsing 12-hour format: "2:00 PM", "2:00PM"
|
|
8311
|
+
const time12Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\s*(am|pm)$/;
|
|
8312
|
+
const match12 = trimmed.match(time12Pattern);
|
|
8313
|
+
if (match12 && !is24Hour) {
|
|
8314
|
+
const hour12 = parseInt(match12[1], 10);
|
|
8315
|
+
const minute = parseInt(match12[2], 10);
|
|
8316
|
+
const second = match12[3] ? parseInt(match12[3], 10) : null;
|
|
8317
|
+
const meridiem = match12[4];
|
|
8318
|
+
if (hour12 >= 1 &&
|
|
8319
|
+
hour12 <= 12 &&
|
|
8320
|
+
minute >= 0 &&
|
|
8321
|
+
minute <= 59 &&
|
|
8322
|
+
(second === null || (second >= 0 && second <= 59))) {
|
|
8323
|
+
return { hour: hour12, minute, second, meridiem };
|
|
8324
|
+
}
|
|
8325
|
+
}
|
|
8326
|
+
return { hour: null, minute: null, second: null, meridiem: null };
|
|
8327
|
+
};
|
|
8328
|
+
const handleTimeValueChange = (details) => {
|
|
8329
|
+
if (details.value.length === 0) {
|
|
8330
|
+
handleTimeChange(null, null, null, null);
|
|
8331
|
+
filter('');
|
|
8332
|
+
return;
|
|
8333
|
+
}
|
|
8334
|
+
const selectedValue = details.value[0];
|
|
8335
|
+
const selectedOption = timeOptions.find((opt) => opt.value === selectedValue);
|
|
8336
|
+
if (selectedOption) {
|
|
8337
|
+
filter('');
|
|
8338
|
+
if (is24Hour) {
|
|
8339
|
+
const opt24 = selectedOption;
|
|
8340
|
+
handleTimeChange(opt24.hour, opt24.minute, opt24.second, null);
|
|
8341
|
+
}
|
|
8342
|
+
else {
|
|
8343
|
+
const opt12 = selectedOption;
|
|
8344
|
+
handleTimeChange(opt12.hour, opt12.minute, null, opt12.meridiem);
|
|
8345
|
+
}
|
|
8346
|
+
}
|
|
8347
|
+
};
|
|
8348
|
+
// Track the current input value for Enter key handling
|
|
8349
|
+
const [timeInputValue, setTimeInputValue] = React.useState('');
|
|
8350
|
+
const handleTimeInputChange = (details) => {
|
|
8351
|
+
// Store the input value and filter
|
|
8352
|
+
setTimeInputValue(details.inputValue);
|
|
8353
|
+
filter(details.inputValue);
|
|
8354
|
+
};
|
|
8355
|
+
const handleTimeInputKeyDown = (e) => {
|
|
8356
|
+
if (e.key === 'Enter') {
|
|
8357
|
+
e.preventDefault();
|
|
8358
|
+
// Use the stored input value
|
|
8359
|
+
const parsed = parseCustomTimeInput(timeInputValue);
|
|
8360
|
+
if (parsed.hour !== null && parsed.minute !== null) {
|
|
8361
|
+
if (is24Hour) {
|
|
8362
|
+
handleTimeChange(parsed.hour, parsed.minute, parsed.second, null);
|
|
8363
|
+
}
|
|
8364
|
+
else {
|
|
8365
|
+
if (parsed.meridiem !== null) {
|
|
8366
|
+
handleTimeChange(parsed.hour, parsed.minute, null, parsed.meridiem);
|
|
8012
8367
|
}
|
|
8013
|
-
}
|
|
8368
|
+
}
|
|
8369
|
+
// Clear the filter and input value after applying
|
|
8370
|
+
filter('');
|
|
8371
|
+
setTimeInputValue('');
|
|
8372
|
+
// Close the popover if value is valid
|
|
8373
|
+
setTimePopoverOpen(false);
|
|
8374
|
+
}
|
|
8375
|
+
}
|
|
8376
|
+
};
|
|
8377
|
+
// Calendar rendering
|
|
8378
|
+
const renderCalendar = () => {
|
|
8379
|
+
const { calendars, getBackProps, getForwardProps, getDateProps } = calendarProps;
|
|
8380
|
+
if (calendars.length === 0)
|
|
8381
|
+
return null;
|
|
8382
|
+
const calendar = calendars[0];
|
|
8383
|
+
return (jsxRuntime.jsxs(react.Grid, { gap: 4, children: [jsxRuntime.jsxs(react.Grid, { templateColumns: 'repeat(4, auto)', justifyContent: 'center', children: [jsxRuntime.jsx(react.Button, { variant: 'ghost', ...getBackProps({ offset: 12 }), children: '<<' }), jsxRuntime.jsx(react.Button, { variant: 'ghost', ...getBackProps(), children: backButtonLabel }), jsxRuntime.jsx(react.Button, { variant: 'ghost', ...getForwardProps(), children: forwardButtonLabel }), jsxRuntime.jsx(react.Button, { variant: 'ghost', ...getForwardProps({ offset: 12 }), children: '>>' })] }), jsxRuntime.jsx(react.Grid, { justifyContent: 'center', children: jsxRuntime.jsxs(react.Text, { children: [monthNamesShort[calendar.month], " ", calendar.year] }) }), jsxRuntime.jsx(react.Grid, { templateColumns: 'repeat(7, auto)', justifyContent: 'center', children: [0, 1, 2, 3, 4, 5, 6].map((weekdayNum) => {
|
|
8384
|
+
return (jsxRuntime.jsx(react.Text, { textAlign: 'center', fontWeight: "semibold", minW: "40px", children: weekdayNamesShort[weekdayNum] }, `header-${weekdayNum}`));
|
|
8385
|
+
}) }), calendar.weeks.map((week, weekIndex) => (jsxRuntime.jsx(react.Grid, { templateColumns: 'repeat(7, auto)', justifyContent: 'center', children: week.map((dateObj, dayIndex) => {
|
|
8386
|
+
if (!dateObj) {
|
|
8387
|
+
return (jsxRuntime.jsx("div", { style: { minWidth: '40px' } }, `empty-${dayIndex}`));
|
|
8388
|
+
}
|
|
8389
|
+
const { date, selected, selectable, isCurrentMonth } = dateObj;
|
|
8390
|
+
const dateProps = getDateProps({
|
|
8391
|
+
dateObj,
|
|
8392
|
+
});
|
|
8393
|
+
return (jsxRuntime.jsx(react.Button, { variant: selected ? 'solid' : 'ghost', colorPalette: selected ? 'blue' : undefined, size: "sm", minW: "40px", disabled: !selectable, opacity: isCurrentMonth ? 1 : 0.4, ...dateProps, children: date.getDate() }, `${date.getTime()}`));
|
|
8394
|
+
}) }, `week-${weekIndex}`)))] }));
|
|
8395
|
+
};
|
|
8396
|
+
return (jsxRuntime.jsxs(react.Flex, { direction: "row", gap: 2, align: "center", children: [jsxRuntime.jsxs(react.Popover.Root, { open: datePopoverOpen, onOpenChange: (e) => setDatePopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsxs(react.Button, { size: "sm", variant: "outline", onClick: () => setDatePopoverOpen(true), justifyContent: "start", children: [jsxRuntime.jsx(md.MdDateRange, {}), dateDisplayText] }) }), portalled ? (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "350px", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsxs(react.Grid, { gap: 4, children: [jsxRuntime.jsx(react.InputGroup, { endElement: jsxRuntime.jsxs(react.Popover.Root, { open: calendarPopoverOpen, onOpenChange: (e) => setCalendarPopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.Button, { variant: "ghost", size: "xs", "aria-label": "Open calendar", onClick: () => setCalendarPopoverOpen(true), children: jsxRuntime.jsx(md.MdDateRange, {}) }) }), jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "350px", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: renderCalendar() }) }) })] }), children: jsxRuntime.jsx(react.Input, { value: dateInputValue, onChange: handleDateInputChange, onBlur: handleDateInputBlur, onKeyDown: handleDateInputKeyDown, placeholder: "YYYY-MM-DD" }) }), showQuickActions && (jsxRuntime.jsxs(react.Grid, { templateColumns: "repeat(4, 1fr)", gap: 2, children: [jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getYesterday()), disabled: !isDateValid(getYesterday()), children: quickActionLabels.yesterday }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getToday()), disabled: !isDateValid(getToday()), children: quickActionLabels.today }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getTomorrow()), disabled: !isDateValid(getTomorrow()), children: quickActionLabels.tomorrow }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getPlus7Days()), disabled: !isDateValid(getPlus7Days()), children: quickActionLabels.plus7Days })] }))] }) }) }) }) })) : (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsxs(react.Grid, { gap: 4, children: [jsxRuntime.jsx(react.InputGroup, { endElement: jsxRuntime.jsxs(react.Popover.Root, { open: calendarPopoverOpen, onOpenChange: (e) => setCalendarPopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.Button, { variant: "ghost", size: "xs", "aria-label": "Open calendar", onClick: () => setCalendarPopoverOpen(true), children: jsxRuntime.jsx(md.MdDateRange, {}) }) }), jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "350px", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: renderCalendar() }) }) })] }), children: jsxRuntime.jsx(react.Input, { value: dateInputValue, onChange: handleDateInputChange, onBlur: handleDateInputBlur, onKeyDown: handleDateInputKeyDown, placeholder: "YYYY-MM-DD" }) }), showQuickActions && (jsxRuntime.jsxs(react.Grid, { templateColumns: "repeat(4, 1fr)", gap: 2, children: [jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getYesterday()), disabled: !isDateValid(getYesterday()), children: quickActionLabels.yesterday }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getToday()), disabled: !isDateValid(getToday()), children: quickActionLabels.today }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getTomorrow()), disabled: !isDateValid(getTomorrow()), children: quickActionLabels.tomorrow }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getPlus7Days()), disabled: !isDateValid(getPlus7Days()), children: quickActionLabels.plus7Days })] }))] }) }) }) }))] }), jsxRuntime.jsxs(react.Popover.Root, { open: timePopoverOpen, onOpenChange: (e) => setTimePopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsxs(react.Button, { size: "sm", variant: "outline", onClick: () => setTimePopoverOpen(true), justifyContent: "start", children: [jsxRuntime.jsx(bs.BsClock, {}), timeDisplayText] }) }), portalled ? (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "300px", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsx(react.Grid, { gap: 2, children: jsxRuntime.jsxs(react.Combobox.Root, { value: currentTimeValue ? [currentTimeValue] : [], onValueChange: handleTimeValueChange, onInputValueChange: handleTimeInputChange, collection: collection, allowCustomValue: true, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { placeholder: timePickerLabels?.placeholder ??
|
|
8397
|
+
(is24Hour ? 'HH:mm' : 'hh:mm AM/PM'), onKeyDown: handleTimeInputKeyDown }) }), jsxRuntime.jsx(react.Combobox.IndicatorGroup, { children: jsxRuntime.jsx(react.Combobox.Trigger, {}) })] }), jsxRuntime.jsx(react.Portal, { disabled: true, children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [jsxRuntime.jsx(react.Combobox.Empty, { children: timePickerLabels?.emptyMessage ??
|
|
8398
|
+
'No time found' }), collection.items.map((item) => {
|
|
8399
|
+
const option = item;
|
|
8400
|
+
return (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsxs(react.Flex, { justify: "space-between", align: "center", w: "100%", children: [jsxRuntime.jsx(react.Text, { children: option.label }), option.durationText && (jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.500", children: option.durationText }))] }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, option.value));
|
|
8401
|
+
})] }) }) })] }) }) }) }) }) })) : (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "300px", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsx(react.Grid, { gap: 2, children: jsxRuntime.jsxs(react.Combobox.Root, { value: currentTimeValue ? [currentTimeValue] : [], onValueChange: handleTimeValueChange, onInputValueChange: handleTimeInputChange, collection: collection, allowCustomValue: true, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { placeholder: timePickerLabels?.placeholder ??
|
|
8402
|
+
(is24Hour ? 'HH:mm' : 'hh:mm AM/PM'), onKeyDown: handleTimeInputKeyDown }) }), jsxRuntime.jsx(react.Combobox.IndicatorGroup, { children: jsxRuntime.jsx(react.Combobox.Trigger, {}) })] }), jsxRuntime.jsx(react.Portal, { disabled: true, children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [jsxRuntime.jsx(react.Combobox.Empty, { children: timePickerLabels?.emptyMessage ?? 'No time found' }), collection.items.map((item) => {
|
|
8403
|
+
const option = item;
|
|
8404
|
+
return (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsxs(react.Flex, { justify: "space-between", align: "center", w: "100%", children: [jsxRuntime.jsx(react.Text, { children: option.label }), option.durationText && (jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.500", children: option.durationText }))] }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, option.value));
|
|
8405
|
+
})] }) }) })] }) }) }) }) }))] }), showTimezoneSelector && (jsxRuntime.jsxs(react.Popover.Root, { open: timezonePopoverOpen, onOpenChange: (e) => setTimezonePopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => setTimezonePopoverOpen(true), justifyContent: "start", children: timezoneDisplayText || 'Select timezone' }) }), portalled ? (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "250px", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsx(react.Grid, { gap: 2, children: jsxRuntime.jsxs(react.Select.Root, { size: "sm", collection: timezoneCollection, value: timezoneOffset ? [timezoneOffset] : [], onValueChange: (e) => {
|
|
8406
|
+
const newOffset = e.value[0];
|
|
8407
|
+
if (newOffset) {
|
|
8408
|
+
setTimezoneOffset(newOffset);
|
|
8409
|
+
// Update date-time with new offset
|
|
8410
|
+
if (selectedDate &&
|
|
8411
|
+
hour !== null &&
|
|
8412
|
+
minute !== null) {
|
|
8413
|
+
updateDateTime(selectedDate, hour, minute, second, meridiem);
|
|
8414
|
+
}
|
|
8415
|
+
// Close popover after selection
|
|
8416
|
+
setTimezonePopoverOpen(false);
|
|
8417
|
+
}
|
|
8418
|
+
}, children: [jsxRuntime.jsxs(react.Select.Control, { children: [jsxRuntime.jsx(react.Select.Trigger, {}), jsxRuntime.jsx(react.Select.IndicatorGroup, { children: jsxRuntime.jsx(react.Select.Indicator, {}) })] }), jsxRuntime.jsx(react.Select.Positioner, { children: jsxRuntime.jsx(react.Select.Content, { children: timezoneCollection.items.map((item) => (jsxRuntime.jsxs(react.Select.Item, { item: item, children: [jsxRuntime.jsx(react.Select.ItemText, { children: item.label }), jsxRuntime.jsx(react.Select.ItemIndicator, {})] }, item.value))) }) })] }) }) }) }) }) })) : (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "250px", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsx(react.Grid, { gap: 2, children: jsxRuntime.jsxs(react.Select.Root, { size: "sm", collection: timezoneCollection, value: timezoneOffset ? [timezoneOffset] : [], onValueChange: (e) => {
|
|
8419
|
+
const newOffset = e.value[0];
|
|
8420
|
+
if (newOffset) {
|
|
8421
|
+
setTimezoneOffset(newOffset);
|
|
8422
|
+
// Update date-time with new offset (pass it directly to avoid stale state)
|
|
8423
|
+
if (selectedDate &&
|
|
8424
|
+
hour !== null &&
|
|
8425
|
+
minute !== null) {
|
|
8426
|
+
updateDateTime(selectedDate, hour, minute, second, meridiem, newOffset);
|
|
8427
|
+
}
|
|
8428
|
+
// Close popover after selection
|
|
8429
|
+
setTimezonePopoverOpen(false);
|
|
8430
|
+
}
|
|
8431
|
+
}, children: [jsxRuntime.jsxs(react.Select.Control, { children: [jsxRuntime.jsx(react.Select.Trigger, {}), jsxRuntime.jsx(react.Select.IndicatorGroup, { children: jsxRuntime.jsx(react.Select.Indicator, {}) })] }), jsxRuntime.jsx(react.Select.Positioner, { children: jsxRuntime.jsx(react.Select.Content, { children: timezoneCollection.items.map((item) => (jsxRuntime.jsxs(react.Select.Item, { item: item, children: [jsxRuntime.jsx(react.Select.ItemText, { children: item.label }), jsxRuntime.jsx(react.Select.ItemIndicator, {})] }, item.value))) }) })] }) }) }) }) }))] }))] }));
|
|
8014
8432
|
}
|
|
8015
8433
|
|
|
8016
8434
|
dayjs.extend(utc);
|
|
@@ -8024,11 +8442,12 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
|
|
|
8024
8442
|
dateFormat = 'YYYY-MM-DD[T]HH:mm:ssZ', } = schema;
|
|
8025
8443
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
8026
8444
|
const colLabel = formI18n.colLabel;
|
|
8027
|
-
|
|
8445
|
+
React.useState(false);
|
|
8028
8446
|
const selectedDate = watch(colLabel);
|
|
8029
|
-
|
|
8447
|
+
selectedDate && dayjs(selectedDate).tz(timezone).isValid()
|
|
8030
8448
|
? dayjs(selectedDate).tz(timezone).format(displayDateFormat)
|
|
8031
8449
|
: '';
|
|
8450
|
+
// Set default date on mount if no value exists
|
|
8032
8451
|
const dateTimePickerLabelsConfig = {
|
|
8033
8452
|
monthNamesShort: dateTimePickerLabels?.monthNamesShort ?? [
|
|
8034
8453
|
'January',
|
|
@@ -8070,9 +8489,7 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
|
|
|
8070
8489
|
}
|
|
8071
8490
|
}, timezone: timezone, labels: dateTimePickerLabelsConfig, timePickerLabels: timePickerLabels }));
|
|
8072
8491
|
return (jsxRuntime.jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
8073
|
-
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children:
|
|
8074
|
-
setOpen(true);
|
|
8075
|
-
}, justifyContent: 'start', children: [jsxRuntime.jsx(md.MdDateRange, {}), displayDate || ''] }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "350px", minH: "10rem", children: jsxRuntime.jsx(react.Popover.Body, { children: dateTimePickerContent }) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "350px", minH: "10rem", children: jsxRuntime.jsx(react.Popover.Body, { children: dateTimePickerContent }) }) }) }))] }) }));
|
|
8492
|
+
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: dateTimePickerContent }));
|
|
8076
8493
|
};
|
|
8077
8494
|
|
|
8078
8495
|
const SchemaRenderer = ({ schema, prefix, column, }) => {
|