feat(mentor): round-level auto-fill toolbar on Projects tab (§C)
Adds an 'Auto-fill remaining' button above ProjectStatesTable on the
MENTORING round Projects tab. Calls mentor.autoAssignBulkForRound,
respecting the round's configJson.eligibility:
- requested_only / all_advancing: enabled, count from new
round.getProjectsNeedingMentor query
- admin_selected: disabled with explanatory copy
Plan: docs/superpowers/plans/2026-04-28-pr4-mentor-assignment-ux.md
This commit is contained in:
@@ -145,6 +145,73 @@ const stateColors: Record<string, string> = Object.fromEntries(
|
||||
Object.entries(projectStateConfig).map(([k, v]) => [k, v.bg])
|
||||
)
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// Mentoring round: Auto-fill remaining toolbar (Projects tab)
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
function MentoringBulkAssignToolbar({
|
||||
roundId,
|
||||
configJson,
|
||||
}: {
|
||||
roundId: string
|
||||
configJson: Record<string, unknown>
|
||||
}) {
|
||||
const utils = trpc.useUtils()
|
||||
const eligibility = (configJson.eligibility as string) ?? 'requested_only'
|
||||
const isAdminSelected = eligibility === 'admin_selected'
|
||||
|
||||
const { data: pending } = trpc.round.getProjectsNeedingMentor.useQuery(
|
||||
{ roundId },
|
||||
{ enabled: !isAdminSelected, refetchInterval: 30_000 },
|
||||
)
|
||||
const count = pending?.count ?? 0
|
||||
|
||||
const bulk = trpc.mentor.autoAssignBulkForRound.useMutation({
|
||||
onSuccess: (result) => {
|
||||
toast.success(result.message)
|
||||
utils.round.getProjectsNeedingMentor.invalidate({ roundId })
|
||||
utils.project.list.invalidate()
|
||||
},
|
||||
onError: (err) => toast.error(err.message),
|
||||
})
|
||||
|
||||
const eligibilityLabel = eligibility.replace('_', ' ')
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between rounded-md border bg-muted/30 px-4 py-2.5">
|
||||
<div className="text-sm">
|
||||
{isAdminSelected ? (
|
||||
<>
|
||||
<span className="font-medium">Eligibility: admin-selected</span>
|
||||
<span className="text-muted-foreground ml-2">
|
||||
— auto-fill is disabled. Assign each project manually.
|
||||
</span>
|
||||
</>
|
||||
) : count > 0 ? (
|
||||
<>
|
||||
<span className="font-medium">{count}</span>{' '}
|
||||
<span className="text-muted-foreground">
|
||||
project{count === 1 ? '' : 's'} eligible for auto-fill ({eligibilityLabel})
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-muted-foreground">
|
||||
All eligible projects have a mentor.
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => bulk.mutate({ roundId })}
|
||||
disabled={isAdminSelected || count === 0 || bulk.isPending}
|
||||
>
|
||||
{bulk.isPending ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : null}
|
||||
Auto-fill remaining
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// Main Page Component
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
@@ -1477,6 +1544,9 @@ export default function RoundDetailPage() {
|
||||
|
||||
{/* ═══════════ PROJECTS TAB ═══════════ */}
|
||||
<TabsContent value="projects" className="space-y-4">
|
||||
{isMentoring && (
|
||||
<MentoringBulkAssignToolbar roundId={roundId} configJson={config} />
|
||||
)}
|
||||
<ProjectStatesTable
|
||||
competitionId={competitionId}
|
||||
roundId={roundId}
|
||||
|
||||
Reference in New Issue
Block a user