Round 25: Substitute Teacher, School Newsletter, Career Guidance, Styling

- Substitute Teacher Management View: pool, absences, assignments, statistics, weekly schedule
- School Newsletter View: editor, templates, distribution, analytics, open/click tracking
- Student Career & Guidance View: career exploration, interest quiz, portfolio, goals, appointments
- 6 new Prisma models: SubstituteTeacher, TeacherAbsence, SubstitutionAssignment, CareerProfile, CareerGoal, CareerAppointment
- Enhanced Newsletter model with content, scheduling, and analytics fields
- Enhanced settings-view with gradient header, glassmorphism cards, danger zone
- Enhanced student-portal-view with animated welcome banner, quick action cards
- 16+ new API route files
- 300+ i18n keys added
- All views verified working in browser
This commit is contained in:
Mustafa Cetin committed 2026-07-30 06:36:34 +00:00
1 parent a159ed7fe6
commit 3d0d518bb9
57 files changed
+8370 -48

No files matched your search

+181
View File
@@ -108,6 +108,11 @@ model School {
dataExportJobs DataExportJob[]
wellnessCheckins WellnessCheckin[]
wellnessScores WellnessScore[]
careerProfiles CareerProfile[]
careerAppointments CareerAppointment[]
substituteTeachers SubstituteTeacher[]
teacherAbsences TeacherAbsence[]
substitutionAssignments SubstitutionAssignment[]
}
model User {
@@ -194,6 +199,9 @@ model User {
bookReservations BookReservation[]
dataImportJobs DataImportJob[]
dataExportJobs DataExportJob[]
careerAppointments CareerAppointment[]
substituteTeacher SubstituteTeacher?
teacherAbsences TeacherAbsence[]
}
model SchoolYear {
@@ -252,6 +260,7 @@ model ClassGroup {
aiTestGenerations AITestGeneration[]
examPlans ExamPlan[]
seatingCharts SeatingChart[]
substitutionAssignments SubstitutionAssignment[]
}
model ClassGroupTeacher {
@@ -314,6 +323,7 @@ model Student {
bookReservations BookReservation[]
wellnessCheckins WellnessCheckin[]
wellnessScores WellnessScore[]
careerProfile CareerProfile?
@@index([userId])
}
@@ -364,6 +374,7 @@ model Subject {
examPlans ExamPlan[]
studyPlans StudyPlan[]
studySessions StudySession[]
substitutionAssignments SubstitutionAssignment[]
}
model CompetencyTemplate {
@@ -1935,13 +1946,24 @@ model Newsletter {
schoolId String
authorId String
title String
subject String? // email subject line
content String // rich text content (HTML)
summary String? // short summary for preview
imageUrl String? // cover image URL
bannerImageUrl String? // banner image URL for newsletter header
category String @default("general") // general, academic, sports, arts, events, community
templateType String @default("monthly") // monthly, event, parent, emergency
targetAudience String? // JSON: { type: "all" | "classes" | "roles", ids: [...] }
status String @default("draft") // draft, scheduled, sent, archived
isPublished Boolean @default(false)
publishedAt DateTime?
scheduledAt DateTime?
sentAt DateTime?
tags String? // JSON array of tags
openCount Int @default(0)
clickCount Int @default(0)
bounceCount Int @default(0)
totalRecipients Int @default(0)
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -1951,6 +1973,7 @@ model Newsletter {
author User @relation(fields: [authorId], references: [id])
@@index([schoolId, isPublished, publishedAt])
@@index([schoolId, status])
@@map("newsletters")
}
@@ -2929,3 +2952,161 @@ model WellnessScore {
@@index([date])
@@map("wellness_scores")
}
// ─── Student Career & Guidance ───────────────────────────────────────
model CareerProfile {
id String @id @default(cuid())
schoolId String
studentId String @unique
interests String? // JSON: ["technology", "health", "business"]
strengths String? // JSON: ["mathematics", "communication"]
careerCluster String? // Technology, Health, Business, Arts, Science, Social
desiredCareer String?
educationPath String? // Hauptschule, Realschule, Gymnasium, Berufsschule
workExperiences String? // JSON: [{ company, role, startDate, endDate }]
volunteerExps String? // JSON: [{ organization, role, startDate, endDate }]
certifications String? // JSON: [{ name, date, issuer }]
documents String? // JSON: [{ name, url, type }]
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
school School @relation(fields: [schoolId], references: [id])
student Student @relation(fields: [studentId], references: [id])
goals CareerGoal[]
appointments CareerAppointment[]
@@index([schoolId])
@@index([studentId])
@@index([careerCluster])
@@map("career_profiles")
}
model CareerGoal {
id String @id @default(cuid())
profileId String
title String
description String?
category String // short_term, long_term, academic, professional
targetDate DateTime?
progress Int @default(0) // 0-100
status String @default("active") // active, completed, abandoned
milestones String? // JSON: [{ title, completed, date }]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
profile CareerProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
@@index([profileId])
@@index([category])
@@index([status])
@@map("career_goals")
}
model CareerAppointment {
id String @id @default(cuid())
schoolId String
profileId String
counselorId String
date DateTime
duration Int @default(30) // minutes
type String @default("guidance") // guidance, career, academic, college
status String @default("scheduled") // scheduled, completed, cancelled
notes String?
actionItems String? // JSON: [{ item, completed }]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
school School @relation(fields: [schoolId], references: [id])
profile CareerProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
counselor User @relation(fields: [counselorId], references: [id])
@@index([schoolId])
@@index([profileId])
@@index([counselorId])
@@index([date])
@@index([status])
@@map("career_appointments")
}
model SubstituteTeacher {
id String @id @default(cuid())
schoolId String
userId String? @unique // optional link to User account
firstName String
lastName String
email String?
phone String?
qualifications String? // JSON: ["Mathematics", "Physics", "German"]
subjects String? // JSON: subject IDs they can teach
gradeLevels String? // JSON: ["1-4", "5-6", "7-10"]
availability String? // JSON: { "monday": ["08:00-12:00"], "tuesday": [...] }
maxDaysPerWeek Int @default(5)
rating Float @default(0) // 0-5 average rating
totalAssignments Int @default(0)
notes String?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
user User? @relation(fields: [userId], references: [id])
assignments SubstitutionAssignment[]
@@index([schoolId])
@@index([userId])
@@map("substitute_teachers")
}
model TeacherAbsence {
id String @id @default(cuid())
schoolId String
teacherId String
type String // SICK, PERSONAL, TRAINING, OTHER
startDate DateTime
endDate DateTime
reason String?
status String @default("reported") // reported, covered, cancelled
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
school School @relation(fields: [schoolId], references: [id])
teacher User @relation(fields: [teacherId], references: [id])
assignments SubstitutionAssignment[]
@@index([schoolId])
@@index([teacherId])
@@index([startDate])
@@map("teacher_absences")
}
model SubstitutionAssignment {
id String @id @default(cuid())
schoolId String
absenceId String
substituteId String
classGroupId String?
subjectId String?
date DateTime
period Int? // lesson period number
room String?
status String @default("pending") // pending, confirmed, completed, cancelled
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
school School @relation(fields: [schoolId], references: [id])
absence TeacherAbsence @relation(fields: [absenceId], references: [id])
substitute SubstituteTeacher @relation(fields: [substituteId], references: [id])
classGroup ClassGroup? @relation(fields: [classGroupId], references: [id])
subject Subject? @relation(fields: [subjectId], references: [id])
@@index([schoolId])
@@index([absenceId])
@@index([substituteId])
@@index([date])
@@map("substitution_assignments")
}