Fix: Metadata time Picker (#12796)

### What problem does this PR solve?

Fix: Metadata time Picker

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
chanx
2026-01-23 16:55:43 +08:00
committed by GitHub
parent e1df82946e
commit 11470906cf
4 changed files with 143 additions and 77 deletions

View File

@ -10,6 +10,8 @@ import { Locale } from 'date-fns';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { Calendar as CalendarIcon } from 'lucide-react'; import { Calendar as CalendarIcon } from 'lucide-react';
import * as React from 'react'; import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from './button';
import { TimePicker } from './time-picker'; import { TimePicker } from './time-picker';
// import TimePicker from 'react-time-picker'; // import TimePicker from 'react-time-picker';
interface DateInputProps extends Omit< interface DateInputProps extends Omit<
@ -22,9 +24,8 @@ interface DateInputProps extends Omit<
dateFormat?: string; dateFormat?: string;
timeFormat?: string; timeFormat?: string;
showTimeSelectOnly?: boolean; showTimeSelectOnly?: boolean;
showTimeInput?: boolean;
timeInputLabel?: string;
locale?: Locale; // Support for internationalization locale?: Locale; // Support for internationalization
openChange?: (open: boolean) => void;
} }
const DateInput = React.forwardRef<HTMLInputElement, DateInputProps>( const DateInput = React.forwardRef<HTMLInputElement, DateInputProps>(
@ -37,41 +38,45 @@ const DateInput = React.forwardRef<HTMLInputElement, DateInputProps>(
timeFormat = 'HH:mm:ss', timeFormat = 'HH:mm:ss',
showTimeSelect = false, showTimeSelect = false,
showTimeSelectOnly = false, showTimeSelectOnly = false,
showTimeInput = false, openChange,
timeInputLabel = '',
...props ...props
}, },
ref, ref,
) => { ) => {
const { t } = useTranslation();
const [selectedDate, setSelectedDate] = React.useState<Date | undefined>(
value,
);
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const handleDateSelect = (date: Date | undefined) => { const handleDateSelect = (date: Date | undefined) => {
if (value) { if (selectedDate) {
const valueDate = dayjs(value); const valueDate = dayjs(selectedDate);
date?.setHours(valueDate.hour()); date?.setHours(valueDate.hour());
date?.setMinutes(valueDate.minute()); date?.setMinutes(valueDate.minute());
date?.setSeconds(valueDate.second()); date?.setSeconds(valueDate.second());
} }
onChange?.(date); setSelectedDate(date);
// setOpen(false); // onChange?.(date);
}; };
const handleTimeSelect = (date: Date | undefined) => { const handleTimeSelect = (date: Date | undefined) => {
const valueDate = dayjs(value); const valueDate = dayjs(selectedDate);
if (value) { if (selectedDate) {
date?.setFullYear(valueDate.year()); date?.setFullYear(valueDate.year());
date?.setMonth(valueDate.month()); date?.setMonth(valueDate.month());
date?.setDate(valueDate.date()); date?.setDate(valueDate.date());
} }
if (date) { if (date) {
onChange?.(date); // onChange?.(date);
setSelectedDate(date);
} else { } else {
valueDate?.hour(0); valueDate?.hour(0);
valueDate?.minute(0); valueDate?.minute(0);
valueDate?.second(0); valueDate?.second(0);
onChange?.(valueDate.toDate()); // onChange?.(valueDate.toDate());
setSelectedDate(valueDate.toDate());
} }
// setOpen(false);
}; };
// Determine display format based on the type of date picker // Determine display format based on the type of date picker
@ -84,14 +89,23 @@ const DateInput = React.forwardRef<HTMLInputElement, DateInputProps>(
// Format the date according to the specified format // Format the date according to the specified format
const formattedValue = React.useMemo(() => { const formattedValue = React.useMemo(() => {
return value && !isNaN(value.getTime()) return selectedDate && !isNaN(selectedDate.getTime())
? dayjs(value).format(displayFormat) ? dayjs(selectedDate).format(displayFormat)
: ''; : '';
}, [value, displayFormat]); }, [selectedDate, displayFormat]);
const handleOpenChange = (open: boolean) => {
setOpen(open);
openChange?.(open);
};
return ( return (
<div className="grid gap-2"> <div className="grid gap-2">
<Popover open={open} onOpenChange={setOpen}> <Popover
open={open}
onOpenChange={handleOpenChange}
disableOutsideClick
>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<div className="relative"> <div className="relative">
<Input <Input
@ -113,12 +127,12 @@ const DateInput = React.forwardRef<HTMLInputElement, DateInputProps>(
<PopoverContent className="w-auto p-2" align="start"> <PopoverContent className="w-auto p-2" align="start">
<Calendar <Calendar
mode="single" mode="single"
selected={value} selected={selectedDate}
onSelect={handleDateSelect} onSelect={handleDateSelect}
/> />
{showTimeSelect && ( {showTimeSelect && (
<TimePicker <TimePicker
value={value} value={selectedDate}
onChange={(value: Date | undefined) => { onChange={(value: Date | undefined) => {
handleTimeSelect(value); handleTimeSelect(value);
}} }}
@ -126,6 +140,29 @@ const DateInput = React.forwardRef<HTMLInputElement, DateInputProps>(
/> />
// <TimePicker onChange={onChange} value={value} /> // <TimePicker onChange={onChange} value={value} />
)} )}
<div className="w-full flex justify-end mt-2">
<Button
variant="ghost"
type="button"
className="text-sm mr-2"
onClick={() => {
onChange?.(value);
handleOpenChange(false);
}}
>
{t('common.cancel')}
</Button>
<Button
type="button"
className="text-sm text-text-primary-inverse "
onClick={() => {
onChange?.(selectedDate);
handleOpenChange(false);
}}
>
{t('common.confirm')}
</Button>
</div>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
</div> </div>

View File

@ -5,20 +5,32 @@ import * as React from 'react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
const Popover = (props: PopoverPrimitive.PopoverProps) => { interface PopoverProps extends PopoverPrimitive.PopoverProps {
const { children, open: openState, onOpenChange } = props; disableOutsideClick?: boolean;
}
const Popover = ({
children,
open: openState,
onOpenChange,
disableOutsideClick = false,
}: PopoverProps) => {
const [open, setOpen] = React.useState(true); const [open, setOpen] = React.useState(true);
React.useEffect(() => { React.useEffect(() => {
setOpen(!!openState); setOpen(!!openState);
}, [openState]); }, [openState]);
const handleOnOpenChange = React.useCallback( const handleOnOpenChange = React.useCallback(
(e: boolean) => { (e: boolean) => {
if (disableOutsideClick && !e) {
return;
}
if (onOpenChange) { if (onOpenChange) {
onOpenChange?.(e); onOpenChange?.(e);
} }
setOpen(e); setOpen(e);
}, },
[onOpenChange], [onOpenChange, disableOutsideClick],
); );
return ( return (
<PopoverPrimitive.Root open={open} onOpenChange={handleOnOpenChange}> <PopoverPrimitive.Root open={open} onOpenChange={handleOnOpenChange}>

View File

@ -153,7 +153,7 @@ const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
bordered = true, bordered = true,
disabledTime, disabledTime,
hideDisabledOptions = false, hideDisabledOptions = false,
inputReadOnly = false, inputReadOnly = true,
use12Hours = false, use12Hours = false,
size = 'middle', size = 'middle',
status, status,
@ -495,6 +495,7 @@ const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
onOpenChange={handleOpenChange} onOpenChange={handleOpenChange}
modal={false} // Use non-modal dialog box, consistent with Ant Design behavior modal={false} // Use non-modal dialog box, consistent with Ant Design behavior
> >
<PopoverTrigger asChild>
<div className="relative"> <div className="relative">
<Input <Input
type="text" type="text"
@ -527,15 +528,15 @@ const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
{clearIcon || '✕'} {clearIcon || '✕'}
</button> </button>
)} )}
<PopoverTrigger asChild>
<div className="cursor-pointer"> <div className="cursor-pointer">
{suffixIcon || ( {suffixIcon || (
<Clock size={16} className="text-muted-foreground" /> <Clock size={16} className="text-muted-foreground" />
)} )}
</div> </div>
</div>
</div>
</PopoverTrigger> </PopoverTrigger>
</div>
</div>
<PopoverContent <PopoverContent
className={cn('w-auto p-3', popupClassName)} className={cn('w-auto p-3', popupClassName)}
align={ align={

View File

@ -67,13 +67,15 @@ export const useMetadataColumns = ({
setEditingValue({ field, value, newValue: value }); setEditingValue({ field, value, newValue: value });
}; };
const saveEditedValue = useCallback(() => { const saveEditedValue = useCallback(
if (editingValue) { (newValue?: { field: string; value: string; newValue: string }) => {
const realValue = newValue || editingValue;
if (realValue) {
setTableData((prev) => { setTableData((prev) => {
return prev.map((row) => { return prev.map((row) => {
if (row.field === editingValue.field) { if (row.field === realValue.field) {
const updatedValues = row.values.map((v) => const updatedValues = row.values.map((v) =>
v === editingValue.value ? editingValue.newValue : v, v === realValue.value ? realValue.newValue : v,
); );
return { ...row, values: updatedValues }; return { ...row, values: updatedValues };
} }
@ -83,7 +85,9 @@ export const useMetadataColumns = ({
setEditingValue(null); setEditingValue(null);
setShouldSave(true); setShouldSave(true);
} }
}, [editingValue, setTableData, setShouldSave]); },
[editingValue, setTableData, setShouldSave],
);
const cancelEditValue = () => { const cancelEditValue = () => {
setEditingValue(null); setEditingValue(null);
@ -214,15 +218,23 @@ export const useMetadataColumns = ({
<DateInput <DateInput
value={new Date(editingValue.newValue)} value={new Date(editingValue.newValue)}
onChange={(value) => { onChange={(value) => {
setEditingValue({ console.log('value', value);
const newValue = {
...editingValue, ...editingValue,
newValue: formatDate( newValue: formatDate(
value, value,
'YYYY-MM-DDTHH:mm:ss', 'YYYY-MM-DDTHH:mm:ss',
), ),
}); };
setEditingValue(newValue);
saveEditedValue(newValue);
// onValueChange(index, formatDate(value), true); // onValueChange(index, formatDate(value), true);
}} }}
// openChange={(open) => {
// console.log('open', open);
// if (!open) {
// }
// }}
showTimeSelect={true} showTimeSelect={true}
/> />
)} )}
@ -261,7 +273,11 @@ export const useMetadataColumns = ({
aria-label="Edit" aria-label="Edit"
> >
<div className="flex gap-1 items-center"> <div className="flex gap-1 items-center">
<div className="text-sm truncate max-w-24">{value}</div> <div className="text-sm truncate max-w-24">
{row.original.valueType === metadataValueTypeEnum.time
? formatDate(value, 'DD/MM/YYYY HH:mm:ss')
: value}
</div>
{isDeleteSingleValue && ( {isDeleteSingleValue && (
<Button <Button
variant={'delete'} variant={'delete'}