Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-17 02:56:44 +00:00
co-authored by renee-png
parent ca5b7ea01a
commit 9536ac8e02
6 changed files with 750 additions and 7 deletions
@@ -0,0 +1,171 @@
import { useState, useEffect } from "react";
import { supabase } from "@/integrations/supabase/client";
import { useAuth } from "@/lib/auth";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Calendar } from "@/components/ui/calendar";
import { CalendarIcon, Loader2 } from "lucide-react";
import { format, addDays } from "date-fns";
import { toast } from "sonner";
import { createNotifications } from "@/lib/notifications";
interface Template {
id: string;
name: string;
}
export function ApplyWorkflowDialog({
open,
onOpenChange,
caseId,
onApplied,
}: {
open: boolean;
onOpenChange: (v: boolean) => void;
caseId: string;
onApplied?: () => void;
}) {
const { user } = useAuth();
const [templates, setTemplates] = useState<Template[]>([]);
const [templateId, setTemplateId] = useState<string | null>(null);
const [startDate, setStartDate] = useState<Date>(new Date());
const [submitting, setSubmitting] = useState(false);
useEffect(() => {
if (!open) return;
supabase
.from("workflow_templates")
.select("id, name")
.order("name")
.then(({ data }) => setTemplates((data ?? []) as Template[]));
setTemplateId(null);
setStartDate(new Date());
}, [open]);
const apply = async () => {
if (!user || !templateId) return;
setSubmitting(true);
const { data: tpl } = await supabase
.from("workflow_template_tasks")
.select("*")
.eq("template_id", templateId)
.order("sort_order");
const items = tpl ?? [];
if (!items.length) {
setSubmitting(false);
toast.error("Template has no tasks.");
return;
}
const inserts = items.map((it: any, idx: number) => ({
title: it.title,
description: it.description,
priority: it.priority,
due_date: format(addDays(startDate, it.days_from_start), "yyyy-MM-dd"),
case_id: caseId,
workflow_template_id: templateId,
created_by: user.id,
sort_order: idx,
}));
const { data: created, error } = await supabase.from("tasks").insert(inserts).select("id");
if (error || !created) {
setSubmitting(false);
toast.error(error?.message || "Failed to apply");
return;
}
// Insert default assignees + history
const assigneeRows: any[] = [];
const historyRows: any[] = [];
const notifs: any[] = [];
created.forEach((row, idx) => {
const def = items[idx].default_assignee_id;
if (def) {
assigneeRows.push({ task_id: row.id, user_id: def, assigned_by: user.id });
if (def !== user.id) {
notifs.push({
user_id: def,
kind: "task_assigned",
title: `Assigned: ${items[idx].title}`,
link: `/tasks?task=${row.id}`,
task_id: row.id,
case_id: caseId,
created_by: user.id,
});
}
}
historyRows.push({
task_id: row.id,
actor_id: user.id,
event: "created_from_workflow",
detail: { template_id: templateId },
});
});
if (assigneeRows.length) await supabase.from("task_assignees").insert(assigneeRows);
if (historyRows.length) await supabase.from("task_history").insert(historyRows);
if (notifs.length) await createNotifications(notifs);
setSubmitting(false);
toast.success(`Created ${created.length} tasks`);
onApplied?.();
onOpenChange(false);
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>Apply workflow template</DialogTitle>
</DialogHeader>
<div className="space-y-3">
<div>
<Label className="text-xs">Template</Label>
<Select value={templateId ?? ""} onValueChange={setTemplateId}>
<SelectTrigger>
<SelectValue placeholder="Select template…" />
</SelectTrigger>
<SelectContent>
{templates.map((t) => (
<SelectItem key={t.id} value={t.id}>{t.name}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label className="text-xs">Start date (due dates computed from this)</Label>
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" className="w-full justify-start font-normal">
<CalendarIcon className="h-3.5 w-3.5 mr-2" />
{format(startDate, "MM/dd/yyyy")}
</Button>
</PopoverTrigger>
<PopoverContent className="p-0" align="start">
<Calendar mode="single" selected={startDate} onSelect={(d) => d && setStartDate(d)} />
</PopoverContent>
</Popover>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>Cancel</Button>
<Button onClick={apply} disabled={!templateId || submitting}>
{submitting && <Loader2 className="h-3.5 w-3.5 mr-1.5 animate-spin" />}
Apply
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
@@ -47,6 +47,7 @@ interface TaskRow {
parent_task_id: string | null;
created_by: string | null;
completed_at: string | null;
completed_by: string | null;
}
interface Profile {