05f6eba5d8
Splits StaffingPanel.tsx into: - StaffingSearchForm: skill tags, dates, hours input, submit button - ScoringExplanation: the 3-column scoring breakdown card - StaffingResultCard: individual suggestion card with details and assign form - StaffingResultsList: list orchestration with loading/empty states - StaffingPanel: thin orchestrator (~100 lines) managing state and tRPC query Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
75 lines
2.0 KiB
TypeScript
75 lines
2.0 KiB
TypeScript
"use client";
|
|
|
|
import { StaffingResultCard, type SuggestionLike } from "./StaffingResultCard.js";
|
|
import type { SearchCriteria } from "./StaffingSearchForm.js";
|
|
|
|
interface StaffingResultsListProps {
|
|
suggestions: SuggestionLike[] | undefined;
|
|
isLoading: boolean;
|
|
submitted: boolean;
|
|
searchCriteria: SearchCriteria;
|
|
assignedIds: Set<string>;
|
|
onAssigned: (resourceId: string, resourceName: string) => void;
|
|
onError: (message: string) => void;
|
|
}
|
|
|
|
export function StaffingResultsList({
|
|
suggestions,
|
|
isLoading,
|
|
submitted,
|
|
searchCriteria,
|
|
assignedIds,
|
|
onAssigned,
|
|
onError,
|
|
}: StaffingResultsListProps) {
|
|
const visible = suggestions?.filter((s) => !assignedIds.has(s.resourceId));
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="app-surface-strong py-16 text-center text-sm text-gray-500">
|
|
Finding best matches...
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (visible && visible.length === 0) {
|
|
return (
|
|
<div className="app-surface-strong py-16 text-center text-sm text-gray-500">
|
|
{assignedIds.size > 0
|
|
? "All suggestions have been assigned."
|
|
: "No resources found matching your criteria."}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!submitted) {
|
|
return (
|
|
<div className="app-surface-strong border-dashed py-20 text-center">
|
|
<div className="mx-auto max-w-md">
|
|
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">No suggestions yet</div>
|
|
<p className="mt-2 text-sm text-gray-500">
|
|
Add the required skills and date range, then run the search to see ranked staffing matches.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!visible) return null;
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
{visible.map((suggestion, idx) => (
|
|
<StaffingResultCard
|
|
key={suggestion.resourceId}
|
|
suggestion={suggestion}
|
|
rank={idx + 1}
|
|
searchCriteria={searchCriteria}
|
|
onAssigned={onAssigned}
|
|
onError={onError}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|