184 lines
9.2 KiB
Markdown
184 lines
9.2 KiB
Markdown
# 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:<project>/` 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<AppState>()` untuk rebuild saat state berubah.
|
|
- Gunakan `context.read<AppState>()` 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>(<scope>): <description>`
|
|
- 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.
|