Conversation
Overview now owns participant management: - AdminOverviewCommandCenter pulls in unlock / reopen / status edit / remove handlers, wires ParticipantStatusEditModal + ConfirmModal, and runs the 10s participants polling that ContestParticipantsScreen used to drive. - AdminOverviewScreen exposes a header-level "新增參賽者" action (hidden when classroom-bound) that opens AddParticipantModal. Panel surface reduced: - Drop "participants" / "logs" from AdminPanelId, the renderer registry, and PaperExam / Coding module panel arrays. - Remove the matching SideMenu nav meta entries so neither shows up in the contest admin navigation. - Remove the participants entry from AdminPreparationDashboard and the ageing "violationCount → logs" / "participants" widgets from OverviewActionWidgets. - Delete ContestParticipantsScreen and its participantsScreen.config (their behaviour now lives in the overview drawer). i18n: - Drop adminLayout.nav.participants / adminLayout.nav.logs in zh-TW / en / ja / ko. - Add adminOverview.screen.actions.addParticipant. Tests updated for the new surface (visibility, drawer animation, event-type humanisation, panel mocks). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AdminExamResultOverview and AdminQuestionStatsGallery each used to call useContestResultDashboard internally, which meant getExamDashboardSummary fired twice on mount and twice again on every refreshKey bump. Lift the hook up into AdminOverviewScreen and pass dashboard / loading / error / loadQuestionDetail / detailLoadingIds / detailErrors through props so both children share one fetch. Tests updated to provide the lifted props directly instead of mocking the hook. Also realign AdminOverviewCommandCenter test for the ProportionalMeterChart distribution view (replaces the per-status progress bars and inline 考生分佈總覽 heading). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…upe participant polls ContestAdminContext used to gate participants and exam events behind a shared Promise.all, so the heavier getExamEvents + getContestActivities payload would hold up the participant grid for as long as those took to return. Split the slices: - refreshParticipants and refreshOverviewMetrics resolve initialLoading on their own, so the participant grid can render the moment the participants payload lands. - refreshExamEvents runs alongside in parallel and tracks its own examEventsLoading flag. - refreshAdminData / refreshAllAdminData still exist for explicit refresh calls, but now compose the smaller helpers. The 10s participant poll in AdminOverviewCommandCenter was also re-triggering full re-renders every cycle because each successful fetch replaced the participants array with a fresh reference. Compare the volatile fields (status, score, violations, lock metadata, timestamps) in refreshParticipants and bail on setState when the snapshot is unchanged so background polls stay quiet. ContestLogsScreen embedded view + AdminInsightRail priority events card now consume examEventsLoading so they keep their loading skeletons while events are still in flight, instead of flashing an empty state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…suals Add event-type and priority icons to IncidentCard, refine InsightRail layout and segmented dashboard styles, and tweak sidebar / workspace / panel toolbar spacing. Also nudge ContestLogsScreen styles, eventTaxonomy labels, and useGradingData hook. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 30s background poll on AdminOverviewCommandCenter ran whenever the admin panel was open, even on draft / pre-start / already-finished contests where the participant snapshot can't change. Compute contestInProgress in AdminOverviewScreen (published + now within [startTime, endTime)) and gate the interval on that flag, so the only participants/ traffic outside an active exam is the explicit refresh the admin triggers themselves. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR refactors the contest admin experience by retiring the dedicated Participants/Logs panels and consolidating key information into the Overview, while reducing duplicate fetches and improving perceived load performance.
Changes:
- Removed deprecated Participants/Logs admin panels and moved core participant/logs workflows into the Overview command center.
- Reduced duplicate network work by sharing a single result-dashboard fetch and deferring the heavier exam-events fetch so first paint isn’t blocked.
- Polished Overview visuals (incident/event icons, segmented dashboard header layout) and gated participant polling to only run while the contest is actually in progress.
Reviewed changes
Copilot reviewed 44 out of 44 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/shared/ui/list/PanelToolbar.module.scss | Align toolbar height with Carbon “md” button sizing. |
| frontend/src/i18n/locales/zh-TW/contest.json | Removes nav entries for retired panels; adds “add participant” action label. |
| frontend/src/i18n/locales/ko/contest.json | Removes nav entries for retired panels. |
| frontend/src/i18n/locales/ja/contest.json | Removes nav entries for retired panels. |
| frontend/src/i18n/locales/en/contest.json | Removes nav entries for retired panels; adds “add participant” action label. |
| frontend/src/features/contest/screens/settings/participants/participantsScreen.config.ts | Deletes participants screen config (screen retired). |
| frontend/src/features/contest/screens/settings/grading/useGradingData.ts | Adds option to avoid refetching grading data when only participant list changes. |
| frontend/src/features/contest/screens/settings/ContestParticipantsScreen.tsx | Removes the legacy Participants screen (panel retired). |
| frontend/src/features/contest/screens/settings/ContestLogsScreen.tsx | Improves embedded logs rendering (icons/labels) and loading state handling. |
| frontend/src/features/contest/screens/settings/ContestLogsScreen.test.tsx | Updates logs screen tests for new label rendering and loading flags. |
| frontend/src/features/contest/screens/settings/ContestLogsScreen.module.scss | Adds styling for new skeleton/feed padding and compact priority icons. |
| frontend/src/features/contest/screens/index.ts | Stops exporting the removed Participants screen. |
| frontend/src/features/contest/screens/admin/panels/AdminOverviewScreen.tsx | Adds header action bar, “add participant” flow, shared dashboard fetch, and in-progress gating signals. |
| frontend/src/features/contest/screens/admin/panels/AdminOverviewScreen.test.tsx | Updates tests for new Overview header structure (no PanelToolbar heading). |
| frontend/src/features/contest/screens/admin/panels/AdminOverviewScreen.module.scss | Adds contest header layout styles and adjusts page padding. |
| frontend/src/features/contest/modules/types.ts | Removes “logs”/“participants” from AdminPanelId. |
| frontend/src/features/contest/modules/PaperExamModule.tsx | Removes retired Participants panel from paper-exam admin panels list. |
| frontend/src/features/contest/modules/CodingModule.tsx | Removes retired Participants panel from coding admin panels list. |
| frontend/src/features/contest/modules/adminPanelVisibility.test.ts | Updates visibility expectations (participants panel no longer present). |
| frontend/src/features/contest/modules/AdminPanelRendererRegistry.tsx | Removes renderers for retired Participants/Logs panels. |
| frontend/src/features/contest/contexts/ContestAdminContext.tsx | Splits events loading state, dedupes participant polling updates, defers event fetch from first paint. |
| frontend/src/features/contest/constants/eventTaxonomy.ts | Adjusts priority tag color mapping (P2 → purple). |
| frontend/src/features/contest/components/admin/statistics/AdminQuestionStatsGallery.tsx | Accepts shared dashboard props and adds drawer animations. |
| frontend/src/features/contest/components/admin/statistics/AdminQuestionStatsGallery.test.tsx | Refactors tests to pass dashboard props directly and accounts for animated unmount. |
| frontend/src/features/contest/components/admin/statistics/AdminExamResultOverview.tsx | Accepts shared dashboard props instead of owning the fetch hook. |
| frontend/src/features/contest/components/admin/statistics/AdminExamResultOverview.test.tsx | Refactors tests to pass dashboard props directly. |
| frontend/src/features/contest/components/admin/OverviewActionWidgets.tsx | Removes widgets that navigated to retired panels. |
| frontend/src/features/contest/components/admin/layout/AdminDashboardLayout.tsx | Removes retired panel nav items (participants/logs). |
| frontend/src/features/contest/components/admin/IncidentCard.tsx | Adds event-type/priority iconography and label fallbacks for incidents. |
| frontend/src/features/contest/components/admin/IncidentCard.module.scss | Adds icon coloring and adjusts P2 styling. |
| frontend/src/features/contest/components/admin/AdminSegmentedDashboard.tsx | Supports optional header row for the segmented dashboard layout. |
| frontend/src/features/contest/components/admin/AdminSegmentedDashboard.module.scss | Adds header row border styling. |
| frontend/src/features/contest/components/admin/AdminPreparationDashboard.tsx | Removes participants entry that navigated to retired panel. |
| frontend/src/features/contest/components/admin/AdminPreparationDashboard.test.tsx | Updates tests to reflect removed participants entry. |
| frontend/src/features/contest/components/admin/AdminOverviewCommandCenter.tsx | Consolidates participant drilldown + logs feed into Overview; adds status actions, polling gate, and animated participant drawer. |
| frontend/src/features/contest/components/admin/AdminOverviewCommandCenter.test.tsx | Updates tests for MeterChart + animated drawer behavior and new context mocks. |
| frontend/src/features/contest/components/admin/AdminOverviewCommandCenter.module.scss | Tweaks padding to match the new overview layout. |
| frontend/src/features/contest/components/admin/AdminInsightRail.tsx | Switches distribution UI to a MeterChart and normalizes priority-series grouping. |
| frontend/src/features/contest/components/admin/AdminInsightRail.module.scss | Adds styles for the new chart frame and description text. |
| frontend/src/features/chatbot/components/workspace/WorkspaceShell.module.scss | Removes left-panel border and aligns styling with app layer tokens. |
| frontend/src/features/app/components/workspace/WorkspaceTopNav.module.scss | Switches top nav background token to layer color. |
| frontend/src/features/app/components/SideMenu.tsx | Removes retired admin panel links; improves admin route matching; adds compact classroom avatar initial. |
| frontend/src/features/app/components/SideMenu.scss | Adds styling for compact classroom avatar and active state. |
| frontend/src/features/app/components/AppSidebar.module.scss | Adjusts border behavior and collapsed layout styling. |
Comments suppressed due to low confidence (1)
frontend/src/i18n/locales/en/contest.json:90
- New key adminOverview.screen.actions.addParticipant was added here, but it’s not present in other locale files (e.g. ja/ko). Since the UI calls t(key, fallback) with a Chinese fallback string, non-English locales will show the Chinese tooltip/label for this new action. Please add the corresponding translation key in the other contest.json locales (or run the repo’s i18n sync/check scripts) to keep translations consistent.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| a.userId !== b.userId || | ||
| a.examStatus !== b.examStatus || | ||
| a.connectionStatus !== b.connectionStatus || | ||
| a.liveMonitoringOnline !== b.liveMonitoringOnline || | ||
| a.score !== b.score || | ||
| a.violationCount !== b.violationCount || | ||
| a.lockReason !== b.lockReason || | ||
| a.submitReason !== b.submitReason | ||
| ) { |
| const EVENT_TYPE_LABEL_FALLBACK: Record<string, string> = { | ||
| mouse_leave_triggered: "滑鼠離開視窗(觸發)", | ||
| exit_fullscreen_triggered: "退出全螢幕(觸發)", | ||
| tab_hidden_triggered: "分頁隱藏(觸發)", | ||
| tab_hidden_restored: "分頁可見(恢復)", | ||
| window_blur_triggered: "視窗失焦(觸發)", | ||
| window_blur_restored: "回到考試視窗(恢復)", | ||
| multi_display_triggered: "多螢幕偵測(觸發)", | ||
| multi_display_restored: "多螢幕狀態恢復", | ||
| screen_share_interrupted: "螢幕分享中斷", | ||
| screen_share_restored: "螢幕分享恢復", | ||
| webcam_interrupted: "Webcam 中斷", | ||
| webcam_restored: "Webcam 恢復", | ||
| webcam_quality_degraded: "Webcam 品質下降", | ||
| viewport_interrupted: "視窗完整性中斷", | ||
| viewport_restored: "視窗完整性恢復", | ||
| }; |
| mouse_leave_triggered: "滑鼠離開視窗(觸發)", | ||
| exit_fullscreen_triggered: "退出全螢幕(觸發)", | ||
| tab_hidden_triggered: "分頁隱藏(觸發)", | ||
| tab_hidden_restored: "分頁可見(恢復)", | ||
| window_blur_triggered: "視窗失焦(觸發)", | ||
| window_blur_restored: "回到考試視窗(恢復)", | ||
| multi_display_triggered: "多螢幕偵測(觸發)", | ||
| multi_display_restored: "多螢幕狀態恢復", | ||
| screen_share_interrupted: "螢幕分享中斷", | ||
| screen_share_restored: "螢幕分享恢復", | ||
| webcam_interrupted: "Webcam 中斷", | ||
| webcam_restored: "Webcam 恢復", | ||
| webcam_quality_degraded: "Webcam 品質下降", | ||
| viewport_interrupted: "視窗完整性中斷", | ||
| viewport_restored: "視窗完整性恢復", |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b0fddabb42
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const contestInProgress = useMemo(() => { | ||
| if (!contest || contest.status !== "published") return false; | ||
| const startMs = new Date(contest.startTime).getTime(); | ||
| const endMs = new Date(contest.endTime).getTime(); | ||
| if (Number.isNaN(startMs) || Number.isNaN(endMs)) return false; | ||
| const now = Date.now(); | ||
| return now >= startMs && now < endMs; | ||
| }, [contest]); |
There was a problem hiding this comment.
Recompute contestInProgress on wall-clock changes
contestInProgress is memoized only on contest, but it derives from Date.now(), so its value freezes after render. If an admin opens the page before startTime, the 30s participant poll never starts when the contest actually begins; if they keep the tab open past endTime, polling keeps running unnecessarily. This breaks the intended “poll only while running” behavior for long-lived sessions and can both miss live updates and generate avoidable API traffic.
Useful? React with 👍 / 👎.
- ContestAdminContext: include liveMonitoringSources in participant snapshot comparison so screen_share/webcam status changes propagate to the drawer. - eventTaxonomy: extract shared getEventTypeLabel / getEventTypeIcon / getPriorityIcon and switch ContestLogsScreen + IncidentCard to use them (drops duplicate hardcoded zh-TW fallback tables). - i18n: add 15 new logs.eventTypes.* keys to en/ja/ko/zh-TW so non-zh locales no longer fall back to Chinese strings; add missing adminOverview.screen.actions.addParticipant translations to ja/ko; pick up sync:i18n side-effects on common.json. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…wiring - Trim AdminExamResultOverview metric cells (drop caption + 60% legend text) and update its test to match. - Simplify AdminInsightRail rendering and AdminOverviewCommandCenter + AdminQuestionStatsGallery handoff for the consolidated overview. - Drop unused AdminPreparationDashboard fetch from AdminOverviewScreen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Generic FilterOption<TId> so QUESTION_KIND_FILTERS no longer aliases ParticipantStatusFilter. - Type-cast cloneElement target with the real AdminQuestionStatsGallery prop subset. - Tighten getEventTypeLabel translate-fn signature for i18next TFunction compatibility; drop unused getPriorityIcon import in ContestLogsScreen. - Pick up further admin overview tweaks (insight rail / overview command center / exam result overview / overview screen / question stats gallery) that landed in parallel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backend stores ExamQuestion.order as a 0-based index. The result dashboard was rendering it directly, so the question stats gallery showed Q0, Q1, Q2 ... while the rest of the exam UI counts from Q1. Add a small getDisplayOrder helper and apply it everywhere the gallery exposes the order (card label, dialog aria-label, drawer heading, search haystack). Test updated to match the new labels. Note: any duplicate labels in the gallery (e.g. multiple Q1 cards) indicate the underlying ExamQuestion rows share the same order value in the database — that's a data integrity issue worth investigating separately. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Existing dev / prod data contains contests where multiple ExamQuestion rows share the same ``order`` value, surfaced in the question stats gallery as repeating Q0 / Q1 cards. Two compounding bugs: - The exam editor sends explicit order from the client (e.g. ``source.order + 1`` when duplicating a question, or ``questions.length`` when appending), and ``perform_create`` honoured the value without checking for collisions. - ``ExamQuestion.Meta`` had an index on (contest, order) but no unique constraint, so the database happily accepted the dups. This change patches both ends: - New management command ``renumber_exam_questions`` (with --dry-run / --contest-id / --all flags) compacts every contest's orders to a contiguous 0..N-1 range using a two-pass negative-pivot rewrite that stays legal under the new unique constraint. - Migration 0075 runs the same logic on existing data; 0076 then adds ``UniqueConstraint(fields=['contest', 'order'])`` with ``Deferrable.DEFERRED`` so transactional reorders can flip orders mid-flight without violating the constraint. - ``perform_create`` now wraps the insert in ``transaction.atomic``, uses ``select_for_update`` to serialise concurrent writes, and applies push-semantics: when the client requests an explicit slot K, every existing row with order >= K shifts up by 1 before the new row lands. - ``import_from_bank`` and ``reorder`` likewise lock the contest's rows and run inside ``transaction.atomic`` so concurrent admins can't race past max(order) or break the new constraint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rtening Same-day startDateTimeLabel / endDateTimeLabel drop the date prefix per the recent normalizeScheduleLabels behaviour; update fixtures so the preparation/dashboard model tests match runtime output. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
contestInProgressso we no longer hammer the API for draft / pre-start / finished contests.Commits
refactor(contest-admin): retire participants and logs panelsperf(contest-admin): share single dashboard fetch across overview panelsperf(contest-admin): unblock first paint on slow events fetch and dedupe participant pollsstyle(contest-admin): polish overview dashboards and incident card visualsperf(contest-admin): only poll participants while contest is runningTest plan
🤖 Generated with Claude Code