# Coding Standard — Dutra Store (POS Grosir Flutter) Gunakan skill ini sebagai acuan setiap kali menulis, mengubah, atau meninjau kode aplikasi. Jika ada pertentangan, prioritaskan yang tercantum di sini. ## 1. Prinsip Umum ### 1.1 Clean Code - Satu function/method lakukan satu hal saja (Single Responsibility). - Nama variable/function harus deskriptif: `isPaying` bukan `flag`, `recordPayment` bukan `save`. - Jangan duplikasi logic — extract ke function/helper yang reuseable. - Komentar hanya untuk "mengapa", bukan "apa yang dilakukan". Kode harus self-explanatory untuk "apa". ### 1.2 File Organization - **Satu file satu class utama**. Exception: helper kecil yang sangat spesifik untuk screen itu. - Nama file: `snake_case.dart`. Nama class: `PascalCase`. - Urutan import: 1. `dart:` core 2. `package:` third-party 3. `package:/` internal 4. Relative import `./` atau `../` - Jangan gunakan relative import untuk file di folder berbeda; gunakan package import. ### 1.3 Naming Convention - **Class/Widget**: `PascalCase` → `KasirScreen`, `_CartBarState` - **Private class/widget**: underscore prefix → `_CartBar`, `_ReceiptRow` - **Function/method**: `camelCase` → `addToCart`, `_showReceipt` - **Variable**: `camelCase` → `isPayingRef`, `payingStart` - **Constant**: `lowercase with underscores` atau `PascalCase` sesuai konvensi project. Untuk warna di `colors.dart` pakai `lowercase_with_underscores`: `kAccent`, `kSuccess`. - **Boolean**: pakai prefix `is`, `has`, `should`, `can` → `isLoading`, `hasError`, `shouldShow` ### 1.4 Null Safety - Hindari `!` (force unwrap) kecuali benar-benar yakin tidak null. - Gunakan `late` hanya untuk field yang di-init di `initState` atau setelah async yang dijamin selesai. - Gunakan `?` (nullable) untuk parameter yang opsional. - Gunakan `??` (default value) daripada ternary cek null. - Jangan biarkan `dynamic` keculatau untuk JSON parsing yang benar-benar dinamis. ## 2. State Management (Provider) ### 2.1 AppState Structure - `AppState` extends `ChangeNotifier`. - Semua mutable state disimpan di `AppState`, bukan di widget state kecuali state lokal UI (misal `_search` TextEditingController). - Gunakan `context.watch()` untuk rebuild saat state berubah. - Gunakan `context.read()` untuk aksi tanpa rebuild (misal di `onPressed`). - Jangan panggil `notifyListeners()` di dalam getter — getter harus pure. ### 2.2 Async Operations - Semua async yang modifikasi state harus wrapped dengan try-catch. - Gunakan `_withTimeout()` untuk semua operasi Supabase agar tidak hang selamanya. - Set `notifyListeners()` di akhir operasi sukses maupun gagal agar UI tetap konsisten. - Jangan lupa handle `mounted` check sebelum `setState` atau `notifyListeners` di dalam async callback. ### 2.3 Offline Queue - Semua operasi tulis (insert/update/delete) ke Supabase harus menyimpan ke `_offlineQueue` jika gagal. - Jangan duplicate queue logic — panggil `_offlineQueue.addToQueue()` di catch block. - Saat online, `OfflineQueueService` akan otomatis retry. ## 3. Supabase & Database ### 3.1 Query Pattern - Pakai `_withTimeout()` untuk semua query Supabase. - Gunakan `.maybeSingle()` untuk query yang return 0 atau 1 row. - Gunakan `.select()` dengan kolom spesifik kecuali butuh semua kolom. - Jangan hardcode ID di query — gunakan parameter. ### 3.2 Error Handling - Tangkap `PostgrestException` untuk error spesifik Supabase. - Tangkap `TimeoutException` untuk timeout. - Tangkap `Exception` umum sebagai fallback. - Jangan expose stack trace ke user — tampilkan pesan yang ramah. ### 3.3 Transaction Safety - Untuk operasi yang butuh atomic (misal checkout + update stock), lakukan di urutan yang benar: 1. Insert sale 2. Update stock untuk setiap item 3. Reload products + sales - Jika salah satu gagal, rollback manual atau tambahkan ke offline queue. ## 4. Widget & UI Pattern ### 4.1 Widget Composition - Prefer composition over inheritance. - Ekstrak widget kecil yang berulang jadi `const` widget terpisah. - Gunakan `const` constructor untuk semua widget yang immutable. - Jangan buat `StatefulWidget` kecuali butuh state lokal. ### 4.2 StatefulWidget Best Practices - State harus minimal — simpan hanya yang benar-benar perlu berubah. - Jangan simpan objek kompleks di state tanpa dispose (misal controller, timer, animation). - Semua `FocusNode`, `AnimationController`, `Timer`, `StreamSubscription` harus di-dispose. - Gunakan `SingleTickerProviderStateMixin` untuk `AnimationController`, `TickerProviderStateMixin` untuk multiple. ### 4.3 Dialog & Bottom Sheet - Gunakan `showDialog` + `AlertDialog` untuk dialog konfirmasi/form pendek. - Gunakan `showModalBottomSheet` dengan `isScrollControlled: true` untuk form panjang atau list yang perlu scroll. - Bungkus form dengan `SingleChildScrollView` + `padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom)`. - Gunakan `StatefulBuilder` jika dialog butuh update UI internal (misal loading, toggle). - Jangan pass `context` dari dialog ke parent untuk navigasi — pass context yang benar saat showDialog. ### 4.4 List & Grid - Gunakan `ListView.builder` untuk list panjang, `ListView.separated` untuk list dengan separator. - Gunakan `GridView.builder` untuk grid panjang. - Tambahkan `shrinkWrap: true` dan `physics: NeverScrollableScrollPhysics()` jika list/grid di dalam `SingleChildScrollView`. - Jangan lupa `itemExtent` atau `prototypeItem` jika list item heightnya fixed untuk performa. ### 4.5 Text & Format - Semua angka mata uang **wajib** menggunakan `formatRupiah()`. - Jangan hardcode string angka/uang di UI. - Gunakan `TextOverflow.ellipsis` untuk teks yang bisa panjang. - Gunakan `maxLines` untuk membatasi teks di `Row`/`Column` yang bisa overflow. ## 5. Error & Edge Case Handling ### 5.1 Mounted Check - Setiap async callback yang akses `context` atau `setState` harus check `if (!mounted) return` sebelum akses. - Jangan gunakan `mounted` untuk logic bisnis — hanya untuk guard UI update. ### 5.2 Empty/Null State - Selalu handle kasus data kosong: `if (products.isEmpty)`, `if (cart.isEmpty)`. - Tampilkan empty state yang informatif, bukan cuma `Container()` atau `SizedBox.shrink()`. ### 5.3 Boundary Value - Qty minimum: 1 (jika <= 0, remove dari cart). - Harga minimum: 0 (jika <= 0, tampilkan error atau disable button). - Stok: clamp ke `max(0, stok - qty)` saat checkout. - Diskon: clamp ke `min(diskon, subtotal)`. ## 6. Testing & Verification ### 6.1 Static Analysis - Jalankan `flutter analyze` sebelum commit. - Target: **No issues found** (tanpa error, warning yang bisa diperbaiki). - Dead code harus dihapus, tidak di-`//` comment. ### 6.2 Manual Testing Checklist - [ ] Test flow lengkap: login → kasir → tambah produk → bayar → struk → riwayat → detail. - [ ] Test role kasir: akses yang disembunyikan benar-benar hilang. - [ ] Test role admin: semua aksi admin muncul dan berfungsi. - [ ] Test offline: matikan wifi, buat transaksi, hidupkan wifi, cek sync. - [ ] Test keyboard: input uang/diskon dengan keyboard muncul. - [ ] Test small screen: HP 5" atau emulator 360x640. - [ ] Test landscape (jika applicable). - [ ] Test rotation (jika applicable). ### 6.3 Performance - Jangan rebuild seluruh list saat satu item berubah — gunakan `ListView.builder` + `key`. - Jangan simpan controller di `build()` — simpan di `State` dan dispose. - Jangan lakukan network call di `build()` — pindah ke `initState` atau `didChangeDependencies`. - Gunakan `const` constructor untuk widget yang immutable. ## 7. Git & Commit ### 7.1 Commit Message - Format: `(): ` - Type: `feat`, `fix`, `refactor`, `style`, `docs`, `test`, `chore` - Contoh: `feat(kasir): tambah tombol Tambah Manual untuk pesanan offline` - Jangan commit dengan message "update" atau "fix" tanpa deskripsi. ### 7.2 Branching - Feature branch dari `main`: `feature/nama-fitur` - Bug fix dari `main`: `fix/nama-bug` - Jangan commit langsung ke `main` kecuali hotfix yang sudah di-review. ## 8. Anti-Patterns (Jangan Lakukan) - ❌ Jangan hardcode warna, spacing, atau font size — gunakan design tokens. - ❌ Jangan pakai `GlobalKey` kecuali benar-benar perlu. - ❌ Jangan simpan state di `StatelessWidget` — pindah ke `StatefulWidget` atau `AppState`. - ❌ Jangan lupa dispose controller/timer/stream subscription. - ❌ Jangan paksa `setState` di dalam `build()` — akan infinite loop. - ❌ Jangan pakai `await` di dalam `build()` tanpa FutureBuilder/future handling. - ❌ Jangan lupa handle error untuk semua async — jangan biarkan app crash. - ❌ Jangan gabungkan logic bisnis dengan UI — pisahkan di `AppState` atau service layer. - ❌ Jangan duplicate widget — ekstrak jadi widget terpisah atau helper function. - ❌ Jangan lupa check `mounted` sebelum akses context setelah async gap. ## 9. Referensi Codebase - `lib/state/app_state.dart` — pattern state management, async handling, offline queue. - `lib/screens/kasir_screen.dart` — pattern dialog, bottom sheet, form handling. - `lib/screens/riwayat_screen.dart` — pattern list dengan role-based visibility, detail sheet. - `lib/utils/colors.dart` — design tokens (warna, spacing, radius). - `lib/utils/format.dart` — formatRupiah, formatTanggal, formatWaktu. - `lib/models/` — pattern model dengan factory constructor + toJson/fromJson.