mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-02-04 01:25:07 +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 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>
|
||||||
|
|||||||
@ -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}>
|
||||||
|
|||||||
@ -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,47 +495,48 @@ 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
|
||||||
>
|
>
|
||||||
<div className="relative">
|
<PopoverTrigger asChild>
|
||||||
<Input
|
<div className="relative">
|
||||||
type="text"
|
<Input
|
||||||
value={inputValue}
|
type="text"
|
||||||
onChange={handleInputChange}
|
value={inputValue}
|
||||||
placeholder={placeholder}
|
onChange={handleInputChange}
|
||||||
disabled={disabled}
|
placeholder={placeholder}
|
||||||
readOnly={inputReadOnly}
|
disabled={disabled}
|
||||||
className={cn(
|
readOnly={inputReadOnly}
|
||||||
'pl-3 pr-8 py-2 font-normal',
|
className={cn(
|
||||||
size === 'large' && 'h-10 text-base',
|
'pl-3 pr-8 py-2 font-normal',
|
||||||
size === 'small' && 'h-8 text-sm',
|
size === 'large' && 'h-10 text-base',
|
||||||
status === 'error' && 'border-red-500',
|
size === 'small' && 'h-8 text-sm',
|
||||||
status === 'warning' && '!border-yellow-500',
|
status === 'error' && 'border-red-500',
|
||||||
!bordered && 'border-transparent',
|
status === 'warning' && '!border-yellow-500',
|
||||||
'cursor-pointer',
|
!bordered && 'border-transparent',
|
||||||
)}
|
'cursor-pointer',
|
||||||
autoFocus={autoFocus}
|
)}
|
||||||
/>
|
autoFocus={autoFocus}
|
||||||
<div className="absolute right-3 top-1/2 transform -translate-y-1/2 flex items-center">
|
/>
|
||||||
{allowClear && value && inputValue && (
|
<div className="absolute right-3 top-1/2 transform -translate-y-1/2 flex items-center">
|
||||||
<button
|
{allowClear && value && inputValue && (
|
||||||
type="button"
|
<button
|
||||||
onClick={(e) => {
|
type="button"
|
||||||
e.stopPropagation();
|
onClick={(e) => {
|
||||||
handleClear();
|
e.stopPropagation();
|
||||||
}}
|
handleClear();
|
||||||
className="mr-2 text-muted-foreground hover:text-foreground"
|
}}
|
||||||
>
|
className="mr-2 text-muted-foreground hover:text-foreground"
|
||||||
{clearIcon || '✕'}
|
>
|
||||||
</button>
|
{clearIcon || '✕'}
|
||||||
)}
|
</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>
|
||||||
</PopoverTrigger>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</PopoverTrigger>
|
||||||
<PopoverContent
|
<PopoverContent
|
||||||
className={cn('w-auto p-3', popupClassName)}
|
className={cn('w-auto p-3', popupClassName)}
|
||||||
align={
|
align={
|
||||||
|
|||||||
@ -67,23 +67,27 @@ 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 }) => {
|
||||||
setTableData((prev) => {
|
const realValue = newValue || editingValue;
|
||||||
return prev.map((row) => {
|
if (realValue) {
|
||||||
if (row.field === editingValue.field) {
|
setTableData((prev) => {
|
||||||
const updatedValues = row.values.map((v) =>
|
return prev.map((row) => {
|
||||||
v === editingValue.value ? editingValue.newValue : v,
|
if (row.field === realValue.field) {
|
||||||
);
|
const updatedValues = row.values.map((v) =>
|
||||||
return { ...row, values: updatedValues };
|
v === realValue.value ? realValue.newValue : v,
|
||||||
}
|
);
|
||||||
return row;
|
return { ...row, values: updatedValues };
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
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'}
|
||||||
|
|||||||
Reference in New Issue
Block a user