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>,
<Divider key="home-divider" />,
// Timetable Section
// My Work Section
<Typography
key="timetable-header"
key="work-header"
variant="subtitle2"
sx={{
px: 2,
@ -233,26 +233,8 @@ const Header: React.FC = () => {
fontSize: '0.75rem'
}}
>
Timetable & Classes
My Work
</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')}>
<ListItemIcon>
<Book />
@ -262,15 +244,6 @@ const Header: React.FC = () => {
secondary="Manage your classes"
/>
</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')}>
<ListItemIcon>
<Lessons />
@ -289,16 +262,7 @@ const Header: React.FC = () => {
secondary="Plan and manage lessons"
/>
</MenuItem>,
<MenuItem key="student-lessons" onClick={() => handleNavigation('/student-lessons')}>
<ListItemIcon>
<Student />
</ListItemIcon>
<ListItemText
primary="Student Lessons"
secondary="Your class timetable"
/>
</MenuItem>,
<Divider key="timetable-divider" />,
<Divider key="work-divider" />,
// School Admin Section
...(isSchoolAdmin ? [
@ -331,71 +295,6 @@ const Header: React.FC = () => {
<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
...(isPlatformAdmin ? [
<Divider key="admin-divider" />,
@ -420,8 +319,6 @@ const Header: React.FC = () => {
</MenuItem>
] : []),
// Authentication Section
<Divider key="auth-divider" />,
<MenuItem key="signout" onClick={handleSignOut}>
<ListItemIcon>
<Logout />

View File

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

View File

@ -36,7 +36,7 @@ function NotFound() {
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>
404
</Typography>

View File

@ -2,6 +2,7 @@ import axios from 'axios';
import { supabase } from '../supabaseClient';
import {
Class,
ClassWithRole,
ClassWithRelations,
ClassFilters,
Timetable,
@ -79,10 +80,30 @@ export const classService = {
await axios.delete(`${API_BASE}/database/timetable/classes/${classId}`, { headers });
},
async getMyClasses(): Promise<Class[]> {
async getMyClasses(): Promise<ClassWithRole[]> {
const headers = await getAuthHeaders();
const response = await axios.get(`${API_BASE}/database/timetable/classes/me/student`, { headers });
return response.data.classes;
const [teacherRes, studentRes] = await Promise.allSettled([
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[]> {

View File

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

View File

@ -27,6 +27,15 @@ export interface Class {
created_at: string;
updated_at: string;
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 {