Files
app-grosir/.kilo/agent/uiux-pos-grosir.md

14 KiB
Raw Blame History

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 & Token

  • kAccent — amber untuk aksen dan highlight.
  • kSuccess — hijau untuk status lunas/berhasil.
  • kDanger — merah untuk pembatalan/error/stok habis.
  • kWarning — amber/oranye untuk peringatan stok rendah, bayar nanti.
  • kMuted — abu-abu untuk teks sekunder/timestamp.
  • kSurface2 — background chip yang tidak aktif.
  • kBorder — border untuk chip dan outline.
  • kText — warna teks utama (default di theme gelap).

2.3 Typography Scale

Gunakan scale berikut, jangan hardcode font size kecuali specified:

  • Display / H1: 28px, bold, untuk heading besar
  • H2: 22px, bold, untuk section title
  • H3: 18px, semi-bold, untuk card title / dialog title
  • Body large: 16px, regular, untuk teks isi utama
  • Body: 14px, regular, untuk teks body standar
  • Body small: 13px, regular, untuk helper/subtitle
  • Caption: 12px, regular, untuk timestamp/label kecil
  • Overline: 10px, bold, uppercase tracking, untuk badge/status kecil
  • Monospace: untuk angka/harga agar stabil

Semua angka harga/uang wajib menggunakan formatRupiah().

2.4 Spacing Scale

Pakai multiples of 4 untuk spacing:

  • 4, 8, 12, 16, 20, 24, 32, 48
  • Padding screen/list: EdgeInsets.all(12)
  • Padding form/dialog: EdgeInsets.all(16)
  • Gap antar field: SizedBox(height: 10) atau SizedBox(height: 12)
  • Gap antar section: SizedBox(height: 14) atau SizedBox(height: 20)
  • Jangan gunakan nilai spacing yang tidak kelipatan 4 kecuali ada alasan khusus

2.5 Elevation & Surface

  • Card: Card widget dengan default elevation, atau Container dengan color: kSurface2 jika butuh kontrol penuh.
  • Active/pressed state: gunakan InkWell / InkResponse dengan splash color aksen transparan.
  • Bottom sheet: color: kSurface, border top kBorder, radius top 24.
  • Dialog: backgroundColor: kSurface, content padding dari AlertDialog default atau override sesuai kebutuhan.

2.6 Iconography

  • Ukuran icon default: 20–24dp untuk list tile, 18dp untuk button, 28dp untuk dialog icon.
  • Gunakan outlined variant untuk icon di dalam input/field (prefixIcon).
  • Gunakan filled/rounded untuk icon utama dan CTA.
  • Jangan campur gaya icon dalam satu screen.

2.7 Animation Standard

  • Micro-interaction (button press, card tap): duration 150–200ms, curve Curves.easeInOut
  • Sheet expand/collapse: duration 300ms, curve Curves.fastOutSlowIn
  • Dialog enter/exit: duration 200ms, curve Curves.easeInOut
  • Spinner: CircularProgressIndicator dengan strokeWidth: 2, duration default
  • Pulse animation (cart badge): duration 180ms, scale 1.0 → 0.9 → 1.0
  • SnackBar: duration default 3 detik, kecuali butuh lebih pendek/lama
  • Jangan animasikan opacity/color untuk status penting (sukses/gagal) — gunakan warna solid agar cepat terdeteksi

2.8 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.9 Loading State

  • Inline di button (recommended untuk semua operasi > 500ms):
    • Spinner: SizedBox(height: 18, width: 18, child: CircularProgressIndicator(strokeWidth: 2, color: Color(0xFF06210F))) untuk light button text
    • Spinner: SizedBox(height: 18, width: 18, child: CircularProgressIndicator(strokeWidth: 2, color: kAccent)) untuk dark button text
    • Ganti label menjadi Mencetak... / Memproses... / Menyimpan...
    • Disable button (onPressed: submitting ? null : ())
  • Full-screen: gunakan CircularProgressIndicator(color: kAccent) di Center.
  • Inline di list/sheet: gunakan LinearProgressIndicator hanya untuk determinate progress yang jelas.
  • Skeleton: untuk list yang loading, pakai ListTile dengan shimmer effect atau placeholder abu-abu.
  • Jangan biarkan user bisa double-tap aksi yang sudah loading.

2.10 Empty State

  • Jangan tampilkan teks kosong tanpa visual indicator.
  • Minimal: icon 48dp + teks. Contoh: Icon(Icons.shopping_cart_outlined, size: 48, color: kMuted) + teks "Keranjang kosong".
  • Center alignment, Column dengan MainAxisSize.min.
  • Jika ada aksi yang bisa dilakukan (misal tambah produk), tampilkan FilledButton sebagai helper.
  • Konsisten pakai ikon outlined variant untuk empty state.

2.11 Error State

  • Error dari form validation: tampilkan inline di bawah field, warna kDanger, font 13, maxLines: 2.
  • Error dari operasi database/API: tampilkan SnackBar merah (kDanger) dengan durasi 3 detik.
  • Jangan hanya pakai warna tanpa label. Tambahkan ikon/prefix jika perlu.
  • Jika field error, pastikan TextField tetap bisa di-focus dan di-scroll.
  • Untuk error yang bisa diperbaiki (misal form), jangan matikan form.

2.12 Dialog Button Layout

  • Mobile (< 600dp): tombol dialog harus full-width stacked vertikal dengan SizedBox(height: 8) antar tombol.
  • Tablet (>= 600dp): bisa pakai Row horizontal untuk 2 tombol, dengan Expanded.
  • Urutan: Batal di atas/kiri, Konfirmasi di bawah/kanan.
  • Tombol konfirmasi destruktif: FilledButton dengan backgroundColor: kDanger.
  • Tombol konfirmasi positif: FilledButton dengan backgroundColor: kAccent atau kSuccess.
  • Gunakan StatefulBuilder jika dialog butuh loading state atau update UI setelah aksi.

2.13 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 + pengaturan toko + kelola akun kasir + harga pokok/laba.
  • Kasir: lihat transaksi hari ini, tidak ada aksi batalkan, tidak ada filter tanggal, tidak ada pengaturan toko, tidak ada harga pokok/laba.
  • Guest: tidak diizinkan akses.

2.14 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.
  • Tambah handle bar (container 40x4 dengan radius 4, warna kBorder) di atas sheet untuk affordance drag.

2.15 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.
  • Dialog dengan form: contentPadding: EdgeInsets.fromLTRB(16, 16, 16, 8), actionsPadding: EdgeInsets.fromLTRB(16, 0, 16, 16).
  • Dialog dengan list items: contentPadding: EdgeInsets.all(16).
  • Jangan buat dialog terlalu tinggi; batasi dengan ConstrainedBox + SingleChildScrollView jika konten bisa panjang.

2.16 SnackBar

  • Gunakan ScaffoldMessenger.of(context).showSnackBar() untuk feedback singkat.
  • Durasi default 3 detik untuk info, 5 detik untuk error penting.
  • Posisikan di atas bottom navigation jika ada, atau default bottom.
  • Jangan tampilkan SnackBar di atas dialog yang masih terbuka — tunggu dialog ditutup dulu.

2.17 Status & Badge

  • Gunakan badge untuk status dengan warna sesuai:
    • Lunas / Berhasil: badge hijau (kSuccess) dengan label teks
    • Belum Lunas / Bayar Nanti: badge amber (kWarning) dengan label teks
    • Dibatalkan: badge merah (kDanger) + teks DIBATALKAN
    • Stok Habis: badge merah + teks "Habis"
    • Stok Rendah: badge oranye + teks "Stok rendah (X/Y)"
    • Manual: badge amber dengan label "MANUAL" (caps, tracking +0.5)
  • Badge style: Container dengan padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2), borderRadius: BorderRadius.circular(8), color: warna.withValues(alpha: 0.15).
  • Untuk badge kecil (10–12px), gunakan fontWeight: FontWeight.w800 agar tetap terbaca.

2.18 List & Card

  • Gunakan ListView.separated dengan itemCount yang sesuai untuk performa.
  • Separator: Divider(height: 1, thickness: 1, color: kBorder) atau SizedBox(height: 8/12) untuk card list.
  • Card list: Card widget dengan margin: EdgeInsets.symmetric(horizontal: 12, vertical: 4) atau EdgeInsets.all(12).
  • Jangan hardcode tinggi list item; gunakan Column + MainAxisSize.min.
  • Teks panjang: maxLines: 2, overflow: TextOverflow.ellipsis untuk nama produk.

2.19 Grid Produk

  • 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.
  • Setiap kartu produk harus menampilkan: inisial nama (avatar circle amber), nama produk, harga, stok/badge stok.

2.20 Auto-Scroll & Keyboard

  • 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.21 Responsif

  • Mobile (< 600dp): 2 kolom grid, tombol stacked vertikal di dialog.
  • Tablet (>= 600dp): 3–4 kolom grid, tombol bisa horizontal di dialog.
  • Gunakan LayoutBuilder atau MediaQuery untuk menentukan layout berdasarkan lebar screen.
  • Jangan gunakan hardcoded width untuk widget utama; gunakan Expanded, Flexible, atau MediaQuery.

2.22 Aksesibilitas

  • Minimal tap target: 44x44dp untuk semua tombol/icon.
  • Kontras teks minimal 4.5:1 (cek dengan tool contrast checker).
  • Jangan andal warna saja (tambah ikon/label). Contoh: stok habis = badge merah + teks "Habis".
  • Setiap aksi ada SnackBar feedback: sukses hijau, error merah, info biru/abu.
  • Gunakan Semantics widget untuk icon-only button agar screen reader bisa membaca.

3. 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.
  • Bottom bar keranjang harus selalu terlihat, tidak tertutup keyboard.

4. 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 kWarning.
  • Di Riwayat, transaksi unpaid harus punya badge/status yang menonjol.
  • Saat customer datang bayar: tombol "Terima Pembayaran" atau "Bayar Sisa" di detail transaksi.

5. Reusability & Konsistensi

  • Buat widget reuseable untuk komponen berulang: _Tag, _ReportCard, _Avatar, _Stepper, _PaymentMethodChip, _ReceiptRow, _SettingsListTile.
  • Jangan duplikasi warna/spacing. Taruh konstanta di lib/utils/colors.dart.
  • Konsisten pakai formatRupiah() untuk semua angka mata uang.
  • Nama widget internal: pakai underscore prefix (_) kecuali widget yang diekspor.

6. 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.
  • Coba flow lengkap: tambah produk → checkout → cetak struk → riwayat → detail.
  • Coba role kasir dan admin untuk memastikan visibility benar.

7. 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.
  • lib/screens/pengaturan_screen.dart — contoh settings form + role-based visibility.