diff --git a/src/components/cases/collections-tab.tsx b/src/components/cases/collections-tab.tsx index 049d0d4..fac886d 100644 --- a/src/components/cases/collections-tab.tsx +++ b/src/components/cases/collections-tab.tsx @@ -358,6 +358,7 @@ export function CaseCollectionsTab({ open={!!wfCollectionId} onOpenChange={(v) => !v && setWfCollectionId(null)} collectionId={wfCollectionId} + caseId={caseId} onApplied={() => { setWfCollectionId(null); load(); diff --git a/src/components/collections/apply-collection-workflow-dialog.tsx b/src/components/collections/apply-collection-workflow-dialog.tsx index eb76263..6303165 100644 --- a/src/components/collections/apply-collection-workflow-dialog.tsx +++ b/src/components/collections/apply-collection-workflow-dialog.tsx @@ -22,21 +22,29 @@ 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; } +/** + * Applies a workflow to a collection by creating tasks in the unified `tasks` + * table linked to the collection's parent case. This way the spawned tasks + * appear in the case Tasks tab and the global Tasks list. + */ export function ApplyCollectionWorkflowDialog({ open, onOpenChange, collectionId, + caseId, onApplied, }: { open: boolean; onOpenChange: (v: boolean) => void; collectionId: string; + caseId?: string | null; onApplied?: () => void; }) { const { user } = useAuth(); @@ -50,7 +58,6 @@ export function ApplyCollectionWorkflowDialog({ supabase .from("workflow_templates") .select("id, name") - .eq("kind", "collection") .order("name") .then(({ data }) => setTemplates((data ?? []) as Template[])); setTemplateId(null); @@ -59,6 +66,10 @@ export function ApplyCollectionWorkflowDialog({ const apply = async () => { if (!user || !templateId) return; + if (!caseId) { + toast.error("This collection has no linked case — cannot create tasks."); + return; + } setSubmitting(true); const { data: tpl } = await supabase .from("workflow_template_tasks") @@ -72,21 +83,53 @@ export function ApplyCollectionWorkflowDialog({ return; } const first = items[0]; - const { error } = await supabase.from("collection_tasks").insert({ - collection_id: collectionId, - title: first.title, - due_date: format(addDays(startDate, first.days_from_start), "yyyy-MM-dd"), - sort_order: 0, - assignee_id: first.default_assignee_id ?? null, - workflow_template_id: templateId, - template_task_id: first.id, - created_by: user.id, - }); - setSubmitting(false); - if (error) { - toast.error(error.message || "Failed to apply"); + 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, collection_id: collectionId }, + }); + setSubmitting(false); toast.success( `Workflow started — ${items.length} step${items.length === 1 ? "" : "s"} will run sequentially.`, ); @@ -131,15 +174,15 @@ export function ApplyCollectionWorkflowDialog({

- Only the first task is created now. Each subsequent task is auto-created when the - previous one is marked complete, with a due date X days after completion. + Tasks are created on the linked case. Only the first task is created now — each next + task spawns automatically when the previous one is marked complete.

- diff --git a/src/components/tasks/apply-workflow-picker-dialog.tsx b/src/components/tasks/apply-workflow-picker-dialog.tsx index edd33e0..cf84d08 100644 --- a/src/components/tasks/apply-workflow-picker-dialog.tsx +++ b/src/components/tasks/apply-workflow-picker-dialog.tsx @@ -61,7 +61,7 @@ export function ApplyWorkflowPickerDialog({ setCaseId(null); setStartDate(new Date()); Promise.all([ - supabase.from("workflow_templates").select("id, name").eq("kind", "task").order("name"), + supabase.from("workflow_templates").select("id, name").order("name"), supabase .from("cases") .select("id, case_number, title") diff --git a/src/routes/collections.$collectionId.tsx b/src/routes/collections.$collectionId.tsx index bb05def..5928d41 100644 --- a/src/routes/collections.$collectionId.tsx +++ b/src/routes/collections.$collectionId.tsx @@ -536,6 +536,7 @@ function CollectionDetailRoute() { open={applyWfOpen} onOpenChange={setApplyWfOpen} collectionId={collection.id} + caseId={collection.case?.id ?? null} onApplied={refreshTasks} /> diff --git a/src/routes/settings.workflows.tsx b/src/routes/settings.workflows.tsx index 3cef2da..cafbaa0 100644 --- a/src/routes/settings.workflows.tsx +++ b/src/routes/settings.workflows.tsx @@ -59,7 +59,6 @@ function WorkflowsPage() { const [newOpen, setNewOpen] = useState(false); const [newName, setNewName] = useState(""); const [newDesc, setNewDesc] = useState(""); - const [newKind, setNewKind] = useState<"task" | "collection">("task"); const [newTask, setNewTask] = useState({ title: "", days: 7, priority: "normal" as const }); const loadTemplates = useCallback(async () => { @@ -100,7 +99,7 @@ function WorkflowsPage() { .insert({ name: newName.trim(), description: newDesc.trim() || null, - kind: newKind, + kind: "task", created_by: user.id, }) .select() @@ -111,18 +110,11 @@ function WorkflowsPage() { } setNewName(""); setNewDesc(""); - setNewKind("task"); setNewOpen(false); await loadTemplates(); setSelected(data as Template); }; - const updateTemplateKind = async (id: string, kind: "task" | "collection") => { - await supabase.from("workflow_templates").update({ kind }).eq("id", id); - await loadTemplates(); - if (selected?.id === id) setSelected({ ...selected, kind }); - }; - const deleteTemplate = async (id: string) => { if (!confirm("Delete this workflow template?")) return; await supabase.from("workflow_templates").delete().eq("id", id); @@ -183,13 +175,6 @@ function WorkflowsPage() { >
{t.name}
- - {t.kind === "collection" ? "Collection" : "Task"} -
{t.description &&
{t.description}
} @@ -212,19 +197,6 @@ function WorkflowsPage() { )}
- - @@ -321,18 +293,6 @@ function WorkflowsPage() {