Files
app-grosir/.kilo/agent/coding-standard.md

9.2 KiB

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.