import { useEffect, useRef, useState } from "react"; import { api, type Member, type UserRow } from "./api"; /** * Dropdown checklist to pick which ml_support users belong to a project. Searchable * + scrollable (built for ~30 users). Ticking a user adds them as a project member; * un-ticking removes them. Members are who can see the project and be assigned videos. * Admin only. By default no one is selected. */ export function MembersPicker({ projectId, users, members, onChange, }: { projectId: number; users: UserRow[]; // all ml_support users members: Member[]; onChange: () => void; // reload the project's members after a change }) { const [open, setOpen] = useState(false); const [q, setQ] = useState(""); const [busy, setBusy] = useState(null); const [err, setErr] = useState(null); const ref = useRef(null); const memberSet = new Set(members.map((m) => m.username)); useEffect(() => { if (!open) return; const h = (e: MouseEvent) => { if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false); }; document.addEventListener("mousedown", h); return () => document.removeEventListener("mousedown", h); }, [open]); const toggle = async (username: string) => { setBusy(username); setErr(null); try { if (memberSet.has(username)) await api.removeMember(projectId, username); else await api.addMember(projectId, username); onChange(); } catch (e) { setErr(String(e)); } finally { setBusy(null); } }; const filtered = users.filter((u) => u.username.toLowerCase().includes(q.trim().toLowerCase())); return (
{open && (
setQ(e.target.value)} autoFocus />
{filtered.map((u) => ( ))} {users.length === 0 &&
no ml_support users yet — create some in Admin
} {users.length > 0 && filtered.length === 0 &&
no matches
}
{err &&
⚠ {err}
}
{members.length} selected · ticked users can be assigned videos
)}
); }