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

View File

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

View File

@ -153,7 +153,7 @@ const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
bordered = true,
disabledTime,
hideDisabledOptions = false,
inputReadOnly = false,
inputReadOnly = true,
use12Hours = false,
size = 'middle',
status,
@ -495,47 +495,48 @@ const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
onOpenChange={handleOpenChange}
modal={false} // Use non-modal dialog box, consistent with Ant Design behavior
>
<div className="relative">
<Input
type="text"
value={inputValue}
onChange={handleInputChange}
placeholder={placeholder}
disabled={disabled}
readOnly={inputReadOnly}
className={cn(
'pl-3 pr-8 py-2 font-normal',
size === 'large' && 'h-10 text-base',
size === 'small' && 'h-8 text-sm',
status === 'error' && 'border-red-500',
status === 'warning' && '!border-yellow-500',
!bordered && 'border-transparent',
'cursor-pointer',
)}
autoFocus={autoFocus}
/>
<div className="absolute right-3 top-1/2 transform -translate-y-1/2 flex items-center">
{allowClear && value && inputValue && (
<button
type="button"
onClick={(e) => {
e.stopPropagation();
handleClear();
}}
className="mr-2 text-muted-foreground hover:text-foreground"
>
{clearIcon || '✕'}
</button>
)}
<PopoverTrigger asChild>
<PopoverTrigger asChild>
<div className="relative">
<Input
type="text"
value={inputValue}
onChange={handleInputChange}
placeholder={placeholder}
disabled={disabled}
readOnly={inputReadOnly}
className={cn(
'pl-3 pr-8 py-2 font-normal',
size === 'large' && 'h-10 text-base',
size === 'small' && 'h-8 text-sm',
status === 'error' && 'border-red-500',
status === 'warning' && '!border-yellow-500',
!bordered && 'border-transparent',
'cursor-pointer',
)}
autoFocus={autoFocus}
/>
<div className="absolute right-3 top-1/2 transform -translate-y-1/2 flex items-center">
{allowClear && value && inputValue && (
<button
type="button"
onClick={(e) => {
e.stopPropagation();
handleClear();
}}
className="mr-2 text-muted-foreground hover:text-foreground"
>
{clearIcon || '✕'}
</button>
)}
<div className="cursor-pointer">
{suffixIcon || (
<Clock size={16} className="text-muted-foreground" />
)}
</div>
</PopoverTrigger>
</div>
</div>
</div>
</PopoverTrigger>
<PopoverContent
className={cn('w-auto p-3', popupClassName)}
align={

View File

@ -67,23 +67,27 @@ export const useMetadataColumns = ({
setEditingValue({ field, value, newValue: value });
};
const saveEditedValue = useCallback(() => {
if (editingValue) {
setTableData((prev) => {
return prev.map((row) => {
if (row.field === editingValue.field) {
const updatedValues = row.values.map((v) =>
v === editingValue.value ? editingValue.newValue : v,
);
return { ...row, values: updatedValues };
}
return row;
const saveEditedValue = useCallback(
(newValue?: { field: string; value: string; newValue: string }) => {
const realValue = newValue || editingValue;
if (realValue) {
setTableData((prev) => {
return prev.map((row) => {
if (row.field === realValue.field) {
const updatedValues = row.values.map((v) =>
v === realValue.value ? realValue.newValue : v,
);
return { ...row, values: updatedValues };
}
return row;
});
});
});
setEditingValue(null);
setShouldSave(true);
}
}, [editingValue, setTableData, setShouldSave]);
setEditingValue(null);
setShouldSave(true);
}
},
[editingValue, setTableData, setShouldSave],
);
const cancelEditValue = () => {
setEditingValue(null);
@ -214,15 +218,23 @@ export const useMetadataColumns = ({
<DateInput
value={new Date(editingValue.newValue)}
onChange={(value) => {
setEditingValue({
console.log('value', value);
const newValue = {
...editingValue,
newValue: formatDate(
value,
'YYYY-MM-DDTHH:mm:ss',
),
});
};
setEditingValue(newValue);
saveEditedValue(newValue);
// onValueChange(index, formatDate(value), true);
}}
// openChange={(open) => {
// console.log('open', open);
// if (!open) {
// }
// }}
showTimeSelect={true}
/>
)}
@ -261,7 +273,11 @@ export const useMetadataColumns = ({
aria-label="Edit"
>
<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 && (
<Button
variant={'delete'}