Files
app-grosir/.kilo/agent/uiux-pos-grosir.md
2026-07-23 22:12:32 +07:00

196 lines
11 KiB
Markdown

# UI/UX Design Skill — Dutra Store (POS Grosir Flutter)
Gunakan skill ini sebagai acuan setiap kali mengubah, menambah, atau meninjau tampilan aplikasi POS grosir berbasis Flutter ini. Jika ada pertentangan, prioritaskan yang tercantum di sini.
## 1. Tujuan Utama
- Transaksi cepat tanpa hambatan visual.
- Tidak ada overflow/keluar layar di ukuran HP kecil.
- Status stok, harga, dan aksi utama terlihat jelas dalam 1 scan.
- Maintain konsistensi visual antar screen.
## 2. Design System
### 2.1 Tema
- Theme gelap (dark) sebagai basis utama.
- Warna aksen: amber (`Colors.amber`).
- Radius default: 18 untuk kartu, 12 untuk input/dialog, 8 untuk chip/tag.
- Font: gunakan `Theme.of(context).textTheme` agar sesuai pengaturan sistem.
### 2.2 Warna Semantik
- `kAccent` — amber untuk aksen dan highlight.
- `kSuccess` — hijau untuk status lunas/berhasil.
- `kDanger` — merah untuk pembatalan/error/stok habis.
- `kMuted` — abu-abu untuk teks sekunder/timestamp.
- `kSurface2` — background chip yang tidak aktif.
- `kBorder` — border untuk chip dan outline.
### 2.3 Spacing & Layout
- Gunakan `EdgeInsets` tetap untuk konsistensi:
- `EdgeInsets.all(12)` padding screen/list.
- `EdgeInsets.all(16)` padding form/dialog.
- `EdgeInsets.symmetric(horizontal: 14, vertical: 10)` untuk baris list.
- Gap antar field/form: `SizedBox(height: 10)`.
- Gap antar section: `SizedBox(height: 14)`.
### 2.4 Kartu (Card)
- Semua item list/kartu harus punya padding minimal `EdgeInsets.all(12)`.
- Jangan hardcode tinggi kartu; gunakan `Column` + `MainAxisSize.min` atau `IntrinsicHeight` bila perlu.
- Jika ada 3+ field di dalam kartu, prioritaskan wrap/scroll.
- Status transaksi harus jelas:
- Lunas: badge/icon hijau.
- Belum lunas: badge/icon amber/oranye.
- Dibatalkan: badge/icon merah + `TextDecoration.lineThrough`.
### 2.5 Input & Form
- Label jelas, helper text opsional untuk field opsional.
- Jangan taruh form di dalam `Column` tanpa `SingleChildScrollView` jika total tinggi form bisa melebihi layar.
- Saat keyboard muncul, pastikan field terlihat:
- Bungkus form dengan `SingleChildScrollView` + `padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom)`.
### 2.6 Tombol & Aksi
- Primary action (Bayar, Tambah, Simpan): `FilledButton` berwarna aksen.
- Destructive action (Hapus, Batalkan): `FilledButton` / `OutlinedButton` berwarna merah (`kDanger`).
- Secondary action (Batal, Tutup): `TextButton` atau `OutlinedButton`.
- Setiap dialog harus punya urutan aksi: **Konfirmasi** di kanan/bawah, **Batal** di kiri/atas.
### 2.6a Haptic & Feedback
- Aksi signifikan yang cepat: tambah item ke keranjang, print struk, scan barcode → `HapticFeedback.lightImpact()`.
- Aksi destruktif atau butuh konfirmasi: hapus item/batal transaksi → `HapticFeedback.mediumImpact()`.
- Sukses transaksi/tampilkan feedback →lanjut ke `SnackBar` hijau (`kSuccess`), jangan pakai dialog sukses untuk hal yang cepat.
- Jangan paksa haptic di setiap tap; hanya untuk aksi yang jelas berdampak.
### 2.7 Auto-Scroll & Keyboard Behavior
- Setiap form di dalam bottom sheet atau dialog harus meng-handle keyboard muncul/tutup.
- Gunakan `MediaQuery.of(context).viewInsets.bottom` untuk padding bawah dinamis pada `SingleChildScrollView`.
- Untuk bottom sheet form yang tinggi dan perlu auto-expand:
1. Pakai `DraggableScrollableController`.
2. Buat `FocusNode` untuk setiap `TextField`.
3. Tambah listener ke setiap focus node: jika `hasFocus`, animasikan sheet ke `maxChildSize` (misal `0.92`), lalu panggil `Scrollable.ensureVisible()` agar field aktif terlihat.
4. Durasi animasi sheet: `300ms`, scroll: `250ms`, alignment: `0.0`.
- Jangan andal `Scaffold.resizeToAvoidBottomInset` saja untuk bottom sheet form kompleks; manual padding lebih stabil untuk kasus form panjang.
### 2.8 Empty State
- Jangan tampilkan teks kosong tanpa visual indicator.
- Minimal: icon + teks. Contoh: `Icon(Icons.shopping_cart_outlined, size: 48, color: kMuted)` + teks "Keranjang kosong".
- Center alignment, `Column` dengan `MainAxisSize.min`.
- Konsisten pakai ikon `outlined` variant untuk empty state.
### 2.9 Dialog Button Layout
- Mobile: tombol dialog harus **full-width stacked vertikal** dengan `SizedBox(height: 8)` antar tombol.
- Urutan: **Batal** di atas, **Konfirmasi** di bawah.
- Jangan pakai `Row` untuk 2 tombol di dialog mobile, kecuali screen width > 600dp.
- Tombol konfirmasi destruktif: `FilledButton` dengan `backgroundColor: kDanger`.
- Tombol konfirmasi positif: `FilledButton` dengan `backgroundColor: kAccent` atau `kSuccess`.
### 2.10 Loading State
- Untuk operasi < 500ms: tidak perlu loading indicator.
- Untuk operasi > 500ms: tampilkan loading di button yang dipencet.
- Spinner: `CircularProgressIndicator(strokeWidth: 2, color: Color(0xFF06210F))` untuk light button text.
- Spinner: `CircularProgressIndicator(strokeWidth: 2, color: kAccent)` untuk dark button text.
- Untuk full-screen loading: gunakan `CircularProgressIndicator(color: kAccent)` di `Center`.
- Jangan gunakan `LinearProgressIndicator` kecuali untuk determinate progress yang jelas.
- Jika list kosong dan pengguna bisa melakukan aksi (misal tambah produk), tampilkan `FloatingActionButton` atau `FilledButton` sebagai helper agar tidak dead-end.
### 2.11 Error State
- Error dari form validation: tampilkan inline di bawah field, warna `kDanger`, font 13.
- Error dari operasi database/API: tampilkan `SnackBar` merah (`kDanger`).
- Jangan hanya pakai warna tanpa label. Tambahkan ikon/prefix jika perlu.
- Jika field error, pastikan `TextField` tetap bisa di-focus dan di-scroll.
## 3. Responsif & Overflow
### 3.1 Grid Produk (Kasir)
- Default: 2 kolom di layar HP (`crossAxisCount: 2`).
- Tablet (lebar > 600dp): naik jadi 3 kolom.
- Jangan pakai `childAspectRatio` tetap yang bikin kartu terlalu tinggi/pendek. Gunakan `AspectRatio(1.1)` atau biarkan `GridView` yang menentukan tinggi.
### 3.2 Payment Method Chips
- Urutan chip di UI: **Tunai**, **Bank**, **QRIS**, **Bayar Nanti**, **Split**.
- Untuk mobile, kelompokkan menjadi:
- Baris atas: 3 chip (`Tunai`, `Bank`, `QRIS`) dalam satu `Row` dengan `Expanded`.
- Divider tipis antar kelompok.
- Baris bawah: 2 chip (`Bayar Nanti`, `Split`) dalam satu `Row` dengan `Expanded`.
- Jangan pakai `Wrap` untuk payment chips. `Wrap` boleh dipakai kembali untuk kasus lain seperti `Filter chips` atau `Tag`.
- Chip harus compact, aman di layar kecil, dan tidak memaksa scroll horizontal.
### 3.3 Bottom Sheet
- Keranjang harus berupa bottom sheet, bukan panel tetap.
- Sheet harus bisa di-drag (`DraggableScrollableSheet` atau `showModalBottomSheet` dengan `isScrollControlled: true`).
- Konten sheet harus **selalu scrollable** (`SingleChildScrollView`), tanpa `Expanded` di dalamnya, agar footer tidak pernah terhalangi keyboard.
- Untuk menutup sheet dari aksi di dalamnya, gunakan `Navigator.pop(sheetContext)` bukan `parentContext`.
### 3.4 Dialog
- Semua dialog input yang panjang harus dibungkus `SingleChildScrollView` atau `SingleChildScrollView` di dalam `AlertDialog` agar bisa di-scroll saat keyboard muncul.
- Jangan pakai `Expanded` di dalam dialog yang berpotensi overflow.
### 3.5 Produk & Form Panjang
- Tab Produk adalah gabungan form + list produk. Gunakan `SingleChildScrollView` + `ListView.builder` dengan `shrinkWrap: true` agar form bisa di-scroll tanpa overflow.
### 3.6 Teks
- Jangan hardcode font size. Gunakan skema `TextStyle` dari `ThemeData`.
- Jika label teks bisa panjang (mis. nama produk), batasi maksimal 2 baris dengan `maxLines: 2` + `overflow: TextOverflow.ellipsis`.
- ID transaksi/nama panjang: `Expanded` + `TextOverflow.ellipsis` agar tidak overflow di `Row`.
## 4. UX untuk Kasir (Critical Path)
- Target: transaksi selesai dalam 3 tap (pilih produk → bayar → selesai).
- Jangan sembunyikan aksi utama (Bayar) di bawah banyak opsi.
- Tampilkan total, diskon, dan kembalian secara **real-time**.
- Gunakan chip/segmented button untuk tipe pembeli (Umum/Reseller) — jangan dropdown.
- Setiap item di keranjang harus bisa diubah qty atau tipe harga dalam 1 tap.
- Jika stok habis, matikan visual (opacity turun, disable tap) dan tampilkan badge "Habis".
- Jika stok menipis, tampilkan badge peringatan oranye sebelum habis.
## 5. Payment Flow: Bayar Nanti
- Saat checkout pilih **Bayar Nanti**, tombol harus jelas: **"Pesanan Sekarang"**.
- Field "Uang Dibayar" menjadi disabled dengan label **"Belum Bayar"**.
- Tampilkan status **"Belum Bayar"** di receipt/summary dengan warna `kMuted`.
- Di Riwayat, transaksi unpaid harus punya badge/status yang menonjol.
- Saat customer datang bayar: tombol **"Terima Pembayaran"** atau **"Bayar Sisa"** di detail transaksi.
## 6. Aksesibilitas & Feedback
- Minimal tap target: 44x44dp untuk semua tombol/icon.
- Warna teks harus kontras dengan background (minimal 4.5:1 untuk teks normal).
- Jangan hanya andal warna untuk status (tambah ikon/label). Contoh: stok habis = badge merah + teks "Habis".
- Setiap aksi berhasil/gagal harus ada `SnackBar` feedback:
- Sukses: hijau (`kSuccess`).
- Error: merah (`kDanger`).
- Info: biru/abu.
- Loading state harus muncul saat operasi > 500ms. Gunakan `CircularProgressIndicator(color: kAccent)`.
## 7. Role-Based Visibility
- Fitur yang tidak diizinkan untuk role tertentu harus **disembunyikan sepenuhnya**, bukan cuma di-disable.
- Admin: lihat semua transaksi + filter tanggal + aksi batalkan transaksi.
- Kasir: lihat transaksi hari ini, tidak ada aksi batalkan, tidak ada filter tanggal.
## 8. List & Pagination
- Gunakan `ListView.separated` dengan `itemCount` yang sesuai untuk performa.
- Tombol "Muat lebih banyak" harus ada di bawah list jika data bisa banyak.
- Filter chips: gunakan `Wrap` agar responsif di layar kecil.
## 8a. Printer & Thermal Receipt
- Semua teks struk harus Bahasa Indonesia dan ringkas.
- Struk wajib menampilkan: nama toko, tanggal, jam, nomor transaksi, daftar item, diskon (jika ada), total, metode pembayaran, detail pembayaran tunai/split (jika ada), dan **nama kasir**.
- Jangan tampilkan label per-item **Umum/Reseller** di struk thermal, agar lebih rapi.
- Jika `splitPayment` ada, tampilkan per bagian: `Split Tunai`, `Split Bank`, atau `Split QRIS` beserta nominalnya.
- Batasi lebar konten sesuai PaperSize.mm58 agar tidak terpotong.
- Setelah cetak, arahkan feedback ke user melalui `SnackBar`, bukan hanya log.
- Flow pairing printer harus bisa diakses oleh role kasir, bukan hanya admin.
## 9. Reusability
- Buat widget kecil reuseable untuk komponen yang berulang: `_Tag`, `_ReportCard`, `_Avatar`, `_Stepper`, `_PaymentMethodChip`.
- Jangan duplikasi warna/spacing. Taruh konstanta di `lib/utils/colors.dart`.
## 10. Checklist Sebelum Submit Perubahan UI/UX
- [ ] `flutter analyze` → No issues found (tanpa error).
- [ ] Coba di layar kecil (HP 5") dan besar (tablet).
- [ ] Coba dengan keyboard muncul (input uang/diskon).
- [ ] Coba mode gelap dan terang (jika supported).
- [ ] Pastikan tidak ada hardcoded height yang bikin overflow.
- [ ] Semua teks dalam Bahasa Indonesia.
- [ ] Konsisten pakai `formatRupiah()` untuk angka mata uang.
## 11. Referensi Project
- `lib/utils/colors.dart` — design tokens.
- `lib/screens/kasir_screen.dart` — contoh bottom sheet cart, grid produk, dialog pembayaran.
- `lib/screens/riwayat_screen.dart` — contoh kartu laporan, filter role-based.