mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-01-23 19:46:39 +08:00
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:
@ -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>
|
||||
|
||||
@ -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}>
|
||||
|
||||
@ -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={
|
||||
|
||||
@ -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'}
|
||||
|
||||
Reference in New Issue
Block a user