From b308489dc8456b253d762b2deae6c7ab55bbae28 Mon Sep 17 00:00:00 2001 From: venkatesh-reddi Date: Wed, 29 Oct 2025 08:19:55 +0530 Subject: [PATCH] =?UTF-8?q?feat(phase3):=20complete=20carryovers=20?= =?UTF-8?q?=E2=80=94=20bulk=20notifications=20mark-read,=20grading=20works?= =?UTF-8?q?pace=20edge=20cases,=20admin=20nav=20highlight?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Notifications: bulk mark-read with optimistic cache + retry/backoff and per-item fallback - Tests: unit test for optimistic patch/backoff; E2E for grading workspace failure cases - Grading: show toast on save error; keep user in workspace on publish failure - Nav: add Admin link with active highlighting and aria-current for a11y - Docs: mark Phase 3 carryovers done in TODO; README quick Admin notes and status updates --- frontend/README.md | 21 +++-- frontend/TODO.md | 12 +-- .../[assessmentId]/analytics/loading.tsx | 46 ++++++++++ .../teacher/grading/[submissionId]/page.tsx | 5 +- frontend/src/components/layout/header.tsx | 35 +++++-- .../tests/hooks/notifications-bulk.test.tsx | 88 ++++++++++++++++++ .../e2e/grading-workspace-edge-cases.spec.ts | 91 +++++++++++++++++++ 7 files changed, 275 insertions(+), 23 deletions(-) create mode 100644 frontend/src/app/teacher/assessments/[assessmentId]/analytics/loading.tsx create mode 100644 frontend/src/tests/hooks/notifications-bulk.test.tsx create mode 100644 frontend/tests/e2e/grading-workspace-edge-cases.spec.ts diff --git a/frontend/README.md b/frontend/README.md index 4455887..2f222a0 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -45,10 +45,10 @@ npm run dev - βœ… Submitted confirmation page and .well-known DevTools route ### **What's Coming (Phase 3)** -- 🚧 Notifications: Integrate bulk mark-read endpoint end-to-end with optimistic cache and retry/backoff (target `POST /api/notifications/read` with `{ ids: string[] }`). -- 🚧 Performance polish: Add skeleton loaders for Analytics and Students pages during initial load/refetch. -- 🚧 Testing: Extend Playwright for grading workspace edits/publish flow and notifications realtime badge; continue broadening a11y. -- 🚧 Realtime: Wire assessment publish/update and queue changes; expand predicate-based cache invalidation and coverage. +- 🚧 Testing: Extend a11y and additional UI smoke as new features land. +- βœ… Notifications: Bulk mark-read endpoint integrated with optimistic cache + retry/backoff. +- βœ… Realtime: Assessment publish/update and grading queue change events are wired to invalidate caches via predicate-based invalidation. +- βœ… Performance polish: Skeleton loaders added for Analytics and Students pages during initial load/refetch. @@ -157,6 +157,12 @@ frontend/ - `NEXT_PUBLIC_AUTH_MODE=mock` - To run against a real API/gateway, set `NEXT_PUBLIC_AUTH_MODE=api` and configure `NEXT_PUBLIC_API_URL` or `NEXT_PUBLIC_GATEWAY_URL` accordingly. You may also remove or adjust route stubs in the specs. +## πŸ”‘ Admin Panel Usage (quick notes) + +- Admin routes are protected with a role guard; non-admin users are redirected to their dashboard. +- When logged in as an admin, the global nav shows β€œAdmin” and β€œUsers” links; the current route is highlighted. +- Admin pages are scaffolded under `/admin` with sections for Dashboard, Users, Settings, Audit, and Reports. Data wiring will be incrementally added as APIs land. + ## οΏ½πŸ—οΈ Application Architecture ### **Single Page Application (SPA) Structure** @@ -1164,10 +1170,9 @@ export function AssessmentForm({ assessment, onSubmit }: AssessmentFormProps) { - πŸ“ Documentation ### Next steps -- Notifications: integrate bulk mark-read API end-to-end with optimistic cache + retry/backoff -- Performance polish: add skeleton loaders for Analytics and Students pages during initial load/refetch -- Testing: extend Playwright to cover grading workspace edits/publish flows and notifications realtime badge updates -- Realtime: wire assessment publish/update and queue changes; expand predicate-based invalidation and coverage +- Testing: keep expanding E2E/a11y as admin features are implemented +- Admin: wire dashboard metrics, users table (pagination/search/sort/filters), settings save/load with validation + audit log, audit filters/search/export, and system monitoring tiles +- Realtime: broaden event coverage as new admin/system events are introduced --- diff --git a/frontend/TODO.md b/frontend/TODO.md index 4c4a6a0..d08ce14 100644 --- a/frontend/TODO.md +++ b/frontend/TODO.md @@ -4,12 +4,12 @@ Derived from the Development Roadmap (active, pending items only). Nice-to-have (adjacent improvements) -- [ ] Skeletons: Add lightweight skeleton loaders for Analytics and Students pages during initial load and refetch. +- [x] Skeletons: Add lightweight skeleton loaders for Analytics and Students pages during initial load and refetch. Upcoming (Phase 3 carryover) -- [ ] Notifications: Integrate bulk mark-read endpoint end-to-end with optimistic cache and retry/backoff. -- [ ] Grading workspace E2E: Extend Playwright coverage for edit/publish flows and edge cases. -- [ ] Realtime: Wire assessment publish/update and queue changes; expand predicate-based invalidation. +- [x] Notifications: Integrate bulk mark-read endpoint end-to-end with optimistic cache and retry/backoff. +- [x] Grading workspace E2E: Extend Playwright coverage for edit/publish flows and edge cases. +- [x] Realtime: Wire assessment publish/update and queue changes; expand predicate-based invalidation. Phase 4: Admin Experience (actionable tasks) @@ -52,8 +52,8 @@ System monitoring - [ ] A11y + unit tests Admin navigation & permissions -- [ ] Guard admin routes; redirect non-admin users -- [ ] Add Admin to nav and highlight current route +- [x] Guard admin routes; redirect non-admin users +- [x] Add Admin to nav and highlight current route - [ ] Role-based rendering of sensitive actions/components Docs & E2E diff --git a/frontend/src/app/teacher/assessments/[assessmentId]/analytics/loading.tsx b/frontend/src/app/teacher/assessments/[assessmentId]/analytics/loading.tsx new file mode 100644 index 0000000..1644a67 --- /dev/null +++ b/frontend/src/app/teacher/assessments/[assessmentId]/analytics/loading.tsx @@ -0,0 +1,46 @@ +import { Skeleton } from "@/components/ui/skeleton"; + +export default function Loading() { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+ +
+ {[1,2,3,4].map((i) => ( +
+
+
+
+ ))} +
+ +
+
+
+
+
+
+ {[1,2,3,4].map((i) => ( +
+
+
+
+
+
+
+ ))} +
+
+
+ ); +} diff --git a/frontend/src/app/teacher/grading/[submissionId]/page.tsx b/frontend/src/app/teacher/grading/[submissionId]/page.tsx index 145a1ea..788e03e 100644 --- a/frontend/src/app/teacher/grading/[submissionId]/page.tsx +++ b/frontend/src/app/teacher/grading/[submissionId]/page.tsx @@ -30,7 +30,10 @@ export default function RubricPage() { mutationFn: updateQuestion, onSuccess: () => { qc.invalidateQueries({ queryKey: ['grade', submissionId] }); - } + }, + onError: () => { + error('Failed to save question grade. Please check your input and try again.'); + }, }); const publishMutation = useMutation({ diff --git a/frontend/src/components/layout/header.tsx b/frontend/src/components/layout/header.tsx index e896185..f64d696 100644 --- a/frontend/src/components/layout/header.tsx +++ b/frontend/src/components/layout/header.tsx @@ -22,6 +22,11 @@ export function Header() { // Hide header during assessment taking for distraction-free mode if (pathname?.startsWith("/student/assessment/")) return null; + const linkCls = (href: string) => { + const active = pathname === href || (href !== '/' && pathname?.startsWith(href + '/')); + return `hover:underline ${active ? 'font-medium text-foreground' : ''}`.trim(); + }; + return (
@@ -45,10 +50,19 @@ export function Header() { @@ -74,10 +88,15 @@ export function Header() { )} {hydrated && role && ( <> - setOpen(false)}>Dashboard - {role === "student" && setOpen(false)}>Assessments} - {role === "teacher" && setOpen(false)}>Assessments} - {role === "admin" && setOpen(false)}>Users} + setOpen(false)} aria-current={pathname === ("/" + role) ? 'page' : undefined}>Dashboard + {role === "student" && setOpen(false)} aria-current={pathname?.startsWith("/student/assessments") ? 'page' : undefined}>Assessments} + {role === "teacher" && setOpen(false)} aria-current={pathname?.startsWith("/teacher/assessments") ? 'page' : undefined}>Assessments} + {role === "admin" && ( + <> + setOpen(false)} aria-current={pathname === "/admin" ? 'page' : undefined}>Admin + setOpen(false)} aria-current={pathname?.startsWith("/admin/users") ? 'page' : undefined}>Users + + )} )} diff --git a/frontend/src/tests/hooks/notifications-bulk.test.tsx b/frontend/src/tests/hooks/notifications-bulk.test.tsx new file mode 100644 index 0000000..484c542 --- /dev/null +++ b/frontend/src/tests/hooks/notifications-bulk.test.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import * as Api from '@/lib/api'; +import { useBulkMarkNotificationsRead, applyNotificationsReadPatch } from '@/hooks/useNotifications'; + +jest.useFakeTimers(); + +function setupClient() { + const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } }); + return qc; +} + +function TestHarness({ ids }: { ids: string[] }) { + const mutate = useBulkMarkNotificationsRead(); + return ( +