import React, { useEffect, useState, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { Box, Typography, Button, CircularProgress, Alert, Chip, Card, CardContent, CardActions, IconButton, Tooltip, TextField, InputAdornment, MenuItem, Select, FormControl, InputLabel, Dialog, DialogTitle, DialogContent, DialogActions, } from '@mui/material'; import { Add, Search, LibraryBooks, Edit, Delete, FilterList } from '@mui/icons-material'; import { useAuth } from '../../contexts/AuthContext'; const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; // ─── Types ──────────────────────────────────────────────────────────────────── interface PlanSummary { id: string; title: string; subject: string | null; year_group: string | null; duration_minutes: number | null; objectives: { id: string; text: string; bloom_level?: string }[]; activities: { id: string; section: string; title: string }[]; homework: string | null; is_public: boolean; created_at: string; updated_at: string; creator_name?: string; } // ─── Create plan dialog ─────────────────────────────────────────────────────── interface CreateDialogProps { open: boolean; onClose: () => void; onCreate: (data: { title: string; subject?: string; year_group?: string }) => Promise; } function CreatePlanDialog({ open, onClose, onCreate }: CreateDialogProps) { const [title, setTitle] = useState(''); const [subject, setSubject] = useState(''); const [yearGroup, setYearGroup] = useState(''); const [saving, setSaving] = useState(false); const reset = () => { setTitle(''); setSubject(''); setYearGroup(''); }; const handleCreate = async () => { if (!title.trim()) return; setSaving(true); await onCreate({ title: title.trim(), subject: subject || undefined, year_group: yearGroup || undefined }); setSaving(false); reset(); onClose(); }; const handleClose = () => { reset(); onClose(); }; return ( New Lesson Plan setTitle(e.target.value)} size="small" fullWidth autoFocus required placeholder="e.g. Introduction to Photosynthesis" /> setSubject(e.target.value)} size="small" fullWidth placeholder="e.g. Biology" /> setYearGroup(e.target.value)} size="small" sx={{ width: 140 }} placeholder="e.g. 9" /> ); } // ─── Plan card ──────────────────────────────────────────────────────────────── function PlanCard({ plan, onOpen, onDelete }: { plan: PlanSummary; onOpen: (id: string) => void; onDelete: (id: string) => void; }) { const objCount = plan.objectives?.length ?? 0; const actCount = plan.activities?.length ?? 0; return ( onOpen(plan.id)} > {plan.title} {plan.subject && ( )} {plan.year_group && ( )} {plan.duration_minutes && ( )} {plan.is_public && ( )} {objCount} objective{objCount !== 1 ? 's' : ''} · {actCount} activit{actCount !== 1 ? 'ies' : 'y'} {plan.homework ? ' · homework set' : ''} e.stopPropagation()}> onOpen(plan.id)}> onDelete(plan.id)}> ); } // ─── Main page ──────────────────────────────────────────────────────────────── const LessonPlansPage: React.FC = () => { const { accessToken } = useAuth(); const navigate = useNavigate(); const [plans, setPlans] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [createOpen, setCreateOpen] = useState(false); const [search, setSearch] = useState(''); const [filterSubject, setFilterSubject] = useState(''); const load = useCallback(async () => { if (!accessToken) return; setLoading(true); setError(null); try { const res = await fetch(`${API_BASE}/lessons/plans`, { headers: { Authorization: `Bearer ${accessToken}` }, }); const data = await res.json(); if (Array.isArray(data)) setPlans(data); else setError(data.detail || 'Failed to load plans'); } catch (e: any) { setError(e.message); } finally { setLoading(false); } }, [accessToken]); useEffect(() => { load(); }, [load]); const handleCreate = async (body: { title: string; subject?: string; year_group?: string }) => { const res = await fetch(`${API_BASE}/lessons/plans`, { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); const data = await res.json(); if (data.id) { navigate(`/lesson-plans/${data.id}`); } else { load(); } }; const handleDelete = async (id: string) => { if (!window.confirm('Delete this lesson plan?')) return; await fetch(`${API_BASE}/lessons/plans/${id}`, { method: 'DELETE', headers: { Authorization: `Bearer ${accessToken}` }, }); setPlans(prev => prev.filter(p => p.id !== id)); }; const subjects = Array.from(new Set(plans.map(p => p.subject).filter(Boolean) as string[])).sort(); const filtered = plans.filter(p => { const matchSearch = !search || p.title.toLowerCase().includes(search.toLowerCase()) || (p.subject || '').toLowerCase().includes(search.toLowerCase()); const matchSubject = !filterSubject || p.subject === filterSubject; return matchSearch && matchSubject; }); return ( {/* Header */} Lesson Plans {plans.length} plan{plans.length !== 1 ? 's' : ''} {/* Filters */} setSearch(e.target.value)} InputProps={{ startAdornment: , }} sx={{ width: 280 }} /> {subjects.length > 0 && ( Subject )} {(search || filterSubject) && ( )} {error && {error}} {loading ? ( ) : filtered.length === 0 ? ( {plans.length === 0 ? 'No lesson plans yet.' : 'No plans match your filters.'} {plans.length === 0 && ( )} ) : ( {filtered.map(plan => ( navigate(`/lesson-plans/${id}`)} onDelete={handleDelete} /> ))} )} setCreateOpen(false)} onCreate={handleCreate} /> ); }; export default LessonPlansPage;