lusa-back-planner/src/components/AddStageDialog.tsx
2026-03-06 13:25:24 -05:00

199 lines
5.8 KiB
TypeScript

import { useState, useMemo } from "react";
import { format } from "date-fns";
import { Plus, CalendarIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import { isNonWorkDay } from "@/lib/workdays";
import type { GeneratedMilestone } from "@/lib/templates";
const STAGE_PRESETS = [
{ label: "Client Review", description: "Client reviews deliverable" },
{ label: "Revisions", description: "Incorporate feedback and polish" },
{ label: "Final QA", description: "Quality assurance and sign-off" },
] as const;
interface AddStageDialogProps {
dueDate: Date;
holidays: Set<string>;
onAdd: (milestone: GeneratedMilestone) => void;
}
const AddStageDialog = ({ dueDate, holidays, onAdd }: AddStageDialogProps) => {
const [open, setOpen] = useState(false);
const [step, setStep] = useState<"pick" | "custom" | "date">("pick");
const [label, setLabel] = useState("");
const [description, setDescription] = useState("");
const [date, setDate] = useState<Date | undefined>();
const today = useMemo(() => {
const d = new Date();
d.setHours(0, 0, 0, 0);
return d;
}, []);
const reset = () => {
setStep("pick");
setLabel("");
setDescription("");
setDate(undefined);
};
const handlePreset = (preset: (typeof STAGE_PRESETS)[number]) => {
setLabel(preset.label);
setDescription(preset.description);
setStep("date");
};
const handleCustomNext = () => {
if (label.trim()) setStep("date");
};
const handleSubmit = () => {
if (!date || !label.trim()) return;
const d = new Date(date);
d.setHours(0, 0, 0, 0);
onAdd({
label: label.trim(),
description: description.trim() || label.trim(),
date: d,
daysBeforeDue: Math.round(
(dueDate.getTime() - d.getTime()) / (1000 * 60 * 60 * 24)
),
isPast: d < today,
});
reset();
setOpen(false);
};
return (
<Popover
open={open}
onOpenChange={(v) => {
setOpen(v);
if (!v) reset();
}}
>
<PopoverTrigger asChild>
<Button
variant="outline"
size="sm"
className="gap-1.5 text-xs uppercase tracking-wide"
>
<Plus className="h-3.5 w-3.5" />
Add Stage
</Button>
</PopoverTrigger>
<PopoverContent className="w-72 p-0" align="start">
{step === "pick" && (
<div className="p-3 space-y-1">
<p className="text-xs font-medium text-muted-foreground mb-2 uppercase tracking-wide">
Choose stage type
</p>
{STAGE_PRESETS.map((preset) => (
<button
key={preset.label}
onClick={() => handlePreset(preset)}
className="w-full text-left px-3 py-2 rounded-md text-sm hover:bg-accent transition-colors"
>
{preset.label}
</button>
))}
<button
onClick={() => setStep("custom")}
className="w-full text-left px-3 py-2 rounded-md text-sm hover:bg-accent transition-colors text-muted-foreground"
>
Custom
</button>
</div>
)}
{step === "custom" && (
<div className="p-3 space-y-3">
<div className="space-y-1.5">
<Label className="text-xs">Label</Label>
<Input
placeholder="e.g. Internal Review"
value={label}
onChange={(e) => setLabel(e.target.value)}
className="h-8 text-sm"
/>
</div>
<div className="space-y-1.5">
<Label className="text-xs">Description</Label>
<Input
placeholder="Optional description"
value={description}
onChange={(e) => setDescription(e.target.value)}
className="h-8 text-sm"
/>
</div>
<div className="flex gap-2">
<Button
variant="ghost"
size="sm"
onClick={() => {
reset();
setStep("pick");
}}
className="text-xs"
>
Back
</Button>
<Button
size="sm"
onClick={handleCustomNext}
disabled={!label.trim()}
className="text-xs"
>
Next
</Button>
</div>
</div>
)}
{step === "date" && (
<div className="p-3 space-y-3">
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
{label} Pick a date
</p>
<Calendar
mode="single"
selected={date}
onSelect={setDate}
disabled={(d) => d < today || isNonWorkDay(d, holidays)}
className={cn("p-0 pointer-events-auto")}
/>
<div className="flex gap-2 justify-end">
<Button
variant="ghost"
size="sm"
onClick={() => setStep("pick")}
className="text-xs"
>
Back
</Button>
<Button
size="sm"
onClick={handleSubmit}
disabled={!date}
className="text-xs"
>
Add
</Button>
</div>
</div>
)}
</PopoverContent>
</Popover>
);
};
export default AddStageDialog;