fix(nav): trim burger menu, fix MyClasses for teachers, fix NotFound crash

- Header: trim menu to My Work section (My Lessons, My Classes, Lesson Plans), School Admin (gated), Platform Admin (gated)
- MyClassesPage: fix loading/error state destructure (classesLoading not myClassesLoading)
- NotFound: fix ErrorOutline -> ErrorOutlineIcon to prevent 404 page crash
- timetableService: getMyClasses now calls both /me/teacher and /me/student, merges with role annotation
- timetableStore: myClasses type updated to ClassWithRole[]
- timetable.types: add ClassWithRole interface and code/institute_id optional fields to Class

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
kcar 2026-05-27 12:58:33 +01:00
parent 98a4212e46
commit 63d6d1bbca
6 changed files with 42 additions and 114 deletions

View File

@ -219,9 +219,9 @@ const Header: React.FC = () => {
</MenuItem>, </MenuItem>,
<Divider key="home-divider" />, <Divider key="home-divider" />,
// Timetable Section // My Work Section
<Typography <Typography
key="timetable-header" key="work-header"
variant="subtitle2" variant="subtitle2"
sx={{ sx={{
px: 2, px: 2,
@ -233,26 +233,8 @@ const Header: React.FC = () => {
fontSize: '0.75rem' fontSize: '0.75rem'
}} }}
> >
Timetable & Classes My Work
</Typography>, </Typography>,
<MenuItem key="timetable" onClick={() => handleNavigation('/timetable')}>
<ListItemIcon>
<Schedule />
</ListItemIcon>
<ListItemText
primary="My Timetable"
secondary="View your schedule"
/>
</MenuItem>,
<MenuItem key="classes" onClick={() => handleNavigation('/classes')}>
<ListItemIcon>
<Class />
</ListItemIcon>
<ListItemText
primary="Browse Classes"
secondary="Find and enroll in classes"
/>
</MenuItem>,
<MenuItem key="my-classes" onClick={() => handleNavigation('/my-classes')}> <MenuItem key="my-classes" onClick={() => handleNavigation('/my-classes')}>
<ListItemIcon> <ListItemIcon>
<Book /> <Book />
@ -262,15 +244,6 @@ const Header: React.FC = () => {
secondary="Manage your classes" secondary="Manage your classes"
/> />
</MenuItem>, </MenuItem>,
<MenuItem key="enrollment-requests" onClick={() => handleNavigation('/enrollment-requests')}>
<ListItemIcon>
<Enrollment />
</ListItemIcon>
<ListItemText
primary="Enrollment Requests"
secondary="Review pending enrollments"
/>
</MenuItem>,
<MenuItem key="my-lessons" onClick={() => handleNavigation('/my-lessons')}> <MenuItem key="my-lessons" onClick={() => handleNavigation('/my-lessons')}>
<ListItemIcon> <ListItemIcon>
<Lessons /> <Lessons />
@ -289,16 +262,7 @@ const Header: React.FC = () => {
secondary="Plan and manage lessons" secondary="Plan and manage lessons"
/> />
</MenuItem>, </MenuItem>,
<MenuItem key="student-lessons" onClick={() => handleNavigation('/student-lessons')}> <Divider key="work-divider" />,
<ListItemIcon>
<Student />
</ListItemIcon>
<ListItemText
primary="Student Lessons"
secondary="Your class timetable"
/>
</MenuItem>,
<Divider key="timetable-divider" />,
// School Admin Section // School Admin Section
...(isSchoolAdmin ? [ ...(isSchoolAdmin ? [
@ -331,71 +295,6 @@ const Header: React.FC = () => {
<Divider key="school-admin-divider" />, <Divider key="school-admin-divider" />,
] : []), ] : []),
// Features Section
<Typography
key="features-header"
variant="subtitle2"
sx={{
px: 2,
py: 1,
color: theme.palette.text.secondary,
fontWeight: 600,
letterSpacing: '0.5px',
textTransform: 'uppercase',
fontSize: '0.75rem'
}}
>
Features
</Typography>,
<MenuItem key="calendar" onClick={() => handleNavigation('/calendar')}>
<ListItemIcon>
<Calendar />
</ListItemIcon>
<ListItemText primary="Calendar" />
</MenuItem>,
<MenuItem key="teacher-planner" onClick={() => handleNavigation('/teacher-planner')}>
<ListItemIcon>
<TeacherPlanner />
</ListItemIcon>
<ListItemText primary="Teacher Planner" />
</MenuItem>,
<MenuItem key="exam-marker" onClick={() => handleNavigation('/exam-marker')}>
<ListItemIcon>
<ExamMarker />
</ListItemIcon>
<ListItemText primary="Exam Marker" />
</MenuItem>,
<Divider key="features-divider" />,
// Utilities Section
<Typography
key="utilities-header"
variant="subtitle2"
sx={{
px: 2,
py: 1,
color: theme.palette.text.secondary,
fontWeight: 600,
letterSpacing: '0.5px',
textTransform: 'uppercase',
fontSize: '0.75rem'
}}
>
Utilities
</Typography>,
<MenuItem key="settings" onClick={() => handleNavigation('/settings')}>
<ListItemIcon>
<Settings />
</ListItemIcon>
<ListItemText primary="Settings" />
</MenuItem>,
<MenuItem key="search" onClick={() => handleNavigation('/search')}>
<ListItemIcon>
<Search />
</ListItemIcon>
<ListItemText primary="Search" />
</MenuItem>,
// Platform Admin Section // Platform Admin Section
...(isPlatformAdmin ? [ ...(isPlatformAdmin ? [
<Divider key="admin-divider" />, <Divider key="admin-divider" />,
@ -420,8 +319,6 @@ const Header: React.FC = () => {
</MenuItem> </MenuItem>
] : []), ] : []),
// Authentication Section
<Divider key="auth-divider" />,
<MenuItem key="signout" onClick={handleSignOut}> <MenuItem key="signout" onClick={handleSignOut}>
<ListItemIcon> <ListItemIcon>
<Logout /> <Logout />

View File

@ -8,8 +8,8 @@ const MyClassesPage: React.FC = () => {
const { profile } = useUser(); const { profile } = useUser();
const { const {
myClasses, myClasses,
myClassesLoading, classesLoading: myClassesLoading,
myClassesError, classesError: myClassesError,
fetchMyClasses, fetchMyClasses,
} = useTimetableStore(); } = useTimetableStore();

View File

@ -36,7 +36,7 @@ function NotFound() {
gap: 3 gap: 3
}} }}
> >
<ErrorOutline sx={{ fontSize: 60, color: theme.palette.error.main }} /> <ErrorOutlineIcon sx={{ fontSize: 60, color: theme.palette.error.main }} />
<Typography variant="h2" component="h1" gutterBottom> <Typography variant="h2" component="h1" gutterBottom>
404 404
</Typography> </Typography>

View File

@ -2,6 +2,7 @@ import axios from 'axios';
import { supabase } from '../supabaseClient'; import { supabase } from '../supabaseClient';
import { import {
Class, Class,
ClassWithRole,
ClassWithRelations, ClassWithRelations,
ClassFilters, ClassFilters,
Timetable, Timetable,
@ -79,10 +80,30 @@ export const classService = {
await axios.delete(`${API_BASE}/database/timetable/classes/${classId}`, { headers }); await axios.delete(`${API_BASE}/database/timetable/classes/${classId}`, { headers });
}, },
async getMyClasses(): Promise<Class[]> { async getMyClasses(): Promise<ClassWithRole[]> {
const headers = await getAuthHeaders(); const headers = await getAuthHeaders();
const response = await axios.get(`${API_BASE}/database/timetable/classes/me/student`, { headers }); const [teacherRes, studentRes] = await Promise.allSettled([
return response.data.classes; axios.get(`${API_BASE}/database/timetable/classes/me/teacher`, { headers }),
axios.get(`${API_BASE}/database/timetable/classes/me/student`, { headers }),
]);
const teacherClasses: ClassWithRole[] = teacherRes.status === 'fulfilled'
? (teacherRes.value.data.classes || []).map((c: Class) => ({
...c,
class_id: c.id,
role: 'teacher' as const,
status: 'active',
}))
: [];
const studentClasses: ClassWithRole[] = studentRes.status === 'fulfilled'
? (studentRes.value.data.classes || []).map((c: Class) => ({
...c,
class_id: c.id,
role: 'student' as const,
status: 'active',
}))
: [];
const seen = new Set(teacherClasses.map(c => c.id));
return [...teacherClasses, ...studentClasses.filter(c => !seen.has(c.id))];
}, },
async getMyTeachingClasses(): Promise<Class[]> { async getMyTeachingClasses(): Promise<Class[]> {

View File

@ -2,6 +2,7 @@ import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware'; import { devtools, persist } from 'zustand/middleware';
import { import {
Class, Class,
ClassWithRole,
ClassWithRelations, ClassWithRelations,
Timetable, Timetable,
TimetableWithRelations, TimetableWithRelations,
@ -21,7 +22,7 @@ interface TimetableState {
// Classes // Classes
classes: Class[]; classes: Class[];
currentClass: ClassWithRelations | null; currentClass: ClassWithRelations | null;
myClasses: Class[]; myClasses: ClassWithRole[];
myTeachingClasses: Class[]; myTeachingClasses: Class[];
classesLoading: boolean; classesLoading: boolean;
classesError: string | null; classesError: string | null;

View File

@ -27,6 +27,15 @@ export interface Class {
created_at: string; created_at: string;
updated_at: string; updated_at: string;
is_active: boolean; is_active: boolean;
code?: string;
institute_id?: string;
}
export interface ClassWithRole extends Class {
role: 'teacher' | 'student' | 'assistant';
status: string;
class_id?: string;
is_primary_teacher?: boolean;
} }
export interface ClassTeacher { export interface ClassTeacher {