feat: semi-finalist tracker dashboard, account reminders, search + UX fixes

- Add getSemiFinalistStats query with per-category/per-award breakdown
- Add sendAccountReminders mutation with invite token generation and dedup
- Add SemiFinalistTracker dashboard widget with progress bars and remind buttons
- Add ACCOUNT_REMINDER email template
- Extend project search to match team member name/email (7 locations)
- Fix Passed count deduplication: count distinct projects, not round-state rows
- Fix role switcher: visible pills above user section, auto-refresh session on mount

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 15:41:03 +01:00
parent af03c12ae5
commit 43e21c6c6e
10 changed files with 622 additions and 14 deletions

View File

@@ -35,6 +35,7 @@ import { ActivityFeed } from '@/components/dashboard/activity-feed'
import { CategoryBreakdown } from '@/components/dashboard/category-breakdown'
import { DashboardSkeleton } from '@/components/dashboard/dashboard-skeleton'
import { RecentEvaluations } from '@/components/dashboard/recent-evaluations'
import { SemiFinalistTracker } from '@/components/dashboard/semi-finalist-tracker'
type DashboardContentProps = {
editionId: string
@@ -125,6 +126,10 @@ export function DashboardContent({ editionId, sessionName }: DashboardContentPro
{ limit: 8 },
{ enabled: !!editionId, refetchInterval: 5_000 }
)
const { data: semiFinalistStats } = trpc.dashboard.getSemiFinalistStats.useQuery(
{ editionId },
{ enabled: !!editionId, refetchInterval: 60_000 }
)
if (isLoading) {
return <DashboardSkeleton />
@@ -271,7 +276,18 @@ export function DashboardContent({ editionId, sessionName }: DashboardContentPro
<SmartActions actions={nextActions} />
</AnimatedCard>
<AnimatedCard index={6}>
{semiFinalistStats && semiFinalistStats.byCategory.length > 0 && (
<AnimatedCard index={6}>
<SemiFinalistTracker
byCategory={semiFinalistStats.byCategory}
byAward={semiFinalistStats.byAward}
unactivatedProjects={semiFinalistStats.unactivatedProjects}
editionId={editionId}
/>
</AnimatedCard>
)}
<AnimatedCard index={7}>
<ActivityFeed activity={liveActivity ?? recentActivity} />
</AnimatedCard>
</div>
@@ -280,12 +296,12 @@ export function DashboardContent({ editionId, sessionName }: DashboardContentPro
{/* Bottom Full Width */}
<div className="grid gap-6 lg:grid-cols-12">
<div className="lg:col-span-8">
<AnimatedCard index={7}>
<AnimatedCard index={8}>
<GeographicSummaryCard programId={editionId} />
</AnimatedCard>
</div>
<div className="lg:col-span-4">
<AnimatedCard index={8}>
<AnimatedCard index={9}>
<CategoryBreakdown
categories={categoryBreakdown}
issues={oceanIssueBreakdown}