Compare commits

..
Author SHA1 Message Date
kcar 34ea1e3f08 G1 surface homework in lesson planner UI 2026-07-24 19:20:43 +01:00
10 changed files with 90 additions and 353 deletions
+2 -9
View File
@@ -10,7 +10,6 @@ import {
} from '@mui/icons-material'; } from '@mui/icons-material';
import { useAuth } from '../../contexts/AuthContext'; import { useAuth } from '../../contexts/AuthContext';
import { ResultsWidget } from '../exam'; import { ResultsWidget } from '../exam';
import MarkbookPanel from './MarkbookPanel';
const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api';
@@ -271,7 +270,6 @@ const ClassDetailPage: React.FC = () => {
{/* Tabs */} {/* Tabs */}
<Tabs value={tab} onChange={(_, v) => setTab(v)} sx={{ borderBottom: 1, borderColor: 'divider', mb: 2 }}> <Tabs value={tab} onChange={(_, v) => setTab(v)} sx={{ borderBottom: 1, borderColor: 'divider', mb: 2 }}>
<Tab label={`Students (${cls.student_count})`} /> <Tab label={`Students (${cls.student_count})`} />
<Tab label="Markbook" />
<Tab label={`Requests${pendingCount > 0 ? ` (${pendingCount})` : ''}`} /> <Tab label={`Requests${pendingCount > 0 ? ` (${pendingCount})` : ''}`} />
<Tab label={`Teachers (${cls.teachers.length})`} /> <Tab label={`Teachers (${cls.teachers.length})`} />
</Tabs> </Tabs>
@@ -334,13 +332,8 @@ const ClassDetailPage: React.FC = () => {
</Box> </Box>
)} )}
{/* Markbook tab */}
{tab === 1 && (
<MarkbookPanel classId={cls.id} accessToken={accessToken || ''} />
)}
{/* Enrollment requests tab */} {/* Enrollment requests tab */}
{tab === 2 && ( {tab === 1 && (
<Box> <Box>
{cls.enrollment_requests.length === 0 ? ( {cls.enrollment_requests.length === 0 ? (
<Typography color="text.secondary" sx={{ py: 4, textAlign: 'center' }}> <Typography color="text.secondary" sx={{ py: 4, textAlign: 'center' }}>
@@ -399,7 +392,7 @@ const ClassDetailPage: React.FC = () => {
)} )}
{/* Teachers tab */} {/* Teachers tab */}
{tab === 3 && ( {tab === 2 && (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
{cls.teachers.length === 0 ? ( {cls.teachers.length === 0 ? (
<Typography color="text.secondary" sx={{ py: 4, textAlign: 'center' }}> <Typography color="text.secondary" sx={{ py: 4, textAlign: 'center' }}>
+22 -2
View File
@@ -39,6 +39,7 @@ interface LessonPlan {
year_group: string | null; year_group: string | null;
duration_minutes: number | null; duration_minutes: number | null;
context_notes: string | null; context_notes: string | null;
homework: string | null;
objectives: Objective[]; objectives: Objective[];
activities: Activity[]; activities: Activity[];
is_public: boolean; is_public: boolean;
@@ -88,6 +89,7 @@ const LessonPlanDetailPage: React.FC = () => {
const [yearGroup, setYearGroup] = useState(''); const [yearGroup, setYearGroup] = useState('');
const [duration, setDuration] = useState(''); const [duration, setDuration] = useState('');
const [contextNotes, setContextNotes] = useState(''); const [contextNotes, setContextNotes] = useState('');
const [homework, setHomework] = useState('');
const [isPublic, setIsPublic] = useState(false); const [isPublic, setIsPublic] = useState(false);
const [objectives, setObjectives] = useState<Objective[]>([]); const [objectives, setObjectives] = useState<Objective[]>([]);
const [activities, setActivities] = useState<Activity[]>([]); const [activities, setActivities] = useState<Activity[]>([]);
@@ -110,6 +112,7 @@ const LessonPlanDetailPage: React.FC = () => {
setYearGroup(data.year_group || ''); setYearGroup(data.year_group || '');
setDuration(data.duration_minutes?.toString() || ''); setDuration(data.duration_minutes?.toString() || '');
setContextNotes(data.context_notes || ''); setContextNotes(data.context_notes || '');
setHomework(data.homework || '');
setIsPublic(data.is_public || false); setIsPublic(data.is_public || false);
setObjectives(data.objectives || []); setObjectives(data.objectives || []);
setActivities(data.activities || []); setActivities(data.activities || []);
@@ -128,7 +131,7 @@ const LessonPlanDetailPage: React.FC = () => {
// ── Save ────────────────────────────────────────────────────────────────── // ── Save ──────────────────────────────────────────────────────────────────
const save = useCallback(async ( const save = useCallback(async (
overrides?: Partial<{ title: string; subject: string; year_group: string; duration_minutes: number | null; context_notes: string; is_public: boolean; objectives: Objective[]; activities: Activity[] }> overrides?: Partial<{ title: string; subject: string; year_group: string; duration_minutes: number | null; context_notes: string; homework: string; is_public: boolean; objectives: Objective[]; activities: Activity[] }>
) => { ) => {
if (!accessToken || !planId) return; if (!accessToken || !planId) return;
setSaving(true); setSaving(true);
@@ -143,6 +146,7 @@ const LessonPlanDetailPage: React.FC = () => {
year_group: yearGroup || null, year_group: yearGroup || null,
duration_minutes: duration ? parseInt(duration, 10) : null, duration_minutes: duration ? parseInt(duration, 10) : null,
context_notes: contextNotes || null, context_notes: contextNotes || null,
homework: homework || '',
is_public: isPublic, is_public: isPublic,
objectives, objectives,
activities, activities,
@@ -156,7 +160,7 @@ const LessonPlanDetailPage: React.FC = () => {
} finally { } finally {
setSaving(false); setSaving(false);
} }
}, [accessToken, planId, title, subject, yearGroup, duration, contextNotes, isPublic, objectives, activities]); }, [accessToken, planId, title, subject, yearGroup, duration, contextNotes, homework, isPublic, objectives, activities]);
const scheduleAutoSave = useCallback(() => { const scheduleAutoSave = useCallback(() => {
if (autoSaveTimer.current) clearTimeout(autoSaveTimer.current); if (autoSaveTimer.current) clearTimeout(autoSaveTimer.current);
@@ -359,6 +363,22 @@ const LessonPlanDetailPage: React.FC = () => {
/> />
</Box> </Box>
{/* Homework */}
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" fontWeight={600} sx={{ mb: 0.75 }}>
Homework
</Typography>
<TextField
value={homework}
onChange={e => { setHomework(e.target.value); scheduleAutoSave(); }}
multiline
minRows={2}
fullWidth
size="small"
placeholder="Homework to carry with this plan when assigned to a taught lesson…"
/>
</Box>
<Divider sx={{ mb: 3 }} /> <Divider sx={{ mb: 3 }} />
{/* Objectives */} {/* Objectives */}
+2
View File
@@ -21,6 +21,7 @@ interface PlanSummary {
duration_minutes: number | null; duration_minutes: number | null;
objectives: { id: string; text: string; bloom_level?: string }[]; objectives: { id: string; text: string; bloom_level?: string }[];
activities: { id: string; section: string; title: string }[]; activities: { id: string; section: string; title: string }[];
homework: string | null;
is_public: boolean; is_public: boolean;
created_at: string; created_at: string;
updated_at: string; updated_at: string;
@@ -144,6 +145,7 @@ function PlanCard({ plan, onOpen, onDelete }: {
</Box> </Box>
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" color="text.secondary">
{objCount} objective{objCount !== 1 ? 's' : ''} · {actCount} activit{actCount !== 1 ? 'ies' : 'y'} {objCount} objective{objCount !== 1 ? 's' : ''} · {actCount} activit{actCount !== 1 ? 'ies' : 'y'}
{plan.homework ? ' · homework set' : ''}
</Typography> </Typography>
</CardContent> </CardContent>
<CardActions sx={{ pt: 0, px: 1.5, pb: 1.5, justifyContent: 'flex-end' }} onClick={e => e.stopPropagation()}> <CardActions sx={{ pt: 0, px: 1.5, pb: 1.5, justifyContent: 'flex-end' }} onClick={e => e.stopPropagation()}>
-319
View File
@@ -1,319 +0,0 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import {
Alert,
Box,
Button,
CircularProgress,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Paper,
Stack,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TextField,
Typography,
} from '@mui/material';
import AddIcon from '@mui/icons-material/Add';
import DownloadIcon from '@mui/icons-material/Download';
import { API_BASE } from '../../config/apiConfig';
interface Assessment {
id: string;
title: string;
date?: string | null;
max_marks: number;
}
interface MarkbookStudent {
student_id: string;
student_name: string;
row_number: number;
marks: Record<string, number | null>;
total: number | null;
percentage: number | null;
}
interface AssessmentSummary {
assessment_id: string;
entered_count: number;
average_mark: number | null;
average_percentage: number | null;
}
interface MarkbookGrid {
assessments: Assessment[];
students: MarkbookStudent[];
assessment_summaries: AssessmentSummary[];
summary: {
student_count: number;
assessment_count: number;
entered_mark_count: number;
class_average_mark: number | null;
};
}
interface MarkbookPanelProps {
classId: string;
accessToken: string;
}
const MARKBOOK_BASE = `${API_BASE}/api/markbook`;
function formatNumber(value: number | null | undefined) {
return value === null || value === undefined ? '—' : Number(value).toFixed(Number.isInteger(value) ? 0 : 1);
}
function markInputValue(value: number | null | undefined) {
return value === null || value === undefined ? '' : String(value);
}
function AddAssessmentDialog({
open,
saving,
onClose,
onCreate,
}: {
open: boolean;
saving: boolean;
onClose: () => void;
onCreate: (payload: { title: string; date?: string; max_marks: number }) => Promise<void>;
}) {
const [title, setTitle] = useState('');
const [date, setDate] = useState('');
const [maxMarks, setMaxMarks] = useState('100');
useEffect(() => {
if (open) {
setTitle('');
setDate('');
setMaxMarks('100');
}
}, [open]);
const handleCreate = async () => {
const parsedMax = Number(maxMarks);
if (!title.trim() || !Number.isFinite(parsedMax) || parsedMax <= 0) return;
await onCreate({ title: title.trim(), date: date || undefined, max_marks: parsedMax });
};
return (
<Dialog open={open} onClose={onClose} maxWidth="xs" fullWidth>
<DialogTitle>Add assessment column</DialogTitle>
<DialogContent sx={{ pt: 2 }}>
<Stack spacing={2} sx={{ mt: 1 }}>
<TextField label="Title" size="small" value={title} onChange={(e) => setTitle(e.target.value)} autoFocus />
<TextField label="Date" type="date" size="small" value={date} onChange={(e) => setDate(e.target.value)} InputLabelProps={{ shrink: true }} />
<TextField label="Max marks" type="number" size="small" value={maxMarks} onChange={(e) => setMaxMarks(e.target.value)} inputProps={{ min: 0, step: 0.5 }} />
</Stack>
</DialogContent>
<DialogActions sx={{ px: 3, pb: 2 }}>
<Button onClick={onClose} disabled={saving}>Cancel</Button>
<Button onClick={handleCreate} variant="contained" disabled={saving || !title.trim()} startIcon={saving ? <CircularProgress size={16} /> : <AddIcon />}>
Add
</Button>
</DialogActions>
</Dialog>
);
}
const MarkbookPanel: React.FC<MarkbookPanelProps> = ({ classId, accessToken }) => {
const [grid, setGrid] = useState<MarkbookGrid | null>(null);
const [loading, setLoading] = useState(true);
const [savingKey, setSavingKey] = useState<string | null>(null);
const [addOpen, setAddOpen] = useState(false);
const [savingAssessment, setSavingAssessment] = useState(false);
const [error, setError] = useState<string | null>(null);
const headers = useMemo(() => ({ Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' }), [accessToken]);
const load = useCallback(async () => {
if (!classId || !accessToken) return;
setLoading(true);
setError(null);
try {
const res = await fetch(`${MARKBOOK_BASE}/classes/${classId}/grid`, { headers });
const body = await res.json();
if (!res.ok) throw new Error(body.detail || 'Failed to load markbook');
setGrid(body);
} catch (e) {
setError(e instanceof Error ? e.message : String(e));
} finally {
setLoading(false);
}
}, [accessToken, classId, headers]);
useEffect(() => {
void load();
}, [load]);
const createAssessment = async (payload: { title: string; date?: string; max_marks: number }) => {
setSavingAssessment(true);
setError(null);
try {
const res = await fetch(`${MARKBOOK_BASE}/classes/${classId}/assessments`, {
method: 'POST',
headers,
body: JSON.stringify(payload),
});
const body = await res.json();
if (!res.ok) throw new Error(body.detail || 'Failed to create assessment');
setAddOpen(false);
await load();
} catch (e) {
setError(e instanceof Error ? e.message : String(e));
} finally {
setSavingAssessment(false);
}
};
const saveMark = async (studentId: string, assessment: Assessment, raw: string) => {
const key = `${studentId}:${assessment.id}`;
const trimmed = raw.trim();
const mark = trimmed === '' ? null : Number(trimmed);
if (mark !== null && (!Number.isFinite(mark) || mark < 0 || mark > Number(assessment.max_marks))) {
setError(`Mark must be between 0 and ${assessment.max_marks}`);
return;
}
setSavingKey(key);
setError(null);
try {
const res = await fetch(`${MARKBOOK_BASE}/classes/${classId}/assessments/${assessment.id}/marks/${studentId}`, {
method: 'PUT',
headers,
body: JSON.stringify({ mark }),
});
const body = await res.json();
if (!res.ok) throw new Error(body.detail || 'Failed to save mark');
await load();
} catch (e) {
setError(e instanceof Error ? e.message : String(e));
} finally {
setSavingKey(null);
}
};
const downloadCsv = async () => {
setError(null);
try {
const res = await fetch(`${MARKBOOK_BASE}/classes/${classId}/csv`, { headers });
const text = await res.text();
if (!res.ok) throw new Error(text || 'Failed to export CSV');
const blob = new Blob([text], { type: 'text/csv;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `markbook-${classId}.csv`;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
} catch (e) {
setError(e instanceof Error ? e.message : String(e));
}
};
if (loading) {
return <Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}><CircularProgress /></Box>;
}
if (!grid) {
return <Alert severity="error">{error || 'Could not load markbook'}</Alert>;
}
const summaryByAssessment = new Map(grid.assessment_summaries.map((s) => [s.assessment_id, s]));
return (
<Box>
{error && <Alert severity="error" onClose={() => setError(null)} sx={{ mb: 2 }}>{error}</Alert>}
<Stack direction="row" spacing={1} sx={{ mb: 2 }} justifyContent="space-between" alignItems="center" flexWrap="wrap" useFlexGap>
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
<Typography variant="body2" color="text.secondary">{grid.summary.student_count} roster students</Typography>
<Typography variant="body2" color="text.secondary">{grid.summary.assessment_count} assessments</Typography>
<Typography variant="body2" color="text.secondary">Class average mark {formatNumber(grid.summary.class_average_mark)}</Typography>
</Stack>
<Stack direction="row" spacing={1}>
<Button size="small" variant="outlined" startIcon={<DownloadIcon />} onClick={downloadCsv} disabled={grid.assessments.length === 0}>CSV</Button>
<Button size="small" variant="contained" startIcon={<AddIcon />} onClick={() => setAddOpen(true)}>Add assessment</Button>
</Stack>
</Stack>
{grid.assessments.length === 0 ? (
<Paper variant="outlined" sx={{ py: 4, px: 2, textAlign: 'center' }}>
<Typography color="text.secondary" sx={{ mb: 2 }}>No assessment columns yet.</Typography>
<Button variant="contained" startIcon={<AddIcon />} onClick={() => setAddOpen(true)}>Add first assessment</Button>
</Paper>
) : (
<TableContainer component={Paper} variant="outlined" sx={{ maxHeight: 620 }}>
<Table size="small" stickyHeader>
<TableHead>
<TableRow>
<TableCell sx={{ minWidth: 44 }}>#</TableCell>
<TableCell sx={{ minWidth: 180 }}>Student</TableCell>
{grid.assessments.map((assessment) => (
<TableCell key={assessment.id} align="right" sx={{ minWidth: 130 }}>
<Typography variant="body2" fontWeight={700}>{assessment.title}</Typography>
<Typography variant="caption" color="text.secondary">
{assessment.date ? `${new Date(assessment.date).toLocaleDateString('en-GB')} · ` : ''}/{assessment.max_marks}
</Typography>
</TableCell>
))}
<TableCell align="right">Total</TableCell>
<TableCell align="right">%</TableCell>
</TableRow>
</TableHead>
<TableBody>
{grid.students.map((student) => (
<TableRow key={student.student_id}>
<TableCell>{student.row_number}</TableCell>
<TableCell>
<Typography variant="body2" fontWeight={600}>{student.student_name || student.student_id}</Typography>
<Typography variant="caption" color="text.secondary">{student.student_id}</Typography>
</TableCell>
{grid.assessments.map((assessment) => {
const key = `${student.student_id}:${assessment.id}`;
return (
<TableCell key={assessment.id} align="right">
<TextField
size="small"
type="number"
defaultValue={markInputValue(student.marks[assessment.id])}
onBlur={(e) => void saveMark(student.student_id, assessment, e.target.value)}
disabled={savingKey === key}
inputProps={{ min: 0, max: assessment.max_marks, step: 0.5, style: { textAlign: 'right' } }}
sx={{ width: 86 }}
/>
</TableCell>
);
})}
<TableCell align="right"><strong>{formatNumber(student.total)}</strong></TableCell>
<TableCell align="right">{formatNumber(student.percentage)}</TableCell>
</TableRow>
))}
<TableRow sx={{ bgcolor: 'action.hover' }}>
<TableCell />
<TableCell><strong>Average</strong></TableCell>
{grid.assessments.map((assessment) => {
const summary = summaryByAssessment.get(assessment.id);
return <TableCell key={assessment.id} align="right"><strong>{formatNumber(summary?.average_mark)}</strong></TableCell>;
})}
<TableCell />
<TableCell />
</TableRow>
</TableBody>
</Table>
</TableContainer>
)}
<AddAssessmentDialog open={addOpen} saving={savingAssessment} onClose={() => setAddOpen(false)} onCreate={createAssessment} />
</Box>
);
};
export default MarkbookPanel;
@@ -22,6 +22,7 @@ interface Lesson {
week_cycle: string; week_cycle: string;
day_of_week: string; day_of_week: string;
status: string; status: string;
homework: string | null;
teacher_name: string | null; teacher_name: string | null;
} }
@@ -95,6 +96,11 @@ function LessonCard({ lesson }: { lesson: Lesson }) {
sx={{ height: 16, fontSize: '0.65rem' }} sx={{ height: 16, fontSize: '0.65rem' }}
/> />
</Box> </Box>
{lesson.homework && (
<Typography variant="caption" sx={{ color: 'primary.main', fontSize: '0.68rem', fontWeight: 600 }}>
Homework: {lesson.homework.length > 80 ? lesson.homework.slice(0, 80) + '…' : lesson.homework}
</Typography>
)}
{lesson.teacher_name && ( {lesson.teacher_name && (
<Typography variant="caption" sx={{ color: 'text.secondary', fontSize: '0.68rem' }}> <Typography variant="caption" sx={{ color: 'text.secondary', fontSize: '0.68rem' }}>
{lesson.teacher_name} {lesson.teacher_name}
+21 -3
View File
@@ -30,6 +30,7 @@ interface Lesson {
day_of_week: string; day_of_week: string;
status: string; status: string;
lesson_plan: Record<string, any>; lesson_plan: Record<string, any>;
homework: string | null;
notes: string | null; notes: string | null;
whiteboard_room_id: string | null; whiteboard_room_id: string | null;
} }
@@ -76,16 +77,18 @@ const STATUS_COLORS: Record<string, 'default' | 'primary' | 'success' | 'error'
interface EditDialogProps { interface EditDialogProps {
lesson: Lesson | null; lesson: Lesson | null;
onClose: () => void; onClose: () => void;
onSave: (id: string, updates: { notes?: string; status?: string }) => Promise<void>; onSave: (id: string, updates: { homework?: string; notes?: string; status?: string }) => Promise<void>;
} }
function LessonEditDialog({ lesson, onClose, onSave }: EditDialogProps) { function LessonEditDialog({ lesson, onClose, onSave }: EditDialogProps) {
const [homework, setHomework] = useState('');
const [notes, setNotes] = useState(''); const [notes, setNotes] = useState('');
const [status, setStatus] = useState('planned'); const [status, setStatus] = useState('planned');
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
useEffect(() => { useEffect(() => {
if (lesson) { if (lesson) {
setHomework(lesson.homework || '');
setNotes(lesson.notes || ''); setNotes(lesson.notes || '');
setStatus(lesson.status || 'planned'); setStatus(lesson.status || 'planned');
} }
@@ -94,7 +97,7 @@ function LessonEditDialog({ lesson, onClose, onSave }: EditDialogProps) {
const handleSave = async () => { const handleSave = async () => {
if (!lesson) return; if (!lesson) return;
setSaving(true); setSaving(true);
await onSave(lesson.id, { notes, status }); await onSave(lesson.id, { homework, notes, status });
setSaving(false); setSaving(false);
onClose(); onClose();
}; };
@@ -127,6 +130,16 @@ function LessonEditDialog({ lesson, onClose, onSave }: EditDialogProps) {
<MenuItem value="substituted">Substituted</MenuItem> <MenuItem value="substituted">Substituted</MenuItem>
</Select> </Select>
</FormControl> </FormControl>
<TextField
label="Homework"
value={homework}
onChange={e => setHomework(e.target.value)}
multiline
minRows={2}
fullWidth
size="small"
placeholder="Homework for this lesson…"
/>
<TextField <TextField
label="Notes" label="Notes"
value={notes} value={notes}
@@ -209,6 +222,11 @@ function LessonCard({ lesson, onEdit, onAssignPlan }: LessonCardProps) {
sx={{ height: 16, fontSize: '0.65rem' }} sx={{ height: 16, fontSize: '0.65rem' }}
/> />
</Box> </Box>
{lesson.homework && (
<Typography variant="caption" sx={{ color: 'primary.main', fontSize: '0.68rem', fontWeight: 600, mt: 0.25 }}>
Homework: {lesson.homework.length > 80 ? lesson.homework.slice(0, 80) + '…' : lesson.homework}
</Typography>
)}
{lesson.notes && ( {lesson.notes && (
<Typography variant="caption" sx={{ color: 'text.secondary', fontSize: '0.68rem', fontStyle: 'italic', mt: 0.25 }}> <Typography variant="caption" sx={{ color: 'text.secondary', fontSize: '0.68rem', fontStyle: 'italic', mt: 0.25 }}>
{lesson.notes.length > 80 ? lesson.notes.slice(0, 80) + '…' : lesson.notes} {lesson.notes.length > 80 ? lesson.notes.slice(0, 80) + '…' : lesson.notes}
@@ -287,7 +305,7 @@ const TaughtLessonsPage: React.FC = () => {
} }
}; };
const handleSaveLesson = async (id: string, updates: { notes?: string; status?: string }) => { const handleSaveLesson = async (id: string, updates: { homework?: string; notes?: string; status?: string }) => {
if (!accessToken) return; if (!accessToken) return;
await fetch(`${API_BASE}/timetable/lessons/${id}`, { await fetch(`${API_BASE}/timetable/lessons/${id}`, {
method: 'PATCH', method: 'PATCH',
@@ -29,106 +29,112 @@ export class CCPlannedLessonNodeShapeUtil extends CCBaseShapeUtil<CCPlannedLesso
renderContent = (shape: CCPlannedLessonNodeShape) => { renderContent = (shape: CCPlannedLessonNodeShape) => {
const styles = getNodeStyles(shape.type) const styles = getNodeStyles(shape.type)
return ( return (
<div style={styles.container}> <div style={styles.container}>
<NodeProperty <NodeProperty
label="Subject Class" label="Subject Class"
value={shape.props.subject_class} value={shape.props.subject_class}
labelStyle={styles.property.label} labelStyle={styles.property.label}
valueStyle={styles.property.value} valueStyle={styles.property.value}
/> />
<NodeProperty <NodeProperty
label="Date" label="Date"
value={shape.props.date} value={shape.props.date}
labelStyle={styles.property.label} labelStyle={styles.property.label}
valueStyle={styles.property.value} valueStyle={styles.property.value}
/> />
<NodeProperty <NodeProperty
label="Start Time" label="Start Time"
value={shape.props.start_time} value={shape.props.start_time}
labelStyle={styles.property.label} labelStyle={styles.property.label}
valueStyle={styles.property.value} valueStyle={styles.property.value}
/> />
<NodeProperty <NodeProperty
label="End Time" label="End Time"
value={shape.props.end_time} value={shape.props.end_time}
labelStyle={styles.property.label} labelStyle={styles.property.label}
valueStyle={styles.property.value} valueStyle={styles.property.value}
/> />
<NodeProperty <NodeProperty
label="Period Code" label="Period Code"
value={shape.props.period_code} value={shape.props.period_code}
labelStyle={styles.property.label} labelStyle={styles.property.label}
valueStyle={styles.property.value} valueStyle={styles.property.value}
/> />
<NodeProperty <NodeProperty
label="Year Group" label="Year Group"
value={shape.props.year_group} value={shape.props.year_group}
labelStyle={styles.property.label} labelStyle={styles.property.label}
valueStyle={styles.property.value} valueStyle={styles.property.value}
/> />
<NodeProperty <NodeProperty
label="Subject" label="Subject"
value={shape.props.subject} value={shape.props.subject}
labelStyle={styles.property.label} labelStyle={styles.property.label}
valueStyle={styles.property.value} valueStyle={styles.property.value}
/> />
<NodeProperty <NodeProperty
label="Teacher Code" label="Teacher Code"
value={shape.props.teacher_code} value={shape.props.teacher_code}
labelStyle={styles.property.label} labelStyle={styles.property.label}
valueStyle={styles.property.value} valueStyle={styles.property.value}
/> />
<NodeProperty <NodeProperty
label="Planning Status" label="Planning Status"
value={shape.props.planning_status} value={shape.props.planning_status}
labelStyle={styles.property.label} labelStyle={styles.property.label}
valueStyle={styles.property.value} valueStyle={styles.property.value}
/> />
<NodeProperty <NodeProperty
label="Homework"
value={shape.props.homework}
labelStyle={styles.property.label}
valueStyle={styles.property.value}
/>
<NodeProperty
label="Topic Code" label="Topic Code"
value={shape.props.topic_code} value={shape.props.topic_code}
labelStyle={styles.property.label} labelStyle={styles.property.label}
valueStyle={styles.property.value} valueStyle={styles.property.value}
/> />
<NodeProperty <NodeProperty
label="Topic Name" label="Topic Name"
value={shape.props.topic_name} value={shape.props.topic_name}
labelStyle={styles.property.label} labelStyle={styles.property.label}
valueStyle={styles.property.value} valueStyle={styles.property.value}
/> />
<NodeProperty <NodeProperty
label="Lesson Code" label="Lesson Code"
value={shape.props.lesson_code} value={shape.props.lesson_code}
labelStyle={styles.property.label} labelStyle={styles.property.label}
valueStyle={styles.property.value} valueStyle={styles.property.value}
/> />
<NodeProperty <NodeProperty
label="Lesson Name" label="Lesson Name"
value={shape.props.lesson_name} value={shape.props.lesson_name}
labelStyle={styles.property.label} labelStyle={styles.property.label}
valueStyle={styles.property.value} valueStyle={styles.property.value}
/> />
<NodeProperty <NodeProperty
label="Learning Statement Codes" label="Learning Statement Codes"
value={shape.props.learning_statement_codes} value={shape.props.learning_statement_codes}
labelStyle={styles.property.label} labelStyle={styles.property.label}
valueStyle={styles.property.value} valueStyle={styles.property.value}
/> />
<NodeProperty <NodeProperty
label="Learning Statements" label="Learning Statements"
value={shape.props.learning_statements} value={shape.props.learning_statements}
labelStyle={styles.property.label} labelStyle={styles.property.label}
valueStyle={styles.property.value} valueStyle={styles.property.value}
/> />
<NodeProperty <NodeProperty
label="Learning Resource Codes" label="Learning Resource Codes"
value={shape.props.learning_resource_codes} value={shape.props.learning_resource_codes}
labelStyle={styles.property.label} labelStyle={styles.property.label}
valueStyle={styles.property.value} valueStyle={styles.property.value}
/> />
<NodeProperty <NodeProperty
label="Learning Resources" label="Learning Resources"
value={shape.props.learning_resources} value={shape.props.learning_resources}
labelStyle={styles.property.label} labelStyle={styles.property.label}
@@ -137,4 +143,4 @@ export class CCPlannedLessonNodeShapeUtil extends CCBaseShapeUtil<CCPlannedLesso
</div> </div>
) )
} }
} }
@@ -5,7 +5,8 @@ import { CCGraphShape, GraphShapeType } from './cc-graph-types'
// Helper function to create version IDs for a shape type // Helper function to create version IDs for a shape type
const createVersions = (shapeType: GraphShapeType) => { const createVersions = (shapeType: GraphShapeType) => {
return createShapePropsMigrationIds(shapeType, { return createShapePropsMigrationIds(shapeType, {
Initial: 1 // All shapes start at version 1 as required by TLDraw Initial: 1, // All shapes start at version 1 as required by TLDraw
AddPlannedLessonHomework: 2,
}) })
} }
@@ -21,6 +22,13 @@ const createMigrationSequence = (shapeType: GraphShapeType) => {
return props return props
}, },
}, },
...(shapeType === 'cc-planned-lesson-node' ? [{
id: versions.AddPlannedLessonHomework,
up: (props: CCGraphShape['props']) => ({
homework: '',
...props,
}),
}] : []),
], ],
}) })
} }
@@ -199,6 +199,7 @@ export const ccGraphShapeProps = {
subject: T.string, subject: T.string,
teacher_code: T.string, teacher_code: T.string,
planning_status: T.string, planning_status: T.string,
homework: T.string,
topic_code: T.string, topic_code: T.string,
topic_name: T.string, topic_name: T.string,
lesson_code: T.string, lesson_code: T.string,
@@ -560,6 +561,7 @@ export const getDefaultCCPlannedLessonNodeProps = () => ({
subject: '', subject: '',
teacher_code: '', teacher_code: '',
planning_status: '', planning_status: '',
homework: '',
topic_code: '', topic_code: '',
topic_name: '', topic_name: '',
lesson_code: '', lesson_code: '',
@@ -204,6 +204,7 @@ export type CCPlannedLessonNodeProps = CCGraphShapeProps & {
subject: string subject: string
teacher_code: string teacher_code: string
planning_status: string planning_status: string
homework: string
topic_code: string topic_code: string
topic_name: string topic_name: string
lesson_code: string lesson_code: string