Compare commits

..

No commits in common. "510bef02b6d702b35b0d873156824016e9980329" and "61ef95a35e6396bc56b7b0b8260af0f30876a748" have entirely different histories.

2 changed files with 12 additions and 163 deletions

View File

@ -1,14 +1,12 @@
import React, { useEffect, useState, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Box, Typography, Button, CircularProgress, Alert, Chip,
Dialog, DialogTitle, DialogContent, DialogActions,
TextField, Select, MenuItem, FormControl, InputLabel,
Divider, IconButton, Tooltip, List, ListItemButton, ListItemText,
InputAdornment,
Divider, IconButton, Tooltip,
} from '@mui/material';
import {
ChevronLeft, ChevronRight, Today, EditNote, LibraryAdd, Search, OpenInNew,
ChevronLeft, ChevronRight, Today, EditNote,
} from '@mui/icons-material';
import { useAuth } from '../../contexts/AuthContext';
@ -154,10 +152,9 @@ function LessonEditDialog({ lesson, onClose, onSave }: EditDialogProps) {
interface LessonCardProps {
lesson: Lesson;
onEdit: (l: Lesson) => void;
onAssignPlan: (l: Lesson) => void;
}
function LessonCard({ lesson, onEdit, onAssignPlan }: LessonCardProps) {
function LessonCard({ lesson, onEdit }: LessonCardProps) {
return (
<Box
sx={{
@ -177,18 +174,11 @@ function LessonCard({ lesson, onEdit, onAssignPlan }: LessonCardProps) {
<Typography variant="caption" sx={{ fontWeight: 700, lineHeight: 1.2 }}>
{lesson.class_name || lesson.period_code}
</Typography>
<Box sx={{ display: 'flex', gap: 0 }}>
<Tooltip title="Assign lesson plan">
<IconButton size="small" onClick={() => onAssignPlan(lesson)} sx={{ p: 0.25 }}>
<LibraryAdd fontSize="inherit" />
</IconButton>
</Tooltip>
<Tooltip title="Edit lesson">
<IconButton size="small" onClick={() => onEdit(lesson)} sx={{ p: 0.25 }}>
<EditNote fontSize="inherit" />
</IconButton>
</Tooltip>
</Box>
<Tooltip title="Edit lesson">
<IconButton size="small" onClick={() => onEdit(lesson)} sx={{ ml: 0.5, p: 0.25 }}>
<EditNote fontSize="inherit" />
</IconButton>
</Tooltip>
</Box>
{lesson.subject && (
<Typography variant="caption" sx={{ color: 'text.secondary', fontSize: '0.7rem' }}>
@ -222,7 +212,6 @@ function LessonCard({ lesson, onEdit, onAssignPlan }: LessonCardProps) {
const TaughtLessonsPage: React.FC = () => {
const { accessToken } = useAuth();
const navigate = useNavigate();
const [weekStart, setWeekStart] = useState<string>(toMonday(new Date()));
const [days, setDays] = useState<DayEntry[]>([]);
const [loading, setLoading] = useState(false);
@ -230,11 +219,6 @@ const TaughtLessonsPage: React.FC = () => {
const [materializing, setMaterializing] = useState(false);
const [materializeResult, setMaterializeResult] = useState<string | null>(null);
const [editingLesson, setEditingLesson] = useState<Lesson | null>(null);
const [assigningLesson, setAssigningLesson] = useState<Lesson | null>(null);
const [plans, setPlans] = useState<{ id: string; title: string; subject: string | null }[]>([]);
const [planSearch, setPlanSearch] = useState('');
const [assignSaving, setAssignSaving] = useState(false);
const [assignResult, setAssignResult] = useState<string | null>(null);
const loadLessons = useCallback(async (ws: string) => {
if (!accessToken) return;
@ -297,51 +281,10 @@ const TaughtLessonsPage: React.FC = () => {
loadLessons(weekStart);
};
const openAssignDialog = async (lesson: Lesson) => {
setAssigningLesson(lesson);
setPlanSearch('');
setAssignResult(null);
if (plans.length === 0) {
try {
const res = await fetch(`${API_BASE}/lessons/plans`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
const data = await res.json();
if (Array.isArray(data)) setPlans(data);
} catch { /* silently ignore */ }
}
};
const handleAssignPlan = async (planId: string) => {
if (!assigningLesson) return;
setAssignSaving(true);
try {
const res = await fetch(`${API_BASE}/lessons/plans/${planId}/deliver`, {
method: 'POST',
headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ taught_lesson_id: assigningLesson.id }),
});
const data = await res.json();
if (data.id) {
setAssignResult('Plan linked ✓');
setTimeout(() => { setAssigningLesson(null); setAssignResult(null); }, 1200);
} else {
setAssignResult(data.detail || 'Failed to link plan');
}
} catch (e: any) {
setAssignResult(e.message);
} finally {
setAssignSaving(false);
}
};
const filteredPlans = plans.filter(p =>
!planSearch || p.title.toLowerCase().includes(planSearch.toLowerCase())
|| (p.subject || '').toLowerCase().includes(planSearch.toLowerCase())
);
const totalLessons = days.reduce((sum, d) => sum + d.lessons.length, 0);
// Format week label
const weekDates = days.filter(d => d.lessons.length > 0 || true).map(d => d.date);
const weekLabel = days.length > 0
? `${formatDate(days[0].date)} ${formatDate(days[days.length - 1].date)}`
: '';
@ -426,7 +369,6 @@ const TaughtLessonsPage: React.FC = () => {
key={lesson.id}
lesson={lesson}
onEdit={setEditingLesson}
onAssignPlan={openAssignDialog}
/>
))
)}
@ -440,71 +382,6 @@ const TaughtLessonsPage: React.FC = () => {
onClose={() => setEditingLesson(null)}
onSave={handleSaveLesson}
/>
{/* Assign Plan dialog */}
<Dialog open={!!assigningLesson} onClose={() => setAssigningLesson(null)} maxWidth="sm" fullWidth>
<DialogTitle sx={{ pb: 1 }}>
<Typography variant="h6">Assign Lesson Plan</Typography>
{assigningLesson && (
<Typography variant="caption" color="text.secondary">
{assigningLesson.class_name || assigningLesson.period_code} · {assigningLesson.date}
</Typography>
)}
</DialogTitle>
<DialogContent sx={{ pt: 1 }}>
{assignResult ? (
<Alert severity={assignResult.includes('✓') ? 'success' : 'error'} sx={{ mb: 1 }}>
{assignResult}
</Alert>
) : null}
<TextField
size="small"
fullWidth
placeholder="Search plans…"
value={planSearch}
onChange={e => setPlanSearch(e.target.value)}
InputProps={{
startAdornment: <InputAdornment position="start"><Search fontSize="small" /></InputAdornment>,
}}
sx={{ mb: 1 }}
/>
{plans.length === 0 ? (
<Typography variant="body2" color="text.secondary" sx={{ py: 2, textAlign: 'center' }}>
No lesson plans yet.{' '}
<Button size="small" endIcon={<OpenInNew fontSize="small" />}
onClick={() => { setAssigningLesson(null); navigate('/lesson-plans'); }}>
Create one
</Button>
</Typography>
) : (
<List dense disablePadding sx={{ maxHeight: 300, overflow: 'auto' }}>
{filteredPlans.map(p => (
<ListItemButton
key={p.id}
onClick={() => handleAssignPlan(p.id)}
disabled={assignSaving}
sx={{ borderRadius: 1, mb: 0.25 }}
>
<ListItemText
primary={p.title}
secondary={p.subject || undefined}
primaryTypographyProps={{ variant: 'body2' }}
secondaryTypographyProps={{ variant: 'caption' }}
/>
{assignSaving && <CircularProgress size={16} sx={{ ml: 1 }} />}
</ListItemButton>
))}
</List>
)}
</DialogContent>
<DialogActions sx={{ px: 3, pb: 2 }}>
<Button onClick={() => setAssigningLesson(null)}>Close</Button>
<Button size="small" endIcon={<OpenInNew fontSize="small" />}
onClick={() => { setAssigningLesson(null); navigate('/lesson-plans'); }}>
Manage Plans
</Button>
</DialogActions>
</Dialog>
</Box>
);
};

View File

@ -19,9 +19,7 @@ import {
GridOn as GridIcon,
Settings as SetupIcon,
Edit as EditIcon,
Launch as LaunchIcon,
} from '@mui/icons-material';
import { useNavigate } from 'react-router-dom';
import { useNavigationStore } from '../../../../../../stores/navigationStore';
import { useAuth } from '../../../../../../contexts/AuthContext';
import { NeoGraphNode } from '../../../../../../types/navigation';
@ -126,7 +124,6 @@ interface TreeItemProps {
function TreeItem({ node, depth, onSelect, onExpand }: TreeItemProps) {
const ctx = useContext(NavPanelContext);
const navigate = useNavigate();
const [expanded, setExpanded] = useState(node.is_section && node.status === 'populated');
const [children, setChildren] = useState<TreeNode[]>(node.children || []);
const [loading, setLoading] = useState(false);
@ -135,7 +132,6 @@ function TreeItem({ node, depth, onSelect, onExpand }: TreeItemProps) {
const isCalendarSection = isSection && node.section_id === 'calendar';
const isTimetableSection = isSection && node.section_id === 'timetable';
const isSchoolSection = isSection && node.section_id === 'school';
const isClassesSection = isSection && node.section_id === 'classes';
const SectionIcon = node.section_id ? SECTION_ICONS[node.section_id] : null;
const Icon = SectionIcon || NODE_ICONS[node.node_type] || HomeIcon;
@ -200,8 +196,6 @@ function TreeItem({ node, depth, onSelect, onExpand }: TreeItemProps) {
const showLegacySetup = isTimetableSection && node.status === 'empty' && !ss;
const showTimetableEdit = isTimetableSection && node.status === 'populated'
&& ss && ss.status !== 'no_school' && !!ss.teacher_has_timetable;
const showTimetableView = isTimetableSection && node.status === 'populated';
const showClassesView = isClassesSection && node.status === 'populated';
if (isSection) {
return (
@ -229,7 +223,7 @@ function TreeItem({ node, depth, onSelect, onExpand }: TreeItemProps) {
</IconButton>
)
)}
{isEmpty && !isCalendarSection && !isTimetableSection && !isSchoolSection && !isClassesSection && (
{isEmpty && !isCalendarSection && !isTimetableSection && !isSchoolSection && (
<Box sx={{ width: 18, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
{node.status === 'no_school'
? <UnlinkedIcon sx={{ fontSize: 11, color: 'text.disabled' }} />
@ -255,7 +249,7 @@ function TreeItem({ node, depth, onSelect, onExpand }: TreeItemProps) {
{node.label}
</Typography>
{isEmpty && !isCalendarSection && !isTimetableSection && !isSchoolSection && !isClassesSection && node.status && (
{isEmpty && !isCalendarSection && !isTimetableSection && !isSchoolSection && node.status && (
<Tooltip title={STATUS_MESSAGES[node.status]} placement="right">
<Typography variant="caption" sx={{ fontSize: '0.6rem', color: 'text.disabled', ml: 0.5, flexShrink: 0 }}>
{node.status === 'no_school' ? '—' : '…'}
@ -325,28 +319,6 @@ function TreeItem({ node, depth, onSelect, onExpand }: TreeItemProps) {
</IconButton>
</Tooltip>
)}
{showTimetableView && (
<Tooltip title="View as lesson list" placement="right">
<IconButton
size="small"
sx={{ p: 0.25, ml: 0.5, color: 'text.secondary' }}
onClick={e => { e.stopPropagation(); navigate('/my-lessons'); }}
>
<LaunchIcon sx={{ fontSize: 13 }} />
</IconButton>
</Tooltip>
)}
{showClassesView && (
<Tooltip title="View my classes" placement="right">
<IconButton
size="small"
sx={{ p: 0.25, ml: 0.5, color: 'text.secondary' }}
onClick={e => { e.stopPropagation(); navigate('/my-classes'); }}
>
<LaunchIcon sx={{ fontSize: 13 }} />
</IconButton>
</Tooltip>
)}
</Box>
{/* Calendar mode toggle */}