From 1308c3ba8794fe5965ceaab7d9bcde8ad7dbf5bc Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 19 Feb 2026 08:20:13 +0100 Subject: [PATCH] Admin platform audit: fix bugs, harden backend, add auto-refresh, clean dead code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 — Critical bugs: - Fix deliberation participant selection (wire jury group query) - Fix reports "By Round" tab (inline content instead of 404 route) - Fix messages "Sent History" (add message.sent procedure, wire tab) - Add missing fields to competition award form (criteriaText, maxRankedPicks) - Wire LiveControlPanel buttons (cursor, voting, scores) - Fix ResultLockControls empty snapshot (fetch actual data before lock) - Fix SubmissionWindowManager losing fields on edit Phase 2 — Backend fixes: - Remove write-in-query from specialAward.get - Fix award eligibility job overwriting manual shortlist overrides - Fix filtering startJob deleting all prior results (defer cleanup to post-success) - Tighten access control: protectedProcedure → adminProcedure on 8 procedures - Add audit logging to deliberation mutations - Add FINALIST/SEMIFINALIST delete guard on project.delete/bulkDelete Phase 3 — Auto-refresh: - Add refetchInterval to 15+ admin pages/components (10s–30s) - Fix AI job polling: derive speed from job status for all viewers Phase 4 — Dead code cleanup: - Delete unused command-palette, pdf-report, admin-page-transition - Remove dead subItems sidebar code, unused GripVertical import - Replace redundant isGenerating state with mutation.isPending - Add Role column to jury members table - Remove misleading manual mentor assignment stub Phase 5 — UX improvements: - Fix rounds page single-competition assumption (add selector) - Remove raw UUID fallback in deliberation config - Fix programs page "Stage" → "Round" terminology Phase 6 — Backend hardening: - Complete logAudit calls (add prisma, ipAddress, userAgent) - Batch analytics queries (fix N+1 in getCrossRoundComparison, getYearOverYear) - Batch user.bulkCreate writes (assignments, jury memberships, intents) - Remove any casts from deliberation service (typed PrismaClient + TransactionClient) - Fix stale DeliberationStatus enum values blocking build 40 files changed, 1010 insertions(+), 612 deletions(-) Co-Authored-By: Claude Opus 4.6 --- src/app/(admin)/admin/audit/page.tsx | 2 +- src/app/(admin)/admin/awards/[id]/page.tsx | 2 +- src/app/(admin)/admin/awards/page.tsx | 5 +- .../[competitionId]/awards/new/page.tsx | 70 ++++-- .../deliberation/[sessionId]/page.tsx | 9 +- .../[competitionId]/deliberation/page.tsx | 90 +++++++- .../juries/[juryGroupId]/page.tsx | 5 +- .../competitions/[competitionId]/page.tsx | 7 +- src/app/(admin)/admin/competitions/page.tsx | 2 +- src/app/(admin)/admin/messages/page.tsx | 80 ++++--- src/app/(admin)/admin/programs/[id]/page.tsx | 2 +- .../admin/projects/[id]/mentor/page.tsx | 19 -- src/app/(admin)/admin/projects/[id]/page.tsx | 7 +- src/app/(admin)/admin/reports/page.tsx | 104 ++++++++- src/app/(admin)/admin/rounds/page.tsx | 25 +- .../deliberation/[sessionId]/page.tsx | 10 +- .../deliberation/admin-override-dialog.tsx | 4 +- .../admin/deliberation/results-panel.tsx | 12 +- .../admin/evaluation-summary-card.tsx | 8 +- .../admin/jury/jury-members-table.tsx | 8 +- .../admin/live/live-control-panel.tsx | 156 ++++++++----- src/components/admin/pdf-report.tsx | 166 -------------- .../admin/result/result-lock-controls.tsx | 38 ++- .../admin/round/file-requirements-editor.tsx | 1 - .../admin/round/filtering-dashboard.tsx | 10 +- .../admin/round/submission-window-manager.tsx | 8 +- .../rounds/config/deliberation-config.tsx | 9 +- .../jury/deliberation-ranking-form.tsx | 3 +- src/components/layouts/admin-sidebar.tsx | 27 --- src/server/routers/analytics.ts | 217 ++++++++++-------- src/server/routers/competition.ts | 2 +- src/server/routers/deliberation.ts | 63 ++++- src/server/routers/filtering.ts | 125 ++++++++-- src/server/routers/message.ts | 39 ++++ src/server/routers/project.ts | 22 +- src/server/routers/roundEngine.ts | 6 +- src/server/routers/specialAward.ts | 5 - src/server/routers/user.ts | 172 ++++++++------ src/server/services/award-eligibility-job.ts | 48 +++- src/server/services/deliberation.ts | 36 +-- 40 files changed, 1011 insertions(+), 613 deletions(-) delete mode 100644 src/components/admin/pdf-report.tsx diff --git a/src/app/(admin)/admin/audit/page.tsx b/src/app/(admin)/admin/audit/page.tsx index 2d3faab..df1cc8c 100644 --- a/src/app/(admin)/admin/audit/page.tsx +++ b/src/app/(admin)/admin/audit/page.tsx @@ -151,7 +151,7 @@ export default function AuditLogPage() { ) // Fetch audit logs - const { data, isLoading, refetch } = trpc.audit.list.useQuery(queryInput) + const { data, isLoading, refetch } = trpc.audit.list.useQuery(queryInput, { refetchInterval: 30_000 }) // Fetch users for filter dropdown const { data: usersData } = trpc.user.list.useQuery({ diff --git a/src/app/(admin)/admin/awards/[id]/page.tsx b/src/app/(admin)/admin/awards/[id]/page.tsx index 474f8f8..474588c 100644 --- a/src/app/(admin)/admin/awards/[id]/page.tsx +++ b/src/app/(admin)/admin/awards/[id]/page.tsx @@ -162,7 +162,7 @@ export default function AwardDetailPage({ // Core queries — lazy-load tab-specific data based on activeTab const { data: award, isLoading, refetch } = - trpc.specialAward.get.useQuery({ id: awardId }) + trpc.specialAward.get.useQuery({ id: awardId }, { refetchInterval: 30_000 }) const { data: eligibilityData, refetch: refetchEligibility } = trpc.specialAward.listEligible.useQuery({ awardId, diff --git a/src/app/(admin)/admin/awards/page.tsx b/src/app/(admin)/admin/awards/page.tsx index 49ab981..6566bf1 100644 --- a/src/app/(admin)/admin/awards/page.tsx +++ b/src/app/(admin)/admin/awards/page.tsx @@ -40,7 +40,10 @@ const SCORING_LABELS: Record = { } export default function AwardsListPage() { - const { data: awards, isLoading } = trpc.specialAward.list.useQuery({}) + const { data: awards, isLoading } = trpc.specialAward.list.useQuery( + {}, + { refetchInterval: 30_000 } + ) const [search, setSearch] = useState('') const debouncedSearch = useDebounce(search, 300) diff --git a/src/app/(admin)/admin/competitions/[competitionId]/awards/new/page.tsx b/src/app/(admin)/admin/competitions/[competitionId]/awards/new/page.tsx index e682960..9112d0f 100644 --- a/src/app/(admin)/admin/competitions/[competitionId]/awards/new/page.tsx +++ b/src/app/(admin)/admin/competitions/[competitionId]/awards/new/page.tsx @@ -22,8 +22,10 @@ export default function NewAwardPage({ params: paramsPromise }: { params: Promis const [formData, setFormData] = useState({ name: '', description: '', + criteriaText: '', useAiEligibility: false, - scoringMode: 'PICK_WINNER' as 'PICK_WINNER' | 'RANKED' | 'SCORED' + scoringMode: 'PICK_WINNER' as 'PICK_WINNER' | 'RANKED' | 'SCORED', + maxRankedPicks: '3', }); const { data: competition } = trpc.competition.getById.useQuery({ @@ -63,8 +65,10 @@ export default function NewAwardPage({ params: paramsPromise }: { params: Promis competitionId: params.competitionId, name: formData.name.trim(), description: formData.description.trim() || undefined, + criteriaText: formData.criteriaText.trim() || undefined, scoringMode: formData.scoringMode, - useAiEligibility: formData.useAiEligibility + useAiEligibility: formData.useAiEligibility, + maxRankedPicks: formData.scoringMode === 'RANKED' ? parseInt(formData.maxRankedPicks) : undefined, }); }; @@ -114,22 +118,17 @@ export default function NewAwardPage({ params: paramsPromise }: { params: Promis
- - + +