@bsol-oss/react-datatable5 12.0.0-beta.90 → 12.0.0-beta.91
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 +28 -3
- package/dist/index.js +886 -237
- package/dist/index.mjs +889 -241
- package/dist/types/components/DataTable/display/TextCell.d.ts +11 -4
- package/dist/types/components/DataTable/display/TextWithCopy.d.ts +8 -0
- package/dist/types/components/DatePicker/DatePickerInput.d.ts +18 -0
- package/dist/types/components/DatePicker/DateTimePicker.d.ts +4 -1
- package/dist/types/components/DatePicker/IsoTimePicker.d.ts +2 -1
- package/dist/types/components/DatePicker/index.d.ts +2 -1
- package/dist/types/components/TimePicker/TimePicker.d.ts +2 -1
- package/dist/types/index.d.ts +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -34,6 +34,7 @@ var dayjs = require('dayjs');
|
|
|
34
34
|
var utc = require('dayjs/plugin/utc');
|
|
35
35
|
var timezone = require('dayjs/plugin/timezone');
|
|
36
36
|
var ti = require('react-icons/ti');
|
|
37
|
+
var customParseFormat = require('dayjs/plugin/customParseFormat');
|
|
37
38
|
var matchSorterUtils = require('@tanstack/match-sorter-utils');
|
|
38
39
|
|
|
39
40
|
function _interopNamespaceDefault(e) {
|
|
@@ -3335,11 +3336,136 @@ const TableLoadingComponent = ({ render, }) => {
|
|
|
3335
3336
|
return jsxRuntime.jsx(jsxRuntime.Fragment, { children: render(query.isLoading) });
|
|
3336
3337
|
};
|
|
3337
3338
|
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3339
|
+
// Helper function to highlight matching text
|
|
3340
|
+
const highlightText$1 = (text, searchTerm) => {
|
|
3341
|
+
if (!searchTerm || searchTerm.trim() === '') {
|
|
3342
|
+
return String(text);
|
|
3343
|
+
}
|
|
3344
|
+
const textStr = String(text);
|
|
3345
|
+
const searchLower = searchTerm.toLowerCase();
|
|
3346
|
+
const textLower = textStr.toLowerCase();
|
|
3347
|
+
const parts = [];
|
|
3348
|
+
let lastIndex = 0;
|
|
3349
|
+
let index = textLower.indexOf(searchLower, lastIndex);
|
|
3350
|
+
while (index !== -1) {
|
|
3351
|
+
// Add text before match
|
|
3352
|
+
if (index > lastIndex) {
|
|
3353
|
+
parts.push(textStr.substring(lastIndex, index));
|
|
3354
|
+
}
|
|
3355
|
+
// Add highlighted match
|
|
3356
|
+
parts.push(jsxRuntime.jsx(react.Text, { as: "mark", bg: {
|
|
3357
|
+
base: 'yellow.200',
|
|
3358
|
+
_dark: 'yellow.800',
|
|
3359
|
+
}, color: {
|
|
3360
|
+
base: 'gray.900',
|
|
3361
|
+
_dark: 'gray.100',
|
|
3362
|
+
}, px: 0.5, borderRadius: "sm", children: textStr.substring(index, index + searchTerm.length) }, index));
|
|
3363
|
+
lastIndex = index + searchTerm.length;
|
|
3364
|
+
index = textLower.indexOf(searchLower, lastIndex);
|
|
3365
|
+
}
|
|
3366
|
+
// Add remaining text
|
|
3367
|
+
if (lastIndex < textStr.length) {
|
|
3368
|
+
parts.push(textStr.substring(lastIndex));
|
|
3369
|
+
}
|
|
3370
|
+
return parts.length > 0 ? jsxRuntime.jsx(jsxRuntime.Fragment, { children: parts }) : textStr;
|
|
3371
|
+
};
|
|
3372
|
+
const TextWithCopy = ({ text, globalFilter, highlightedText, }) => {
|
|
3373
|
+
const textValue = String(text ?? '');
|
|
3374
|
+
const displayText = highlightedText !== undefined
|
|
3375
|
+
? highlightedText
|
|
3376
|
+
: highlightText$1(textValue, globalFilter);
|
|
3377
|
+
return (jsxRuntime.jsxs(react.HStack, { gap: 2, alignItems: "center", children: [jsxRuntime.jsx(react.Text, { as: "span", children: displayText }), jsxRuntime.jsx(react.Clipboard.Root, { value: textValue, children: jsxRuntime.jsx(react.Clipboard.Trigger, { asChild: true, children: jsxRuntime.jsx(react.IconButton, { size: "xs", variant: "ghost", "aria-label": "Copy", children: jsxRuntime.jsx(react.Clipboard.Indicator, { copied: jsxRuntime.jsx(lu.LuCheck, {}), children: jsxRuntime.jsx(lu.LuCopy, {}) }) }) }) })] }));
|
|
3378
|
+
};
|
|
3379
|
+
|
|
3380
|
+
// Helper function to highlight matching text
|
|
3381
|
+
const highlightText = (text, searchTerm) => {
|
|
3382
|
+
if (!searchTerm || searchTerm.trim() === '') {
|
|
3383
|
+
return String(text);
|
|
3384
|
+
}
|
|
3385
|
+
const textStr = String(text);
|
|
3386
|
+
const searchLower = searchTerm.toLowerCase();
|
|
3387
|
+
const textLower = textStr.toLowerCase();
|
|
3388
|
+
const parts = [];
|
|
3389
|
+
let lastIndex = 0;
|
|
3390
|
+
let index = textLower.indexOf(searchLower, lastIndex);
|
|
3391
|
+
while (index !== -1) {
|
|
3392
|
+
// Add text before match
|
|
3393
|
+
if (index > lastIndex) {
|
|
3394
|
+
parts.push(textStr.substring(lastIndex, index));
|
|
3395
|
+
}
|
|
3396
|
+
// Add highlighted match
|
|
3397
|
+
parts.push(jsxRuntime.jsx(react.Text, { as: "mark", bg: {
|
|
3398
|
+
base: 'yellow.200',
|
|
3399
|
+
_dark: 'yellow.800',
|
|
3400
|
+
}, color: {
|
|
3401
|
+
base: 'gray.900',
|
|
3402
|
+
_dark: 'gray.100',
|
|
3403
|
+
}, px: 0.5, borderRadius: "sm", children: textStr.substring(index, index + searchTerm.length) }, index));
|
|
3404
|
+
lastIndex = index + searchTerm.length;
|
|
3405
|
+
index = textLower.indexOf(searchLower, lastIndex);
|
|
3406
|
+
}
|
|
3407
|
+
// Add remaining text
|
|
3408
|
+
if (lastIndex < textStr.length) {
|
|
3409
|
+
parts.push(textStr.substring(lastIndex));
|
|
3410
|
+
}
|
|
3411
|
+
return parts.length > 0 ? jsxRuntime.jsx(jsxRuntime.Fragment, { children: parts }) : textStr;
|
|
3412
|
+
};
|
|
3413
|
+
const RenderValue = ({ text, href, onClick, isCopyable, isBadge, badgeColor, colorPalette, globalFilter, }) => {
|
|
3414
|
+
const highlightedText = React.useMemo(() => highlightText(text ?? '', globalFilter), [text, globalFilter]);
|
|
3415
|
+
if (isBadge) {
|
|
3416
|
+
return (jsxRuntime.jsx(react.Badge, { colorPalette: colorPalette || badgeColor, children: highlightedText }));
|
|
3417
|
+
}
|
|
3418
|
+
// onClick takes precedence over href
|
|
3419
|
+
if (onClick) {
|
|
3420
|
+
return (jsxRuntime.jsx(react.Box, { as: "button", onClick: onClick, cursor: "pointer", textAlign: "left", _hover: {
|
|
3421
|
+
textDecoration: 'underline',
|
|
3422
|
+
color: {
|
|
3423
|
+
base: 'blue.500',
|
|
3424
|
+
_dark: 'blue.400',
|
|
3425
|
+
},
|
|
3426
|
+
}, transition: "all 0.2s", children: highlightedText }));
|
|
3341
3427
|
}
|
|
3342
|
-
|
|
3428
|
+
if (href) {
|
|
3429
|
+
return (jsxRuntime.jsxs(react.Link, { href: href, target: "_blank", rel: "noopener noreferrer", _hover: {
|
|
3430
|
+
textDecoration: 'underline',
|
|
3431
|
+
}, children: [highlightedText, " ", jsxRuntime.jsx(react.Icon, { as: lu.LuExternalLink })] }));
|
|
3432
|
+
}
|
|
3433
|
+
if (isCopyable) {
|
|
3434
|
+
return (jsxRuntime.jsx(TextWithCopy, { text: text, globalFilter: globalFilter, highlightedText: highlightedText }));
|
|
3435
|
+
}
|
|
3436
|
+
return jsxRuntime.jsx(jsxRuntime.Fragment, { children: highlightedText });
|
|
3437
|
+
};
|
|
3438
|
+
const TextCell = ({ text, href, onClick, isCopyable, isBadge, badgeColor, colorPalette,
|
|
3439
|
+
// Legacy props
|
|
3440
|
+
label, containerProps = {}, textProps = {}, children, }) => {
|
|
3441
|
+
// Get globalFilter from context
|
|
3442
|
+
// If not in DataTable context, will use default empty string from context
|
|
3443
|
+
const { globalFilter } = useDataTableContext();
|
|
3444
|
+
// Legacy API: if children is provided, use old behavior
|
|
3445
|
+
if (children !== undefined) {
|
|
3446
|
+
const displayText = typeof children === 'string' || typeof children === 'number'
|
|
3447
|
+
? String(children)
|
|
3448
|
+
: children;
|
|
3449
|
+
const highlightedDisplayText = typeof displayText === 'string' || typeof displayText === 'number'
|
|
3450
|
+
? highlightText(displayText, globalFilter)
|
|
3451
|
+
: displayText;
|
|
3452
|
+
if (label) {
|
|
3453
|
+
return (jsxRuntime.jsx(react.Flex, { alignItems: 'center', height: '100%', ...containerProps, children: jsxRuntime.jsx(Tooltip, { content: jsxRuntime.jsx(react.Text, { as: "span", overflow: "hidden", textOverflow: 'ellipsis', children: label }), children: jsxRuntime.jsx(react.Text, { as: "span", overflow: "hidden", textOverflow: 'ellipsis', wordBreak: 'break-all', ...textProps, children: highlightedDisplayText }) }) }));
|
|
3454
|
+
}
|
|
3455
|
+
return (jsxRuntime.jsx(react.Flex, { alignItems: 'center', height: '100%', ...containerProps, children: jsxRuntime.jsx(react.Text, { as: "span", overflow: "hidden", textOverflow: 'ellipsis', wordBreak: 'break-all', ...textProps, children: highlightedDisplayText }) }));
|
|
3456
|
+
}
|
|
3457
|
+
// New API: use text prop
|
|
3458
|
+
const displayValue = text ?? children;
|
|
3459
|
+
if (Array.isArray(displayValue)) {
|
|
3460
|
+
return (jsxRuntime.jsx(react.Flex, { gap: 2, flexWrap: "wrap", children: displayValue.map((item, index) => {
|
|
3461
|
+
const highlightedItem = highlightText(item, globalFilter);
|
|
3462
|
+
return (jsxRuntime.jsx(react.Badge, { colorPalette: colorPalette || badgeColor, children: highlightedItem }, index));
|
|
3463
|
+
}) }));
|
|
3464
|
+
}
|
|
3465
|
+
if (!!displayValue === false) {
|
|
3466
|
+
return (jsxRuntime.jsx(react.Text, { textOverflow: "ellipsis", whiteSpace: "nowrap", overflow: "hidden", wordBreak: "break-all", display: "flex", alignItems: "center", height: "100%", children: "-" }));
|
|
3467
|
+
}
|
|
3468
|
+
return (jsxRuntime.jsx(react.Box, { textOverflow: "ellipsis", whiteSpace: "nowrap", wordBreak: "break-all", overflow: "auto", display: "flex", alignItems: "center", height: "100%", children: jsxRuntime.jsx(RenderValue, { text: displayValue, href: href, onClick: onClick, isCopyable: isCopyable, isBadge: isBadge, badgeColor: badgeColor, colorPalette: colorPalette, globalFilter: globalFilter }) }));
|
|
3343
3469
|
};
|
|
3344
3470
|
|
|
3345
3471
|
const Tag = React__namespace.forwardRef(function Tag(props, ref) {
|
|
@@ -5315,15 +5441,15 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
|
|
|
5315
5441
|
// Check if we're currently searching (user typed but debounce hasn't fired yet)
|
|
5316
5442
|
const isSearching = searchText !== debouncedSearchText;
|
|
5317
5443
|
// Transform data for combobox collection
|
|
5444
|
+
// label is used for filtering/searching (must be a string)
|
|
5445
|
+
// raw item is stored for custom rendering
|
|
5318
5446
|
const comboboxItems = React.useMemo(() => {
|
|
5319
5447
|
return dataList.map((item) => ({
|
|
5320
|
-
label:
|
|
5321
|
-
? String(renderDisplay(item))
|
|
5322
|
-
: String(item[display_column] ?? ''),
|
|
5448
|
+
label: String(item[display_column] ?? ''), // Always use display_column for filtering
|
|
5323
5449
|
value: String(item[column_ref]),
|
|
5324
5450
|
raw: item,
|
|
5325
5451
|
}));
|
|
5326
|
-
}, [dataList, display_column, column_ref
|
|
5452
|
+
}, [dataList, display_column, column_ref]);
|
|
5327
5453
|
// Use filter hook for combobox
|
|
5328
5454
|
const { contains } = react.useFilter({ sensitivity: 'base' });
|
|
5329
5455
|
// Create collection for combobox
|
|
@@ -5382,13 +5508,17 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
|
|
|
5382
5508
|
? idPickerLabels?.emptySearchResult ??
|
|
5383
5509
|
formI18n.t('empty_search_result')
|
|
5384
5510
|
: idPickerLabels?.initialResults ??
|
|
5385
|
-
formI18n.t('initial_results') })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children:
|
|
5511
|
+
formI18n.t('initial_results') })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: !!renderDisplay === true
|
|
5512
|
+
? renderDisplay(item.raw)
|
|
5513
|
+
: 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: formI18n.t('loading_failed') })) : isFetching || isLoading || isPending || isSearching ? (
|
|
5386
5514
|
// Show skeleton items to prevent UI shift
|
|
5387
5515
|
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
|
|
5388
5516
|
? idPickerLabels?.emptySearchResult ??
|
|
5389
5517
|
formI18n.t('empty_search_result')
|
|
5390
5518
|
: idPickerLabels?.initialResults ??
|
|
5391
|
-
formI18n.t('initial_results') })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children:
|
|
5519
|
+
formI18n.t('initial_results') })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: !!renderDisplay === true
|
|
5520
|
+
? renderDisplay(item.raw)
|
|
5521
|
+
: item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
|
|
5392
5522
|
};
|
|
5393
5523
|
|
|
5394
5524
|
const NumberInputRoot = React__namespace.forwardRef(function NumberInput(props, ref) {
|
|
@@ -5767,7 +5897,7 @@ meridiemLabel: _meridiemLabel = {
|
|
|
5767
5897
|
pm: 'pm',
|
|
5768
5898
|
},
|
|
5769
5899
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
5770
|
-
onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedDate, }) {
|
|
5900
|
+
onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedDate, portalled = true, }) {
|
|
5771
5901
|
// Generate time options (every 15 minutes)
|
|
5772
5902
|
const timeOptions = React.useMemo(() => {
|
|
5773
5903
|
const options = [];
|
|
@@ -5790,12 +5920,10 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
|
|
|
5790
5920
|
for (let h = 1; h <= 12; h++) {
|
|
5791
5921
|
for (let m = 0; m < 60; m += 15) {
|
|
5792
5922
|
const hour24 = mer === 'am' ? (h === 12 ? 0 : h) : h === 12 ? 12 : h + 12;
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
.format('HH:mmZ');
|
|
5798
|
-
const displayTime = dayjs(`1970-01-01T${timeStr}`, 'HH:mmZ').format('hh:mm a');
|
|
5923
|
+
// Format time directly without using dayjs with dummy dates
|
|
5924
|
+
const formattedHour = h.toString().padStart(2, '0');
|
|
5925
|
+
const formattedMinute = m.toString().padStart(2, '0');
|
|
5926
|
+
const displayTime = `${formattedHour}:${formattedMinute} ${mer}`;
|
|
5799
5927
|
// Filter out times that would result in negative duration (only when dates are the same)
|
|
5800
5928
|
if (startDateTime && selectedDate && shouldFilterByDate) {
|
|
5801
5929
|
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
@@ -5808,8 +5936,8 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
|
|
|
5808
5936
|
continue; // Skip this option as it would result in negative duration
|
|
5809
5937
|
}
|
|
5810
5938
|
}
|
|
5811
|
-
// Calculate
|
|
5812
|
-
let
|
|
5939
|
+
// Calculate duration if startTime is provided
|
|
5940
|
+
let durationText;
|
|
5813
5941
|
if (startDateTime && selectedDate) {
|
|
5814
5942
|
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
5815
5943
|
const optionDateTime = selectedDateObj
|
|
@@ -5822,21 +5950,30 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
|
|
|
5822
5950
|
const diffMs = optionDateTime.diff(startDateTime);
|
|
5823
5951
|
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
5824
5952
|
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5953
|
+
const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
|
|
5954
|
+
if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
|
|
5955
|
+
let diffText = '';
|
|
5956
|
+
if (diffHours > 0) {
|
|
5957
|
+
diffText = `${diffHours}h ${diffMinutes}m`;
|
|
5958
|
+
}
|
|
5959
|
+
else if (diffMinutes > 0) {
|
|
5960
|
+
diffText = `${diffMinutes}m ${diffSeconds}s`;
|
|
5961
|
+
}
|
|
5962
|
+
else {
|
|
5963
|
+
diffText = `${diffSeconds}s`;
|
|
5964
|
+
}
|
|
5965
|
+
durationText = `+${diffText}`;
|
|
5830
5966
|
}
|
|
5831
5967
|
}
|
|
5832
5968
|
}
|
|
5833
5969
|
options.push({
|
|
5834
|
-
label,
|
|
5970
|
+
label: displayTime,
|
|
5835
5971
|
value: `${h}:${m.toString().padStart(2, '0')}:${mer}`,
|
|
5836
5972
|
hour: h,
|
|
5837
5973
|
minute: m,
|
|
5838
5974
|
meridiem: mer,
|
|
5839
5975
|
searchText: displayTime, // Use base time without duration for searching
|
|
5976
|
+
durationText,
|
|
5840
5977
|
});
|
|
5841
5978
|
}
|
|
5842
5979
|
}
|
|
@@ -5850,22 +5987,6 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
|
|
|
5850
5987
|
itemToValue: (item) => item.value,
|
|
5851
5988
|
filter: contains,
|
|
5852
5989
|
});
|
|
5853
|
-
// Track input mode vs display mode
|
|
5854
|
-
const [isInputMode, setIsInputMode] = React.useState(false);
|
|
5855
|
-
const [inputValue, setInputValue] = React.useState('');
|
|
5856
|
-
const inputRef = React.useRef(null);
|
|
5857
|
-
// Switch to display mode when value is selected
|
|
5858
|
-
React.useEffect(() => {
|
|
5859
|
-
if (hour !== null && minute !== null && meridiem !== null) {
|
|
5860
|
-
setIsInputMode(false);
|
|
5861
|
-
}
|
|
5862
|
-
}, [hour, minute, meridiem]);
|
|
5863
|
-
// Focus input when switching to input mode
|
|
5864
|
-
React.useEffect(() => {
|
|
5865
|
-
if (isInputMode && inputRef.current) {
|
|
5866
|
-
inputRef.current.focus();
|
|
5867
|
-
}
|
|
5868
|
-
}, [isInputMode]);
|
|
5869
5990
|
// Get current value string for combobox
|
|
5870
5991
|
const currentValue = React.useMemo(() => {
|
|
5871
5992
|
if (hour === null || minute === null || meridiem === null) {
|
|
@@ -5873,14 +5994,14 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
|
|
|
5873
5994
|
}
|
|
5874
5995
|
return `${hour}:${minute.toString().padStart(2, '0')}:${meridiem}`;
|
|
5875
5996
|
}, [hour, minute, meridiem]);
|
|
5876
|
-
//
|
|
5877
|
-
const
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
return
|
|
5997
|
+
// Calculate duration difference
|
|
5998
|
+
const durationDiff = React.useMemo(() => {
|
|
5999
|
+
if (!startTime ||
|
|
6000
|
+
!selectedDate ||
|
|
6001
|
+
hour === null ||
|
|
6002
|
+
minute === null ||
|
|
6003
|
+
meridiem === null) {
|
|
6004
|
+
return null;
|
|
5884
6005
|
}
|
|
5885
6006
|
const hour24 = meridiem === 'am'
|
|
5886
6007
|
? hour === 12
|
|
@@ -5889,45 +6010,42 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
|
|
|
5889
6010
|
: hour === 12
|
|
5890
6011
|
? 12
|
|
5891
6012
|
: hour + 12;
|
|
5892
|
-
const
|
|
5893
|
-
|
|
6013
|
+
const startDateObj = dayjs(startTime).tz(timezone);
|
|
6014
|
+
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
6015
|
+
const currentDateTime = selectedDateObj
|
|
5894
6016
|
.hour(hour24)
|
|
5895
6017
|
.minute(minute)
|
|
5896
|
-
.
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
}
|
|
6018
|
+
.second(0)
|
|
6019
|
+
.millisecond(0);
|
|
6020
|
+
if (!startDateObj.isValid() || !currentDateTime.isValid()) {
|
|
6021
|
+
return null;
|
|
6022
|
+
}
|
|
6023
|
+
const diffMs = currentDateTime.diff(startDateObj);
|
|
6024
|
+
if (diffMs < 0) {
|
|
6025
|
+
return null;
|
|
6026
|
+
}
|
|
6027
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
6028
|
+
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
6029
|
+
const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
|
|
6030
|
+
if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
|
|
6031
|
+
let diffText = '';
|
|
6032
|
+
if (diffHours > 0) {
|
|
6033
|
+
diffText = `${diffHours}h ${diffMinutes}m`;
|
|
6034
|
+
}
|
|
6035
|
+
else if (diffMinutes > 0) {
|
|
6036
|
+
diffText = `${diffMinutes}m ${diffSeconds}s`;
|
|
6037
|
+
}
|
|
6038
|
+
else {
|
|
6039
|
+
diffText = `${diffSeconds}s`;
|
|
5919
6040
|
}
|
|
6041
|
+
return `+${diffText}`;
|
|
5920
6042
|
}
|
|
5921
|
-
return
|
|
5922
|
-
}, [hour, minute, meridiem,
|
|
5923
|
-
// Choose text based on mode
|
|
5924
|
-
const displayText = isInputMode ? inputModeText : displayModeText;
|
|
6043
|
+
return null;
|
|
6044
|
+
}, [hour, minute, meridiem, startTime, selectedDate, timezone]);
|
|
5925
6045
|
const handleClear = () => {
|
|
5926
6046
|
setHour(null);
|
|
5927
6047
|
setMinute(null);
|
|
5928
6048
|
setMeridiem(null);
|
|
5929
|
-
setIsInputMode(false);
|
|
5930
|
-
setInputValue('');
|
|
5931
6049
|
filter(''); // Reset filter to show all options
|
|
5932
6050
|
onChange({ hour: null, minute: null, meridiem: null });
|
|
5933
6051
|
};
|
|
@@ -5942,8 +6060,6 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
|
|
|
5942
6060
|
setHour(selectedOption.hour);
|
|
5943
6061
|
setMinute(selectedOption.minute);
|
|
5944
6062
|
setMeridiem(selectedOption.meridiem);
|
|
5945
|
-
setIsInputMode(false); // Switch to display mode
|
|
5946
|
-
setInputValue('');
|
|
5947
6063
|
filter(''); // Reset filter after selection
|
|
5948
6064
|
onChange({
|
|
5949
6065
|
hour: selectedOption.hour,
|
|
@@ -5952,41 +6068,16 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
|
|
|
5952
6068
|
});
|
|
5953
6069
|
}
|
|
5954
6070
|
};
|
|
5955
|
-
//
|
|
5956
|
-
const
|
|
5957
|
-
|
|
5958
|
-
e.preventDefault();
|
|
5959
|
-
const firstOption = collection.items[0];
|
|
5960
|
-
if (firstOption) {
|
|
5961
|
-
const selectedOption = timeOptions.find((opt) => opt.value === firstOption.value);
|
|
5962
|
-
if (selectedOption) {
|
|
5963
|
-
setHour(selectedOption.hour);
|
|
5964
|
-
setMinute(selectedOption.minute);
|
|
5965
|
-
setMeridiem(selectedOption.meridiem);
|
|
5966
|
-
setIsInputMode(false); // Switch to display mode
|
|
5967
|
-
setInputValue('');
|
|
5968
|
-
filter('');
|
|
5969
|
-
onChange({
|
|
5970
|
-
hour: selectedOption.hour,
|
|
5971
|
-
minute: selectedOption.minute,
|
|
5972
|
-
meridiem: selectedOption.meridiem,
|
|
5973
|
-
});
|
|
5974
|
-
}
|
|
5975
|
-
}
|
|
5976
|
-
}
|
|
5977
|
-
};
|
|
5978
|
-
const handleInputValueChange = (details) => {
|
|
5979
|
-
const inputValue = details.inputValue.trim();
|
|
5980
|
-
setInputValue(inputValue);
|
|
5981
|
-
setIsInputMode(true); // Switch to input mode
|
|
6071
|
+
// Parse input value and update state
|
|
6072
|
+
const parseAndCommitInput = (value) => {
|
|
6073
|
+
const trimmedValue = value.trim();
|
|
5982
6074
|
// Filter the collection based on input
|
|
5983
|
-
filter(
|
|
5984
|
-
if (!
|
|
5985
|
-
setIsInputMode(false);
|
|
6075
|
+
filter(trimmedValue);
|
|
6076
|
+
if (!trimmedValue) {
|
|
5986
6077
|
return;
|
|
5987
6078
|
}
|
|
5988
6079
|
// Try to parse custom input using explicit regex patterns
|
|
5989
|
-
const normalized =
|
|
6080
|
+
const normalized = trimmedValue.toLowerCase().replace(/\s+/g, '');
|
|
5990
6081
|
// Pattern 1: 12-hour format with meridiem (e.g., "930pm", "1230am", "9:30pm", "12:30am")
|
|
5991
6082
|
// Matches: 1-2 digits hour, optional colon, 2 digits minute, am/pm
|
|
5992
6083
|
const pattern12HourWithMeridiem = /^(\d{1,2}):?(\d{2})(am|pm)$/;
|
|
@@ -5997,29 +6088,43 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
|
|
|
5997
6088
|
const parsedMeridiem = match12Hour[3];
|
|
5998
6089
|
// Validate hour (1-12)
|
|
5999
6090
|
if (parsedHour < 1 || parsedHour > 12) {
|
|
6091
|
+
// Parse failed, select first result
|
|
6092
|
+
selectFirstResult();
|
|
6000
6093
|
return;
|
|
6001
6094
|
}
|
|
6002
6095
|
// Validate minute (0-59)
|
|
6003
|
-
|
|
6096
|
+
if (parsedMinute < 0 || parsedMinute > 59) {
|
|
6097
|
+
// Parse failed, select first result
|
|
6098
|
+
selectFirstResult();
|
|
6099
|
+
return;
|
|
6100
|
+
}
|
|
6004
6101
|
setHour(parsedHour);
|
|
6005
|
-
setMinute(
|
|
6102
|
+
setMinute(parsedMinute);
|
|
6006
6103
|
setMeridiem(parsedMeridiem);
|
|
6007
6104
|
onChange({
|
|
6008
6105
|
hour: parsedHour,
|
|
6009
|
-
minute:
|
|
6106
|
+
minute: parsedMinute,
|
|
6010
6107
|
meridiem: parsedMeridiem,
|
|
6011
6108
|
});
|
|
6012
6109
|
return;
|
|
6013
6110
|
}
|
|
6014
6111
|
// Pattern 2: 24-hour format (e.g., "2130", "09:30", "21:30")
|
|
6015
6112
|
// Matches: 1-2 digits hour, optional colon, 2 digits minute
|
|
6016
|
-
const pattern24Hour = /^(\d{2}):?(\d{2})$/;
|
|
6113
|
+
const pattern24Hour = /^(\d{1,2}):?(\d{2})$/;
|
|
6017
6114
|
const match24Hour = normalized.match(pattern24Hour);
|
|
6018
6115
|
if (match24Hour) {
|
|
6019
6116
|
let parsedHour = parseInt(match24Hour[1], 10);
|
|
6020
6117
|
const parsedMinute = parseInt(match24Hour[2], 10);
|
|
6021
6118
|
// Validate hour (0-23)
|
|
6022
|
-
if (parsedHour > 23) {
|
|
6119
|
+
if (parsedHour < 0 || parsedHour > 23) {
|
|
6120
|
+
// Parse failed, select first result
|
|
6121
|
+
selectFirstResult();
|
|
6122
|
+
return;
|
|
6123
|
+
}
|
|
6124
|
+
// Validate minute (0-59)
|
|
6125
|
+
if (parsedMinute < 0 || parsedMinute > 59) {
|
|
6126
|
+
// Parse failed, select first result
|
|
6127
|
+
selectFirstResult();
|
|
6023
6128
|
return;
|
|
6024
6129
|
}
|
|
6025
6130
|
// Convert 24-hour to 12-hour format
|
|
@@ -6039,23 +6144,62 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
|
|
|
6039
6144
|
else {
|
|
6040
6145
|
parsedMeridiem = 'am';
|
|
6041
6146
|
}
|
|
6042
|
-
// Validate minute (0-59)
|
|
6043
|
-
const validMinute = parsedMinute > 59 ? 0 : parsedMinute;
|
|
6044
6147
|
setHour(parsedHour);
|
|
6045
|
-
setMinute(
|
|
6148
|
+
setMinute(parsedMinute);
|
|
6046
6149
|
setMeridiem(parsedMeridiem);
|
|
6047
6150
|
onChange({
|
|
6048
6151
|
hour: parsedHour,
|
|
6049
|
-
minute:
|
|
6152
|
+
minute: parsedMinute,
|
|
6050
6153
|
meridiem: parsedMeridiem,
|
|
6051
6154
|
});
|
|
6052
6155
|
return;
|
|
6053
6156
|
}
|
|
6157
|
+
// Parse failed, select first result
|
|
6158
|
+
selectFirstResult();
|
|
6054
6159
|
};
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6160
|
+
// Select first result from filtered collection
|
|
6161
|
+
const selectFirstResult = () => {
|
|
6162
|
+
if (collection.items.length > 0) {
|
|
6163
|
+
const firstItem = collection.items[0];
|
|
6164
|
+
setHour(firstItem.hour);
|
|
6165
|
+
setMinute(firstItem.minute);
|
|
6166
|
+
setMeridiem(firstItem.meridiem);
|
|
6167
|
+
filter(''); // Reset filter after selection
|
|
6168
|
+
onChange({
|
|
6169
|
+
hour: firstItem.hour,
|
|
6170
|
+
minute: firstItem.minute,
|
|
6171
|
+
meridiem: firstItem.meridiem,
|
|
6172
|
+
});
|
|
6173
|
+
}
|
|
6174
|
+
};
|
|
6175
|
+
const handleInputValueChange = (details) => {
|
|
6176
|
+
// Filter the collection based on input, but don't parse yet
|
|
6177
|
+
filter(details.inputValue);
|
|
6178
|
+
};
|
|
6179
|
+
const handleFocus = (e) => {
|
|
6180
|
+
// Select all text when focusing
|
|
6181
|
+
e.target.select();
|
|
6182
|
+
};
|
|
6183
|
+
const handleBlur = (e) => {
|
|
6184
|
+
// Parse and commit the input value when losing focus
|
|
6185
|
+
const inputValue = e.target.value;
|
|
6186
|
+
if (inputValue) {
|
|
6187
|
+
parseAndCommitInput(inputValue);
|
|
6188
|
+
}
|
|
6189
|
+
};
|
|
6190
|
+
const handleKeyDown = (e) => {
|
|
6191
|
+
// Commit input on Enter key
|
|
6192
|
+
if (e.key === 'Enter') {
|
|
6193
|
+
e.preventDefault();
|
|
6194
|
+
const inputValue = e.currentTarget.value;
|
|
6195
|
+
if (inputValue) {
|
|
6196
|
+
parseAndCommitInput(inputValue);
|
|
6197
|
+
}
|
|
6198
|
+
// Blur the input
|
|
6199
|
+
e.currentTarget?.blur();
|
|
6200
|
+
}
|
|
6201
|
+
};
|
|
6202
|
+
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", openOnClick: true, flex: 1, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { placeholder: "hh:mm a", 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: "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", colorPalette: "blue", 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 }) })), jsxRuntime.jsx(react.Button, { onClick: handleClear, size: "sm", variant: "ghost", children: jsxRuntime.jsx(react.Icon, { children: jsxRuntime.jsx(md.MdCancel, {}) }) })] }) }));
|
|
6059
6203
|
}
|
|
6060
6204
|
|
|
6061
6205
|
dayjs.extend(timezone);
|
|
@@ -6133,11 +6277,138 @@ const TimePicker = ({ column, schema, prefix }) => {
|
|
|
6133
6277
|
} }) }) }) }) }))] }) }));
|
|
6134
6278
|
};
|
|
6135
6279
|
|
|
6280
|
+
dayjs.extend(utc);
|
|
6281
|
+
dayjs.extend(timezone);
|
|
6282
|
+
dayjs.extend(customParseFormat);
|
|
6283
|
+
function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateFormat = 'YYYY-MM-DD', displayFormat = 'YYYY-MM-DD', labels = {
|
|
6284
|
+
monthNamesShort: [
|
|
6285
|
+
'Jan',
|
|
6286
|
+
'Feb',
|
|
6287
|
+
'Mar',
|
|
6288
|
+
'Apr',
|
|
6289
|
+
'May',
|
|
6290
|
+
'Jun',
|
|
6291
|
+
'Jul',
|
|
6292
|
+
'Aug',
|
|
6293
|
+
'Sep',
|
|
6294
|
+
'Oct',
|
|
6295
|
+
'Nov',
|
|
6296
|
+
'Dec',
|
|
6297
|
+
],
|
|
6298
|
+
weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
|
6299
|
+
backButtonLabel: 'Back',
|
|
6300
|
+
forwardButtonLabel: 'Next',
|
|
6301
|
+
}, timezone = 'Asia/Hong_Kong', minDate, maxDate, firstDayOfWeek, showOutsideDays, monthsToDisplay = 1, insideDialog = false, readOnly = false, }) {
|
|
6302
|
+
const [open, setOpen] = React.useState(false);
|
|
6303
|
+
const [inputValue, setInputValue] = React.useState('');
|
|
6304
|
+
// Update input value when prop value changes
|
|
6305
|
+
React.useEffect(() => {
|
|
6306
|
+
if (value) {
|
|
6307
|
+
const formatted = typeof value === 'string'
|
|
6308
|
+
? dayjs(value).tz(timezone).isValid()
|
|
6309
|
+
? dayjs(value).tz(timezone).format(displayFormat)
|
|
6310
|
+
: ''
|
|
6311
|
+
: dayjs(value).tz(timezone).format(displayFormat);
|
|
6312
|
+
setInputValue(formatted);
|
|
6313
|
+
}
|
|
6314
|
+
else {
|
|
6315
|
+
setInputValue('');
|
|
6316
|
+
}
|
|
6317
|
+
}, [value, displayFormat, timezone]);
|
|
6318
|
+
// Convert value to Date object for DatePicker
|
|
6319
|
+
const selectedDate = value
|
|
6320
|
+
? typeof value === 'string'
|
|
6321
|
+
? dayjs(value).tz(timezone).isValid()
|
|
6322
|
+
? dayjs(value).tz(timezone).toDate()
|
|
6323
|
+
: new Date()
|
|
6324
|
+
: value
|
|
6325
|
+
: new Date();
|
|
6326
|
+
// Shared function to parse and validate input value
|
|
6327
|
+
const parseAndValidateInput = (inputVal) => {
|
|
6328
|
+
// If empty, clear the value
|
|
6329
|
+
if (!inputVal.trim()) {
|
|
6330
|
+
onChange?.(undefined);
|
|
6331
|
+
setInputValue('');
|
|
6332
|
+
return;
|
|
6333
|
+
}
|
|
6334
|
+
// Try parsing with displayFormat first
|
|
6335
|
+
let parsedDate = dayjs(inputVal, displayFormat, true);
|
|
6336
|
+
// If that fails, try common date formats
|
|
6337
|
+
if (!parsedDate.isValid()) {
|
|
6338
|
+
parsedDate = dayjs(inputVal);
|
|
6339
|
+
}
|
|
6340
|
+
// If still invalid, try parsing with dateFormat
|
|
6341
|
+
if (!parsedDate.isValid()) {
|
|
6342
|
+
parsedDate = dayjs(inputVal, dateFormat, true);
|
|
6343
|
+
}
|
|
6344
|
+
// If valid, check constraints and update
|
|
6345
|
+
if (parsedDate.isValid()) {
|
|
6346
|
+
const dateObj = parsedDate.tz(timezone).toDate();
|
|
6347
|
+
// Check min/max constraints
|
|
6348
|
+
if (minDate && dateObj < minDate) {
|
|
6349
|
+
// Invalid: before minDate, reset to prop value
|
|
6350
|
+
resetToPropValue();
|
|
6351
|
+
return;
|
|
6352
|
+
}
|
|
6353
|
+
if (maxDate && dateObj > maxDate) {
|
|
6354
|
+
// Invalid: after maxDate, reset to prop value
|
|
6355
|
+
resetToPropValue();
|
|
6356
|
+
return;
|
|
6357
|
+
}
|
|
6358
|
+
// Valid date - format and update
|
|
6359
|
+
const formattedDate = parsedDate.tz(timezone).format(dateFormat);
|
|
6360
|
+
const formattedDisplay = parsedDate.tz(timezone).format(displayFormat);
|
|
6361
|
+
onChange?.(formattedDate);
|
|
6362
|
+
setInputValue(formattedDisplay);
|
|
6363
|
+
}
|
|
6364
|
+
else {
|
|
6365
|
+
// Invalid date - reset to prop value
|
|
6366
|
+
resetToPropValue();
|
|
6367
|
+
}
|
|
6368
|
+
};
|
|
6369
|
+
// Helper function to reset input to prop value
|
|
6370
|
+
const resetToPropValue = () => {
|
|
6371
|
+
if (value) {
|
|
6372
|
+
const formatted = typeof value === 'string'
|
|
6373
|
+
? dayjs(value).tz(timezone).isValid()
|
|
6374
|
+
? dayjs(value).tz(timezone).format(displayFormat)
|
|
6375
|
+
: ''
|
|
6376
|
+
: dayjs(value).tz(timezone).format(displayFormat);
|
|
6377
|
+
setInputValue(formatted);
|
|
6378
|
+
}
|
|
6379
|
+
else {
|
|
6380
|
+
setInputValue('');
|
|
6381
|
+
}
|
|
6382
|
+
};
|
|
6383
|
+
const handleInputChange = (e) => {
|
|
6384
|
+
// Only update the input value, don't parse yet
|
|
6385
|
+
setInputValue(e.target.value);
|
|
6386
|
+
};
|
|
6387
|
+
const handleInputBlur = () => {
|
|
6388
|
+
// Parse and validate when input loses focus
|
|
6389
|
+
parseAndValidateInput(inputValue);
|
|
6390
|
+
};
|
|
6391
|
+
const handleKeyDown = (e) => {
|
|
6392
|
+
// Parse and validate when Enter is pressed
|
|
6393
|
+
if (e.key === 'Enter') {
|
|
6394
|
+
e.preventDefault();
|
|
6395
|
+
parseAndValidateInput(inputValue);
|
|
6396
|
+
}
|
|
6397
|
+
};
|
|
6398
|
+
const handleDateSelected = ({ date }) => {
|
|
6399
|
+
const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
|
|
6400
|
+
onChange?.(formattedDate);
|
|
6401
|
+
setOpen(false);
|
|
6402
|
+
};
|
|
6403
|
+
const datePickerContent = (jsxRuntime.jsx(DatePicker$1, { selected: selectedDate, onDateSelected: handleDateSelected, labels: labels, minDate: minDate, maxDate: maxDate, firstDayOfWeek: firstDayOfWeek, showOutsideDays: showOutsideDays, monthsToDisplay: monthsToDisplay }));
|
|
6404
|
+
return (jsxRuntime.jsxs(react.Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(InputGroup, { endElement: jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.IconButton, { variant: "ghost", size: "2xs", "aria-label": "Open calendar", onClick: () => setOpen(true), children: jsxRuntime.jsx(react.Icon, { children: jsxRuntime.jsx(md.MdDateRange, {}) }) }) }), children: jsxRuntime.jsx(react.Input, { value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onKeyDown: handleKeyDown, placeholder: placeholder, readOnly: readOnly }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { children: datePickerContent }) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { children: datePickerContent }) }) }) }))] }));
|
|
6405
|
+
}
|
|
6406
|
+
|
|
6136
6407
|
dayjs.extend(utc);
|
|
6137
6408
|
dayjs.extend(timezone);
|
|
6138
6409
|
function IsoTimePicker({ hour, setHour, minute, setMinute, second, setSecond,
|
|
6139
6410
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
6140
|
-
onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Kong', }) {
|
|
6411
|
+
onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, }) {
|
|
6141
6412
|
// Generate time options (every 15 minutes, seconds always 0)
|
|
6142
6413
|
const timeOptions = React.useMemo(() => {
|
|
6143
6414
|
const options = [];
|
|
@@ -6172,8 +6443,8 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
|
|
|
6172
6443
|
continue; // Skip this option as it would result in negative duration
|
|
6173
6444
|
}
|
|
6174
6445
|
}
|
|
6175
|
-
// Calculate
|
|
6176
|
-
let
|
|
6446
|
+
// Calculate duration if startTime is provided
|
|
6447
|
+
let durationText;
|
|
6177
6448
|
if (startDateTime && selectedDate) {
|
|
6178
6449
|
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
6179
6450
|
const optionDateTime = selectedDateObj
|
|
@@ -6198,17 +6469,18 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
|
|
|
6198
6469
|
else {
|
|
6199
6470
|
diffText = `${diffSeconds}s`;
|
|
6200
6471
|
}
|
|
6201
|
-
|
|
6472
|
+
durationText = `+${diffText}`;
|
|
6202
6473
|
}
|
|
6203
6474
|
}
|
|
6204
6475
|
}
|
|
6205
6476
|
options.push({
|
|
6206
|
-
label,
|
|
6477
|
+
label: timeDisplay,
|
|
6207
6478
|
value: `${h}:${m}:0`,
|
|
6208
6479
|
hour: h,
|
|
6209
6480
|
minute: m,
|
|
6210
6481
|
second: 0,
|
|
6211
6482
|
searchText: timeDisplay, // Use base time without duration for searching
|
|
6483
|
+
durationText,
|
|
6212
6484
|
});
|
|
6213
6485
|
}
|
|
6214
6486
|
}
|
|
@@ -6228,46 +6500,46 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
|
|
|
6228
6500
|
}
|
|
6229
6501
|
return `${hour}:${minute}:${second}`;
|
|
6230
6502
|
}, [hour, minute, second]);
|
|
6231
|
-
//
|
|
6232
|
-
const
|
|
6233
|
-
if (
|
|
6234
|
-
|
|
6503
|
+
// Calculate duration difference
|
|
6504
|
+
const durationDiff = React.useMemo(() => {
|
|
6505
|
+
if (!startTime ||
|
|
6506
|
+
!selectedDate ||
|
|
6507
|
+
hour === null ||
|
|
6508
|
+
minute === null ||
|
|
6509
|
+
second === null) {
|
|
6510
|
+
return null;
|
|
6235
6511
|
}
|
|
6236
|
-
const
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
diffText = `${diffMinutes}m ${diffSeconds}s`;
|
|
6261
|
-
}
|
|
6262
|
-
else {
|
|
6263
|
-
diffText = `${diffSeconds}s`;
|
|
6264
|
-
}
|
|
6265
|
-
return `${timeDisplay} (+${diffText})`;
|
|
6266
|
-
}
|
|
6267
|
-
}
|
|
6512
|
+
const startDateObj = dayjs(startTime).tz(timezone);
|
|
6513
|
+
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
6514
|
+
const currentDateTime = selectedDateObj
|
|
6515
|
+
.hour(hour)
|
|
6516
|
+
.minute(minute)
|
|
6517
|
+
.second(second ?? 0)
|
|
6518
|
+
.millisecond(0);
|
|
6519
|
+
if (!startDateObj.isValid() || !currentDateTime.isValid()) {
|
|
6520
|
+
return null;
|
|
6521
|
+
}
|
|
6522
|
+
const diffMs = currentDateTime.diff(startDateObj);
|
|
6523
|
+
if (diffMs < 0) {
|
|
6524
|
+
return null;
|
|
6525
|
+
}
|
|
6526
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
6527
|
+
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
6528
|
+
const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
|
|
6529
|
+
if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
|
|
6530
|
+
let diffText = '';
|
|
6531
|
+
if (diffHours > 0) {
|
|
6532
|
+
diffText = `${diffHours}h ${diffMinutes}m`;
|
|
6533
|
+
}
|
|
6534
|
+
else if (diffMinutes > 0) {
|
|
6535
|
+
diffText = `${diffMinutes}m ${diffSeconds}s`;
|
|
6268
6536
|
}
|
|
6537
|
+
else {
|
|
6538
|
+
diffText = `${diffSeconds}s`;
|
|
6539
|
+
}
|
|
6540
|
+
return `+${diffText}`;
|
|
6269
6541
|
}
|
|
6270
|
-
return
|
|
6542
|
+
return null;
|
|
6271
6543
|
}, [hour, minute, second, startTime, selectedDate, timezone]);
|
|
6272
6544
|
const handleClear = () => {
|
|
6273
6545
|
setHour(null);
|
|
@@ -6295,16 +6567,17 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
|
|
|
6295
6567
|
});
|
|
6296
6568
|
}
|
|
6297
6569
|
};
|
|
6298
|
-
|
|
6299
|
-
|
|
6570
|
+
// Parse input value and update state
|
|
6571
|
+
const parseAndCommitInput = (value) => {
|
|
6572
|
+
const trimmedValue = value.trim();
|
|
6300
6573
|
// Filter the collection based on input
|
|
6301
|
-
filter(
|
|
6302
|
-
if (!
|
|
6574
|
+
filter(trimmedValue);
|
|
6575
|
+
if (!trimmedValue) {
|
|
6303
6576
|
return;
|
|
6304
6577
|
}
|
|
6305
6578
|
// Parse HH:mm:ss or HH:mm format
|
|
6306
6579
|
const timePattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
|
|
6307
|
-
const match =
|
|
6580
|
+
const match = trimmedValue.match(timePattern);
|
|
6308
6581
|
if (match) {
|
|
6309
6582
|
const parsedHour = parseInt(match[1], 10);
|
|
6310
6583
|
const parsedMinute = parseInt(match[2], 10);
|
|
@@ -6324,11 +6597,12 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
|
|
|
6324
6597
|
minute: parsedMinute,
|
|
6325
6598
|
second: parsedSecond,
|
|
6326
6599
|
});
|
|
6600
|
+
return;
|
|
6327
6601
|
}
|
|
6328
6602
|
}
|
|
6329
6603
|
else {
|
|
6330
6604
|
// Try to parse formats like "123045" (HHmmss) or "1230" (HHmm)
|
|
6331
|
-
const numbersOnly =
|
|
6605
|
+
const numbersOnly = trimmedValue.replace(/[^0-9]/g, '');
|
|
6332
6606
|
if (numbersOnly.length >= 4) {
|
|
6333
6607
|
const parsedHour = parseInt(numbersOnly.slice(0, 2), 10);
|
|
6334
6608
|
const parsedMinute = parseInt(numbersOnly.slice(2, 4), 10);
|
|
@@ -6348,11 +6622,56 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
|
|
|
6348
6622
|
minute: parsedMinute,
|
|
6349
6623
|
second: parsedSecond,
|
|
6350
6624
|
});
|
|
6625
|
+
return;
|
|
6351
6626
|
}
|
|
6352
6627
|
}
|
|
6353
6628
|
}
|
|
6629
|
+
// Parse failed, select first result
|
|
6630
|
+
selectFirstResult();
|
|
6631
|
+
};
|
|
6632
|
+
// Select first result from filtered collection
|
|
6633
|
+
const selectFirstResult = () => {
|
|
6634
|
+
if (collection.items.length > 0) {
|
|
6635
|
+
const firstItem = collection.items[0];
|
|
6636
|
+
setHour(firstItem.hour);
|
|
6637
|
+
setMinute(firstItem.minute);
|
|
6638
|
+
setSecond(firstItem.second);
|
|
6639
|
+
filter(''); // Reset filter after selection
|
|
6640
|
+
onChange({
|
|
6641
|
+
hour: firstItem.hour,
|
|
6642
|
+
minute: firstItem.minute,
|
|
6643
|
+
second: firstItem.second,
|
|
6644
|
+
});
|
|
6645
|
+
}
|
|
6646
|
+
};
|
|
6647
|
+
const handleInputValueChange = (details) => {
|
|
6648
|
+
// Filter the collection based on input, but don't parse yet
|
|
6649
|
+
filter(details.inputValue);
|
|
6650
|
+
};
|
|
6651
|
+
const handleFocus = (e) => {
|
|
6652
|
+
// Select all text when focusing
|
|
6653
|
+
e.target.select();
|
|
6654
|
+
};
|
|
6655
|
+
const handleBlur = (e) => {
|
|
6656
|
+
// Parse and commit the input value when losing focus
|
|
6657
|
+
const inputValue = e.target.value;
|
|
6658
|
+
if (inputValue) {
|
|
6659
|
+
parseAndCommitInput(inputValue);
|
|
6660
|
+
}
|
|
6661
|
+
};
|
|
6662
|
+
const handleKeyDown = (e) => {
|
|
6663
|
+
// Commit input on Enter key
|
|
6664
|
+
if (e.key === 'Enter') {
|
|
6665
|
+
e.preventDefault();
|
|
6666
|
+
const inputValue = e.currentTarget.value;
|
|
6667
|
+
if (inputValue) {
|
|
6668
|
+
parseAndCommitInput(inputValue);
|
|
6669
|
+
}
|
|
6670
|
+
// Blur the input
|
|
6671
|
+
e.currentTarget?.blur();
|
|
6672
|
+
}
|
|
6354
6673
|
};
|
|
6355
|
-
return (jsxRuntime.jsx(react.Flex, { direction: "column", gap: 3, children: jsxRuntime.jsxs(react.
|
|
6674
|
+
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", openOnClick: true, flex: 1, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { placeholder: "HH:mm:ss", 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: "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 }) })), jsxRuntime.jsx(react.Button, { onClick: handleClear, size: "sm", variant: "ghost", children: jsxRuntime.jsx(react.Icon, { children: jsxRuntime.jsx(md.MdCancel, {}) }) })] }) }));
|
|
6356
6675
|
}
|
|
6357
6676
|
|
|
6358
6677
|
dayjs.extend(utc);
|
|
@@ -6375,30 +6694,193 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
|
|
|
6375
6694
|
weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
|
6376
6695
|
backButtonLabel: 'Back',
|
|
6377
6696
|
forwardButtonLabel: 'Next',
|
|
6378
|
-
}, timezone = 'Asia/Hong_Kong', startTime, }) {
|
|
6379
|
-
|
|
6697
|
+
}, timezone = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, }) {
|
|
6698
|
+
console.log('[DateTimePicker] Component initialized with props:', {
|
|
6699
|
+
value,
|
|
6700
|
+
format,
|
|
6701
|
+
showSeconds,
|
|
6702
|
+
timezone,
|
|
6703
|
+
startTime,
|
|
6704
|
+
minDate,
|
|
6705
|
+
maxDate,
|
|
6706
|
+
});
|
|
6707
|
+
// Initialize selectedDate from value prop, converting ISO to YYYY-MM-DD format
|
|
6708
|
+
const getDateString = React.useCallback((val) => {
|
|
6709
|
+
if (!val)
|
|
6710
|
+
return '';
|
|
6711
|
+
const dateObj = dayjs(val).tz(timezone);
|
|
6712
|
+
return dateObj.isValid() ? dateObj.format('YYYY-MM-DD') : '';
|
|
6713
|
+
}, [timezone]);
|
|
6714
|
+
const [selectedDate, setSelectedDate] = React.useState(getDateString(value));
|
|
6715
|
+
// Helper to get time values from value prop with timezone
|
|
6716
|
+
const getTimeFromValue = React.useCallback((val) => {
|
|
6717
|
+
console.log('[DateTimePicker] getTimeFromValue called:', {
|
|
6718
|
+
val,
|
|
6719
|
+
timezone,
|
|
6720
|
+
showSeconds,
|
|
6721
|
+
});
|
|
6722
|
+
if (!val) {
|
|
6723
|
+
console.log('[DateTimePicker] No value provided, returning nulls');
|
|
6724
|
+
return {
|
|
6725
|
+
hour12: null,
|
|
6726
|
+
minute: null,
|
|
6727
|
+
meridiem: null,
|
|
6728
|
+
hour24: null,
|
|
6729
|
+
second: null,
|
|
6730
|
+
};
|
|
6731
|
+
}
|
|
6732
|
+
const dateObj = dayjs(val).tz(timezone);
|
|
6733
|
+
console.log('[DateTimePicker] Parsed date object:', {
|
|
6734
|
+
original: val,
|
|
6735
|
+
timezone,
|
|
6736
|
+
isValid: dateObj.isValid(),
|
|
6737
|
+
formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
|
|
6738
|
+
hour24: dateObj.hour(),
|
|
6739
|
+
minute: dateObj.minute(),
|
|
6740
|
+
second: dateObj.second(),
|
|
6741
|
+
});
|
|
6742
|
+
if (!dateObj.isValid()) {
|
|
6743
|
+
console.log('[DateTimePicker] Invalid date object, returning nulls');
|
|
6744
|
+
return {
|
|
6745
|
+
hour12: null,
|
|
6746
|
+
minute: null,
|
|
6747
|
+
meridiem: null,
|
|
6748
|
+
hour24: null,
|
|
6749
|
+
second: null,
|
|
6750
|
+
};
|
|
6751
|
+
}
|
|
6752
|
+
const hour24Value = dateObj.hour();
|
|
6753
|
+
const hour12Value = hour24Value % 12 || 12;
|
|
6754
|
+
const minuteValue = dateObj.minute();
|
|
6755
|
+
const meridiemValue = hour24Value >= 12 ? 'pm' : 'am';
|
|
6756
|
+
const secondValue = showSeconds ? dateObj.second() : null;
|
|
6757
|
+
const result = {
|
|
6758
|
+
hour12: hour12Value,
|
|
6759
|
+
minute: minuteValue,
|
|
6760
|
+
meridiem: meridiemValue,
|
|
6761
|
+
hour24: hour24Value,
|
|
6762
|
+
second: secondValue,
|
|
6763
|
+
};
|
|
6764
|
+
console.log('[DateTimePicker] Extracted time values:', result);
|
|
6765
|
+
return result;
|
|
6766
|
+
}, [timezone, showSeconds]);
|
|
6767
|
+
const initialTime = getTimeFromValue(value);
|
|
6768
|
+
console.log('[DateTimePicker] Initial time from value:', {
|
|
6769
|
+
value,
|
|
6770
|
+
initialTime,
|
|
6771
|
+
});
|
|
6380
6772
|
// Time state for 12-hour format
|
|
6381
|
-
const [hour12, setHour12] = React.useState(
|
|
6382
|
-
const [minute, setMinute] = React.useState(
|
|
6383
|
-
const [meridiem, setMeridiem] = React.useState(
|
|
6773
|
+
const [hour12, setHour12] = React.useState(initialTime.hour12);
|
|
6774
|
+
const [minute, setMinute] = React.useState(initialTime.minute);
|
|
6775
|
+
const [meridiem, setMeridiem] = React.useState(initialTime.meridiem);
|
|
6384
6776
|
// Time state for 24-hour format
|
|
6385
|
-
const [hour24, setHour24] = React.useState(
|
|
6386
|
-
const [second, setSecond] = React.useState(
|
|
6777
|
+
const [hour24, setHour24] = React.useState(initialTime.hour24);
|
|
6778
|
+
const [second, setSecond] = React.useState(initialTime.second);
|
|
6779
|
+
// Sync selectedDate and time states when value prop changes
|
|
6780
|
+
React.useEffect(() => {
|
|
6781
|
+
console.log('[DateTimePicker] useEffect triggered - value changed:', {
|
|
6782
|
+
value,
|
|
6783
|
+
timezone,
|
|
6784
|
+
format,
|
|
6785
|
+
});
|
|
6786
|
+
// If value is null, undefined, or invalid, clear all fields
|
|
6787
|
+
if (!value || value === null || value === undefined) {
|
|
6788
|
+
console.log('[DateTimePicker] Value is null/undefined, clearing all fields');
|
|
6789
|
+
setSelectedDate('');
|
|
6790
|
+
setHour12(null);
|
|
6791
|
+
setMinute(null);
|
|
6792
|
+
setMeridiem(null);
|
|
6793
|
+
setHour24(null);
|
|
6794
|
+
setSecond(null);
|
|
6795
|
+
return;
|
|
6796
|
+
}
|
|
6797
|
+
// Check if value is valid
|
|
6798
|
+
const dateObj = dayjs(value).tz(timezone);
|
|
6799
|
+
if (!dateObj.isValid()) {
|
|
6800
|
+
console.log('[DateTimePicker] Invalid value, clearing all fields');
|
|
6801
|
+
setSelectedDate('');
|
|
6802
|
+
setHour12(null);
|
|
6803
|
+
setMinute(null);
|
|
6804
|
+
setMeridiem(null);
|
|
6805
|
+
setHour24(null);
|
|
6806
|
+
setSecond(null);
|
|
6807
|
+
return;
|
|
6808
|
+
}
|
|
6809
|
+
const dateString = getDateString(value);
|
|
6810
|
+
console.log('[DateTimePicker] Setting selectedDate:', dateString);
|
|
6811
|
+
setSelectedDate(dateString);
|
|
6812
|
+
const timeData = getTimeFromValue(value);
|
|
6813
|
+
console.log('[DateTimePicker] Updating time states:', {
|
|
6814
|
+
timeData,
|
|
6815
|
+
});
|
|
6816
|
+
setHour12(timeData.hour12);
|
|
6817
|
+
setMinute(timeData.minute);
|
|
6818
|
+
setMeridiem(timeData.meridiem);
|
|
6819
|
+
setHour24(timeData.hour24);
|
|
6820
|
+
setSecond(timeData.second);
|
|
6821
|
+
}, [value, getTimeFromValue, getDateString, timezone]);
|
|
6387
6822
|
const handleDateChange = (date) => {
|
|
6823
|
+
console.log('[DateTimePicker] handleDateChange called:', {
|
|
6824
|
+
date,
|
|
6825
|
+
timezone,
|
|
6826
|
+
showSeconds,
|
|
6827
|
+
currentTimeStates: { hour12, minute, meridiem, hour24, second },
|
|
6828
|
+
});
|
|
6829
|
+
// If date is empty or invalid, clear all fields
|
|
6830
|
+
if (!date || date === '') {
|
|
6831
|
+
console.log('[DateTimePicker] Empty date, clearing all fields');
|
|
6832
|
+
setSelectedDate('');
|
|
6833
|
+
setHour12(null);
|
|
6834
|
+
setMinute(null);
|
|
6835
|
+
setMeridiem(null);
|
|
6836
|
+
setHour24(null);
|
|
6837
|
+
setSecond(null);
|
|
6838
|
+
onChange?.(undefined);
|
|
6839
|
+
return;
|
|
6840
|
+
}
|
|
6388
6841
|
setSelectedDate(date);
|
|
6842
|
+
// Parse the date string (YYYY-MM-DD) in the specified timezone
|
|
6843
|
+
const dateObj = dayjs.tz(date, timezone);
|
|
6844
|
+
console.log('[DateTimePicker] Parsed date object:', {
|
|
6845
|
+
date,
|
|
6846
|
+
timezone,
|
|
6847
|
+
isValid: dateObj.isValid(),
|
|
6848
|
+
isoString: dateObj.toISOString(),
|
|
6849
|
+
formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
|
|
6850
|
+
});
|
|
6851
|
+
if (!dateObj.isValid()) {
|
|
6852
|
+
console.warn('[DateTimePicker] Invalid date object in handleDateChange, clearing fields');
|
|
6853
|
+
setSelectedDate('');
|
|
6854
|
+
setHour12(null);
|
|
6855
|
+
setMinute(null);
|
|
6856
|
+
setMeridiem(null);
|
|
6857
|
+
setHour24(null);
|
|
6858
|
+
setSecond(null);
|
|
6859
|
+
onChange?.(undefined);
|
|
6860
|
+
return;
|
|
6861
|
+
}
|
|
6389
6862
|
// When showSeconds is false, ignore seconds from the date
|
|
6390
|
-
|
|
6391
|
-
if (!showSeconds && dateObj.isValid()) {
|
|
6863
|
+
if (!showSeconds) {
|
|
6392
6864
|
const dateWithoutSeconds = dateObj.second(0).millisecond(0).toISOString();
|
|
6865
|
+
console.log('[DateTimePicker] Updating date without seconds:', dateWithoutSeconds);
|
|
6393
6866
|
updateDateTime(dateWithoutSeconds);
|
|
6394
6867
|
}
|
|
6395
6868
|
else {
|
|
6396
|
-
|
|
6869
|
+
const dateWithSeconds = dateObj.toISOString();
|
|
6870
|
+
console.log('[DateTimePicker] Updating date with seconds:', dateWithSeconds);
|
|
6871
|
+
updateDateTime(dateWithSeconds);
|
|
6397
6872
|
}
|
|
6398
6873
|
};
|
|
6399
6874
|
const handleTimeChange = (timeData) => {
|
|
6875
|
+
console.log('[DateTimePicker] handleTimeChange called:', {
|
|
6876
|
+
timeData,
|
|
6877
|
+
format,
|
|
6878
|
+
selectedDate,
|
|
6879
|
+
timezone,
|
|
6880
|
+
});
|
|
6400
6881
|
if (format === 'iso-date-time') {
|
|
6401
6882
|
const data = timeData;
|
|
6883
|
+
console.log('[DateTimePicker] ISO format - setting 24-hour time:', data);
|
|
6402
6884
|
setHour24(data.hour);
|
|
6403
6885
|
setMinute(data.minute);
|
|
6404
6886
|
if (showSeconds) {
|
|
@@ -6411,60 +6893,161 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
|
|
|
6411
6893
|
}
|
|
6412
6894
|
else {
|
|
6413
6895
|
const data = timeData;
|
|
6896
|
+
console.log('[DateTimePicker] 12-hour format - setting time:', data);
|
|
6414
6897
|
setHour12(data.hour);
|
|
6415
6898
|
setMinute(data.minute);
|
|
6416
6899
|
setMeridiem(data.meridiem);
|
|
6417
6900
|
}
|
|
6418
|
-
// Use selectedDate if valid, otherwise
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6901
|
+
// Use selectedDate if valid, otherwise clear all fields
|
|
6902
|
+
if (!selectedDate || !dayjs(selectedDate).isValid()) {
|
|
6903
|
+
console.log('[DateTimePicker] No valid selectedDate, clearing all fields');
|
|
6904
|
+
setSelectedDate('');
|
|
6905
|
+
setHour12(null);
|
|
6906
|
+
setMinute(null);
|
|
6907
|
+
setMeridiem(null);
|
|
6908
|
+
setHour24(null);
|
|
6909
|
+
setSecond(null);
|
|
6910
|
+
onChange?.(undefined);
|
|
6911
|
+
return;
|
|
6912
|
+
}
|
|
6913
|
+
const dateObj = dayjs(selectedDate).tz(timezone);
|
|
6423
6914
|
if (dateObj.isValid()) {
|
|
6424
6915
|
updateDateTime(dateObj.toISOString(), timeData);
|
|
6425
6916
|
}
|
|
6917
|
+
else {
|
|
6918
|
+
console.warn('[DateTimePicker] Invalid date object in handleTimeChange, clearing fields');
|
|
6919
|
+
setSelectedDate('');
|
|
6920
|
+
setHour12(null);
|
|
6921
|
+
setMinute(null);
|
|
6922
|
+
setMeridiem(null);
|
|
6923
|
+
setHour24(null);
|
|
6924
|
+
setSecond(null);
|
|
6925
|
+
onChange?.(undefined);
|
|
6926
|
+
}
|
|
6426
6927
|
};
|
|
6427
6928
|
const updateDateTime = (date, timeData) => {
|
|
6428
|
-
|
|
6929
|
+
console.log('[DateTimePicker] updateDateTime called:', {
|
|
6930
|
+
date,
|
|
6931
|
+
timeData,
|
|
6932
|
+
format,
|
|
6933
|
+
currentStates: { hour12, minute, meridiem, hour24, second },
|
|
6934
|
+
});
|
|
6935
|
+
if (!date || date === null || date === undefined) {
|
|
6936
|
+
console.log('[DateTimePicker] No date provided, clearing all fields and calling onChange(undefined)');
|
|
6937
|
+
setSelectedDate('');
|
|
6938
|
+
setHour12(null);
|
|
6939
|
+
setMinute(null);
|
|
6940
|
+
setMeridiem(null);
|
|
6941
|
+
setHour24(null);
|
|
6942
|
+
setSecond(null);
|
|
6429
6943
|
onChange?.(undefined);
|
|
6430
6944
|
return;
|
|
6431
6945
|
}
|
|
6432
6946
|
// use dayjs to convert the date to the timezone
|
|
6433
6947
|
const dateObj = dayjs(date).tz(timezone);
|
|
6434
6948
|
if (!dateObj.isValid()) {
|
|
6949
|
+
console.warn('[DateTimePicker] Invalid date object in updateDateTime, clearing fields:', date);
|
|
6950
|
+
setSelectedDate('');
|
|
6951
|
+
setHour12(null);
|
|
6952
|
+
setMinute(null);
|
|
6953
|
+
setMeridiem(null);
|
|
6954
|
+
setHour24(null);
|
|
6955
|
+
setSecond(null);
|
|
6956
|
+
onChange?.(undefined);
|
|
6435
6957
|
return;
|
|
6436
6958
|
}
|
|
6437
6959
|
const newDate = dateObj.toDate();
|
|
6438
6960
|
if (format === 'iso-date-time') {
|
|
6439
6961
|
const data = timeData;
|
|
6440
|
-
|
|
6441
|
-
|
|
6962
|
+
// Use timeData values if provided, otherwise fall back to current state
|
|
6963
|
+
// But if timeData is explicitly provided with nulls, we need to check if all are null
|
|
6964
|
+
const h = data !== undefined ? data.hour : hour24;
|
|
6965
|
+
const m = data !== undefined ? data.minute : minute;
|
|
6442
6966
|
// Always ignore seconds when showSeconds is false - set to 0
|
|
6443
|
-
const s = showSeconds
|
|
6967
|
+
const s = showSeconds
|
|
6968
|
+
? data !== undefined
|
|
6969
|
+
? data.second ?? null
|
|
6970
|
+
: second ?? 0
|
|
6971
|
+
: 0;
|
|
6972
|
+
// If all time values are null, clear the value
|
|
6973
|
+
if (h === null && m === null && (showSeconds ? s === null : true)) {
|
|
6974
|
+
console.log('[DateTimePicker] All time values are null, clearing value');
|
|
6975
|
+
onChange?.(undefined);
|
|
6976
|
+
return;
|
|
6977
|
+
}
|
|
6978
|
+
console.log('[DateTimePicker] ISO format - setting time on date:', {
|
|
6979
|
+
h,
|
|
6980
|
+
m,
|
|
6981
|
+
s,
|
|
6982
|
+
showSeconds,
|
|
6983
|
+
});
|
|
6444
6984
|
if (h !== null)
|
|
6445
6985
|
newDate.setHours(h);
|
|
6446
6986
|
if (m !== null)
|
|
6447
6987
|
newDate.setMinutes(m);
|
|
6448
|
-
newDate.setSeconds(s);
|
|
6988
|
+
newDate.setSeconds(s ?? 0);
|
|
6449
6989
|
}
|
|
6450
6990
|
else {
|
|
6451
6991
|
const data = timeData;
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6992
|
+
console.log('[DateTimePicker] Processing 12-hour format:', {
|
|
6993
|
+
'data !== undefined': data !== undefined,
|
|
6994
|
+
'data?.hour': data?.hour,
|
|
6995
|
+
'data?.minute': data?.minute,
|
|
6996
|
+
'data?.meridiem': data?.meridiem,
|
|
6997
|
+
'current hour12': hour12,
|
|
6998
|
+
'current minute': minute,
|
|
6999
|
+
'current meridiem': meridiem,
|
|
7000
|
+
});
|
|
7001
|
+
// Use timeData values if provided, otherwise fall back to current state
|
|
7002
|
+
const h = data !== undefined ? data.hour : hour12;
|
|
7003
|
+
const m = data !== undefined ? data.minute : minute;
|
|
7004
|
+
const mer = data !== undefined ? data.meridiem : meridiem;
|
|
7005
|
+
console.log('[DateTimePicker] Resolved time values:', { h, m, mer });
|
|
7006
|
+
// If all time values are null, clear the value
|
|
7007
|
+
if (h === null && m === null && mer === null) {
|
|
7008
|
+
console.log('[DateTimePicker] All time values are null, clearing value');
|
|
7009
|
+
onChange?.(undefined);
|
|
7010
|
+
return;
|
|
7011
|
+
}
|
|
7012
|
+
console.log('[DateTimePicker] 12-hour format - converting time:', {
|
|
7013
|
+
h,
|
|
7014
|
+
m,
|
|
7015
|
+
mer,
|
|
7016
|
+
});
|
|
6455
7017
|
if (h !== null && mer !== null) {
|
|
6456
7018
|
let hour24 = h;
|
|
6457
7019
|
if (mer === 'am' && h === 12)
|
|
6458
7020
|
hour24 = 0;
|
|
6459
7021
|
else if (mer === 'pm' && h < 12)
|
|
6460
7022
|
hour24 = h + 12;
|
|
7023
|
+
console.log('[DateTimePicker] Converted to 24-hour:', {
|
|
7024
|
+
h,
|
|
7025
|
+
mer,
|
|
7026
|
+
hour24,
|
|
7027
|
+
});
|
|
6461
7028
|
newDate.setHours(hour24);
|
|
6462
7029
|
}
|
|
6463
|
-
|
|
7030
|
+
else {
|
|
7031
|
+
console.log('[DateTimePicker] Skipping hour update - h or mer is null:', {
|
|
7032
|
+
h,
|
|
7033
|
+
mer,
|
|
7034
|
+
});
|
|
7035
|
+
}
|
|
7036
|
+
if (m !== null) {
|
|
6464
7037
|
newDate.setMinutes(m);
|
|
7038
|
+
}
|
|
7039
|
+
else {
|
|
7040
|
+
console.log('[DateTimePicker] Skipping minute update - m is null');
|
|
7041
|
+
}
|
|
6465
7042
|
newDate.setSeconds(0);
|
|
6466
7043
|
}
|
|
6467
|
-
|
|
7044
|
+
const finalISO = dayjs(newDate).tz(timezone).toISOString();
|
|
7045
|
+
console.log('[DateTimePicker] Final ISO string to emit:', {
|
|
7046
|
+
newDate: newDate.toISOString(),
|
|
7047
|
+
timezone,
|
|
7048
|
+
finalISO,
|
|
7049
|
+
});
|
|
7050
|
+
onChange?.(finalISO);
|
|
6468
7051
|
};
|
|
6469
7052
|
const handleClear = () => {
|
|
6470
7053
|
setSelectedDate('');
|
|
@@ -6480,14 +7063,92 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
|
|
|
6480
7063
|
const normalizedStartTime = startTime
|
|
6481
7064
|
? dayjs(startTime).tz(timezone).millisecond(0).toISOString()
|
|
6482
7065
|
: undefined;
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
7066
|
+
// Determine minDate: prioritize explicit minDate prop, then fall back to startTime
|
|
7067
|
+
const effectiveMinDate = minDate
|
|
7068
|
+
? minDate
|
|
7069
|
+
: normalizedStartTime && dayjs(normalizedStartTime).tz(timezone).isValid()
|
|
7070
|
+
? dayjs(normalizedStartTime).tz(timezone).startOf('day').toDate()
|
|
7071
|
+
: undefined;
|
|
7072
|
+
// Log current state before render
|
|
7073
|
+
React.useEffect(() => {
|
|
7074
|
+
console.log('[DateTimePicker] Current state before render:', {
|
|
7075
|
+
isISO,
|
|
7076
|
+
hour12,
|
|
7077
|
+
minute,
|
|
7078
|
+
meridiem,
|
|
7079
|
+
hour24,
|
|
7080
|
+
second,
|
|
7081
|
+
selectedDate,
|
|
7082
|
+
normalizedStartTime,
|
|
7083
|
+
timezone,
|
|
7084
|
+
});
|
|
7085
|
+
}, [
|
|
7086
|
+
isISO,
|
|
7087
|
+
hour12,
|
|
7088
|
+
minute,
|
|
7089
|
+
meridiem,
|
|
7090
|
+
hour24,
|
|
7091
|
+
second,
|
|
7092
|
+
selectedDate,
|
|
7093
|
+
normalizedStartTime,
|
|
7094
|
+
timezone,
|
|
7095
|
+
]);
|
|
7096
|
+
// Compute display text from current state
|
|
7097
|
+
const displayText = React.useMemo(() => {
|
|
7098
|
+
if (!selectedDate)
|
|
7099
|
+
return null;
|
|
7100
|
+
const dateObj = dayjs.tz(selectedDate, timezone);
|
|
7101
|
+
if (!dateObj.isValid())
|
|
7102
|
+
return null;
|
|
7103
|
+
if (isISO) {
|
|
7104
|
+
// For ISO format, use hour24, minute, second
|
|
7105
|
+
if (hour24 === null || minute === null)
|
|
7106
|
+
return null;
|
|
7107
|
+
const dateTimeObj = dateObj
|
|
7108
|
+
.hour(hour24)
|
|
7109
|
+
.minute(minute)
|
|
7110
|
+
.second(second ?? 0);
|
|
7111
|
+
return dateTimeObj.format(showSeconds ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm');
|
|
7112
|
+
}
|
|
7113
|
+
else {
|
|
7114
|
+
// For 12-hour format, use hour12, minute, meridiem
|
|
7115
|
+
if (hour12 === null || minute === null || meridiem === null)
|
|
7116
|
+
return null;
|
|
7117
|
+
// Convert to 24-hour format for dayjs
|
|
7118
|
+
let hour24Value = hour12;
|
|
7119
|
+
if (meridiem === 'am' && hour12 === 12)
|
|
7120
|
+
hour24Value = 0;
|
|
7121
|
+
else if (meridiem === 'pm' && hour12 < 12)
|
|
7122
|
+
hour24Value = hour12 + 12;
|
|
7123
|
+
const dateTimeObj = dateObj.hour(hour24Value).minute(minute).second(0);
|
|
7124
|
+
return dateTimeObj.format('YYYY-MM-DD hh:mm A');
|
|
7125
|
+
}
|
|
7126
|
+
}, [
|
|
7127
|
+
selectedDate,
|
|
7128
|
+
isISO,
|
|
7129
|
+
hour12,
|
|
7130
|
+
minute,
|
|
7131
|
+
meridiem,
|
|
7132
|
+
hour24,
|
|
7133
|
+
second,
|
|
7134
|
+
showSeconds,
|
|
7135
|
+
timezone,
|
|
7136
|
+
]);
|
|
7137
|
+
const timezoneOffset = React.useMemo(() => {
|
|
7138
|
+
if (!selectedDate)
|
|
7139
|
+
return null;
|
|
7140
|
+
const dateObj = dayjs.tz(selectedDate, timezone);
|
|
7141
|
+
return dateObj.isValid() ? dateObj.format('Z') : null;
|
|
7142
|
+
}, [selectedDate, timezone]);
|
|
7143
|
+
return (jsxRuntime.jsxs(react.Flex, { direction: "column", gap: 4, p: 4, border: "1px solid", borderColor: "gray.200", borderRadius: "md", children: [jsxRuntime.jsx(DatePickerInput, { value: selectedDate || undefined, onChange: (date) => {
|
|
7144
|
+
if (date) {
|
|
7145
|
+
handleDateChange(date);
|
|
7146
|
+
}
|
|
7147
|
+
else {
|
|
7148
|
+
setSelectedDate('');
|
|
7149
|
+
onChange?.(undefined);
|
|
7150
|
+
}
|
|
7151
|
+
}, placeholder: "Select a date", dateFormat: "YYYY-MM-DD", displayFormat: "YYYY-MM-DD", labels: labels, timezone: timezone, minDate: effectiveMinDate, maxDate: maxDate, monthsToDisplay: 1, readOnly: true }), jsxRuntime.jsxs(react.Grid, { templateColumns: "1fr auto", alignItems: "center", gap: 4, children: [isISO ? (jsxRuntime.jsx(IsoTimePicker, { hour: hour24, setHour: setHour24, minute: minute, setMinute: setMinute, second: showSeconds ? second : null, setSecond: showSeconds ? setSecond : () => { }, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone, portalled: portalled })) : (jsxRuntime.jsx(TimePicker$1, { hour: hour12, setHour: setHour12, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone, portalled: portalled })), jsxRuntime.jsx(react.Button, { onClick: handleClear, size: "sm", variant: "outline", colorScheme: "red", children: jsxRuntime.jsx(react.Icon, { as: fa6.FaTrash }) })] }), displayText && (jsxRuntime.jsxs(react.Flex, { gap: 2, children: [jsxRuntime.jsx(react.Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: displayText }), timezoneOffset && (jsxRuntime.jsx(react.Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezoneOffset })), jsxRuntime.jsx(react.Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezone })] }))] }));
|
|
6491
7152
|
}
|
|
6492
7153
|
|
|
6493
7154
|
dayjs.extend(utc);
|
|
@@ -6503,32 +7164,9 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
|
|
|
6503
7164
|
const colLabel = formI18n.colLabel;
|
|
6504
7165
|
const [open, setOpen] = React.useState(false);
|
|
6505
7166
|
const selectedDate = watch(colLabel);
|
|
6506
|
-
const displayDate = dayjs(selectedDate)
|
|
6507
|
-
.tz(timezone)
|
|
6508
|
-
|
|
6509
|
-
React.useEffect(() => {
|
|
6510
|
-
try {
|
|
6511
|
-
if (selectedDate) {
|
|
6512
|
-
// Parse the selectedDate as UTC or in a specific timezone to avoid +8 hour shift
|
|
6513
|
-
// For example, parse as UTC:
|
|
6514
|
-
const parsedDate = dayjs(selectedDate).tz(timezone);
|
|
6515
|
-
if (!parsedDate.isValid())
|
|
6516
|
-
return;
|
|
6517
|
-
// Format according to dateFormat from schema
|
|
6518
|
-
const formatted = parsedDate.format(dateFormat);
|
|
6519
|
-
// Update the form value only if different to avoid loops
|
|
6520
|
-
if (formatted !== selectedDate) {
|
|
6521
|
-
setValue(colLabel, formatted, {
|
|
6522
|
-
shouldValidate: true,
|
|
6523
|
-
shouldDirty: true,
|
|
6524
|
-
});
|
|
6525
|
-
}
|
|
6526
|
-
}
|
|
6527
|
-
}
|
|
6528
|
-
catch (e) {
|
|
6529
|
-
console.error(e);
|
|
6530
|
-
}
|
|
6531
|
-
}, [selectedDate, dateFormat, colLabel, setValue]);
|
|
7167
|
+
const displayDate = selectedDate && dayjs(selectedDate).tz(timezone).isValid()
|
|
7168
|
+
? dayjs(selectedDate).tz(timezone).format(displayDateFormat)
|
|
7169
|
+
: '';
|
|
6532
7170
|
const dateTimePickerLabelsConfig = {
|
|
6533
7171
|
monthNamesShort: dateTimePickerLabels?.monthNamesShort ?? [
|
|
6534
7172
|
formI18n.translate.t(`common.month_1`, {
|
|
@@ -6601,12 +7239,22 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
|
|
|
6601
7239
|
}),
|
|
6602
7240
|
};
|
|
6603
7241
|
const dateTimePickerContent = (jsxRuntime.jsx(DateTimePicker$1, { value: selectedDate, onChange: (date) => {
|
|
6604
|
-
|
|
7242
|
+
if (!date || date === null || date === undefined) {
|
|
7243
|
+
setValue(colLabel, undefined);
|
|
7244
|
+
return;
|
|
7245
|
+
}
|
|
7246
|
+
const dateObj = dayjs(date).tz(timezone);
|
|
7247
|
+
if (dateObj.isValid()) {
|
|
7248
|
+
setValue(colLabel, dateObj.format(dateFormat));
|
|
7249
|
+
}
|
|
7250
|
+
else {
|
|
7251
|
+
setValue(colLabel, undefined);
|
|
7252
|
+
}
|
|
6605
7253
|
}, timezone: timezone, labels: dateTimePickerLabelsConfig }));
|
|
6606
7254
|
return (jsxRuntime.jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
6607
|
-
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: () => {
|
|
7255
|
+
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, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
|
|
6608
7256
|
setOpen(true);
|
|
6609
|
-
}, justifyContent: 'start', children: [jsxRuntime.jsx(md.MdDateRange, {}),
|
|
7257
|
+
}, justifyContent: 'start', children: [jsxRuntime.jsx(md.MdDateRange, {}), displayDate || ''] }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "450px", minH: "25rem", 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: "450px", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { children: dateTimePickerContent }) }) }) }))] }) }));
|
|
6610
7258
|
};
|
|
6611
7259
|
|
|
6612
7260
|
const SchemaRenderer = ({ schema, prefix, column, }) => {
|
|
@@ -8041,6 +8689,7 @@ exports.CardHeader = CardHeader;
|
|
|
8041
8689
|
exports.DataDisplay = DataDisplay;
|
|
8042
8690
|
exports.DataTable = DataTable;
|
|
8043
8691
|
exports.DataTableServer = DataTableServer;
|
|
8692
|
+
exports.DatePickerInput = DatePickerInput;
|
|
8044
8693
|
exports.DefaultCardTitle = DefaultCardTitle;
|
|
8045
8694
|
exports.DefaultForm = DefaultForm;
|
|
8046
8695
|
exports.DefaultTable = DefaultTable;
|