mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-26 17:26:23 +02:00
feat: implement date and time picker in HITL edit panel
- Added a DateTimePickerField component to allow users to select date and time in the HITL edit panel. - Updated the EmailsTagField to use a ref for onChange to improve performance. - Modified the ExtraField type to support "datetime-local" for better event management in Google Calendar tools.
This commit is contained in:
parent
4b54826d78
commit
5e23949af6
3 changed files with 114 additions and 8 deletions
|
|
@ -1,19 +1,22 @@
|
|||
"use client";
|
||||
|
||||
import { TagInput, type Tag as TagType } from "emblor";
|
||||
import { format } from "date-fns";
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import { XIcon } from "lucide-react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { CalendarIcon, XIcon } from "lucide-react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
closeHitlEditPanelAtom,
|
||||
hitlEditPanelAtom,
|
||||
} from "@/atoms/chat/hitl-edit-panel.atom";
|
||||
import type { ExtraField } from "@/atoms/chat/hitl-edit-panel.atom";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { PlateEditor } from "@/components/editor/plate-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Drawer, DrawerContent, DrawerHandle, DrawerTitle } from "@/components/ui/drawer";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { useMediaQuery } from "@/hooks/use-media-query";
|
||||
|
||||
|
|
@ -44,14 +47,16 @@ function EmailsTagField({
|
|||
const [tags, setTags] = useState<TagType[]>(() => parseEmailsToTags(value));
|
||||
const [activeTagIndex, setActiveTagIndex] = useState<number | null>(null);
|
||||
const isInitialMount = useRef(true);
|
||||
const onChangeRef = useRef(onChange);
|
||||
onChangeRef.current = onChange;
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialMount.current) {
|
||||
isInitialMount.current = false;
|
||||
return;
|
||||
}
|
||||
onChange(tagsToEmailString(tags));
|
||||
}, [tags, onChange]);
|
||||
onChangeRef.current(tagsToEmailString(tags));
|
||||
}, [tags]);
|
||||
|
||||
const handleSetTags = useCallback(
|
||||
(newTags: TagType[] | ((prev: TagType[]) => TagType[])) => {
|
||||
|
|
@ -97,6 +102,103 @@ function EmailsTagField({
|
|||
);
|
||||
}
|
||||
|
||||
function parseDateTimeValue(value: string): { date: Date | undefined; time: string } {
|
||||
if (!value) return { date: undefined, time: "09:00" };
|
||||
try {
|
||||
const d = new Date(value);
|
||||
if (Number.isNaN(d.getTime())) return { date: undefined, time: "09:00" };
|
||||
return {
|
||||
date: d,
|
||||
time: format(d, "HH:mm"),
|
||||
};
|
||||
} catch {
|
||||
return { date: undefined, time: "09:00" };
|
||||
}
|
||||
}
|
||||
|
||||
function buildLocalDateTimeString(date: Date | undefined, time: string): string {
|
||||
if (!date) return "";
|
||||
const [hours, minutes] = time.split(":").map(Number);
|
||||
const combined = new Date(date);
|
||||
combined.setHours(hours ?? 9, minutes ?? 0, 0, 0);
|
||||
const y = combined.getFullYear();
|
||||
const m = String(combined.getMonth() + 1).padStart(2, "0");
|
||||
const d = String(combined.getDate()).padStart(2, "0");
|
||||
const h = String(combined.getHours()).padStart(2, "0");
|
||||
const min = String(combined.getMinutes()).padStart(2, "0");
|
||||
return `${y}-${m}-${d}T${h}:${min}:00`;
|
||||
}
|
||||
|
||||
function DateTimePickerField({
|
||||
id,
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
id: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
}) {
|
||||
const parsed = useMemo(() => parseDateTimeValue(value), [value]);
|
||||
const [selectedDate, setSelectedDate] = useState<Date | undefined>(parsed.date);
|
||||
const [time, setTime] = useState(parsed.time);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleDateSelect = useCallback(
|
||||
(day: Date | undefined) => {
|
||||
setSelectedDate(day);
|
||||
onChange(buildLocalDateTimeString(day, time));
|
||||
setOpen(false);
|
||||
},
|
||||
[time, onChange]
|
||||
);
|
||||
|
||||
const handleTimeChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newTime = e.target.value;
|
||||
setTime(newTime);
|
||||
onChange(buildLocalDateTimeString(selectedDate, newTime));
|
||||
},
|
||||
[selectedDate, onChange]
|
||||
);
|
||||
|
||||
const displayLabel = selectedDate
|
||||
? `${format(selectedDate, "MMM d, yyyy")} at ${time}`
|
||||
: "Pick date & time";
|
||||
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
id={id}
|
||||
type="button"
|
||||
className="flex-1 flex items-center gap-2 h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring"
|
||||
>
|
||||
<CalendarIcon className="size-3.5 text-muted-foreground shrink-0" />
|
||||
<span className={selectedDate ? "text-foreground" : "text-muted-foreground"}>
|
||||
{displayLabel}
|
||||
</span>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={selectedDate}
|
||||
onSelect={handleDateSelect}
|
||||
defaultMonth={selectedDate}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Input
|
||||
type="time"
|
||||
value={time}
|
||||
onChange={handleTimeChange}
|
||||
className="w-[120px] text-sm shrink-0 pl-1.5 [&::-webkit-calendar-picker-indicator]:order-first [&::-webkit-calendar-picker-indicator]:mr-1"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function HitlEditPanelContent({
|
||||
title: initialTitle,
|
||||
content: initialContent,
|
||||
|
|
@ -173,6 +275,12 @@ export function HitlEditPanelContent({
|
|||
onChange={(v) => handleExtraFieldChange(field.key, v)}
|
||||
placeholder={`Add ${field.label.toLowerCase()}`}
|
||||
/>
|
||||
) : field.type === "datetime-local" ? (
|
||||
<DateTimePickerField
|
||||
id={`extra-field-${field.key}`}
|
||||
value={extraFieldValues[field.key] ?? ""}
|
||||
onChange={(v) => handleExtraFieldChange(field.key, v)}
|
||||
/>
|
||||
) : field.type === "textarea" ? (
|
||||
<Textarea
|
||||
id={`extra-field-${field.key}`}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue