Changes
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
co-authored by
renee-png
parent
70e2f43afd
commit
bb0cd38520
@@ -0,0 +1,205 @@
|
||||
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;
|
||||
}
|
||||
interface CaseRow {
|
||||
id: string;
|
||||
case_number: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standalone "apply workflow" dialog for the Tasks page — lets the user
|
||||
* pick BOTH a case and a template before sequentially spawning the first task.
|
||||
*/
|
||||
export function ApplyWorkflowPickerDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
onApplied,
|
||||
}: {
|
||||
open: boolean;
|
||||
onOpenChange: (v: boolean) => void;
|
||||
onApplied?: () => void;
|
||||
}) {
|
||||
const { user } = useAuth();
|
||||
const [templates, setTemplates] = useState<Template[]>([]);
|
||||
const [cases, setCases] = useState<CaseRow[]>([]);
|
||||
const [templateId, setTemplateId] = useState<string | null>(null);
|
||||
const [caseId, setCaseId] = useState<string | null>(null);
|
||||
const [startDate, setStartDate] = useState<Date>(new Date());
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
setTemplateId(null);
|
||||
setCaseId(null);
|
||||
setStartDate(new Date());
|
||||
Promise.all([
|
||||
supabase.from("workflow_templates").select("id, name").order("name"),
|
||||
supabase
|
||||
.from("cases")
|
||||
.select("id, case_number, title")
|
||||
.is("archived_at", null)
|
||||
.order("case_number"),
|
||||
]).then(([t, c]) => {
|
||||
setTemplates((t.data ?? []) as Template[]);
|
||||
setCases((c.data ?? []) as CaseRow[]);
|
||||
});
|
||||
}, [open]);
|
||||
|
||||
const apply = async () => {
|
||||
if (!user || !templateId || !caseId) 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 first = items[0];
|
||||
const { data: created, error } = await supabase
|
||||
.from("tasks")
|
||||
.insert({
|
||||
title: first.title,
|
||||
description: first.description,
|
||||
priority: first.priority,
|
||||
due_date: format(addDays(startDate, first.days_from_start), "yyyy-MM-dd"),
|
||||
case_id: caseId,
|
||||
workflow_template_id: templateId,
|
||||
template_task_id: first.id,
|
||||
created_by: user.id,
|
||||
sort_order: 0,
|
||||
})
|
||||
.select("id")
|
||||
.single();
|
||||
if (error || !created) {
|
||||
setSubmitting(false);
|
||||
toast.error(error?.message || "Failed to apply");
|
||||
return;
|
||||
}
|
||||
if (first.default_assignee_id) {
|
||||
await supabase.from("task_assignees").insert({
|
||||
task_id: created.id,
|
||||
user_id: first.default_assignee_id,
|
||||
assigned_by: user.id,
|
||||
});
|
||||
if (first.default_assignee_id !== user.id) {
|
||||
await createNotifications([
|
||||
{
|
||||
user_id: first.default_assignee_id,
|
||||
kind: "task_assigned",
|
||||
title: `Assigned: ${first.title}`,
|
||||
link: `/tasks?task=${created.id}`,
|
||||
task_id: created.id,
|
||||
case_id: caseId,
|
||||
created_by: user.id,
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
await supabase.from("task_history").insert({
|
||||
task_id: created.id,
|
||||
actor_id: user.id,
|
||||
event: "created_from_workflow",
|
||||
detail: { template_id: templateId, sequential: true, total_steps: items.length },
|
||||
});
|
||||
setSubmitting(false);
|
||||
toast.success(
|
||||
`Workflow started — ${items.length} step${items.length === 1 ? "" : "s"} will run sequentially.`,
|
||||
);
|
||||
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">Case</Label>
|
||||
<Select value={caseId ?? ""} onValueChange={setCaseId}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select case…" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{cases.map((c) => (
|
||||
<SelectItem key={c.id} value={c.id}>
|
||||
{c.case_number} — {c.title}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<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">First task due date (next steps chain off completion)</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 || !caseId || submitting}>
|
||||
{submitting && <Loader2 className="h-3.5 w-3.5 mr-1.5 animate-spin" />}
|
||||
Apply
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user