feat: review single AI-generated events before saving
This commit is contained in:
@@ -22,6 +22,7 @@ interface EventDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
editingId: string | null;
|
||||
dialogSource: "manual" | "ai";
|
||||
title: string;
|
||||
setTitle: (title: string) => void;
|
||||
description: string;
|
||||
@@ -46,6 +47,7 @@ export const EventDialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
editingId,
|
||||
dialogSource,
|
||||
title,
|
||||
setTitle,
|
||||
description,
|
||||
@@ -65,6 +67,19 @@ export const EventDialog = ({
|
||||
onSave,
|
||||
onReset,
|
||||
}: EventDialogProps) => {
|
||||
const isAiDraft = dialogSource === "ai" && !editingId;
|
||||
const titleText = editingId
|
||||
? "Edit Event"
|
||||
: isAiDraft
|
||||
? "Review AI Draft"
|
||||
: "New Event";
|
||||
const descriptionText = editingId
|
||||
? "Update the event details below. Title and start date are required."
|
||||
: isAiDraft
|
||||
? "AI filled in this event from your prompt. Review each field, then save when it looks right."
|
||||
: "Create an event manually. Title and start date are required.";
|
||||
const saveLabel = editingId ? "Update Event" : "Save Event";
|
||||
|
||||
const handleOpenChange = (val: boolean) => {
|
||||
if (!val) onReset();
|
||||
onOpenChange(val);
|
||||
@@ -94,52 +109,67 @@ export const EventDialog = ({
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogContent className="glass-strong max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-base">
|
||||
{editingId ? "Edit Event" : "New Event"}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="sr-only">
|
||||
Fill in the event details below. Title and start date are required.
|
||||
</DialogDescription>
|
||||
<DialogTitle className="text-base">{titleText}</DialogTitle>
|
||||
<DialogDescription>{descriptionText}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-3">
|
||||
<Input
|
||||
id="event-title"
|
||||
name="title"
|
||||
placeholder="Event title"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
className="font-medium"
|
||||
/>
|
||||
{isAiDraft && (
|
||||
<div className="rounded-md border border-primary/20 bg-primary/5 px-3 py-2 text-xs leading-relaxed text-primary">
|
||||
This draft was generated from natural language. Double-check
|
||||
dates, times, location, recurrence, and links before saving.
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Textarea
|
||||
id="event-description"
|
||||
name="description"
|
||||
className="field-sizing-content min-h-[60px] max-h-40 resize-none placeholder:text-muted-foreground/50"
|
||||
placeholder="Add a description..."
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
/>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="event-title">Title</Label>
|
||||
<Input
|
||||
id="event-title"
|
||||
name="title"
|
||||
placeholder="Event title"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
className="font-medium"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="event-description">Description / notes</Label>
|
||||
<Textarea
|
||||
id="event-description"
|
||||
name="description"
|
||||
className="field-sizing-content min-h-[60px] max-h-40 resize-none placeholder:text-muted-foreground/50"
|
||||
placeholder="Add a description..."
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="relative">
|
||||
<LucideMapPin className="absolute left-3 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground/50" />
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="event-location">Location</Label>
|
||||
<div className="relative">
|
||||
<LucideMapPin className="absolute left-3 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground/50" />
|
||||
<Input
|
||||
id="event-location"
|
||||
name="location"
|
||||
placeholder="Location"
|
||||
value={location}
|
||||
onChange={(e) => setLocation(e.target.value)}
|
||||
className="pl-8"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="event-url">URL</Label>
|
||||
<Input
|
||||
id="event-location"
|
||||
name="location"
|
||||
placeholder="Location"
|
||||
value={location}
|
||||
onChange={(e) => setLocation(e.target.value)}
|
||||
className="pl-8"
|
||||
id="event-url"
|
||||
name="url"
|
||||
placeholder="URL"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
id="event-url"
|
||||
name="url"
|
||||
placeholder="URL"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<RecurrencePicker
|
||||
@@ -195,10 +225,16 @@ export const EventDialog = ({
|
||||
</div>
|
||||
|
||||
<DialogFooter className="gap-2 sm:gap-0">
|
||||
<Button variant="ghost" onClick={() => handleOpenChange(false)}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => handleOpenChange(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={onSave}>{editingId ? "Update" : "Create"}</Button>
|
||||
<Button type="button" onClick={onSave}>
|
||||
{saveLabel}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
Reference in New Issue
Block a user