Files
Centralised_VideoVerificati…/dashboard/src/MembersPicker.tsx
2026-06-16 14:32:21 +05:30

89 lines
3.2 KiB
TypeScript

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<string | null>(null);
const [err, setErr] = useState<string | null>(null);
const ref = useRef<HTMLDivElement>(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 (
<div className="members-picker" ref={ref}>
<button className="btn" onClick={() => setOpen((v) => !v)}>
{members.length === 0 ? "+ Assign users" : `Members (${members.length}) ▾`}
</button>
{open && (
<div className="members-dropdown">
<input
className="members-search"
placeholder="search ml_support…"
value={q}
onChange={(e) => setQ(e.target.value)}
autoFocus
/>
<div className="members-list">
{filtered.map((u) => (
<label key={u.id} className={`members-item${memberSet.has(u.username) ? " on" : ""}`}>
<input
type="checkbox"
checked={memberSet.has(u.username)}
disabled={busy === u.username}
onChange={() => toggle(u.username)}
/>
<span className="mi-name">{u.username}</span>
{u.display_name && u.display_name !== u.username && <span className="dim mi-disp">{u.display_name}</span>}
</label>
))}
{users.length === 0 && <div className="dim mi-empty">no ml_support users yet create some in Admin</div>}
{users.length > 0 && filtered.length === 0 && <div className="dim mi-empty">no matches</div>}
</div>
{err && <div className="mi-err"> {err}</div>}
<div className="members-foot dim">{members.length} selected · ticked users can be assigned videos</div>
</div>
)}
</div>
);
}