2026-07-17 23:30:41 +05:30

161 lines
7.4 KiB
TypeScript

"use client";
import { FormEvent, useEffect, useMemo, useState } from "react";
import { AppShell } from "@/components/app-shell";
import { apiFetch } from "@/lib/api";
type Household = { id: string; name: string };
type Person = { id: string; email: string; fullName?: string | null };
type AccountantTask = {
id: string;
title: string;
description?: string | null;
taskType: string;
status: string;
priority: string;
dueDate?: string | null;
completedAt?: string | null;
assignedTo?: Person | null;
createdBy?: Person | null;
};
export default function AccountantPage() {
const [households, setHouseholds] = useState<Household[]>([]);
const [householdId, setHouseholdId] = useState("");
const [tasks, setTasks] = useState<AccountantTask[]>([]);
const [draft, setDraft] = useState({ title: "", description: "", taskType: "review", priority: "medium", dueDate: "" });
const [status, setStatus] = useState("");
const selectedHousehold = useMemo(() => households.find((item) => item.id === householdId), [households, householdId]);
useEffect(() => {
void loadHouseholds();
}, []);
useEffect(() => {
if (householdId) void loadTasks(householdId);
}, [householdId]);
const loadHouseholds = async () => {
const res = await apiFetch<Household[]>("/api/households");
if (res.error) {
setStatus(res.error.message);
return;
}
const list = res.data ?? [];
setHouseholds(list);
setHouseholdId((current) => current || list[0]?.id || "");
};
const loadTasks = async (id = householdId) => {
const res = await apiFetch<AccountantTask[]>(`/api/households/${id}/accountant-tasks`);
if (res.error) {
setStatus(res.error.message);
setTasks([]);
return;
}
setStatus("");
setTasks(res.data ?? []);
};
const createTask = async (event: FormEvent) => {
event.preventDefault();
if (!householdId || !draft.title.trim()) return;
const res = await apiFetch<AccountantTask>(`/api/households/${householdId}/accountant-tasks`, {
method: "POST",
body: JSON.stringify({
title: draft.title,
description: draft.description || undefined,
taskType: draft.taskType,
priority: draft.priority,
dueDate: draft.dueDate || undefined,
}),
});
if (res.error) {
setStatus(res.error.message);
return;
}
setDraft({ title: "", description: "", taskType: "review", priority: "medium", dueDate: "" });
await loadTasks();
};
const updateTask = async (task: AccountantTask, nextStatus: string) => {
if (!householdId) return;
const res = await apiFetch<AccountantTask>(`/api/households/${householdId}/accountant-tasks/${task.id}`, {
method: "PATCH",
body: JSON.stringify({ status: nextStatus }),
});
if (res.error) {
setStatus(res.error.message);
return;
}
await loadTasks();
};
return (
<AppShell title="Accountant Workflow" subtitle="Track household advisor requests, reviews, and follow-ups.">
<div className="mx-auto flex w-full max-w-6xl flex-col gap-6">
{status ? <div className="rounded-md border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-900">{status}</div> : null}
<section className="rounded-lg border border-slate-200 bg-white p-4">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div>
<h2 className="text-base font-semibold text-slate-950">Household</h2>
<p className="text-sm text-slate-600">{selectedHousehold?.name ?? "Choose a household to manage advisor work."}</p>
</div>
<select value={householdId} onChange={(event) => setHouseholdId(event.target.value)} className="rounded-md border border-slate-300 px-3 py-2 text-sm">
{households.map((household) => (
<option key={household.id} value={household.id}>{household.name}</option>
))}
</select>
</div>
</section>
<form onSubmit={createTask} className="rounded-lg border border-slate-200 bg-white p-4">
<h2 className="text-base font-semibold text-slate-950">New Task</h2>
<div className="mt-4 grid gap-3 md:grid-cols-2">
<input value={draft.title} onChange={(event) => setDraft({ ...draft, title: event.target.value })} placeholder="Task title" className="rounded-md border border-slate-300 px-3 py-2 text-sm" />
<input type="date" value={draft.dueDate} onChange={(event) => setDraft({ ...draft, dueDate: event.target.value })} className="rounded-md border border-slate-300 px-3 py-2 text-sm" />
<select value={draft.taskType} onChange={(event) => setDraft({ ...draft, taskType: event.target.value })} className="rounded-md border border-slate-300 px-3 py-2 text-sm">
<option value="review">Review</option>
<option value="document_request">Document request</option>
<option value="tax_prep">Tax prep</option>
<option value="advice">Advice</option>
</select>
<select value={draft.priority} onChange={(event) => setDraft({ ...draft, priority: event.target.value })} className="rounded-md border border-slate-300 px-3 py-2 text-sm">
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
</div>
<textarea value={draft.description} onChange={(event) => setDraft({ ...draft, description: event.target.value })} placeholder="Notes or request details" className="mt-3 min-h-24 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
<button className="mt-3 rounded-md bg-slate-950 px-4 py-2 text-sm font-medium text-white">Create task</button>
</form>
<section className="grid gap-3">
{tasks.length ? tasks.map((task) => (
<article key={task.id} className="rounded-lg border border-slate-200 bg-white p-4">
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div>
<h3 className="font-semibold text-slate-950">{task.title}</h3>
<p className="mt-1 text-sm text-slate-600">{task.description || "No details added."}</p>
<p className="mt-2 text-xs uppercase text-slate-500">
{task.taskType.replace(/_/g, " ")} · {task.priority} · {task.dueDate ? new Date(task.dueDate).toLocaleDateString() : "No due date"}
</p>
</div>
<div className="flex gap-2">
<button onClick={() => updateTask(task, "open")} className="rounded-md border border-slate-300 px-3 py-1.5 text-xs">Open</button>
<button onClick={() => updateTask(task, "in_progress")} className="rounded-md border border-blue-200 px-3 py-1.5 text-xs text-blue-700">In progress</button>
<button onClick={() => updateTask(task, "completed")} className="rounded-md border border-emerald-200 px-3 py-1.5 text-xs text-emerald-700">Complete</button>
</div>
</div>
<div className="mt-3 text-xs text-slate-500">Status: {task.status.replace(/_/g, " ")}</div>
</article>
)) : (
<div className="rounded-lg border border-dashed border-slate-300 bg-white p-6 text-sm text-slate-500">No accountant tasks yet.</div>
)}
</section>
</div>
</AppShell>
);
}