@bsol-oss/react-datatable5 12.0.0-beta.90 → 12.0.0-beta.92
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 +38 -7
- package/dist/index.js +1242 -521
- package/dist/index.mjs +1244 -525
- package/dist/types/components/DataTable/display/RecordDisplay.d.ts +2 -2
- 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/Form/SchemaFormContext.d.ts +2 -1
- package/dist/types/components/Form/components/core/FormRoot.d.ts +3 -4
- package/dist/types/components/Form/components/fields/ArrayRenderer.d.ts +1 -1
- package/dist/types/components/Form/components/fields/StringInputField.d.ts +0 -1
- package/dist/types/components/Form/components/fields/TextAreaInput.d.ts +0 -5
- package/dist/types/components/Form/components/types/CustomJSONSchema7.d.ts +8 -0
- package/dist/types/components/Form/components/viewers/ArrayViewer.d.ts +1 -1
- package/dist/types/components/Form/components/viewers/BooleanViewer.d.ts +1 -1
- package/dist/types/components/Form/components/viewers/DateTimeViewer.d.ts +1 -1
- package/dist/types/components/Form/components/viewers/DateViewer.d.ts +1 -1
- package/dist/types/components/Form/components/viewers/IdViewer.d.ts +1 -1
- package/dist/types/components/Form/components/viewers/ObjectViewer.d.ts +1 -1
- package/dist/types/components/Form/components/viewers/RecordViewer.d.ts +2 -2
- package/dist/types/components/Form/components/viewers/StringViewer.d.ts +1 -6
- package/dist/types/components/Form/components/viewers/TextAreaViewer.d.ts +1 -6
- package/dist/types/components/Form/components/viewers/TimeViewer.d.ts +1 -1
- package/dist/types/components/Form/utils/useFormI18n.d.ts +5 -3
- package/dist/types/components/TimePicker/TimePicker.d.ts +2 -1
- package/dist/types/components/ui/pagination.d.ts +10 -7
- package/dist/types/index.d.ts +1 -0
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import { Button as Button$1, AbsoluteCenter, Spinner, Span, IconButton, Portal, Dialog, Flex, Text, useDisclosure, DialogBackdrop, RadioGroup as RadioGroup$1, Grid, Box, Slider as Slider$1, HStack, For, CheckboxCard as CheckboxCard$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, Tooltip as Tooltip$1, Group, InputElement, Icon, EmptyState as EmptyState$2, VStack, List, Table as Table$1, Checkbox as Checkbox$1, Card, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Tag as Tag$1, Image, Alert, Field as Field$1, Popover, useFilter, useListCollection, Combobox, Tabs, Skeleton, NumberInput, Show, RadioCard, CheckboxGroup, InputGroup as InputGroup$1, Center, Heading } from '@chakra-ui/react';
|
|
2
|
+
import { Button as Button$1, AbsoluteCenter, Spinner, Span, IconButton, Portal, Dialog, Flex, Text, useDisclosure, DialogBackdrop, RadioGroup as RadioGroup$1, Grid, Box, Slider as Slider$1, HStack, For, CheckboxCard as CheckboxCard$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, Tooltip as Tooltip$1, Group, InputElement, Icon, EmptyState as EmptyState$2, VStack, List, Table as Table$1, Checkbox as Checkbox$1, Card, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Clipboard, Badge, Link, Tag as Tag$1, Image, Alert, Field as Field$1, Popover, useFilter, useListCollection, Combobox, Tabs, Skeleton, NumberInput, Show, RadioCard, CheckboxGroup, InputGroup as InputGroup$1, Center, Heading } from '@chakra-ui/react';
|
|
3
3
|
import { AiOutlineColumnWidth } from 'react-icons/ai';
|
|
4
4
|
import * as React from 'react';
|
|
5
|
-
import React__default, { createContext, useContext, useState, useEffect, useRef, useMemo, forwardRef } from 'react';
|
|
6
|
-
import { LuX, LuCheck, LuChevronRight, LuSearch, LuImage, LuFile } from 'react-icons/lu';
|
|
5
|
+
import React__default, { createContext, useContext, useState, useEffect, useRef, useMemo, forwardRef, useCallback } from 'react';
|
|
6
|
+
import { LuX, LuCheck, LuChevronRight, LuCopy, LuExternalLink, LuSearch, LuImage, LuFile } from 'react-icons/lu';
|
|
7
7
|
import { MdOutlineSort, MdFilterAlt, MdSearch, MdOutlineChecklist, MdClear, MdOutlineViewColumn, MdFilterListAlt, MdPushPin, MdCancel, MdDateRange } from 'react-icons/md';
|
|
8
8
|
import { FaUpDown, FaGripLinesVertical, FaTrash } from 'react-icons/fa6';
|
|
9
9
|
import { BiDownArrow, BiUpArrow, BiError } from 'react-icons/bi';
|
|
@@ -33,6 +33,7 @@ import dayjs from 'dayjs';
|
|
|
33
33
|
import utc from 'dayjs/plugin/utc';
|
|
34
34
|
import timezone from 'dayjs/plugin/timezone';
|
|
35
35
|
import { TiDeleteOutline } from 'react-icons/ti';
|
|
36
|
+
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
|
36
37
|
import { rankItem } from '@tanstack/match-sorter-utils';
|
|
37
38
|
|
|
38
39
|
const DataTableContext = createContext({
|
|
@@ -541,16 +542,111 @@ const { withContext } = createRecipeContext({ key: "button" });
|
|
|
541
542
|
const LinkButton = withContext("a");
|
|
542
543
|
|
|
543
544
|
const [RootPropsProvider, useRootProps] = createContext$1({
|
|
544
|
-
name:
|
|
545
|
+
name: 'RootPropsProvider',
|
|
545
546
|
});
|
|
546
547
|
const variantMap = {
|
|
547
|
-
outline: { default:
|
|
548
|
-
solid: { default:
|
|
549
|
-
subtle: { default:
|
|
548
|
+
outline: { default: 'ghost', ellipsis: 'plain', current: 'outline' },
|
|
549
|
+
solid: { default: 'outline', ellipsis: 'outline', current: 'solid' },
|
|
550
|
+
subtle: { default: 'ghost', ellipsis: 'plain', current: 'subtle' },
|
|
550
551
|
};
|
|
551
552
|
const PaginationRoot = React.forwardRef(function PaginationRoot(props, ref) {
|
|
552
|
-
const { size =
|
|
553
|
-
|
|
553
|
+
const { size = 'sm', variant = 'outline', getHref, siblingCount, minSiblingCount = 1, maxSiblingCount, ...rest } = props;
|
|
554
|
+
const containerRef = React.useRef(null);
|
|
555
|
+
const [calculatedSiblingCount, setCalculatedSiblingCount] = React.useState(siblingCount);
|
|
556
|
+
React.useEffect(() => {
|
|
557
|
+
if (siblingCount !== undefined || !containerRef.current) {
|
|
558
|
+
setCalculatedSiblingCount(siblingCount);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
const container = containerRef.current;
|
|
562
|
+
let rafId = null;
|
|
563
|
+
const calculateSiblingCount = () => {
|
|
564
|
+
if (!container)
|
|
565
|
+
return;
|
|
566
|
+
const width = container.offsetWidth;
|
|
567
|
+
if (width === 0)
|
|
568
|
+
return;
|
|
569
|
+
// Estimate button width based on size
|
|
570
|
+
// These are approximate widths including padding for different button sizes
|
|
571
|
+
const buttonWidthMap = {
|
|
572
|
+
xs: 28,
|
|
573
|
+
sm: 36,
|
|
574
|
+
md: 40,
|
|
575
|
+
lg: 44,
|
|
576
|
+
};
|
|
577
|
+
let buttonWidth = buttonWidthMap[size] || 36;
|
|
578
|
+
// Try to measure actual button if available (for more accuracy)
|
|
579
|
+
const buttons = container.querySelectorAll('button');
|
|
580
|
+
if (buttons.length > 0) {
|
|
581
|
+
const firstButton = buttons[0];
|
|
582
|
+
if (firstButton.offsetWidth > 0) {
|
|
583
|
+
// Use measured width, but account for text content variation
|
|
584
|
+
const measuredWidth = firstButton.offsetWidth;
|
|
585
|
+
// Page number buttons might be slightly wider due to text, use measured width
|
|
586
|
+
buttonWidth = Math.max(buttonWidth, measuredWidth);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
// Account for prev/next buttons and gaps
|
|
590
|
+
// HStack gap is typically 8px in Chakra UI
|
|
591
|
+
const gap = 8;
|
|
592
|
+
const prevNextWidth = buttonWidth * 2 + gap;
|
|
593
|
+
const availableWidth = Math.max(0, width - prevNextWidth);
|
|
594
|
+
// Each page button takes buttonWidth + gap
|
|
595
|
+
const buttonWithGap = buttonWidth + gap;
|
|
596
|
+
const maxButtons = Math.floor(availableWidth / buttonWithGap);
|
|
597
|
+
// Calculate sibling count
|
|
598
|
+
// Minimum structure: [prev] [1] [current] [last] [next] = 5 buttons
|
|
599
|
+
// With siblings: [prev] [1] [...] [current-N] ... [current] ... [current+N] [...] [last] [next]
|
|
600
|
+
// We need: prev(1) + first(1) + ellipsis(1) + siblings*2 + current(1) + ellipsis(1) + last(1) + next(1)
|
|
601
|
+
// Minimum: 5 buttons (prev, first, current, last, next)
|
|
602
|
+
// With siblings: 5 + siblings*2 + ellipsis*2 (if needed)
|
|
603
|
+
const minRequired = 5;
|
|
604
|
+
const extraButtons = Math.max(0, maxButtons - minRequired);
|
|
605
|
+
// Calculate sibling count
|
|
606
|
+
// If we have enough space for ellipsis (2 buttons), account for that
|
|
607
|
+
let calculated = minSiblingCount;
|
|
608
|
+
if (extraButtons >= 4) {
|
|
609
|
+
// Space for ellipsis (2) + siblings
|
|
610
|
+
calculated = Math.floor((extraButtons - 2) / 2);
|
|
611
|
+
}
|
|
612
|
+
else if (extraButtons >= 2) {
|
|
613
|
+
// Space for some siblings but not ellipsis
|
|
614
|
+
calculated = Math.floor(extraButtons / 2);
|
|
615
|
+
}
|
|
616
|
+
// Apply max limit if provided
|
|
617
|
+
if (maxSiblingCount !== undefined) {
|
|
618
|
+
calculated = Math.min(calculated, maxSiblingCount);
|
|
619
|
+
}
|
|
620
|
+
setCalculatedSiblingCount(Math.max(minSiblingCount, calculated));
|
|
621
|
+
};
|
|
622
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
623
|
+
// Use requestAnimationFrame to debounce and ensure DOM is updated
|
|
624
|
+
if (rafId !== null) {
|
|
625
|
+
cancelAnimationFrame(rafId);
|
|
626
|
+
}
|
|
627
|
+
rafId = requestAnimationFrame(calculateSiblingCount);
|
|
628
|
+
});
|
|
629
|
+
resizeObserver.observe(container);
|
|
630
|
+
// Initial calculation after a short delay to ensure buttons are rendered
|
|
631
|
+
const timeoutId = setTimeout(calculateSiblingCount, 100);
|
|
632
|
+
return () => {
|
|
633
|
+
resizeObserver.disconnect();
|
|
634
|
+
if (rafId !== null) {
|
|
635
|
+
cancelAnimationFrame(rafId);
|
|
636
|
+
}
|
|
637
|
+
clearTimeout(timeoutId);
|
|
638
|
+
};
|
|
639
|
+
}, [size, siblingCount, minSiblingCount, maxSiblingCount]);
|
|
640
|
+
const mergedRef = React.useCallback((node) => {
|
|
641
|
+
if (typeof ref === 'function') {
|
|
642
|
+
ref(node);
|
|
643
|
+
}
|
|
644
|
+
else if (ref) {
|
|
645
|
+
ref.current = node;
|
|
646
|
+
}
|
|
647
|
+
containerRef.current = node;
|
|
648
|
+
}, [ref]);
|
|
649
|
+
return (jsx(RootPropsProvider, { value: { size, variantMap: variantMap[variant], getHref }, children: jsx(Pagination$1.Root, { ref: mergedRef, type: getHref ? 'link' : 'button', siblingCount: calculatedSiblingCount, ...rest }) }));
|
|
554
650
|
});
|
|
555
651
|
const PaginationEllipsis = React.forwardRef(function PaginationEllipsis(props, ref) {
|
|
556
652
|
const { size, variantMap } = useRootProps();
|
|
@@ -584,16 +680,16 @@ const PaginationNextTrigger = React.forwardRef(function PaginationNextTrigger(pr
|
|
|
584
680
|
});
|
|
585
681
|
const PaginationItems = (props) => {
|
|
586
682
|
return (jsx(Pagination$1.Context, { children: ({ pages }) => pages.map((page, index) => {
|
|
587
|
-
return page.type ===
|
|
683
|
+
return page.type === 'ellipsis' ? (jsx(PaginationEllipsis, { index: index, ...props }, index)) : (jsx(PaginationItem, { type: "page", value: page.value, ...props }, index));
|
|
588
684
|
}) }));
|
|
589
685
|
};
|
|
590
686
|
React.forwardRef(function PaginationPageText(props, ref) {
|
|
591
|
-
const { format =
|
|
687
|
+
const { format = 'compact', ...rest } = props;
|
|
592
688
|
const { page, totalPages, pageRange, count } = usePaginationContext();
|
|
593
689
|
const content = React.useMemo(() => {
|
|
594
|
-
if (format ===
|
|
690
|
+
if (format === 'short')
|
|
595
691
|
return `${page} / ${totalPages}`;
|
|
596
|
-
if (format ===
|
|
692
|
+
if (format === 'compact')
|
|
597
693
|
return `${page} / ${totalPages}`;
|
|
598
694
|
return `${pageRange.start + 1} - ${Math.min(pageRange.end, count)} / ${count}`;
|
|
599
695
|
}, [format, page, totalPages, pageRange, count]);
|
|
@@ -3081,14 +3177,14 @@ const DefaultCardTitle = () => {
|
|
|
3081
3177
|
const TableCards = ({ isSelectable = false, showDisplayNameOnly = false, renderTitle = DefaultCardTitle, cardBodyProps = {}, }) => {
|
|
3082
3178
|
const { table, rowSelection, setRowSelection } = useDataTableContext();
|
|
3083
3179
|
return (jsx(Fragment, { children: table.getRowModel().rows.map((row) => {
|
|
3084
|
-
return (jsx(Card.Root, { flex: '1 0 20rem', children: jsxs(Card.Body, { display: 'flex', flexFlow: 'column', gap: '0.5rem', ...cardBodyProps, children: [isSelectable && (jsx(Checkbox, {
|
|
3180
|
+
return (jsx(Card.Root, { flex: '1 0 20rem', children: jsxs(Card.Body, { display: 'flex', flexFlow: 'column', gap: '0.5rem', ...cardBodyProps, children: [isSelectable && (jsx(Checkbox, { checked: isRowSelected(row.id, rowSelection),
|
|
3085
3181
|
disabled: !canRowSelect(row),
|
|
3086
3182
|
// indeterminate: row.getIsSomeSelected(),
|
|
3087
3183
|
onChange: createRowToggleHandler(row, rowSelection, setRowSelection) })), renderTitle(row), jsx(Grid, { templateColumns: 'auto 1fr', gap: '1rem', children: row.getVisibleCells().map((cell) => {
|
|
3088
|
-
return (jsxs(
|
|
3184
|
+
return (jsxs(Box, { display: "contents", children: [jsxs(Box, { children: [showDisplayNameOnly && (jsx(Text, { fontWeight: 'bold', children: cell.column.columnDef.meta?.displayName ??
|
|
3089
3185
|
cell.column.id })), !showDisplayNameOnly && (jsx(Fragment, { children: flexRender(cell.column.columnDef.header,
|
|
3090
3186
|
// @ts-expect-error Assuming the CellContext interface is the same as HeaderContext
|
|
3091
|
-
cell.getContext()) }))] }
|
|
3187
|
+
cell.getContext()) }))] }), jsx(Box, { justifySelf: 'end', children: flexRender(cell.column.columnDef.cell, cell.getContext()) })] }, `chakra-table-cardcell-${cell.id}`));
|
|
3092
3188
|
}) })] }) }, `chakra-table-card-${row.id}`));
|
|
3093
3189
|
}) }));
|
|
3094
3190
|
};
|
|
@@ -3119,9 +3215,13 @@ const TableFooter = ({ showSelector = false, alwaysShowSelector = true, }) => {
|
|
|
3119
3215
|
}
|
|
3120
3216
|
return false;
|
|
3121
3217
|
};
|
|
3122
|
-
return (jsx(Table$1.Footer, { children: table.getFooterGroups().map((footerGroup) => (jsxs(Table$1.Row, { display: 'flex', children: [showSelector && (
|
|
3123
|
-
|
|
3124
|
-
|
|
3218
|
+
return (jsx(Table$1.Footer, { children: table.getFooterGroups().map((footerGroup) => (jsxs(Table$1.Row, { display: 'flex', children: [showSelector && (jsx(Table$1.Cell, { padding: `${table.getDensityValue()}px`, onMouseEnter: () => handleRowHover(true), onMouseLeave: () => handleRowHover(false), display: 'grid', justifyItems: 'center', alignItems: 'center', color: {
|
|
3219
|
+
base: 'colorPalette.900',
|
|
3220
|
+
_dark: 'colorPalette.100',
|
|
3221
|
+
},
|
|
3222
|
+
bg: { base: 'colorPalette.50', _dark: 'colorPalette.950' }, children: isCheckBoxVisible() ? (jsx(Checkbox, { width: `${SELECTION_BOX_WIDTH}px`, height: `${SELECTION_BOX_WIDTH}px`, checked: areAllRowsSelected(table, rowSelection),
|
|
3223
|
+
// indeterminate: areSomeRowsSelected(table, rowSelection),
|
|
3224
|
+
onChange: createToggleAllRowsHandler(table, rowSelection, setRowSelection) })) : (jsx(Box, { as: "span", width: `${SELECTION_BOX_WIDTH}px`, height: `${SELECTION_BOX_WIDTH}px` })) })), footerGroup.headers.map((header) => (jsx(Table$1.Cell, { padding: '0', columnSpan: `${header.colSpan}`,
|
|
3125
3225
|
// styling resize and pinning start
|
|
3126
3226
|
maxWidth: `${header.getSize()}px`, width: `${header.getSize()}px`, display: 'grid', children: jsx(MenuRoot$1, { children: jsx(MenuTrigger$1, { asChild: true, children: jsx(Box, { padding: `${table.getDensityValue()}px`, display: 'flex', alignItems: 'center', justifyContent: 'start', borderRadius: '0rem', children: jsxs(Flex, { gap: "0.5rem", alignItems: 'center', children: [header.isPlaceholder
|
|
3127
3227
|
? null
|
|
@@ -3315,11 +3415,136 @@ const TableLoadingComponent = ({ render, }) => {
|
|
|
3315
3415
|
return jsx(Fragment, { children: render(query.isLoading) });
|
|
3316
3416
|
};
|
|
3317
3417
|
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3418
|
+
// Helper function to highlight matching text
|
|
3419
|
+
const highlightText$1 = (text, searchTerm) => {
|
|
3420
|
+
if (!searchTerm || searchTerm.trim() === '') {
|
|
3421
|
+
return String(text);
|
|
3422
|
+
}
|
|
3423
|
+
const textStr = String(text);
|
|
3424
|
+
const searchLower = searchTerm.toLowerCase();
|
|
3425
|
+
const textLower = textStr.toLowerCase();
|
|
3426
|
+
const parts = [];
|
|
3427
|
+
let lastIndex = 0;
|
|
3428
|
+
let index = textLower.indexOf(searchLower, lastIndex);
|
|
3429
|
+
while (index !== -1) {
|
|
3430
|
+
// Add text before match
|
|
3431
|
+
if (index > lastIndex) {
|
|
3432
|
+
parts.push(textStr.substring(lastIndex, index));
|
|
3433
|
+
}
|
|
3434
|
+
// Add highlighted match
|
|
3435
|
+
parts.push(jsx(Text, { as: "mark", bg: {
|
|
3436
|
+
base: 'yellow.200',
|
|
3437
|
+
_dark: 'yellow.800',
|
|
3438
|
+
}, color: {
|
|
3439
|
+
base: 'gray.900',
|
|
3440
|
+
_dark: 'gray.100',
|
|
3441
|
+
}, px: 0.5, borderRadius: "sm", children: textStr.substring(index, index + searchTerm.length) }, index));
|
|
3442
|
+
lastIndex = index + searchTerm.length;
|
|
3443
|
+
index = textLower.indexOf(searchLower, lastIndex);
|
|
3444
|
+
}
|
|
3445
|
+
// Add remaining text
|
|
3446
|
+
if (lastIndex < textStr.length) {
|
|
3447
|
+
parts.push(textStr.substring(lastIndex));
|
|
3448
|
+
}
|
|
3449
|
+
return parts.length > 0 ? jsx(Fragment, { children: parts }) : textStr;
|
|
3450
|
+
};
|
|
3451
|
+
const TextWithCopy = ({ text, globalFilter, highlightedText, }) => {
|
|
3452
|
+
const textValue = String(text ?? '');
|
|
3453
|
+
const displayText = highlightedText !== undefined
|
|
3454
|
+
? highlightedText
|
|
3455
|
+
: highlightText$1(textValue, globalFilter);
|
|
3456
|
+
return (jsxs(HStack, { gap: 2, alignItems: "center", children: [jsx(Text, { as: "span", children: displayText }), jsx(Clipboard.Root, { value: textValue, children: jsx(Clipboard.Trigger, { asChild: true, children: jsx(IconButton, { size: "xs", variant: "ghost", "aria-label": "Copy", fontSize: "1em", children: jsx(Clipboard.Indicator, { copied: jsx(LuCheck, {}), children: jsx(LuCopy, {}) }) }) }) })] }));
|
|
3457
|
+
};
|
|
3458
|
+
|
|
3459
|
+
// Helper function to highlight matching text
|
|
3460
|
+
const highlightText = (text, searchTerm) => {
|
|
3461
|
+
if (!searchTerm || searchTerm.trim() === '') {
|
|
3462
|
+
return String(text);
|
|
3463
|
+
}
|
|
3464
|
+
const textStr = String(text);
|
|
3465
|
+
const searchLower = searchTerm.toLowerCase();
|
|
3466
|
+
const textLower = textStr.toLowerCase();
|
|
3467
|
+
const parts = [];
|
|
3468
|
+
let lastIndex = 0;
|
|
3469
|
+
let index = textLower.indexOf(searchLower, lastIndex);
|
|
3470
|
+
while (index !== -1) {
|
|
3471
|
+
// Add text before match
|
|
3472
|
+
if (index > lastIndex) {
|
|
3473
|
+
parts.push(textStr.substring(lastIndex, index));
|
|
3474
|
+
}
|
|
3475
|
+
// Add highlighted match
|
|
3476
|
+
parts.push(jsx(Text, { as: "mark", bg: {
|
|
3477
|
+
base: 'yellow.200',
|
|
3478
|
+
_dark: 'yellow.800',
|
|
3479
|
+
}, color: {
|
|
3480
|
+
base: 'gray.900',
|
|
3481
|
+
_dark: 'gray.100',
|
|
3482
|
+
}, px: 0.5, borderRadius: "sm", children: textStr.substring(index, index + searchTerm.length) }, index));
|
|
3483
|
+
lastIndex = index + searchTerm.length;
|
|
3484
|
+
index = textLower.indexOf(searchLower, lastIndex);
|
|
3485
|
+
}
|
|
3486
|
+
// Add remaining text
|
|
3487
|
+
if (lastIndex < textStr.length) {
|
|
3488
|
+
parts.push(textStr.substring(lastIndex));
|
|
3489
|
+
}
|
|
3490
|
+
return parts.length > 0 ? jsx(Fragment, { children: parts }) : textStr;
|
|
3491
|
+
};
|
|
3492
|
+
const RenderValue = ({ text, href, onClick, isCopyable, isBadge, badgeColor, colorPalette, globalFilter, }) => {
|
|
3493
|
+
const highlightedText = useMemo(() => highlightText(text ?? '', globalFilter), [text, globalFilter]);
|
|
3494
|
+
if (isBadge) {
|
|
3495
|
+
return (jsx(Badge, { colorPalette: colorPalette || badgeColor, children: highlightedText }));
|
|
3496
|
+
}
|
|
3497
|
+
// onClick takes precedence over href
|
|
3498
|
+
if (onClick) {
|
|
3499
|
+
return (jsx(Box, { as: "button", onClick: onClick, cursor: "pointer", textAlign: "left", _hover: {
|
|
3500
|
+
textDecoration: 'underline',
|
|
3501
|
+
color: {
|
|
3502
|
+
base: 'blue.500',
|
|
3503
|
+
_dark: 'blue.400',
|
|
3504
|
+
},
|
|
3505
|
+
}, transition: "all 0.2s", children: highlightedText }));
|
|
3321
3506
|
}
|
|
3322
|
-
|
|
3507
|
+
if (href) {
|
|
3508
|
+
return (jsxs(Link, { href: href, target: "_blank", rel: "noopener noreferrer", _hover: {
|
|
3509
|
+
textDecoration: 'underline',
|
|
3510
|
+
}, children: [highlightedText, " ", jsx(Icon, { as: LuExternalLink })] }));
|
|
3511
|
+
}
|
|
3512
|
+
if (isCopyable) {
|
|
3513
|
+
return (jsx(TextWithCopy, { text: text, globalFilter: globalFilter, highlightedText: highlightedText }));
|
|
3514
|
+
}
|
|
3515
|
+
return jsx(Fragment, { children: highlightedText });
|
|
3516
|
+
};
|
|
3517
|
+
const TextCell = ({ text, href, onClick, isCopyable, isBadge, badgeColor, colorPalette,
|
|
3518
|
+
// Legacy props
|
|
3519
|
+
label, containerProps = {}, textProps = {}, children, }) => {
|
|
3520
|
+
// Get globalFilter from context
|
|
3521
|
+
// If not in DataTable context, will use default empty string from context
|
|
3522
|
+
const { globalFilter } = useDataTableContext();
|
|
3523
|
+
// Legacy API: if children is provided, use old behavior
|
|
3524
|
+
if (children !== undefined) {
|
|
3525
|
+
const displayText = typeof children === 'string' || typeof children === 'number'
|
|
3526
|
+
? String(children)
|
|
3527
|
+
: children;
|
|
3528
|
+
const highlightedDisplayText = typeof displayText === 'string' || typeof displayText === 'number'
|
|
3529
|
+
? highlightText(displayText, globalFilter)
|
|
3530
|
+
: displayText;
|
|
3531
|
+
if (label) {
|
|
3532
|
+
return (jsx(Flex, { alignItems: 'center', height: '100%', ...containerProps, children: jsx(Tooltip, { content: jsx(Text, { as: "span", overflow: "hidden", textOverflow: 'ellipsis', children: label }), children: jsx(Text, { as: "span", overflow: "hidden", textOverflow: 'ellipsis', wordBreak: 'break-all', ...textProps, children: highlightedDisplayText }) }) }));
|
|
3533
|
+
}
|
|
3534
|
+
return (jsx(Flex, { alignItems: 'center', height: '100%', ...containerProps, children: jsx(Text, { as: "span", overflow: "hidden", textOverflow: 'ellipsis', wordBreak: 'break-all', ...textProps, children: highlightedDisplayText }) }));
|
|
3535
|
+
}
|
|
3536
|
+
// New API: use text prop
|
|
3537
|
+
const displayValue = text ?? children;
|
|
3538
|
+
if (Array.isArray(displayValue)) {
|
|
3539
|
+
return (jsx(Flex, { gap: 2, flexWrap: "wrap", children: displayValue.map((item, index) => {
|
|
3540
|
+
const highlightedItem = highlightText(item, globalFilter);
|
|
3541
|
+
return (jsx(Badge, { colorPalette: colorPalette || badgeColor, children: highlightedItem }, index));
|
|
3542
|
+
}) }));
|
|
3543
|
+
}
|
|
3544
|
+
if (!!displayValue === false) {
|
|
3545
|
+
return (jsx(Text, { textOverflow: "ellipsis", whiteSpace: "nowrap", overflow: "hidden", wordBreak: "break-all", display: "flex", alignItems: "center", height: "100%", children: "-" }));
|
|
3546
|
+
}
|
|
3547
|
+
return (jsx(Box, { textOverflow: "ellipsis", whiteSpace: "nowrap", wordBreak: "break-all", overflow: "auto", display: "flex", alignItems: "center", height: "100%", children: jsx(RenderValue, { text: displayValue, href: href, onClick: onClick, isCopyable: isCopyable, isBadge: isBadge, badgeColor: badgeColor, colorPalette: colorPalette, globalFilter: globalFilter }) }));
|
|
3323
3548
|
};
|
|
3324
3549
|
|
|
3325
3550
|
const Tag = React.forwardRef(function Tag(props, ref) {
|
|
@@ -3482,7 +3707,7 @@ const snakeToLabel = (str) => {
|
|
|
3482
3707
|
.join(" "); // Join with space
|
|
3483
3708
|
};
|
|
3484
3709
|
|
|
3485
|
-
const RecordDisplay = ({ object, boxProps, translate, prefix =
|
|
3710
|
+
const RecordDisplay = ({ object, boxProps, translate, prefix = '', }) => {
|
|
3486
3711
|
const getColumn = ({ field }) => {
|
|
3487
3712
|
if (translate !== undefined) {
|
|
3488
3713
|
return translate.t(`${prefix}${field}`);
|
|
@@ -3492,8 +3717,9 @@ const RecordDisplay = ({ object, boxProps, translate, prefix = "", }) => {
|
|
|
3492
3717
|
if (object === null) {
|
|
3493
3718
|
return jsx(Fragment, { children: "null" });
|
|
3494
3719
|
}
|
|
3495
|
-
return (jsx(Grid, { rowGap: 1, padding: 1, overflow:
|
|
3496
|
-
|
|
3720
|
+
return (jsx(Grid, { rowGap: 1, padding: 1, overflow: 'auto', ...boxProps, children: Object.entries(object).map(([field, value], index) => {
|
|
3721
|
+
const uniqueKey = `${prefix}${field}-${index}`;
|
|
3722
|
+
return (jsxs(Grid, { columnGap: 2, gridTemplateColumns: 'auto 1fr', children: [jsx(Text, { color: 'colorPalette.400', children: getColumn({ field }) }), typeof value === 'object' && value !== null ? (jsx(RecordDisplay, { object: value, prefix: `${prefix}${field}.`, translate: translate })) : (jsx(Text, { children: JSON.stringify(value) }))] }, uniqueKey));
|
|
3497
3723
|
}) }));
|
|
3498
3724
|
};
|
|
3499
3725
|
|
|
@@ -3784,13 +4010,10 @@ const idPickerSanityCheck = (column, foreign_key) => {
|
|
|
3784
4010
|
if (!!foreign_key == false) {
|
|
3785
4011
|
throw new Error(`The key foreign_key does not exist in properties of column ${column} when using id-picker.`);
|
|
3786
4012
|
}
|
|
3787
|
-
const { table, column: foreignKeyColumn
|
|
4013
|
+
const { table, column: foreignKeyColumn } = foreign_key;
|
|
3788
4014
|
if (!!table == false) {
|
|
3789
4015
|
throw new Error(`The key table does not exist in properties of column ${table} when using id-picker.`);
|
|
3790
4016
|
}
|
|
3791
|
-
if (!!display_column == false) {
|
|
3792
|
-
throw new Error(`The key display_column does not exist in properties of column ${column} when using id-picker.`);
|
|
3793
|
-
}
|
|
3794
4017
|
if (!!foreignKeyColumn == false) {
|
|
3795
4018
|
throw new Error(`The key column does not exist in properties of column ${column} when using id-picker.`);
|
|
3796
4019
|
}
|
|
@@ -3799,7 +4022,7 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
|
|
|
3799
4022
|
showSubmitButton: true,
|
|
3800
4023
|
showResetButton: true,
|
|
3801
4024
|
showTitle: true,
|
|
3802
|
-
}, requireConfirmation = false, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, insideDialog = false, }) => {
|
|
4025
|
+
}, requireConfirmation = false, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, formButtonLabels, insideDialog = false, }) => {
|
|
3803
4026
|
const [isSuccess, setIsSuccess] = useState(false);
|
|
3804
4027
|
const [isError, setIsError] = useState(false);
|
|
3805
4028
|
const [isSubmiting, setIsSubmiting] = useState(false);
|
|
@@ -3889,6 +4112,7 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
|
|
|
3889
4112
|
idPickerLabels,
|
|
3890
4113
|
enumPickerLabels,
|
|
3891
4114
|
filePickerLabels,
|
|
4115
|
+
formButtonLabels,
|
|
3892
4116
|
ajvResolver: ajvResolver(schema),
|
|
3893
4117
|
insideDialog,
|
|
3894
4118
|
}, children: jsx(FormProvider, { ...form, children: children }) }));
|
|
@@ -3898,40 +4122,107 @@ function removeIndex(str) {
|
|
|
3898
4122
|
return str.replace(/\.\d+\./g, ".");
|
|
3899
4123
|
}
|
|
3900
4124
|
|
|
4125
|
+
/**
|
|
4126
|
+
* Custom hook to simplify i18n translation for form fields.
|
|
4127
|
+
* Automatically handles colLabel construction and removeIndex logic.
|
|
4128
|
+
*
|
|
4129
|
+
* @param column - The column name
|
|
4130
|
+
* @param prefix - The prefix for the field (usually empty string or parent path)
|
|
4131
|
+
* @returns Object with translation helper functions
|
|
4132
|
+
*
|
|
4133
|
+
* @example
|
|
4134
|
+
* ```tsx
|
|
4135
|
+
* const formI18n = useFormI18n(column, prefix);
|
|
4136
|
+
*
|
|
4137
|
+
* // Get field label
|
|
4138
|
+
* <Field label={formI18n.label()} />
|
|
4139
|
+
*
|
|
4140
|
+
* // Get error message
|
|
4141
|
+
* <Text>{formI18n.required()}</Text>
|
|
4142
|
+
*
|
|
4143
|
+
* // Get custom translation key
|
|
4144
|
+
* <Text>{formI18n.t('add_more')}</Text>
|
|
4145
|
+
*
|
|
4146
|
+
* // Access the raw colLabel
|
|
4147
|
+
* const colLabel = formI18n.colLabel;
|
|
4148
|
+
* ```
|
|
4149
|
+
*/
|
|
4150
|
+
const useFormI18n$1 = (column, prefix = '', schema) => {
|
|
4151
|
+
const { translate } = useSchemaContext();
|
|
4152
|
+
const colLabel = `${prefix}${column}`;
|
|
4153
|
+
return {
|
|
4154
|
+
/**
|
|
4155
|
+
* The constructed column label (prefix + column)
|
|
4156
|
+
*/
|
|
4157
|
+
colLabel,
|
|
4158
|
+
/**
|
|
4159
|
+
* Get the field label from schema title prop, or fall back to translation
|
|
4160
|
+
* Uses schema.title if available, otherwise: translate.t(removeIndex(`${colLabel}.field_label`))
|
|
4161
|
+
*/
|
|
4162
|
+
label: (options) => {
|
|
4163
|
+
if (schema?.title) {
|
|
4164
|
+
return schema.title;
|
|
4165
|
+
}
|
|
4166
|
+
return translate.t(removeIndex(`${colLabel}.field_label`), options);
|
|
4167
|
+
},
|
|
4168
|
+
/**
|
|
4169
|
+
* Get the required error message translation
|
|
4170
|
+
* Equivalent to: translate.t(removeIndex(`${colLabel}.field_required`))
|
|
4171
|
+
*/
|
|
4172
|
+
required: (options) => {
|
|
4173
|
+
return translate.t(removeIndex(`${colLabel}.field_required`), options);
|
|
4174
|
+
},
|
|
4175
|
+
/**
|
|
4176
|
+
* Get a translation for any custom key relative to the field
|
|
4177
|
+
* Equivalent to: translate.t(removeIndex(`${colLabel}.${key}`))
|
|
4178
|
+
*
|
|
4179
|
+
* @param key - The translation key suffix (e.g., 'add_more', 'total', etc.)
|
|
4180
|
+
* @param options - Optional translation options (e.g., defaultValue, interpolation variables)
|
|
4181
|
+
*/
|
|
4182
|
+
t: (key, options) => {
|
|
4183
|
+
return translate.t(removeIndex(`${colLabel}.${key}`), options);
|
|
4184
|
+
},
|
|
4185
|
+
/**
|
|
4186
|
+
* Access to the original translate object for edge cases
|
|
4187
|
+
*/
|
|
4188
|
+
translate,
|
|
4189
|
+
};
|
|
4190
|
+
};
|
|
4191
|
+
|
|
3901
4192
|
const ArrayRenderer = ({ schema, column, prefix, }) => {
|
|
3902
|
-
const { gridRow, gridColumn =
|
|
4193
|
+
const { gridRow, gridColumn = '1/span 12', required, items } = schema;
|
|
3903
4194
|
// @ts-expect-error TODO: find suitable types
|
|
3904
4195
|
const { type } = items;
|
|
3905
|
-
const { translate } = useSchemaContext();
|
|
3906
4196
|
const colLabel = `${prefix}${column}`;
|
|
3907
4197
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
4198
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
3908
4199
|
const { formState: { errors }, setValue, watch, } = useFormContext();
|
|
3909
4200
|
const fields = (watch(colLabel) ?? []);
|
|
3910
|
-
return (jsxs(Flex, { gridRow, gridColumn, flexFlow:
|
|
3911
|
-
base:
|
|
3912
|
-
_dark:
|
|
3913
|
-
}, children: [jsx(Grid, { gridTemplateColumns:
|
|
4201
|
+
return (jsxs(Flex, { gridRow, gridColumn, flexFlow: 'column', gap: 2, children: [jsxs(Box, { as: "label", children: [formI18n.label(), isRequired && jsx("span", { children: "*" })] }), jsx(Flex, { flexFlow: 'column', gap: 2, children: fields.map((field, index) => (jsxs(Grid, { gridTemplateColumns: '1fr auto', gap: 2, bgColor: { base: 'colorPalette.100', _dark: 'colorPalette.900' }, p: 2, borderRadius: 4, borderWidth: 1, borderColor: {
|
|
4202
|
+
base: 'colorPalette.200',
|
|
4203
|
+
_dark: 'colorPalette.800',
|
|
4204
|
+
}, children: [jsx(Grid, { gridTemplateColumns: 'repeat(12, 1fr)', autoFlow: 'row', children: jsx(SchemaRenderer, { column: `${index}`,
|
|
3914
4205
|
prefix: `${colLabel}.`,
|
|
3915
4206
|
// @ts-expect-error find suitable types
|
|
3916
|
-
schema: { showLabel: false, ...(items ?? {}) } }) }), jsx(Flex, { justifyContent:
|
|
4207
|
+
schema: { showLabel: false, ...(items ?? {}) } }) }), jsx(Flex, { justifyContent: 'end', children: jsx(Button$1, { variant: 'ghost', onClick: () => {
|
|
3917
4208
|
setValue(colLabel, fields.filter((_, curIndex) => {
|
|
3918
4209
|
return curIndex !== index;
|
|
3919
4210
|
}));
|
|
3920
4211
|
}, children: jsx(Icon, { children: jsx(CgTrash, {}) }) }) })] }, `${colLabel}.${index}`))) }), jsx(Flex, { children: jsx(Button$1, { onClick: () => {
|
|
3921
|
-
if (type ===
|
|
4212
|
+
if (type === 'number') {
|
|
3922
4213
|
setValue(colLabel, [...fields, 0]);
|
|
3923
4214
|
return;
|
|
3924
4215
|
}
|
|
3925
|
-
if (type ===
|
|
3926
|
-
setValue(colLabel, [...fields,
|
|
4216
|
+
if (type === 'string') {
|
|
4217
|
+
setValue(colLabel, [...fields, '']);
|
|
3927
4218
|
return;
|
|
3928
4219
|
}
|
|
3929
|
-
if (type ===
|
|
4220
|
+
if (type === 'boolean') {
|
|
3930
4221
|
setValue(colLabel, [...fields, false]);
|
|
3931
4222
|
return;
|
|
3932
4223
|
}
|
|
3933
4224
|
setValue(colLabel, [...fields, {}]);
|
|
3934
|
-
}, children: translate.t(removeIndex(`${colLabel}.add`)) }) }), errors[`${column}`] && (jsx(Text, { color:
|
|
4225
|
+
}, children: translate.t(removeIndex(`${colLabel}.add`)) }) }), errors[`${column}`] && (jsx(Text, { color: 'red.400', children: formI18n.required() }))] }));
|
|
3935
4226
|
};
|
|
3936
4227
|
|
|
3937
4228
|
const Field = React.forwardRef(function Field(props, ref) {
|
|
@@ -3941,15 +4232,13 @@ const Field = React.forwardRef(function Field(props, ref) {
|
|
|
3941
4232
|
|
|
3942
4233
|
const BooleanPicker = ({ schema, column, prefix }) => {
|
|
3943
4234
|
const { watch, formState: { errors }, setValue, } = useFormContext();
|
|
3944
|
-
const { translate } = useSchemaContext();
|
|
3945
4235
|
const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
|
|
3946
4236
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
3947
4237
|
const colLabel = `${prefix}${column}`;
|
|
3948
4238
|
const value = watch(colLabel);
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
: undefined, invalid: !!errors[colLabel], children: jsx(CheckboxCard, { checked: value, variant: 'surface', onChange: () => {
|
|
4239
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
4240
|
+
return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
4241
|
+
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsx(CheckboxCard, { checked: value, variant: 'surface', onChange: () => {
|
|
3953
4242
|
setValue(colLabel, !value);
|
|
3954
4243
|
} }) }));
|
|
3955
4244
|
};
|
|
@@ -4065,76 +4354,12 @@ let DatePicker$1 = class DatePicker extends React__default.Component {
|
|
|
4065
4354
|
}
|
|
4066
4355
|
};
|
|
4067
4356
|
|
|
4068
|
-
/**
|
|
4069
|
-
* Custom hook to simplify i18n translation for form fields.
|
|
4070
|
-
* Automatically handles colLabel construction and removeIndex logic.
|
|
4071
|
-
*
|
|
4072
|
-
* @param column - The column name
|
|
4073
|
-
* @param prefix - The prefix for the field (usually empty string or parent path)
|
|
4074
|
-
* @returns Object with translation helper functions
|
|
4075
|
-
*
|
|
4076
|
-
* @example
|
|
4077
|
-
* ```tsx
|
|
4078
|
-
* const formI18n = useFormI18n(column, prefix);
|
|
4079
|
-
*
|
|
4080
|
-
* // Get field label
|
|
4081
|
-
* <Field label={formI18n.label()} />
|
|
4082
|
-
*
|
|
4083
|
-
* // Get error message
|
|
4084
|
-
* <Text>{formI18n.required()}</Text>
|
|
4085
|
-
*
|
|
4086
|
-
* // Get custom translation key
|
|
4087
|
-
* <Text>{formI18n.t('add_more')}</Text>
|
|
4088
|
-
*
|
|
4089
|
-
* // Access the raw colLabel
|
|
4090
|
-
* const colLabel = formI18n.colLabel;
|
|
4091
|
-
* ```
|
|
4092
|
-
*/
|
|
4093
|
-
const useFormI18n = (column, prefix = "") => {
|
|
4094
|
-
const { translate } = useSchemaContext();
|
|
4095
|
-
const colLabel = `${prefix}${column}`;
|
|
4096
|
-
return {
|
|
4097
|
-
/**
|
|
4098
|
-
* The constructed column label (prefix + column)
|
|
4099
|
-
*/
|
|
4100
|
-
colLabel,
|
|
4101
|
-
/**
|
|
4102
|
-
* Get the field label translation
|
|
4103
|
-
* Equivalent to: translate.t(removeIndex(`${colLabel}.field_label`))
|
|
4104
|
-
*/
|
|
4105
|
-
label: (options) => {
|
|
4106
|
-
return translate.t(removeIndex(`${colLabel}.field_label`), options);
|
|
4107
|
-
},
|
|
4108
|
-
/**
|
|
4109
|
-
* Get the required error message translation
|
|
4110
|
-
* Equivalent to: translate.t(removeIndex(`${colLabel}.field_required`))
|
|
4111
|
-
*/
|
|
4112
|
-
required: (options) => {
|
|
4113
|
-
return translate.t(removeIndex(`${colLabel}.field_required`), options);
|
|
4114
|
-
},
|
|
4115
|
-
/**
|
|
4116
|
-
* Get a translation for any custom key relative to the field
|
|
4117
|
-
* Equivalent to: translate.t(removeIndex(`${colLabel}.${key}`))
|
|
4118
|
-
*
|
|
4119
|
-
* @param key - The translation key suffix (e.g., 'add_more', 'total', etc.)
|
|
4120
|
-
* @param options - Optional translation options (e.g., defaultValue, interpolation variables)
|
|
4121
|
-
*/
|
|
4122
|
-
t: (key, options) => {
|
|
4123
|
-
return translate.t(removeIndex(`${colLabel}.${key}`), options);
|
|
4124
|
-
},
|
|
4125
|
-
/**
|
|
4126
|
-
* Access to the original translate object for edge cases
|
|
4127
|
-
*/
|
|
4128
|
-
translate,
|
|
4129
|
-
};
|
|
4130
|
-
};
|
|
4131
|
-
|
|
4132
4357
|
dayjs.extend(utc);
|
|
4133
4358
|
dayjs.extend(timezone);
|
|
4134
4359
|
const DatePicker = ({ column, schema, prefix }) => {
|
|
4135
4360
|
const { watch, formState: { errors }, setValue, } = useFormContext();
|
|
4136
4361
|
const { timezone, dateTimePickerLabels, insideDialog } = useSchemaContext();
|
|
4137
|
-
const formI18n = useFormI18n(column, prefix);
|
|
4362
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
4138
4363
|
const { required, gridColumn = 'span 12', gridRow = 'span 1', displayDateFormat = 'YYYY-MM-DD', dateFormat = 'YYYY-MM-DD', } = schema;
|
|
4139
4364
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
4140
4365
|
const colLabel = formI18n.colLabel;
|
|
@@ -4252,7 +4477,7 @@ dayjs.extend(timezone);
|
|
|
4252
4477
|
const DateRangePicker = ({ column, schema, prefix, }) => {
|
|
4253
4478
|
const { watch, formState: { errors }, setValue, } = useFormContext();
|
|
4254
4479
|
const { timezone, insideDialog } = useSchemaContext();
|
|
4255
|
-
const formI18n = useFormI18n(column, prefix);
|
|
4480
|
+
const formI18n = useFormI18n$1(column, prefix);
|
|
4256
4481
|
const { required, gridColumn = 'span 12', gridRow = 'span 1', displayDateFormat = 'YYYY-MM-DD', dateFormat = 'YYYY-MM-DD', } = schema;
|
|
4257
4482
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
4258
4483
|
const colLabel = formI18n.colLabel;
|
|
@@ -4350,7 +4575,7 @@ const DateRangePicker = ({ column, schema, prefix, }) => {
|
|
|
4350
4575
|
const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLimit = false, }) => {
|
|
4351
4576
|
const { watch, formState: { errors }, setValue, } = useFormContext();
|
|
4352
4577
|
const { enumPickerLabels, insideDialog } = useSchemaContext();
|
|
4353
|
-
const formI18n = useFormI18n(column, prefix);
|
|
4578
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
4354
4579
|
const { required, variant } = schema;
|
|
4355
4580
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
4356
4581
|
const { gridColumn = 'span 12', gridRow = 'span 1', renderDisplay } = schema;
|
|
@@ -4994,7 +5219,7 @@ function MediaBrowserDialog({ open, onClose, onSelect, title, filterImageOnly =
|
|
|
4994
5219
|
const FilePicker = ({ column, schema, prefix }) => {
|
|
4995
5220
|
const { setValue, formState: { errors }, watch, } = useFormContext();
|
|
4996
5221
|
const { filePickerLabels } = useSchemaContext();
|
|
4997
|
-
const formI18n = useFormI18n(column, prefix);
|
|
5222
|
+
const formI18n = useFormI18n$1(column, prefix);
|
|
4998
5223
|
const { required, gridColumn = 'span 12', gridRow = 'span 1', type, } = schema;
|
|
4999
5224
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
5000
5225
|
const isSingleSelect = type === 'string';
|
|
@@ -5070,7 +5295,7 @@ const FilePicker = ({ column, schema, prefix }) => {
|
|
|
5070
5295
|
const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
|
|
5071
5296
|
const { setValue, formState: { errors }, watch, } = useFormContext();
|
|
5072
5297
|
const { filePickerLabels } = useSchemaContext();
|
|
5073
|
-
const formI18n = useFormI18n(column, prefix);
|
|
5298
|
+
const formI18n = useFormI18n$1(column, prefix);
|
|
5074
5299
|
const { required, gridColumn = 'span 12', gridRow = 'span 1', filePicker, type, } = schema;
|
|
5075
5300
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
5076
5301
|
const isSingleSelect = type === 'string';
|
|
@@ -5211,13 +5436,18 @@ const getTableData = async ({ serverUrl, in_table, searching = "", where = [], l
|
|
|
5211
5436
|
}
|
|
5212
5437
|
};
|
|
5213
5438
|
|
|
5439
|
+
// Default renderDisplay function that stringifies JSON
|
|
5440
|
+
const defaultRenderDisplay = (item) => {
|
|
5441
|
+
return JSON.stringify(item);
|
|
5442
|
+
};
|
|
5443
|
+
|
|
5214
5444
|
const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
|
|
5215
5445
|
const { watch, getValues, formState: { errors }, setValue, } = useFormContext();
|
|
5216
5446
|
const { serverUrl, idMap, setIdMap, idPickerLabels, insideDialog } = useSchemaContext();
|
|
5217
|
-
const formI18n = useFormI18n(column, prefix);
|
|
5447
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
5218
5448
|
const { required, gridColumn = 'span 12', gridRow = 'span 1', renderDisplay, foreign_key, } = schema;
|
|
5219
5449
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
5220
|
-
const { table, column: column_ref,
|
|
5450
|
+
const { table, column: column_ref, customQueryFn, } = foreign_key;
|
|
5221
5451
|
const [searchText, setSearchText] = useState('');
|
|
5222
5452
|
const [debouncedSearchText, setDebouncedSearchText] = useState('');
|
|
5223
5453
|
const [limit] = useState(50); // Increased limit for combobox
|
|
@@ -5295,15 +5525,19 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
|
|
|
5295
5525
|
// Check if we're currently searching (user typed but debounce hasn't fired yet)
|
|
5296
5526
|
const isSearching = searchText !== debouncedSearchText;
|
|
5297
5527
|
// Transform data for combobox collection
|
|
5528
|
+
// label is used for filtering/searching (must be a string)
|
|
5529
|
+
// raw item is stored for custom rendering
|
|
5298
5530
|
const comboboxItems = useMemo(() => {
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5531
|
+
const renderFn = renderDisplay || defaultRenderDisplay;
|
|
5532
|
+
return dataList.map((item) => {
|
|
5533
|
+
const rendered = renderFn(item);
|
|
5534
|
+
return {
|
|
5535
|
+
label: typeof rendered === 'string' ? rendered : JSON.stringify(item), // Use string for filtering
|
|
5536
|
+
value: String(item[column_ref]),
|
|
5537
|
+
raw: item,
|
|
5538
|
+
};
|
|
5539
|
+
});
|
|
5540
|
+
}, [dataList, column_ref, renderDisplay]);
|
|
5307
5541
|
// Use filter hook for combobox
|
|
5308
5542
|
const { contains } = useFilter({ sensitivity: 'base' });
|
|
5309
5543
|
// Create collection for combobox
|
|
@@ -5349,9 +5583,9 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
|
|
|
5349
5583
|
return (jsx(Tag, { closable: true, onClick: () => {
|
|
5350
5584
|
const newValue = currentValue.filter((itemId) => itemId !== id);
|
|
5351
5585
|
setValue(colLabel, newValue);
|
|
5352
|
-
}, children:
|
|
5586
|
+
}, children: renderDisplay
|
|
5353
5587
|
? renderDisplay(item)
|
|
5354
|
-
: item
|
|
5588
|
+
: defaultRenderDisplay(item) }, id));
|
|
5355
5589
|
}) })), jsxs(Combobox.Root, { collection: collection, value: currentValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, multiple: isMultiple, closeOnSelect: !isMultiple, openOnClick: true, invalid: !!errors[colLabel], width: "100%", positioning: insideDialog
|
|
5356
5590
|
? { strategy: 'fixed', hideWhenDetached: true }
|
|
5357
5591
|
: undefined, children: [jsxs(Combobox.Control, { children: [jsx(Combobox.Input, { placeholder: idPickerLabels?.typeToSearch ?? formI18n.t('type_to_search') }), jsxs(Combobox.IndicatorGroup, { children: [(isFetching || isLoading || isPending) && jsx(Spinner, { size: "xs" }), isError && (jsx(Icon, { color: "fg.error", children: jsx(BiError, {}) })), !isMultiple && currentValue.length > 0 && (jsx(Combobox.ClearTrigger, { onClick: () => {
|
|
@@ -5362,13 +5596,17 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
|
|
|
5362
5596
|
? idPickerLabels?.emptySearchResult ??
|
|
5363
5597
|
formI18n.t('empty_search_result')
|
|
5364
5598
|
: idPickerLabels?.initialResults ??
|
|
5365
|
-
formI18n.t('initial_results') })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children:
|
|
5599
|
+
formI18n.t('initial_results') })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: !!renderDisplay === true
|
|
5600
|
+
? renderDisplay(item.raw)
|
|
5601
|
+
: item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsx(Combobox.Content, { children: isError ? (jsx(Text, { p: 2, color: "fg.error", fontSize: "sm", children: formI18n.t('loading_failed') })) : isFetching || isLoading || isPending || isSearching ? (
|
|
5366
5602
|
// Show skeleton items to prevent UI shift
|
|
5367
5603
|
jsx(Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsx(Flex, { p: 2, align: "center", gap: 2, children: jsx(Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsx(Combobox.Empty, { children: searchText
|
|
5368
5604
|
? idPickerLabels?.emptySearchResult ??
|
|
5369
5605
|
formI18n.t('empty_search_result')
|
|
5370
5606
|
: idPickerLabels?.initialResults ??
|
|
5371
|
-
formI18n.t('initial_results') })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children:
|
|
5607
|
+
formI18n.t('initial_results') })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: !!renderDisplay === true
|
|
5608
|
+
? renderDisplay(item.raw)
|
|
5609
|
+
: item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
|
|
5372
5610
|
};
|
|
5373
5611
|
|
|
5374
5612
|
const NumberInputRoot = React.forwardRef(function NumberInput$1(props, ref) {
|
|
@@ -5436,15 +5674,15 @@ const extractErrorMessage = (error) => {
|
|
|
5436
5674
|
|
|
5437
5675
|
const NumberInputField = ({ schema, column, prefix, }) => {
|
|
5438
5676
|
const { setValue, formState: { errors }, watch, } = useFormContext();
|
|
5439
|
-
const { translate } = useSchemaContext();
|
|
5440
5677
|
const { required, gridColumn = 'span 12', gridRow = 'span 1', numberStorageType = 'number', } = schema;
|
|
5441
5678
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
5442
5679
|
const colLabel = `${prefix}${column}`;
|
|
5443
5680
|
const value = watch(`${colLabel}`);
|
|
5444
5681
|
const fieldError = getFieldError(errors, colLabel);
|
|
5445
|
-
|
|
5682
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
5683
|
+
return (jsx(Field, { label: formI18n.label(), required: isRequired, gridColumn, gridRow, errorText: fieldError
|
|
5446
5684
|
? fieldError.includes('required')
|
|
5447
|
-
?
|
|
5685
|
+
? formI18n.required()
|
|
5448
5686
|
: fieldError
|
|
5449
5687
|
: undefined, invalid: !!fieldError, children: jsx(NumberInputRoot, { value: value, onValueChange: (details) => {
|
|
5450
5688
|
// Store as string or number based on configuration, default to number
|
|
@@ -5457,14 +5695,14 @@ const NumberInputField = ({ schema, column, prefix, }) => {
|
|
|
5457
5695
|
|
|
5458
5696
|
const ObjectInput = ({ schema, column, prefix }) => {
|
|
5459
5697
|
const { properties, gridColumn = 'span 12', gridRow = 'span 1', required, showLabel = true, } = schema;
|
|
5460
|
-
const { translate } = useSchemaContext();
|
|
5461
5698
|
const colLabel = `${prefix}${column}`;
|
|
5462
5699
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
5700
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
5463
5701
|
const { formState: { errors }, } = useFormContext();
|
|
5464
5702
|
if (properties === undefined) {
|
|
5465
5703
|
throw new Error(`properties is undefined when using ObjectInput`);
|
|
5466
5704
|
}
|
|
5467
|
-
return (jsxs(Box, { gridRow, gridColumn, children: [showLabel && (jsxs(Box, { as: "label", children: [
|
|
5705
|
+
return (jsxs(Box, { gridRow, gridColumn, children: [showLabel && (jsxs(Box, { as: "label", children: [formI18n.label(), isRequired && jsx("span", { children: "*" })] })), jsx(Grid, { bgColor: { base: 'colorPalette.100', _dark: 'colorPalette.900' }, p: 2, borderRadius: 4, borderWidth: 1, borderColor: {
|
|
5468
5706
|
base: 'colorPalette.200',
|
|
5469
5707
|
_dark: 'colorPalette.800',
|
|
5470
5708
|
}, gap: "4", padding: '4', gridTemplateColumns: 'repeat(12, 1fr)', autoFlow: 'row', children: Object.keys(properties ?? {}).map((key) => {
|
|
@@ -5474,10 +5712,10 @@ const ObjectInput = ({ schema, column, prefix }) => {
|
|
|
5474
5712
|
prefix: `${prefix}${column}.`,
|
|
5475
5713
|
properties,
|
|
5476
5714
|
parentRequired: required }, `form-${colLabel}-${key}`));
|
|
5477
|
-
}) }), errors[`${column}`] && (jsx(Text, { color: 'red.400', children:
|
|
5715
|
+
}) }), errors[`${column}`] && (jsx(Text, { color: 'red.400', children: formI18n.required() }))] }));
|
|
5478
5716
|
};
|
|
5479
5717
|
|
|
5480
|
-
const RecordInput
|
|
5718
|
+
const RecordInput = ({ column, schema, prefix }) => {
|
|
5481
5719
|
const { formState: { errors }, setValue, getValues, } = useFormContext();
|
|
5482
5720
|
const { translate } = useSchemaContext();
|
|
5483
5721
|
const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
|
|
@@ -5486,9 +5724,8 @@ const RecordInput$1 = ({ column, schema, prefix }) => {
|
|
|
5486
5724
|
const [showNewEntries, setShowNewEntries] = useState(false);
|
|
5487
5725
|
const [newKey, setNewKey] = useState();
|
|
5488
5726
|
const [newValue, setNewValue] = useState();
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
: undefined, invalid: !!errors[column], children: [entries.map(([key, value]) => {
|
|
5727
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
5728
|
+
return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn, gridRow, errorText: errors[`${column}`] ? formI18n.required() : undefined, invalid: !!errors[column], children: [entries.map(([key, value]) => {
|
|
5492
5729
|
return (jsxs(Grid, { templateColumns: '1fr 1fr auto', gap: 1, children: [jsx(Input, { value: key, onChange: (e) => {
|
|
5493
5730
|
const filtered = entries.filter(([target]) => {
|
|
5494
5731
|
return target !== key;
|
|
@@ -5533,12 +5770,12 @@ const RecordInput$1 = ({ column, schema, prefix }) => {
|
|
|
5533
5770
|
|
|
5534
5771
|
const StringInputField = ({ column, schema, prefix, }) => {
|
|
5535
5772
|
const { register, formState: { errors }, } = useFormContext();
|
|
5536
|
-
const { translate } = useSchemaContext();
|
|
5537
5773
|
const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
|
|
5538
5774
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
5539
5775
|
const colLabel = `${prefix}${column}`;
|
|
5540
5776
|
const fieldError = getFieldError(errors, colLabel);
|
|
5541
|
-
|
|
5777
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
5778
|
+
return (jsx(Fragment, { children: jsx(Field, { label: formI18n.label(), required: isRequired, gridColumn: gridColumn, gridRow: gridRow, errorText: fieldError, invalid: !!fieldError, children: jsx(Input, { ...register(`${colLabel}`, { required: isRequired }), autoComplete: "off" }) }) }));
|
|
5542
5779
|
};
|
|
5543
5780
|
|
|
5544
5781
|
const RadioCardItem = React.forwardRef(function RadioCardItem(props, ref) {
|
|
@@ -5723,17 +5960,17 @@ Textarea.displayName = "Textarea";
|
|
|
5723
5960
|
|
|
5724
5961
|
const TextAreaInput = ({ column, schema, prefix, }) => {
|
|
5725
5962
|
const { register, formState: { errors }, } = useFormContext();
|
|
5726
|
-
const { translate } = useSchemaContext();
|
|
5727
5963
|
const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
|
|
5728
5964
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
5729
5965
|
const colLabel = `${prefix}${column}`;
|
|
5730
5966
|
const form = useFormContext();
|
|
5731
5967
|
const { setValue, watch } = form;
|
|
5732
5968
|
const fieldError = getFieldError(errors, colLabel);
|
|
5969
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
5733
5970
|
const watchValue = watch(colLabel);
|
|
5734
|
-
return (jsx(Fragment, { children: jsx(Field, { label:
|
|
5971
|
+
return (jsx(Fragment, { children: jsx(Field, { label: formI18n.label(), required: isRequired, gridColumn: gridColumn ?? 'span 4', gridRow: gridRow ?? 'span 1', display: "grid", errorText: fieldError
|
|
5735
5972
|
? fieldError.includes('required')
|
|
5736
|
-
?
|
|
5973
|
+
? formI18n.required()
|
|
5737
5974
|
: fieldError
|
|
5738
5975
|
: undefined, invalid: !!fieldError, children: jsx(Textarea, { value: watchValue, onChange: (value) => setValue(colLabel, value) }) }) }));
|
|
5739
5976
|
};
|
|
@@ -5747,7 +5984,7 @@ meridiemLabel: _meridiemLabel = {
|
|
|
5747
5984
|
pm: 'pm',
|
|
5748
5985
|
},
|
|
5749
5986
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
5750
|
-
onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedDate, }) {
|
|
5987
|
+
onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedDate, portalled = true, }) {
|
|
5751
5988
|
// Generate time options (every 15 minutes)
|
|
5752
5989
|
const timeOptions = useMemo(() => {
|
|
5753
5990
|
const options = [];
|
|
@@ -5770,12 +6007,10 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
|
|
|
5770
6007
|
for (let h = 1; h <= 12; h++) {
|
|
5771
6008
|
for (let m = 0; m < 60; m += 15) {
|
|
5772
6009
|
const hour24 = mer === 'am' ? (h === 12 ? 0 : h) : h === 12 ? 12 : h + 12;
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
.format('HH:mmZ');
|
|
5778
|
-
const displayTime = dayjs(`1970-01-01T${timeStr}`, 'HH:mmZ').format('hh:mm a');
|
|
6010
|
+
// Format time directly without using dayjs with dummy dates
|
|
6011
|
+
const formattedHour = h.toString().padStart(2, '0');
|
|
6012
|
+
const formattedMinute = m.toString().padStart(2, '0');
|
|
6013
|
+
const displayTime = `${formattedHour}:${formattedMinute} ${mer}`;
|
|
5779
6014
|
// Filter out times that would result in negative duration (only when dates are the same)
|
|
5780
6015
|
if (startDateTime && selectedDate && shouldFilterByDate) {
|
|
5781
6016
|
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
@@ -5788,8 +6023,8 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
|
|
|
5788
6023
|
continue; // Skip this option as it would result in negative duration
|
|
5789
6024
|
}
|
|
5790
6025
|
}
|
|
5791
|
-
// Calculate
|
|
5792
|
-
let
|
|
6026
|
+
// Calculate duration if startTime is provided
|
|
6027
|
+
let durationText;
|
|
5793
6028
|
if (startDateTime && selectedDate) {
|
|
5794
6029
|
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
5795
6030
|
const optionDateTime = selectedDateObj
|
|
@@ -5802,21 +6037,30 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
|
|
|
5802
6037
|
const diffMs = optionDateTime.diff(startDateTime);
|
|
5803
6038
|
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
5804
6039
|
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
6040
|
+
const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
|
|
6041
|
+
if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
|
|
6042
|
+
let diffText = '';
|
|
6043
|
+
if (diffHours > 0) {
|
|
6044
|
+
diffText = `${diffHours}h ${diffMinutes}m`;
|
|
6045
|
+
}
|
|
6046
|
+
else if (diffMinutes > 0) {
|
|
6047
|
+
diffText = `${diffMinutes}m ${diffSeconds}s`;
|
|
6048
|
+
}
|
|
6049
|
+
else {
|
|
6050
|
+
diffText = `${diffSeconds}s`;
|
|
6051
|
+
}
|
|
6052
|
+
durationText = `+${diffText}`;
|
|
5810
6053
|
}
|
|
5811
6054
|
}
|
|
5812
6055
|
}
|
|
5813
6056
|
options.push({
|
|
5814
|
-
label,
|
|
6057
|
+
label: displayTime,
|
|
5815
6058
|
value: `${h}:${m.toString().padStart(2, '0')}:${mer}`,
|
|
5816
6059
|
hour: h,
|
|
5817
6060
|
minute: m,
|
|
5818
6061
|
meridiem: mer,
|
|
5819
6062
|
searchText: displayTime, // Use base time without duration for searching
|
|
6063
|
+
durationText,
|
|
5820
6064
|
});
|
|
5821
6065
|
}
|
|
5822
6066
|
}
|
|
@@ -5830,22 +6074,6 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
|
|
|
5830
6074
|
itemToValue: (item) => item.value,
|
|
5831
6075
|
filter: contains,
|
|
5832
6076
|
});
|
|
5833
|
-
// Track input mode vs display mode
|
|
5834
|
-
const [isInputMode, setIsInputMode] = useState(false);
|
|
5835
|
-
const [inputValue, setInputValue] = useState('');
|
|
5836
|
-
const inputRef = useRef(null);
|
|
5837
|
-
// Switch to display mode when value is selected
|
|
5838
|
-
useEffect(() => {
|
|
5839
|
-
if (hour !== null && minute !== null && meridiem !== null) {
|
|
5840
|
-
setIsInputMode(false);
|
|
5841
|
-
}
|
|
5842
|
-
}, [hour, minute, meridiem]);
|
|
5843
|
-
// Focus input when switching to input mode
|
|
5844
|
-
useEffect(() => {
|
|
5845
|
-
if (isInputMode && inputRef.current) {
|
|
5846
|
-
inputRef.current.focus();
|
|
5847
|
-
}
|
|
5848
|
-
}, [isInputMode]);
|
|
5849
6077
|
// Get current value string for combobox
|
|
5850
6078
|
const currentValue = useMemo(() => {
|
|
5851
6079
|
if (hour === null || minute === null || meridiem === null) {
|
|
@@ -5853,14 +6081,14 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
|
|
|
5853
6081
|
}
|
|
5854
6082
|
return `${hour}:${minute.toString().padStart(2, '0')}:${meridiem}`;
|
|
5855
6083
|
}, [hour, minute, meridiem]);
|
|
5856
|
-
//
|
|
5857
|
-
const
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
return
|
|
6084
|
+
// Calculate duration difference
|
|
6085
|
+
const durationDiff = useMemo(() => {
|
|
6086
|
+
if (!startTime ||
|
|
6087
|
+
!selectedDate ||
|
|
6088
|
+
hour === null ||
|
|
6089
|
+
minute === null ||
|
|
6090
|
+
meridiem === null) {
|
|
6091
|
+
return null;
|
|
5864
6092
|
}
|
|
5865
6093
|
const hour24 = meridiem === 'am'
|
|
5866
6094
|
? hour === 12
|
|
@@ -5869,45 +6097,42 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
|
|
|
5869
6097
|
: hour === 12
|
|
5870
6098
|
? 12
|
|
5871
6099
|
: hour + 12;
|
|
5872
|
-
const
|
|
5873
|
-
|
|
6100
|
+
const startDateObj = dayjs(startTime).tz(timezone);
|
|
6101
|
+
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
6102
|
+
const currentDateTime = selectedDateObj
|
|
5874
6103
|
.hour(hour24)
|
|
5875
6104
|
.minute(minute)
|
|
5876
|
-
.
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
: `${diffMinutes}m`;
|
|
5896
|
-
return `${timeDisplay} (+${diffText})`;
|
|
5897
|
-
}
|
|
5898
|
-
}
|
|
6105
|
+
.second(0)
|
|
6106
|
+
.millisecond(0);
|
|
6107
|
+
if (!startDateObj.isValid() || !currentDateTime.isValid()) {
|
|
6108
|
+
return null;
|
|
6109
|
+
}
|
|
6110
|
+
const diffMs = currentDateTime.diff(startDateObj);
|
|
6111
|
+
if (diffMs < 0) {
|
|
6112
|
+
return null;
|
|
6113
|
+
}
|
|
6114
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
6115
|
+
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
6116
|
+
const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
|
|
6117
|
+
if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
|
|
6118
|
+
let diffText = '';
|
|
6119
|
+
if (diffHours > 0) {
|
|
6120
|
+
diffText = `${diffHours}h ${diffMinutes}m`;
|
|
6121
|
+
}
|
|
6122
|
+
else if (diffMinutes > 0) {
|
|
6123
|
+
diffText = `${diffMinutes}m ${diffSeconds}s`;
|
|
5899
6124
|
}
|
|
6125
|
+
else {
|
|
6126
|
+
diffText = `${diffSeconds}s`;
|
|
6127
|
+
}
|
|
6128
|
+
return `+${diffText}`;
|
|
5900
6129
|
}
|
|
5901
|
-
return
|
|
5902
|
-
}, [hour, minute, meridiem,
|
|
5903
|
-
// Choose text based on mode
|
|
5904
|
-
const displayText = isInputMode ? inputModeText : displayModeText;
|
|
6130
|
+
return null;
|
|
6131
|
+
}, [hour, minute, meridiem, startTime, selectedDate, timezone]);
|
|
5905
6132
|
const handleClear = () => {
|
|
5906
6133
|
setHour(null);
|
|
5907
6134
|
setMinute(null);
|
|
5908
6135
|
setMeridiem(null);
|
|
5909
|
-
setIsInputMode(false);
|
|
5910
|
-
setInputValue('');
|
|
5911
6136
|
filter(''); // Reset filter to show all options
|
|
5912
6137
|
onChange({ hour: null, minute: null, meridiem: null });
|
|
5913
6138
|
};
|
|
@@ -5922,8 +6147,6 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
|
|
|
5922
6147
|
setHour(selectedOption.hour);
|
|
5923
6148
|
setMinute(selectedOption.minute);
|
|
5924
6149
|
setMeridiem(selectedOption.meridiem);
|
|
5925
|
-
setIsInputMode(false); // Switch to display mode
|
|
5926
|
-
setInputValue('');
|
|
5927
6150
|
filter(''); // Reset filter after selection
|
|
5928
6151
|
onChange({
|
|
5929
6152
|
hour: selectedOption.hour,
|
|
@@ -5932,41 +6155,16 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
|
|
|
5932
6155
|
});
|
|
5933
6156
|
}
|
|
5934
6157
|
};
|
|
5935
|
-
//
|
|
5936
|
-
const
|
|
5937
|
-
|
|
5938
|
-
e.preventDefault();
|
|
5939
|
-
const firstOption = collection.items[0];
|
|
5940
|
-
if (firstOption) {
|
|
5941
|
-
const selectedOption = timeOptions.find((opt) => opt.value === firstOption.value);
|
|
5942
|
-
if (selectedOption) {
|
|
5943
|
-
setHour(selectedOption.hour);
|
|
5944
|
-
setMinute(selectedOption.minute);
|
|
5945
|
-
setMeridiem(selectedOption.meridiem);
|
|
5946
|
-
setIsInputMode(false); // Switch to display mode
|
|
5947
|
-
setInputValue('');
|
|
5948
|
-
filter('');
|
|
5949
|
-
onChange({
|
|
5950
|
-
hour: selectedOption.hour,
|
|
5951
|
-
minute: selectedOption.minute,
|
|
5952
|
-
meridiem: selectedOption.meridiem,
|
|
5953
|
-
});
|
|
5954
|
-
}
|
|
5955
|
-
}
|
|
5956
|
-
}
|
|
5957
|
-
};
|
|
5958
|
-
const handleInputValueChange = (details) => {
|
|
5959
|
-
const inputValue = details.inputValue.trim();
|
|
5960
|
-
setInputValue(inputValue);
|
|
5961
|
-
setIsInputMode(true); // Switch to input mode
|
|
6158
|
+
// Parse input value and update state
|
|
6159
|
+
const parseAndCommitInput = (value) => {
|
|
6160
|
+
const trimmedValue = value.trim();
|
|
5962
6161
|
// Filter the collection based on input
|
|
5963
|
-
filter(
|
|
5964
|
-
if (!
|
|
5965
|
-
setIsInputMode(false);
|
|
6162
|
+
filter(trimmedValue);
|
|
6163
|
+
if (!trimmedValue) {
|
|
5966
6164
|
return;
|
|
5967
6165
|
}
|
|
5968
6166
|
// Try to parse custom input using explicit regex patterns
|
|
5969
|
-
const normalized =
|
|
6167
|
+
const normalized = trimmedValue.toLowerCase().replace(/\s+/g, '');
|
|
5970
6168
|
// Pattern 1: 12-hour format with meridiem (e.g., "930pm", "1230am", "9:30pm", "12:30am")
|
|
5971
6169
|
// Matches: 1-2 digits hour, optional colon, 2 digits minute, am/pm
|
|
5972
6170
|
const pattern12HourWithMeridiem = /^(\d{1,2}):?(\d{2})(am|pm)$/;
|
|
@@ -5977,29 +6175,43 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
|
|
|
5977
6175
|
const parsedMeridiem = match12Hour[3];
|
|
5978
6176
|
// Validate hour (1-12)
|
|
5979
6177
|
if (parsedHour < 1 || parsedHour > 12) {
|
|
6178
|
+
// Parse failed, select first result
|
|
6179
|
+
selectFirstResult();
|
|
5980
6180
|
return;
|
|
5981
6181
|
}
|
|
5982
6182
|
// Validate minute (0-59)
|
|
5983
|
-
|
|
6183
|
+
if (parsedMinute < 0 || parsedMinute > 59) {
|
|
6184
|
+
// Parse failed, select first result
|
|
6185
|
+
selectFirstResult();
|
|
6186
|
+
return;
|
|
6187
|
+
}
|
|
5984
6188
|
setHour(parsedHour);
|
|
5985
|
-
setMinute(
|
|
6189
|
+
setMinute(parsedMinute);
|
|
5986
6190
|
setMeridiem(parsedMeridiem);
|
|
5987
6191
|
onChange({
|
|
5988
6192
|
hour: parsedHour,
|
|
5989
|
-
minute:
|
|
6193
|
+
minute: parsedMinute,
|
|
5990
6194
|
meridiem: parsedMeridiem,
|
|
5991
6195
|
});
|
|
5992
6196
|
return;
|
|
5993
6197
|
}
|
|
5994
6198
|
// Pattern 2: 24-hour format (e.g., "2130", "09:30", "21:30")
|
|
5995
6199
|
// Matches: 1-2 digits hour, optional colon, 2 digits minute
|
|
5996
|
-
const pattern24Hour = /^(\d{2}):?(\d{2})$/;
|
|
6200
|
+
const pattern24Hour = /^(\d{1,2}):?(\d{2})$/;
|
|
5997
6201
|
const match24Hour = normalized.match(pattern24Hour);
|
|
5998
6202
|
if (match24Hour) {
|
|
5999
6203
|
let parsedHour = parseInt(match24Hour[1], 10);
|
|
6000
6204
|
const parsedMinute = parseInt(match24Hour[2], 10);
|
|
6001
6205
|
// Validate hour (0-23)
|
|
6002
|
-
if (parsedHour > 23) {
|
|
6206
|
+
if (parsedHour < 0 || parsedHour > 23) {
|
|
6207
|
+
// Parse failed, select first result
|
|
6208
|
+
selectFirstResult();
|
|
6209
|
+
return;
|
|
6210
|
+
}
|
|
6211
|
+
// Validate minute (0-59)
|
|
6212
|
+
if (parsedMinute < 0 || parsedMinute > 59) {
|
|
6213
|
+
// Parse failed, select first result
|
|
6214
|
+
selectFirstResult();
|
|
6003
6215
|
return;
|
|
6004
6216
|
}
|
|
6005
6217
|
// Convert 24-hour to 12-hour format
|
|
@@ -6019,32 +6231,72 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
|
|
|
6019
6231
|
else {
|
|
6020
6232
|
parsedMeridiem = 'am';
|
|
6021
6233
|
}
|
|
6022
|
-
// Validate minute (0-59)
|
|
6023
|
-
const validMinute = parsedMinute > 59 ? 0 : parsedMinute;
|
|
6024
6234
|
setHour(parsedHour);
|
|
6025
|
-
setMinute(
|
|
6235
|
+
setMinute(parsedMinute);
|
|
6026
6236
|
setMeridiem(parsedMeridiem);
|
|
6027
6237
|
onChange({
|
|
6028
6238
|
hour: parsedHour,
|
|
6029
|
-
minute:
|
|
6239
|
+
minute: parsedMinute,
|
|
6030
6240
|
meridiem: parsedMeridiem,
|
|
6031
6241
|
});
|
|
6032
6242
|
return;
|
|
6033
6243
|
}
|
|
6244
|
+
// Parse failed, select first result
|
|
6245
|
+
selectFirstResult();
|
|
6246
|
+
};
|
|
6247
|
+
// Select first result from filtered collection
|
|
6248
|
+
const selectFirstResult = () => {
|
|
6249
|
+
if (collection.items.length > 0) {
|
|
6250
|
+
const firstItem = collection.items[0];
|
|
6251
|
+
setHour(firstItem.hour);
|
|
6252
|
+
setMinute(firstItem.minute);
|
|
6253
|
+
setMeridiem(firstItem.meridiem);
|
|
6254
|
+
filter(''); // Reset filter after selection
|
|
6255
|
+
onChange({
|
|
6256
|
+
hour: firstItem.hour,
|
|
6257
|
+
minute: firstItem.minute,
|
|
6258
|
+
meridiem: firstItem.meridiem,
|
|
6259
|
+
});
|
|
6260
|
+
}
|
|
6261
|
+
};
|
|
6262
|
+
const handleInputValueChange = (details) => {
|
|
6263
|
+
// Filter the collection based on input, but don't parse yet
|
|
6264
|
+
filter(details.inputValue);
|
|
6265
|
+
};
|
|
6266
|
+
const handleFocus = (e) => {
|
|
6267
|
+
// Select all text when focusing
|
|
6268
|
+
e.target.select();
|
|
6269
|
+
};
|
|
6270
|
+
const handleBlur = (e) => {
|
|
6271
|
+
// Parse and commit the input value when losing focus
|
|
6272
|
+
const inputValue = e.target.value;
|
|
6273
|
+
if (inputValue) {
|
|
6274
|
+
parseAndCommitInput(inputValue);
|
|
6275
|
+
}
|
|
6276
|
+
};
|
|
6277
|
+
const handleKeyDown = (e) => {
|
|
6278
|
+
// Commit input on Enter key
|
|
6279
|
+
if (e.key === 'Enter') {
|
|
6280
|
+
e.preventDefault();
|
|
6281
|
+
const inputValue = e.currentTarget.value;
|
|
6282
|
+
if (inputValue) {
|
|
6283
|
+
parseAndCommitInput(inputValue);
|
|
6284
|
+
}
|
|
6285
|
+
// Blur the input
|
|
6286
|
+
e.currentTarget?.blur();
|
|
6287
|
+
}
|
|
6034
6288
|
};
|
|
6035
|
-
return (
|
|
6036
|
-
e.stopPropagation();
|
|
6037
|
-
setIsInputMode(true);
|
|
6038
|
-
} })] })), isInputMode && (jsxs(Combobox.IndicatorGroup, { children: [jsx(Combobox.ClearTrigger, {}), jsx(Combobox.Trigger, {})] }))] }), jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [jsx(Combobox.Empty, { children: "No time found" }), collection.items.map((item) => (jsxs(Combobox.Item, { item: item, children: [item.label, jsx(Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "ghost", children: jsx(Icon, { children: jsx(MdCancel, {}) }) })] }));
|
|
6289
|
+
return (jsx(Flex, { direction: "column", gap: 3, children: jsxs(Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxs(Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", openOnClick: true, flex: 1, children: [jsxs(Combobox.Control, { children: [jsx(InputGroup$1, { startElement: jsx(BsClock, {}), children: jsx(Combobox.Input, { placeholder: "hh:mm a", onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsx(Combobox.IndicatorGroup, { children: jsx(Combobox.Trigger, {}) })] }), jsx(Portal, { disabled: !portalled, children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [jsx(Combobox.Empty, { children: "No time found" }), collection.items.map((item) => (jsxs(Combobox.Item, { item: item, children: [jsxs(Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsx(Text, { flex: 1, children: item.label }), item.durationText && (jsx(Tag$1.Root, { size: "sm", colorPalette: "blue", children: jsx(Tag$1.Label, { children: item.durationText }) }))] }), jsx(Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: durationDiff }) })), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "ghost", children: jsx(Icon, { children: jsx(MdCancel, {}) }) })] }) }));
|
|
6039
6290
|
}
|
|
6040
6291
|
|
|
6041
6292
|
dayjs.extend(timezone);
|
|
6042
6293
|
const TimePicker = ({ column, schema, prefix }) => {
|
|
6043
6294
|
const { watch, formState: { errors }, setValue, } = useFormContext();
|
|
6044
|
-
const {
|
|
6295
|
+
const { timezone, insideDialog } = useSchemaContext();
|
|
6045
6296
|
const { required, gridColumn = 'span 12', gridRow = 'span 1', timeFormat = 'HH:mm:ssZ', displayTimeFormat = 'hh:mm A', } = schema;
|
|
6046
6297
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
6047
6298
|
const colLabel = `${prefix}${column}`;
|
|
6299
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
6048
6300
|
const [open, setOpen] = useState(false);
|
|
6049
6301
|
const value = watch(colLabel);
|
|
6050
6302
|
const displayedTime = dayjs(`1970-01-01T${value}`).tz(timezone).isValid()
|
|
@@ -6099,10 +6351,8 @@ const TimePicker = ({ column, schema, prefix }) => {
|
|
|
6099
6351
|
const timeString = getTimeString(newHour, newMinute, newMeridiem);
|
|
6100
6352
|
setValue(colLabel, timeString, { shouldValidate: true, shouldDirty: true });
|
|
6101
6353
|
};
|
|
6102
|
-
return (jsx(Field, { label:
|
|
6103
|
-
gridRow, errorText: errors[`${colLabel}`]
|
|
6104
|
-
? translate.t(removeIndex(`${colLabel}.field_required`))
|
|
6105
|
-
: undefined, invalid: !!errors[colLabel], children: jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsx(Popover.Trigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
|
|
6354
|
+
return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
6355
|
+
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsx(Popover.Trigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
|
|
6106
6356
|
setOpen(true);
|
|
6107
6357
|
}, justifyContent: 'start', children: [jsx(IoMdClock, {}), !!value ? `${displayedTime}` : ''] }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { maxH: "70vh", overflowY: "auto", children: jsx(Popover.Body, { overflow: "visible", children: jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, meridiemLabel: {
|
|
6108
6358
|
am: translate.t(`common.am`, { defaultValue: 'AM' }),
|
|
@@ -6115,21 +6365,148 @@ const TimePicker = ({ column, schema, prefix }) => {
|
|
|
6115
6365
|
|
|
6116
6366
|
dayjs.extend(utc);
|
|
6117
6367
|
dayjs.extend(timezone);
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6368
|
+
dayjs.extend(customParseFormat);
|
|
6369
|
+
function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateFormat = 'YYYY-MM-DD', displayFormat = 'YYYY-MM-DD', labels = {
|
|
6370
|
+
monthNamesShort: [
|
|
6371
|
+
'Jan',
|
|
6372
|
+
'Feb',
|
|
6373
|
+
'Mar',
|
|
6374
|
+
'Apr',
|
|
6375
|
+
'May',
|
|
6376
|
+
'Jun',
|
|
6377
|
+
'Jul',
|
|
6378
|
+
'Aug',
|
|
6379
|
+
'Sep',
|
|
6380
|
+
'Oct',
|
|
6381
|
+
'Nov',
|
|
6382
|
+
'Dec',
|
|
6383
|
+
],
|
|
6384
|
+
weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
|
6385
|
+
backButtonLabel: 'Back',
|
|
6386
|
+
forwardButtonLabel: 'Next',
|
|
6387
|
+
}, timezone = 'Asia/Hong_Kong', minDate, maxDate, firstDayOfWeek, showOutsideDays, monthsToDisplay = 1, insideDialog = false, readOnly = false, }) {
|
|
6388
|
+
const [open, setOpen] = useState(false);
|
|
6389
|
+
const [inputValue, setInputValue] = useState('');
|
|
6390
|
+
// Update input value when prop value changes
|
|
6391
|
+
useEffect(() => {
|
|
6392
|
+
if (value) {
|
|
6393
|
+
const formatted = typeof value === 'string'
|
|
6394
|
+
? dayjs(value).tz(timezone).isValid()
|
|
6395
|
+
? dayjs(value).tz(timezone).format(displayFormat)
|
|
6396
|
+
: ''
|
|
6397
|
+
: dayjs(value).tz(timezone).format(displayFormat);
|
|
6398
|
+
setInputValue(formatted);
|
|
6399
|
+
}
|
|
6400
|
+
else {
|
|
6401
|
+
setInputValue('');
|
|
6402
|
+
}
|
|
6403
|
+
}, [value, displayFormat, timezone]);
|
|
6404
|
+
// Convert value to Date object for DatePicker
|
|
6405
|
+
const selectedDate = value
|
|
6406
|
+
? typeof value === 'string'
|
|
6407
|
+
? dayjs(value).tz(timezone).isValid()
|
|
6408
|
+
? dayjs(value).tz(timezone).toDate()
|
|
6409
|
+
: new Date()
|
|
6410
|
+
: value
|
|
6411
|
+
: new Date();
|
|
6412
|
+
// Shared function to parse and validate input value
|
|
6413
|
+
const parseAndValidateInput = (inputVal) => {
|
|
6414
|
+
// If empty, clear the value
|
|
6415
|
+
if (!inputVal.trim()) {
|
|
6416
|
+
onChange?.(undefined);
|
|
6417
|
+
setInputValue('');
|
|
6418
|
+
return;
|
|
6419
|
+
}
|
|
6420
|
+
// Try parsing with displayFormat first
|
|
6421
|
+
let parsedDate = dayjs(inputVal, displayFormat, true);
|
|
6422
|
+
// If that fails, try common date formats
|
|
6423
|
+
if (!parsedDate.isValid()) {
|
|
6424
|
+
parsedDate = dayjs(inputVal);
|
|
6425
|
+
}
|
|
6426
|
+
// If still invalid, try parsing with dateFormat
|
|
6427
|
+
if (!parsedDate.isValid()) {
|
|
6428
|
+
parsedDate = dayjs(inputVal, dateFormat, true);
|
|
6429
|
+
}
|
|
6430
|
+
// If valid, check constraints and update
|
|
6431
|
+
if (parsedDate.isValid()) {
|
|
6432
|
+
const dateObj = parsedDate.tz(timezone).toDate();
|
|
6433
|
+
// Check min/max constraints
|
|
6434
|
+
if (minDate && dateObj < minDate) {
|
|
6435
|
+
// Invalid: before minDate, reset to prop value
|
|
6436
|
+
resetToPropValue();
|
|
6437
|
+
return;
|
|
6438
|
+
}
|
|
6439
|
+
if (maxDate && dateObj > maxDate) {
|
|
6440
|
+
// Invalid: after maxDate, reset to prop value
|
|
6441
|
+
resetToPropValue();
|
|
6442
|
+
return;
|
|
6443
|
+
}
|
|
6444
|
+
// Valid date - format and update
|
|
6445
|
+
const formattedDate = parsedDate.tz(timezone).format(dateFormat);
|
|
6446
|
+
const formattedDisplay = parsedDate.tz(timezone).format(displayFormat);
|
|
6447
|
+
onChange?.(formattedDate);
|
|
6448
|
+
setInputValue(formattedDisplay);
|
|
6449
|
+
}
|
|
6450
|
+
else {
|
|
6451
|
+
// Invalid date - reset to prop value
|
|
6452
|
+
resetToPropValue();
|
|
6453
|
+
}
|
|
6454
|
+
};
|
|
6455
|
+
// Helper function to reset input to prop value
|
|
6456
|
+
const resetToPropValue = () => {
|
|
6457
|
+
if (value) {
|
|
6458
|
+
const formatted = typeof value === 'string'
|
|
6459
|
+
? dayjs(value).tz(timezone).isValid()
|
|
6460
|
+
? dayjs(value).tz(timezone).format(displayFormat)
|
|
6461
|
+
: ''
|
|
6462
|
+
: dayjs(value).tz(timezone).format(displayFormat);
|
|
6463
|
+
setInputValue(formatted);
|
|
6464
|
+
}
|
|
6465
|
+
else {
|
|
6466
|
+
setInputValue('');
|
|
6467
|
+
}
|
|
6468
|
+
};
|
|
6469
|
+
const handleInputChange = (e) => {
|
|
6470
|
+
// Only update the input value, don't parse yet
|
|
6471
|
+
setInputValue(e.target.value);
|
|
6472
|
+
};
|
|
6473
|
+
const handleInputBlur = () => {
|
|
6474
|
+
// Parse and validate when input loses focus
|
|
6475
|
+
parseAndValidateInput(inputValue);
|
|
6476
|
+
};
|
|
6477
|
+
const handleKeyDown = (e) => {
|
|
6478
|
+
// Parse and validate when Enter is pressed
|
|
6479
|
+
if (e.key === 'Enter') {
|
|
6480
|
+
e.preventDefault();
|
|
6481
|
+
parseAndValidateInput(inputValue);
|
|
6482
|
+
}
|
|
6483
|
+
};
|
|
6484
|
+
const handleDateSelected = ({ date }) => {
|
|
6485
|
+
const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
|
|
6486
|
+
onChange?.(formattedDate);
|
|
6487
|
+
setOpen(false);
|
|
6488
|
+
};
|
|
6489
|
+
const datePickerContent = (jsx(DatePicker$1, { selected: selectedDate, onDateSelected: handleDateSelected, labels: labels, minDate: minDate, maxDate: maxDate, firstDayOfWeek: firstDayOfWeek, showOutsideDays: showOutsideDays, monthsToDisplay: monthsToDisplay }));
|
|
6490
|
+
return (jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(InputGroup, { endElement: jsx(Popover.Trigger, { asChild: true, children: jsx(IconButton, { variant: "ghost", size: "2xs", "aria-label": "Open calendar", onClick: () => setOpen(true), children: jsx(Icon, { children: jsx(MdDateRange, {}) }) }) }), children: jsx(Input, { value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onKeyDown: handleKeyDown, placeholder: placeholder, readOnly: readOnly }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) }) }))] }));
|
|
6491
|
+
}
|
|
6492
|
+
|
|
6493
|
+
dayjs.extend(utc);
|
|
6494
|
+
dayjs.extend(timezone);
|
|
6495
|
+
function IsoTimePicker({ hour, setHour, minute, setMinute, second, setSecond,
|
|
6496
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
6497
|
+
onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, }) {
|
|
6498
|
+
// Generate time options (every 15 minutes, seconds always 0)
|
|
6499
|
+
const timeOptions = useMemo(() => {
|
|
6500
|
+
const options = [];
|
|
6501
|
+
// Get start time for comparison if provided
|
|
6502
|
+
let startDateTime = null;
|
|
6503
|
+
let shouldFilterByDate = false;
|
|
6504
|
+
if (startTime && selectedDate) {
|
|
6505
|
+
const startDateObj = dayjs(startTime).tz(timezone);
|
|
6506
|
+
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
6507
|
+
if (startDateObj.isValid() && selectedDateObj.isValid()) {
|
|
6508
|
+
startDateTime = startDateObj;
|
|
6509
|
+
// Only filter if dates are the same
|
|
6133
6510
|
shouldFilterByDate =
|
|
6134
6511
|
startDateObj.format('YYYY-MM-DD') ===
|
|
6135
6512
|
selectedDateObj.format('YYYY-MM-DD');
|
|
@@ -6152,8 +6529,8 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
|
|
|
6152
6529
|
continue; // Skip this option as it would result in negative duration
|
|
6153
6530
|
}
|
|
6154
6531
|
}
|
|
6155
|
-
// Calculate
|
|
6156
|
-
let
|
|
6532
|
+
// Calculate duration if startTime is provided
|
|
6533
|
+
let durationText;
|
|
6157
6534
|
if (startDateTime && selectedDate) {
|
|
6158
6535
|
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
6159
6536
|
const optionDateTime = selectedDateObj
|
|
@@ -6178,17 +6555,18 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
|
|
|
6178
6555
|
else {
|
|
6179
6556
|
diffText = `${diffSeconds}s`;
|
|
6180
6557
|
}
|
|
6181
|
-
|
|
6558
|
+
durationText = `+${diffText}`;
|
|
6182
6559
|
}
|
|
6183
6560
|
}
|
|
6184
6561
|
}
|
|
6185
6562
|
options.push({
|
|
6186
|
-
label,
|
|
6563
|
+
label: timeDisplay,
|
|
6187
6564
|
value: `${h}:${m}:0`,
|
|
6188
6565
|
hour: h,
|
|
6189
6566
|
minute: m,
|
|
6190
6567
|
second: 0,
|
|
6191
6568
|
searchText: timeDisplay, // Use base time without duration for searching
|
|
6569
|
+
durationText,
|
|
6192
6570
|
});
|
|
6193
6571
|
}
|
|
6194
6572
|
}
|
|
@@ -6208,46 +6586,46 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
|
|
|
6208
6586
|
}
|
|
6209
6587
|
return `${hour}:${minute}:${second}`;
|
|
6210
6588
|
}, [hour, minute, second]);
|
|
6211
|
-
//
|
|
6212
|
-
const
|
|
6213
|
-
if (
|
|
6214
|
-
|
|
6589
|
+
// Calculate duration difference
|
|
6590
|
+
const durationDiff = useMemo(() => {
|
|
6591
|
+
if (!startTime ||
|
|
6592
|
+
!selectedDate ||
|
|
6593
|
+
hour === null ||
|
|
6594
|
+
minute === null ||
|
|
6595
|
+
second === null) {
|
|
6596
|
+
return null;
|
|
6215
6597
|
}
|
|
6216
|
-
const
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
diffText = `${diffMinutes}m ${diffSeconds}s`;
|
|
6241
|
-
}
|
|
6242
|
-
else {
|
|
6243
|
-
diffText = `${diffSeconds}s`;
|
|
6244
|
-
}
|
|
6245
|
-
return `${timeDisplay} (+${diffText})`;
|
|
6246
|
-
}
|
|
6247
|
-
}
|
|
6598
|
+
const startDateObj = dayjs(startTime).tz(timezone);
|
|
6599
|
+
const selectedDateObj = dayjs(selectedDate).tz(timezone);
|
|
6600
|
+
const currentDateTime = selectedDateObj
|
|
6601
|
+
.hour(hour)
|
|
6602
|
+
.minute(minute)
|
|
6603
|
+
.second(second ?? 0)
|
|
6604
|
+
.millisecond(0);
|
|
6605
|
+
if (!startDateObj.isValid() || !currentDateTime.isValid()) {
|
|
6606
|
+
return null;
|
|
6607
|
+
}
|
|
6608
|
+
const diffMs = currentDateTime.diff(startDateObj);
|
|
6609
|
+
if (diffMs < 0) {
|
|
6610
|
+
return null;
|
|
6611
|
+
}
|
|
6612
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
6613
|
+
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
6614
|
+
const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
|
|
6615
|
+
if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
|
|
6616
|
+
let diffText = '';
|
|
6617
|
+
if (diffHours > 0) {
|
|
6618
|
+
diffText = `${diffHours}h ${diffMinutes}m`;
|
|
6619
|
+
}
|
|
6620
|
+
else if (diffMinutes > 0) {
|
|
6621
|
+
diffText = `${diffMinutes}m ${diffSeconds}s`;
|
|
6248
6622
|
}
|
|
6623
|
+
else {
|
|
6624
|
+
diffText = `${diffSeconds}s`;
|
|
6625
|
+
}
|
|
6626
|
+
return `+${diffText}`;
|
|
6249
6627
|
}
|
|
6250
|
-
return
|
|
6628
|
+
return null;
|
|
6251
6629
|
}, [hour, minute, second, startTime, selectedDate, timezone]);
|
|
6252
6630
|
const handleClear = () => {
|
|
6253
6631
|
setHour(null);
|
|
@@ -6275,16 +6653,17 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
|
|
|
6275
6653
|
});
|
|
6276
6654
|
}
|
|
6277
6655
|
};
|
|
6278
|
-
|
|
6279
|
-
|
|
6656
|
+
// Parse input value and update state
|
|
6657
|
+
const parseAndCommitInput = (value) => {
|
|
6658
|
+
const trimmedValue = value.trim();
|
|
6280
6659
|
// Filter the collection based on input
|
|
6281
|
-
filter(
|
|
6282
|
-
if (!
|
|
6660
|
+
filter(trimmedValue);
|
|
6661
|
+
if (!trimmedValue) {
|
|
6283
6662
|
return;
|
|
6284
6663
|
}
|
|
6285
6664
|
// Parse HH:mm:ss or HH:mm format
|
|
6286
6665
|
const timePattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
|
|
6287
|
-
const match =
|
|
6666
|
+
const match = trimmedValue.match(timePattern);
|
|
6288
6667
|
if (match) {
|
|
6289
6668
|
const parsedHour = parseInt(match[1], 10);
|
|
6290
6669
|
const parsedMinute = parseInt(match[2], 10);
|
|
@@ -6304,11 +6683,12 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
|
|
|
6304
6683
|
minute: parsedMinute,
|
|
6305
6684
|
second: parsedSecond,
|
|
6306
6685
|
});
|
|
6686
|
+
return;
|
|
6307
6687
|
}
|
|
6308
6688
|
}
|
|
6309
6689
|
else {
|
|
6310
6690
|
// Try to parse formats like "123045" (HHmmss) or "1230" (HHmm)
|
|
6311
|
-
const numbersOnly =
|
|
6691
|
+
const numbersOnly = trimmedValue.replace(/[^0-9]/g, '');
|
|
6312
6692
|
if (numbersOnly.length >= 4) {
|
|
6313
6693
|
const parsedHour = parseInt(numbersOnly.slice(0, 2), 10);
|
|
6314
6694
|
const parsedMinute = parseInt(numbersOnly.slice(2, 4), 10);
|
|
@@ -6328,11 +6708,56 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
|
|
|
6328
6708
|
minute: parsedMinute,
|
|
6329
6709
|
second: parsedSecond,
|
|
6330
6710
|
});
|
|
6711
|
+
return;
|
|
6331
6712
|
}
|
|
6332
6713
|
}
|
|
6333
6714
|
}
|
|
6715
|
+
// Parse failed, select first result
|
|
6716
|
+
selectFirstResult();
|
|
6717
|
+
};
|
|
6718
|
+
// Select first result from filtered collection
|
|
6719
|
+
const selectFirstResult = () => {
|
|
6720
|
+
if (collection.items.length > 0) {
|
|
6721
|
+
const firstItem = collection.items[0];
|
|
6722
|
+
setHour(firstItem.hour);
|
|
6723
|
+
setMinute(firstItem.minute);
|
|
6724
|
+
setSecond(firstItem.second);
|
|
6725
|
+
filter(''); // Reset filter after selection
|
|
6726
|
+
onChange({
|
|
6727
|
+
hour: firstItem.hour,
|
|
6728
|
+
minute: firstItem.minute,
|
|
6729
|
+
second: firstItem.second,
|
|
6730
|
+
});
|
|
6731
|
+
}
|
|
6732
|
+
};
|
|
6733
|
+
const handleInputValueChange = (details) => {
|
|
6734
|
+
// Filter the collection based on input, but don't parse yet
|
|
6735
|
+
filter(details.inputValue);
|
|
6736
|
+
};
|
|
6737
|
+
const handleFocus = (e) => {
|
|
6738
|
+
// Select all text when focusing
|
|
6739
|
+
e.target.select();
|
|
6740
|
+
};
|
|
6741
|
+
const handleBlur = (e) => {
|
|
6742
|
+
// Parse and commit the input value when losing focus
|
|
6743
|
+
const inputValue = e.target.value;
|
|
6744
|
+
if (inputValue) {
|
|
6745
|
+
parseAndCommitInput(inputValue);
|
|
6746
|
+
}
|
|
6747
|
+
};
|
|
6748
|
+
const handleKeyDown = (e) => {
|
|
6749
|
+
// Commit input on Enter key
|
|
6750
|
+
if (e.key === 'Enter') {
|
|
6751
|
+
e.preventDefault();
|
|
6752
|
+
const inputValue = e.currentTarget.value;
|
|
6753
|
+
if (inputValue) {
|
|
6754
|
+
parseAndCommitInput(inputValue);
|
|
6755
|
+
}
|
|
6756
|
+
// Blur the input
|
|
6757
|
+
e.currentTarget?.blur();
|
|
6758
|
+
}
|
|
6334
6759
|
};
|
|
6335
|
-
return (jsx(Flex, { direction: "column", gap: 3, children: jsxs(
|
|
6760
|
+
return (jsx(Flex, { direction: "column", gap: 3, children: jsxs(Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxs(Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", openOnClick: true, flex: 1, children: [jsxs(Combobox.Control, { children: [jsx(InputGroup$1, { startElement: jsx(BsClock, {}), children: jsx(Combobox.Input, { placeholder: "HH:mm:ss", onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsx(Combobox.IndicatorGroup, { children: jsx(Combobox.Trigger, {}) })] }), jsx(Portal, { disabled: !portalled, children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [jsx(Combobox.Empty, { children: "No time found" }), collection.items.map((item) => (jsxs(Combobox.Item, { item: item, children: [jsxs(Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsx(Text, { flex: 1, children: item.label }), item.durationText && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: item.durationText }) }))] }), jsx(Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: durationDiff }) })), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "ghost", children: jsx(Icon, { children: jsx(MdCancel, {}) }) })] }) }));
|
|
6336
6761
|
}
|
|
6337
6762
|
|
|
6338
6763
|
dayjs.extend(utc);
|
|
@@ -6355,30 +6780,193 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
|
|
|
6355
6780
|
weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
|
6356
6781
|
backButtonLabel: 'Back',
|
|
6357
6782
|
forwardButtonLabel: 'Next',
|
|
6358
|
-
}, timezone = 'Asia/Hong_Kong', startTime, }) {
|
|
6359
|
-
|
|
6783
|
+
}, timezone = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, }) {
|
|
6784
|
+
console.log('[DateTimePicker] Component initialized with props:', {
|
|
6785
|
+
value,
|
|
6786
|
+
format,
|
|
6787
|
+
showSeconds,
|
|
6788
|
+
timezone,
|
|
6789
|
+
startTime,
|
|
6790
|
+
minDate,
|
|
6791
|
+
maxDate,
|
|
6792
|
+
});
|
|
6793
|
+
// Initialize selectedDate from value prop, converting ISO to YYYY-MM-DD format
|
|
6794
|
+
const getDateString = useCallback((val) => {
|
|
6795
|
+
if (!val)
|
|
6796
|
+
return '';
|
|
6797
|
+
const dateObj = dayjs(val).tz(timezone);
|
|
6798
|
+
return dateObj.isValid() ? dateObj.format('YYYY-MM-DD') : '';
|
|
6799
|
+
}, [timezone]);
|
|
6800
|
+
const [selectedDate, setSelectedDate] = useState(getDateString(value));
|
|
6801
|
+
// Helper to get time values from value prop with timezone
|
|
6802
|
+
const getTimeFromValue = useCallback((val) => {
|
|
6803
|
+
console.log('[DateTimePicker] getTimeFromValue called:', {
|
|
6804
|
+
val,
|
|
6805
|
+
timezone,
|
|
6806
|
+
showSeconds,
|
|
6807
|
+
});
|
|
6808
|
+
if (!val) {
|
|
6809
|
+
console.log('[DateTimePicker] No value provided, returning nulls');
|
|
6810
|
+
return {
|
|
6811
|
+
hour12: null,
|
|
6812
|
+
minute: null,
|
|
6813
|
+
meridiem: null,
|
|
6814
|
+
hour24: null,
|
|
6815
|
+
second: null,
|
|
6816
|
+
};
|
|
6817
|
+
}
|
|
6818
|
+
const dateObj = dayjs(val).tz(timezone);
|
|
6819
|
+
console.log('[DateTimePicker] Parsed date object:', {
|
|
6820
|
+
original: val,
|
|
6821
|
+
timezone,
|
|
6822
|
+
isValid: dateObj.isValid(),
|
|
6823
|
+
formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
|
|
6824
|
+
hour24: dateObj.hour(),
|
|
6825
|
+
minute: dateObj.minute(),
|
|
6826
|
+
second: dateObj.second(),
|
|
6827
|
+
});
|
|
6828
|
+
if (!dateObj.isValid()) {
|
|
6829
|
+
console.log('[DateTimePicker] Invalid date object, returning nulls');
|
|
6830
|
+
return {
|
|
6831
|
+
hour12: null,
|
|
6832
|
+
minute: null,
|
|
6833
|
+
meridiem: null,
|
|
6834
|
+
hour24: null,
|
|
6835
|
+
second: null,
|
|
6836
|
+
};
|
|
6837
|
+
}
|
|
6838
|
+
const hour24Value = dateObj.hour();
|
|
6839
|
+
const hour12Value = hour24Value % 12 || 12;
|
|
6840
|
+
const minuteValue = dateObj.minute();
|
|
6841
|
+
const meridiemValue = hour24Value >= 12 ? 'pm' : 'am';
|
|
6842
|
+
const secondValue = showSeconds ? dateObj.second() : null;
|
|
6843
|
+
const result = {
|
|
6844
|
+
hour12: hour12Value,
|
|
6845
|
+
minute: minuteValue,
|
|
6846
|
+
meridiem: meridiemValue,
|
|
6847
|
+
hour24: hour24Value,
|
|
6848
|
+
second: secondValue,
|
|
6849
|
+
};
|
|
6850
|
+
console.log('[DateTimePicker] Extracted time values:', result);
|
|
6851
|
+
return result;
|
|
6852
|
+
}, [timezone, showSeconds]);
|
|
6853
|
+
const initialTime = getTimeFromValue(value);
|
|
6854
|
+
console.log('[DateTimePicker] Initial time from value:', {
|
|
6855
|
+
value,
|
|
6856
|
+
initialTime,
|
|
6857
|
+
});
|
|
6360
6858
|
// Time state for 12-hour format
|
|
6361
|
-
const [hour12, setHour12] = useState(
|
|
6362
|
-
const [minute, setMinute] = useState(
|
|
6363
|
-
const [meridiem, setMeridiem] = useState(
|
|
6859
|
+
const [hour12, setHour12] = useState(initialTime.hour12);
|
|
6860
|
+
const [minute, setMinute] = useState(initialTime.minute);
|
|
6861
|
+
const [meridiem, setMeridiem] = useState(initialTime.meridiem);
|
|
6364
6862
|
// Time state for 24-hour format
|
|
6365
|
-
const [hour24, setHour24] = useState(
|
|
6366
|
-
const [second, setSecond] = useState(
|
|
6863
|
+
const [hour24, setHour24] = useState(initialTime.hour24);
|
|
6864
|
+
const [second, setSecond] = useState(initialTime.second);
|
|
6865
|
+
// Sync selectedDate and time states when value prop changes
|
|
6866
|
+
useEffect(() => {
|
|
6867
|
+
console.log('[DateTimePicker] useEffect triggered - value changed:', {
|
|
6868
|
+
value,
|
|
6869
|
+
timezone,
|
|
6870
|
+
format,
|
|
6871
|
+
});
|
|
6872
|
+
// If value is null, undefined, or invalid, clear all fields
|
|
6873
|
+
if (!value || value === null || value === undefined) {
|
|
6874
|
+
console.log('[DateTimePicker] Value is null/undefined, clearing all fields');
|
|
6875
|
+
setSelectedDate('');
|
|
6876
|
+
setHour12(null);
|
|
6877
|
+
setMinute(null);
|
|
6878
|
+
setMeridiem(null);
|
|
6879
|
+
setHour24(null);
|
|
6880
|
+
setSecond(null);
|
|
6881
|
+
return;
|
|
6882
|
+
}
|
|
6883
|
+
// Check if value is valid
|
|
6884
|
+
const dateObj = dayjs(value).tz(timezone);
|
|
6885
|
+
if (!dateObj.isValid()) {
|
|
6886
|
+
console.log('[DateTimePicker] Invalid value, clearing all fields');
|
|
6887
|
+
setSelectedDate('');
|
|
6888
|
+
setHour12(null);
|
|
6889
|
+
setMinute(null);
|
|
6890
|
+
setMeridiem(null);
|
|
6891
|
+
setHour24(null);
|
|
6892
|
+
setSecond(null);
|
|
6893
|
+
return;
|
|
6894
|
+
}
|
|
6895
|
+
const dateString = getDateString(value);
|
|
6896
|
+
console.log('[DateTimePicker] Setting selectedDate:', dateString);
|
|
6897
|
+
setSelectedDate(dateString);
|
|
6898
|
+
const timeData = getTimeFromValue(value);
|
|
6899
|
+
console.log('[DateTimePicker] Updating time states:', {
|
|
6900
|
+
timeData,
|
|
6901
|
+
});
|
|
6902
|
+
setHour12(timeData.hour12);
|
|
6903
|
+
setMinute(timeData.minute);
|
|
6904
|
+
setMeridiem(timeData.meridiem);
|
|
6905
|
+
setHour24(timeData.hour24);
|
|
6906
|
+
setSecond(timeData.second);
|
|
6907
|
+
}, [value, getTimeFromValue, getDateString, timezone]);
|
|
6367
6908
|
const handleDateChange = (date) => {
|
|
6909
|
+
console.log('[DateTimePicker] handleDateChange called:', {
|
|
6910
|
+
date,
|
|
6911
|
+
timezone,
|
|
6912
|
+
showSeconds,
|
|
6913
|
+
currentTimeStates: { hour12, minute, meridiem, hour24, second },
|
|
6914
|
+
});
|
|
6915
|
+
// If date is empty or invalid, clear all fields
|
|
6916
|
+
if (!date || date === '') {
|
|
6917
|
+
console.log('[DateTimePicker] Empty date, clearing all fields');
|
|
6918
|
+
setSelectedDate('');
|
|
6919
|
+
setHour12(null);
|
|
6920
|
+
setMinute(null);
|
|
6921
|
+
setMeridiem(null);
|
|
6922
|
+
setHour24(null);
|
|
6923
|
+
setSecond(null);
|
|
6924
|
+
onChange?.(undefined);
|
|
6925
|
+
return;
|
|
6926
|
+
}
|
|
6368
6927
|
setSelectedDate(date);
|
|
6928
|
+
// Parse the date string (YYYY-MM-DD) in the specified timezone
|
|
6929
|
+
const dateObj = dayjs.tz(date, timezone);
|
|
6930
|
+
console.log('[DateTimePicker] Parsed date object:', {
|
|
6931
|
+
date,
|
|
6932
|
+
timezone,
|
|
6933
|
+
isValid: dateObj.isValid(),
|
|
6934
|
+
isoString: dateObj.toISOString(),
|
|
6935
|
+
formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
|
|
6936
|
+
});
|
|
6937
|
+
if (!dateObj.isValid()) {
|
|
6938
|
+
console.warn('[DateTimePicker] Invalid date object in handleDateChange, clearing fields');
|
|
6939
|
+
setSelectedDate('');
|
|
6940
|
+
setHour12(null);
|
|
6941
|
+
setMinute(null);
|
|
6942
|
+
setMeridiem(null);
|
|
6943
|
+
setHour24(null);
|
|
6944
|
+
setSecond(null);
|
|
6945
|
+
onChange?.(undefined);
|
|
6946
|
+
return;
|
|
6947
|
+
}
|
|
6369
6948
|
// When showSeconds is false, ignore seconds from the date
|
|
6370
|
-
|
|
6371
|
-
if (!showSeconds && dateObj.isValid()) {
|
|
6949
|
+
if (!showSeconds) {
|
|
6372
6950
|
const dateWithoutSeconds = dateObj.second(0).millisecond(0).toISOString();
|
|
6951
|
+
console.log('[DateTimePicker] Updating date without seconds:', dateWithoutSeconds);
|
|
6373
6952
|
updateDateTime(dateWithoutSeconds);
|
|
6374
6953
|
}
|
|
6375
6954
|
else {
|
|
6376
|
-
|
|
6955
|
+
const dateWithSeconds = dateObj.toISOString();
|
|
6956
|
+
console.log('[DateTimePicker] Updating date with seconds:', dateWithSeconds);
|
|
6957
|
+
updateDateTime(dateWithSeconds);
|
|
6377
6958
|
}
|
|
6378
6959
|
};
|
|
6379
6960
|
const handleTimeChange = (timeData) => {
|
|
6961
|
+
console.log('[DateTimePicker] handleTimeChange called:', {
|
|
6962
|
+
timeData,
|
|
6963
|
+
format,
|
|
6964
|
+
selectedDate,
|
|
6965
|
+
timezone,
|
|
6966
|
+
});
|
|
6380
6967
|
if (format === 'iso-date-time') {
|
|
6381
6968
|
const data = timeData;
|
|
6969
|
+
console.log('[DateTimePicker] ISO format - setting 24-hour time:', data);
|
|
6382
6970
|
setHour24(data.hour);
|
|
6383
6971
|
setMinute(data.minute);
|
|
6384
6972
|
if (showSeconds) {
|
|
@@ -6391,60 +6979,161 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
|
|
|
6391
6979
|
}
|
|
6392
6980
|
else {
|
|
6393
6981
|
const data = timeData;
|
|
6982
|
+
console.log('[DateTimePicker] 12-hour format - setting time:', data);
|
|
6394
6983
|
setHour12(data.hour);
|
|
6395
6984
|
setMinute(data.minute);
|
|
6396
6985
|
setMeridiem(data.meridiem);
|
|
6397
6986
|
}
|
|
6398
|
-
// Use selectedDate if valid, otherwise
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6987
|
+
// Use selectedDate if valid, otherwise clear all fields
|
|
6988
|
+
if (!selectedDate || !dayjs(selectedDate).isValid()) {
|
|
6989
|
+
console.log('[DateTimePicker] No valid selectedDate, clearing all fields');
|
|
6990
|
+
setSelectedDate('');
|
|
6991
|
+
setHour12(null);
|
|
6992
|
+
setMinute(null);
|
|
6993
|
+
setMeridiem(null);
|
|
6994
|
+
setHour24(null);
|
|
6995
|
+
setSecond(null);
|
|
6996
|
+
onChange?.(undefined);
|
|
6997
|
+
return;
|
|
6998
|
+
}
|
|
6999
|
+
const dateObj = dayjs(selectedDate).tz(timezone);
|
|
6403
7000
|
if (dateObj.isValid()) {
|
|
6404
7001
|
updateDateTime(dateObj.toISOString(), timeData);
|
|
6405
7002
|
}
|
|
7003
|
+
else {
|
|
7004
|
+
console.warn('[DateTimePicker] Invalid date object in handleTimeChange, clearing fields');
|
|
7005
|
+
setSelectedDate('');
|
|
7006
|
+
setHour12(null);
|
|
7007
|
+
setMinute(null);
|
|
7008
|
+
setMeridiem(null);
|
|
7009
|
+
setHour24(null);
|
|
7010
|
+
setSecond(null);
|
|
7011
|
+
onChange?.(undefined);
|
|
7012
|
+
}
|
|
6406
7013
|
};
|
|
6407
7014
|
const updateDateTime = (date, timeData) => {
|
|
6408
|
-
|
|
7015
|
+
console.log('[DateTimePicker] updateDateTime called:', {
|
|
7016
|
+
date,
|
|
7017
|
+
timeData,
|
|
7018
|
+
format,
|
|
7019
|
+
currentStates: { hour12, minute, meridiem, hour24, second },
|
|
7020
|
+
});
|
|
7021
|
+
if (!date || date === null || date === undefined) {
|
|
7022
|
+
console.log('[DateTimePicker] No date provided, clearing all fields and calling onChange(undefined)');
|
|
7023
|
+
setSelectedDate('');
|
|
7024
|
+
setHour12(null);
|
|
7025
|
+
setMinute(null);
|
|
7026
|
+
setMeridiem(null);
|
|
7027
|
+
setHour24(null);
|
|
7028
|
+
setSecond(null);
|
|
6409
7029
|
onChange?.(undefined);
|
|
6410
7030
|
return;
|
|
6411
7031
|
}
|
|
6412
7032
|
// use dayjs to convert the date to the timezone
|
|
6413
7033
|
const dateObj = dayjs(date).tz(timezone);
|
|
6414
7034
|
if (!dateObj.isValid()) {
|
|
7035
|
+
console.warn('[DateTimePicker] Invalid date object in updateDateTime, clearing fields:', date);
|
|
7036
|
+
setSelectedDate('');
|
|
7037
|
+
setHour12(null);
|
|
7038
|
+
setMinute(null);
|
|
7039
|
+
setMeridiem(null);
|
|
7040
|
+
setHour24(null);
|
|
7041
|
+
setSecond(null);
|
|
7042
|
+
onChange?.(undefined);
|
|
6415
7043
|
return;
|
|
6416
7044
|
}
|
|
6417
7045
|
const newDate = dateObj.toDate();
|
|
6418
7046
|
if (format === 'iso-date-time') {
|
|
6419
7047
|
const data = timeData;
|
|
6420
|
-
|
|
6421
|
-
|
|
7048
|
+
// Use timeData values if provided, otherwise fall back to current state
|
|
7049
|
+
// But if timeData is explicitly provided with nulls, we need to check if all are null
|
|
7050
|
+
const h = data !== undefined ? data.hour : hour24;
|
|
7051
|
+
const m = data !== undefined ? data.minute : minute;
|
|
6422
7052
|
// Always ignore seconds when showSeconds is false - set to 0
|
|
6423
|
-
const s = showSeconds
|
|
7053
|
+
const s = showSeconds
|
|
7054
|
+
? data !== undefined
|
|
7055
|
+
? data.second ?? null
|
|
7056
|
+
: second ?? 0
|
|
7057
|
+
: 0;
|
|
7058
|
+
// If all time values are null, clear the value
|
|
7059
|
+
if (h === null && m === null && (showSeconds ? s === null : true)) {
|
|
7060
|
+
console.log('[DateTimePicker] All time values are null, clearing value');
|
|
7061
|
+
onChange?.(undefined);
|
|
7062
|
+
return;
|
|
7063
|
+
}
|
|
7064
|
+
console.log('[DateTimePicker] ISO format - setting time on date:', {
|
|
7065
|
+
h,
|
|
7066
|
+
m,
|
|
7067
|
+
s,
|
|
7068
|
+
showSeconds,
|
|
7069
|
+
});
|
|
6424
7070
|
if (h !== null)
|
|
6425
7071
|
newDate.setHours(h);
|
|
6426
7072
|
if (m !== null)
|
|
6427
7073
|
newDate.setMinutes(m);
|
|
6428
|
-
newDate.setSeconds(s);
|
|
7074
|
+
newDate.setSeconds(s ?? 0);
|
|
6429
7075
|
}
|
|
6430
7076
|
else {
|
|
6431
7077
|
const data = timeData;
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
7078
|
+
console.log('[DateTimePicker] Processing 12-hour format:', {
|
|
7079
|
+
'data !== undefined': data !== undefined,
|
|
7080
|
+
'data?.hour': data?.hour,
|
|
7081
|
+
'data?.minute': data?.minute,
|
|
7082
|
+
'data?.meridiem': data?.meridiem,
|
|
7083
|
+
'current hour12': hour12,
|
|
7084
|
+
'current minute': minute,
|
|
7085
|
+
'current meridiem': meridiem,
|
|
7086
|
+
});
|
|
7087
|
+
// Use timeData values if provided, otherwise fall back to current state
|
|
7088
|
+
const h = data !== undefined ? data.hour : hour12;
|
|
7089
|
+
const m = data !== undefined ? data.minute : minute;
|
|
7090
|
+
const mer = data !== undefined ? data.meridiem : meridiem;
|
|
7091
|
+
console.log('[DateTimePicker] Resolved time values:', { h, m, mer });
|
|
7092
|
+
// If all time values are null, clear the value
|
|
7093
|
+
if (h === null && m === null && mer === null) {
|
|
7094
|
+
console.log('[DateTimePicker] All time values are null, clearing value');
|
|
7095
|
+
onChange?.(undefined);
|
|
7096
|
+
return;
|
|
7097
|
+
}
|
|
7098
|
+
console.log('[DateTimePicker] 12-hour format - converting time:', {
|
|
7099
|
+
h,
|
|
7100
|
+
m,
|
|
7101
|
+
mer,
|
|
7102
|
+
});
|
|
6435
7103
|
if (h !== null && mer !== null) {
|
|
6436
7104
|
let hour24 = h;
|
|
6437
7105
|
if (mer === 'am' && h === 12)
|
|
6438
7106
|
hour24 = 0;
|
|
6439
7107
|
else if (mer === 'pm' && h < 12)
|
|
6440
7108
|
hour24 = h + 12;
|
|
7109
|
+
console.log('[DateTimePicker] Converted to 24-hour:', {
|
|
7110
|
+
h,
|
|
7111
|
+
mer,
|
|
7112
|
+
hour24,
|
|
7113
|
+
});
|
|
6441
7114
|
newDate.setHours(hour24);
|
|
6442
7115
|
}
|
|
6443
|
-
|
|
7116
|
+
else {
|
|
7117
|
+
console.log('[DateTimePicker] Skipping hour update - h or mer is null:', {
|
|
7118
|
+
h,
|
|
7119
|
+
mer,
|
|
7120
|
+
});
|
|
7121
|
+
}
|
|
7122
|
+
if (m !== null) {
|
|
6444
7123
|
newDate.setMinutes(m);
|
|
7124
|
+
}
|
|
7125
|
+
else {
|
|
7126
|
+
console.log('[DateTimePicker] Skipping minute update - m is null');
|
|
7127
|
+
}
|
|
6445
7128
|
newDate.setSeconds(0);
|
|
6446
7129
|
}
|
|
6447
|
-
|
|
7130
|
+
const finalISO = dayjs(newDate).tz(timezone).toISOString();
|
|
7131
|
+
console.log('[DateTimePicker] Final ISO string to emit:', {
|
|
7132
|
+
newDate: newDate.toISOString(),
|
|
7133
|
+
timezone,
|
|
7134
|
+
finalISO,
|
|
7135
|
+
});
|
|
7136
|
+
onChange?.(finalISO);
|
|
6448
7137
|
};
|
|
6449
7138
|
const handleClear = () => {
|
|
6450
7139
|
setSelectedDate('');
|
|
@@ -6460,14 +7149,92 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
|
|
|
6460
7149
|
const normalizedStartTime = startTime
|
|
6461
7150
|
? dayjs(startTime).tz(timezone).millisecond(0).toISOString()
|
|
6462
7151
|
: undefined;
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
7152
|
+
// Determine minDate: prioritize explicit minDate prop, then fall back to startTime
|
|
7153
|
+
const effectiveMinDate = minDate
|
|
7154
|
+
? minDate
|
|
7155
|
+
: normalizedStartTime && dayjs(normalizedStartTime).tz(timezone).isValid()
|
|
7156
|
+
? dayjs(normalizedStartTime).tz(timezone).startOf('day').toDate()
|
|
7157
|
+
: undefined;
|
|
7158
|
+
// Log current state before render
|
|
7159
|
+
useEffect(() => {
|
|
7160
|
+
console.log('[DateTimePicker] Current state before render:', {
|
|
7161
|
+
isISO,
|
|
7162
|
+
hour12,
|
|
7163
|
+
minute,
|
|
7164
|
+
meridiem,
|
|
7165
|
+
hour24,
|
|
7166
|
+
second,
|
|
7167
|
+
selectedDate,
|
|
7168
|
+
normalizedStartTime,
|
|
7169
|
+
timezone,
|
|
7170
|
+
});
|
|
7171
|
+
}, [
|
|
7172
|
+
isISO,
|
|
7173
|
+
hour12,
|
|
7174
|
+
minute,
|
|
7175
|
+
meridiem,
|
|
7176
|
+
hour24,
|
|
7177
|
+
second,
|
|
7178
|
+
selectedDate,
|
|
7179
|
+
normalizedStartTime,
|
|
7180
|
+
timezone,
|
|
7181
|
+
]);
|
|
7182
|
+
// Compute display text from current state
|
|
7183
|
+
const displayText = useMemo(() => {
|
|
7184
|
+
if (!selectedDate)
|
|
7185
|
+
return null;
|
|
7186
|
+
const dateObj = dayjs.tz(selectedDate, timezone);
|
|
7187
|
+
if (!dateObj.isValid())
|
|
7188
|
+
return null;
|
|
7189
|
+
if (isISO) {
|
|
7190
|
+
// For ISO format, use hour24, minute, second
|
|
7191
|
+
if (hour24 === null || minute === null)
|
|
7192
|
+
return null;
|
|
7193
|
+
const dateTimeObj = dateObj
|
|
7194
|
+
.hour(hour24)
|
|
7195
|
+
.minute(minute)
|
|
7196
|
+
.second(second ?? 0);
|
|
7197
|
+
return dateTimeObj.format(showSeconds ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm');
|
|
7198
|
+
}
|
|
7199
|
+
else {
|
|
7200
|
+
// For 12-hour format, use hour12, minute, meridiem
|
|
7201
|
+
if (hour12 === null || minute === null || meridiem === null)
|
|
7202
|
+
return null;
|
|
7203
|
+
// Convert to 24-hour format for dayjs
|
|
7204
|
+
let hour24Value = hour12;
|
|
7205
|
+
if (meridiem === 'am' && hour12 === 12)
|
|
7206
|
+
hour24Value = 0;
|
|
7207
|
+
else if (meridiem === 'pm' && hour12 < 12)
|
|
7208
|
+
hour24Value = hour12 + 12;
|
|
7209
|
+
const dateTimeObj = dateObj.hour(hour24Value).minute(minute).second(0);
|
|
7210
|
+
return dateTimeObj.format('YYYY-MM-DD hh:mm A');
|
|
7211
|
+
}
|
|
7212
|
+
}, [
|
|
7213
|
+
selectedDate,
|
|
7214
|
+
isISO,
|
|
7215
|
+
hour12,
|
|
7216
|
+
minute,
|
|
7217
|
+
meridiem,
|
|
7218
|
+
hour24,
|
|
7219
|
+
second,
|
|
7220
|
+
showSeconds,
|
|
7221
|
+
timezone,
|
|
7222
|
+
]);
|
|
7223
|
+
const timezoneOffset = useMemo(() => {
|
|
7224
|
+
if (!selectedDate)
|
|
7225
|
+
return null;
|
|
7226
|
+
const dateObj = dayjs.tz(selectedDate, timezone);
|
|
7227
|
+
return dateObj.isValid() ? dateObj.format('Z') : null;
|
|
7228
|
+
}, [selectedDate, timezone]);
|
|
7229
|
+
return (jsxs(Flex, { direction: "column", gap: 4, p: 4, border: "1px solid", borderColor: "gray.200", borderRadius: "md", children: [jsx(DatePickerInput, { value: selectedDate || undefined, onChange: (date) => {
|
|
7230
|
+
if (date) {
|
|
7231
|
+
handleDateChange(date);
|
|
7232
|
+
}
|
|
7233
|
+
else {
|
|
7234
|
+
setSelectedDate('');
|
|
7235
|
+
onChange?.(undefined);
|
|
7236
|
+
}
|
|
7237
|
+
}, placeholder: "Select a date", dateFormat: "YYYY-MM-DD", displayFormat: "YYYY-MM-DD", labels: labels, timezone: timezone, minDate: effectiveMinDate, maxDate: maxDate, monthsToDisplay: 1, readOnly: true }), jsxs(Grid, { templateColumns: "1fr auto", alignItems: "center", gap: 4, children: [isISO ? (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 })) : (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 })), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "outline", colorScheme: "red", children: jsx(Icon, { as: FaTrash }) })] }), displayText && (jsxs(Flex, { gap: 2, children: [jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: displayText }), timezoneOffset && (jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezoneOffset })), jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezone })] }))] }));
|
|
6471
7238
|
}
|
|
6472
7239
|
|
|
6473
7240
|
dayjs.extend(utc);
|
|
@@ -6475,7 +7242,7 @@ dayjs.extend(timezone);
|
|
|
6475
7242
|
const DateTimePicker = ({ column, schema, prefix, }) => {
|
|
6476
7243
|
const { watch, formState: { errors }, setValue, } = useFormContext();
|
|
6477
7244
|
const { timezone, dateTimePickerLabels, insideDialog } = useSchemaContext();
|
|
6478
|
-
const formI18n = useFormI18n(column, prefix);
|
|
7245
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
6479
7246
|
const { required, gridColumn = 'span 12', gridRow = 'span 1', displayDateFormat = 'YYYY-MM-DD HH:mm:ss',
|
|
6480
7247
|
// with timezone
|
|
6481
7248
|
dateFormat = 'YYYY-MM-DD[T]HH:mm:ssZ', } = schema;
|
|
@@ -6483,32 +7250,9 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
|
|
|
6483
7250
|
const colLabel = formI18n.colLabel;
|
|
6484
7251
|
const [open, setOpen] = useState(false);
|
|
6485
7252
|
const selectedDate = watch(colLabel);
|
|
6486
|
-
const displayDate = dayjs(selectedDate)
|
|
6487
|
-
.tz(timezone)
|
|
6488
|
-
|
|
6489
|
-
useEffect(() => {
|
|
6490
|
-
try {
|
|
6491
|
-
if (selectedDate) {
|
|
6492
|
-
// Parse the selectedDate as UTC or in a specific timezone to avoid +8 hour shift
|
|
6493
|
-
// For example, parse as UTC:
|
|
6494
|
-
const parsedDate = dayjs(selectedDate).tz(timezone);
|
|
6495
|
-
if (!parsedDate.isValid())
|
|
6496
|
-
return;
|
|
6497
|
-
// Format according to dateFormat from schema
|
|
6498
|
-
const formatted = parsedDate.format(dateFormat);
|
|
6499
|
-
// Update the form value only if different to avoid loops
|
|
6500
|
-
if (formatted !== selectedDate) {
|
|
6501
|
-
setValue(colLabel, formatted, {
|
|
6502
|
-
shouldValidate: true,
|
|
6503
|
-
shouldDirty: true,
|
|
6504
|
-
});
|
|
6505
|
-
}
|
|
6506
|
-
}
|
|
6507
|
-
}
|
|
6508
|
-
catch (e) {
|
|
6509
|
-
console.error(e);
|
|
6510
|
-
}
|
|
6511
|
-
}, [selectedDate, dateFormat, colLabel, setValue]);
|
|
7253
|
+
const displayDate = selectedDate && dayjs(selectedDate).tz(timezone).isValid()
|
|
7254
|
+
? dayjs(selectedDate).tz(timezone).format(displayDateFormat)
|
|
7255
|
+
: '';
|
|
6512
7256
|
const dateTimePickerLabelsConfig = {
|
|
6513
7257
|
monthNamesShort: dateTimePickerLabels?.monthNamesShort ?? [
|
|
6514
7258
|
formI18n.translate.t(`common.month_1`, {
|
|
@@ -6581,12 +7325,22 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
|
|
|
6581
7325
|
}),
|
|
6582
7326
|
};
|
|
6583
7327
|
const dateTimePickerContent = (jsx(DateTimePicker$1, { value: selectedDate, onChange: (date) => {
|
|
6584
|
-
|
|
7328
|
+
if (!date || date === null || date === undefined) {
|
|
7329
|
+
setValue(colLabel, undefined);
|
|
7330
|
+
return;
|
|
7331
|
+
}
|
|
7332
|
+
const dateObj = dayjs(date).tz(timezone);
|
|
7333
|
+
if (dateObj.isValid()) {
|
|
7334
|
+
setValue(colLabel, dateObj.format(dateFormat));
|
|
7335
|
+
}
|
|
7336
|
+
else {
|
|
7337
|
+
setValue(colLabel, undefined);
|
|
7338
|
+
}
|
|
6585
7339
|
}, timezone: timezone, labels: dateTimePickerLabelsConfig }));
|
|
6586
7340
|
return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
6587
|
-
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsx(Popover.Trigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
|
|
7341
|
+
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(Popover.Trigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
|
|
6588
7342
|
setOpen(true);
|
|
6589
|
-
}, justifyContent: 'start', children: [jsx(MdDateRange, {}),
|
|
7343
|
+
}, justifyContent: 'start', children: [jsx(MdDateRange, {}), displayDate || ''] }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "450px", minH: "25rem", children: jsx(Popover.Body, { children: dateTimePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "450px", minH: "25rem", children: jsx(Popover.Body, { children: dateTimePickerContent }) }) }) }))] }) }));
|
|
6590
7344
|
};
|
|
6591
7345
|
|
|
6592
7346
|
const SchemaRenderer = ({ schema, prefix, column, }) => {
|
|
@@ -6630,7 +7384,7 @@ const SchemaRenderer = ({ schema, prefix, column, }) => {
|
|
|
6630
7384
|
if (innerProperties) {
|
|
6631
7385
|
return jsx(ObjectInput, { schema: colSchema, prefix, column });
|
|
6632
7386
|
}
|
|
6633
|
-
return jsx(RecordInput
|
|
7387
|
+
return jsx(RecordInput, { schema: colSchema, prefix, column });
|
|
6634
7388
|
}
|
|
6635
7389
|
if (type === 'array') {
|
|
6636
7390
|
if (variant === 'id-picker') {
|
|
@@ -6684,32 +7438,30 @@ const ColumnRenderer = ({ column, properties, prefix, parentRequired, }) => {
|
|
|
6684
7438
|
};
|
|
6685
7439
|
|
|
6686
7440
|
const ArrayViewer = ({ schema, column, prefix }) => {
|
|
6687
|
-
const { gridColumn =
|
|
6688
|
-
const { translate } = useSchemaContext();
|
|
7441
|
+
const { gridColumn = 'span 12', gridRow = 'span 1', required, items, } = schema;
|
|
6689
7442
|
const colLabel = `${prefix}${column}`;
|
|
6690
7443
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
7444
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
6691
7445
|
const { watch, formState: { errors }, } = useFormContext();
|
|
6692
7446
|
const values = watch(colLabel) ?? [];
|
|
6693
|
-
return (jsxs(Box, { gridRow, gridColumn, children: [jsxs(Box, { as: "label", gridColumn:
|
|
6694
|
-
base:
|
|
6695
|
-
_dark:
|
|
6696
|
-
}, children: jsx(Grid, { gap: "4", gridTemplateColumns:
|
|
7447
|
+
return (jsxs(Box, { gridRow, gridColumn, children: [jsxs(Box, { as: "label", gridColumn: '1/span12', children: [formI18n.label(), isRequired && jsx("span", { children: "*" })] }), jsx(Flex, { flexFlow: 'column', gap: 1, children: values.map((field, index) => (jsx(Flex, { flexFlow: 'column', bgColor: { base: 'colorPalette.100', _dark: 'colorPalette.900' }, p: '2', borderRadius: 'md', borderWidth: 'thin', borderColor: {
|
|
7448
|
+
base: 'colorPalette.200',
|
|
7449
|
+
_dark: 'colorPalette.800',
|
|
7450
|
+
}, children: jsx(Grid, { gap: "4", gridTemplateColumns: 'repeat(12, 1fr)', autoFlow: 'row', children: jsx(SchemaViewer, { column: `${index}`,
|
|
6697
7451
|
prefix: `${colLabel}.`,
|
|
6698
7452
|
// @ts-expect-error find suitable types
|
|
6699
|
-
schema: { showLabel: false, ...(items ?? {}) } }) }) }, `form-${prefix}${column}.${index}`))) }), errors[`${column}`] && (jsx(Text, { color:
|
|
7453
|
+
schema: { showLabel: false, ...(items ?? {}) } }) }) }, `form-${prefix}${column}.${index}`))) }), errors[`${column}`] && (jsx(Text, { color: 'red.400', children: formI18n.required() }))] }));
|
|
6700
7454
|
};
|
|
6701
7455
|
|
|
6702
7456
|
const BooleanViewer = ({ schema, column, prefix, }) => {
|
|
6703
7457
|
const { watch, formState: { errors }, } = useFormContext();
|
|
6704
|
-
const {
|
|
6705
|
-
const { required, gridColumn = "span 12", gridRow = "span 1" } = schema;
|
|
7458
|
+
const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
|
|
6706
7459
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
6707
7460
|
const colLabel = `${prefix}${column}`;
|
|
6708
7461
|
const value = watch(colLabel);
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
: translate.t(removeIndex(`${colLabel}.false`)) }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }));
|
|
7462
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
7463
|
+
return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
7464
|
+
gridRow, children: [jsx(Text, { children: value ? formI18n.t('true') : formI18n.t('false') }), errors[`${column}`] && (jsx(Text, { color: 'red.400', children: formI18n.required() }))] }));
|
|
6713
7465
|
};
|
|
6714
7466
|
|
|
6715
7467
|
const CustomViewer = ({ column, schema, prefix }) => {
|
|
@@ -6726,19 +7478,22 @@ const CustomViewer = ({ column, schema, prefix }) => {
|
|
|
6726
7478
|
|
|
6727
7479
|
const DateViewer = ({ column, schema, prefix }) => {
|
|
6728
7480
|
const { watch, formState: { errors }, } = useFormContext();
|
|
6729
|
-
const {
|
|
6730
|
-
const { required, gridColumn =
|
|
7481
|
+
const { timezone } = useSchemaContext();
|
|
7482
|
+
const { required, gridColumn = 'span 4', gridRow = 'span 1', displayDateFormat = 'YYYY-MM-DD', } = schema;
|
|
6731
7483
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
6732
7484
|
const colLabel = `${prefix}${column}`;
|
|
6733
7485
|
const selectedDate = watch(colLabel);
|
|
6734
|
-
const
|
|
6735
|
-
|
|
6736
|
-
|
|
7486
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
7487
|
+
const displayDate = dayjs(selectedDate)
|
|
7488
|
+
.tz(timezone)
|
|
7489
|
+
.format(displayDateFormat);
|
|
7490
|
+
return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
7491
|
+
gridRow, children: [jsxs(Text, { children: [" ", selectedDate !== undefined ? displayDate : ''] }), errors[`${column}`] && (jsx(Text, { color: 'red.400', children: formI18n.required() }))] }));
|
|
6737
7492
|
};
|
|
6738
7493
|
|
|
6739
7494
|
const EnumViewer = ({ column, isMultiple = false, schema, prefix, }) => {
|
|
6740
7495
|
const { watch, formState: { errors }, } = useFormContext();
|
|
6741
|
-
const formI18n = useFormI18n(column, prefix);
|
|
7496
|
+
const formI18n = useFormI18n$1(column, prefix);
|
|
6742
7497
|
const { required } = schema;
|
|
6743
7498
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
6744
7499
|
const { gridColumn = "span 12", gridRow = "span 1", renderDisplay } = schema;
|
|
@@ -6759,54 +7514,56 @@ const EnumViewer = ({ column, isMultiple = false, schema, prefix, }) => {
|
|
|
6759
7514
|
|
|
6760
7515
|
const FileViewer = ({ column, schema, prefix }) => {
|
|
6761
7516
|
const { watch } = useFormContext();
|
|
6762
|
-
const {
|
|
6763
|
-
const { required, gridColumn = "span 12", gridRow = "span 1", } = schema;
|
|
7517
|
+
const { required, gridColumn = 'span 12', gridRow = 'span 1', } = schema;
|
|
6764
7518
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
6765
7519
|
const currentFiles = (watch(column) ?? []);
|
|
6766
|
-
const
|
|
6767
|
-
return (jsx(Field, { label:
|
|
6768
|
-
return (jsx(Card.Root, { variant:
|
|
7520
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
7521
|
+
return (jsx(Field, { label: formI18n.label(), required: isRequired, gridColumn: gridColumn, gridRow: gridRow, display: 'grid', gridTemplateRows: 'auto 1fr auto', alignItems: 'stretch', children: jsx(Flex, { flexFlow: 'column', gap: 1, children: currentFiles.map((file) => {
|
|
7522
|
+
return (jsx(Card.Root, { variant: 'subtle', children: jsxs(Card.Body, { gap: "2", display: 'flex', flexFlow: 'row', alignItems: 'center', padding: '2', children: [file.type.startsWith('image/') && (jsx(Image, { src: URL.createObjectURL(file), alt: file.name, boxSize: "50px", objectFit: "cover", borderRadius: "md", marginRight: "2" })), jsx(Box, { children: file.name })] }) }, file.name));
|
|
6769
7523
|
}) }) }));
|
|
6770
7524
|
};
|
|
6771
7525
|
|
|
6772
7526
|
const IdViewer = ({ column, schema, prefix, isMultiple = false, }) => {
|
|
6773
7527
|
const { watch, formState: { errors }, } = useFormContext();
|
|
6774
7528
|
const { idMap, translate } = useSchemaContext();
|
|
6775
|
-
const { required, gridColumn =
|
|
7529
|
+
const { required, gridColumn = 'span 12', gridRow = 'span 1', renderDisplay, foreign_key, } = schema;
|
|
6776
7530
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
6777
|
-
const
|
|
7531
|
+
const formI18n = useFormI18n(column, prefix, schema);
|
|
6778
7532
|
const colLabel = `${prefix}${column}`;
|
|
6779
7533
|
const watchId = watch(colLabel);
|
|
6780
7534
|
const watchIds = (watch(colLabel) ?? []);
|
|
6781
7535
|
const getPickedValue = () => {
|
|
6782
7536
|
if (Object.keys(idMap).length <= 0) {
|
|
6783
|
-
return
|
|
7537
|
+
return '';
|
|
6784
7538
|
}
|
|
6785
7539
|
const record = idMap[watchId];
|
|
6786
7540
|
if (record === undefined) {
|
|
6787
|
-
return
|
|
7541
|
+
return '';
|
|
6788
7542
|
}
|
|
6789
|
-
|
|
7543
|
+
const rendered = renderDisplay
|
|
7544
|
+
? renderDisplay(record)
|
|
7545
|
+
: defaultRenderDisplay(record);
|
|
7546
|
+
return typeof rendered === 'string' ? rendered : JSON.stringify(record);
|
|
6790
7547
|
};
|
|
6791
|
-
return (jsxs(Field, { label:
|
|
6792
|
-
gridRow, children: [isMultiple && (jsx(Flex, { flexFlow:
|
|
7548
|
+
return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
7549
|
+
gridRow, children: [isMultiple && (jsx(Flex, { flexFlow: 'wrap', gap: 1, children: watchIds.map((id) => {
|
|
6793
7550
|
const item = idMap[id];
|
|
6794
7551
|
if (item === undefined) {
|
|
6795
7552
|
return (jsx(Text, { children: translate.t(removeIndex(`${colLabel}.undefined`)) }, id));
|
|
6796
7553
|
}
|
|
6797
|
-
return (jsx(Tag, { closable: true, children:
|
|
7554
|
+
return (jsx(Tag, { closable: true, children: renderDisplay
|
|
6798
7555
|
? renderDisplay(item)
|
|
6799
|
-
: item
|
|
6800
|
-
}) })), !isMultiple && jsx(Text, { children: getPickedValue() }), errors[`${colLabel}`] && (jsx(Text, { color:
|
|
7556
|
+
: defaultRenderDisplay(item) }, id));
|
|
7557
|
+
}) })), !isMultiple && jsx(Text, { children: getPickedValue() }), errors[`${colLabel}`] && (jsx(Text, { color: 'red.400', children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }));
|
|
6801
7558
|
};
|
|
6802
7559
|
|
|
6803
7560
|
const NumberViewer = ({ schema, column, prefix, }) => {
|
|
6804
7561
|
const { watch, formState: { errors }, } = useFormContext();
|
|
6805
|
-
const { translate } = useSchemaContext();
|
|
6806
7562
|
const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
|
|
6807
7563
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
6808
7564
|
const colLabel = `${prefix}${column}`;
|
|
6809
7565
|
const value = watch(colLabel);
|
|
7566
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
6810
7567
|
// Format the value for display if formatOptions are provided
|
|
6811
7568
|
const formatValue = (val) => {
|
|
6812
7569
|
if (val === undefined || val === null || val === '')
|
|
@@ -6825,90 +7582,49 @@ const NumberViewer = ({ schema, column, prefix, }) => {
|
|
|
6825
7582
|
}
|
|
6826
7583
|
return String(val);
|
|
6827
7584
|
};
|
|
6828
|
-
return (jsxs(Field, { label:
|
|
7585
|
+
return (jsxs(Field, { label: formI18n.label(), required: isRequired, gridColumn, gridRow, children: [jsx(Text, { children: formatValue(value) }), errors[`${column}`] && (jsx(Text, { color: 'red.400', children: formI18n.required() }))] }));
|
|
6829
7586
|
};
|
|
6830
7587
|
|
|
6831
7588
|
const ObjectViewer = ({ schema, column, prefix }) => {
|
|
6832
|
-
const { properties, gridColumn =
|
|
6833
|
-
const { translate } = useSchemaContext();
|
|
7589
|
+
const { properties, gridColumn = 'span 12', gridRow = 'span 1', required, showLabel = true, } = schema;
|
|
6834
7590
|
const colLabel = `${prefix}${column}`;
|
|
6835
7591
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
7592
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
6836
7593
|
const { formState: { errors }, } = useFormContext();
|
|
6837
7594
|
if (properties === undefined) {
|
|
6838
7595
|
throw new Error(`properties is undefined when using ObjectInput`);
|
|
6839
7596
|
}
|
|
6840
|
-
return (jsxs(Box, { gridRow, gridColumn, children: [showLabel && (jsxs(Box, { as: "label", children: [
|
|
6841
|
-
base:
|
|
6842
|
-
_dark:
|
|
7597
|
+
return (jsxs(Box, { gridRow, gridColumn, children: [showLabel && (jsxs(Box, { as: "label", children: [formI18n.label(), isRequired && jsx("span", { children: "*" })] })), jsx(Grid, { gap: "4", padding: '4', gridTemplateColumns: 'repeat(12, 1fr)', autoFlow: 'row', bgColor: { base: 'colorPalette.100', _dark: 'colorPalette.900' }, p: '1', borderRadius: 'md', borderWidth: 'thin', borderColor: {
|
|
7598
|
+
base: 'colorPalette.200',
|
|
7599
|
+
_dark: 'colorPalette.800',
|
|
6843
7600
|
}, children: Object.keys(properties ?? {}).map((key) => {
|
|
6844
7601
|
return (
|
|
6845
7602
|
// @ts-expect-error find suitable types
|
|
6846
7603
|
jsx(ColumnViewer, { column: `${key}`,
|
|
6847
7604
|
prefix: `${prefix}${column}.`,
|
|
6848
7605
|
properties }, `form-objectviewer-${colLabel}-${key}`));
|
|
6849
|
-
}) }), errors[`${column}`] && (jsx(Text, { color:
|
|
7606
|
+
}) }), errors[`${column}`] && (jsx(Text, { color: 'red.400', children: formI18n.required() }))] }));
|
|
6850
7607
|
};
|
|
6851
7608
|
|
|
6852
|
-
const
|
|
6853
|
-
const { formState: { errors },
|
|
6854
|
-
const {
|
|
6855
|
-
const { required, gridColumn = "span 12", gridRow = "span 1" } = schema;
|
|
7609
|
+
const RecordViewer = ({ column, schema, prefix }) => {
|
|
7610
|
+
const { formState: { errors }, getValues, } = useFormContext();
|
|
7611
|
+
const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
|
|
6856
7612
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
6857
7613
|
const entries = Object.entries(getValues(column) ?? {});
|
|
6858
|
-
const
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
return (jsxs(Grid, { templateColumns: "1fr 1fr auto", gap: 1, children: [jsx(Input, { value: key, onChange: (e) => {
|
|
6863
|
-
const filtered = entries.filter(([target]) => {
|
|
6864
|
-
return target !== key;
|
|
6865
|
-
});
|
|
6866
|
-
setValue(column, Object.fromEntries([...filtered, [e.target.value, value]]));
|
|
6867
|
-
}, autoComplete: "off" }), jsx(Input, { value: value, onChange: (e) => {
|
|
6868
|
-
setValue(column, {
|
|
6869
|
-
...getValues(column),
|
|
6870
|
-
[key]: e.target.value,
|
|
6871
|
-
});
|
|
6872
|
-
}, autoComplete: "off" }), jsx(IconButton, { variant: "ghost", onClick: () => {
|
|
6873
|
-
const filtered = entries.filter(([target]) => {
|
|
6874
|
-
return target !== key;
|
|
6875
|
-
});
|
|
6876
|
-
setValue(column, Object.fromEntries([...filtered]));
|
|
6877
|
-
}, children: jsx(CgClose, {}) })] }));
|
|
6878
|
-
}), jsx(Show, { when: showNewEntries, children: jsxs(Card.Root, { children: [jsx(Card.Body, { gap: "2", children: jsxs(Grid, { templateColumns: "1fr 1fr auto", gap: 1, children: [jsx(Input, { value: newKey, onChange: (e) => {
|
|
6879
|
-
setNewKey(e.target.value);
|
|
6880
|
-
}, autoComplete: "off" }), jsx(Input, { value: newValue, onChange: (e) => {
|
|
6881
|
-
setNewValue(e.target.value);
|
|
6882
|
-
}, autoComplete: "off" })] }) }), jsxs(Card.Footer, { justifyContent: "flex-end", children: [jsx(IconButton, { variant: "subtle", onClick: () => {
|
|
6883
|
-
setShowNewEntries(false);
|
|
6884
|
-
setNewKey(undefined);
|
|
6885
|
-
setNewValue(undefined);
|
|
6886
|
-
}, children: jsx(CgClose, {}) }), jsx(Button, { onClick: () => {
|
|
6887
|
-
if (!!newKey === false) {
|
|
6888
|
-
setShowNewEntries(false);
|
|
6889
|
-
setNewKey(undefined);
|
|
6890
|
-
setNewValue(undefined);
|
|
6891
|
-
return;
|
|
6892
|
-
}
|
|
6893
|
-
setValue(column, Object.fromEntries([...entries, [newKey, newValue]]));
|
|
6894
|
-
setShowNewEntries(false);
|
|
6895
|
-
setNewKey(undefined);
|
|
6896
|
-
setNewValue(undefined);
|
|
6897
|
-
}, children: translate.t(`${column}.save`) })] })] }) }), jsx(Button, { onClick: () => {
|
|
6898
|
-
setShowNewEntries(true);
|
|
6899
|
-
setNewKey(undefined);
|
|
6900
|
-
setNewValue(undefined);
|
|
6901
|
-
}, children: translate.t(`${column}.addNew`) }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: translate.t(`${column}.field_required`) }))] }));
|
|
7614
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
7615
|
+
return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn, gridRow, children: [entries.length === 0 ? (jsx(Text, { color: "gray.500", children: "No entries" })) : (jsx(Grid, { templateColumns: '1fr 1fr', gap: 2, children: entries.map(([key, value]) => {
|
|
7616
|
+
return (jsxs(Grid, { templateColumns: '1fr 1fr', gap: 2, children: [jsxs(Text, { fontWeight: "medium", children: [key, ":"] }), jsx(Text, { children: String(value ?? '') })] }, key));
|
|
7617
|
+
}) })), errors[`${column}`] && (jsx(Text, { color: 'red.400', children: formI18n.required() }))] }));
|
|
6902
7618
|
};
|
|
6903
7619
|
|
|
6904
7620
|
const StringViewer = ({ column, schema, prefix, }) => {
|
|
6905
7621
|
const { watch, formState: { errors }, } = useFormContext();
|
|
6906
|
-
const {
|
|
6907
|
-
const { required, gridColumn = "span 12", gridRow = "span 1" } = schema;
|
|
7622
|
+
const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
|
|
6908
7623
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
6909
7624
|
const colLabel = `${prefix}${column}`;
|
|
6910
7625
|
const value = watch(colLabel);
|
|
6911
|
-
|
|
7626
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
7627
|
+
return (jsx(Fragment, { children: jsxs(Field, { label: formI18n.label(), required: isRequired, gridColumn: gridColumn ?? 'span 4', gridRow: gridRow ?? 'span 1', children: [jsx(Text, { children: value }), errors[colLabel] && (jsx(Text, { color: 'red.400', children: formI18n.required() }))] }) }));
|
|
6912
7628
|
};
|
|
6913
7629
|
|
|
6914
7630
|
const TagViewer = ({ column, schema, prefix }) => {
|
|
@@ -6998,40 +7714,44 @@ const TagViewer = ({ column, schema, prefix }) => {
|
|
|
6998
7714
|
|
|
6999
7715
|
const TextAreaViewer = ({ column, schema, prefix, }) => {
|
|
7000
7716
|
const { watch, formState: { errors }, } = useFormContext();
|
|
7001
|
-
const {
|
|
7002
|
-
const { required, gridColumn = "span 12", gridRow = "span 1" } = schema;
|
|
7717
|
+
const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
|
|
7003
7718
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
7004
7719
|
const colLabel = `${prefix}${column}`;
|
|
7005
7720
|
const value = watch(colLabel);
|
|
7006
|
-
|
|
7721
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
7722
|
+
return (jsx(Fragment, { children: jsxs(Field, { label: formI18n.label(), required: isRequired, gridColumn: gridColumn, gridRow: gridRow, children: [jsx(Text, { whiteSpace: "pre-wrap", children: value }), ' ', errors[colLabel] && (jsx(Text, { color: 'red.400', children: formI18n.required() }))] }) }));
|
|
7007
7723
|
};
|
|
7008
7724
|
|
|
7009
7725
|
const TimeViewer = ({ column, schema, prefix }) => {
|
|
7010
7726
|
const { watch, formState: { errors }, } = useFormContext();
|
|
7011
|
-
const {
|
|
7012
|
-
const { required, gridColumn =
|
|
7727
|
+
const { timezone } = useSchemaContext();
|
|
7728
|
+
const { required, gridColumn = 'span 12', gridRow = 'span 1', displayTimeFormat = 'hh:mm A', } = schema;
|
|
7013
7729
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
7014
7730
|
const colLabel = `${prefix}${column}`;
|
|
7015
7731
|
const selectedDate = watch(colLabel);
|
|
7732
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
7016
7733
|
const displayedTime = dayjs(`1970-01-01T${selectedDate}`)
|
|
7017
7734
|
.tz(timezone)
|
|
7018
7735
|
.isValid()
|
|
7019
7736
|
? dayjs(`1970-01-01T${selectedDate}`).tz(timezone).format(displayTimeFormat)
|
|
7020
|
-
:
|
|
7021
|
-
return (jsxs(Field, { label:
|
|
7022
|
-
gridRow, children: [jsx(Text, { children: displayedTime }), errors[`${column}`] && (jsx(Text, { color:
|
|
7737
|
+
: '';
|
|
7738
|
+
return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
7739
|
+
gridRow, children: [jsx(Text, { children: displayedTime }), errors[`${column}`] && (jsx(Text, { color: 'red.400', children: formI18n.required() }))] }));
|
|
7023
7740
|
};
|
|
7024
7741
|
|
|
7025
7742
|
const DateTimeViewer = ({ column, schema, prefix }) => {
|
|
7026
7743
|
const { watch, formState: { errors }, } = useFormContext();
|
|
7027
|
-
const {
|
|
7028
|
-
const { required, gridColumn =
|
|
7744
|
+
const { timezone } = useSchemaContext();
|
|
7745
|
+
const { required, gridColumn = 'span 4', gridRow = 'span 1', displayDateFormat = 'YYYY-MM-DD HH:mm:ss', } = schema;
|
|
7029
7746
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
7030
7747
|
const colLabel = `${prefix}${column}`;
|
|
7031
7748
|
const selectedDate = watch(colLabel);
|
|
7032
|
-
const
|
|
7033
|
-
|
|
7034
|
-
|
|
7749
|
+
const formI18n = useFormI18n$1(column, prefix, schema);
|
|
7750
|
+
const displayDate = dayjs(selectedDate)
|
|
7751
|
+
.tz(timezone)
|
|
7752
|
+
.format(displayDateFormat);
|
|
7753
|
+
return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
7754
|
+
gridRow, children: [jsxs(Text, { children: [" ", selectedDate !== undefined ? displayDate : ''] }), errors[`${column}`] && (jsx(Text, { color: 'red.400', children: formI18n.required() }))] }));
|
|
7035
7755
|
};
|
|
7036
7756
|
|
|
7037
7757
|
const SchemaViewer = ({ schema, prefix, column, }) => {
|
|
@@ -7072,7 +7792,7 @@ const SchemaViewer = ({ schema, prefix, column, }) => {
|
|
|
7072
7792
|
if (innerProperties) {
|
|
7073
7793
|
return jsx(ObjectViewer, { schema: colSchema, prefix, column });
|
|
7074
7794
|
}
|
|
7075
|
-
return jsx(
|
|
7795
|
+
return jsx(RecordViewer, { schema: colSchema, prefix, column });
|
|
7076
7796
|
}
|
|
7077
7797
|
if (type === 'array') {
|
|
7078
7798
|
if (variant === 'id-picker') {
|
|
@@ -7120,7 +7840,7 @@ const ColumnViewer = ({ column, properties, prefix, }) => {
|
|
|
7120
7840
|
};
|
|
7121
7841
|
|
|
7122
7842
|
const SubmitButton = () => {
|
|
7123
|
-
const { translate, setValidatedData, setIsError, setIsConfirming,
|
|
7843
|
+
const { translate, setValidatedData, setIsError, setIsConfirming, requireConfirmation, onFormSubmit, formButtonLabels, } = useSchemaContext();
|
|
7124
7844
|
const methods = useFormContext();
|
|
7125
7845
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7126
7846
|
const onValid = (data) => {
|
|
@@ -7149,11 +7869,11 @@ const SubmitButton = () => {
|
|
|
7149
7869
|
};
|
|
7150
7870
|
return (jsx(Button$1, { onClick: () => {
|
|
7151
7871
|
methods.handleSubmit(onValid)();
|
|
7152
|
-
}, formNoValidate: true, children: translate.t('submit') }));
|
|
7872
|
+
}, formNoValidate: true, children: formButtonLabels?.submit ?? translate.t('submit') }));
|
|
7153
7873
|
};
|
|
7154
7874
|
|
|
7155
7875
|
const FormBody = () => {
|
|
7156
|
-
const { schema, order, ignore, include, translate, isSuccess, setIsSuccess, isError, setIsError, isSubmiting, setIsSubmiting, isConfirming, setIsConfirming, validatedData, setValidatedData, error, getUpdatedData, customErrorRenderer, customSuccessRenderer, displayConfig, onFormSubmit, } = useSchemaContext();
|
|
7876
|
+
const { schema, order, ignore, include, translate, isSuccess, setIsSuccess, isError, setIsError, isSubmiting, setIsSubmiting, isConfirming, setIsConfirming, validatedData, setValidatedData, error, getUpdatedData, customErrorRenderer, customSuccessRenderer, displayConfig, onFormSubmit, formButtonLabels, } = useSchemaContext();
|
|
7157
7877
|
const { showSubmitButton, showResetButton } = displayConfig;
|
|
7158
7878
|
const methods = useFormContext();
|
|
7159
7879
|
const { properties } = schema;
|
|
@@ -7183,7 +7903,7 @@ const FormBody = () => {
|
|
|
7183
7903
|
if (customSuccessRenderer) {
|
|
7184
7904
|
return customSuccessRenderer(resetHandler);
|
|
7185
7905
|
}
|
|
7186
|
-
return (jsxs(Flex, { flexFlow: 'column', gap: "2", children: [jsxs(Alert.Root, { status: "success", children: [jsx(Alert.Indicator, {}), jsx(Alert.Content, { children: jsx(Alert.Title, { children: translate.t('submit_success') }) })] }), jsx(Flex, { justifyContent: 'end', children: jsx(Button$1, { onClick: resetHandler, formNoValidate: true, children: translate.t('submit_again') }) })] }));
|
|
7906
|
+
return (jsxs(Flex, { flexFlow: 'column', gap: "2", children: [jsxs(Alert.Root, { status: "success", children: [jsx(Alert.Indicator, {}), jsx(Alert.Content, { children: jsx(Alert.Title, { children: translate.t('submit_success') }) })] }), jsx(Flex, { justifyContent: 'end', children: jsx(Button$1, { onClick: resetHandler, formNoValidate: true, children: formButtonLabels?.submitAgain ?? translate.t('submit_again') }) })] }));
|
|
7187
7907
|
}
|
|
7188
7908
|
if (isConfirming) {
|
|
7189
7909
|
return (jsxs(Flex, { flexFlow: 'column', gap: "2", children: [jsx(Grid, { gap: 4, gridTemplateColumns: 'repeat(12, 1fr)', gridTemplateRows: 'repeat(12, max-content)', autoFlow: 'row', children: ordered.map((column) => {
|
|
@@ -7194,9 +7914,9 @@ const FormBody = () => {
|
|
|
7194
7914
|
properties: properties, prefix: ``, column }, `form-viewer-${column}`));
|
|
7195
7915
|
}) }), jsxs(Flex, { justifyContent: 'end', gap: '2', children: [jsx(Button$1, { onClick: () => {
|
|
7196
7916
|
setIsConfirming(false);
|
|
7197
|
-
}, variant: 'subtle', children: translate.t('cancel') }), jsx(Button$1, { onClick: () => {
|
|
7917
|
+
}, variant: 'subtle', children: formButtonLabels?.cancel ?? translate.t('cancel') }), jsx(Button$1, { onClick: () => {
|
|
7198
7918
|
onFormSubmit(validatedData);
|
|
7199
|
-
}, children: translate.t('confirm') })] }), isSubmiting && (jsx(Box, { pos: "absolute", inset: "0", bg: "bg/80", children: jsx(Center, { h: "full", children: jsx(Spinner, { color: "teal.500" }) }) })), isError && customErrorRenderer && customErrorRenderer(error)] }));
|
|
7919
|
+
}, children: formButtonLabels?.confirm ?? translate.t('confirm') })] }), isSubmiting && (jsx(Box, { pos: "absolute", inset: "0", bg: "bg/80", children: jsx(Center, { h: "full", children: jsx(Spinner, { color: "teal.500" }) }) })), isError && customErrorRenderer && customErrorRenderer(error)] }));
|
|
7200
7920
|
}
|
|
7201
7921
|
return (jsxs(Flex, { flexFlow: 'column', gap: "2", children: [jsx(Grid, { gap: "4", gridTemplateColumns: 'repeat(12, 1fr)', autoFlow: 'row', children: ordered.map((column) => {
|
|
7202
7922
|
return (jsx(ColumnRenderer
|
|
@@ -7206,7 +7926,7 @@ const FormBody = () => {
|
|
|
7206
7926
|
properties: properties, prefix: ``, parentRequired: schema.required, column }, `form-input-${column}`));
|
|
7207
7927
|
}) }), jsxs(Flex, { justifyContent: 'end', gap: "2", children: [showResetButton && (jsx(Button$1, { onClick: () => {
|
|
7208
7928
|
methods.reset();
|
|
7209
|
-
}, variant: 'subtle', children: translate.t('reset') })), showSubmitButton && jsx(SubmitButton, {})] }), isError && customErrorRenderer && customErrorRenderer(error)] }));
|
|
7929
|
+
}, variant: 'subtle', children: formButtonLabels?.reset ?? translate.t('reset') })), showSubmitButton && jsx(SubmitButton, {})] }), isError && customErrorRenderer && customErrorRenderer(error)] }));
|
|
7210
7930
|
};
|
|
7211
7931
|
|
|
7212
7932
|
const FormTitle = () => {
|
|
@@ -7525,7 +8245,6 @@ const TableDataDisplay = ({ colorPalette, emptyComponent, }) => {
|
|
|
7525
8245
|
return `minmax(${size}px, ${(size / totalWidths) * 100}%)`;
|
|
7526
8246
|
})
|
|
7527
8247
|
.join(' ');
|
|
7528
|
-
console.log({ columnWidths }, 'hadfg');
|
|
7529
8248
|
const cellProps = {
|
|
7530
8249
|
flex: '1 0 0%',
|
|
7531
8250
|
overflow: 'auto',
|
|
@@ -7542,22 +8261,22 @@ const TableDataDisplay = ({ colorPalette, emptyComponent, }) => {
|
|
|
7542
8261
|
}
|
|
7543
8262
|
return (jsxs(Grid, { templateColumns: `${columnWidths}`, overflow: 'auto', borderWidth: '1px', color: { base: 'colorPalette.900', _dark: 'colorPalette.100' }, borderColor: { base: 'colorPalette.200', _dark: 'colorPalette.800' }, colorPalette, children: [jsx(Grid, { templateColumns: `${columnWidths}`, column: `1/span ${columns.length}`, bg: { base: 'colorPalette.200', _dark: 'colorPalette.800' }, colorPalette, children: columnHeaders.map((header) => {
|
|
7544
8263
|
const columnDef = columnsMap[header];
|
|
7545
|
-
return (jsx(Box, { flex: '1 0 0%', paddingX: '2', py: '1', overflow: 'auto', textOverflow: 'ellipsis', children: columnDef?.meta?.displayName ?? header }));
|
|
7546
|
-
}) }), data.map((record) => {
|
|
7547
|
-
return (jsx(
|
|
8264
|
+
return (jsx(Box, { flex: '1 0 0%', paddingX: '2', py: '1', overflow: 'auto', textOverflow: 'ellipsis', children: columnDef?.meta?.displayName ?? header }, `chakra-table-header-${header}`));
|
|
8265
|
+
}) }), data.map((record, recordIndex) => {
|
|
8266
|
+
return (jsx(Box, { display: "contents", children: columnHeaders.map((header) => {
|
|
7548
8267
|
const { cell } = columnsMap[header];
|
|
7549
8268
|
const value = record[header];
|
|
7550
8269
|
if (!!record === false) {
|
|
7551
|
-
return (jsx(Box, { ...cellProps }));
|
|
8270
|
+
return (jsx(Box, { ...cellProps }, `chakra-table-cell-${recordIndex}-${header}`));
|
|
7552
8271
|
}
|
|
7553
8272
|
if (cell) {
|
|
7554
|
-
return (jsx(Box, { ...cellProps, children: cell({ row: { original: record } }) }));
|
|
8273
|
+
return (jsx(Box, { ...cellProps, children: cell({ row: { original: record } }) }, `chakra-table-cell-${recordIndex}-${header}`));
|
|
7555
8274
|
}
|
|
7556
8275
|
if (typeof value === 'object') {
|
|
7557
|
-
return (jsx(Box, { ...cellProps, children: jsx(RecordDisplay, { object: value }) }));
|
|
8276
|
+
return (jsx(Box, { ...cellProps, children: jsx(RecordDisplay, { object: value }) }, `chakra-table-cell-${recordIndex}-${header}`));
|
|
7558
8277
|
}
|
|
7559
|
-
return jsx(Box, { ...cellProps, children: value });
|
|
7560
|
-
}) }));
|
|
8278
|
+
return (jsx(Box, { ...cellProps, children: value }, `chakra-table-cell-${recordIndex}-${header}`));
|
|
8279
|
+
}) }, `chakra-table-record-${recordIndex}`));
|
|
7561
8280
|
})] }));
|
|
7562
8281
|
};
|
|
7563
8282
|
|
|
@@ -7710,19 +8429,19 @@ const DataDisplay = ({ variant = '' }) => {
|
|
|
7710
8429
|
return cell.id === `${rowId}_${column.id}`;
|
|
7711
8430
|
});
|
|
7712
8431
|
if (column.columns.length > 0) {
|
|
7713
|
-
return (jsxs(Card.Root, { margin: '1', gridColumn: 'span 12', children: [jsx(Card.Header, { color: 'gray.400', children: column.columnDef.meta?.displayName ?? column.id }), jsx(Card.Body, { display: 'grid', gap: '4', gridTemplateColumns: 'repeat(12, 1fr)', children: column.columns.map((
|
|
7714
|
-
if (!
|
|
7715
|
-
return
|
|
8432
|
+
return (jsxs(Card.Root, { margin: '1', gridColumn: 'span 12', children: [jsx(Card.Header, { color: 'gray.400', children: column.columnDef.meta?.displayName ?? column.id }), jsx(Card.Body, { display: 'grid', gap: '4', gridTemplateColumns: 'repeat(12, 1fr)', children: column.columns.map((subColumn) => {
|
|
8433
|
+
if (!subColumn.getIsVisible()) {
|
|
8434
|
+
return null;
|
|
7716
8435
|
}
|
|
7717
8436
|
const foundCell = row
|
|
7718
8437
|
.getVisibleCells()
|
|
7719
8438
|
.find((cell) => {
|
|
7720
|
-
return cell.id === `${rowId}_${
|
|
8439
|
+
return cell.id === `${rowId}_${subColumn.id}`;
|
|
7721
8440
|
});
|
|
7722
|
-
return jsx(CellRenderer, { cell: foundCell });
|
|
7723
|
-
}) })] }, `chakra-table-card-${
|
|
8441
|
+
return (jsx(CellRenderer, { cell: foundCell }, `chakra-table-cell-${rowId}-${subColumn.id}`));
|
|
8442
|
+
}) })] }, `chakra-table-card-${rowId}-${column.id}`));
|
|
7724
8443
|
}
|
|
7725
|
-
return jsx(CellRenderer, { cell: childCell });
|
|
8444
|
+
return (jsx(CellRenderer, { cell: childCell }, `chakra-table-cell-${rowId}-${column.id}`));
|
|
7726
8445
|
}) }) }, `chakra-table-card-${rowId}`));
|
|
7727
8446
|
}) }));
|
|
7728
8447
|
};
|
|
@@ -8017,4 +8736,4 @@ function DataTableServer({ columns, enableRowSelection = true, enableMultiRowSel
|
|
|
8017
8736
|
}, children: jsx(DataTableServerContext.Provider, { value: { url, query }, children: children }) }));
|
|
8018
8737
|
}
|
|
8019
8738
|
|
|
8020
|
-
export { CardHeader, DataDisplay, DataTable, DataTableServer, DefaultCardTitle, DefaultForm, DefaultTable, DefaultTableServer, DensityToggleButton, EditSortingButton, EmptyState, ErrorAlert, FilterDialog, FormBody, FormRoot, FormTitle, GlobalFilter, MediaLibraryBrowser, PageSizeControl, Pagination, RecordDisplay, ReloadButton, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, RowCountText, SelectAllRowsToggle, Table, TableBody, TableCardContainer, TableCards, TableComponent, TableControls, TableDataDisplay, TableFilter, TableFilterTags, TableFooter, TableHeader, TableLoadingComponent, TableSelector, TableSorter, TableViewer, TextCell, ViewDialog, buildErrorMessages, buildFieldErrors, buildRequiredErrors, convertToAjvErrorsFormat, createErrorMessage, getColumns, getMultiDates, getRangeDates, idPickerSanityCheck, useDataTable, useDataTableContext, useDataTableServer, useForm, widthSanityCheck };
|
|
8739
|
+
export { CardHeader, DataDisplay, DataTable, DataTableServer, DatePickerInput, DefaultCardTitle, DefaultForm, DefaultTable, DefaultTableServer, DensityToggleButton, EditSortingButton, EmptyState, ErrorAlert, FilterDialog, FormBody, FormRoot, FormTitle, GlobalFilter, MediaLibraryBrowser, PageSizeControl, Pagination, RecordDisplay, ReloadButton, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, RowCountText, SelectAllRowsToggle, Table, TableBody, TableCardContainer, TableCards, TableComponent, TableControls, TableDataDisplay, TableFilter, TableFilterTags, TableFooter, TableHeader, TableLoadingComponent, TableSelector, TableSorter, TableViewer, TextCell, ViewDialog, buildErrorMessages, buildFieldErrors, buildRequiredErrors, convertToAjvErrorsFormat, createErrorMessage, defaultRenderDisplay, getColumns, getMultiDates, getRangeDates, idPickerSanityCheck, useDataTable, useDataTableContext, useDataTableServer, useForm, widthSanityCheck };
|