"use client"; import { useEffect, useState } from "react"; import type { FieldConfig, FieldHistoryEntry, SelectOption, StageSectionConfig, StaffReportField, StaffReportPayload, StaffReportSection, } from "@/types/form"; import { MembershipChart, MEMBERS_ACTUAL_NAME, MEMBERS_GOAL_NAME } from "./report/MembershipChart"; import { DateTimeline } from "./report/DateTimeline"; import { FieldHistoryRow, formatShortDate, formatLongDate, computeDateRange, } from "./report/FieldHistory"; import { LoadingState, EmptyState, ErrorState } from "./report/ReportStates"; interface StaffReportViewProps { org: number; authKey: string; } type LoadState = | { kind: "loading" } | { kind: "error"; message: string } | { kind: "ready"; data: StaffReportPayload }; export function StaffReportView({ org, authKey }: StaffReportViewProps) { const [load, setLoad] = useState({ kind: "loading" }); useEffect(() => { let alive = true; (async () => { try { const res = await fetch( `/api/staff/report?org=${encodeURIComponent(String(org))}&key=${encodeURIComponent(authKey)}`, { cache: "no-store" }, ); if (!res.ok) { const body = (await res.json().catch(() => ({}))) as { error?: string }; if (alive) setLoad({ kind: "error", message: body.error ?? `Request failed (${res.status})`, }); return; } const data = (await res.json()) as StaffReportPayload; if (alive) setLoad({ kind: "ready", data }); } catch (e) { const msg = e instanceof Error ? e.message : String(e); if (alive) setLoad({ kind: "error", message: msg }); } })(); return () => { alive = false; }; }, [org, authKey]); if (load.kind === "loading") return ; if (load.kind === "error") return ; const { data } = load; if (data.sections.length === 0 && data.activities.length === 0) return ; // Pull the membership + goal series out wherever they live (Check_in_data__organizing_). const checkInSection = data.sections.find((s) => s.groupName === "Check_in_data__organizing_"); const membersField = checkInSection?.fields.find((f) => f.descriptor.name === MEMBERS_ACTUAL_NAME); const goalField = checkInSection?.fields.find((f) => f.descriptor.name === MEMBERS_GOAL_NAME); const stageLabel = data.currentStage ? data.options[75]?.find((o) => o.value === data.currentStage)?.label ?? data.currentStage : "—"; const dateRange = computeDateRange(data.activities.map((a) => a.date)); return (

Staff report · Internal use only

{data.orgName}

{data.orgId}} />
{membersField && membersField.history.length > 0 ? ( ) : null} {/* Build a synthetic "sections + fieldHistory" view the DateTimeline understands. */} {data.sections.map((section) => ( ))}
); } function historyOnly(f: StaffReportField): FieldHistoryEntry[] { return f.history; } function Stat({ label, value }: { label: string; value: React.ReactNode }) { return (
{label}
{value}
); } function StaffSection({ section, options, authKey, }: { section: StaffReportSection; options: Record; authKey: string; }) { const filled = section.fields.filter((f) => f.history.length > 0); const empty = section.fields.filter((f) => f.history.length === 0); return (

{section.groupTitle}

{section.fields.length} field{section.fields.length === 1 ? "" : "s"} ·{" "} {filled.length} with data

    {filled.map((f) => (
  • {f.descriptor.render === "file" ? ( ) : ( )}
  • ))} {empty.map((f) => (
  • {f.descriptor.label}
  • ))}
); } /** * Adapt a StaffReportField to the FieldConfig shape FieldHistoryRow / FormattedValue * expect. The renderer only reads `name`, `label`, `type`, `optionGroupId`. */ function fieldConfigFor(f: StaffReportField): FieldConfig { return { name: f.descriptor.name, label: f.descriptor.label, type: renderToFieldType(f.descriptor.render), optionGroupId: f.descriptor.optionGroupId, }; } function renderToFieldType(r: StaffReportField["descriptor"]["render"]): FieldConfig["type"] { switch (r) { case "currency": return "currency"; case "date": return "date"; case "datetime": return "date"; case "select": return "select"; case "multiselect": return "multiselect"; case "file": return "file"; case "longtext": return "textarea"; case "boolean": return "boolean"; case "number": return "number"; default: return "text"; } } function FileFieldRow({ field, authKey }: { field: StaffReportField; authKey: string }) { return (

{field.descriptor.label}

    {field.history.map((entry) => ( ))}
); } function FilePreviewItem({ entry, authKey, }: { entry: FieldHistoryEntry; authKey: string; }) { const v = entry.value as { id?: number | string; file_name?: string } | null; if (!v || v.id === undefined) return null; const id = String(v.id); const name = v.file_name ?? `file-${id}`; const href = `/api/staff/file?id=${encodeURIComponent(id)}&key=${encodeURIComponent(authKey)}`; const ext = (name.split(".").pop() ?? "").toLowerCase(); // SVG omitted on purpose — the proxy forces SVG to download (XSS hardening), // so an inline here would just show a broken thumbnail. const isImage = ["png", "jpg", "jpeg", "gif", "webp"].includes(ext); const isPdf = ext === "pdf"; return (
  • {isImage ? ( {/* eslint-disable-next-line @next/next/no-img-element */} {name} ) : null}
    {name} {entry.date ? (

    submitted {formatLongDate(entry.date)}

    ) : null} {isPdf ? : null}
  • ); } function PdfPreviewButton({ href, name }: { href: string; name: string }) { const [open, setOpen] = useState(false); return ( <> {open ? (
    setOpen(false)} >
    e.stopPropagation()} >