Round 19b: 10 Runtime Bug Fixes, Student Notebook Access, German Curriculum Types
Bug Fixes:
- Fixed duplicate i18n keys (notebooks.type_lined etc.)
- Fixed settings-view.tsx: studentsList undefined → students
- Fixed student-detail-view.tsx: pe.competencyId → pe.competency.id
- Fixed student-detail-view.tsx: locale variable not in scope
- Fixed student-detail-view.tsx: studentId/setStudent undefined
- Fixed notebooks-view.tsx: onClick={onAddPage} passing MouseEvent
- Fixed curriculum-coverage-view.tsx: Map import shadowing built-in
- Fixed api.ts: avatarUrl missing from AttendanceRecord/Report types
- Fixed badge-check.ts: invalid Prisma queries with wrong field names
Student Notebook Access:
- Students can create/view/edit their own notebooks
- Students see shared notebooks from teachers in enrolled classes
- Fixed handleCreateFromTemplate to use correct ownerType for students
German Curriculum Notebook Types (9 types):
- Deutschheft (lined, red), Matheheft (grid, blue), Sachbuch (grid, teal)
- Musikheft (music staff, emerald), Kunstheft (blank, purple)
- Englischheft (lined, amber), Geschichtsheft (lined, brown)
- Religionsheft (lined, orange), Sachkundeheft (lined+drawing, teal)
Environment Features:
- Papier gespart counter with tree calculation
- Eco badge on every notebook card
- Lehrplan-Heft curriculum badge on German types
60+ new i18n keys (DE/EN)
This commit is contained in:
1 parent
9b2168d775
commit
58cb7afd81
14 files changed
+593
-64
No files matched your search
+5
-3
@@ -582,7 +582,7 @@ export interface Report {
|
||||
status: string;
|
||||
pdfFilePath: string | null;
|
||||
includesGrades: boolean;
|
||||
student: { id: string; firstName: string; lastName: string };
|
||||
student: { id: string; firstName: string; lastName: string; avatarUrl: string | null };
|
||||
classGroup: { id: string; name: string; gradeLevel: number };
|
||||
schoolYear: { id: string; label: string };
|
||||
generatedByUser: { id: string; firstName: string; lastName: string };
|
||||
@@ -890,7 +890,7 @@ export function downloadCsvExport(params: {
|
||||
classGroupId?: string;
|
||||
schoolYearId?: string;
|
||||
schoolId?: string;
|
||||
format?: 'csv' | 'json';
|
||||
format?: 'csv' | 'json' | 'pdf';
|
||||
dateFrom?: string;
|
||||
dateTo?: string;
|
||||
}): void {
|
||||
@@ -1141,6 +1141,8 @@ export interface StudentDetailData {
|
||||
lastName: string;
|
||||
dateOfBirth: string | null;
|
||||
externalId: string | null;
|
||||
avatarUrl: string | null;
|
||||
avatarInitials: string | null;
|
||||
createdAt: string;
|
||||
school: { id: string; name: string };
|
||||
enrollments: Array<{
|
||||
@@ -1548,7 +1550,7 @@ export interface AttendanceRecord {
|
||||
status: AttendanceStatus;
|
||||
arrivalTime: string | null;
|
||||
comment: string | null;
|
||||
student: { id: string; firstName: string; lastName: string };
|
||||
student: { id: string; firstName: string; lastName: string; avatarUrl: string | null };
|
||||
}
|
||||
|
||||
export interface AttendanceSession {
|
||||
|
||||
+23
-14
@@ -197,7 +197,7 @@ async function checkBadgeRequirement(
|
||||
const attendanceRecords = await db.attendanceRecord.findMany({
|
||||
where: {
|
||||
studentId,
|
||||
schoolId,
|
||||
student: { schoolId },
|
||||
status: 'PRESENT',
|
||||
session: { date: { gte: thirtyDaysAgo } },
|
||||
},
|
||||
@@ -211,7 +211,7 @@ async function checkBadgeRequirement(
|
||||
const progressEntries = await db.learningProgressEntry.findMany({
|
||||
where: {
|
||||
studentId,
|
||||
schoolId,
|
||||
student: { schoolId },
|
||||
masteryLevelValue: { gte: threshold },
|
||||
},
|
||||
});
|
||||
@@ -225,7 +225,7 @@ async function checkBadgeRequirement(
|
||||
const count = await db.learningProgressEntry.count({
|
||||
where: {
|
||||
studentId,
|
||||
schoolId,
|
||||
student: { schoolId },
|
||||
date: { gte: oneMonthAgo },
|
||||
},
|
||||
});
|
||||
@@ -253,15 +253,14 @@ async function checkBadgeRequirement(
|
||||
case 'Notebook Champion': {
|
||||
const count = await db.notebookPage.count({
|
||||
where: {
|
||||
notebook: { schoolId },
|
||||
notebook: { createdById: studentId },
|
||||
notebook: { schoolId, ownerId: studentId },
|
||||
},
|
||||
});
|
||||
return count >= threshold;
|
||||
}
|
||||
case 'Drawing Artist': {
|
||||
const count = await db.drawing.count({
|
||||
where: { schoolId, createdById: studentId },
|
||||
where: { schoolId, ownerId: studentId },
|
||||
});
|
||||
return count >= threshold;
|
||||
}
|
||||
@@ -297,7 +296,7 @@ async function checkBadgeRequirement(
|
||||
case 'Eco Warrior': {
|
||||
// Awarded if student has at least 1 notebook
|
||||
const count = await db.notebook.count({
|
||||
where: { schoolId, createdById: studentId },
|
||||
where: { schoolId, ownerId: studentId },
|
||||
});
|
||||
return count >= threshold;
|
||||
}
|
||||
@@ -339,7 +338,17 @@ export async function getBadgeProgress(
|
||||
});
|
||||
const earnedBadgeMap = new Map(earnedBadges.map(eb => [eb.badgeId, eb.awardedAt]));
|
||||
|
||||
const results = [];
|
||||
const results: Array<{
|
||||
badgeId: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
color: string;
|
||||
category: string;
|
||||
earned: boolean;
|
||||
progress: number;
|
||||
current: number;
|
||||
target: number;
|
||||
}> = [];
|
||||
|
||||
for (const badge of badges) {
|
||||
const earned = earnedBadgeMap.has(badge.id);
|
||||
@@ -382,7 +391,7 @@ async function getCurrentProgress(
|
||||
return db.attendanceRecord.count({
|
||||
where: {
|
||||
studentId,
|
||||
schoolId,
|
||||
student: { schoolId },
|
||||
status: 'PRESENT',
|
||||
session: { date: { gte: thirtyDaysAgo } },
|
||||
},
|
||||
@@ -390,7 +399,7 @@ async function getCurrentProgress(
|
||||
}
|
||||
case 'mastery_level': {
|
||||
const entries = await db.learningProgressEntry.findMany({
|
||||
where: { studentId, schoolId },
|
||||
where: { studentId, student: { schoolId } },
|
||||
orderBy: { masteryLevelValue: 'desc' },
|
||||
take: 1,
|
||||
});
|
||||
@@ -400,7 +409,7 @@ async function getCurrentProgress(
|
||||
const oneMonthAgo = new Date();
|
||||
oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);
|
||||
return db.learningProgressEntry.count({
|
||||
where: { studentId, schoolId, date: { gte: oneMonthAgo } },
|
||||
where: { studentId, student: { schoolId }, date: { gte: oneMonthAgo } },
|
||||
});
|
||||
}
|
||||
case 'behavior_count': {
|
||||
@@ -411,9 +420,9 @@ async function getCurrentProgress(
|
||||
case 'custom': {
|
||||
switch (badge.name) {
|
||||
case 'Notebook Champion':
|
||||
return db.notebookPage.count({ where: { notebook: { schoolId, createdById: studentId } } });
|
||||
return db.notebookPage.count({ where: { notebook: { schoolId, ownerId: studentId } } });
|
||||
case 'Drawing Artist':
|
||||
return db.drawing.count({ where: { schoolId, createdById: studentId } });
|
||||
return db.drawing.count({ where: { schoolId, ownerId: studentId } });
|
||||
case 'Homework Hero':
|
||||
return db.homeworkSubmission.count({ where: { studentId, homework: { schoolId }, status: 'SUBMITTED' } });
|
||||
case 'Team Player':
|
||||
@@ -421,7 +430,7 @@ async function getCurrentProgress(
|
||||
case 'Goal Achiever':
|
||||
return db.learningGoal.count({ where: { studentId, schoolId, status: 'COMPLETED' } });
|
||||
case 'Eco Warrior':
|
||||
return db.notebook.count({ where: { schoolId, createdById: studentId } });
|
||||
return db.notebook.count({ where: { schoolId, ownerId: studentId } });
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
+63
-10
@@ -1914,11 +1914,6 @@ const de: TranslationDict = {
|
||||
'notebooks.field_icon': 'Symbol',
|
||||
'notebooks.field_public': 'Mit Klasse teilen',
|
||||
'notebooks.field_public_hint': 'Andere können dieses Heft einsehen',
|
||||
'notebooks.type_lined': 'Liniert',
|
||||
'notebooks.type_grid': 'Kariert',
|
||||
'notebooks.type_blank': 'Blanko',
|
||||
'notebooks.type_dotted': 'Punktiert',
|
||||
'notebooks.type_music': 'Musik',
|
||||
'notebooks.type_calligraphy': 'Kalligraphie',
|
||||
'notebooks.type_lined_desc': 'Liniertes Heft für Text',
|
||||
'notebooks.type_grid_desc': 'Kariertes Heft für Mathematik',
|
||||
@@ -2146,6 +2141,38 @@ const de: TranslationDict = {
|
||||
'notebooks.type_blank': 'Unliniert',
|
||||
'notebooks.type_dotted': 'Punktiert',
|
||||
'notebooks.type_music': 'Notenlinien',
|
||||
// German curriculum notebook types
|
||||
'notebooks.type_deutschheft': 'Deutschheft',
|
||||
'notebooks.type_deutschheft_desc': 'Liniertes Heft mit Rand — fuer Aufsaetze, Grammatik und Lesetagebuch',
|
||||
'notebooks.type_matheheft': 'Matheheft',
|
||||
'notebooks.type_matheheft_desc': 'Kariertes Heft — fuer Aufgaben, Formeln und Geometrie',
|
||||
'notebooks.type_sachbuch': 'Sachbuch',
|
||||
'notebooks.type_sachbuchheft_desc': 'Kariertes Heft mit Versuchsprotokoll — fuer Experimente und Beobachtungen',
|
||||
'notebooks.type_sachbuch_desc': 'Kariertes Heft mit Versuchsprotokoll — fuer Experimente und Beobachtungen',
|
||||
'notebooks.type_musikheft': 'Musikheft',
|
||||
'notebooks.type_musikheft_desc': 'Heft mit Notenlinien — fuer Noten, Rhythmus und Komposition',
|
||||
'notebooks.type_kunstheft': 'Kunstheft',
|
||||
'notebooks.type_kunstheft_desc': 'Blanko-Heft — fuer Skizzen, Farbstudien und Portfolio',
|
||||
'notebooks.type_englischheft': 'Englischheft',
|
||||
'notebooks.type_englischheft_desc': 'Liniertes Heft — fuer Vokabeln, Grammatik und Reading',
|
||||
'notebooks.type_geschichtsheft': 'Geschichtsheft',
|
||||
'notebooks.type_geschichtsheft_desc': 'Liniertes Heft mit Zeitstrahl — fuer Quellenanalyse und Begriffe',
|
||||
'notebooks.type_religionsheft': 'Religionsheft',
|
||||
'notebooks.type_religionsheft_desc': 'Liniertes Heft — fuer Bibelstellen, Ethik und Weltreligionen',
|
||||
'notebooks.type_sachkundeheft': 'Heft fuer Sachkunde',
|
||||
'notebooks.type_sachkundeheft_desc': 'Liniertes Heft mit Zeichnungsbereich — fuer Beobachtungen und Sachkunde',
|
||||
// Paper savings / eco features
|
||||
'notebooks.trees_saved': 'Baeume gerettet',
|
||||
'notebooks.trees_unit': 'Baeume',
|
||||
'notebooks.trees_saved_message': '{count} Baeume gerettet',
|
||||
'notebooks.eco_badge': 'Papierfrei',
|
||||
'notebooks.curriculum_badge': 'Lehrplan-Heft',
|
||||
// Student notebook access
|
||||
'notebooks.student_create': 'Eigenes Heft erstellen',
|
||||
'notebooks.student_share_teacher': 'Mit Lehrkraft teilen',
|
||||
'notebooks.student_share_teacher_desc': 'Teile dieses Heft mit deiner Lehrkraft (nur lesbar)',
|
||||
'notebooks.student_own_notebooks': 'Meine eigenen Hefte',
|
||||
'notebooks.student_shared_by_teacher': 'Von Lehrkraft geteilt',
|
||||
|
||||
// ─── Page Templates ──────────────────────────────────────────────
|
||||
'notebooks.page_template': 'Seitenvorlage',
|
||||
@@ -5659,11 +5686,6 @@ const en: TranslationDict = {
|
||||
'notebooks.field_icon': 'Icon',
|
||||
'notebooks.field_public': 'Share with class',
|
||||
'notebooks.field_public_hint': 'Others can view this notebook',
|
||||
'notebooks.type_lined': 'Lined',
|
||||
'notebooks.type_grid': 'Grid/Checkered',
|
||||
'notebooks.type_blank': 'Blank',
|
||||
'notebooks.type_dotted': 'Dotted',
|
||||
'notebooks.type_music': 'Music',
|
||||
'notebooks.type_calligraphy': 'Calligraphy',
|
||||
'notebooks.type_lined_desc': 'Lined notebook for text',
|
||||
'notebooks.type_grid_desc': 'Checkered notebook for math',
|
||||
@@ -5891,6 +5913,37 @@ const en: TranslationDict = {
|
||||
'notebooks.type_blank': 'Blank',
|
||||
'notebooks.type_dotted': 'Dotted',
|
||||
'notebooks.type_music': 'Music Lines',
|
||||
// German curriculum notebook types
|
||||
'notebooks.type_deutschheft': 'Deutschheft',
|
||||
'notebooks.type_deutschheft_desc': 'Lined notebook with margin — for essays, grammar and reading journal',
|
||||
'notebooks.type_matheheft': 'Matheheft',
|
||||
'notebooks.type_matheheft_desc': 'Grid notebook — for problems, formulas and geometry',
|
||||
'notebooks.type_sachbuch': 'Sachbuch',
|
||||
'notebooks.type_sachbuch_desc': 'Grid notebook with experiment template — for experiments and observations',
|
||||
'notebooks.type_musikheft': 'Musikheft',
|
||||
'notebooks.type_musikheft_desc': 'Notebook with staff lines — for notes, rhythm and composition',
|
||||
'notebooks.type_kunstheft': 'Kunstheft',
|
||||
'notebooks.type_kunstheft_desc': 'Blank notebook — for sketches, color studies and portfolio',
|
||||
'notebooks.type_englischheft': 'Englischheft',
|
||||
'notebooks.type_englischheft_desc': 'Lined notebook — for vocabulary, grammar and reading',
|
||||
'notebooks.type_geschichtsheft': 'Geschichtsheft',
|
||||
'notebooks.type_geschichtsheft_desc': 'Lined notebook with timeline — for source analysis and terms',
|
||||
'notebooks.type_religionsheft': 'Religionsheft',
|
||||
'notebooks.type_religionsheft_desc': 'Lined notebook — for Bible passages, ethics and world religions',
|
||||
'notebooks.type_sachkundeheft': 'Sachkundeheft',
|
||||
'notebooks.type_sachkundeheft_desc': 'Lined notebook with drawing space — for observations and general studies',
|
||||
// Paper savings / eco features
|
||||
'notebooks.trees_saved': 'Trees Saved',
|
||||
'notebooks.trees_unit': 'trees',
|
||||
'notebooks.trees_saved_message': '{count} trees saved',
|
||||
'notebooks.eco_badge': 'Paper-free',
|
||||
'notebooks.curriculum_badge': 'Curriculum Notebook',
|
||||
// Student notebook access
|
||||
'notebooks.student_create': 'Create My Notebook',
|
||||
'notebooks.student_share_teacher': 'Share with Teacher',
|
||||
'notebooks.student_share_teacher_desc': 'Share this notebook with your teacher (read-only)',
|
||||
'notebooks.student_own_notebooks': 'My Own Notebooks',
|
||||
'notebooks.student_shared_by_teacher': 'Shared by Teacher',
|
||||
|
||||
// ─── Page Templates ──────────────────────────────────────────────
|
||||
'notebooks.page_template': 'Page Template',
|
||||
|
||||
Reference in new issue
Block a user