From d77eb4cbe5da14d2dd93b4cc8c360b0f66a21d19 Mon Sep 17 00:00:00 2001 From: NesiciCoding Date: Thu, 13 Aug 2026 16:31:26 +0200 Subject: [PATCH 1/2] perf: migrate StudentsPage to selector store MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the six domain-hook subscriptions with useStoreSelector/useStoreActions so the component reads a single store slice. 🤖 Generated with Codebuff Co-Authored-By: Codebuff --- src/pages/StudentsPage.tsx | 43 ++++++++++++++++++----- src/pages/__tests__/StudentsPage.test.tsx | 5 +++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/pages/StudentsPage.tsx b/src/pages/StudentsPage.tsx index 02a3b875..51bd7659 100644 --- a/src/pages/StudentsPage.tsx +++ b/src/pages/StudentsPage.tsx @@ -27,7 +27,7 @@ import type { EventData } from 'react-joyride'; import { DragDropContext, Droppable, Draggable, type DropResult } from '@hello-pangea/dnd'; import { getStudentsTourSteps } from '../data/TutorialSteps'; import Topbar from '../components/Layout/Topbar'; -import { useAssessment, useAuthoring, useClasses, useGrading, useSettings, useStudents } from '../context/AppContext'; +import { useStoreActions, useStoreSelector } from '../context/useStore'; import { useDbStatus } from '../hooks/useDbStatus'; import { useToast } from '../hooks/useToast'; import Papa from 'papaparse'; @@ -277,13 +277,40 @@ const derivedByStudentCache = new Map ({ + students: s.students, + classes: s.classes, + studentRubrics: s.studentRubrics, + rubrics: s.rubrics, + gradeScales: s.gradeScales, + selfAssessments: s.selfAssessments, + analysisResults: s.analysisResults, + tests: s.tests, + studentTests: s.studentTests, + settings: s.settings, + })); + const { + addStudent, + updateStudent, + deleteStudent, + setStudentPassword, + addClass, + updateClass, + deleteClass, + mergeClasses, + updateSettings, + } = useStoreActions(); const dbStatus = useDbStatus(); const { showToast } = useToast(); diff --git a/src/pages/__tests__/StudentsPage.test.tsx b/src/pages/__tests__/StudentsPage.test.tsx index 2a935347..e4a089f0 100644 --- a/src/pages/__tests__/StudentsPage.test.tsx +++ b/src/pages/__tests__/StudentsPage.test.tsx @@ -126,6 +126,11 @@ vi.mock('../../context/AppContext', () => ({ usePlatform: () => mockAppValue, })); +vi.mock('../../context/useStore', () => ({ + useStoreSelector: (selector: (state: any) => any) => selector(mockAppValue), + useStoreActions: () => mockAppValue, +})); + vi.mock('../../utils/cefrStudentAggregator', () => ({ getCefrStudentOverview: vi.fn( () => From 065adffb01e915c40c9695d97587596a7c400c88 Mon Sep 17 00:00:00 2001 From: NesiciCoding Date: Tue, 18 Aug 2026 09:15:30 +0200 Subject: [PATCH 2/2] fix(render): exclude archived students/deleted rubrics on StudentsPage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DELETE_STUDENT soft-deletes via archivedAt and the roster domain hooks filtered those rows out; the selector-store migration read the raw slice, so a deleted student stayed visible in the roster (caught by the E2E student-delete specs in all three browsers). Restore the active-student / active-rubric filtering. 🤖 Generated with Codebuff Co-Authored-By: Codebuff --- src/pages/StudentsPage.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pages/StudentsPage.tsx b/src/pages/StudentsPage.tsx index 51bd7659..6f89a202 100644 --- a/src/pages/StudentsPage.tsx +++ b/src/pages/StudentsPage.tsx @@ -278,9 +278,9 @@ export default function StudentsPage() { const { t, i18n } = useTranslation(); const navigate = useNavigate(); const { - students, + students: allStudents, classes, - studentRubrics, + studentRubrics: allStudentRubrics, rubrics, gradeScales, selfAssessments, @@ -300,6 +300,11 @@ export default function StudentsPage() { studentTests: s.studentTests, settings: s.settings, })); + // The roster domain hooks filtered soft-deleted rows; keep that behavior here. + // (DELETE_STUDENT soft-deletes via archivedAt — without this filter the student + // row would remain visible after deletion.) + const students = useMemo(() => allStudents.filter((s) => !s.archivedAt), [allStudents]); + const studentRubrics = useMemo(() => allStudentRubrics.filter((sr) => !sr.deletedAt), [allStudentRubrics]); const { addStudent, updateStudent,