Fitur lengkap: - Auth JWT + RBAC (Admin/Teacher) + halaman login - Shell 3 kolom (left nav, topbar, right rail dashboard) - CRUD siswa + follow up kanban + status calon/student/ex - Dashboard: siswa aktif, kehadiran, bar sumber lead, perlu follow up - Pembayaran prorata per pertemuan (cash/transfer/qris) - Kelas, enrollment, generate sesi, kehadiran bulk - Jurnal aktivitas + upload multi-foto - Raport + placement test (tampil di detail siswa) - Pengaturan & seed data contoh Stack: Next.js 16, TypeScript, Tailwind v4, Prisma (SQLite), Recharts, Zod
282 lines
8.6 KiB
Plaintext
282 lines
8.6 KiB
Plaintext
// Kodeva — Prisma schema
|
|
// DEV: SQLite (tanpa Docker/Postgres). Untuk PRODUKSI ganti provider ke "postgresql"
|
|
// dan ubah String enum/list kembali menjadi enum + String[] (lihat AGENTS.md §6).
|
|
//
|
|
// Nilai enum disimpan sebagai String agar kompatibel SQLite. Konstanta TypeScript
|
|
// tersedia di src/lib/constants.ts.
|
|
|
|
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "sqlite"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
model User {
|
|
id String @id @default(cuid())
|
|
name String
|
|
email String @unique
|
|
passwordHash String
|
|
role String @default("TEACHER") // ADMIN | TEACHER
|
|
avatarUrl String?
|
|
phone String?
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
classesTaught Class[] @relation("ClassTeacher")
|
|
sessions Session[]
|
|
journals Journal[]
|
|
reportCards ReportCard[]
|
|
placementTests PlacementTest[] @relation("Examiner")
|
|
followUpsOwned FollowUp[] @relation("FollowUpPIC")
|
|
}
|
|
|
|
model Student {
|
|
id String @id @default(cuid())
|
|
fullName String
|
|
phone String
|
|
email String? @unique
|
|
photoUrl String?
|
|
parentName String?
|
|
parentPhone String?
|
|
birthDate DateTime?
|
|
gender String? // MALE | FEMALE
|
|
address String?
|
|
leadSource String // WHATSAPP | REFERRAL | BANNER | SOCIAL_MEDIA
|
|
status String @default("CALON_STUDENT") // CALON_STUDENT | STUDENT | EX_STUDENT
|
|
notes String?
|
|
joinedAt DateTime?
|
|
archivedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
enrollments Enrollment[]
|
|
attendances Attendance[]
|
|
payments Payment[]
|
|
reportCards ReportCard[]
|
|
placementTests PlacementTest[]
|
|
followUps FollowUp[]
|
|
journalStudents JournalStudent[]
|
|
|
|
@@index([status])
|
|
@@index([leadSource])
|
|
}
|
|
|
|
model Class {
|
|
id String @id @default(cuid())
|
|
name String
|
|
code String @unique
|
|
description String?
|
|
level String // BEGINNER | INTERMEDIATE | ADVANCED
|
|
teacherId String?
|
|
teacher User? @relation("ClassTeacher", fields: [teacherId], references: [id])
|
|
totalMeetings Int @default(10)
|
|
packagePrice Int @default(0)
|
|
pricePerMeeting Int?
|
|
capacity Int @default(12)
|
|
schedule String?
|
|
startDate DateTime?
|
|
endDate DateTime?
|
|
status String @default("ACTIVE") // DRAFT | ACTIVE | COMPLETED | ARCHIVED
|
|
color String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
enrollments Enrollment[]
|
|
sessions Session[]
|
|
payments Payment[]
|
|
journals Journal[]
|
|
reportCards ReportCard[]
|
|
}
|
|
|
|
model Enrollment {
|
|
id String @id @default(cuid())
|
|
studentId String
|
|
student Student @relation(fields: [studentId], references: [id], onDelete: Cascade)
|
|
classId String
|
|
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
|
|
status String @default("ACTIVE") // ACTIVE | COMPLETED | DROPPED
|
|
joinedAt DateTime @default(now())
|
|
leftAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@unique([studentId, classId])
|
|
}
|
|
|
|
model Session {
|
|
id String @id @default(cuid())
|
|
classId String
|
|
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
|
|
meetingNumber Int
|
|
date DateTime
|
|
topic String?
|
|
teacherId String?
|
|
teacher User? @relation(fields: [teacherId], references: [id])
|
|
createdAt DateTime @default(now())
|
|
|
|
attendances Attendance[]
|
|
journals Journal[]
|
|
|
|
@@unique([classId, meetingNumber])
|
|
}
|
|
|
|
model Attendance {
|
|
id String @id @default(cuid())
|
|
sessionId String
|
|
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
|
studentId String
|
|
student Student @relation(fields: [studentId], references: [id], onDelete: Cascade)
|
|
status String // PRESENT | LATE | EXCUSED | SICK | ABSENT
|
|
note String?
|
|
recordedById String?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@unique([sessionId, studentId])
|
|
}
|
|
|
|
model Payment {
|
|
id String @id @default(cuid())
|
|
invoiceNumber String @unique
|
|
studentId String
|
|
student Student @relation(fields: [studentId], references: [id])
|
|
classId String
|
|
class Class @relation(fields: [classId], references: [id])
|
|
meetingsPaid Int
|
|
pricePerMeeting Int
|
|
amount Int
|
|
method String // CASH | TRANSFER | QRIS
|
|
status String @default("PAID") // UNPAID | PARTIAL | PAID | REFUNDED
|
|
paidAt DateTime
|
|
note String?
|
|
proofUrl String?
|
|
createdById String?
|
|
createdAt DateTime @default(now())
|
|
}
|
|
|
|
model Journal {
|
|
id String @id @default(cuid())
|
|
classId String
|
|
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
|
|
sessionId String?
|
|
session Session? @relation(fields: [sessionId], references: [id])
|
|
title String
|
|
description String?
|
|
activityDate DateTime
|
|
tags String @default("") // comma separated
|
|
authorId String
|
|
author User @relation(fields: [authorId], references: [id])
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
photos JournalPhoto[]
|
|
students JournalStudent[]
|
|
}
|
|
|
|
model JournalPhoto {
|
|
id String @id @default(cuid())
|
|
journalId String
|
|
journal Journal @relation(fields: [journalId], references: [id], onDelete: Cascade)
|
|
url String
|
|
caption String?
|
|
sortOrder Int @default(0)
|
|
}
|
|
|
|
model JournalStudent {
|
|
journalId String
|
|
studentId String
|
|
journal Journal @relation(fields: [journalId], references: [id], onDelete: Cascade)
|
|
student Student @relation(fields: [studentId], references: [id], onDelete: Cascade)
|
|
|
|
@@id([journalId, studentId])
|
|
}
|
|
|
|
model ReportCard {
|
|
id String @id @default(cuid())
|
|
studentId String
|
|
student Student @relation(fields: [studentId], references: [id], onDelete: Cascade)
|
|
classId String
|
|
class Class @relation(fields: [classId], references: [id])
|
|
period String
|
|
teacherId String
|
|
teacher User @relation(fields: [teacherId], references: [id])
|
|
attendanceSummary String?
|
|
finalScore Int?
|
|
grade String?
|
|
teacherNotes String?
|
|
publishedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
scores ReportScore[]
|
|
}
|
|
|
|
model ReportScore {
|
|
id String @id @default(cuid())
|
|
reportCardId String
|
|
reportCard ReportCard @relation(fields: [reportCardId], references: [id], onDelete: Cascade)
|
|
aspect String
|
|
score Int
|
|
note String?
|
|
}
|
|
|
|
model PlacementTest {
|
|
id String @id @default(cuid())
|
|
studentId String
|
|
student Student @relation(fields: [studentId], references: [id], onDelete: Cascade)
|
|
testDate DateTime
|
|
levelResult String // BEGINNER | INTERMEDIATE | ADVANCED
|
|
recommendedClassId String?
|
|
totalScore Int
|
|
examinerId String?
|
|
examiner User? @relation("Examiner", fields: [examinerId], references: [id])
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
|
|
scores PlacementScore[]
|
|
}
|
|
|
|
model PlacementScore {
|
|
id String @id @default(cuid())
|
|
placementTestId String
|
|
placementTest PlacementTest @relation(fields: [placementTestId], references: [id], onDelete: Cascade)
|
|
category String
|
|
score Int
|
|
}
|
|
|
|
model FollowUp {
|
|
id String @id @default(cuid())
|
|
studentId String
|
|
student Student @relation(fields: [studentId], references: [id], onDelete: Cascade)
|
|
status String @default("NEW") // NEW | CONTACTED | TRIAL | OFFER_SENT | ENROLLED | LOST
|
|
picId String?
|
|
pic User? @relation("FollowUpPIC", fields: [picId], references: [id])
|
|
nextFollowUpAt DateTime?
|
|
lastContactedAt DateTime?
|
|
channel String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
activities FollowUpActivity[]
|
|
|
|
@@index([status])
|
|
@@index([nextFollowUpAt])
|
|
}
|
|
|
|
model FollowUpActivity {
|
|
id String @id @default(cuid())
|
|
followUpId String
|
|
followUp FollowUp @relation(fields: [followUpId], references: [id], onDelete: Cascade)
|
|
type String // call | wa | email | meeting
|
|
note String
|
|
authorId String?
|
|
createdAt DateTime @default(now())
|
|
}
|
|
|
|
model Setting {
|
|
key String @id
|
|
value String // JSON string (SQLite)
|
|
}
|