import { useCallback, useEffect, useMemo, useState } from "react";
import { ArrowUpRight, Plus, Users2, Star } from "lucide-react";
import { api } from "../api.js";
import { Badge, Button, cx, Dialog, Empty, ErrorNote, Field, fmt, fmtDate, Panel, Spinner, Stat, StatCell, StatRow } from "../ui.jsx";
export default function Team() {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [person, setPerson] = useState("");
const [project, setProject] = useState("");
const [overdueOnly, setOverdueOnly] = useState(false);
const [q, setQ] = useState("");
const [creating, setCreating] = useState(false);
const load = useCallback(() => api("/api/team").then(setData).catch(setError), []);
useEffect(() => { load(); }, [load]);
const tasks = useMemo(() => {
const term = q.trim().toLowerCase();
return (data?.tasks || []).filter(
(t) =>
(!person || t.assignees.some((a) => String(a.id) === person)) &&
(!project || String(t.project?.id) === project) &&
(!overdueOnly || t.overdue) &&
(!term || t.name.toLowerCase().includes(term) || t.project?.name.toLowerCase().includes(term))
);
}, [data, person, project, overdueOnly, q]);
if (error) return ;
if (!data) return ;
if (!data.status.connected) {
return (
Team
{data.status.error || "An admin can connect it in Connections → Odoo (address, database, username and API key)."}
);
}
const all = data.tasks;
const overdue = all.filter((t) => t.overdue).length;
const unassigned = all.filter((t) => !t.assignees.length).length;
const usedProjects = data.projects.filter((p) => all.some((t) => t.project?.id === p.id));
const busiest = data.workload.reduce((m, w) => Math.max(m, w.open), 0);
return (
}
bodyClass="overflow-x-auto"
>
{tasks.length ? (
| Task | Assignee | Stage | Deadline | |
{tasks.map((t) => (
|
{t.priority === "1" && }
{t.name}
{t.project?.name || "No project"}
|
{t.assignees.map((a) => a.name).join(", ") || unassigned} |
{t.stage?.name || "—"} |
{t.deadline ? fmtDate(t.deadline.replace(" ", "T") + "Z") : "—"}
{t.overdue && overdue}
|
|
))}
) : (
Clear the filters to see every open task.
)}
setCreating(false)} data={data} onCreated={load} />
);
}
function NewTaskDialog({ open, onClose, data, onCreated }) {
const [form, setForm] = useState({ projectId: "", name: "", description: "", userIds: [], deadline: "" });
const [busy, setBusy] = useState(false);
const [error, setError] = useState(null);
useEffect(() => { if (open) { setForm({ projectId: "", name: "", description: "", userIds: [], deadline: "" }); setError(null); } }, [open]);
const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.value }));
const toggleUser = (id) => setForm((f) => ({ ...f, userIds: f.userIds.includes(id) ? f.userIds.filter((x) => x !== id) : [...f.userIds, id] }));
const submit = async () => {
setBusy(true);
setError(null);
try {
await api("/api/team/tasks", { method: "POST", body: form });
onClose();
onCreated();
} catch (e) {
setError(e);
} finally {
setBusy(false);
}
};
return (
);
}