import { useActionMutation, useActionQuery, } from "@agent-native/core/client/hooks"; import { IconCheck, IconPlus, IconX } from "@tabler/icons-react"; import { useState } from "react"; import { Link } from "react-router"; import { toast } from "sonner"; import { LoadingRows, PageHeader, SetupEmptyState, } from "@/components/crm/Surface"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { normalizeTasks } from "@/lib/types"; interface ManageTaskInput { taskId?: string; title?: string; description?: string; dueAt?: string; status?: "open" | "done" | "cancelled"; } export default function TasksRoute() { const query = useActionQuery( "list-crm-tasks" as never, { limit: 100 } as never, ); const manage = useActionMutation( "manage-crm-task" as never, ); const tasks = normalizeTasks(query.data); const [pendingTaskIds, setPendingTaskIds] = useState>(new Set()); async function setStatus( taskId: string, status: "open" | "done" | "cancelled", ) { setPendingTaskIds((current) => new Set(current).add(taskId)); try { await manage.mutateAsync({ taskId, status }); toast.success(status === "done" ? "Task completed." : "Task updated."); } catch (error) { const message = error instanceof Error ? error.message : "Task update failed."; toast.error( message.includes("mirrored provider task") ? "This is a mirrored provider task. Create a CRM proposal or complete it upstream." : message, ); } finally { setPendingTaskIds((current) => { const next = new Set(current); next.delete(taskId); return next; }); } } return ( <> } /> {query.isLoading ? ( ) : tasks.length ? (
{tasks.map((task) => (

{task.title}

{task.status}

{task.dueAt ? `Due ${formatDate(task.dueAt)}` : "No due date"} {task.recordId ? ( <> {" ยท "} Open record ) : null}

{task.status !== "done" ? ( ) : ( )} {task.status !== "cancelled" ? ( ) : null}
))}
) : ( )} ); } function CreateTaskDialog({ mutation, }: { mutation: { isPending: boolean; mutateAsync: (input: ManageTaskInput) => Promise; }; }) { const [open, setOpen] = useState(false); const [title, setTitle] = useState(""); const [description, setDescription] = useState(""); const [dueAt, setDueAt] = useState(""); async function createTask() { try { await mutation.mutateAsync({ title, description: description || undefined, dueAt: dueAt ? new Date(dueAt).toISOString() : undefined, status: "open", }); setOpen(false); setTitle(""); setDescription(""); setDueAt(""); toast.success("Task created."); } catch (error) { toast.error( error instanceof Error ? error.message : "Task creation failed.", ); } } return ( Create follow-up task Tasks are local by default and never write to the provider silently.
setTitle(event.target.value)} />