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

79 lines
3.2 KiB
TypeScript

import { useCallback, useEffect, useRef, useState } from "react";
import { api, type Folder } from "./api";
/**
* Dropdown checklist of the project's folders. Ticking folders restricts the videos
* shown/counted to those folders (admins verify only some folders; the rest are
* backup/misc). None ticked = consider all folders. Admin only.
*/
export function FoldersPicker({ projectId, onChange }: { projectId: number; onChange: () => void }) {
const [open, setOpen] = useState(false);
const [folders, setFolders] = useState<Folder[]>([]);
const [busy, setBusy] = useState(false);
const [err, setErr] = useState<string | null>(null);
const ref = useRef<HTMLDivElement>(null);
const load = useCallback(async () => {
try { setFolders(await api.folders(projectId)); } catch (e) { setErr(String(e)); }
}, [projectId]);
useEffect(() => { if (open) void load(); }, [open, load]);
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 includedCount = folders.filter((f) => f.included).length;
const commit = async (next: Folder[]) => {
setFolders(next);
setBusy(true);
setErr(null);
try {
await api.setFolders(projectId, next.filter((f) => f.included).map((f) => f.folder));
onChange();
} catch (e) {
setErr(String(e));
await load();
} finally {
setBusy(false);
}
};
const toggle = (folder: string) =>
commit(folders.map((f) => (f.folder === folder ? { ...f, included: !f.included } : f)));
const considerAll = () => commit(folders.map((f) => ({ ...f, included: false })));
const label = includedCount === 0 ? "Folders: all ▾" : `Folders: ${includedCount}/${folders.length}`;
return (
<div className="members-picker" ref={ref}>
<button className="btn" onClick={() => setOpen((v) => !v)} title="Pick which folders to verify">{label}</button>
{open && (
<div className="members-dropdown">
<div className="folders-hint dim">Tick folders to verify. None ticked = all folders shown.</div>
<div className="members-list">
{folders.map((f) => (
<label key={f.folder} className={`members-item${f.included ? " on" : ""}`}>
<input type="checkbox" checked={f.included} disabled={busy} onChange={() => toggle(f.folder)} />
<span className="mi-name">{f.folder || "(root)"}</span>
<span className="dim mi-disp">{f.video_count}</span>
</label>
))}
{folders.length === 0 && <div className="dim mi-empty">no folders sync the project first</div>}
</div>
{err && <div className="mi-err"> {err}</div>}
<div className="members-foot">
<button className="btn" disabled={busy || includedCount === 0} onClick={considerAll}>Consider all</button>
<span className="dim" style={{ marginLeft: 8 }}>
{includedCount === 0 ? "all folders shown" : `${includedCount} selected`}
</span>
</div>
</div>
)}
</div>
);
}