Lewati ke konten utama

Doc 12 — Tier C Addendum (Multi-Tenant + 5 Personas + P2 Modules)

Purpose: Supplements docs 08 (Flutter Handoff Map), 09 (Route Specification), and 10 (Widget Component Spec) with all Tier C scope additions. Reflects scope upgrade from Rp 145 juta (MVP 6 personas) → Rp 226 juta (Complete: multi-tenant + 11 personas + P2 features).

Audience: Flutter dev, product owner, client.

Scope snapshot (new):

  • Kepsek → Kepala Yayasan refactor (rename + content-refocus, 14 screens)
  • Multi-tenant auth flow (Kode Lembaga pattern B): 3 new screens
  • Splash screen unified (Parent + Staff converged to emerald gradient)
  • 5 new personas: Bendahara Yayasan (10), Pembina Asrama (8), Pembimbing Hafalan (6), PPDB (7), plus existing personas untouched
  • 3 P2 feature modules: Kamar Management (6), Pelanggaran + Surat (5), Payroll Management (6)

Total new screens: 51 (+ 14 renamed). Combined with original 101 screens = 152 screens total.


1. Kepsek → Kepala Yayasan Refactor

Why this matters architecturally

The Laravel backend separates kepala_sekolah (lembaga-scoped, reads one institution) from kepala_yayasan (yayasan-scoped, reads consolidated across all child institutions via parent_tenant_id). The original Figma conflated both under "Kepsek" persona, which would have created route-guard ambiguity in Flutter. Post-refactor, every screen name encodes its scope via suffix.

Screen rename map

Old (Kepsek)New (Kepala Yayasan)Scope suffix signals
Kepsek / Executive DashboardKepala Yayasan / Executive Dashboard (Lembaga View)drill-into one flagship
Kepsek / Laporan AkademikKepala Yayasan / Laporan Akademik Lembagasingle-lembaga
Kepsek / Laporan KeuanganKepala Yayasan / Laporan Keuangan Lembagasingle-lembaga
Kepsek / Monitoring GuruKepala Yayasan / Monitoring Guru Lembagasingle-lembaga
Kepsek / Executive Dashboard (Dark Mode)Kepala Yayasan / Executive Dashboard (Dark Mode)unchanged
Kepsek / Notifikasi CenterKepala Yayasan / Notifikasi Centerunchanged
Kepsek / DashboardKepala Yayasan / Dashboard Ringkasunchanged
Kepsek / Laporan AnalyticsKepala Yayasan / Laporan Analytics Konsolidasicross-lembaga
Kepsek / Keuangan OverviewKepala Yayasan / Keuangan Konsolidasicross-lembaga
Kepsek / ProfilKepala Yayasan / Profilunchanged
Kepsek / Yayasan DashboardKepala Yayasan / Dashboard Konsolidasiyayasan-level
Kepsek / Manajemen GuruKepala Yayasan / Manajemen Guru Yayasanyayasan-level
Kepsek / Unit PendidikanKepala Yayasan / Unit Pendidikanyayasan-level
Kepsek / Audit TrailKepala Yayasan / Audit Trail Yayasanyayasan-level

Flutter widget class rename

Old: KepsekExecutiveDashboard → New: KepalaYayasanExecutiveDashboardLembaga
Old: KepsekLaporanKeuangan → New: KepalaYayasanLaporanKeuanganLembaga
Old: KepsekYayasanDashboard → New: KepalaYayasanDashboardKonsolidasi

Route guard policy mapping

// app/router/guards.dart
const _lembagaViewRoles = ['kepala_yayasan']; // can also drill-into one lembaga
const _konsolidasiRoles = ['kepala_yayasan']; // cross-lembaga only
const _yayasanLevelRoles = ['kepala_yayasan']; // yayasan-wide auth

// Route pattern parses the suffix:
String guardFor(String screenName) {
if (screenName.endsWith('Lembaga')) return 'view-lembaga-scope';
if (screenName.endsWith('Konsolidasi')) return 'view-yayasan-consolidated';
if (screenName.endsWith('Yayasan')) return 'view-yayasan-scope';
return 'view-default';
}

2. Multi-Tenant Authentication Flow

The three new auth screens

2.1 Lembaga Onboarding (first launch)

  • Figma node: 509:2 (page 8. Shared)
  • Route: /auth/onboarding
  • Trigger: SharedPreferences.getString('kode_lembaga') returns null
  • Outcome: persists selected kode_lembaga to localStorage, navigates to /auth/login

2.2 Parent + Staff Login with Kode Lembaga field

  • Figma nodes: 9:2 (Parent, page 2. Parent) + 495:2 (Staff, page 8. Shared)
  • Route: /auth/login
  • New field ID: KodeLembagaInput clones the EmailInput style, prepends to the stack
  • API: POST /api/auth/login with payload { kode_lembaga, email, password }
  • Server behaviour: kode_lembaga resolves → tenant_id via Laravel LembagaModel::where('kode','=',$r->kode)->first()

2.3 Lembaga Switcher (multi-lembaga staff)

  • Figma node: 512:2 (page 8. Shared)
  • Route: /auth/switch-lembaga
  • Trigger: AuthResponse.lembaga_memberships.length > 1
  • Outcome: writes selectedLembagaId to in-memory state + X-Tenant header for all subsequent requests

Flutter widget catalogue (new)

// widgets/auth/kode_lembaga_field.dart
class KodeLembagaField extends StatefulWidget {
final String? prefilledCode;
final Function(String code, LembagaSuggest? suggestion) onChange;
// ...
}

// widgets/auth/lembaga_suggest_dropdown.dart
class LembagaSuggestDropdown extends StatelessWidget {
final List<LembagaSuggest> suggestions;
final LembagaSuggest? selectedOne;
// Shows 5 matching lembaga with color avatar + jenjang pill
}

// widgets/auth/lembaga_card.dart
class LembagaCard extends StatelessWidget {
final Lembaga lembaga;
final bool isDefault; // → ⭐ "Default" chip + emerald border
final String lastAccess;
final VoidCallback onTap;
}

3. Bendahara Yayasan Persona — 10 Screens

Page reference

  • Figma page: 6. Bendahara Yayasan (node 513:2)
  • Palette: Emerald primary (#2e7d32) matching brand, same as Parent

Screen inventory

#ScreenFigma IDFlutter class
1Dashboard Konsolidasi513:3BendYayasanDashboardKonsolidasi
2Neraca Konsolidasi (ISAK 35)513:89BendYayasanNeracaKonsolidasi
3Laporan Aktivitas Konsolidasi515:2BendYayasanLaporanAktivitasKonsolidasi
4Arus Kas Konsolidasi515:91BendYayasanArusKasKonsolidasi
5Perubahan Aset Neto515:180BendYayasanPerubahanAsetNeto
6Approval Payroll Queue516:2BendYayasanApprovalPayrollQueue
7Approval Payroll Detail516:77BendYayasanApprovalPayrollDetail
8Audit Trail Konsolidasi517:2BendYayasanAuditTrailKonsolidasi
9Lembaga Comparison517:94BendYayasanLembagaComparison
10Profil Bendahara Yayasan517:163BendYayasanProfil

Routes

GET /bendahara-yayasan → Dashboard
GET /bendahara-yayasan/neraca → Screen 2
GET /bendahara-yayasan/aktivitas → Screen 3
GET /bendahara-yayasan/arus-kas → Screen 4
GET /bendahara-yayasan/aset-neto → Screen 5
GET /bendahara-yayasan/approval → Screen 6
GET /bendahara-yayasan/approval/:batchId → Screen 7
GET /bendahara-yayasan/audit → Screen 8
GET /bendahara-yayasan/comparison → Screen 9
GET /bendahara-yayasan/profil → Screen 10
POST /bendahara-yayasan/approval/:id/approve → mutation
POST /bendahara-yayasan/approval/:id/reject → mutation

ISAK 35 compliance — critical terminology

Flutter text widgets must use the following exactly (other terms fail PAP 2024 audit):

  • Aset Neto (never Ekuitas)
  • Laporan Aktivitas (never Laba Rugi Komprehensif)
  • Aset Neto Tidak Terikat, Terikat Temporer, Terikat Permanen
  • Eliminasi Inter-Entitas with red E badge on line items

New widgets

// widgets/finance/konsolidasi_row.dart
class KonsolidasiRow extends StatelessWidget {
final String label;
final String value;
final bool isTotal;
final bool isEliminasi; // → red E badge trailing
final bool isIndented; // → deeper left padding for sub-items
}

// widgets/finance/aset_neto_card.dart
class AsetNetoCard extends StatelessWidget {
final String kategori; // "Tidak Terikat" | "Terikat Temporer" | "Terikat Permanen (Wakaf)"
final String saldoAwal;
final List<RollforwardItem> movements;
final String saldoAkhir;
final Color aksenWarna; // emerald / amber / purple per kategori
}

// widgets/finance/approval_batch_card.dart
class ApprovalBatchCard extends StatelessWidget {
final Lembaga lembaga; // → avatar color + initial
final String periode;
final String totalAmount;
final int staffCount;
final DateTime due;
final bool isUrgent; // < 3 days → red border + "Urgent" pill
final String makerName;
}

4. Pembina Asrama Persona — 8 Screens

Page reference

  • Figma page: 9. Pembina Asrama (node 519:2)
  • Palette: Teal #0f766e primary, darkest #134e4a (evokes malam tenang)

Screen inventory

#ScreenFigma IDFlutter class
1Dashboard Asrama519:3PembinaAsramaDashboard
2Absensi Shalat Jama'ahPembinaAsramaAbsensiShalatJamaah
3Check-in MalamPembinaAsramaCheckinMalam
4Laporan KesehatanPembinaAsramaLaporanKesehatan
5Kegiatan MalamPembinaAsramaKegiatanMalam
6PelanggaranPembinaAsramaPelanggaran
7Profil KamarPembinaAsramaProfilKamar
8Profil PembinaPembinaAsramaProfil

Data model (new tables)

-- Server-side (tenant DB)
CREATE TABLE absensi_shalat_jamaah (
id, santri_id, waktu ENUM('subuh','zuhur','ashar','magrib','isya'),
tanggal, hadir BOOL, keterangan, recorded_by, created_at
);
CREATE TABLE check_in_malam (
id, santri_id, tanggal, jam_kembali, status ENUM('hadir','izin','sakit','terlambat'),
keterangan, recorded_by
);
CREATE TABLE laporan_kesehatan (
id, santri_id, tanggal_awal, tanggal_akhir NULL, gejala TEXT,
pengobatan TEXT, urgency ENUM('ringan','sedang','berat'), monitoring_note TEXT
);

Routes

GET /pembina-asrama → Dashboard
GET /pembina-asrama/shalat → Screen 2
POST /pembina-asrama/shalat/record → batch attendance per kamar
GET /pembina-asrama/checkin → Screen 3
GET /pembina-asrama/kesehatan → Screen 4
POST /pembina-asrama/kesehatan → add sick santri
GET /pembina-asrama/kegiatan → Screen 5
GET /pembina-asrama/pelanggaran → Screen 6
GET /pembina-asrama/kamar/:kamarId → Screen 7

5. Pembimbing Hafalan Persona — 6 Screens

Page reference

  • Figma page: 10. Pembimbing Hafalan (node 520:2)
  • Palette: Royal purple #6d28d9 (reverential, Qur'anic)

Screen inventory

#ScreenFlutter class
1Dashboard HafalanPembimbingHafalanDashboard
2Daftar Santri (by juz)PembimbingHafalanDaftarSantri
3Setoran Baru (form)PembimbingHafalanSetoranBaru
4Riwayat SetoranPembimbingHafalanRiwayatSetoran
5Target MingguanPembimbingHafalanTargetMingguan
6Profil PembimbingPembimbingHafalanProfil

Data model — Tahfidz progression

CREATE TABLE setoran_hafalan (
id, santri_id, pembimbing_id, tanggal, jam,
surah_id, ayat_awal INT, ayat_akhir INT,
rating_kelancaran TINYINT (1-5),
rating_tajwid TINYINT (1-5),
rating_makhraj TINYINT (1-5),
rating_kefasihan TINYINT (1-5),
catatan TEXT, recorded_at
);
CREATE TABLE target_mingguan (
id, santri_id, pembimbing_id, minggu_ke, tahun,
deskripsi_target, progress_pct TINYINT, status ENUM('active','done','missed')
);

Widget spec — Quality rating

// widgets/hafalan/quality_rating_row.dart
class QualityRatingRow extends StatelessWidget {
final String aspectLabel; // "Kelancaran", "Tajwid", "Makharijul Huruf", "Kefasihan"
final int currentRating; // 0-5
final Function(int) onChange; // emits tap on star
final String descriptor; // "Sangat baik", "Baik sekali", "Cukup", "Perlu latihan", "Kurang"
}

// widgets/hafalan/juz_progress_badge.dart
class JuzProgressBadge extends StatelessWidget {
final int juzNumber;
final double pctComplete;
final Color aksenWarna; // purple when in progress, gold when complete
}

6. PPDB Flow — 7 Screens

Page reference

  • Figma page: 11. PPDB (Admissions) (node 521:2)
  • Palette: Emerald + Amber accent (welcoming, trusted)

Screen inventory

Public (no auth):

#ScreenRoute
1Welcome / Landing/ppdb
2Form Step 1 — Data Santri/ppdb/daftar/1
3Form Step 2 — Data Orang Tua/ppdb/daftar/2
4Form Step 3 — Review & Submit/ppdb/daftar/3
5Status Pendaftaran/ppdb/status/:noPPDB

Admin (Admin Lembaga role):

#ScreenRoute
6Dashboard PPDB/admin-lembaga/ppdb
7Detail Calon Santri/admin-lembaga/ppdb/:calonId

Architecture note

Public PPDB screens cannot use the standard app shell (bottom nav, auth chips) because the user is pre-auth. These render with a minimal brochure shell (PpdbPublicScaffold) distinct from the authenticated TenantScaffold.

// scaffolds/ppdb_public_scaffold.dart
class PpdbPublicScaffold extends StatelessWidget {
final Widget body;
final String lembagaName; // injected via kode_lembaga route param
final bool showBackButton;
// No bottom nav. Static footer with call-center info.
}

7. P2 Feature Modules — 17 Screens

7.1 Kamar / Asrama Management — 6 screens

  • Figma page: 12. P2 Feature Extensions, x=0–2300 band
  • Palette: Indigo #6366f1
  • Consumers: Pembina Asrama + Admin Lembaga (shared module)
ScreenRoute
Dashboard Kamar/kamar
Daftar Kamar/kamar/list
Assign Santri ke Kamar/kamar/assign/:kamarId
Room Detail/kamar/:kamarId
Stats & History/kamar/stats
Add Kamar/kamar/new

7.2 Data Pelanggaran + Surat — 5 screens

  • Figma page: 12. P2 Feature Extensions, x=2760–4600 band
  • Palette: Red #dc2626
  • Consumers: Pembina Asrama + Admin Lembaga + Kepala Yayasan
ScreenRoute
Dashboard Pelanggaran/pelanggaran
Catat Pelanggaran Baru/pelanggaran/new
Detail Kasus/pelanggaran/:caseId
Generator Surat/pelanggaran/surat
Rekap/pelanggaran/rekap

7.3 Payroll Management — 6 screens

  • Figma page: 12. P2 Feature Extensions, x=5060–7360 band
  • Palette: Cyan #0ea5e9
  • Consumers: Bendahara Yayasan + Admin Lembaga
ScreenRoute
Payroll Dashboard/payroll
Staff Master/payroll/staff
Compute Preview/payroll/compute
Slip Gaji Individual/payroll/slip/:staffId/:periode
Summary Report/payroll/report
Formula Settings/payroll/settings

8. Splash Screen Unification

Before

  • Parent splash: emerald gradient, "Portal Orang Tua Santri"
  • Shared splash: blue gradient, "Portal Manajemen Pesantren"

After (both)

  • Emerald gradient #43a047 → #2e7d32 → #1b5e20
  • Logo Inter Bold
  • Subtitle "Portal Manajemen Pesantren" (inclusive, role-agnostic)

Flutter implication

Only one splash_screen.dart needed. No conditional paths.


9. Global Design Token Additions

// design_tokens/palette_personas.dart
abstract class PersonaPalette {
// Existing
static const parent = Color(0xFF2E7D32); // emerald
static const staff = Color(0xFF2E7D32); // emerald (unified)

// New
static const bendaharaYayasan = Color(0xFF2E7D32); // same emerald, yayasan-scope
static const pembinaAsrama = Color(0xFF0F766E); // teal
static const pembimbingHafalan = Color(0xFF6D28D9); // royal purple

// Feature modules (cross-persona)
static const kamarMgmt = Color(0xFF6366F1); // indigo
static const pelanggaran = Color(0xFFDC2626); // red
static const payroll = Color(0xFF0EA5E9); // cyan

// Semantic (already in foundation)
static const success = Color(0xFF059669);
static const warning = Color(0xFFD97706);
static const danger = Color(0xFFDC2626);
static const info = Color(0xFF2563EB);
}

10. Implementation Checklist (Flutter)

  • Regenerate app_theme.dart from new tokens via Style Dictionary pipeline
  • Refactor KepsekDashboardKepalaYayasanDashboardKonsolidasi + rename 13 other screens
  • Implement KodeLembagaField with localStorage persistence
  • Build LembagaOnboardingScreen + auto-suggest API GET /public/lembaga/search?q=
  • Build LembagaSwitcherScreen + X-Tenant header middleware
  • Implement 5 new persona route trees
  • Implement 3 P2 feature module route trees
  • Wire Flutter accessibility labels for Arabic text in Setoran form
  • QA Dark mode variants for all 51 new screens (only 1 existing Dark variant exists — Kepala Yayasan Executive Dashboard)
  • Update assets/icons/ for 20+ new icon requirements

11. Tier C+ Addendum (Post-Review Revisions)

11.1 Kepala Sekolah / Kepala Yayasan Split (Revision)

The initial "Kepsek → Kepala Yayasan rename" was an interpretation error. Backend forensic revealed both roles exist independently:

RoleSeeded?ScopeBackend file
kepala_sekolah✅ yesSingle lembagaDynamicRolePermissionSeeder.php
kepala_yayasan❌ code-onlyYayasan consolidationJournalService.php:392 hardcoded

Resolution: Split into two separate personas with clean scope boundaries.

Page 7a — Kepala Sekolah (NEW, lembaga-scope)

#ScreenFigma IDFlutter class
1Executive Dashboard90:3KepalaSekolahExecutiveDashboard
2Laporan Akademik90:109KepalaSekolahLaporanAkademik
3Laporan Keuangan90:201KepalaSekolahLaporanKeuangan
4Monitoring Guru90:291KepalaSekolahMonitoringGuru
5Executive Dashboard (Dark Mode)109:3KepalaSekolahExecutiveDashboardDark
6Notifikasi Center109:108KepalaSekolahNotifikasiCenter
7Dashboard Ringkas306:2KepalaSekolahDashboardRingkas
8Profil306:215KepalaSekolahProfil

Routes:

GET /kepala-sekolah → Executive Dashboard (default lembaga)
GET /kepala-sekolah/akademik → Laporan Akademik
GET /kepala-sekolah/keuangan → Laporan Keuangan
GET /kepala-sekolah/guru → Monitoring Guru
GET /kepala-sekolah/notifikasi → Notifikasi Center
GET /kepala-sekolah/profil → Profil

Page 7b — Kepala Yayasan (REDUCED to 6 screens, yayasan-scope)

#ScreenFigma IDFlutter class
1Laporan Analytics Konsolidasi306:70KepalaYayasanLaporanAnalyticsKonsolidasi
2Keuangan Konsolidasi306:150KepalaYayasanKeuanganKonsolidasi
3Dashboard Konsolidasi339:2KepalaYayasanDashboardKonsolidasi
4Manajemen Guru Yayasan339:52KepalaYayasanManajemenGuruYayasan
5Unit Pendidikan339:116KepalaYayasanUnitPendidikan
6Audit Trail Yayasan339:170KepalaYayasanAuditTrailYayasan

Routes:

GET /kepala-yayasan → Dashboard Konsolidasi
GET /kepala-yayasan/analytics → Laporan Analytics Konsolidasi
GET /kepala-yayasan/keuangan → Keuangan Konsolidasi
GET /kepala-yayasan/guru → Manajemen Guru Yayasan
GET /kepala-yayasan/lembaga → Unit Pendidikan (list lembaga anak)
GET /kepala-yayasan/audit → Audit Trail Yayasan

11.2 Vector Icon Library Replacement

Problem: Before revision, 691 text-nodes across 13 pages used Unicode emoji (✉ ⚿ 🏠 🔔 etc). Emojis render inconsistently across Android OEM skins, cannot be theme-tinted, and fail accessibility labels.

Solution: Built 🔧 Icon Library page (28 shape-composed vector icons) + ran bulk sweep.

Icon factory — 28 icons from primitive shape composition:

Field: mail · lock · eye · building Navigation: chevron-right · chevron-left · arrow-right · arrow-left · caret-down Status: check · close · warning · info · alert Core: bell · home · book · money · people · settings · search · plus · calendar · phone · location · chat · download Persona-specific: bed · pill · moon · mosque · target · trophy · star · clock · file

Sweep results — 360 emoji text-nodes replaced across 12 pages:

PageReplaced
2. Parent42
3. Guru40
4. Wali Kelas39
5. Bendahara17
6. Bendahara Yayasan14
6. Admin Lembaga44
8. Kepala Sekolah12
9. Kepala Yayasan17
8. Shared15
9. Pembina Asrama32
10. Pembimbing Hafalan16
11. PPDB22
12. P2 Extensions36
1. Foundation & Components14
TOTAL360

Flutter implication: Each icon maps to a 24×24 dart widget in amal_icons.dart:

abstract class AmalIcons {
static const IconData mail = IconData(0xE001, fontFamily: 'AmalIcons');
static const IconData lock = IconData(0xE002, fontFamily: 'AmalIcons');
// ... generated from icon library page via Figma export + IconFont generator
}

Alternatively use Material Symbols with semantic matching:

Icons.mail_outline → AmalIcons.mail
Icons.lock_outline → AmalIcons.lock
Icons.visibility_outlined → AmalIcons.eye

Emojis intentionally NOT replaced (keep for emotional/cultural context):

  • (bismillah) — religious content, not icon
  • 🎉 🏆 (celebration, achievement) — emotional signaling
  • 🤒 😊 — character illustrations for health/mood
  • Any emoji embedded inside longer string (e.g., "🌙 Waktu Isya")

11.3 Bottom Nav Standardization

Audit findings: 9 persona pages, 2 distinct architectures:

  • Architecture A (flat): Guru, Kepala Sekolah — BottomNav children are alternating Frame (icon) + Text (label)
  • Architecture B (grouped): Wali Kelas, Bendahara, Admin Lembaga, Kepala Yayasan, Bendahara Yayasan, Pembina Asrama, Pembimbing Hafalan — each item is a child frame Nav-{Label} containing icon + label

Standardization applied:

  1. Renamed Bendahara Yayasan nav label "Dashboard" → Beranda (18 text nodes, 9 frame renames). Rationale: Indonesian-first consistency across all personas.
  2. Verified first-item "Beranda" consistency: 9/9 personas ✅
  3. Verified last-item "Profil" or equivalent: 7/9 personas (Admin Lembaga uses "Menu" as drawer-overflow, semantically correct)

Final nav map per persona (standardized):

PersonaBeranda23456 (Parent only)
ParentBerandaTagihanTabunganNilaiAbsensiProfil
GuruBerandaKehadiranNilaiJadwalProfil
Wali KelasBerandaKelasNilaiAbsensiProfil
BendaharaBerandaBayarJurnalLaporanProfil
Admin LembagaBerandaAkademikKeuanganSDMMenu
Kepala SekolahBerandaLaporanKeuanganGuruProfil
Kepala YayasanBerandaLaporanKeuanganGuruProfil
Bendahara YayasanBerandaLaporanApprovalAuditProfil
Pembina AsramaBerandaShalatMalamKesehatanProfil
Pembimbing HafalanBerandaSantriSetoranTargetProfil

Flutter implementation:

// shared/bottom_nav.dart
class AmalBottomNav extends StatelessWidget {
final PersonaType persona;
final int activeIndex;
// Loads per-persona items from _personaNavs map keyed by PersonaType.
// Enforces visual uniformity: 5 items (6 for Parent), 80px height,
// icon 24px, label 10px, active = persona primary color
}

Post-deploy fix (same session, 2 iterations): Initial bottom nav standardization left 15 IconDot placeholders empty (colored rectangles, not icons). First fix attempted stroke-style outline icons, but user feedback flagged inconsistency with canonical filled-style pattern used by old personas (Guru, Wali Kelas, Bendahara, Admin Lembaga, Kepala Yayasan). Canonical spec extracted via audit: 22×22 icon frame, single FILLED vector or multi-primitive composition (solid fill + white cutouts for detail), Inter 11px labels (Medium inactive / Semi Bold active), slate-500 inactive color #717078, persona-primary active color. Second fix applied: 115 icons rebuilt using multi-primitive filled-style composition. Icons mapped by label: Beranda→home (triangle roof+body), Laporan→file (rect with 3 text lines), Approval→check-circle (filled circle + white ✓), Audit→eye (filled oval + white iris + dark pupil), Profil→people (circle head + body), Shalat→mosque (dome+body+arch), Malam→moon (crescent), Kesehatan→pill (capsule 45°), Santri→people, Setoran→book (rect + spine + 6 text lines), Target→target (3 concentric rings + center). Professional design system lesson learned: When adding to existing design system, inherit the canonical pattern via audit — don't build parallel. This principle is now documented as guideline for Flutter handoff phase.

11.4 Updated Total Screen Count

Previous+Tier C+Total
101 screens (original MVP)+52 screens + 8 Kepsek split161 screens

11.5 Updated Pricing — Rp 245.000.000

Delta from Tier C (Rp 226 jt):

  • Kepala Sekolah persona separation: +Rp 12 juta (Q1=C chosen: rebuild with pure lembaga-scope content)
  • Vector icon library + 360 emoji replacements: +Rp 4 juta
  • Bottom nav audit + standardization: +Rp 3 juta

New total: Rp 245.000.000


12. Tier C++ Addendum (Professional Review Fixes)

Following a UX review audit, 6 specific quality fixes applied to bring new pages to canonical pattern compliance.

12.1 Icon Library Expansion (37 icons total, was 27)

10 new icons added: sunrise, sun, sun-cloud, sunset, moon-filled, briefcase, medical-kit, calendar-event, building-alt, star-filled. All follow multi-primitive filled-style composition matching canonical pattern.

12.2 Pembina Asrama Shalat Picker Fix

Previous state: 5 emoji (🌅 ☀️ 🌤️ 🌇 🌙) in waktu segmented picker. Replaced with 5 vector icons at 16×16 inside 40px segmented tiles. Active state (Isya) uses white moon on green tile, done states use persona primary color with ✓ checkmark.

12.3 Check-in Malam Progress Ring Fix

Critical fix: Previous "98%" progress indicator was a solid emerald disc (visual lie — showed 100% while label said 98%). Replaced with proper arc-based ring using ELLIPSE.arcData with innerRadius: 0.78 for donut effect. Arc spans -π/2 to -π/2 + 2π × 0.98 = 352.8°, leaving a visible 2% gap at 12 o'clock that matches the true completion state.

Flutter implementation hint:

SizedBox(
width: 64, height: 64,
child: CircularProgressIndicator(
value: 0.98,
strokeWidth: 7,
backgroundColor: Color(0xFFCCFBF1),
valueColor: AlwaysStoppedAnimation(Color(0xFF0F766E)),
),
)

12.4 Status Pill Semantic Harmonization

Previous: 5 different pill colors (pink/orange/pink/orange/purple) carried no semantic meaning.

New 3-color semantic scale:

  • Red (#fee2e2 / #b91c1c) — critical/unreachable: "Belum ada info"
  • Amber (#fef3c7 / #92400e) — late/past-deadline: "Terlambat dari izin", "Izin pulang kemarin"
  • Slate (#f1f5f9 / #475569) — scheduled absence: "Izin ke rumah sakit", "Sakit di UKS"

Row borders also color-coded with severity-border variants (#fca5a5, #fcd34d, #cbd5e1).

12.5 Kegiatan Icon Semantic Disambiguation

Previous: "Kegiatan" tile used generic file icon — identical to "Laporan" in bottom nav (semantic collision). New: uses calendar-event icon (calendar with event dot indicator), visually distinct from plain file.

12.6 Per-Kamar Severity Scaling

Previous: All missing-santri kamar (-1, -2) used uniform amber color.

New progressive severity scale:

  • Green (penuh): attendance = capacity (12/12)
  • Amber (-1): 1 santri missing
  • Red (-2, -3): 2+ santri missing — urgent attention

Left strip + count pill + number text all color-coordinated per severity level. Enables 1-second visual triage.

12.7 Professional UX Principles Documented

For future additions to the design system:

  1. Inherit pattern via audit — don't build parallel. Extract canonical spec from existing components before adding new ones.
  2. Color should carry meaning, not decoration — palette size should match data semantic dimensions.
  3. Progressive disclosure through visual weight — severity gradients (green → amber → red) enable subconscious triage.
  4. Mixed-string emojis are fine for Figma — restructuring to icon+text in Figma is high-cost; Flutter implementation handles this naturally via Row(children: [Icon, Text]).
  5. Use native primitives where possibleELLIPSE.arcData beats manual SVG path calculation for progress indicators.

12.8 Updated Pricing — Rp 252.000.000

Delta from Tier C+ (Rp 245 jt):

  • Icon library expansion (10 new icons): +Rp 1 juta (absorbed into Design line)
  • P1 fixes (Shalat + Progress Ring + standalone emoji critical): +Rp 3 juta
  • P2 fixes (Status pills + Kegiatan + severity): +Rp 2 juta
  • P3 fixes (Icon library docs + semantic guidelines): +Rp 1 juta

New total: Rp 252.000.000


13. Material Symbols Mapping (Flutter Implementation Reference)

Critical handoff guidance: The custom shape-composed vector icons in Figma are communication artifacts for design review. For production Flutter implementation, use Material Symbols Rounded (Google's official icon library, built into Flutter via Icons.xxx). Reasons:

  1. Pixel-perfect at any size — vector font format renders crisply 16px through 64px
  2. Battle-tested — used by Google Workspace, billions of installs
  3. Theme-tintable via single color property — no manual stroke/fill management
  4. Built into FlutterIcons.wb_sunny etc, no dependency to add
  5. Solves the "16px composition limit" that custom shape icons hit in Figma

13.1 Bottom Navigation Icons

PersonaLabelFigma IconMaterial SymbolFlutter Code
AllBerandahome (multi-primitive filled)Icons.homeIcon(Icons.home, color: PersonaColor.primary)
Bendahara YayasanLaporanfileIcons.descriptionIcon(Icons.description)
Bendahara YayasanApprovalcheck-circleIcons.check_circleIcon(Icons.check_circle)
Bendahara YayasanAuditeyeIcons.visibilityIcon(Icons.visibility)
AllProfilpeopleIcons.personIcon(Icons.person)
Pembina AsramaShalatmosqueIcons.mosqueIcon(Icons.mosque)
Pembina AsramaMalammoon-filledIcons.bedtimeIcon(Icons.bedtime)
Pembina AsramaKesehatanpillIcons.medical_servicesIcon(Icons.medical_services)
Pembimbing HafalanSantripeopleIcons.groupsIcon(Icons.groups)
Pembimbing HafalanSetoranbookIcons.menu_bookIcon(Icons.menu_book)
Pembimbing HafalanTargettargetIcons.adjustIcon(Icons.adjust)

13.2 Pembina Asrama — Shalat Picker (5 Waktu)

The 16×16 weather icons in Figma reach pixel-density limits. Flutter implementation should use Material Symbols at 20×20 minimum:

WaktuFigma IconMaterial SymbolFlutter Code
Subuh (Fajr)crescent + starIcons.brightness_4 (alternative: Icons.dark_mode)Icon(Icons.brightness_4, size: 20)
Zuhurfull sun + raysIcons.wb_sunnyIcon(Icons.wb_sunny, size: 20)
Asharsun + horizonIcons.brightness_5Icon(Icons.brightness_5, size: 20)
Magribsun settingIcons.brightness_6 (alternative: Icons.brightness_3)Icon(Icons.brightness_6, size: 20)
Isyafilled crescentIcons.bedtime (alternative: Icons.nights_stay)Icon(Icons.bedtime, size: 20)

Alternative — Phosphor Icons (more religiously authentic):

# pubspec.yaml
dependencies:
phosphor_flutter: ^2.0.0
import 'package:phosphor_flutter/phosphor_flutter.dart';

final shalatIcons = {
'Subuh': PhosphorIcons.sunHorizon(PhosphorIconsStyle.fill),
'Zuhur': PhosphorIcons.sun(PhosphorIconsStyle.fill),
'Ashar': PhosphorIcons.sunDim(PhosphorIconsStyle.fill),
'Magrib': PhosphorIcons.sunset(PhosphorIconsStyle.fill),
'Isya': PhosphorIcons.moonStars(PhosphorIconsStyle.fill),
};

Phosphor has dedicated sunrise/sunset/moonStars icons — semantically more accurate than Material Symbols' generic brightness icons.

13.3 Akses Cepat Tile Icons

TileFigma IconMaterial SymbolPhosphor Alternative
Shalatmosque (custom)Icons.mosquePhosphorIcons.mosque()
Check-inhomeIcons.cottagePhosphorIcons.house()
Kesehatanpill capsuleIcons.medical_servicesPhosphorIcons.firstAidKit()
Kegiatancalendar-eventIcons.eventPhosphorIcons.calendarCheck()
KamarbedIcons.bedPhosphorIcons.bed()
Pelanggaranwarning triangleIcons.reportPhosphorIcons.warning()
Profil (settings)gearIcons.settingsPhosphorIcons.gear()
NotifikasibellIcons.notificationsPhosphorIcons.bell()

13.4 Form Field Icons (Input adornments)

FieldFigma IconMaterial SymbolPosition
EmailmailIcons.mail_outlineprefixIcon
PasswordlockIcons.lock_outlineprefixIcon
Show/hide passwordeyeIcons.visibility_outlined / Icons.visibility_off_outlinedsuffixIcon
Kode LembagabuildingIcons.business_outlinedprefixIcon
NIKid-cardIcons.credit_cardprefixIcon
Tanggal LahircakeIcons.cake_outlinedprefixIcon
TeleponphoneIcons.phone_outlinedprefixIcon
AlamatlocationIcons.location_on_outlinedprefixIcon

13.5 Status & Severity Icons

StateFigma IconMaterial Symbol
Success / Donecheck (filled circle)Icons.check_circle
Warningwarning (triangle)Icons.warning
Error / CriticalalertIcons.error
InfoinfoIcons.info
Neutral / Help? badgeIcons.help_outline

13.6 Action Icons

ActionFigma IconMaterial Symbol
Add newplusIcons.add
SearchsearchIcons.search
Filter(chip dropdown)Icons.filter_list
Sort(chip dropdown)Icons.sort
DownloaddownloadIcons.download
Upload(mirror download)Icons.upload
Edit(pencil)Icons.edit_outlined
DeletecloseIcons.delete_outline
Share(paper plane)Icons.share
Refresh(rotate)Icons.refresh

13.7 Implementation Strategy

Recommended Flutter setup — abstract icon access via shared widget:

// lib/shared/icons/amal_icons.dart
abstract class AmalIcons {
// Bottom nav
static const IconData beranda = Icons.home;
static const IconData laporan = Icons.description;
static const IconData approval = Icons.check_circle;
static const IconData audit = Icons.visibility;
static const IconData profil = Icons.person;

// Shalat (use Phosphor for semantic accuracy if desired)
static const IconData subuh = Icons.brightness_4;
static const IconData zuhur = Icons.wb_sunny;
static const IconData ashar = Icons.brightness_5;
static const IconData magrib = Icons.brightness_6;
static const IconData isya = Icons.bedtime;

// Akses Cepat (Pembina Asrama)
static const IconData shalat = Icons.mosque;
static const IconData checkIn = Icons.cottage;
static const IconData kesehatan = Icons.medical_services;
static const IconData kegiatan = Icons.event;
static const IconData kamar = Icons.bed;
static const IconData pelanggaran = Icons.report;

// Form fields
static const IconData email = Icons.mail_outline;
static const IconData password = Icons.lock_outline;
static const IconData kodeLembaga = Icons.business_outlined;
}

Usage anywhere:

Icon(AmalIcons.shalat, size: 22, color: PersonaPalette.pembinaAsrama)

This pattern means every screen uses the same icon constant. To swap from Material Symbols to Phosphor (or any other library), only update AmalIcons — all 161 screens benefit automatically.

13.7.1 Font Stack — Multi-Script Support (Indonesian + Arabic)

Critical: Inter font does NOT include Arabic glyphs (or Quranic ligatures like ﷽ U+FDFD bismillah). Using Inter for Arabic text causes:

  • Wrong glyph fallback (renders via system font with incorrect metrics)
  • Width calculation broken (e.g., bismillah glyph reports as 203px wide instead of 36px)
  • Layout overflow (text extends outside its container)

Required font stack for Amal app:

# pubspec.yaml
fonts:
- family: Inter
fonts:
- asset: assets/fonts/Inter-Regular.ttf
- asset: assets/fonts/Inter-Medium.ttf
weight: 500
- asset: assets/fonts/Inter-SemiBold.ttf
weight: 600
- asset: assets/fonts/Inter-Bold.ttf
weight: 700
- family: Amiri
fonts:
- asset: assets/fonts/Amiri-Regular.ttf
- asset: assets/fonts/Amiri-Bold.ttf
weight: 700

Usage pattern — abstract via TextStyle constants:

// lib/shared/theme/typography.dart
abstract class AmalText {
static const TextStyle title = TextStyle(fontFamily: 'Inter', fontSize: 28, fontWeight: FontWeight.bold);
static const TextStyle body = TextStyle(fontFamily: 'Inter', fontSize: 14);
// Arabic-specific
static const TextStyle arabicHeader = TextStyle(fontFamily: 'Amiri', fontSize: 24);
static const TextStyle arabicBody = TextStyle(fontFamily: 'Amiri', fontSize: 18);
}

// Usage:
Text('﷽', style: AmalText.arabicHeader.copyWith(color: Color(0xFFE9D5FF)))
Text('الْكَهْف', style: AmalText.arabicBody)

Where Arabic text appears in app:

  • Pembimbing Hafalan Dashboard hero — "بِسْمِ اللهِ" bismillah accent (compact form)
  • Pembimbing Hafalan Setoran Baru — surah name in Arabic alongside transliteration
  • Direction C (Warm Islamic) mockups — calligraphy headers (deferred from Tier C+)
  • Any future quote/dua/ayat content

⚠️ Important — Avoid U+FDFD bismillah ligature:

The Unicode character (U+FDFD "Arabic Ligature Sallallahou Alayhe Wasallam") is an ornamental banner glyph designed for classical Quranic typography. In Amiri Bold at 28px, it renders 321px wide — far too wide for modern UI accent placement. Always use the compact text form instead:

❌ Avoid✅ UseReason
(U+FDFD)بِسْمِ اللهِCompact form fits as UI accent (38px vs 321px wide)
Single ligature charMulti-char textModern Islamic apps (Muslim Pro, Athan) all use compact form
// ❌ Wrong - overflows in mobile UI
Text('﷽', style: TextStyle(fontFamily: 'Amiri', fontSize: 28))

// ✅ Right - compact accent
Text('بِسْمِ اللهِ', style: TextStyle(fontFamily: 'Amiri', fontSize: 16, fontWeight: FontWeight.bold))

13.8 Why This Matters

  1. Saves design budget — no need to perfect 37 custom icons in Figma
  2. Production-grade quality — Material Symbols are pixel-perfect at any size
  3. Accessibility built-in — Material Symbols have semantic labels for screen readers
  4. Theme-tintable — single color property, works with light/dark mode
  5. Future-proof — adding new persona/feature = pick from existing library, not commission new SVG

Custom Figma icons remain valuable as design communication — they convey intent, brand feel, and structural pattern. But for production rendering, Material Symbols is the right tool.


14. Production Cleanup — Duplicate Frame Removal

During post-Tier C audit, discovered staff self-attendance screens (built in earlier sessions) had massive frame duplication:

ScreenOriginal childrenDuplicate framesCleaned children
Guru / Presensi Hari Ini624022
Guru / Riwayat Presensi653926
Guru / Pengajuan Izin472720
WK / Presensi Hari Ini472918
WK / Riwayat Presensi442024
WK / Pengajuan Izin321517
TOTAL297170127

Root cause: Frame duplication accident during incremental builds — likely Cmd+D (duplicate) actions that were never undone, accumulated layer-by-layer at exact same coordinates.

Detection script (Plugin API):

const seen = new Map();
const dupes = [];
for (const child of screen.children) {
const sig = `${child.name}|${Math.round(child.x)}|${Math.round(child.y)}|${Math.round(child.width)}`;
if (seen.has(sig)) dupes.push(child);
else seen.set(sig, child);
}
for (const d of dupes) d.remove();

Concurrent fix — Invisible CTA buttons: 4 button frames had #ffffff background with white text → invisible. Detected via findAll(name matches Submit|Clock In|Kirim|Simpan|Daftar) + check fill type. Applied emerald gradient + white text + shadow.

Flutter implication: Production code must enforce one-of-each-element discipline. Use widget keys (Key('clock-in-button')) so React-style reconciliation prevents accidental duplicates. Never use Stack to compose primary CTAs — use Column so a duplicate can't visually overlap and hide its sibling.


15. P5 & PAUD Module — Kurikulum Merdeka Mobile (Tier C+++++)

Backend Project Amal sudah implement penuh P5 (Proyek Penguatan Profil Pelajar Pancasila) sesuai Permendikbudristek No. 13 Tahun 2025. Mobile module dibangun sebagai cross-cutting feature yang mounted ke 3 personas (Guru formal, Wali Kelas, Parent) dengan conditional rendering by jenjang.

15.1 Multi-Jenjang Scope Matrix

Scope rules — yang menentukan UI yang ditampilkan:

JenjangCurriculumShow P5?Mobile Screens Used
TK (Taman Kanak-Kanak)PAUD/Kurikulum Merdeka PAUDPAUD STPPA screens (alternative)
MI (Madrasah Ibtidaiyah)Kurikulum MerdekaP5 Fase A/B/C screens
MTs (Madrasah Tsanawiyah)Kurikulum MerdekaP5 Fase D screens
MA (Madrasah Aliyah)Kurikulum MerdekaP5 Fase E/F screens
Diniyah (religious-only)Kurikulum DiniyahSkip P5 entirely (use Pembimbing Hafalan)

15.2 Page Reference (RESTRUCTURED — v1.7)

Architectural decision: P5/PAUD screens awalnya di module page 15. P5 & PAUD, tapi setelah review direstrukturisasi ke persona pages mereka karena:

  1. Setiap persona punya distinct workflow & UI (input vs view, formal vs read-only)
  2. Flutter dev navigates persona-by-persona, bukan feature-by-feature
  3. P5 bukan "shared entity" seperti P2 modules (Kamar/Pelanggaran/Payroll) — lebih ke "persona-specific workflow yang share data"

New location of 9 screens:

  • Page 2. Parent (Orang Tua): 3 screens (P5 Progress Anak [Formal], PAUD Perkembangan Anak [TK], Rapor P5 [Formal])
  • Page 3. Guru (Teacher): 4 screens (P5 Project List, Quick Assessment, Photo Evidence, Progress Siswa) — semua [Formal]
  • Page 4. Wali Kelas: 2 screens (P5 Aggregate Kelas [Formal], PAUD Perkembangan [TK])

Page 15. P5 & PAUD deleted setelah restructure.

Naming convention: [Persona] / [Formal|TK] [Feature]

  • Guru / [Formal] P5 Project List
  • WK / [TK] PAUD Perkembangan
  • Parent / [Formal] Rapor P5

Visual jenjang badge in HeaderBand top-right per screen:

  • "MI · MTs · MA" badge for [Formal] screens
  • "TK (PAUD)" badge for [TK] screens

Diniyah scope note added on page 12. Pembimbing Hafalan: "Santri Diniyah-only TIDAK punya P5 — pakai hafalan/kitab tracking via persona ini."

  • Palette: Amber primary #f59e0b (Kemendikbud Merdeka official); Pink #ec4899 for PAUD distinction

15.3 Screen Inventory (9 total)

#ScreenFigma IDFlutter ClassPersona
1P5 Project List586:3P5ProjectListGuru formal
2P5 Quick Assessment586:60P5QuickAssessmentGuru formal
3P5 Photo Evidence586:138P5PhotoEvidenceGuru formal
4P5 Progress Siswa (radar)586:164P5ProgressSiswaGuru/WK
5P5 Aggregate Kelas586:204P5AggregateKelasWali Kelas formal
6PAUD Perkembangan TK587:2PAUDPerkembanganTKWali Kelas TK
7Parent P5 Progress587:69P5ParentViewOrang Tua (anak MI/MTs/MA)
8Parent PAUD STPPA587:140PAUDParentViewOrang Tua (anak TK)
9P5 Rapor Section587:189P5RaporSectionAll personas

15.4 6 Dimensi Pancasila — Color Mapping

DimensiColorHex
Beriman & BertaqwaEmerald#059669
Berkebinekaan GlobalBlue#2563eb
Bergotong RoyongOrange#ea580c
MandiriPurple#7c3aed
Bernalar KritisIndigo#4f46e5
KreatifPink#db2777

15.5 Routes

GET /p5/projects → Project List (filtered by guru.formal_assignments)
GET /p5/projects/:id/assess → Quick Assessment
POST /p5/projects/:id/assessments → Submit assessment
POST /p5/projects/:id/photos → Upload photo evidence
GET /p5/students/:id/progress → Progress Siswa with radar
GET /p5/classes/:id/aggregate → Aggregate Kelas (only formal MI/MTs/MA)
GET /paud/classes/:id/perkembangan → PAUD STPPA alternative for TK
GET /parent/p5/:childId → Parent view P5 (child age 7-18)
GET /parent/paud/:childId → Parent view PAUD (child age 4-6)
GET /rapor/:studentId/p5/:semester → Rapor P5 section

15.6 Conditional Rendering Logic (Flutter)

// In persona dashboard
if (guru.assignments.any((a) => ['MI','MTs','MA'].contains(a.jenjang))) {
showMenuItem(P5MenuItem());
}

// In parent dashboard (per-child tab)
for (final child in parent.children) {
switch (child.enrolledJenjang) {
case 'TK':
showTab(PAUDProgressTab(child));
case 'MI': case 'MTs': case 'MA':
showTab(P5ProgressTab(child));
// Diniyah-only: no curriculum tab
}
}

// Wali Kelas dashboard
if (waliKelas.kelas.jenjang == 'TK') {
return PAUDPerkembanganScreen();
} else {
return P5AggregateScreen();
}

15.7 Synergy with Pembina Asrama (Future Enhancement, Tier 3)

Activities tracked di Pembina Asrama persona (shalat jama'ah, kegiatan malam, pelanggaran) bisa otomatis menjadi bukti P5 untuk Guru formal:

Pembina ActivityP5 Dimensi Evidence
Shalat berjama'ah hadirBeriman & Bertaqwa
Kerja bakti asramaBergotong Royong
Kegiatan malam mandiriMandiri
Diskusi kitab kuningBernalar Kritis

Cross-module data sharing — deferred to Tier 3 (post-MVP).

15.8 Updated Pricing — Rp 266.000.000

Delta from previous (Rp 252 jt):

  • 8 P5 core screens: +Rp 12 jt
  • 2 PAUD alternative screens (TK): +Rp 1.5 jt
  • Cross-module integration architecture: +Rp 0.5 jt

New total: Rp 266.000.000


16. Selfie Attendance + ML Kit Liveness (Post-Tier-C Addition)

16.1 Business Justification

Original staff self-attendance design relied only on GPS radius verification. Discovered weakness: staff bisa "titip absen" — beri HP ke teman untuk clock-in dari campus while actual physical presence elsewhere.

Selfie + liveness detection eliminates this fraud vector:

  • Photo evidence of physical presence (not just device location)
  • ML Kit liveness check prevents photo-of-photo attacks
  • Timestamp + GPS coordinates embedded in photo metadata
  • Face match score recorded untuk audit trail

Industry precedent: Gadjian, Talenta, BPJS Mobile, Gojek Driver — semua implement pattern ini untuk anti-fraud.

16.2 Screen Changes

Updated screens (2):

  • Guru / Presensi Hari Ini — added selfie info banner between GPS verified dan Clock In button
  • WK / Presensi Hari Ini — same update (mirror)

New screens (4):

  • Guru / Selfie Camera (node 622:12)
  • Guru / Clock In Success (node 622:34)
  • WK / Selfie Camera (node 622:73)
  • WK / Clock In Success (node 622:95)

Archived screens (2):

  • [DEPRECATED] Guru / QR Scanner (moved to Archive page) — legacy QR-based attendance replaced
  • [DEPRECATED] Guru / Kehadiran QR Scanner — same, deprecated design

16.3 Selfie Camera Screen Anatomy

Dark theme (#0f172a) full-screen camera UX:
┌─────────────────────────────────┐
│ ✕ Selfie Kehadiran│ Top bar
│ │
│ ┌─────────────────────────────┐ │
│ │ ML Kit Active [chip] │ │
│ │ │ │
│ │ ╭────────────╮ ✓Terdet │ │ Viewport w/ face guide
│ │ ╱ ╲ │ │
│ │ │ │ │ │
│ │ ╲ ╱ │ │
│ │ ╰────────────╯ │ │
│ │ │ │
│ │ 🙂 Tersenyum... │ │ Liveness instruction
│ │ ━━━━━━━━━━━━━━ 60% │ │ Progress bar
│ └─────────────────────────────┘ │
│ │
│ Posisikan wajah... │ Guidance text
│ │
│ ( ◎ capture ) │ Big capture button
│ Batalkan │
└─────────────────────────────────┘

16.4 Clock In Success Screen Anatomy

Light emerald theme (#ecfdf5) celebration:
┌─────────────────────────────────┐
│ │
│ ╭─────╮ │
│ ╱ ✓ ╲ │ Big check hero
│ ╲ ╱ │
│ ╰─────╯ │
│ │
│ Clock In Berhasil! │ Heading
│ Senin, 7 Apr · 07:23 WIB │
│ [✓ Tepat Waktu] │ Status pill
│ │
│ ┌───────────────────────────┐ │
│ │ Detail Kehadiran 😊✓ │ │ Detail card
│ │ 📍 Lokasi MTs Al-Hikmah │ │ with selfie
│ │ 📷 Selfie Terverifikasi │ │ thumb +
│ │ 🕐 Shift 06:30-15:00 │ │ verified
│ └───────────────────────────┘ │
│ │
│ [Kembali ke Beranda] (primary)│ CTAs
│ Lihat Riwayat Presensi → │
│ │
└─────────────────────────────────┘

16.5 Flutter Implementation

Dependencies to add:

dependencies:
camera: ^0.10.5
google_mlkit_face_detection: ^0.10.0
image_picker: ^1.0.4
path_provider: ^2.1.1
http: ^1.1.0
geolocator: ^10.1.0

Flow code outline:

class SelfieAttendanceFlow {
Future<void> clockIn() async {
// 1. Validate GPS (radius from school)
final position = await Geolocator.getCurrentPosition();
final distance = Geolocator.distanceBetween(
position.latitude, position.longitude,
school.latitude, school.longitude,
);
if (distance > 50) throw OutOfRadiusException();

// 2. Open Selfie Camera screen
final selfie = await Navigator.push<File>(
context, MaterialPageRoute(builder: (_) => SelfieCameraScreen()),
);
if (selfie == null) return;

// 3. Run ML Kit face detection + liveness
final faceDetector = FaceDetector(
options: FaceDetectorOptions(
enableLandmarks: true,
enableClassification: true,
enableTracking: true,
enableContours: true,
performanceMode: FaceDetectorMode.accurate,
),
);
final inputImage = InputImage.fromFile(selfie);
final faces = await faceDetector.processImage(inputImage);

if (faces.isEmpty) throw NoFaceDetectedException();
if (faces.first.smilingProbability! < 0.5) throw LivenessFailException();

// 4. Upload selfie + metadata
final response = await api.post('/api/attendance/selfie-clock-in', {
'selfie_base64': base64Encode(selfie.readAsBytesSync()),
'latitude': position.latitude,
'longitude': position.longitude,
'liveness_score': faces.first.smilingProbability,
'timestamp': DateTime.now().toIso8601String(),
});

// 5. Navigate to Success screen
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (_) => ClockInSuccessScreen(response)),
);
}
}

16.6 Backend Endpoint

New endpoint: POST /api/v1/attendance/selfie-clock-in

Request payload:

{
"selfie_base64": "...",
"latitude": -7.2575,
"longitude": 112.7521,
"liveness_score": 0.87,
"timestamp": "2025-04-07T07:23:00+07:00"
}

Backend validation:

  • Decode base64, save to S3/object storage
  • Verify GPS within school radius (reject if > threshold)
  • Face match against reference photo (via compreface/face-api/AWS Rekognition)
  • Liveness score threshold (reject if < 0.5)
  • Record attendance with selfie_url + verification metadata
  • Return status + attendance_id

Response:

{
"status": "success",
"attendance_id": "att_abc123",
"clock_in_time": "07:23:00",
"is_on_time": true,
"selfie_url": "https://cdn.amal.sch.id/selfies/2025-04-07/guru-123.jpg",
"face_match_score": 0.98,
"liveness_pass": true
}

16.7 Security & Fraud Prevention

Anti-fraud measures:

  1. Liveness detection — ML Kit face classification (smile/blink) prevents static photo spoofing
  2. GPS validation — backend re-verify radius (don't trust client-submitted lat/long)
  3. Face matching — server-side compare with stored reference photo (onboarding baseline)
  4. Rate limiting — max 5 clock-in attempts/hour to prevent brute force
  5. Audit log — all attendance records with selfie URL + metadata preserved

Privacy compliance:

  • Selfie stored encrypted at rest (AES-256)
  • Auto-purge after 12 months (retention policy)
  • GDPR/UU PDP equivalent: user request delete → all selfies deleted
  • Access: admin yayasan + owner student only, not public

16.8 Pricing Impact

Added to Tier C scope:

  • +Rp 22 juta (Selfie Camera + ML Kit + Clock In Success screens + backend + anti-fraud logic)
  • New total: Rp 342 juta (was Rp 320 jt)

Face enrollment (initial onboarding) — Tier 3 (post-MVP):

  • Admin Lembaga captures staff reference photo during hiring onboarding
  • Reference photo becomes face-match baseline
  • Separate Admin screen: "Staff Face Enrollment"

Multi-factor fallback:

  • If selfie fails 3x → fallback ke manual review (Kepala Sekolah approve via notification)
  • Avoids locking out staff due to camera/lighting issues

Both features deferred to Phase 2 of project.


17. Multi-Role Support (Post-Selfie Addition)

17.1 Business Justification

Reality pesantren: 40-60% staff punya 2-4 role concurrent (Ustadz senior = Guru Mapel + Wali Kelas, Kepala Sekolah = also Guru reduced load, etc). Proper multi-role support critical, not nice-to-have.

17.2 Architecture — 2 Patterns Based on Data Overlap

Pattern A — Unified Dashboard (roles dengan HIGH data overlap):

  • Guru + Wali Kelas (same kelas context)
  • Guru + Pembimbing Hafalan (same santri pool)
  • Kepala Sekolah + Guru (academic + teaching same lembaga)

Pattern B — Role Switcher (roles dengan LOW/ZERO data overlap):

  • Guru + Bendahara (academic vs financial)
  • Admin + Bendahara (zero workflow overlap)
  • Kepala Yayasan + Kepala Sekolah (different scope)

17.3 Implementation Details

Unified Dashboard (Guru+WK — node 64:3)

Rebuilt screen dengan 2 sections:

  • 📚 MAPEL SAYA (top section): Jadwal mengajar, Quick Actions (Input Nilai, Absensi Mapel, Materi Ajar)
  • 🎯 KELAS SAYA (bottom section): Existing WK content preserved (KPI, Kehadiran, Tidak Hadir, Ranking)
  • Role Chip top-right: "Guru + Wali Kelas ▾" (tap-able for switch)

Timing-based UX: Pagi-siang = top priority (Guru context), Sore-malam = bottom priority (WK context).

Role Chip Pattern

Applied to 6 dashboards:

  • Guru Dashboard: "Guru · IPA ▾"
  • WK Dashboard: "Wali Kelas · VII-A ▾"
  • Bendahara Dashboard: "Bendahara · Lembaga ▾"
  • Admin Lembaga Dashboard: "Admin · Al-Hikmah ▾"
  • Bendahara Yayasan Dashboard: "Bendahara Yayasan ▾"
  • Kepala Yayasan Dashboard: "Kepala Yayasan ▾"

Visual spec:

  • Size: 150×28px
  • Radius: 14px (pill shape)
  • Position: top-right of header (inside HeaderBand or AppBar)
  • On gradient header: white translucent (22% opacity)
  • On white AppBar: emerald-50 bg + emerald-300 border + emerald-700 text
  • Dropdown caret (▾) signals tap-able
  • Conditional display: Only visible if user.roles.length > 1

Ganti Role Menu (7 Profile Screens)

Added prominent menu item di profile screens:

  • Guru / Profil
  • WK / Profil
  • Bendahara / Profil
  • Admin / Profil Menu
  • BendYayasan / Profil
  • PembinaAsrama / Profil
  • PembimbingHafalan / Profil

Visual spec:

  • Card 380×72px with emerald-50 bg + emerald-300 border
  • Icon chip emerald 44×44 dengan swap-arrows vector (white strokes)
  • Title: "Ganti Role" bold emerald-dark
  • Subtitle: "Berpindah peran aktif (Guru / Wali Kelas / Bendahara)"
  • Chevron right
  • Placed above standard profile sections (Akun, Preferensi, Lainnya)

17.4 Flutter Implementation Pattern

// lib/core/auth/role_manager.dart
class RoleManager {
final User user;
Role _activeRole;

bool shouldShowSwitcher() {
final roles = user.roles;
if (roles.length <= 1) return false;

// Unified pattern for compatible combinations
final unifiedCombos = [
{'guru', 'wali_kelas'},
{'guru', 'pembimbing_hafalan'},
{'wali_kelas', 'pembina_asrama'},
{'kepala_sekolah', 'guru'},
];

final roleSet = Set.from(roles);
for (final combo in unifiedCombos) {
if (roleSet.containsAll(combo) && roleSet.length == combo.length) {
return false; // Unified dashboard — skip switcher
}
}
return true; // Role switcher required
}

Widget dashboardForUser(User user) {
if (shouldShowSwitcher()) {
return RoleSwitcherScreen(user.roles);
}

if (user.hasRoles(['guru', 'wali_kelas'])) {
return GuruWKUnifiedDashboard();
}
if (user.hasRoles(['kepala_sekolah', 'guru'])) {
return KepalaSekolahReducedLoadDashboard();
}

return RoleSpecificDashboard(user.primaryRole);
}
}

// widgets/role_chip.dart
class RoleChip extends StatelessWidget {
final User user;

Widget build(BuildContext context) {
if (user.roles.length <= 1) return SizedBox.shrink();
return GestureDetector(
onTap: () => Navigator.pushNamed(context, '/role-switcher'),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: AppColors.emeraldTint.withOpacity(0.22),
borderRadius: BorderRadius.circular(14),
),
child: Row(children: [
Text(user.activeRoleDisplay),
Icon(Icons.arrow_drop_down, size: 12),
]),
),
);
}
}

17.5 Notification Context Filtering

Added filter tabs pattern di Notifikasi Center (Kepala Yayasan as reference):

  • "Semua (24)" — all notifications
  • "Konsolidasi" — yayasan-scope only
  • "Lembaga" — single lembaga scope only

Backend: each notification tagged dengan role_context: 'guru'|'wali_kelas'|'bendahara'|etc. Mobile filter by role_context == activeRole OR role_context IN unifiedCompatibleRoles.

17.6 Role Switcher Icon Upgrade

Replaced emoji icons dengan proper vector icons di 3 role cards:

  • Teacher icon (filled person silhouette) → emerald for Guru Mapel
  • Clipboard icon → blue for Wali Kelas
  • Briefcase icon → amber for Anggota Komite Keuangan

Color-coded per role = instant visual recognition.

17.7 Pricing Impact

Added to Tier C scope:

  • +Rp 12 juta total:
    • Unified Dashboard rebuild: Rp 4 jt
    • 7 Profile screens Ganti Role menu: Rp 2 jt
    • Role chip di 6 dashboards: Rp 2 jt
    • Role Switcher polish (icons + logic): Rp 1 jt
    • Notification context filter: Rp 2 jt
    • Multi-role testing QA: Rp 1 jt

New total: Rp 354 juta (was Rp 342 jt)


18. Jenjang-Specific Page Separation (TK + Diniyah)

18.1 Context

Pesantren Indonesia umumnya multi-jenjang: TK (PAUD), MI/MTs/MA (Formal Kurikulum Merdeka), Diniyah (kitab kuning + hafalan). Content differences signifikan (40-60% unique per jenjang) sehingga separation pages di Figma justified untuk design clarity.

18.2 Page Restructure

Before:

  • Page 3: "Guru (Teacher)" — generic
  • Page 4: "Wali Kelas" — generic

After:

  • Page 3: "Guru (Formal)" — MI/MTs/MA Kurikulum Merdeka content
  • Page 4: "Wali Kelas (Formal)" — MI/MTs/MA homeroom content
  • NEW Page 3a: "Guru + WK (TK / PAUD)" — 6 screens pink palette
  • NEW Page 3b: "Guru + WK (Diniyah)" — 6 screens brown/sepia palette

18.3 Page 3a — Guru + WK (TK / PAUD)

Palette: Pink (#ec4899 primary) — matches PAUD branding established earlier Screens (6):

  1. Beranda — KPI + aktivitas tema hari ini + akses cepat
  2. Input Perkembangan STPPA — 6 aspek (Nilai Agama, Fisik-Motorik, Kognitif, Bahasa, Sosial-Emosional, Seni) dengan hearts rating + level badge (BB/MB/BSH/BSB)
  3. Progress Anak Detail — hexagonal radar chart + 6 detail cards
  4. Aktivitas Hari Ini — timeline dengan status (done/active/pending)
  5. Komunikasi Wali — chat list casual tone + FAB compose
  6. Profil — stats (18 anak, 6 aspek, 3 thn, 4.9 rating)

Bottom nav: Beranda / Perkembangan / Aktivitas / Komunikasi / Profil

18.4 Page 3b — Guru + WK (Diniyah)

Palette: Brown/sepia (#78350f primary) — kitab kuning parchment, distinct dari Pembimbing Hafalan purple Accent: Emerald (Quranic highlight) + Amiri font untuk Arabic text

Screens (6):

  1. Beranda — Bismillah Arabic hero + KPI + Jadwal Hari Ini dengan waktu shalat context (Ba'da Ashar/Maghrib/Isya)
  2. Input Hafalan — Jenis tabs (Qur'an/Hadits/Kitab) + Surah picker + 4 kualitas rating (Kelancaran/Tajwid/Makharij/Tartil)
  3. Murajaah Tracker — summary 18/22 santri + filter + status per santri (done/partial/pending)
  4. Kitab Kuning Progress — 4 kitab cards (Safinah, Taqrib, Arba'in Nawawi, Aqidah al-Awam) dengan progress bar + ETA khataman
  5. Setoran Hari Ini — queue list dengan "Berikutnya" callout + 14 menunggu list
  6. Profil — stats (22 santri, 156 Juz total, 5 thn, 4.9 rating)

Bottom nav: Beranda / Hafalan / Kitab / Setoran / Profil

18.5 Rationale — Why NOT Separate Role Switcher Pages

User originally proposed 5 pages including "RS Guru+WK" dan "RS Guru+Bendahara". Declined this split karena:

  • Role Switcher adalah SAME screen di runtime (Shared 495:35) dengan content adapts berdasarkan user.roles
  • Terpisahnya page = tidak reflect runtime reality
  • Duplicate content (same screen, different card data = maintenance nightmare)

Scenarios tetap di Shared Role Switcher dengan note di Doc 12 menjelaskan variant behaviors.

18.6 Flutter Implementation Pattern

// lib/core/jenjang/jenjang_router.dart
class JenjangRouter {
static Widget routeGuruDashboard(User user) {
final jenjang = user.primaryAssignmentJenjang;

switch (jenjang) {
case Jenjang.TK:
return GuruWKTKBeranda(); // Pink palette, PAUD STPPA
case Jenjang.Diniyah:
return GuruDiniyahBeranda(); // Brown palette, Kitab + Hafalan
case Jenjang.MI:
case Jenjang.MTs:
case Jenjang.MA:
default:
return GuruFormalBeranda(); // Default Kurikulum Merdeka
}
}
}

18.7 Pricing Impact

Added to Tier C+ scope:

  • +Rp 22 juta total:
    • 6 TK screens (Pink palette, STPPA workflows): Rp 11 jt
    • 6 Diniyah screens (Brown palette, kitab kuning + hafalan): Rp 11 jt
  • Flutter conditional routing logic: absorbed into existing architecture

New total: Rp 376 juta (was Rp 354 jt)


Change Log

  • v1.0: Tier C scope addendum. Multi-tenant auth flow, Kepsek→Kepala Yayasan refactor, 5 new personas, 3 P2 modules. 152 screens total.
  • v1.1: Tier C+ revisions. Kepala Sekolah / Kepala Yayasan split. Vector icon library + 360 emoji replacements. Bottom nav standardization. 161 screens. Rp 245 juta.
  • v1.2: Tier C++ professional review fixes. Icon library expanded to 37 icons. Shalat picker + hero moon + progress ring rebuilt. Status pill semantic harmonization (5 colors → 3 semantic). Per-kamar severity scaling. Kegiatan icon disambiguation. UX principles documented. Rp 252 juta.
  • v1.3 (today): Akses Cepat canonical alignment to Parent pattern (88×84 tiles, white inner chip 22×22). Shalat picker icons rebuilt with celestial-position metaphor. Section 13 added: Material Symbols mapping table for all icon usage — Flutter dev should use Material Symbols (or Phosphor Icons) instead of custom shape-composed vectors. Custom Figma icons documented as communication artifacts; production should use icon font library.