"use client"; import { useState } from "react"; import { trpc } from "~/lib/trpc/client.js"; import { DateInput } from "~/components/ui/DateInput.js"; import { SkillTagInput } from "~/components/ui/SkillTagInput.js"; import { InfoTooltip } from "~/components/ui/InfoTooltip.js"; export function StaffingPanel() { const [requiredSkills, setRequiredSkills] = useState(["TypeScript", "React"]); const [startDate, setStartDate] = useState(new Date().toISOString().split("T")[0] ?? ""); const [endDate, setEndDate] = useState( new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString().split("T")[0] ?? "", ); const [hoursPerDay, setHoursPerDay] = useState(8); const [submitted, setSubmitted] = useState(false); const { data: suggestions, isLoading } = trpc.staffing.getSuggestions.useQuery( { requiredSkills: requiredSkills, startDate: new Date(startDate), endDate: new Date(endDate), hoursPerDay, }, { enabled: submitted }, ); return (
Staffing

Staffing Suggestions

Match open work with the strongest available people based on skills, availability, utilization, and cost.

How scoring works

plANARCHY blends skill fit, free capacity, cost, and current utilization. Add the must-have skills first, then narrow the date window to get cleaner results.

Search Criteria

Define the role needs and let the matching engine rank the best candidates.

setHoursPerDay(Number(e.target.value))} min={1} max={24} className="app-input" />
Skills
Quality of skill overlap with the requested stack.
Availability
Conflicts and free capacity during the selected period.
Cost + Load
Cost efficiency and current utilization weighting.
{isLoading && (
Finding best matches...
)} {suggestions && suggestions.length === 0 && (
No resources found matching your criteria.
)} {suggestions && suggestions.length > 0 && (
{suggestions.map((suggestion, idx) => (
{idx + 1}
{suggestion.resourceName}
{suggestion.eid}
Match Score
{suggestion.score}
{suggestion.matchedSkills.map((skill) => ( {skill} ))} {suggestion.missingSkills.map((skill) => ( {skill} missing ))}
LCR: {(suggestion.estimatedDailyCostCents / 100 / 8).toFixed(0)} €/h Utilization: {Math.round(suggestion.currentUtilization)}% {suggestion.availabilityConflicts.length > 0 && ( {suggestion.availabilityConflicts.length} scheduling conflict{suggestion.availabilityConflicts.length === 1 ? "" : "s"} )}
))}
)} {!submitted && (
No suggestions yet

Add the required skills and date range, then run the search to see ranked staffing matches.

)}
); } function ScoreBar({ label, value, tooltip }: { label: string; value: number; tooltip?: string }) { return (
{label}{tooltip && }
{value}
); }