From c9ca98d6d8ba77046cc9d08dd11536c0828e0504 Mon Sep 17 00:00:00 2001 From: Amadea Date: Fri, 18 Sep 2026 19:33:15 +0700 Subject: [PATCH] chore: tambahkan session log pi agent --- .gitignore | 3 - 01a0b413-a5e6-7750-a679-32337bd68d37.jsonl | 347 +++++++++++++++++++++ 2 files changed, 347 insertions(+), 3 deletions(-) create mode 100644 01a0b413-a5e6-7750-a679-32337bd68d37.jsonl diff --git a/.gitignore b/.gitignore index c8538f0..e2a0d0a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,9 +24,6 @@ .DS_Store *.pem -# pi / agent session logs -*.jsonl - # debug npm-debug.log* yarn-debug.log* diff --git a/01a0b413-a5e6-7750-a679-32337bd68d37.jsonl b/01a0b413-a5e6-7750-a679-32337bd68d37.jsonl new file mode 100644 index 0000000..ceb1998 --- /dev/null +++ b/01a0b413-a5e6-7750-a679-32337bd68d37.jsonl @@ -0,0 +1,347 @@ +{"type":"session","version":3,"id":"01a0b413-a5e6-7750-a679-32337bd68d37","timestamp":"2026-09-18T10:33:01.671Z","cwd":"C:\\Users\\EIGEN\\pi-cwd-20260918"} +{"type":"custom","customType":"pi-web:tool-selection","data":{"version":1,"tools":["read","bash","edit","write"]},"id":"0d3b2a08","parentId":null,"timestamp":"2026-09-18T10:33:01.671Z"} +{"type":"model_change","id":"2e79002e","parentId":"0d3b2a08","timestamp":"2026-09-18T10:33:01.715Z","provider":"llm-eigen","modelId":"deepseek-v4-flash"} +{"type":"thinking_level_change","id":"714a6f6e","parentId":"2e79002e","timestamp":"2026-09-18T10:33:01.715Z","thinkingLevel":"medium"} +{"type":"message","id":"ea6a01a8","parentId":"714a6f6e","timestamp":"2026-09-18T10:33:01.755Z","message":{"role":"user","content":[{"type":"text","text":"Buat agents_md dan design_md untuk project dengan tema system management kursus coding. Dimana untuk project tersebut saya ingin membuat sebagai berikut : \n1. Pada aplikasi tersebut saya ingin membuat CRUD lengkap untuk follow up siswa baru untuk kursus coding. Field nya adalah nama, no telepon, email, foto profil, nama orang tua, tau dari mana kursus nya (whatsapp/ referal/banner/ social media), saya ingin ada status siswa : student, ex student, calon student. \n2. Pada side bar sebelah kanan, saya ingin ada dashboard untuk melihat grafik siswa aktif berapa, kehadiran siswa, grafik bar untuk mengetahui seberapa banyak yang tau kursus dari (whatsapp/ referal/banner/ social media)\n3. Saya ingin dapat memasukan payment. Sehingga ketika melakukan pembayarran dapat di prorata berdasarkan pertemuan (contoh jika siswa membayar 2.000.000 untuk 10x pertemuan) . Tidak usah pakai payment gateway. Bisa ada pilihan untuk metode pembayaran (cash/transfer/qris) \n4. Saya ingin teacher bisa memasukan journaling dan raport untuk para siswa dan juga kehadiran siswa. \n5. Saya ingin bisa mengkonfigurasi kelas apa saja yang ada dan ketika melakukan penginputan siswa bisa dipilih untuk kelas nya. \n6. Saya ingin ada journal aktivitas untuk dapat mengupload foto dan aktivitas yang ada di kelas \n7. Saya ingin ketika membuka dashboard, saya bisa melihat berapa yang harus di follow up, berapa ex student \n8. Saya ingin ada fitur placement test yang nanti nya akan muncul di informasi siswa sebagai hasil test penempatan siswa. \nBuat design yang colorful, ed tech,playful dan jangan dibuat childish. \nBuat contoh data nya"}],"timestamp":1789727581753}} +{"type":"message","id":"0977470b","parentId":"ea6a01a8","timestamp":"2026-09-18T10:33:06.095Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"The user wants me to create agents_md and design_md files for a coding course management system project. Let me first look at the current working directory to understand context.\n\nLet me check the environment and existing files.","thinkingSignature":"reasoning_content"},{"type":"toolCall","id":"4d605146-7dc4-4a91-a0fe-65af7ec4be29","name":"bash","arguments":{"command":"ls -la && echo \"---\" && ls -la . | head -50"}},{"type":"toolCall","id":"0c359953-73c1-4454-b5c2-a7c4299fda7a","name":"bash","arguments":{"command":"pwd; echo \"---\"; find . -maxdepth 2 -type f 2>/dev/null | head -50"}}],"api":"openai-completions","provider":"llm-eigen","model":"deepseek-v4-flash","usage":{"input":1972,"output":150,"cacheRead":0,"cacheWrite":0,"reasoning":42,"totalTokens":2122,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"toolUse","timestamp":1789727581761,"responseId":"ec24394c-288b-405e-876a-2394d899dc9b","rawStopReason":"tool_calls"}} +{"type":"message","id":"c36d8636","parentId":"0977470b","timestamp":"2026-09-18T10:33:06.526Z","message":{"role":"toolResult","toolCallId":"4d605146-7dc4-4a91-a0fe-65af7ec4be29","toolName":"bash","content":[{"type":"text","text":"total 24\ndrwxr-xr-x 1 EIGEN 197121 0 Sep 18 12:37 .\ndrwxr-xr-x 1 EIGEN 197121 0 Sep 18 12:37 ..\n---\ntotal 24\ndrwxr-xr-x 1 EIGEN 197121 0 Sep 18 12:37 .\ndrwxr-xr-x 1 EIGEN 197121 0 Sep 18 12:37 ..\n"}],"isError":false,"timestamp":1789727586526}} +{"type":"message","id":"bf75c565","parentId":"c36d8636","timestamp":"2026-09-18T10:33:06.527Z","message":{"role":"toolResult","toolCallId":"0c359953-73c1-4454-b5c2-a7c4299fda7a","toolName":"bash","content":[{"type":"text","text":"/c/Users/EIGEN/pi-cwd-20260918\n---\n"}],"isError":false,"timestamp":1789727586527}} +{"type":"message","id":"709f665d","parentId":"bf75c565","timestamp":"2026-09-18T10:35:21.332Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"The directory is empty. I need to create agents_md (probably AGENTS.md) and design_md (DESIGN.md) for a coding course management system.\n\nLet me think about what should be in each:\n\n1. **AGENTS.md** - This is the file that guides AI coding agents working on the project. It should contain project overview, tech stack, architecture, conventions, commands, data models, etc.\n\n2. **DESIGN.md** - Design document: UI/UX, design system, colors, typography, components, layout, pages, data examples.\n\nThe user asks in Indonesian. I should respond in Indonesian. Let me create comprehensive files.\n\nLet me name them `AGENTS.md` and `DESIGN.md` (standard convention), but the user said \"agents_md dan design_md\" - I could create both as AGENTS.md and DESIGN.md. Let me clarify by creating AGENTS.md and DESIGN.md. Actually maybe they literally want `agents.md` and `design.md`. I'll go with `AGENTS.md` and `DESIGN.md` as the standard, since that's what \"agents_md\" refers to (the markdown file). Hmm, but to be safe I'll create `AGENTS.md` and `DESIGN.md`. That's the convention.\n\nLet me design the system thoroughly.\n\n### Project: CourseFlow (or \"KodeKursus\" / \"CodeCamp Manager\")\nA coding course management system. Let me name it something ed-tech playful: \"Kodemy\" or \"KelasKode\" or \"CodeCanvas\". Let me call it **\"Kodewave\"** or **\"Kodeku\"**. I'll go with **\"KodeSpace\"** — playful ed-tech. Actually let me use **\"KelasKode\"** (Indonesian, fits the audience). Hmm, but design should be ed-tech colorful. Let me name it **\"Kodeva\"** — sounds modern, playful. Or \"Codepoint\". I'll choose **Kodeva** — Course Management for Coding Academy. Let me just pick \"Kodeva\".\n\nActually, let me make it more descriptive and neutral: **\"CodeCampus\"**? Let me go with **Kodeva** and mention it.\n\nLet me now enumerate requirements:\n\n1. Full CRUD for new student follow-up.\n Fields: nama, no telepon, email, foto profil, nama orang tua, \"tau dari mana\" (source: whatsapp/referral/banner/social media)\n Status siswa: student (active), ex student, calon student (prospect)\n\n2. Right sidebar dashboard: \n - chart of active students\n - student attendance\n - bar chart of acquisition source (whatsapp/referral/banner/social media)\n\n3. Payments: prorated per meeting (e.g., 2,000,000 for 10 meetings). No payment gateway. Payment method (cash/transfer/qris).\n\n4. Teachers can input journaling and report cards (raport) for students, plus attendance.\n\n5. Configure classes, and when inputting students, can select class.\n\n6. Journal of class activities: upload photos and activities.\n\n7. Dashboard: how many need follow-up, how many ex-students.\n\n8. Placement test feature that appears in student info as placement test result.\n\nDesign: colorful, ed-tech, playful, not childish.\n\nNow let me structure the data models:\n\n- **Student**: id, name, phone, email, photoUrl, parentName, source (enum), status (enum: student, ex_student, calon_student), branch/class enrollments, joinedAt, notes, placementTest, followUp status.\n- **FollowUp**: id, studentId, status (new/contacted/scheduled/trial/enrolled/lost), nextFollowUpAt, PIC, notes, history.\n- **Class** (Kelas): id, name, code, level, teacherId, schedule, capacity, pricePerMeeting/pricePerPackage, totalMeetings, status.\n- **Enrollment**: studentId, classId, joinedAt, status.\n- **Attendance**: id, studentId, classId, sessionId/date, status (present/absent/late/excused), note.\n- **Session/Meeting** (Pertemuan): id, classId, meetingNumber, date, topic, teacherId.\n- **Payment**: id, studentId, classId, packageMeetings, totalAmount, amountPaid, method (cash/transfer/qris), date, meetingsAttended/prorated, status (paid/partial/unpaid), invoiceNumber.\n- **Prorating**: amount per meeting = totalAmount / totalMeetings. Prorated amount = meetings × rate.\n- **Journal**: id, classId, sessionId, title, description, photos[], teacherId, date, tags.\n- **ReportCard** (Raport): id, studentId, classId, period, scores (logic, creativity, problem solving, etc.), grade, teacherNotes, publishedAt.\n- **PlacementTest**: id, studentId, date, level result, scores per category, recommendation, examiner, status.\n\nTech stack: I should choose something. Since this is a design + agents doc, let me propose a modern stack:\n- Next.js 15 (App Router) + TypeScript\n- Tailwind CSS + shadcn/ui\n- Recharts for charts\n- Prisma + PostgreSQL (or SQLite for dev)\n- NextAuth / simple auth\n- Zod for validation\n- React Hook Form\n- Uploadthing / local upload for photos\n- date-fns\n- Zustand for state maybe\n\nBut I don't know the user's intended stack. I should pick a sensible default and state it clearly, while keeping AGENTS.md able to guide. The user speaks Indonesian, probably wants a web app. I'll propose Next.js + TypeScript + Tailwind + Prisma. I'll note it as \"recommended stack\" and keep it flexible.\n\nActually, AGENTS.md typically describes the actual repo. Since repo is empty, I'll define the intended architecture.\n\nLet me write comprehensive documents.\n\nColor palette - colorful ed-tech, playful but not childish:\n- Primary: Indigo/Violet #6C5CE7 (or #6366F1)\n- Accent secondary: Coral/Orange #FF7A59, Mint/Teal #10B981, Sunny Yellow #FFC93C, Sky Blue #38BDF8, Pink #F472B6\n- Use gradients: violet→blue, coral→amber\n- Neutrals: slate\n- Rounded corners (xl, 2xl, full for pills), soft shadows, glassmorphism touches\n- Typography: Plus Jakarta Sans (headings) + Inter (body), or Nunito Sans? Playful but not childish → Plus Jakarta Sans / Sora / Outfit. Use \"Outfit\" for display and \"Inter\" for body. Or \"Plus Jakarta Sans\" for both.\n\nLet me design the layout:\n- Left sidebar navigation (main nav)\n- Main content area\n- Right sidebar dashboard (as requested: \"side bar sebelah kanan\" for dashboard). So dashboard widgets live in right rail.\n\nPages:\n- Login\n- Dashboard (overview) with right rail widgets\n- Students (list, filter by status, follow-up board/kanban)\n- Student detail (profile, enrollment, attendance, payments, placement test, raports, journals)\n- Follow-up (kanban: New → Contacted → Trial → Enrolled → Lost)\n- Classes (CRUD)\n- Class detail (sessions, roster, journal)\n- Attendance (per session)\n- Payments (list, create prorated payment)\n- Journals (activity feed with photos)\n- Report cards\n- Placement tests\n- Settings (sources, teachers, users)\n\nNow let me include sample/seed data. The user explicitly asked \"Buat contoh data nya\" (create sample data). So I should provide seed data examples—maybe a JSON or SQL seed, plus sample in docs.\n\nI'll include sample data:\n- Classes: \"Scratch Junior\", \"Python Dasar\", \"Web Development HTML/CSS\", \"JavaScript Intermediate\", \"Robotics Arduino\"\n- Teachers\n- Students (mix of statuses)\n- Payments with prorating examples\n- Journals\n- Report cards\n- Placement tests\n\nLet me compute prorating example: Rp 2.000.000 for 10 meetings → Rp 200.000/meeting. If student attends 6 meetings, prorated = Rp 1.200.000. Good.\n\nLet me now write the files. I'll create:\n1. `AGENTS.md`\n2. `DESIGN.md`\n\nMaybe also a `seed.example.json` or include in AGENTS. The user asked only for two docs, but \"buat contoh data nya\" — I can embed sample data in the docs. To be helpful, I'll also create `prisma/seed.ts` example? The user only asked for agents_md and design_md. I'll embed sample data inside the docs, and maybe create a separate `sample-data.json` as bonus. Hmm, keep focus: create the two files and include sample data sections within. But a separate seed file would be genuinely useful. Let me create the two main files and one bonus `seed.example.ts`? The instruction says \"Buat agents_md dan design_md ... Buat contoh data nya\". I think embedding sample data is enough; but a seed file adds value. I'll include a compact seed data JSON within DESIGN.md or AGENTS.md.\n\nLet me decide: \n- AGENTS.md: project overview, stack, setup commands, folder structure, architecture, domain models, business rules (prorating formula, status lifecycle, follow-up), coding conventions, API routes, environment vars, testing, seed data instructions (reference to seed file), do's and don'ts for agents.\n- DESIGN.md: brand, design principles, color tokens, typography, spacing, components, layout, page-by-page specs, charts, states, accessibility, sample data (UI content examples), design tokens.\n\nActually sample data is more of an app concern; I'll put the data model + seed data in AGENTS.md and put visual sample content (copy, chart data) in DESIGN.md. Both can include relevant samples.\n\nLet me write in Indonesian (mixed with English technical terms), since user writes Indonesian. That'd be most useful.\n\nLet me make it thorough but well-organized. Use tables, code blocks.\n\nLet me now think about the business rules carefully:\n\n**Status lifecycle:**\n- `calon_student` (prospect) → follow up → bisa jadi `student` (setelah bayar/enroll) → suatu saat jadi `ex_student`.\n- Follow-up status pipeline separate: `new`, `contacted`, `trial`, `offer`, `enrolled`, `lost`.\n- \"berapa yang harus di follow up\" = jumlah calon student dengan follow-up due (nextFollowUpAt <= today) dan belum enrolled/lost.\n- \"berapa ex student\" = count status ex_student.\n\n**Prorating:**\n- Paket kelas punya `totalMeetings` dan `packagePrice`.\n- `pricePerMeeting = packagePrice / totalMeetings`.\n- Pembayaran bisa untuk sejumlah pertemuan: `meetingsPaid`.\n- `amountDue = pricePerMeeting * meetingsPaid`.\n- Sisa sesi = `meetingsPaid - attendedMeetings`. Jika siswa berhenti di tengah, refund/prorate sisa = `pricePerMeeting * remainingMeetings`.\n- Contoh: bayar 2.000.000 untuk 10 pertemuan → 200.000/pertemuan. Hadir 6 → terpakai 1.200.000, sisa 800.000 (4 pertemuan) bisa diprorata/refund atau rollover.\n- Edge: pembayaran parsial/cicilan.\n\n**Attendance statuses:** hadir, izin, sakit, alpa, terlambat.\n- Attendance rate = hadir / total sesi.\n\n**Placement test:**\n- Kategori: logic, computational thinking, coding basics, problem solving, creativity.\n- Hasil level: Beginner / Intermediate / Advanced, atau level kelas rekomendasi.\n- Muncul di student detail.\n\n**Report card:**\n- Periode, per class, aspek penilaian (0-100), grade A/B/C, catatan guru, attendance summary, published.\n\n**Journal:**\n- Upload foto, judul aktivitas, deskripsi, topik, tags, kelas, sesi, teacher.\n\n**Auth roles:** admin, teacher (tutor), maybe front office. RBAC.\n\nNow API routes. Let me define REST-ish Next.js route handlers.\n\nLet me write it.\n\nFor design tokens, define CSS variables.\n\nLet me produce the files now. I'll be generous but organized.\n\nLet me name the app **Kodeva** — \"Sistem Manajemen Kursus Coding\". Tagline: \"Belajar kode, kelola kelas, tumbuh bersama.\" Hmm. Or \"CodeCraft Academy Manager\". I'll go with **Kodeva**.\n\nLet me write AGENTS.md.\n\nI'll include seed data as a TypeScript-ish or JSON. Let me put a `prisma/seed.ts`-style sample in AGENTS.md. Actually let me create a separate `seed.example.json` file too? The user asked for two files. I'll embed in AGENTS.md a concise seed dataset. That satisfies \"buat contoh data\".\n\nHmm, but embedding large seed in AGENTS.md makes it bloated. Better: create `AGENTS.md`, `DESIGN.md`, and a bonus `seed.example.ts`. I think that's fine and helpful. But the user specifically requested two files. I'll create the two and include sample data in a dedicated section. Let me keep sample data moderate (not huge).\n\nActually, I'll create a third file `seed.example.ts` as a bonus because it's genuinely useful and directly answers \"buat contoh data nya\". I'll mention it. Let me do that.\n\nLet me plan file contents.\n\n---\n\n# AGENTS.md\n\nSections:\n1. Ringkasan Proyek\n2. Tech Stack\n3. Setup & Commands\n4. Struktur Folder\n5. Arsitektur & Konvensi\n6. Domain Model & Skema Database\n7. Business Rules\n8. API Endpoints\n9. RBAC\n10. Environment Variables\n11. Seed / Contoh Data\n12. Testing & Quality\n13. Do / Don't untuk Agent\n14. Roadmap\n\nLet me write it.\n\nFor tech stack, choose:\n- Next.js 15 (App Router, RSC), TypeScript\n- Tailwind CSS v4 + shadcn/ui + Radix\n- Recharts\n- Prisma ORM + PostgreSQL (dev: SQLite? Prisma schema differences—use PostgreSQL, docker-compose)\n- Auth: Auth.js (NextAuth v5) credentials\n- Validation: Zod\n- Forms: React Hook Form + zodResolver\n- Upload: local `/public/uploads` via API route + sharp (or UploadThing)\n- Dates: date-fns\n- Tables: TanStack Table\n- DnD for kanban: dnd-kit\n- Testing: Vitest + Playwright\n- Lint/format: ESLint + Prettier\n\nPackage manager: pnpm.\n\nFolder structure: standard Next.js app router with route groups.\n\nDomain models with Prisma schema snippet. Let me include full Prisma schema — that's very useful. Could be long but valuable. I'll include it.\n\nLet me write the Prisma schema:\n\n```prisma\nenum StudentStatus { CALON_STUDENT STUDENT EX_STUDENT }\nenum LeadSource { WHATSAPP REFERRAL BANNER SOCIAL_MEDIA }\nenum FollowUpStatus { NEW CONTACTED TRIAL SCHEDULED OFFER ENROLLED LOST }\nenum PaymentMethod { CASH TRANSFER QRIS }\nenum PaymentStatus { UNPAID PARTIAL PAID REFUNDED }\nenum AttendanceStatus { PRESENT LATE EXCUSED SICK ABSENT }\nenum Role { ADMIN TEACHER }\nenum ClassStatus { DRAFT ACTIVE GRADUATED ARCHIVED }\nenum EnrollmentStatus { ACTIVE COMPLETED DROPPED }\nenum PlacementLevel { BEGINNER INTERMEDIATE ADVANCED }\nenum Gender { MALE FEMALE OTHER }\n```\n\nModels: User, Teacher? (User with role TEACHER), Student, Class, Enrollment, Session, Attendance, Payment, Journal, JournalPhoto, ReportCard, ReportScore, PlacementTest, FollowUp, FollowUpActivity, Setting/Tag.\n\nI'll write it out.\n\nBusiness rules:\n- Prorating formula.\n- Follow-up due logic.\n- Attendance rate.\n- Status transitions.\n- Soft delete.\n- Audit log maybe.\n\nAPI endpoints table.\n\nSample data. I'll make seed.example.ts with prisma upserts.\n\n---\n\n# DESIGN.md\n\nSections:\n1. Brand & Personality\n2. Design Principles\n3. Color System (tokens)\n4. Typography\n5. Spacing, Radius, Shadow\n6. Iconography & Illustration\n7. Layout & Navigation (left nav + right rail)\n8. Components library\n9. Charts & Data Viz\n10. Page specs (wireframe-ish)\n11. States (empty/loading/error)\n12. Motion\n13. Accessibility\n14. Responsive\n15. Sample UI data / content examples\n16. Design tokens CSS\n\nLet me write.\n\nColor palette:\n- Brand primary: Violet 600 #6D5DF6 / Indigo. Let's define:\n - Primary (Violet): 50 #F1EEFE, 100 #E4DFFD, 300 #B9AEFB, 500 #7C5CFC, 600 #6D4AEF, 700 #5A38D6\n - Secondary/Accent Coral: #FF6B6B / #FF7A59\n - Success/Mint: #12B886 / #10B981\n - Warning/Amber: #FFB020 / #FBBF24\n - Info/Sky: #2AA9FF / #38BDF8\n - Pink: #F65FA7\n - Neutral slate: #0F172A ... #F8FAFC\n- Gradients: `linear-gradient(135deg,#7C5CFC,#2AA9FF)`, `#FF7A59→#FFB020`, `#12B886→#2AA9FF`.\n- Semantic colors for statuses.\n\nSource colors for chart:\n- WhatsApp: #25D366 (green) — but our mint is #12B886; use #22C55E\n- Referral: #7C5CFC (violet)\n- Banner: #FFB020 (amber)\n- Social Media: #2AA9FF (sky)\n\nStudent status colors:\n- calon_student: amber\n- student: mint/green\n- ex_student: slate\n\nTypography: \"Plus Jakarta Sans\" (headings + UI), \"Inter\" (body)? Or \"Outfit\" display. I'll pick:\n- Display/Headings: **Outfit** or **Plus Jakarta Sans** (700/800)\n- Body: **Inter**\n- Mono (kode/nilai): **JetBrains Mono**\nPlayful but not childish: use Plus Jakarta Sans for both, with Sora for numbers/dashboard. I'll go Plus Jakarta Sans + JetBrains Mono.\n\nRadius: cards 20px (rounded-3xl-ish), buttons 12px, pills full. Soft shadows with colored tint.\n\nLayout:\n- Desktop: 3-column: Left sidebar (240px) + main + right rail (340px).\n- Right rail: \"Insight Panel\" with widgets: Aktif siswa, Kehadiran, Bar chart source, follow-up due, ex-student.\n- Collapsible right rail on smaller screens.\n\nNavigation items:\n- Dashboard (Home)\n- Follow Up (badge count)\n- Siswa\n- Kelas\n- Kehadiran\n- Pembayaran\n- Jurnal Aktivitas\n- Raport\n- Placement Test\n- Pengaturan\n\nPage specs for each.\n\nSample content examples: student names (Indonesian), class names, prices, journal entries, report aspects.\n\nLet me write these files now. This will be long. Let me use write tool.\n\nI'll write AGENTS.md first.\n\nLet me be careful with markdown and code fences.\n\nI'll write in Indonesian.\n\nLet me draft the Prisma schema fully:\n\n```prisma\ngenerator client {\n provider = \"prisma-client-js\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n url = env(\"DATABASE_URL\")\n}\n\n// ---------- ENUMS ----------\nenum Role {\n ADMIN\n TEACHER\n}\n\nenum StudentStatus {\n CALON_STUDENT\n STUDENT\n EX_STUDENT\n}\n\nenum LeadSource {\n WHATSAPP\n REFERRAL\n BANNER\n SOCIAL_MEDIA\n}\n\nenum FollowUpStatus {\n NEW\n CONTACTED\n TRIAL\n OFFER_SENT\n ENROLLED\n LOST\n}\n\nenum Gender {\n MALE\n FEMALE\n}\n\nenum ClassStatus {\n DRAFT\n ACTIVE\n COMPLETED\n ARCHIVED\n}\n\nenum EnrollmentStatus {\n ACTIVE\n COMPLETED\n DROPPED\n}\n\nenum AttendanceStatus {\n PRESENT\n LATE\n EXCUSED\n SICK\n ABSENT\n}\n\nenum PaymentMethod {\n CASH\n TRANSFER\n QRIS\n}\n\nenum PaymentStatus {\n UNPAID\n PARTIAL\n PAID\n REFUNDED\n}\n\nenum PlacementLevel {\n BEGINNER\n INTERMEDIATE\n ADVANCED\n}\n\n// ---------- MODELS ----------\nmodel User {\n id String @id @default(cuid())\n name String\n email String @unique\n passwordHash String\n role Role @default(TEACHER)\n avatarUrl String?\n phone String?\n isActive Boolean @default(true)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n classesTaught Class[] @relation(\"ClassTeacher\")\n sessions Session[]\n journals Journal[]\n reportCards ReportCard[]\n placementTests PlacementTest[] @relation(\"Examiner\")\n followUpsOwned FollowUp[] @relation(\"FollowUpPIC\")\n}\n\nmodel Student {\n id String @id @default(cuid())\n fullName String\n phone String\n email String? @unique\n photoUrl String?\n parentName String?\n parentPhone String?\n birthDate DateTime?\n gender Gender?\n address String?\n leadSource LeadSource\n status StudentStatus @default(CALON_STUDENT)\n notes String?\n joinedAt DateTime?\n archivedAt DateTime?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n enrollments Enrollment[]\n attendances Attendance[]\n payments Payment[]\n reportCards ReportCard[]\n placementTests PlacementTest[]\n followUps FollowUp[]\n journalStudents JournalStudent[]\n}\n\nmodel Class {\n id String @id @default(cuid())\n name String\n code String @unique\n description String?\n level PlacementLevel\n teacherId String?\n teacher User? @relation(\"ClassTeacher\", fields: [teacherId], references: [id])\n totalMeetings Int @default(10)\n packagePrice Int @default(0) // harga paket penuh (IDR)\n pricePerMeeting Int? // opsional override\n capacity Int @default(12)\n schedule String? // \"Senin & Rabu 16:00-17:30\"\n startDate DateTime?\n endDate DateTime?\n status ClassStatus @default(ACTIVE)\n color String? // warna kartu kelas\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n enrollments Enrollment[]\n sessions Session[]\n payments Payment[]\n journals Journal[]\n reportCards ReportCard[]\n}\n\nmodel Enrollment {\n id String @id @default(cuid())\n studentId String\n student Student @relation(fields: [studentId], references: [id], onDelete: Cascade)\n classId String\n class Class @relation(fields: [classId], references: [id], onDelete: Cascade)\n status EnrollmentStatus @default(ACTIVE)\n joinedAt DateTime @default(now())\n leftAt DateTime?\n createdAt DateTime @default(now())\n\n @@unique([studentId, classId])\n}\n\nmodel Session {\n id String @id @default(cuid())\n classId String\n class Class @relation(fields: [classId], references: [id], onDelete: Cascade)\n meetingNumber Int\n date DateTime\n topic String?\n teacherId String?\n teacher User? @relation(fields: [teacherId], references: [id])\n createdAt DateTime @default(now())\n\n attendances Attendance[]\n journals Journal[]\n\n @@unique([classId, meetingNumber])\n}\n\nmodel Attendance {\n id String @id @default(cuid())\n sessionId String\n session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)\n studentId String\n student Student @relation(fields: [studentId], references: [id], onDelete: Cascade)\n status AttendanceStatus\n note String?\n recordedById String?\n createdAt DateTime @default(now())\n\n @@unique([sessionId, studentId])\n}\n\nmodel Payment {\n id String @id @default(cuid())\n invoiceNumber String @unique\n studentId String\n student Student @relation(fields: [studentId], references: [id])\n classId String\n class Class @relation(fields: [classId], references: [id])\n meetingsPaid Int\n pricePerMeeting Int\n amount Int // meetingsPaid * pricePerMeeting\n method PaymentMethod\n status PaymentStatus @default(PAID)\n paidAt DateTime\n note String?\n proofUrl String?\n createdById String?\n createdAt DateTime @default(now())\n}\n\nmodel Journal {\n id String @id @default(cuid())\n classId String\n class Class @relation(fields: [classId], references: [id], onDelete: Cascade)\n sessionId String?\n session Session? @relation(fields: [sessionId], references: [id])\n title String\n description String?\n activityDate DateTime\n tags String[]\n photos JournalPhoto[]\n authorId String\n author User @relation(fields: [authorId], references: [id])\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n students JournalStudent[]\n}\n\nmodel JournalPhoto {\n id String @id @default(cuid())\n journalId String\n journal Journal @relation(fields: [journalId], references: [id], onDelete: Cascade)\n url String\n caption String?\n sortOrder Int @default(0)\n}\n\nmodel JournalStudent {\n journalId String\n studentId String\n journal Journal @relation(fields: [journalId], references: [id], onDelete: Cascade)\n student Student @relation(fields: [studentId], references: [id], onDelete: Cascade)\n\n @@id([journalId, studentId])\n}\n\nmodel ReportCard {\n id String @id @default(cuid())\n studentId String\n student Student @relation(fields: [studentId], references: [id], onDelete: Cascade)\n classId String\n class Class @relation(fields: [classId], references: [id])\n period String // \"2026-Term1\"\n teacherId String\n teacher User @relation(fields: [teacherId], references: [id])\n attendanceSummary String?\n finalScore Int?\n grade String?\n teacherNotes String?\n publishedAt DateTime?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n scores ReportScore[]\n}\n\nmodel ReportScore {\n id String @id @default(cuid())\n reportCardId String\n reportCard ReportCard @relation(fields: [reportCardId], references: [id], onDelete: Cascade)\n aspect String // Logic, Computational Thinking, ...\n score Int\n note String?\n}\n\nmodel PlacementTest {\n id String @id @default(cuid())\n studentId String\n student Student @relation(fields: [studentId], references: [id], onDelete: Cascade)\n testDate DateTime\n levelResult PlacementLevel\n recommendedClassId String?\n totalScore Int\n examinerId String?\n examiner User? @relation(\"Examiner\", fields: [examinerId], references: [id])\n notes String?\n createdAt DateTime @default(now())\n\n scores PlacementScore[]\n}\n\nmodel PlacementScore {\n id String @id @default(cuid())\n placementTestId String\n placementTest PlacementTest @relation(fields: [placementTestId], references: [id], onDelete: Cascade)\n category String\n score Int\n}\n\nmodel FollowUp {\n id String @id @default(cuid())\n studentId String\n student Student @relation(fields: [studentId], references: [id], onDelete: Cascade)\n status FollowUpStatus @default(NEW)\n picId String?\n pic User? @relation(\"FollowUpPIC\", fields: [picId], references: [id])\n nextFollowUpAt DateTime?\n lastContactedAt DateTime?\n channel LeadSource?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n activities FollowUpActivity[]\n}\n\nmodel FollowUpActivity {\n id String @id @default(cuid())\n followUpId String\n followUp FollowUp @relation(fields: [followUpId], references: [id], onDelete: Cascade)\n type String // call, wa, email, meeting\n note String\n createdAt DateTime @default(now())\n authorId String?\n}\n```\n\nGood enough. Maybe a Setting model for lead source labels and app config. I'll add:\n\n```prisma\nmodel Setting {\n key String @id\n value Json\n}\n```\n\nAlso AuditLog optional.\n\nNow business rules.\n\nProrating:\n```\npricePerMeeting = class.pricePerMeeting ?? (class.packagePrice / class.totalMeetings)\namount = meetingsPaid * pricePerMeeting\n```\nProrated refund/value:\n```\nusedMeetings = count(attendance where status IN (PRESENT, LATE))\nremainingMeetings = max(0, totalPaidMeetings - usedMeetings)\nproratedRefund = remainingMeetings * pricePerMeeting\n```\n\nLet me write examples.\n\nFollow-up due:\n```\nfollowUpDue = FollowUp where status NOT IN (ENROLLED, LOST) AND nextFollowUpAt <= endOfToday\n```\n\nActive students:\n```\nStudent where status = STUDENT\n```\n\nAttendance rate:\n```\npresent + late (counted as present) / total attendance records\n```\n\nNow seed data. Let me define realistic sample:\n\nUsers:\n- admin: Sari Wulandari (admin@kodeva.id)\n- teacher: Budi Santoso, Rina Kartika, Ahmad Fauzi\n\nClasses:\n1. KOD-SCR-01 \"Scratch Junior\" level BEGINNER, teacher Rina, 8 meetings, 1.200.000, Senin & Rabu 16:00\n2. KOD-PYT-01 \"Python Dasar\" BEGINNER, Budi, 10 meetings, 2.000.000, Selasa & Kamis 17:00\n3. KOD-WEB-01 \"Web Dev: HTML & CSS\" BEGINNER, Ahmad, 12 meetings, 2.400.000, Sabtu 09:00\n4. KOD-JS-01 \"JavaScript Intermediate\" INTERMEDIATE, Budi, 10 meetings, 2.500.000\n5. KOD-ARD-01 \"Arduino & Robotics\" INTERMEDIATE, Rina, 8 meetings, 1.800.000\n\nStudents (mix):\n- Andi Pratama — STUDENT, REFERRAL, parent Budi Pratama\n- Citra Lestari — CALON_STUDENT, WHATSAPP\n- Dimas Anggara — STUDENT, BANNER\n- Elsa Maharani — CALON_STUDENT, SOCIAL_MEDIA\n- Fajar Nugroho — STUDENT, WHATSAPP\n- Gita Permata — EX_STUDENT, REFERRAL\n- Hadi Susanto — STUDENT, SOCIAL_MEDIA\n- Intan Puspita — CALON_STUDENT, BANNER\n\netc. I'll do ~10 students.\n\nPayments, attendance, journals, report cards, placement tests.\n\nI'll create seed.example.ts with these.\n\nLet me be careful: the seed file will reference ids. Use fixed ids or use create with nested. To keep it simple, I'll use `upsert` by unique fields and then fetch. Actually a simpler approach: deleteMany then create in order, using variables. I'll write clean TypeScript.\n\nLet me write it reasonably.\n\nNow, the DESIGN.md. Let me write comprehensive.\n\nI think I should produce the files now.\n\nLet me start writing.\n\nFor the right sidebar dashboard widgets:\n1. **Stat Cards** (compact): Siswa Aktif, Perlu Follow Up, Ex Student, Kehadiran hari ini %.\n2. **Kehadiran chart**: line/area chart of attendance rate per week, or donut for today.\n3. **Bar chart**: Lead source breakdown.\n4. **Active students**: area chart trend / mini sparkline.\n5. **Follow-up list**: list of students due today with quick action.\n6. **Upcoming sessions today**.\n\nDesign says \"side bar sebelah kanan\" for dashboard. So the main dashboard page has right rail. Possibly on all pages? I'll spec that right rail exists on Dashboard, and collapsible elsewhere. The requirement #2 says \"Pada side bar sebelah kanan, saya ingin ada dashboard untuk melihat grafik...\". So yes, right rail dashboard.\n\nLet me write.\n\nAlso I'll include a \"Layout\" ASCII wireframe.\n\nLet me write the color tokens in CSS variables and Tailwind config.\n\nOK writing now.\n\nI'll keep AGENTS.md focused on engineering, DESIGN.md on UI/UX + sample content. And seed.example.ts for data.\n\nLet me write.","thinkingSignature":"reasoning_content"},{"type":"text","text":"Direktori masih kosong. Saya akan buat tiga file: `AGENTS.md` (panduan engineering/agent), `DESIGN.md` (design system & UX), dan bonus `seed.example.ts` (contoh data siap pakai)."},{"type":"toolCall","id":"11f6ed6e-492e-4d00-ac3f-009dac6b7f9b","name":"write","arguments":{"path":"AGENTS.md","content":"# AGENTS.md — Kodeva\n\n> **Kodeva** — Sistem Manajemen Kursus Coding (Coding Course Management System).\n> Dokumen ini adalah kontrak kerja untuk AI coding agent & developer. Baca sampai habis sebelum menulis kode.\n> Untuk aturan visual/UX, lihat [`DESIGN.md`](./DESIGN.md). Untuk contoh data, lihat [`seed.example.ts`](./seed.example.ts).\n\n---\n\n## 1. Ringkasan Produk\n\nKodeva membantu akademi/bimbel coding mengelola siklus hidup siswa dari **calon student → student → ex student**, lengkap dengan:\n\n| # | Fitur | Ringkas |\n|---|-------|---------|\n| 1 | **CRUD Follow Up Siswa** | Data siswa: nama, no. telepon, email, foto profil, nama orang tua, sumber lead (WhatsApp / Referral / Banner / Social Media), status siswa |\n| 2 | **Dashboard Right Rail** | Grafik siswa aktif, kehadiran, bar chart sumber lead |\n| 3 | **Pembayaran Prorata** | Bayar per pertemuan, tanpa payment gateway, metode Cash / Transfer / QRIS |\n| 4 | **Jurnal & Raport & Kehadiran** | Guru mengisi kehadiran, jurnal kelas, dan raport siswa |\n| 5 | **Konfigurasi Kelas** | Kelas dapat dikonfigurasi; siswa dipilihkan kelas saat pendaftaran |\n| 6 | **Jurnal Aktivitas** | Upload foto + aktivitas kelas |\n| 7 | **Dashboard Ringkasan** | Berapa yang harus di-follow up, berapa ex student |\n| 8 | **Placement Test** | Hasil tes penempatan tampil di detail siswa |\n\n**Pengguna:** Admin (pemilik/operator), Teacher (tutor). Bahasa UI: **Bahasa Indonesia**.\n\n---\n\n## 2. Tech Stack (Default)\n\n```\nRuntime : Node.js 20+ / Bun\nFramework : Next.js 15 (App Router, React Server Components)\nLanguage : TypeScript (strict)\nStyling : Tailwind CSS v4 + CSS variables (lihat DESIGN.md)\nUI Kit : shadcn/ui + Radix Primitives + lucide-react\nCharts : Recharts\nForms : React Hook Form + Zod (zodResolver)\nTables : TanStack Table\nDrag & Drop : dnd-kit (kanban follow-up)\nORM : Prisma\nDatabase : PostgreSQL 16 (dev cepat boleh SQLite, tapi schema final PostgreSQL)\nAuth : Auth.js (NextAuth v5) — Credentials + RBAC\nUpload : API route lokal `/api/uploads` → `public/uploads` (atau S3-compatible)\nDates : date-fns (+ locale `id`)\nTesting : Vitest (unit) + Playwright (e2e)\nLint/Format : ESLint + Prettier\nPackage Mgr : pnpm\n```\n\n> Jika user memilih stack lain (mis. Laravel/Vue), **pertahankan domain model, business rules, dan DESIGN.md**, hanya ganti implementasi. Tanyakan dulu sebelum migrasi stack.\n\n---\n\n## 3. Setup & Perintah\n\n```bash\npnpm install # install dependency\ncp .env.example .env # siapkan env\npnpm db:push # prisma db push (dev)\npnpm db:seed # isi contoh data (seed.example.ts)\npnpm dev # jalankan dev server :3000\npnpm build && pnpm start # production build\n\npnpm lint # eslint\npnpm typecheck # tsc --noEmit\npnpm test # vitest\npnpm test:e2e # playwright\npnpm format # prettier --write .\n```\n\n**Definition of Done** setiap task: `pnpm lint && pnpm typecheck && pnpm test` hijau, tidak ada `any` liar, tidak ada secret ter-commit.\n\n---\n\n## 4. Struktur Folder\n\n```\nkodeva/\n├─ AGENTS.md\n├─ DESIGN.md\n├─ seed.example.ts\n├─ prisma/\n│ ├─ schema.prisma\n│ └─ migrations/\n├─ public/\n│ └─ uploads/ # foto siswa, jurnal, bukti bayar\n├─ src/\n│ ├─ app/\n│ │ ├─ (auth)/login/page.tsx\n│ │ ├─ (app)/\n│ │ │ ├─ layout.tsx # shell: left nav + main + right rail\n│ │ │ ├─ dashboard/page.tsx\n│ │ │ ├─ follow-up/page.tsx # kanban\n│ │ │ ├─ students/\n│ │ │ │ ├─ page.tsx\n│ │ │ │ ├─ new/page.tsx\n│ │ │ │ └─ [id]/page.tsx # detail: profil, kelas, kehadiran, bayar, raport, placement\n│ │ │ ├─ classes/page.tsx\n│ │ │ ├─ classes/[id]/page.tsx\n│ │ │ ├─ attendance/page.tsx\n│ │ │ ├─ payments/page.tsx\n│ │ │ ├─ journals/page.tsx\n│ │ │ ├─ report-cards/page.tsx\n│ │ │ ├─ placements/page.tsx\n│ │ │ └─ settings/page.tsx\n│ │ └─ api/\n│ │ ├─ students/route.ts\n│ │ ├─ students/[id]/route.ts\n│ │ ├─ ... (lihat §8)\n│ │ └─ uploads/route.ts\n│ ├─ components/\n│ │ ├─ ui/ # shadcn primitives\n│ │ ├─ layout/ # AppSidebar, RightRail, Topbar\n│ │ ├─ students/ # StudentForm, StudentCard, StatusBadge\n│ │ ├─ charts/ # ActiveStudentsChart, AttendanceChart, LeadSourceBar\n│ │ ├─ payments/ # PaymentForm, ProrataSummary\n│ │ └─ shared/ # EmptyState, StatCard, DataTable\n│ ├─ lib/\n│ │ ├─ prisma.ts\n│ │ ├─ auth.ts\n│ │ ├─ rbac.ts\n│ │ ├─ validators/ # zod schemas per entitas\n│ │ ├─ prorata.ts # kalkulasi pembayaran (PURE FUNCTION + unit test)\n│ │ ├─ follow-up.ts # query due follow up\n│ │ └─ utils.ts # cn(), formatIDR(), formatDateID()\n│ ├─ hooks/\n│ └─ types/\n└─ tests/\n```\n\n**Aturan:** Server Components untuk baca data, Server Actions / Route Handlers untuk mutasi. Jangan fetch API internal dari Server Component — query Prisma langsung.\n\n---\n\n## 5. Konvensi Kode\n\n- **TypeScript strict.** Dilarang `any`; pakai `unknown` + narrowing. `import type` untuk tipe.\n- **Naming:** komponen `PascalCase`, file komponen `PascalCase.tsx`, util `camelCase.ts`, route segment `kebab-case`.\n- **Validasi:** semua input user divalidasi Zod di boundary (form + route handler) memakai schema yang sama.\n- **Uang:** simpan sebagai `Int` dalam **rupiah penuh** (tanpa sen). Jangan pakai `Float`. Format tampilan lewat `formatIDR()` → `Rp 2.000.000`.\n- **Tanggal:** simpan UTC di DB, tampilkan dengan `date-fns` locale `id` (mis. `Senin, 18 Sep 2026`). Timezone default `Asia/Jakarta`.\n- **ID:** `cuid()`. Jangan auto-increment untuk entitas publik.\n- **Soft delete:** entitas siswa/kelas pakai `archivedAt`; jangan hard delete.\n- **Query:** selalu scope query ke authorization. Jangan pernah mengembalikan data lintas-tenant tanpa cek role.\n- **Error:** route handler mengembalikan `{ data }` atau `{ error: { code, message } }`; jangan bocorkan stack trace.\n- **Commit:** Conventional Commits (`feat(students): ...`, `fix(prorata): ...`).\n\n---\n\n## 6. Domain Model (Prisma)\n\n```prisma\ngenerator client { provider = \"prisma-client-js\" }\ndatasource db { provider = \"postgresql\"; url = env(\"DATABASE_URL\") }\n\nenum Role { ADMIN TEACHER }\nenum StudentStatus { CALON_STUDENT STUDENT EX_STUDENT }\nenum LeadSource { WHATSAPP REFERRAL BANNER SOCIAL_MEDIA }\nenum FollowUpStatus { NEW CONTACTED TRIAL OFFER_SENT ENROLLED LOST }\nenum Gender { MALE FEMALE }\nenum ClassStatus { DRAFT ACTIVE COMPLETED ARCHIVED }\nenum EnrollmentStatus { ACTIVE COMPLETED DROPPED }\nenum AttendanceStatus { PRESENT LATE EXCUSED SICK ABSENT }\nenum PaymentMethod { CASH TRANSFER QRIS }\nenum PaymentStatus { UNPAID PARTIAL PAID REFUNDED }\nenum PlacementLevel { BEGINNER INTERMEDIATE ADVANCED }\n\nmodel User {\n id String @id @default(cuid())\n name String\n email String @unique\n passwordHash String\n role Role @default(TEACHER)\n avatarUrl String?\n phone String?\n isActive Boolean @default(true)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n classesTaught Class[] @relation(\"ClassTeacher\")\n sessions Session[]\n journals Journal[]\n reportCards ReportCard[]\n placementTests PlacementTest[] @relation(\"Examiner\")\n followUpsOwned FollowUp[] @relation(\"FollowUpPIC\")\n}\n\nmodel Student {\n id String @id @default(cuid())\n fullName String\n phone String\n email String? @unique\n photoUrl String?\n parentName String?\n parentPhone String?\n birthDate DateTime?\n gender Gender?\n address String?\n leadSource LeadSource\n status StudentStatus @default(CALON_STUDENT)\n notes String?\n joinedAt DateTime?\n archivedAt DateTime?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n enrollments Enrollment[]\n attendances Attendance[]\n payments Payment[]\n reportCards ReportCard[]\n placementTests PlacementTest[]\n followUps FollowUp[]\n journalStudents JournalStudent[]\n}\n\nmodel Class {\n id String @id @default(cuid())\n name String\n code String @unique\n description String?\n level PlacementLevel\n teacherId String?\n teacher User? @relation(\"ClassTeacher\", fields: [teacherId], references: [id])\n totalMeetings Int @default(10)\n packagePrice Int @default(0) // harga paket penuh (IDR)\n pricePerMeeting Int? // override opsional\n capacity Int @default(12)\n schedule String? // \"Senin & Rabu 16:00-17:30\"\n startDate DateTime?\n endDate DateTime?\n status ClassStatus @default(ACTIVE)\n color String? // hex warna kartu kelas\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n enrollments Enrollment[]\n sessions Session[]\n payments Payment[]\n journals Journal[]\n reportCards ReportCard[]\n}\n\nmodel Enrollment {\n id String @id @default(cuid())\n studentId String\n student Student @relation(fields: [studentId], references: [id], onDelete: Cascade)\n classId String\n class Class @relation(fields: [classId], references: [id], onDelete: Cascade)\n status EnrollmentStatus @default(ACTIVE)\n joinedAt DateTime @default(now())\n leftAt DateTime?\n createdAt DateTime @default(now())\n @@unique([studentId, classId])\n}\n\nmodel Session {\n id String @id @default(cuid())\n classId String\n class Class @relation(fields: [classId], references: [id], onDelete: Cascade)\n meetingNumber Int\n date DateTime\n topic String?\n teacherId String?\n teacher User? @relation(fields: [teacherId], references: [id])\n createdAt DateTime @default(now())\n attendances Attendance[]\n journals Journal[]\n @@unique([classId, meetingNumber])\n}\n\nmodel Attendance {\n id String @id @default(cuid())\n sessionId String\n session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)\n studentId String\n student Student @relation(fields: [studentId], references: [id], onDelete: Cascade)\n status AttendanceStatus\n note String?\n recordedById String?\n createdAt DateTime @default(now())\n @@unique([sessionId, studentId])\n}\n\nmodel Payment {\n id String @id @default(cuid())\n invoiceNumber String @unique\n studentId String\n student Student @relation(fields: [studentId], references: [id])\n classId String\n class Class @relation(fields: [classId], references: [id])\n meetingsPaid Int\n pricePerMeeting Int\n amount Int // meetingsPaid * pricePerMeeting\n method PaymentMethod\n status PaymentStatus @default(PAID)\n paidAt DateTime\n note String?\n proofUrl String?\n createdById String?\n createdAt DateTime @default(now())\n}\n\nmodel Journal {\n id String @id @default(cuid())\n classId String\n class Class @relation(fields: [classId], references: [id], onDelete: Cascade)\n sessionId String?\n session Session? @relation(fields: [sessionId], references: [id])\n title String\n description String?\n activityDate DateTime\n tags String[]\n photos JournalPhoto[]\n students JournalStudent[]\n authorId String\n author User @relation(fields: [authorId], references: [id])\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel JournalPhoto {\n id String @id @default(cuid())\n journalId String\n journal Journal @relation(fields: [journalId], references: [id], onDelete: Cascade)\n url String\n caption String?\n sortOrder Int @default(0)\n}\n\nmodel JournalStudent {\n journalId String\n studentId String\n journal Journal @relation(fields: [journalId], references: [id], onDelete: Cascade)\n student Student @relation(fields: [studentId], references: [id], onDelete: Cascade)\n @@id([journalId, studentId])\n}\n\nmodel ReportCard {\n id String @id @default(cuid())\n studentId String\n student Student @relation(fields: [studentId], references: [id], onDelete: Cascade)\n classId String\n class Class @relation(fields: [classId], references: [id])\n period String // \"2026-Term1\"\n teacherId String\n teacher User @relation(fields: [teacherId], references: [id])\n attendanceSummary String?\n finalScore Int?\n grade String? // A / B / C / D\n teacherNotes String?\n publishedAt DateTime?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n scores ReportScore[]\n}\n\nmodel ReportScore {\n id String @id @default(cuid())\n reportCardId String\n reportCard ReportCard @relation(fields: [reportCardId], references: [id], onDelete: Cascade)\n aspect String // \"Logika\", \"Computational Thinking\", \"Problem Solving\", \"Kreativitas\", \"Kolaborasi\"\n score Int // 0-100\n note String?\n}\n\nmodel PlacementTest {\n id String @id @default(cuid())\n studentId String\n student Student @relation(fields: [studentId], references: [id], onDelete: Cascade)\n testDate DateTime\n levelResult PlacementLevel\n recommendedClassId String?\n totalScore Int\n examinerId String?\n examiner User? @relation(\"Examiner\", fields: [examinerId], references: [id])\n notes String?\n createdAt DateTime @default(now())\n scores PlacementScore[]\n}\n\nmodel PlacementScore {\n id String @id @default(cuid())\n placementTestId String\n placementTest PlacementTest @relation(fields: [placementTestId], references: [id], onDelete: Cascade)\n category String // \"Logika\", \"Dasar Coding\", \"Problem Solving\", \"Kreativitas\"\n score Int // 0-100\n}\n\nmodel FollowUp {\n id String @id @default(cuid())\n studentId String\n student Student @relation(fields: [studentId], references: [id], onDelete: Cascade)\n status FollowUpStatus @default(NEW)\n picId String?\n pic User? @relation(\"FollowUpPIC\", fields: [picId], references: [id])\n nextFollowUpAt DateTime?\n lastContactedAt DateTime?\n channel LeadSource?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n activities FollowUpActivity[]\n}\n\nmodel FollowUpActivity {\n id String @id @default(cuid())\n followUpId String\n followUp FollowUp @relation(fields: [followUpId], references: [id], onDelete: Cascade)\n type String // call | wa | email | meeting\n note String\n authorId String?\n createdAt DateTime @default(now())\n}\n\nmodel Setting {\n key String @id\n value Json\n}\n```\n\n**Relasi penting:** `Student 1—N Enrollment N—1 Class`. Satu siswa bisa ikut beberapa kelas. Kehadiran terikat ke `Session` (pertemuan), bukan langsung ke kelas — ini yang membuat prorata per pertemuan akurat.\n\n---\n\n## 7. Business Rules (WAJIB, sertakan unit test)\n\n### 7.1 Prorata Pembayaran — `src/lib/prorata.ts`\nPembayaran dihitung **per pertemuan**, bukan per bulan.\n\n```ts\npricePerMeeting = klass.pricePerMeeting ?? Math.round(klass.packagePrice / klass.totalMeetings)\namount = meetingsPaid * pricePerMeeting\n```\n\nContoh: paket **Rp 2.000.000 / 10 pertemuan** → `pricePerMeeting = 200.000`.\n- Bayar 10 pertemuan → `amount = 2.000.000` (PAID penuh).\n- Bayar 5 pertemuan → `amount = 1.000.000` (PAID, sisa 5 sesi).\n- Cicilan: `Payment.status = PARTIAL` bila akumulasi `meetingsPaid` < komitmen, lalu naik ke `PAID`.\n\n**Nilai prorata saat siswa berhenti pindah/refund:**\n```ts\nusedMeetings = count(Attendance WHERE studentId = X AND status IN (PRESENT, LATE))\npaidMeetings = sum(Payment.meetingsPaid WHERE status != REFUNDED)\nremainingMeetings = max(0, paidMeetings - usedMeetings)\nproratedValue = remainingMeetings * pricePerMeeting // nilai sisa / refund\n```\n\n**Aturan sesi:** 1 pembayaran = N pertemuan. Kuota sesi siswa = total `meetingsPaid`. Sesi terpakai = kehadiran `PRESENT`/`LATE`. Jika sesi terpakai > kuota → tandai `UNPAID` & tampilkan badge **\"Perlu Perpanjang\"**.\n\n### 7.2 Follow Up Due — `src/lib/follow-up.ts`\n```ts\nfollowUpDue = FollowUp.findMany({\n where: {\n status: { notIn: [\"ENROLLED\", \"LOST\"] },\n nextFollowUpAt: { lte: endOfToday() },\n },\n})\n```\n- Angka **\"Perlu Follow Up\"** di dashboard = `count(followUpDue)`.\n- Angka **\"Ex Student\"** = `count(Student where status = EX_STUDENT)`.\n- Kanban follow-up: kolom `NEW → CONTACTED → TRIAL → OFFER_SENT → ENROLLED`; `LOST` di kolom terpisah.\n- Saat follow-up diubah ke `ENROLLED`, tawarkan form: ubah `Student.status` jadi `STUDENT`, pilih kelas, buat `Enrollment`, lalu arahkan ke input `Payment`.\n\n### 7.3 Kehadiran\n- `attendanceRate = (PRESENT + LATE) / totalSesiTercatat * 100`.\n- `LATE` dihitung hadir tapi ditandai kuning.\n- Guru mengisi kehadiran per `Session` (halaman `attendance`), bisa bulk (Hadir semua) lalu ubah pengecualian.\n\n### 7.4 Status Siswa\n```\nCALON_STUDENT --(enroll + bayar)--> STUDENT --(selesai kelas / berhenti)--> EX_STUDENT\n ^ |\n +---------- (daftar lagi) ----------------+\n```\nPerubahan status manual oleh Admin harus tercatat di activity/audit.\n\n### 7.5 Placement Test\n- Menyimpan skor per kategori (0–100) → `levelResult` (`BEGINNER`/`INTERMEDIATE`/`ADVANCED`) + rekomendasi kelas.\n- Hasil **selalu tampil** di tab \"Placement Test\" pada halaman detail siswa dan sebagai badge di header profil.\n\n### 7.6 Kelas\n- Admin bisa CRUD kelas: nama, kode unik, level, guru, jumlah pertemuan, harga paket, kapasitas, jadwal, warna.\n- Siswa baru **wajib memilih kelas** (minimal 1) saat diubah menjadi `STUDENT`. Validasi kapasitas: tolak jika `enrollments.active >= capacity`.\n- Generate `Session` otomatis sebanyak `totalMeetings` dengan interval jadwal saat kelas diaktifkan (boleh manual).\n\n---\n\n## 8. API Endpoints\n\n| Method | Route | Fungsi | Role |\n|--------|-------|--------|------|\n| GET/POST | `/api/students` | list (filter status/source/q) & create | ADMIN, TEACHER |\n| GET/PATCH/DELETE | `/api/students/[id]` | detail, update, archive | ADMIN, TEACHER |\n| POST | `/api/students/[id]/enroll` | enroll ke kelas | ADMIN |\n| GET/POST | `/api/follow-ups` | list (due) & create | ADMIN, TEACHER |\n| PATCH | `/api/follow-ups/[id]` | ubah status / jadwal / PIC | ADMIN, TEACHER |\n| POST | `/api/follow-ups/[id]/activities` | catat aktivitas follow up | ADMIN, TEACHER |\n| GET/POST | `/api/classes` | list & create kelas | ADMIN |\n| GET/PATCH/DELETE | `/api/classes/[id]` | detail/update/archive | ADMIN |\n| POST | `/api/classes/[id]/sessions` | generate/atur pertemuan | ADMIN, TEACHER |\n| GET/POST | `/api/attendance` | per session & bulk upsert | ADMIN, TEACHER |\n| GET/POST | `/api/payments` | list & buat pembayaran (prorata) | ADMIN |\n| PATCH | `/api/payments/[id]` | update status / refund | ADMIN |\n| GET/POST | `/api/journals` | feed & create (multipart foto) | ADMIN, TEACHER |\n| GET/PATCH/DELETE | `/api/journals/[id]` | detail/update/hapus | ADMIN, TEACHER |\n| GET/POST | `/api/report-cards` | list & create | ADMIN, TEACHER |\n| PATCH | `/api/report-cards/[id]/publish` | publish ke siswa | ADMIN, TEACHER |\n| GET/POST | `/api/placements` | list & create hasil placement | ADMIN, TEACHER |\n| GET | `/api/dashboard/summary` | angka + data grafik right rail | ADMIN, TEACHER |\n| POST | `/api/uploads` | upload file → URL | ADMIN, TEACHER |\n\nSemua route pakai `withAuth(handler, { roles })` dari `src/lib/rbac.ts`. Validasi body dengan Zod. Response sukses `{ data }`, error `{ error: { code, message } }`.\n\n---\n\n## 9. RBAC\n\n| Area | ADMIN | TEACHER |\n|------|:-----:|:-------:|\n| Dashboard & right rail | ✅ | ✅ |\n| CRUD siswa | ✅ | ✅ (tanpa archive) |\n| CRUD follow up | ✅ | ✅ |\n| CRUD kelas & setting | ✅ | 👁 baca saja |\n| Input kehadiran | ✅ | ✅ (kelas sendiri) |\n| Input jurnal & foto | ✅ | ✅ (kelas sendiri) |\n| Input raport | ✅ | ✅ (kelas sendiri) |\n| Input placement test | ✅ | ✅ |\n| Pembayaran | ✅ | 👁 baca saja |\n| Manajemen user | ✅ | ❌ |\n\n---\n\n## 10. Environment Variables\n\n```env\nDATABASE_URL=\"postgresql://user:pass@localhost:5432/kodeva\"\nAUTH_SECRET=\"ganti-dengan-random-32-char\"\nAUTH_TRUST_HOST=true\nNEXT_PUBLIC_APP_NAME=\"Kodeva\"\nNEXT_PUBLIC_APP_URL=\"http://localhost:3000\"\nUPLOAD_DIR=\"./public/uploads\"\nMAX_UPLOAD_MB=5\nTZ=\"Asia/Jakarta\"\n```\n\nJangan pernah commit `.env`. Sediakan `.env.example`.\n\n---\n\n## 11. Contoh Data\n\nDataset contoh lengkap ada di [`seed.example.ts`](./seed.example.ts): 3 user (1 admin, 2 teacher), 5 kelas, 10 siswa (campuran `CALON_STUDENT` / `STUDENT` / `EX_STUDENT`), enrollment, sesi, kehadiran, pembayaran prorata, jurnal + foto, raport, placement test, dan follow up.\n\nRingkasan untuk konteks cepat:\n\n**Kelas (5):**\n| Kode | Nama | Level | Pertemuan | Harga Paket | Rp/Pertemuan |\n|------|------|-------|-----------|-------------|--------------|\n| KOD-SCR-01 | Scratch Junior | Beginner | 8 | Rp 1.200.000 | Rp 150.000 |\n| KOD-PYT-01 | Python Dasar | Beginner | 10 | Rp 2.000.000 | Rp 200.000 |\n| KOD-WEB-01 | Web Dev: HTML & CSS | Beginner | 12 | Rp 2.400.000 | Rp 200.000 |\n| KOD-JS-01 | JavaScript Intermediate | Intermediate | 10 | Rp 2.500.000 | Rp 250.000 |\n| KOD-ARD-01 | Arduino & Robotics | Intermediate | 8 | Rp 1.800.000 | Rp 225.000 |\n\n**Siswa (10):** Andi Pratama (STUDENT), Citra Lestari (CALON_STUDENT), Dimas Anggara (STUDENT), Elsa Maharani (CALON_STUDENT), Fajar Nugroho (STUDENT), Gita Permata (EX_STUDENT), Hadi Susanto (STUDENT), Intan Puspita (CALON_STUDENT), Joko Wijaya (STUDENT), Kirana Dewi (EX_STUDENT).\n\n**Contoh prorata:** Andi bayar Rp 2.000.000 untuk 10 pertemuan Python Dasar → Rp 200.000/pertemuan. Hadir 6 → terpakai Rp 1.200.000, sisa 4 pertemuan (Rp 800.000).\n\n---\n\n## 12. Testing & Quality\n\n- **Unit wajib** untuk `prorata.ts`, `follow-up.ts`, `utils` format uang/tanggal, dan semua Zod schema.\n- **Integration** untuk route handler (happy path + validasi gagal + unauthorized).\n- **E2E** minimal: login → tambah calon siswa → follow up → enroll + bayar prorata → isi kehadiran → isi jurnal → isi raport → lihat placement test di detail siswa.\n- Sertakan test case batas: `totalMeetings = 0`, `meetingsPaid > totalMeetings`, pembayaran parsial, kehadiran melebihi kuota.\n\n---\n\n## 13. Do / Don't untuk Agent\n\n**DO**\n- Baca `DESIGN.md` sebelum membuat/mengubah UI. Pakai token warna & komponen yang sudah ada, jangan hardcode hex baru.\n- Jalankan `prisma generate` setelah mengubah schema.\n- Buat komponen kecil & reusable; jangan file 800 baris.\n- Tangani 4 state: **loading (skeleton), empty, error, success**.\n- Aksesibel: label form, `aria-*`, fokus terlihat, kontras AA.\n- Format uang & tanggal lewat util, jangan inline.\n\n**DON'T**\n- Jangan tambahkan payment gateway / integrasi pihak ketiga pembayaran.\n- Jangan pakai `Float` untuk uang.\n- Jangan fetch internal API dari Server Component.\n- Jangan hapus data keras; gunakan archive.\n- Jangan ubah `DESIGN.md`/palet tanpa persetujuan user.\n- Jangan taruh logika prorata di komponen; selalu di `lib/prorata.ts` + test.\n\n---\n\n## 14. Roadmap\n\n- [ ] **M1** Auth + shell layout (left nav, right rail) + Prisma schema + seed\n- [ ] **M2** CRUD Siswa + Follow Up kanban + status\n- [ ] **M3** CRUD Kelas + Enrollment + Sesi/Pertemuan\n- [ ] **M4** Kehadiran + Pembayaran prorata\n- [ ] **M5** Jurnal + upload foto\n- [ ] **M6** Raport + Placement Test\n- [ ] **M7** Dashboard right rail (semua grafik) + polish\n- [ ] **M8** Export, notifikasi WhatsApp manual, audit log\n"}}],"api":"openai-completions","provider":"llm-eigen","model":"deepseek-v4-flash","usage":{"input":2262,"output":14706,"cacheRead":0,"cacheWrite":0,"reasoning":7421,"totalTokens":16968,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"toolUse","timestamp":1789727586531,"responseId":"dbf96d5a-7fa5-4ebd-8632-c6947a4c3d53","rawStopReason":"tool_calls"}} +{"type":"message","id":"6bd7628d","parentId":"709f665d","timestamp":"2026-09-18T10:35:21.348Z","message":{"role":"toolResult","toolCallId":"11f6ed6e-492e-4d00-ac3f-009dac6b7f9b","toolName":"write","content":[{"type":"text","text":"Successfully wrote to AGENTS.md"}],"isError":false,"timestamp":1789727721347}} +{"type":"message","id":"a53f0170","parentId":"6bd7628d","timestamp":"2026-09-18T10:36:28.899Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"Now DESIGN.md.","thinkingSignature":"reasoning_content"},{"type":"toolCall","id":"ba4d2d0b-2d40-4b82-b46f-fd1a8668a8c3","name":"write","arguments":{"content":"# DESIGN.md — Kodeva Design System\n\n> Panduan visual & UX untuk **Kodeva**. Tema: **Colorful · Ed-Tech · Playful · Rapi (bukan childish)**.\n> Bayangkan antarmuka produk SaaS modern untuk akademi coding — ceria, berenergi, tapi tetap kredibel dan enak dipakai kerja 8 jam.\n\n---\n\n## 1. Brand & Personality\n\n| Atribut | Nilai |\n|---------|-------|\n| Nama | **Kodeva** |\n| Tagline | *\"Kelola kelas coding, tumbuhkan pembuat masa depan.\"* |\n| Karakter | Ceria, cerdas, hangat, terpercaya, sedikit jenaka |\n| Bukan | Kaku korporat, norak, kekanak-kanakan (tanpa kartun balita / font comic) |\n| Metafora | Papan tulis digital yang penuh warna, blok kode berwarna, sticky notes |\n\n**Referensi rasa:** Linear (kerapian) × Duolingo (keceriaan) × Notion (keruang) × Figma (warna).\n\n---\n\n## 2. Design Principles\n\n1. **Warna membawa makna, bukan sekadar hiasan.** Tiap warna punya peran semantik (status, sumber lead, kehadiran). Konsisten di seluruh app.\n2. **Playful lewat bentuk & gerak, bukan lewat font kekanak-kanakan.** Sudut membulat, gradient halus, blob dekoratif tipis, micro-animation.\n3. **Data dulu.** Angka penting (perlu follow up, siswa aktif) langsung terlihat tanpa klik.\n4. **Satu layar, satu fokus.** Hierarki jelas: judul → aksi utama → konten → detail.\n5. **Hangat & manusiawi.** Foto profil bulat, sapaan personal (\"Hai, Sari 👋\"), bahasa Indonesia santai-profesional.\n\n---\n\n## 3. Color System\n\n### 3.1 Brand Palette\n\n| Token | Hex | Preview | Pemakaian |\n|-------|-----|---------|-----------|\n| `--brand-50` | `#F2EFFE` | 🟪 | background lembut |\n| `--brand-100` | `#E4DEFD` | 🟪 | chip, hover |\n| `--brand-300` | `#B9A9FB` | 🟪 | border aktif |\n| `--brand-500` | `#7C5CFC` | 🟪 | **primary**, tombol, ikon aktif |\n| `--brand-600` | `#6A46EF` | 🟪 | hover primary |\n| `--brand-700` | `#5533CE` | 🟪 | pressed / teks di bg terang |\n\n### 3.2 Accent & Semantic\n\n| Token | Hex | Makna |\n|-------|-----|-------|\n| `--coral-500` | `#FF7A59` | aksi sekunder, highlight, CTA promo |\n| `--amber-500` | `#FFB020` | warning, `CALON_STUDENT`, banner |\n| `--mint-500` | `#12B886` | success, `STUDENT` aktif, hadir |\n| `--sky-500` | `#2AA9FF` | info, social media, link |\n| `--pink-500` | `#F65FA7` | aksen dekoratif, tag kreatif |\n| `--danger-500` | `#F04438` | error, alpa, hapus |\n| `--violet-soft` | `#A78BFA` | grafik pelengkap |\n\n### 3.3 Neutral\n\n| Token | Hex | Pemakaian |\n|-------|-----|-----------|\n| `--ink-900` | `#131A2B` | teks utama, heading |\n| `--ink-700` | `#3A4560` | teks sekunder |\n| `--ink-500` | `#6B7590` | placeholder, caption |\n| `--ink-300` | `#C4CAD8` | border |\n| `--ink-100` | `#EEF1F7` | divider, bg hover |\n| `--surface` | `#FFFFFF` | kartu |\n| `--canvas` | `#F7F8FC` | background halaman |\n| `--canvas-tint` | linear-gradient(180deg,#F7F8FC 0%,#F1F0FE 100%) | background app |\n\n### 3.4 Gradient (signature)\n\n```css\n--grad-primary: linear-gradient(135deg, #7C5CFC 0%, #2AA9FF 100%); /* tombol utama, hero */\n--grad-coral: linear-gradient(135deg, #FF7A59 0%, #FFB020 100%); /* kartu highlight */\n--grad-mint: linear-gradient(135deg, #12B886 0%, #2AA9FF 100%); /* stat positif */\n--grad-pink: linear-gradient(135deg, #F65FA7 0%, #7C5CFC 100%); /* dekorasi */\n```\n\nGunakan gradient **hanya** untuk: tombol primary, avatar ring, header kartu statistik, dan 1 blob dekoratif per halaman. Jangan gradient di teks panjang.\n\n### 3.5 Warna Mapping Semantik (WAJIB konsisten)\n\n**Status Siswa**\n| Status | Warna | Badge |\n|--------|-------|-------|\n| `CALON_STUDENT` | Amber `#FFB020` | bg `#FFF4E0`, teks `#9A6100` |\n| `STUDENT` | Mint `#12B886` | bg `#E4F8F1`, teks `#0A7A58` |\n| `EX_STUDENT` | Slate `#6B7590` | bg `#EEF1F7`, teks `#4A5470` |\n\n**Sumber Lead** (dipakai di bar chart & chip)\n| Sumber | Warna | Ikon |\n|--------|-------|------|\n| `WHATSAPP` | `#22C55E` | `MessageCircle` |\n| `REFERRAL` | `#7C5CFC` | `Users` |\n| `BANNER` | `#FFB020` | `Flag` |\n| `SOCIAL_MEDIA` | `#2AA9FF` | `Instagram` |\n\n**Kehadiran**\n| Status | Warna | Label |\n|--------|-------|-------|\n| `PRESENT` | Mint `#12B886` | Hadir |\n| `LATE` | Amber `#FFB020` | Terlambat |\n| `EXCUSED` | Sky `#2AA9FF` | Izin |\n| `SICK` | Violet `#A78BFA` | Sakit |\n| `ABSENT` | Danger `#F04438` | Alpa |\n\n**Follow Up** : NEW `#94A3B8` · CONTACTED `#2AA9FF` · TRIAL `#7C5CFC` · OFFER_SENT `#FFB020` · ENROLLED `#12B886` · LOST `#F04438`\n\n**Metode Bayar** : CASH `#12B886` · TRANSFER `#2AA9FF` · QRIS `#7C5CFC`\n\n---\n\n## 4. Typography\n\n| Peran | Font | Weight | Size |\n|-------|------|--------|------|\n| Display / H1 | **Plus Jakarta Sans** | 800 | 30–36px, tracking `-0.02em` |\n| H2 | Plus Jakarta Sans | 700 | 24px |\n| H3 / Card title | Plus Jakarta Sans | 600 | 18px |\n| Body | **Inter** | 400/500 | 14–15px, line-height 1.6 |\n| Caption / label | Inter | 500 | 12–13px, tracking `0.01em` |\n| Angka dashboard | **Sora** | 700 | 28–32px, tabular-nums |\n| Kode / invoice | **JetBrains Mono** | 500 | 13px |\n\n```\nfont-display : \"Plus Jakarta Sans\", Inter, system-ui, sans-serif\nfont-body : \"Inter\", system-ui, sans-serif\nfont-num : \"Sora\", Inter, sans-serif\nfont-mono : \"JetBrains Mono\", ui-monospace, monospace\n```\n\nAturan: heading **selalu** gelap (`--ink-900`), jangan pakai warna brand untuk heading panjang. Warna brand untuk **aksen kata** (mis. \"3 siswa **perlu follow up**\").\n\n---\n\n## 5. Spacing · Radius · Shadow\n\n- **Spacing scale:** 4 · 8 · 12 · 16 · 20 · 24 · 32 · 40 · 48 · 64 (kelipatan 4).\n- **Radius:** `sm 8px` (input kecil) · `md 12px` (button, input) · `lg 16px` (dropdown) · `xl 20px` (card) · `2xl 24px` (panel besar) · `full` (pill, avatar).\n- **Shadow:**\n```css\n--shadow-xs: 0 1px 2px rgba(19,26,43,.06);\n--shadow-sm: 0 2px 8px rgba(19,26,43,.06);\n--shadow-md: 0 8px 24px rgba(19,26,43,.08);\n--shadow-lg: 0 16px 40px rgba(19,26,43,.10);\n--shadow-brand: 0 8px 20px rgba(124,92,252,.28); /* tombol primary */\n```\n- **Border:** 1px `--ink-100`; kartu aktif 1.5px `--brand-300`.\n- **Focus ring:** `0 0 0 3px rgba(124,92,252,.28)`.\n\n---\n\n## 6. Iconography & Illustration\n\n- **Ikon:** `lucide-react`, stroke 2, ukuran 18/20/24. Selalu dalam container bulat saat jadi aksi.\n- **Ilustrasi/dekorasi:** bentuk blob, lingkaran, blok kode melayang — **opacity 6–12%**, gradient brand. Maksimal 1–2 elemen per halaman, jangan menghalangi konten.\n- **Avatar:** bulat penuh, ukuran 32/40/56/96px, ring gradient `--grad-primary` untuk siswa aktif.\n- **Emoji:** boleh untuk sapaan & empty state yang ramah, maksimal 1 per layar. Jangan di tombol/heading formal.\n\n---\n\n## 7. Layout & Navigasi\n\n### 7.1 Shell Desktop (≥1280px) — 3 kolom\n\n```\n┌──────────────┬────────────────────────────────────────────┬───────────────────────┐\n│ LEFT NAV │ MAIN CONTENT │ RIGHT RAIL │\n│ 248px │ flex-1 (min 640px) │ 340px (sticky) │\n│ │ │ │\n│ ✦ Kodeva │ Topbar: breadcrumb · search · +Aksi · 🔔 │ ┌─────────────────┐ │\n│ │ │ │ Siswa Aktif 128│ │\n│ ▸ Dashboard │ ┌──────────────────────────────────────┐ │ ├─────────────────┤ │\n│ ▸ Follow Up │ │ Konten halaman / tabel / kanban │ │ │ Perlu Follow 12 │ │\n│ (12) 🔴 │ │ │ │ ├─────────────────┤ │\n│ ▸ Siswa │ │ │ │ │ Ex Student 7 │ │\n│ ▸ Kelas │ │ │ │ └─────────────────┘ │\n│ ▸ Kehadiran │ └──────────────────────────────────────┘ │ ┌─────────────────┐ │\n│ ▸ Pembayaran│ │ │ Kehadiran (donut)│ │\n│ ▸ Jurnal │ │ └─────────────────┘ │\n│ ▸ Raport │ │ ┌─────────────────┐ │\n│ ▸ Placement │ │ │ Siswa Aktif (line)│ │\n│ ▸ Pengaturan│ │ └─────────────────┘ │\n│ │ │ ┌─────────────────┐ │\n│ 👤 Sari W. │ │ │ Sumber Lead (bar)│ │\n│ Admin │ │ └─────────────────┘ │\n└──────────────┴────────────────────────────────────────────┴───────────────────────┘\n```\n\n- **Left nav:** background `--surface`, lebar 248px, item aktif = pill gradient brand dengan teks putih + `--shadow-brand`. Badge angka merah untuk follow up due. Dapat collapse jadi 72px (ikon saja).\n- **Right rail (\"Insight Panel\"):** hanya muncul penuh di **Dashboard**; di halaman lain bisa dibuka lewat tombol toggle (`PanelRight`), default tersembunyi agar konten lega. Sticky, scroll independen, tiap widget = kartu `radius xl`.\n- **Topbar:** tinggi 64px, sticky, blur backdrop `rgba(247,248,252,.8)`.\n\n### 7.2 Breakpoint\n\n| Breakpoint | Perilaku |\n|-----------|----------|\n| `≥1440px` | 3 kolom penuh, rail 340px |\n| `1280–1439px` | 3 kolom, rail 300px |\n| `1024–1279px` | rail jadi drawer kanan (default tutup) |\n| `768–1023px` | left nav collapse ikon; rail drawer |\n| `<768px` | bottom nav 5 item utama; tabel → kartu; rail = sheet |\n\n### 7.3 Bottom Nav (mobile)\n`Dashboard · Siswa · Follow Up · Kelas · Lainnya`\n\n---\n\n## 8. Komponen\n\n### 8.1 Tombol\n| Varian | Style |\n|--------|-------|\n| Primary | `--grad-primary`, teks putih, radius md, `--shadow-brand`, hover naik 1px |\n| Secondary | bg `--brand-50`, teks `--brand-700` |\n| Outline | border `--ink-300`, teks `--ink-900` |\n| Ghost | transparan, hover `--ink-100` |\n| Danger | bg `#F04438`, teks putih |\n| Pill/Chip | radius full, padding 6×12 |\n\nUkuran: `sm 32px` · `md 40px` · `lg 48px`. Sertakan ikon kiri untuk aksi utama.\n\n### 8.2 Kartu\n- **Stat Card:** ikon bulat gradient + label caption + angka besar (`font-num`) + delta (`+8%` mint / `-3%` coral). Tinggi 112px.\n- **Content Card:** header (judul + aksi \"Lihat semua\") + body + padding 20–24px.\n- **Student Card:** avatar + nama + badge status + chip sumber + info kelas; hover shadow-md, cursor pointer.\n- **Class Card:** strip warna kelas di kiri, nama, level chip, guru, `6/12 siswa` progress bar, jadwal.\n\n### 8.3 Form\n- Label di atas, 13px `--ink-700`, wajib ada `*` coral untuk required.\n- Input tinggi 40px, radius md, border `--ink-300`, fokus ring brand.\n- **Upload foto profil:** dropzone lingkaran dengan preview + tombol kamera; maks 5MB, JPG/PNG/WebP.\n- Pilihan sumber lead & status: **segmented control** berikon (bukan select biasa) agar playful.\n- Validasi inline: border danger + pesan 12px di bawah field.\n\n### 8.4 Badge & Chip\n- Status siswa pakai badge pill (dot + label).\n- Sumber lead pakai chip berikon dengan warna mapping §3.5.\n- Level kelas: Beginner (mint), Intermediate (sky), Advanced (violet).\n\n### 8.5 Tabel\nHeader `--ink-100`, teks uppercase 12px `--ink-500`. Baris hover `--brand-50/50`, tinggi 56px. Kolom aksi sticky kanan. Di mobile berubah jadi kartu bertumpuk.\n\n### 8.6 Kanban Follow Up\nKolom berisi header warna status + count + total value; kartu bisa drag (dnd-kit), sudut membulat, tampilkan avatar, sumber, \"due 2 hari lalu\" merah.\n\n### 8.7 Empty · Loading · Error\n- **Empty:** ilustrasi blob + judul ramah (\"Belum ada calon siswa nih\") + CTA.\n- **Loading:** skeleton shimmer bentuk sama dengan konten, bukan spinner penuh layar.\n- **Error:** kartu coral lembut + pesan jelas + tombol \"Coba lagi\".\n\n---\n\n## 9. Charts & Data Viz\n\nSemua chart pakai **Recharts**, tinggi 180–220px di rail, kartu putih, grid horizontal tipis `--ink-100`, tanpa garis sumbu Y penuh.\n\n| Chart | Tipe | Warna | Isi |\n|-------|------|-------|-----|\n| **Siswa Aktif** | Area/donut mini | `--grad-primary` | Tren 6 bulan; angka besar di tengah |\n| **Kehadiran** | Donut / radial | PRESENT mint, LATE amber, ABSENT danger | % kehadiran hari ini + ring |\n| **Sumber Lead** | Bar horizontal | mapping §3.5 | Jumlah per WhatsApp/Referral/Banner/Social |\n| **Siswa per Kelas** | Bar vertikal | `--violet-soft` | Kapasitas terpakai |\n| **Pendapatan** | Line/area | `--mint-500` | Opsional di halaman pembayaran |\n\nAturan chart:\n- Tooltip rounded, shadow-md, label Bahasa Indonesia.\n- Angka `font-num`, animasi masuk 400ms ease-out.\n- Selalu sediakan legend berwarna + label teks (jangan andalkan warna saja).\n- Bar chart sumber lead: tampilkan angka di ujung bar.\n\n### 9.1 Right Rail Widgets (Dashboard)\n1. **Stat stack:** Siswa Aktif · Perlu Follow Up · Ex Student (3 kartu mini).\n2. **Kehadiran Hari Ini:** donut + `86%` + rincian Hadir/Terlambat/Alpa.\n3. **Tren Siswa Aktif:** area chart 6 bulan.\n4. **Sumber Lead:** bar chart 4 kategori.\n5. **Perlu Follow Up Hari Ini:** list 4 nama + tombol WhatsApp; link \"Lihat semua\".\n6. **Kelas Hari Ini:** sesi + jam + guru.\n\n---\n\n## 10. Spesifikasi Halaman\n\n### 10.1 Dashboard\nHero sapaan + 4 stat card besar (Siswa Aktif, Perlu Follow Up, Ex Student, Kehadiran %). Bawahnya: grafik Tren Siswa Aktif & Sumber Lead (2 kolom). Right rail = widget §9.1.\n\n### 10.2 Halaman Siswa (list)\n- Filter segmented: **Semua · Calon Student · Student · Ex Student**.\n- Search nama/email/telepon, filter sumber lead, filter kelas.\n- Toggle tampilan **tabel ⇄ kartu**.\n- Tombol \"+ Tambah Siswa\" primary.\n- Kolom: Siswa (avatar+nama+email) · Telepon · Orang Tua · Kelas · Sumber · Status · Aksi.\n\n### 10.3 Form Tambah/Edit Siswa\nGrid 2 kolom:\n- Kiri: Foto profil (dropzone), Nama lengkap*, No. telepon*, Email, Nama orang tua, No. telepon orang tua, Tanggal lahir, Gender.\n- Kanan: **Tahu dari mana*** (segmented: WhatsApp/Referral/Banner/Social Media), **Status*** (segmented: Calon/Student/Ex), **Pilih Kelas** (multi-select, hanya aktif bila status Student), Catatan.\n- Sticky footer: Batal · Simpan.\n\n### 10.4 Detail Siswa\nHeader profil: avatar besar ring gradient, nama, badge status, chip sumber, tombol WhatsApp/Call/Edit. Tab: **Ringkasan · Kelas & Kehadiran · Pembayaran · Placement Test · Raport · Jurnal**.\n- **Placement Test** selalu tampil sebagai kartu: level hasil, skor per kategori (bar), rekomendasi kelas, tanggal, pemeriksa.\n- **Pembayaran**: ringkasan prorata — `Kuota 10 sesi · Terpakai 6 · Sisa 4 · Nilai sisa Rp 800.000`.\n\n### 10.5 Kelas (CRUD)\nGrid kartu kelas dengan strip warna + progress kapasitas. Form: nama, kode, level, guru, total pertemuan, harga paket, kapasitas, jadwal, warna, status. Preview `Rp per pertemuan` otomatis saat mengisi harga & jumlah pertemuan.\n\n### 10.6 Kehadiran\nPilih kelas → pilih pertemuan → daftar siswa dengan tombol status (segmented H/T/I/S/A) + tombol \"Hadir Semua\". Progress \"12/12 terisi\". Simpan.\n\n### 10.7 Pembayaran\nForm: pilih siswa → pilih kelas (auto ambil harga) → **jumlah pertemuan dibayar** (stepper) → tampil kalkulasi prorata besar: `10 × Rp 200.000 = Rp 2.000.000` → metode (Cash/Transfer/QRIS segmented) → tanggal → upload bukti (opsional). List pembayaran: invoice mono, siswa, kelas, sesi, nominal, metode chip, status.\n\n### 10.8 Jurnal Aktivitas\nFeed masonry/card: cover foto, judul, kelas, tanggal, tag, author, jumlah siswa. Tombol \"+ Buat Jurnal\": upload multi-foto (drag reorder), judul, deskripsi, kelas, sesi, tag, tandai siswa yang terlibat.\n\n### 10.9 Raport\nPilih kelas → pilih siswa → form aspek penilaian (slider/input 0–100): Logika, Computational Thinking, Problem Solving, Kreativitas, Kolaborasi → kesimpulan guru, grade otomatis, ringkasan kehadiran. Preview raport bertema (header gradient, tanda tangan guru).\n\n### 10.10 Placement Test\nForm: siswa, tanggal, pemeriksa, skor kategori (bar input), level hasil otomatis dari rata-rata + rekomendasi kelas. Riwayat list.\n\n### 10.11 Pengaturan\nSumber lead (label + warna + aktif), daftar guru/user, info akademi (nama, logo, alamat, kontak), preferensi (mata uang, timezone).\n\n---\n\n## 11. Motion\n\n| Aksi | Durasi | Easing |\n|------|--------|--------|\n| Hover kartu/tombol | 150ms | ease-out |\n| Masuk halaman (fade+up 8px) | 250ms | cubic-bezier(.2,.8,.2,1) |\n| Chart render | 400ms | ease-out |\n| Drawer/sheet | 240ms | cubic-bezier(.2,.8,.2,1) |\n| Konfeti saat siswa enroll 🎉 | 800ms | sekali, opsional |\n\nHormati `prefers-reduced-motion`: matikan animasi non-esensial.\n\n---\n\n## 12. Aksesibilitas\n\n- Kontras teks minimal **AA** (4.5:1 body, 3:1 besar). Jangan taruh teks putih di amber/kuning.\n- Fokus terlihat di semua elemen interaktif (ring brand).\n- Jangan sampaikan status **hanya** lewat warna — selalu ada label/ikon.\n- Semua input punya `