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:
parent
98a4212e46
commit
63d6d1bbca
@ -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 />
|
||||||
|
|||||||
@ -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();
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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[]> {
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user