Skip to content

Release: contest admin polish and overview perf improvements#168

Merged
quan0715 merged 17 commits intomainfrom
dev
May 4, 2026
Merged

Release: contest admin polish and overview perf improvements#168
quan0715 merged 17 commits intomainfrom
dev

Conversation

@quan0715
Copy link
Copy Markdown
Owner

@quan0715 quan0715 commented May 4, 2026

Summary

  • Retire deprecated participants / logs panels in contest admin and consolidate into the overview surface.
  • Share a single dashboard fetch across overview panels to remove duplicate network round-trips.
  • Unblock first paint by deferring slow events fetch and dedupe participant polling.
  • Polish overview dashboards / insight rail / incident card visuals (event-type and priority icons).
  • Gate the 30s participant poll on contestInProgress so we no longer hammer the API for draft / pre-start / finished contests.

Commits

  • refactor(contest-admin): retire participants and logs panels
  • perf(contest-admin): share single dashboard fetch across overview panels
  • perf(contest-admin): unblock first paint on slow events fetch and dedupe participant polls
  • style(contest-admin): polish overview dashboards and incident card visuals
  • perf(contest-admin): only poll participants while contest is running

Test plan

  • Smoke: contest admin overview loads quickly (first paint not blocked by slow events fetch)
  • Smoke: incident card shows event-type / priority icons and inline labels for known event types
  • Smoke: published-and-running contest still polls participants every 30s; draft / scheduled / finished contests do NOT
  • Smoke: overview / insight rail / segmented dashboard layout looks correct on desktop and narrow viewport

🤖 Generated with Claude Code

quan0715 and others added 5 commits May 4, 2026 21:55
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>
Copilot AI review requested due to automatic review settings May 4, 2026 14:35
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +58 to +66
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
) {
Comment on lines +55 to +71
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: "視窗完整性恢復",
};
Comment on lines +105 to +119
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: "視窗完整性恢復",
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +86 to +93
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]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

quan0715 and others added 12 commits May 4, 2026 23:00
- 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>
@quan0715 quan0715 merged commit 37ddc2d into main May 4, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants