"use client"; import { useState, useId } from "react"; import Link from "next/link"; import { trpc } from "~/lib/trpc/client.js"; import { ProficiencyBadge, PROFICIENCY_LABELS, proficiencyClasses } from "./shared.js"; type SkillRule = { skill: string; minProficiency: number }; interface PeopleFinderTabProps { allSkillNames: string[]; allChapters: string[]; } export function PeopleFinderTab({ allSkillNames, allChapters }: PeopleFinderTabProps) { const datalistId = useId(); const [rules, setRules] = useState([]); const [operator, setOperator] = useState<"AND" | "OR">("AND"); const [chapter, setChapter] = useState(""); const activeRules = rules.filter((r) => r.skill.trim().length > 0); const { data: results, isFetching } = trpc.resource.searchBySkills.useQuery( { rules: activeRules, operator, ...(chapter ? { chapter } : {}) }, { enabled: activeRules.length > 0, staleTime: 30_000 }, ); function addRule() { setRules((prev) => [...prev, { skill: "", minProficiency: 1 }]); } function removeRule(idx: number) { setRules((prev) => prev.filter((_, i) => i !== idx)); } function updateRule(idx: number, patch: Partial) { setRules((prev) => prev.map((r, i) => (i === idx ? { ...r, ...patch } : r))); } async function exportXlsx() { if (!results || results.length === 0) return; const XLSX = await import("xlsx"); const rows = results.map((p) => ({ Name: p.displayName, EID: p.eid ?? "", Chapter: p.chapter ?? "", "Matched Skills": p.matchedSkills.map((s) => `${s.skill} (${s.proficiency})`).join(", "), })); const ws = XLSX.utils.json_to_sheet(rows); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "People Finder"); XLSX.writeFile(wb, `people-finder-${Date.now()}.xlsx`); } return (

People Finder

Find resources that match skill criteria
{/* Datalist */} {allSkillNames.map((s) => {/* Rules */}
{rules.map((rule, idx) => (
{idx > 0 ? ( ) : ( knows )} updateRule(idx, { skill: e.target.value })} className="flex-1 min-w-40 px-3 py-1.5 border border-gray-300 dark:border-slate-600 rounded-lg text-sm bg-white dark:bg-slate-800 dark:text-gray-200 focus:outline-none focus:ring-2 focus:ring-brand-500" />
min.
{[1, 2, 3, 4, 5].map((lvl) => ( ))}
))}
{/* Controls row */}
{rules.length > 1 && (
Match: {(["AND", "OR"] as const).map((op) => ( ))}
)} {allChapters.length > 0 && (
Chapter:
)} {results && results.length > 0 && ( )}
{/* Results */} {activeRules.length > 0 && (
{isFetching ? (
Searching...
) : results && results.length === 0 ? (

No resources match these criteria.

) : results && results.length > 0 ? ( <>

{results.length} resource{results.length !== 1 ? "s" : ""} found

{results.map((person) => (
{person.displayName} {person.eid && ( {person.eid} )} {person.chapter && ( {person.chapter} )}
{person.matchedSkills.map((s) => ( {s.skill} {s.proficiency} ))}
View
))}
) : null}
)}
); }