<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Aris Candra Muzaffar</title>
    <description>The latest articles on Forem by Aris Candra Muzaffar (@ariscandra).</description>
    <link>https://forem.com/ariscandra</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3877427%2Fb8a43761-8567-400e-8a5b-e4b0dada08c0.jpeg</url>
      <title>Forem: Aris Candra Muzaffar</title>
      <link>https://forem.com/ariscandra</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ariscandra"/>
    <language>en</language>
    <item>
      <title>Pegangan</title>
      <dc:creator>Aris Candra Muzaffar</dc:creator>
      <pubDate>Thu, 07 May 2026 13:13:30 +0000</pubDate>
      <link>https://forem.com/ariscandra/pegangan-3gpn</link>
      <guid>https://forem.com/ariscandra/pegangan-3gpn</guid>
      <description>&lt;h1&gt;
  
  
  Pegangan Breakdown Laman Big Golf
&lt;/h1&gt;

&lt;p&gt;Dokumen ini dirapikan khusus untuk sesi tanya jawab penguji.&lt;br&gt;
Fokus utamanya bukan cuma “route ke mana”, tapi juga “komponen ini pakai stack apa dan fungsinya untuk apa”.&lt;br&gt;
Semua rujukan dibatasi ke &lt;code&gt;big-golf/&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ringkasan arsitektur satu napas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Entry request web ada di &lt;code&gt;big-golf/public/index.php&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Router manual ada di &lt;code&gt;big-golf/config/router.php&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Render halaman lewat layout tunggal &lt;code&gt;big-golf/views/layouts/utama.php&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Pola MVC dipakai lewat &lt;code&gt;controllers/&lt;/code&gt;, &lt;code&gt;models/&lt;/code&gt;, dan &lt;code&gt;views/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Data utama diproses lewat MySQL PDO dari &lt;code&gt;big-golf/koneksi.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Peta stack per komponen global
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Navbar
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pakai apa:&lt;/strong&gt; PHP partial + Bootstrap dropdown + Bootstrap Icons + CSS kustom + JS vanilla&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dipakai untuk apa:&lt;/strong&gt; render menu desktop dan mobile, state aktif menu, dan perubahan gaya saat scroll&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File bukti:&lt;/strong&gt; &lt;code&gt;big-golf/views/partials/navbar.php&lt;/code&gt;, &lt;code&gt;big-golf/public/assets/css/tema.css&lt;/code&gt;, &lt;code&gt;big-golf/views/layouts/utama.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Footer
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pakai apa:&lt;/strong&gt; PHP partial + Bootstrap utility + Bootstrap Icons + Vue 3 mini mount&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dipakai untuk apa:&lt;/strong&gt; quick links, ikon kontak, dan tahun dinamis di elemen footer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File bukti:&lt;/strong&gt; &lt;code&gt;big-golf/views/partials/footer.php&lt;/code&gt;, &lt;code&gt;big-golf/views/layouts/utama.php&lt;/code&gt;, &lt;code&gt;big-golf/public/assets/css/tema.css&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sistem layout umum
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pakai apa:&lt;/strong&gt; Bootstrap CDN, Vue CDN, partial include, helper render&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dipakai untuk apa:&lt;/strong&gt; memastikan semua laman punya kerangka UI yang konsisten&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File bukti:&lt;/strong&gt; &lt;code&gt;big-golf/views/layouts/utama.php&lt;/code&gt;, &lt;code&gt;big-golf/config/bootstrap.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Form handling umum
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pakai apa:&lt;/strong&gt; endpoint &lt;code&gt;aksi_*.php&lt;/code&gt; + controller method + flash session&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dipakai untuk apa:&lt;/strong&gt; memisahkan alur GET halaman dan POST aksi data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File bukti:&lt;/strong&gt; &lt;code&gt;big-golf/public/aksi_kirim_ulasan.php&lt;/code&gt;, &lt;code&gt;big-golf/public/aksi_kirim_kontak.php&lt;/code&gt;, &lt;code&gt;big-golf/public/aksi_login_admin.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Keamanan umum
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pakai apa:&lt;/strong&gt; CSRF token, &lt;code&gt;hash_equals&lt;/code&gt;, session hardening, &lt;code&gt;password_verify&lt;/code&gt;, &lt;code&gt;htmlspecialchars&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dipakai untuk apa:&lt;/strong&gt; menahan request palsu, mengamankan autentikasi admin, dan mencegah XSS pada output&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File bukti:&lt;/strong&gt; &lt;code&gt;big-golf/controllers/AdminAuthController.php&lt;/code&gt;, &lt;code&gt;big-golf/controllers/UlasanController.php&lt;/code&gt;, &lt;code&gt;big-golf/config/bootstrap.php&lt;/code&gt;, &lt;code&gt;big-golf/views/ulasan/halaman.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Breakdown per laman dan komponen internalnya
&lt;/h2&gt;

&lt;h2&gt;
  
  
  1) Beranda (&lt;code&gt;/&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Route dan alur&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route: &lt;code&gt;/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Controller: &lt;code&gt;big-golf/controllers/BerandaController.php&lt;/code&gt; (&lt;code&gt;tampil&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;View utama: &lt;code&gt;big-golf/views/beranda/index.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Komponen dan penerapan stack&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hero&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai HTML + CSS tema untuk visual utama dan CTA&lt;/li&gt;
&lt;li&gt;Dipakai untuk menyampaikan value cepat di layar pertama&lt;/li&gt;
&lt;li&gt;Bukti: &lt;code&gt;big-golf/views/beranda/index.php&lt;/code&gt;, &lt;code&gt;big-golf/public/assets/css/tema.css&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Section ulasan ringkas&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai PHP render data + JS interaksi ulasan + modal Bootstrap&lt;/li&gt;
&lt;li&gt;Dipakai untuk preview social proof tanpa pindah halaman&lt;/li&gt;
&lt;li&gt;Bukti: &lt;code&gt;big-golf/public/assets/js/beranda-ulasan.js&lt;/code&gt;, &lt;code&gt;big-golf/views/partials/ulasan/modal_tulis.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Section lokasi&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai Google Maps iframe + animasi ringan saat masuk viewport&lt;/li&gt;
&lt;li&gt;Dipakai untuk mempercepat user memahami letak bisnis&lt;/li&gt;
&lt;li&gt;Bukti: &lt;code&gt;big-golf/views/beranda/index.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Kalimat siap ucap&lt;/strong&gt;&lt;br&gt;
“Beranda kami pakai kombinasi server render untuk konten inti dan JS ringan untuk interaksi. Jadi cepat dimuat, tapi tetap terasa hidup.”&lt;/p&gt;




&lt;h2&gt;
  
  
  2) Tentang (&lt;code&gt;/tentang&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Route dan alur&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route: &lt;code&gt;/tentang&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Controller: &lt;code&gt;big-golf/controllers/TentangController.php&lt;/code&gt; (&lt;code&gt;tampil&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;View: &lt;code&gt;big-golf/views/tentang/index.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Komponen dan penerapan stack&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Timeline sejarah&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai script vanilla di view untuk pergantian titik timeline&lt;/li&gt;
&lt;li&gt;Dipakai agar narasi profil tidak monoton&lt;/li&gt;
&lt;li&gt;Bukti: &lt;code&gt;big-golf/views/tentang/index.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Layout konten&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai Bootstrap grid dan utility class&lt;/li&gt;
&lt;li&gt;Dipakai untuk menjaga hierarki konten tetap rapi di mobile dan desktop&lt;/li&gt;
&lt;li&gt;Bukti: &lt;code&gt;big-golf/views/tentang/index.php&lt;/code&gt;, &lt;code&gt;big-golf/views/layouts/utama.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Kalimat siap ucap&lt;/strong&gt;&lt;br&gt;
“Laman Tentang sengaja lebih ringan dari sisi data. Fokusnya storytelling, jadi stack kami pakai untuk pengalaman baca yang enak.”&lt;/p&gt;




&lt;h2&gt;
  
  
  3) Fasilitas (&lt;code&gt;/fasilitas&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Route dan alur&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route: &lt;code&gt;/fasilitas&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Controller: &lt;code&gt;big-golf/controllers/FasilitasController.php&lt;/code&gt; (&lt;code&gt;tampil&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;View: &lt;code&gt;big-golf/views/fasilitas/index.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Komponen dan penerapan stack&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tabel paket&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai tabel Bootstrap&lt;/li&gt;
&lt;li&gt;Dipakai agar informasi harga cepat dipindai&lt;/li&gt;
&lt;li&gt;Bukti: &lt;code&gt;big-golf/views/fasilitas/index.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Card fasilitas pendukung&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai card Bootstrap + CSS tema + efek reveal&lt;/li&gt;
&lt;li&gt;Dipakai untuk menambah konteks layanan tanpa bikin halaman berat&lt;/li&gt;
&lt;li&gt;Bukti: &lt;code&gt;big-golf/views/fasilitas/index.php&lt;/code&gt;, &lt;code&gt;big-golf/public/assets/css/tema.css&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Kalimat siap ucap&lt;/strong&gt;&lt;br&gt;
“Di Fasilitas, Bootstrap kami pakai bukan sekadar gaya, tapi buat menata informasi harga dan layanan supaya cepat dibaca pengunjung.”&lt;/p&gt;




&lt;h2&gt;
  
  
  4) Galeri (&lt;code&gt;/galeri&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Route dan alur&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route: &lt;code&gt;/galeri&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Controller: &lt;code&gt;big-golf/controllers/GaleriController.php&lt;/code&gt; (&lt;code&gt;tampil&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;View: &lt;code&gt;big-golf/views/galeri/index.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Komponen dan penerapan stack&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grid galeri&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai server render URL gambar + CSS galeri&lt;/li&gt;
&lt;li&gt;Dipakai agar konten visual tetap konsisten dan mudah dikelola&lt;/li&gt;
&lt;li&gt;Bukti: &lt;code&gt;big-golf/controllers/GaleriController.php&lt;/code&gt;, &lt;code&gt;big-golf/views/galeri/index.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Lightbox&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai JavaScript vanilla dan elemen dialog&lt;/li&gt;
&lt;li&gt;Dipakai untuk pengalaman lihat foto yang lebih fokus&lt;/li&gt;
&lt;li&gt;Bukti: &lt;code&gt;big-golf/public/assets/js/galeri.js&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Proxy aset&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai script aset endpoint&lt;/li&gt;
&lt;li&gt;Dipakai untuk jalur akses gambar yang konsisten dari web root&lt;/li&gt;
&lt;li&gt;Bukti: &lt;code&gt;big-golf/public/galeri-aset.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Kalimat siap ucap&lt;/strong&gt;&lt;br&gt;
“Galeri kami mengutamakan UX lihat foto. Makanya ada lightbox, navigasi keyboard, dan kontrol state supaya nyaman dipakai.”&lt;/p&gt;




&lt;h2&gt;
  
  
  5) Lokasi dan Kontak (&lt;code&gt;/lokasi-kontak&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Route dan alur&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route: &lt;code&gt;/lokasi-kontak&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Controller tampil: &lt;code&gt;big-golf/controllers/LokasiKontakController.php&lt;/code&gt; (&lt;code&gt;tampil&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Controller submit: &lt;code&gt;big-golf/controllers/LokasiKontakController.php&lt;/code&gt; (&lt;code&gt;prosesKirimForm&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Endpoint POST: &lt;code&gt;big-golf/public/aksi_kirim_kontak.php&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;View: &lt;code&gt;big-golf/views/lokasi_kontak/index.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Komponen dan penerapan stack&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Map dan informasi lokasi&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai iframe map + layout Bootstrap&lt;/li&gt;
&lt;li&gt;Dipakai untuk memberikan konteks lokasi tanpa langkah tambahan&lt;/li&gt;
&lt;li&gt;Bukti: &lt;code&gt;big-golf/views/lokasi_kontak/index.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Form kontak&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai validasi server-side dengan util validator&lt;/li&gt;
&lt;li&gt;Dipakai supaya data masuk bersih sebelum diproses&lt;/li&gt;
&lt;li&gt;Bukti: &lt;code&gt;big-golf/lib/InputValidator.php&lt;/code&gt;, &lt;code&gt;big-golf/controllers/LokasiKontakController.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Redirect kanal komunikasi&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai URL builder ke WA&lt;/li&gt;
&lt;li&gt;Dipakai untuk alur kontak cepat, sesuai kebutuhan bisnis walk in&lt;/li&gt;
&lt;li&gt;Bukti: &lt;code&gt;big-golf/controllers/LokasiKontakController.php&lt;/code&gt;, &lt;code&gt;big-golf/config/url_publik.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Kalimat siap ucap&lt;/strong&gt;&lt;br&gt;
“Laman ini titik konversi utama. User dapat info lokasi dan langsung lanjut komunikasi lewat alur form yang tervalidasi.”&lt;/p&gt;




&lt;h2&gt;
  
  
  6) Ulasan (&lt;code&gt;/ulasan&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Route dan alur&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route list: &lt;code&gt;/ulasan&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Route vote: &lt;code&gt;/ulasan/vote&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Controller: &lt;code&gt;big-golf/controllers/UlasanController.php&lt;/code&gt; (&lt;code&gt;tampilDaftar&lt;/code&gt;, &lt;code&gt;kirimVote&lt;/code&gt;, &lt;code&gt;simpanBaru&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Endpoint kirim ulasan: &lt;code&gt;big-golf/public/aksi_kirim_ulasan.php&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;View: &lt;code&gt;big-golf/views/ulasan/halaman.php&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Model: &lt;code&gt;big-golf/models/UlasanModel.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Komponen dan penerapan stack&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;List ulasan&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai query model dengan filter dan sorting&lt;/li&gt;
&lt;li&gt;Dipakai supaya data publik hanya menampilkan status &lt;code&gt;disetujui&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Bukti: &lt;code&gt;big-golf/models/UlasanModel.php&lt;/code&gt;, &lt;code&gt;big-golf/views/ulasan/halaman.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Vote helpful&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai endpoint POST + CSRF + validasi input integer&lt;/li&gt;
&lt;li&gt;Dipakai untuk interaksi user yang tetap aman&lt;/li&gt;
&lt;li&gt;Bukti: &lt;code&gt;big-golf/controllers/UlasanController.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Modal kirim ulasan&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai Bootstrap modal + JS bintang rating&lt;/li&gt;
&lt;li&gt;Dipakai untuk menurunkan friksi input user&lt;/li&gt;
&lt;li&gt;Bukti: &lt;code&gt;big-golf/views/partials/ulasan/modal_tulis.php&lt;/code&gt;, &lt;code&gt;big-golf/public/assets/js/ulasan-modal.js&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Kalimat siap ucap&lt;/strong&gt;&lt;br&gt;
“Di laman Ulasan, stack backend kami kerja untuk kualitas data, dan stack frontend kami kerja untuk membuat pengisian ulasan tetap gampang.”&lt;/p&gt;




&lt;h2&gt;
  
  
  7) Login Admin (&lt;code&gt;/login.php&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Route dan alur&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Entry GET: &lt;code&gt;big-golf/public/login.php&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Controller form: &lt;code&gt;big-golf/controllers/AdminAuthController.php&lt;/code&gt; (&lt;code&gt;tampil_form_login&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Endpoint POST: &lt;code&gt;big-golf/public/aksi_login_admin.php&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Controller proses: &lt;code&gt;big-golf/controllers/AdminAuthController.php&lt;/code&gt; (&lt;code&gt;proses_login&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;View: &lt;code&gt;big-golf/views/admin/login.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Komponen dan penerapan stack&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Form login&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai Bootstrap form&lt;/li&gt;
&lt;li&gt;Dipakai untuk UI sederhana yang mudah dipakai admin non teknis&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Verifikasi kredensial&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai &lt;code&gt;password_verify&lt;/code&gt; pada hash DB&lt;/li&gt;
&lt;li&gt;Dipakai untuk keamanan password&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Anti brute force dasar&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai counter session dan lock waktu singkat&lt;/li&gt;
&lt;li&gt;Dipakai untuk membatasi percobaan gagal berulang&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Kalimat siap ucap&lt;/strong&gt;&lt;br&gt;
“Login admin tidak cuma cek password, tapi juga dilapisi CSRF, lock percobaan gagal, dan regenerasi session ID.”&lt;/p&gt;




&lt;h2&gt;
  
  
  8) Dashboard Admin (&lt;code&gt;/admin/dashboard&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Route dan alur&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route: &lt;code&gt;/admin/dashboard&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Controller: &lt;code&gt;big-golf/controllers/AdminAuthController.php&lt;/code&gt; (&lt;code&gt;dashboard&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;View: &lt;code&gt;big-golf/views/admin/dashboard.php&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Endpoint aksi moderasi:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;big-golf/public/aksi_setujui_ulasan.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;big-golf/public/aksi_sembunyikan_ulasan.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;big-golf/public/aksi_kembalikan_menunggu_ulasan.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;big-golf/public/aksi_edit_ulasan.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;big-golf/public/aksi_hapus_ulasan.php&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Komponen dan penerapan stack&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Daftar antrian ulasan&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai query model terparameter&lt;/li&gt;
&lt;li&gt;Dipakai untuk menampilkan item yang harus dimoderasi&lt;/li&gt;
&lt;li&gt;Bukti: &lt;code&gt;big-golf/models/UlasanModel.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Aksi moderasi&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai POST endpoint terpisah + CSRF moderasi&lt;/li&gt;
&lt;li&gt;Dipakai supaya alur audit dan kontrol status lebih jelas&lt;/li&gt;
&lt;li&gt;Bukti: &lt;code&gt;big-golf/controllers/AdminAuthController.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Audit trail&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pakai log moderasi di model saat update status&lt;/li&gt;
&lt;li&gt;Dipakai untuk jejak perubahan data&lt;/li&gt;
&lt;li&gt;Bukti: &lt;code&gt;big-golf/models/UlasanModel.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Kalimat siap ucap&lt;/strong&gt;&lt;br&gt;
“Dashboard kami sengaja dibuat fokus. Admin tinggal moderasi, sistem yang menjaga validasi status, token, dan log aksi.”&lt;/p&gt;




&lt;h2&gt;
  
  
  Cheat sheet cepat saat penguji nanya stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;“Navbar kalian pakai apa”&lt;/strong&gt;
Jawab: “&lt;code&gt;views/partials/navbar.php&lt;/code&gt; pakai Bootstrap dropdown dan icon, styling di &lt;code&gt;public/assets/css/tema.css&lt;/code&gt;, perilaku scroll di script layout.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Footer kenapa ada Vue”&lt;/strong&gt;
Jawab: “Kami pakai Vue mini untuk tahun dinamis di footer, jadi Vue tidak dipakai berlebihan.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Galeri ini library apa”&lt;/strong&gt;
Jawab: “Lightbox dibuat dengan JavaScript vanilla di &lt;code&gt;public/assets/js/galeri.js&lt;/code&gt;, bukan plugin berat.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Form kontak backendnya apa”&lt;/strong&gt;
Jawab: “POST ke &lt;code&gt;aksi_kirim_kontak.php&lt;/code&gt;, diproses &lt;code&gt;LokasiKontakController&lt;/code&gt;, divalidasi &lt;code&gt;InputValidator&lt;/code&gt;.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Moderasi aman dari request palsu”&lt;/strong&gt;
Jawab: “Aksi dashboard wajib POST plus token CSRF dan cek session admin.”&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Gate dokumen
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Spesifikasi stack per komponen: terpenuhi&lt;/li&gt;
&lt;li&gt;Mapping route, controller, view, dan endpoint aksi: terpenuhi&lt;/li&gt;
&lt;li&gt;Bahasa sudah dinaturalkan untuk kebutuhan lisan sidang: terpenuhi&lt;/li&gt;
&lt;li&gt;Rujukan file di luar &lt;code&gt;big-golf/&lt;/code&gt;: tidak ada&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Status: LULUS&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>php</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Bank Soal PBW</title>
      <dc:creator>Aris Candra Muzaffar</dc:creator>
      <pubDate>Thu, 07 May 2026 13:12:52 +0000</pubDate>
      <link>https://forem.com/ariscandra/bank-soal-pbw-k16</link>
      <guid>https://forem.com/ariscandra/bank-soal-pbw-k16</guid>
      <description>&lt;h1&gt;
  
  
  Bank Pertanyaan Penguji Big Golf
&lt;/h1&gt;

&lt;p&gt;Dokumen ini versi upgrade untuk latihan sidang.&lt;br&gt;
Isi pertanyaan dibuat lebih lebar, dari konsep dasar sampai keputusan teknis yang bisa ditanya mendadak.&lt;br&gt;
Semua rujukan file hanya dari &lt;code&gt;big-golf/&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cara pakai biar tidak kaku
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Jawab dulu 20 detik dengan pola konsep, alasan, bukti file&lt;/li&gt;
&lt;li&gt;Kalau penguji minta detail, baru masuk ke alur kode&lt;/li&gt;
&lt;li&gt;Jangan buru buru buka banyak file&lt;/li&gt;
&lt;li&gt;Buka satu file yang paling relevan, jelaskan dulu konteksnya&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Ringkasan inti yang harus nempel di kepala
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Arsitektur pakai PHP native dengan pola MVC ringan&lt;/li&gt;
&lt;li&gt;Routing ditangani manual di &lt;code&gt;big-golf/config/router.php&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;UI pakai Bootstrap 5 dan styling kustom di &lt;code&gt;big-golf/public/assets/css/tema.css&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Vue dipakai tipis untuk interaksi kecil, bukan sebagai SPA penuh&lt;/li&gt;
&lt;li&gt;Data ulasan diproses via PDO prepared statements&lt;/li&gt;
&lt;li&gt;Area admin diamankan dengan session auth, CSRF, dan verifikasi password hash&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  A. Konsep dasar web dan stack
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1) Kenapa pilih PHP native
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Kami sengaja pakai PHP native karena scope proyek jelas dan instruksi akademik memang mendorong itu. Dengan struktur MVC, kode tetap rapi meski tidak memakai framework besar.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/public/index.php&lt;/code&gt;, &lt;code&gt;big-golf/config/router.php&lt;/code&gt;, &lt;code&gt;big-golf/controllers/&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2) Kapan Bootstrap membantu paling terasa
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Bootstrap membantu di grid, form, button, modal, dan utilitas responsif. Ini bikin tim bisa fokus ke alur bisnis tanpa mengulang CSS dasar dari nol.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/views/layouts/utama.php&lt;/code&gt;, &lt;code&gt;big-golf/views/admin/login.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3) Kenapa masih pakai CSS kustom kalau sudah ada Bootstrap
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Bootstrap dipakai sebagai fondasi komponen. Identitas visual proyek tetap kami bentuk di &lt;code&gt;tema.css&lt;/code&gt; supaya tidak terasa template generik.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/public/assets/css/tema.css&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4) Vue dipakai di mana dan buat apa
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Vue dipakai tipis untuk kebutuhan interaksi kecil seperti mount tahun footer. Jadi penggunaannya hemat, tidak over engineering.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/views/layouts/utama.php&lt;/code&gt;, &lt;code&gt;big-golf/views/partials/footer.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5) Kenapa tidak dijadikan SPA sekalian
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Kebutuhan aplikasi ini lebih cocok server rendered pages dengan interaksi lokal. Pendekatan itu lebih sederhana, cepat dijelaskan saat sidang, dan pas untuk scope company profile.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/views/layouts/utama.php&lt;/code&gt;, &lt;code&gt;big-golf/config/router.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  6) Apa nilai tambah arsitektur MVC di proyek ini
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
MVC memudahkan pemisahan tanggung jawab. View fokus tampilan, controller fokus alur request, model fokus data.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/controllers/UlasanController.php&lt;/code&gt;, &lt;code&gt;big-golf/models/UlasanModel.php&lt;/code&gt;, &lt;code&gt;big-golf/views/ulasan/halaman.php&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  B. Arsitektur request dan route
&lt;/h2&gt;

&lt;h3&gt;
  
  
  7) Alur request dari browser sampai HTML jadi apa
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Request masuk ke &lt;code&gt;public/index.php&lt;/code&gt;, diteruskan ke router, lalu controller memilih view. Data yang perlu diambil dipanggil dari model sebelum render.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/public/index.php&lt;/code&gt;, &lt;code&gt;big-golf/config/router.php&lt;/code&gt;, &lt;code&gt;big-golf/config/bootstrap.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  8) Kenapa route admin dipisah dengan endpoint aksi
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Kami pisahkan supaya alur GET halaman dan POST mutasi data tidak campur. Ini bikin kontrol keamanan dan audit lebih jelas.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/config/router.php&lt;/code&gt;, &lt;code&gt;big-golf/public/aksi_setujui_ulasan.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  9) Route publik apa saja yang paling penting dihafal
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Yang wajib hafal adalah &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;/tentang&lt;/code&gt;, &lt;code&gt;/fasilitas&lt;/code&gt;, &lt;code&gt;/galeri&lt;/code&gt;, &lt;code&gt;/lokasi-kontak&lt;/code&gt;, &lt;code&gt;/ulasan&lt;/code&gt;, &lt;code&gt;/admin/dashboard&lt;/code&gt;. Daftar ini biasanya muncul saat penguji cek cakupan fitur.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/config/router.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  10) Kenapa tidak pakai dynamic routing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Karena kebutuhan route masih terbatas dan stabil. Route statis membuat alur lebih mudah diuji dan minim kompleksitas.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/config/router.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  11) Apa fungsi helper render halaman
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Helper render dipakai untuk konsistensi layout, data title, dan include partial umum. Jadi tidak perlu copy render logic di banyak controller.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/config/bootstrap.php&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  C. Frontend dan UI detail komponen
&lt;/h2&gt;

&lt;h3&gt;
  
  
  12) Navbar pakai stack apa
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Navbar dirender dari partial PHP, interaksi mobile pakai dropdown Bootstrap, icon pakai Bootstrap Icons, dan gaya visual disetel di &lt;code&gt;tema.css&lt;/code&gt;.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/views/partials/navbar.php&lt;/code&gt;, &lt;code&gt;big-golf/public/assets/css/tema.css&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  13) Footer pakai stack apa
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Footer dibangun dari partial PHP dengan utility Bootstrap, icon links, dan mini mount Vue untuk tahun dinamis. Ini contoh penggunaan Vue yang proporsional.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/views/partials/footer.php&lt;/code&gt;, &lt;code&gt;big-golf/views/layouts/utama.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  14) Lightbox galeri pakai apa
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Lightbox digerakkan JavaScript vanilla, bukan library tambahan besar. Ini cukup untuk navigasi foto dan tetap ringan.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/public/assets/js/galeri.js&lt;/code&gt;, &lt;code&gt;big-golf/views/galeri/index.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  15) Modal tulis ulasan pakai apa
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Modal memakai komponen Bootstrap, form dirender dari partial, dan interaksi bintang ditangani script JS khusus.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/views/partials/ulasan/modal_tulis.php&lt;/code&gt;, &lt;code&gt;big-golf/public/assets/js/ulasan-modal.js&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  16) Kenapa tetap pakai inline script di beberapa laman
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Untuk interaksi yang benar benar lokal pada satu halaman, inline script bisa lebih praktis dan mudah ditelusuri saat debugging. Selama tetap terkontrol, ini masih valid.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/views/tentang/index.php&lt;/code&gt;, &lt;code&gt;big-golf/views/fasilitas/index.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  17) Bagaimana responsivitas dijaga
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Responsivitas dijaga lewat grid Bootstrap, utility class, dan tuning CSS tema. Targetnya supaya konten tetap terbaca rapi di mobile sampai desktop.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/views/layouts/utama.php&lt;/code&gt;, &lt;code&gt;big-golf/public/assets/css/tema.css&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  D. Backend dan alur bisnis
&lt;/h2&gt;

&lt;h3&gt;
  
  
  18) Alur kirim ulasan dari user ke database
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
User kirim form ke endpoint aksi, controller validasi input, lalu model simpan ke tabel ulasan dengan status awal menunggu. Data baru tampil ke publik setelah moderasi admin.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/public/aksi_kirim_ulasan.php&lt;/code&gt;, &lt;code&gt;big-golf/controllers/UlasanController.php&lt;/code&gt;, &lt;code&gt;big-golf/models/UlasanModel.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  19) Alur vote ulasan di-handle di mana
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Vote masuk lewat route &lt;code&gt;/ulasan/vote&lt;/code&gt;, lalu controller memvalidasi payload dan model melakukan update terparameter. Ini menjaga jalur vote tetap aman dari input liar.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/config/router.php&lt;/code&gt;, &lt;code&gt;big-golf/controllers/UlasanController.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  20) Kenapa contact form tidak langsung simpan DB
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Karena tujuan bisnisnya mengarahkan komunikasi ke WhatsApp. Jadi form lebih ke pre formatting pesan, bukan ticketing system.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/public/aksi_kirim_kontak.php&lt;/code&gt;, &lt;code&gt;big-golf/controllers/LokasiKontakController.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  21) Moderasi ulasan alurnya bagaimana
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Admin buka dashboard, pilih aksi setujui sembunyikan edit atau hapus, lalu endpoint POST memproses perubahan status lewat model. Setiap aksi utama disertai proteksi token.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/views/admin/dashboard.php&lt;/code&gt;, &lt;code&gt;big-golf/public/aksi_setujui_ulasan.php&lt;/code&gt;, &lt;code&gt;big-golf/models/UlasanModel.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  22) Kenapa memakai endpoint &lt;code&gt;aksi_*.php&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Pendekatan ini bikin jalur mutasi data sangat eksplisit dan mudah diaudit. Saat sidang pun lebih gampang dijelaskan endpoint mana yang melakukan apa.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/public/aksi_login_admin.php&lt;/code&gt;, &lt;code&gt;big-golf/public/aksi_hapus_ulasan.php&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  E. Database dan data integrity
&lt;/h2&gt;

&lt;h3&gt;
  
  
  23) Kenapa memilih PDO
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
PDO memberi prepared statement yang rapi dan konsisten untuk berbagai query. Ini memudahkan binding parameter dan mengurangi risiko SQL injection.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/koneksi.php&lt;/code&gt;, &lt;code&gt;big-golf/models/UlasanModel.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  24) Apa bukti query aman dari SQL injection
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Query model menggunakan &lt;code&gt;prepare&lt;/code&gt;, &lt;code&gt;bindValue&lt;/code&gt;, dan &lt;code&gt;execute&lt;/code&gt;, bukan menyusun string SQL mentah dari input user.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/models/UlasanModel.php&lt;/code&gt;, &lt;code&gt;big-golf/models/AdminPenggunaModel.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  25) Status ulasan dipakai untuk apa
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Status dipakai untuk memisahkan data publik dan data antrian moderasi. Artinya kontrol kualitas konten tetap di tangan admin.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/models/UlasanModel.php&lt;/code&gt;, &lt;code&gt;big-golf/views/ulasan/halaman.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  26) Apa fungsi log moderasi
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Log moderasi dipakai sebagai jejak perubahan saat status ulasan diubah admin. Ini penting untuk audit internal.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/models/UlasanModel.php&lt;/code&gt;, &lt;code&gt;big-golf/sql/skema.sql&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  27) Bagaimana relasi tabel dijaga
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Skema dibuat dengan constraint dan indeks untuk mendukung integritas data dan performa query utama.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/sql/skema.sql&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  F. Security dan hardening
&lt;/h2&gt;

&lt;h3&gt;
  
  
  28) CSRF dipasang di mana saja
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
CSRF ada di login, logout, moderasi admin, vote ulasan, kirim ulasan, dan form kontak. Token diverifikasi server side memakai &lt;code&gt;hash_equals&lt;/code&gt;.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/controllers/AdminAuthController.php&lt;/code&gt;, &lt;code&gt;big-golf/controllers/UlasanController.php&lt;/code&gt;, &lt;code&gt;big-golf/controllers/LokasiKontakController.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  29) Kenapa &lt;code&gt;hash_equals&lt;/code&gt; penting
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;hash_equals&lt;/code&gt; dipakai untuk perbandingan token yang lebih aman terhadap timing attack sederhana. Ini praktik standar saat verifikasi token.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/controllers/AdminAuthController.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  30) Session hardening ada apa saja
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Session cookie disetel dengan &lt;code&gt;httponly&lt;/code&gt; dan &lt;code&gt;samesite&lt;/code&gt;, lalu session ID diregenerasi saat login. Ini mengurangi risiko pencurian atau fixation sesi.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/config/bootstrap.php&lt;/code&gt;, &lt;code&gt;big-golf/controllers/AdminAuthController.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  31) Mitigasi brute force login ada atau tidak
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Ada pembatasan berbasis percobaan gagal dan lock sementara. Ini belum kelas enterprise, tapi sudah cukup untuk baseline proyek akademik.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/controllers/AdminAuthController.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  32) XSS dicegah bagaimana
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Output dinamis yang tampil di view di escape pakai &lt;code&gt;htmlspecialchars&lt;/code&gt;. Jadi data user tidak diproses sebagai HTML aktif.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/views/ulasan/halaman.php&lt;/code&gt;, &lt;code&gt;big-golf/views/admin/dashboard.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  33) Kenapa validasi input tetap wajib walau ada CSRF
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
CSRF melindungi asal request, bukan kualitas data. Validasi tetap wajib agar data konsisten dan aman diproses sistem.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/lib/InputValidator.php&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  G. QA, testing, dan maintainability
&lt;/h2&gt;

&lt;h3&gt;
  
  
  34) Bagian mana yang paling rawan regresi UI
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Biasanya navbar mobile, galeri lightbox, dan modal ulasan. Tiga area ini punya interaksi lebih banyak dibanding section statis.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/views/partials/navbar.php&lt;/code&gt;, &lt;code&gt;big-golf/public/assets/js/galeri.js&lt;/code&gt;, &lt;code&gt;big-golf/public/assets/js/ulasan-modal.js&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  35) Bagaimana cara cepat smoke test setelah perubahan
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Buka route utama, cek form kontak, kirim ulasan dummy, lalu verifikasi moderasi dari dashboard admin. Itu sudah menutup jalur kritis publik dan admin.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/config/router.php&lt;/code&gt;, &lt;code&gt;big-golf/views/admin/dashboard.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  36) Apa indikator quality yang kalian pakai
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Kami pakai indikator sederhana tapi konkret yaitu alur utama jalan, responsif dasar aman, input tervalidasi, dan konten publik terkurasi.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/views/layouts/utama.php&lt;/code&gt;, &lt;code&gt;big-golf/controllers/UlasanController.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  37) Kenapa struktur ini mudah dipelihara
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Karena alur file cukup jelas. Controller tahu ke model mana, view tahu layoutnya, dan endpoint aksi punya tanggung jawab tegas.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/controllers/&lt;/code&gt;, &lt;code&gt;big-golf/models/&lt;/code&gt;, &lt;code&gt;big-golf/views/&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  H. Performa dan pengalaman pengguna
&lt;/h2&gt;

&lt;h3&gt;
  
  
  38) Apa keputusan performa paling penting di frontend
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Kami menghindari framework berat di sisi client. Interaksi yang dibutuhkan ditangani script kecil yang fokus.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/public/assets/js/galeri.js&lt;/code&gt;, &lt;code&gt;big-golf/public/assets/js/beranda-ulasan.js&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  39) Kenapa gambar diakses lewat endpoint aset
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Agar jalur URL konsisten dengan web root dan lebih gampang dikontrol dari sisi server. Ini membantu menghindari masalah path saat deploy.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/public/galeri-aset.php&lt;/code&gt;, &lt;code&gt;big-golf/public/lokasi-aset.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  40) Bagaimana kalian menjaga interaksi tetap halus
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Kami batasi animasi pada bagian yang punya nilai UX jelas dan tetap sediakan fallback reduced motion. Fokusnya pengalaman nyaman, bukan efek berlebihan.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/public/assets/css/tema.css&lt;/code&gt;, &lt;code&gt;big-golf/views/beranda/index.php&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  I. Deployment, operasi, dan reasoning produk
&lt;/h2&gt;

&lt;h3&gt;
  
  
  41) Kalau pindah server apa yang paling diperhatikan
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Yang paling penting adalah document root ke &lt;code&gt;public&lt;/code&gt;, konfigurasi env database, dan URL publik Maps atau WA. Tiga ini kalau salah biasanya aplikasi jalan setengah.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/public/index.php&lt;/code&gt;, &lt;code&gt;big-golf/.env&lt;/code&gt;, &lt;code&gt;big-golf/config/url_publik.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  42) Kenapa fitur reservasi tidak dibuat booking slot penuh
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Karena kebutuhan bisnis aktual mengarah ke walk in plus komunikasi WhatsApp. Jadi kami pilih solusi yang relevan, bukan fitur kompleks yang tidak terpakai.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/views/lokasi_kontak/index.php&lt;/code&gt;, &lt;code&gt;big-golf/controllers/LokasiKontakController.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  43) Kenapa admin dipusatkan untuk moderasi ulasan
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Karena alur bisnis butuh kontrol kualitas konten publik. Dengan moderasi terpusat, risiko konten tidak layak bisa ditekan.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/views/admin/dashboard.php&lt;/code&gt;, &lt;code&gt;big-golf/models/UlasanModel.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  44) Apa kompromi teknis terbesar di proyek ini
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Komprominya ada pada kesederhanaan arsitektur versus fitur enterprise. Kami pilih pondasi yang cukup kuat untuk scope akademik tanpa menambah beban kompleksitas.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/config/router.php&lt;/code&gt;, &lt;code&gt;big-golf/controllers/AdminAuthController.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  45) Kalau disuruh lanjut versi berikutnya, prioritasnya apa
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban singkat&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Prioritasnya test automation dasar, monitoring error yang lebih jelas, dan hardening login di level global bukan hanya session scoped.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/controllers/AdminAuthController.php&lt;/code&gt;, &lt;code&gt;big-golf/views/admin/dashboard.php&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  J. Pertanyaan menjebak dan contoh jawaban aman
&lt;/h2&gt;

&lt;h3&gt;
  
  
  46) “Pakai Bootstrap berarti kalian tinggal copy template, ya”
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban aman&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Bootstrap kami pakai sebagai fondasi komponen, bukan jadi desain final. Identitas visual tetap kami bentuk sendiri di tema dan konten domain Big Golf.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/public/assets/css/tema.css&lt;/code&gt;, &lt;code&gt;big-golf/views/beranda/index.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  47) “Kalau native, pasti lebih tidak aman dari framework”
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban aman&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Yang menentukan aman atau tidak itu praktik implementasi. Di proyek ini kami pakai prepared statements, CSRF token, escaping output, dan session hardening.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/models/UlasanModel.php&lt;/code&gt;, &lt;code&gt;big-golf/controllers/AdminAuthController.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  48) “Kenapa tidak sekalian pakai React atau Next”
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban aman&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Untuk scope company profile plus moderasi ulasan, stack sekarang sudah pas dari sisi effort dan hasil. Kami memilih teknologi yang paling relevan, bukan paling ramai.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/views/layouts/utama.php&lt;/code&gt;, &lt;code&gt;big-golf/config/router.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  49) “Kalau ada CSRF, berarti pasti aman semua dong”
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban aman&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
CSRF hanya salah satu lapisan. Tetap perlu validasi input, kontrol auth, dan output escaping agar perlindungannya lengkap.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/lib/InputValidator.php&lt;/code&gt;, &lt;code&gt;big-golf/views/ulasan/halaman.php&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  50) “Kenapa data kontak tidak disimpan database saja”
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Jawaban aman&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Karena target operasionalnya komunikasi langsung ke WhatsApp. Jadi kami pilih alur yang paling dipakai di lapangan.&lt;br&gt;
&lt;strong&gt;Rujukan file&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;big-golf/controllers/LokasiKontakController.php&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Tips delivery supaya terdengar natural
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pakai kalimat sederhana seperti menjelaskan ke partner tim&lt;/li&gt;
&lt;li&gt;Hindari jawaban hafalan panjang&lt;/li&gt;
&lt;li&gt;Kalau bingung, balik ke tiga kata kunci yaitu alasan, implementasi, dan bukti file&lt;/li&gt;
&lt;li&gt;Saat ditanya tajam, tenang dulu lalu jawab dari sudut kebutuhan bisnis dan keamanan teknis&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  File standby saat presentasi
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;big-golf/config/router.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;big-golf/views/layouts/utama.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;big-golf/views/partials/navbar.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;big-golf/views/partials/footer.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;big-golf/controllers/UlasanController.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;big-golf/controllers/AdminAuthController.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;big-golf/models/UlasanModel.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;big-golf/lib/InputValidator.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;big-golf/public/assets/css/tema.css&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Gate dokumen
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pertanyaan diperluas lintas lingkup: terpenuhi&lt;/li&gt;
&lt;li&gt;Bahasa dibuat lebih natural untuk lisan sidang: terpenuhi&lt;/li&gt;
&lt;li&gt;Rujukan spesifik ke codebase: terpenuhi&lt;/li&gt;
&lt;li&gt;Rujukan di luar &lt;code&gt;big-golf/&lt;/code&gt;: tidak ada&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Status: LULUS&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>interview</category>
      <category>php</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Skrip Presentasi KNN</title>
      <dc:creator>Aris Candra Muzaffar</dc:creator>
      <pubDate>Tue, 05 May 2026 09:04:55 +0000</pubDate>
      <link>https://forem.com/ariscandra/skrip-presentasi-knn-2kh5</link>
      <guid>https://forem.com/ariscandra/skrip-presentasi-knn-2kh5</guid>
      <description>&lt;h2&gt;
  
  
  Judul Presentasi
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Jejak Digital Mahasiswa: Data Pribadi Kita Dipakai untuk Apa?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tujuan Presentasi (1 kalimat)
&lt;/h2&gt;

&lt;p&gt;Membantu audiens memahami bahwa jejak digital itu nyata, bernilai, berisiko, dan bisa dikelola lewat kebiasaan sederhana.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alokasi Waktu (Total 5 Menit)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aris (Pembuka + hook):&lt;/strong&gt; 0:00–0:50
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Yudha (Konteks data):&lt;/strong&gt; 0:50–1:40
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dewa (Risiko nyata):&lt;/strong&gt; 1:40–2:35
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jabbar (Hak pengguna + UU PDP):&lt;/strong&gt; 2:35–3:30
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pian (Solusi praktis + penutup CTA):&lt;/strong&gt; 3:30–5:00&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Skrip Lengkap (Siap Pakai)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1) Aris — Pembuka &amp;amp; Interaksi Awal (0:00–0:50)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Skrip:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Assalamu’alaikum, selamat [pagi/siang], teman-teman.  &lt;/p&gt;

&lt;p&gt;Coba angkat tangan, siapa yang hari ini sudah buka minimal tiga aplikasi: chat, media sosial, sama marketplace?  &lt;/p&gt;

&lt;p&gt;Oke, hampir semua, ya. Nah, tiap klik, tiap like, tiap lokasi yang kita izinkan, itu bukan aktivitas biasa aja. Itu semua ninggalin &lt;strong&gt;jejak digital&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;Pertanyaan intinya: &lt;strong&gt;data kita sebenarnya dipakai untuk apa?&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;Dalam 5 menit ini, kami mau bahas tiga hal singkat:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Seberapa besar paparan data kita,&lt;/li&gt;
&lt;li&gt;Risikonya buat mahasiswa,&lt;/li&gt;
&lt;li&gt;Dan kebiasaan sederhana supaya lebih aman.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Catatan delivery:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setelah “angkat tangan”, kasih jeda 2–3 detik, lihat audiens.
&lt;/li&gt;
&lt;li&gt;Tone santai tapi tetap tegas.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2) Yudha — Konteks Data (0:50–1:40)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Skrip:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Kita mulai dari gambaran besarnya dulu.  &lt;/p&gt;

&lt;p&gt;Menurut APJII 2024, pengguna internet di Indonesia sudah sekitar &lt;strong&gt;221 juta&lt;/strong&gt;, dengan penetrasi sekitar &lt;strong&gt;79,5%&lt;/strong&gt;. Artinya, mayoritas aktivitas kita sekarang memang terjadi di dunia maya.  &lt;/p&gt;

&lt;p&gt;Dan kelompok usia muda, termasuk usia mahasiswa, adalah pengguna paling aktif. Jadi, topik ini bukan isu jauh—ini isu harian kita sendiri.  &lt;/p&gt;

&lt;p&gt;Sekarang pertanyaan cepat: menurut kalian, data apa yang paling sering kita kasih ke aplikasi tanpa mikir panjang?  &lt;/p&gt;

&lt;p&gt;Biasanya jawabannya: nomor HP, lokasi, kontak, bahkan kebiasaan belanja atau jam online.  &lt;/p&gt;

&lt;p&gt;Dari sini kelihatan: makin praktis aplikasi, biasanya makin banyak data yang diminta.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Catatan delivery:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Saat tanya audiens, ambil 1 jawaban spontan kalau ada.
&lt;/li&gt;
&lt;li&gt;Kalau kelas pasif, langsung lanjut dengan “Biasanya jawabannya...”.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3) Dewa — Dampak Nyata (1:40–2:35)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Skrip:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Masalahnya bukan cuma “data terkumpul”, tapi &lt;strong&gt;data bisa bocor atau disalahgunakan&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;BSSN pernah menyampaikan bahwa dugaan insiden kebocoran data di Indonesia masih tinggi, dan sebagian besar menyasar layanan penting.  &lt;/p&gt;

&lt;p&gt;Buat kita sebagai mahasiswa, dampaknya bisa langsung terasa:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;spam dan phishing makin personal,
&lt;/li&gt;
&lt;li&gt;akun bisa diambil alih,
&lt;/li&gt;
&lt;li&gt;sampai penyalahgunaan identitas untuk pinjol atau scam.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sampai sini, kebayang ya: jejak digital bukan sekadar teori, tapi dampaknya bisa langsung kena ke hidup kita.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Catatan delivery:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fokus ke bahasa awam, jangan terlalu legalistik.
&lt;/li&gt;
&lt;li&gt;Hindari istilah teknis berlebihan.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  4) Jabbar — Hak Pengguna + UU PDP (2:35–3:30)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Skrip:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Kabar baiknya, Indonesia sudah punya &lt;strong&gt;UU Pelindungan Data Pribadi, yaitu UU No. 27 Tahun 2022&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;Artinya, kita sebagai pemilik data punya hak yang jelas:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;berhak tahu data kita dipakai untuk apa,
&lt;/li&gt;
&lt;li&gt;berhak minta perbaikan kalau data salah,
&lt;/li&gt;
&lt;li&gt;dan berhak dapat notifikasi kalau ada kebocoran data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Jadi, posisinya jelas: kita bukan objek pasif. Kita punya hak, tapi kita juga perlu aktif menjaga data kita sendiri.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Catatan delivery:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sampaikan dengan tempo tenang, jangan terdengar seperti baca pasal hukum.
&lt;/li&gt;
&lt;li&gt;Tekankan kata "hak" agar audiens merasa ini relevan buat dirinya.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  5) Pian — Solusi Praktis + Penutup Kuat (3:30–5:00)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Skrip:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Penutup dari kami: jejak digital itu nggak bisa dihapus total, tapi bisa &lt;strong&gt;dikelola&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;Tiga kebiasaan simpel yang bisa langsung kita mulai:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Pertama&lt;/strong&gt;, cek izin aplikasi sebelum klik “setuju”. Kalau minta akses yang nggak relevan, tolak.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Kedua&lt;/strong&gt;, pakai password berbeda untuk akun penting dan aktifkan 2FA.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Ketiga&lt;/strong&gt;, jangan asal unggah data pribadi—termasuk dokumen, tiket, atau info lokasi real-time.  &lt;/p&gt;

&lt;p&gt;Inti presentasi kami satu kalimat:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Di era sistem informasi, yang paling berharga bukan cuma perangkat kita, tapi data diri kita.&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;Terima kasih.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Catatan delivery:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tutup dengan tempo lebih lambat di kalimat inti.
&lt;/li&gt;
&lt;li&gt;Setelah “terima kasih”, semua anggota mengangguk/step maju sedikit (kompak visual).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Cadangan Kalimat (Jika Dosen Tanya)
&lt;/h2&gt;

&lt;p&gt;Gunakan jawaban singkat ini kalau ada pertanyaan mendadak.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;“Apa jejak digital pasti buruk?”&lt;/strong&gt;
Tidak. Jejak digital itu netral; bisa bermanfaat untuk personalisasi layanan, tapi jadi risiko kalau pengguna tidak sadar batas berbagi data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Kenapa mahasiswa harus peduli sekarang?”&lt;/strong&gt;
Karena mahasiswa adalah pengguna digital paling aktif, jadi paling sering terpapar phishing, social engineering, dan penyalahgunaan data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Solusi paling realistis satu apa?”&lt;/strong&gt;
Mulai dari audit izin aplikasi + aktifkan 2FA pada akun utama (email, kampus, keuangan).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Rujukan Data (untuk pegangan tim)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://apjii.or.id/berita/d/apjii-jumlah-pengguna-internet-indonesia-tembus-221-juta-orang" rel="noopener noreferrer"&gt;APJII 2024: Pengguna internet Indonesia 221 juta, penetrasi 79,5%&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.antaranews.com/berita/3790086/bssn-55-persen-kebocoran-data-terjadi-pada-administrasi-pemerintah" rel="noopener noreferrer"&gt;ANTARA (kutipan BSSN): 207 dugaan insiden kebocoran data pada 2023&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://peraturan.bpk.go.id/Details/229798/uu-no-27-tahun-2022" rel="noopener noreferrer"&gt;UU No. 27 Tahun 2022 tentang Pelindungan Data Pribadi (BPK)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>data</category>
      <category>privacy</category>
      <category>socialmedia</category>
    </item>
    <item>
      <title>Bank Tanya Jawab Pengujian</title>
      <dc:creator>Aris Candra Muzaffar</dc:creator>
      <pubDate>Fri, 01 May 2026 16:32:56 +0000</pubDate>
      <link>https://forem.com/ariscandra/bank-tanya-jawab-pengujian-j9m</link>
      <guid>https://forem.com/ariscandra/bank-tanya-jawab-pengujian-j9m</guid>
      <description>&lt;h1&gt;
  
  
  Bank Tanya Jawab Pengujian SaryPOS Berdasarkan Materi Praktikum
&lt;/h1&gt;

&lt;p&gt;Dokumen ini disusun untuk membantu sesi tanya jawab saat pengujian aplikasi &lt;strong&gt;SaryPOS&lt;/strong&gt;. Isi pertanyaan dan jawaban dirangkum dari materi:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;materi_praktikum/Pertemuan 1 Widget Dasar.pdf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;materi_praktikum/Pertemuan 2 State Management Dasar.pdf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;materi_praktikum/Modul 3 Navigation dan Data Flow Antar Halaman.pdf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;materi_praktikum/Modul 4 Supabase.pdf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;materi_praktikum/Pertemuan 5 Deployment App.pdf&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Tujuan dokumen ini adalah memberi jawaban yang mudah dipahami, tetap teknis, dan relevan dengan konteks aplikasi POS.&lt;/p&gt;




&lt;h2&gt;
  
  
  A. Pertanyaan Dasar Konsep Flutter dan Widget
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1) Apa itu widget pada Flutter?
&lt;/h3&gt;

&lt;p&gt;Widget adalah komponen penyusun antarmuka aplikasi. Hampir semua elemen visual di Flutter adalah widget, mulai dari teks, tombol, ikon, sampai seluruh halaman.&lt;/p&gt;

&lt;h3&gt;
  
  
  2) Mengapa konsep widget penting pada SaryPOS?
&lt;/h3&gt;

&lt;p&gt;Karena semua tampilan SaryPOS dibangun dari widget, seperti daftar produk, keranjang, tombol checkout, dan kartu ringkasan laporan.&lt;/p&gt;

&lt;h3&gt;
  
  
  3) Apa perbedaan &lt;code&gt;StatelessWidget&lt;/code&gt; dan &lt;code&gt;StatefulWidget&lt;/code&gt;?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;StatelessWidget&lt;/code&gt;: tampilan statis, tidak berubah setelah dibangun.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;StatefulWidget&lt;/code&gt;: tampilan dinamis, bisa berubah sesuai interaksi atau data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4) Kapan SaryPOS memakai &lt;code&gt;StatefulWidget&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;Saat tampilan harus berubah real-time, misalnya jumlah item keranjang, total harga, status proses transaksi, atau loading data.&lt;/p&gt;

&lt;h3&gt;
  
  
  5) Kapan &lt;code&gt;StatelessWidget&lt;/code&gt; lebih tepat?
&lt;/h3&gt;

&lt;p&gt;Untuk komponen presentasi yang hanya menampilkan data tanpa mengelola perubahan internal, misalnya judul section, label, atau kartu informasi sederhana.&lt;/p&gt;

&lt;h3&gt;
  
  
  6) Apa fungsi &lt;code&gt;Container&lt;/code&gt; dalam UI POS?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Container&lt;/code&gt; dipakai untuk pembungkus elemen agar mudah mengatur ukuran, warna, padding, margin, border, dan alignment.&lt;/p&gt;

&lt;h3&gt;
  
  
  7) Apa fungsi &lt;code&gt;Row&lt;/code&gt; dan &lt;code&gt;Column&lt;/code&gt;?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Row&lt;/code&gt;: menyusun komponen horizontal.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Column&lt;/code&gt;: menyusun komponen vertikal.
Dalam POS, kombinasi keduanya dipakai untuk layout tombol aksi dan ringkasan transaksi.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  8) Apa beda &lt;code&gt;padding&lt;/code&gt; dan &lt;code&gt;margin&lt;/code&gt;?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;padding&lt;/code&gt;: jarak di dalam komponen.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;margin&lt;/code&gt;: jarak antar komponen.
Analogi: padding itu ruang di dalam kotak, margin itu jarak antar kotak.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  9) Mengapa pemilihan widget interaksi penting?
&lt;/h3&gt;

&lt;p&gt;Karena memengaruhi kecepatan kasir dan meminimalkan salah tekan. Tombol aksi utama harus jelas terlihat, ukuran sentuh nyaman, dan responsif.&lt;/p&gt;

&lt;h3&gt;
  
  
  10) Mengapa layout rapi penting dalam aplikasi kasir?
&lt;/h3&gt;

&lt;p&gt;Karena kasir bekerja cepat. Layout yang rapi membuat input lebih cepat, kesalahan lebih sedikit, dan pengalaman pengguna lebih stabil saat jam sibuk.&lt;/p&gt;




&lt;h2&gt;
  
  
  B. Pertanyaan State Management Dasar
&lt;/h2&gt;

&lt;h3&gt;
  
  
  11) Apa itu state?
&lt;/h3&gt;

&lt;p&gt;State adalah data yang dapat berubah selama aplikasi berjalan dan memengaruhi tampilan UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  12) Apa itu state management?
&lt;/h3&gt;

&lt;p&gt;State management adalah cara mengelola perubahan state agar UI tetap sinkron dengan data terbaru.&lt;/p&gt;

&lt;h3&gt;
  
  
  13) Apa bedanya Ephemeral State dan App State?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Ephemeral State: state lokal pada satu widget/halaman.&lt;/li&gt;
&lt;li&gt;App State: state global yang dipakai banyak bagian aplikasi.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  14) Contoh Ephemeral State di SaryPOS?
&lt;/h3&gt;

&lt;p&gt;Nilai input pencarian produk, toggle filter sementara, atau status expand/collapse panel di satu halaman.&lt;/p&gt;

&lt;h3&gt;
  
  
  15) Contoh App State di SaryPOS?
&lt;/h3&gt;

&lt;p&gt;Data sesi pengguna, tema aplikasi, dan informasi peran owner atau karyawan yang memengaruhi banyak layar.&lt;/p&gt;

&lt;h3&gt;
  
  
  16) Kapan menggunakan &lt;code&gt;setState()&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;Saat data lokal widget berubah dan perlu memicu rebuild UI pada widget tersebut.&lt;/p&gt;

&lt;h3&gt;
  
  
  17) Apakah &lt;code&gt;setState()&lt;/code&gt; cukup untuk semua kebutuhan?
&lt;/h3&gt;

&lt;p&gt;Tidak selalu. Untuk aplikasi besar, state global lebih baik dikelola dengan pola yang lebih terstruktur agar mudah dipelihara.&lt;/p&gt;

&lt;h3&gt;
  
  
  18) Mengapa state management penting untuk aplikasi POS?
&lt;/h3&gt;

&lt;p&gt;Karena transaksi butuh akurasi real-time. Jika state berantakan, subtotal, stok, atau status transaksi bisa tidak konsisten.&lt;/p&gt;

&lt;h3&gt;
  
  
  19) Apa risiko jika state tidak sinkron?
&lt;/h3&gt;

&lt;p&gt;UI bisa menampilkan nilai lama, total salah hitung, atau aksi pengguna tidak tercermin di tampilan.&lt;/p&gt;

&lt;h3&gt;
  
  
  20) Bagaimana menjelaskan state management dengan analogi sederhana?
&lt;/h3&gt;

&lt;p&gt;State seperti papan skor pertandingan. Saat skor berubah, papan harus langsung diperbarui agar semua orang melihat data yang benar.&lt;/p&gt;




&lt;h2&gt;
  
  
  C. Pertanyaan Navigasi dan Data Flow Antar Halaman
&lt;/h2&gt;

&lt;h3&gt;
  
  
  21) Apa itu navigation?
&lt;/h3&gt;

&lt;p&gt;Navigation adalah perpindahan antar halaman (screen) dalam aplikasi.&lt;/p&gt;

&lt;h3&gt;
  
  
  22) Apa itu data flow antar halaman?
&lt;/h3&gt;

&lt;p&gt;Data flow adalah proses mengirim data ke halaman tujuan dan menerima data balik ke halaman asal.&lt;/p&gt;

&lt;h3&gt;
  
  
  23) Apa perbedaan navigasi native Flutter dan GetX?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Native Flutter memakai &lt;code&gt;Navigator&lt;/code&gt; dan &lt;code&gt;BuildContext&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;GetX menyediakan cara lebih ringkas untuk route, state, dan dependency.
Pada SaryPOS, pendekatannya dapat bersifat hybrid sesuai kebutuhan modul.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  24) Apa fungsi &lt;code&gt;push&lt;/code&gt; dalam alur POS?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;push&lt;/code&gt; membuka halaman baru tanpa menghapus halaman lama, misalnya dari daftar produk ke detail produk.&lt;/p&gt;

&lt;h3&gt;
  
  
  25) Apa fungsi &lt;code&gt;pop&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;pop&lt;/code&gt; menutup halaman aktif dan kembali ke halaman sebelumnya.&lt;/p&gt;

&lt;h3&gt;
  
  
  26) Apa itu &lt;code&gt;off&lt;/code&gt; (GetX)?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;off&lt;/code&gt; mengganti halaman saat ini dan menghapus satu halaman sebelumnya dari stack.&lt;/p&gt;

&lt;h3&gt;
  
  
  27) Apa itu &lt;code&gt;offAll&lt;/code&gt; (GetX)?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;offAll&lt;/code&gt; membersihkan seluruh stack dan menyisakan halaman tujuan, cocok setelah login sukses agar tidak kembali ke halaman auth.&lt;/p&gt;

&lt;h3&gt;
  
  
  28) Bagaimana cara kirim data ke halaman detail?
&lt;/h3&gt;

&lt;p&gt;Data dikirim lewat parameter constructor saat pindah halaman.&lt;/p&gt;

&lt;h3&gt;
  
  
  29) Bagaimana mengembalikan data ke halaman asal?
&lt;/h3&gt;

&lt;p&gt;Gunakan hasil balik saat &lt;code&gt;pop&lt;/code&gt; atau &lt;code&gt;Get.back(result: ...)&lt;/code&gt; lalu tangkap hasilnya di halaman pemanggil.&lt;/p&gt;

&lt;h3&gt;
  
  
  30) Contoh skenario data flow di SaryPOS?
&lt;/h3&gt;

&lt;p&gt;Halaman checkout membuka dialog diskon, dialog mengembalikan nilai diskon, lalu total pembayaran dihitung ulang di checkout.&lt;/p&gt;

&lt;h3&gt;
  
  
  31) Mengapa manajemen stack penting?
&lt;/h3&gt;

&lt;p&gt;Agar alur back navigation konsisten. Pengguna tidak boleh kembali ke layar yang seharusnya sudah selesai (misalnya layar login setelah masuk).&lt;/p&gt;

&lt;h3&gt;
  
  
  32) Analogi sederhana stack navigasi?
&lt;/h3&gt;

&lt;p&gt;Seperti tumpukan kertas: halaman baru ditaruh di atas (&lt;code&gt;push&lt;/code&gt;), saat kembali kita ambil yang paling atas (&lt;code&gt;pop&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  D. Pertanyaan Supabase dan Integrasi ke Aplikasi
&lt;/h2&gt;

&lt;h3&gt;
  
  
  33) Apa itu Supabase?
&lt;/h3&gt;

&lt;p&gt;Supabase adalah platform backend-as-a-service berbasis PostgreSQL yang menyediakan database, auth, storage, dan API siap pakai.&lt;/p&gt;

&lt;h3&gt;
  
  
  34) Kenapa Supabase cocok untuk SaryPOS?
&lt;/h3&gt;

&lt;p&gt;Karena mempercepat pengembangan backend tanpa membangun server dari nol, namun tetap kuat untuk CRUD data bisnis.&lt;/p&gt;

&lt;h3&gt;
  
  
  35) Fitur Supabase apa yang paling relevan?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Database PostgreSQL&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Auto API&lt;/li&gt;
&lt;li&gt;Storage&lt;/li&gt;
&lt;li&gt;Realtime (opsional sesuai kebutuhan fitur)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  36) Data apa saja yang umum dikelola di Supabase untuk POS?
&lt;/h3&gt;

&lt;p&gt;Data pengguna, produk, stok, transaksi penjualan, dan log aktivitas.&lt;/p&gt;

&lt;h3&gt;
  
  
  37) Apa fungsi URL project Supabase?
&lt;/h3&gt;

&lt;p&gt;Sebagai alamat endpoint agar aplikasi Flutter terhubung ke project backend yang benar.&lt;/p&gt;

&lt;h3&gt;
  
  
  38) Apa fungsi anon/publishable key?
&lt;/h3&gt;

&lt;p&gt;Sebagai kredensial client-side untuk akses aman yang tetap dibatasi oleh aturan auth dan RLS.&lt;/p&gt;

&lt;h3&gt;
  
  
  39) Apa beda publishable key dan secret/service role key?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Publishable key: untuk aplikasi client.&lt;/li&gt;
&lt;li&gt;Secret/service role key: hak akses tinggi, hanya untuk server aman, tidak boleh disimpan di aplikasi publik.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  40) Kenapa tidak boleh menaruh service role key di aplikasi mobile?
&lt;/h3&gt;

&lt;p&gt;Karena berisiko bocor lewat reverse engineering dan dapat membuka akses data berbahaya.&lt;/p&gt;

&lt;h3&gt;
  
  
  41) Apa itu CRUD dalam konteks Supabase?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create: tambah data&lt;/li&gt;
&lt;li&gt;Read: baca data&lt;/li&gt;
&lt;li&gt;Update: ubah data&lt;/li&gt;
&lt;li&gt;Delete: hapus/nonaktifkan data&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  42) Contoh CRUD di SaryPOS?
&lt;/h3&gt;

&lt;p&gt;Tambah produk baru, tampilkan daftar produk, ubah harga, dan hapus produk yang tidak lagi dijual.&lt;/p&gt;

&lt;h3&gt;
  
  
  43) Apa itu RLS (Row Level Security)?
&lt;/h3&gt;

&lt;p&gt;RLS adalah aturan akses level baris data untuk memastikan user hanya bisa mengakses data yang diizinkan.&lt;/p&gt;

&lt;h3&gt;
  
  
  44) Kenapa RLS penting meskipun menu sudah disembunyikan?
&lt;/h3&gt;

&lt;p&gt;Karena keamanan tidak boleh hanya mengandalkan UI. Pembatasan utama harus terjadi di backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  45) Contoh penerapan role owner vs karyawan di Supabase?
&lt;/h3&gt;

&lt;p&gt;Owner bisa mengelola data strategis (produk, karyawan, laporan), karyawan fokus ke transaksi operasional sesuai izin.&lt;/p&gt;

&lt;h3&gt;
  
  
  46) Apa yang dilakukan jika data tidak muncul saat demo?
&lt;/h3&gt;

&lt;p&gt;Lakukan cek berurutan: status login, role user, policy RLS, query/filter, lalu koneksi project Supabase yang dipakai.&lt;/p&gt;




&lt;h2&gt;
  
  
  E. Pertanyaan Deployment Aplikasi Android
&lt;/h2&gt;

&lt;h3&gt;
  
  
  47) Persiapan minimum sebelum deployment?
&lt;/h3&gt;

&lt;p&gt;Pastikan aplikasi stabil di debug, &lt;code&gt;flutter doctor&lt;/code&gt; bersih, ikon aplikasi sudah disesuaikan, dan konfigurasi build benar.&lt;/p&gt;

&lt;h3&gt;
  
  
  48) Mengapa &lt;code&gt;flutter doctor&lt;/code&gt; penting sebelum build release?
&lt;/h3&gt;

&lt;p&gt;Karena memastikan environment toolchain Android/Flutter siap, sehingga mengurangi risiko gagal build.&lt;/p&gt;

&lt;h3&gt;
  
  
  49) Kenapa app icon dan app name perlu disesuaikan?
&lt;/h3&gt;

&lt;p&gt;Agar aplikasi siap dipresentasikan atau didistribusikan secara profesional, bukan tampilan default template.&lt;/p&gt;

&lt;h3&gt;
  
  
  50) Apa yang perlu dicek di konfigurasi Android sebelum rilis?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;applicationId&lt;/code&gt;, &lt;code&gt;versionCode&lt;/code&gt;, &lt;code&gt;versionName&lt;/code&gt;, permission yang diperlukan saja, dan label aplikasi.&lt;/p&gt;

&lt;h3&gt;
  
  
  51) Kenapa pengujian release penting, bukan hanya debug?
&lt;/h3&gt;

&lt;p&gt;Karena perilaku release bisa berbeda (optimasi, minify, konfigurasi env), jadi validasi final wajib dilakukan di mode release.&lt;/p&gt;

&lt;h3&gt;
  
  
  52) Apa checklist uji cepat sebelum APK dibagikan?
&lt;/h3&gt;

&lt;p&gt;Login berhasil, CRUD utama berjalan, transaksi selesai, data sinkron ke backend, logout aman, dan tidak ada crash.&lt;/p&gt;




&lt;h2&gt;
  
  
  F. Pertanyaan Integratif Khusus SaryPOS (Lintas Materi)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  53) Bagaimana hubungan widget, state, dan navigation dalam satu alur transaksi?
&lt;/h3&gt;

&lt;p&gt;Widget membentuk UI, state menyimpan perubahan data transaksi, navigation mengatur perpindahan layar. Ketiganya harus sinkron agar alur kasir lancar.&lt;/p&gt;

&lt;h3&gt;
  
  
  54) Mengapa arsitektur yang rapi memudahkan pengujian?
&lt;/h3&gt;

&lt;p&gt;Karena pemisahan tanggung jawab membuat bug lebih cepat dilacak: UI, logika state, dan data backend bisa diuji bertahap.&lt;/p&gt;

&lt;h3&gt;
  
  
  55) Jika dosen bertanya “buktinya ini bukan hanya UI”, jawab apa?
&lt;/h3&gt;

&lt;p&gt;Jelaskan bahwa data transaksi tersimpan ke backend Supabase, dapat dibaca ulang, dan dibatasi aturan role/RLS, jadi bukan sekadar tampilan.&lt;/p&gt;

&lt;h3&gt;
  
  
  56) Jika ditanya “apa kontribusi GetX di proyek ini?”, jawab apa?
&lt;/h3&gt;

&lt;p&gt;GetX dipakai selektif untuk mempermudah bagian tertentu (misalnya route/dialog), sementara alur lain tetap bisa memakai Navigator native sesuai pola proyek.&lt;/p&gt;

&lt;h3&gt;
  
  
  57) Jika ditanya “bagaimana menjaga konsistensi data saat banyak interaksi?”, jawab apa?
&lt;/h3&gt;

&lt;p&gt;Dengan state management yang disiplin, pemisahan state lokal dan global, serta sinkronisasi operasi CRUD ke backend secara terkontrol.&lt;/p&gt;

&lt;h3&gt;
  
  
  58) Jika ditanya “bagaimana mitigasi kesalahan kasir?”, jawab apa?
&lt;/h3&gt;

&lt;p&gt;Gunakan UI yang jelas, validasi input, konfirmasi aksi penting, update state real-time, dan fallback error message yang informatif.&lt;/p&gt;

&lt;h3&gt;
  
  
  59) Jika ditanya “kenapa memilih Supabase dibanding backend manual?”, jawab apa?
&lt;/h3&gt;

&lt;p&gt;Karena lebih cepat untuk pengembangan akademik, tetap mendukung praktik backend modern (auth, database, policy), dan fokus tim bisa ke kualitas fitur aplikasi.&lt;/p&gt;

&lt;h3&gt;
  
  
  60) Jika ditanya “apa risiko terbesar saat demo live?”, jawab apa?
&lt;/h3&gt;

&lt;p&gt;Risiko terbesar ada pada environment (network, key salah, policy) dan alur data. Solusinya adalah checklist pra-demo dan skenario fallback yang sudah disiapkan.&lt;/p&gt;




&lt;h2&gt;
  
  
  G. Tips Jawaban Saat Pengujian
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Mulai dari jawaban singkat 1 kalimat, lalu lanjut teknis bila diminta.&lt;/li&gt;
&lt;li&gt;Gunakan contoh langsung dari alur SaryPOS, bukan contoh umum saja.&lt;/li&gt;
&lt;li&gt;Jika ada kekurangan, sampaikan jujur lalu jelaskan rencana perbaikan.&lt;/li&gt;
&lt;li&gt;Hindari jawaban terlalu teoritis tanpa kaitan implementasi.&lt;/li&gt;
&lt;li&gt;Tekankan bahwa keamanan akses bukan hanya di UI, tetapi juga di backend.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  H. Template Jawaban Cepat (Bisa Dihafal)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Definisi singkat:&lt;/strong&gt; “X adalah ... yang berfungsi untuk ...”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implementasi di SaryPOS:&lt;/strong&gt; “Di SaryPOS, ini dipakai pada fitur ...”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alasan pemilihan:&lt;/strong&gt; “Dipilih karena ... (efisiensi, konsistensi, keamanan).”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risiko jika salah:&lt;/strong&gt; “Jika tidak benar, dampaknya ...”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Penutup aman:&lt;/strong&gt; “Karena itu kami validasi lewat pengujian ...”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dengan pola ini, jawaban tetap terstruktur walaupun pertanyaannya spontan.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>mobile</category>
      <category>testing</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Teks Pegangan</title>
      <dc:creator>Aris Candra Muzaffar</dc:creator>
      <pubDate>Fri, 01 May 2026 13:23:58 +0000</pubDate>
      <link>https://forem.com/ariscandra/teks-pegangan-44e</link>
      <guid>https://forem.com/ariscandra/teks-pegangan-44e</guid>
      <description>&lt;h1&gt;
  
  
  Teks Pegangan Presentasi - Radar Pramuka (Audit Final)
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Nama Kelompok:&lt;/strong&gt; Radar Pramuka&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Durasi target:&lt;/strong&gt; 10 menit&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Tema:&lt;/strong&gt; Pengujian Keamanan Jaringan pada Web Dummy DesignNet Studio&lt;/p&gt;
&lt;h2&gt;
  
  
  1) Run Sheet 10 Menit (Urut Slide Deck)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Waktu&lt;/th&gt;
&lt;th&gt;Durasi&lt;/th&gt;
&lt;th&gt;Pembicara&lt;/th&gt;
&lt;th&gt;Slide&lt;/th&gt;
&lt;th&gt;Fokus&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;00:00-00:50&lt;/td&gt;
&lt;td&gt;50 detik&lt;/td&gt;
&lt;td&gt;Dimas&lt;/td&gt;
&lt;td&gt;1-3&lt;/td&gt;
&lt;td&gt;Pembuka, tujuan, alur&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;00:50-02:30&lt;/td&gt;
&lt;td&gt;1:40&lt;/td&gt;
&lt;td&gt;Aris&lt;/td&gt;
&lt;td&gt;4-5&lt;/td&gt;
&lt;td&gt;Latar belakang, tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;02:30-03:40&lt;/td&gt;
&lt;td&gt;1:10&lt;/td&gt;
&lt;td&gt;Ariel&lt;/td&gt;
&lt;td&gt;6-7&lt;/td&gt;
&lt;td&gt;Pengantar hasil, reconnaissance &amp;amp; scanning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;03:40-04:50&lt;/td&gt;
&lt;td&gt;1:10&lt;/td&gt;
&lt;td&gt;Aripin&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Web dummy &amp;amp; analisis trafik&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;04:50-06:00&lt;/td&gt;
&lt;td&gt;1:10&lt;/td&gt;
&lt;td&gt;Ariel&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Eksploitasi dasar (reverse/bind shell)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;06:00-07:30&lt;/td&gt;
&lt;td&gt;1:30&lt;/td&gt;
&lt;td&gt;Aripin&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Validasi dengan Metasploit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;07:30-09:30&lt;/td&gt;
&lt;td&gt;2:00&lt;/td&gt;
&lt;td&gt;Dimas&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Analisis pertahanan sistem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;09:30-10:00&lt;/td&gt;
&lt;td&gt;30 detik&lt;/td&gt;
&lt;td&gt;Dimas&lt;/td&gt;
&lt;td&gt;12-13&lt;/td&gt;
&lt;td&gt;Penutup dan Q&amp;amp;A&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Catatan proporsi:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Urutan sudah mengikuti deck: &lt;code&gt;1 -&amp;gt; 2 -&amp;gt; 3 -&amp;gt; 4 -&amp;gt; 5 -&amp;gt; 6 -&amp;gt; 7 -&amp;gt; 8 -&amp;gt; 9 -&amp;gt; 10 -&amp;gt; 11 -&amp;gt; 12 -&amp;gt; 13&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Pembagian porsi tetap sesuai instruksi anggota.&lt;/li&gt;
&lt;li&gt;Handoff antar pembicara dibuat ringkas agar tidak mengganggu ritme.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  2) Script Ringkas per Pembicara (Siap Ucap)
&lt;/h2&gt;
&lt;h2&gt;
  
  
  A) Dimas - Pembuka (Slide 1-3 | 50 detik)
&lt;/h2&gt;

&lt;p&gt;Selamat [pagi/siang], kami dari kelompok &lt;strong&gt;Radar Pramuka&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Pada sesi ini, kami menyampaikan hasil pengujian keamanan jaringan pada studi kasus &lt;strong&gt;DesignNet Studio&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Fokus kami ada tiga: &lt;strong&gt;temuan kerentanan, dampak, dan mitigasi realistis&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Alurnya singkat: latar belakang, tools, hasil uji teknis, lalu rekomendasi pertahanan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transisi:&lt;/strong&gt; Saya persilakan Aris menjelaskan latar belakang dan perangkat yang kami gunakan.&lt;/p&gt;
&lt;h2&gt;
  
  
  B) Aris - Latar Belakang dan Tools (Slide 4-5 | 1 menit 40 detik)
&lt;/h2&gt;

&lt;p&gt;Risiko keamanan infrastruktur digital terus meningkat, termasuk pada studio desain yang menyimpan aset dan data klien.&lt;br&gt;&lt;br&gt;
Karena itu, kami melakukan simulasi penetration testing di lingkungan uji yang terkontrol dan etis.&lt;/p&gt;

&lt;p&gt;Yang kami ukur ada tiga: seberapa mudah attack surface dipetakan, apakah ada jalur eskalasi, dan seberapa kuat kontrol pertahanan awal.&lt;/p&gt;

&lt;p&gt;Tools yang kami pakai: &lt;strong&gt;Kali Linux, Nmap, Wireshark, Netcat, Metasploit&lt;/strong&gt;, dan web dummy berbasis &lt;strong&gt;Apache/MySQL/Docker&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Kombinasi ini dipakai dari tahap discovery sampai validasi dampak.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transisi:&lt;/strong&gt; Berikutnya Ariel membuka bagian hasil pengujian dan menjelaskan information gathering serta network scanning.&lt;/p&gt;
&lt;h2&gt;
  
  
  C) Ariel - Pengantar Hasil + Recon &amp;amp; Scanning (Slide 6-7 | 1 menit 10 detik)
&lt;/h2&gt;

&lt;p&gt;Pada bagian hasil, kami menyusun alur dari pemetaan jaringan sampai validasi dampak.&lt;br&gt;&lt;br&gt;
Tahap awal adalah mengidentifikasi host dan service aktif.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip a
nmap &lt;span class="nt"&gt;-sV&lt;/span&gt; &lt;span class="nt"&gt;-sC&lt;/span&gt; 192.168.6.216
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ip a&lt;/code&gt; kami gunakan untuk verifikasi interface mesin penguji, sedangkan enumerasi target dilakukan oleh Nmap.&lt;br&gt;&lt;br&gt;
Hasil scanning menunjukkan port penting terbuka, termasuk MySQL (&lt;code&gt;3306&lt;/code&gt;) pada host uji.&lt;br&gt;&lt;br&gt;
Temuan ini menjadi dasar pemilihan jalur uji berikutnya.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transisi:&lt;/strong&gt; Selanjutnya Aripin membahas web dummy dan analisis trafik jaringan.&lt;/p&gt;
&lt;h2&gt;
  
  
  D) Aripin - Web Dummy &amp;amp; Analisis Trafik (Slide 8 | 1 menit 10 detik)
&lt;/h2&gt;

&lt;p&gt;Di sisi aplikasi, kami observasi request/response web dummy dengan Wireshark untuk menilai paparan data saat transit.&lt;br&gt;&lt;br&gt;
Analisis ini kami korelasikan dengan hasil scanning agar terlihat jalur risiko dari layer jaringan ke layer aplikasi.&lt;/p&gt;

&lt;p&gt;Pada segmen uji yang kami amati, terlihat indikasi risiko ketika kanal komunikasi belum diamankan konsisten dengan TLS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transisi:&lt;/strong&gt; Saya kembalikan ke Ariel untuk validasi eksploitasi dasar.&lt;/p&gt;
&lt;h2&gt;
  
  
  E) Ariel - Eksploitasi Dasar (Slide 9 | 1 menit 10 detik)
&lt;/h2&gt;

&lt;p&gt;Pada tahap validasi dampak, kami mensimulasikan skenario &lt;strong&gt;reverse shell&lt;/strong&gt; dan &lt;strong&gt;bind shell&lt;/strong&gt; pada lingkungan uji terkontrol.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;whoami
hostname
&lt;/span&gt;ipconfig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Jika shell aktif dan perintah dasar merespons, ini menunjukkan potensi akses command-level bila ada celah eksekusi perintah jarak jauh.&lt;br&gt;&lt;br&gt;
Untuk host non-Windows, verifikasi jaringan dapat memakai &lt;code&gt;ip a&lt;/code&gt; atau &lt;code&gt;ifconfig&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transisi:&lt;/strong&gt; Berikutnya Aripin menampilkan validasi lanjutan menggunakan Metasploit.&lt;/p&gt;
&lt;h2&gt;
  
  
  F) Aripin - Validasi Metasploit (Slide 10 | 1 menit 30 detik)
&lt;/h2&gt;

&lt;p&gt;Pada tahap ini, kami melakukan uji &lt;strong&gt;kredensial lemah/default&lt;/strong&gt; pada layanan MySQL.&lt;br&gt;&lt;br&gt;
Keberhasilan login menunjukkan kelemahan manajemen kredensial dan kontrol akses, bukan otomatis membuktikan CVE tertentu.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;msfconsole
use auxiliary/scanner/mysql/mysql_login
&lt;span class="nb"&gt;set &lt;/span&gt;RHOSTS 192.168.6.183
&lt;span class="nb"&gt;set &lt;/span&gt;USERNAME root
&lt;span class="nb"&gt;set &lt;/span&gt;PASSWORD root
&lt;span class="nb"&gt;set &lt;/span&gt;CreateSession &lt;span class="nb"&gt;true
&lt;/span&gt;run
sessions &lt;span class="nt"&gt;-i&lt;/span&gt; 1
query show databases&lt;span class="p"&gt;;&lt;/span&gt;
query use designnet&lt;span class="p"&gt;;&lt;/span&gt;
query show tables&lt;span class="p"&gt;;&lt;/span&gt;
query &lt;span class="k"&gt;select &lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;, username from &lt;span class="nb"&gt;users &lt;/span&gt;limit 3&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Jika sesi terbuka dan query minimal berhasil, dampaknya langsung ke kerahasiaan data ketika kontrol akses tidak ketat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transisi:&lt;/strong&gt; Saya kembalikan ke Dimas untuk analisis pertahanan dan penutup.&lt;/p&gt;

&lt;h2&gt;
  
  
  G) Dimas - Analisis Pertahanan (Slide 11 | 2 menit)
&lt;/h2&gt;

&lt;p&gt;Tiga temuan prioritas dari pengujian:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Eksposur service penting, termasuk MySQL pada port &lt;code&gt;3306&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Indikasi risiko kebocoran data login pada segmen komunikasi yang belum terenkripsi konsisten.&lt;/li&gt;
&lt;li&gt;Kebutuhan hardening konfigurasi dan pembaruan komponen secara berkala.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Rekomendasi yang kami prioritaskan:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Batasi akses jaringan:&lt;/strong&gt; batasi &lt;code&gt;3306&lt;/code&gt; hanya dari host aplikasi yang sah melalui firewall/ACL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amankan data in transit:&lt;/strong&gt; terapkan TLS dengan konfigurasi benar pada kanal web/login.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amankan data dan identitas:&lt;/strong&gt; simpan password dengan hash kuat, nonaktifkan akun default, dan rotasi secret.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disiplin operasional:&lt;/strong&gt; patch rutin, logging, dan monitoring untuk deteksi dini.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Intinya, keamanan bukan pekerjaan sekali selesai, tetapi proses berkelanjutan.&lt;/p&gt;

&lt;h2&gt;
  
  
  H) Dimas - Penutup (Slide 12-13 | 30 detik)
&lt;/h2&gt;

&lt;p&gt;Kesimpulannya, sistem uji masih memiliki celah yang dapat dieksploitasi.&lt;br&gt;&lt;br&gt;
Namun, risiko dapat diturunkan signifikan dengan kontrol dasar yang diterapkan secara konsisten.&lt;/p&gt;

&lt;p&gt;Terima kasih atas perhatian Bapak/Ibu dan rekan-rekan.&lt;br&gt;&lt;br&gt;
Kami siap masuk ke sesi tanya jawab.&lt;/p&gt;

&lt;h2&gt;
  
  
  3) Cue Cepat per Slide (1 Baris, Urut Deck)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Slide 1:&lt;/strong&gt; salam, identitas tim, konteks proyek.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slide 2:&lt;/strong&gt; tiga tujuan: temuan, dampak, mitigasi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slide 3:&lt;/strong&gt; peta alur presentasi 10 menit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slide 4:&lt;/strong&gt; kenapa kasus ini penting dan etis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slide 5:&lt;/strong&gt; tools dan fungsi singkat masing-masing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slide 6:&lt;/strong&gt; pengantar hasil dan urutan tahap pengujian.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slide 7:&lt;/strong&gt; identifikasi host/service dan hasil scanning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slide 8:&lt;/strong&gt; analisis trafik web dummy dan indikasi risiko transit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slide 9:&lt;/strong&gt; simulasi reverse/bind shell pada lingkungan uji.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slide 10:&lt;/strong&gt; validasi kredensial lemah melalui Metasploit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slide 11:&lt;/strong&gt; prioritas mitigasi yang bisa langsung diterapkan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slide 12:&lt;/strong&gt; kesimpulan 1 kalimat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slide 13:&lt;/strong&gt; terima kasih dan buka Q&amp;amp;A.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4) Aturan Pacing Saat Latihan
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Targetkan &lt;strong&gt;1 kalimat = 6-10 detik&lt;/strong&gt; saat bicara normal.&lt;/li&gt;
&lt;li&gt;Maksimum &lt;strong&gt;2 kalimat utama per slide&lt;/strong&gt; sebelum transisi.&lt;/li&gt;
&lt;li&gt;Jika lewat waktu, potong detail command, jangan potong kesimpulan.&lt;/li&gt;
&lt;li&gt;Gunakan pola jawab Q&amp;amp;A: &lt;strong&gt;temuan -&amp;gt; dampak -&amp;gt; mitigasi&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>networking</category>
      <category>security</category>
      <category>testing</category>
    </item>
    <item>
      <title>Panduan Pengujian: Implementasi Widget, Properti, dan Arsitektur SaryPOS</title>
      <dc:creator>Aris Candra Muzaffar</dc:creator>
      <pubDate>Mon, 13 Apr 2026 21:26:01 +0000</pubDate>
      <link>https://forem.com/ariscandra/panduan-pengujian-implementasi-widget-properti-dan-arsitektur-sarypos-hp1</link>
      <guid>https://forem.com/ariscandra/panduan-pengujian-implementasi-widget-properti-dan-arsitektur-sarypos-hp1</guid>
      <description>&lt;p&gt;Dokumen ini merangkum hasil audit codebase aplikasi &lt;strong&gt;SaryPOS&lt;/strong&gt; (folder &lt;code&gt;sarypos/&lt;/code&gt;) untuk persiapan demonstrasi dan tanya jawab dengan dosen. Setiap bagian menyertakan &lt;strong&gt;lokasi berkas&lt;/strong&gt; dan &lt;strong&gt;cuplikan kode&lt;/strong&gt; yang dapat Anda tunjukkan langsung di IDE.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Konteks proyek:&lt;/strong&gt; aplikasi POS (Point of Sale) ritel untuk Sary Mart, dibuat dengan &lt;strong&gt;Flutter&lt;/strong&gt; dan backend &lt;strong&gt;Supabase&lt;/strong&gt; (PostgreSQL + Storage), dengan pemisahan peran &lt;strong&gt;owner&lt;/strong&gt; vs &lt;strong&gt;karyawan/kasir&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Ringkasan arsitektur (kalimat untuk pembuka presentasi)
&lt;/h2&gt;

&lt;p&gt;SaryPOS memuat konfigurasi dan font di &lt;code&gt;main&lt;/code&gt;, menginisialisasi Supabase, lalu menjalankan &lt;code&gt;GetMaterialApp&lt;/code&gt; dengan &lt;strong&gt;tumpukan warisan (InheritedNotifier)&lt;/strong&gt; untuk tema, sesi pengguna, dan notifikasi in-app. &lt;strong&gt;State utama&lt;/strong&gt; dikelola lewat &lt;code&gt;ChangeNotifier&lt;/code&gt; (&lt;code&gt;PengaturSesi&lt;/code&gt;, &lt;code&gt;PengaturTema&lt;/code&gt;, &lt;code&gt;PengelolaNotifikasiInApp&lt;/code&gt;) dan diwariskan ke seluruh pohon widget. &lt;strong&gt;Navigasi tab&lt;/strong&gt; ada di &lt;code&gt;HalamanUtamaDenganNav&lt;/code&gt; (&lt;code&gt;IndexedStack&lt;/code&gt;); &lt;strong&gt;halaman detail&lt;/strong&gt; banyak memakai &lt;code&gt;Navigator.push&lt;/code&gt; + &lt;code&gt;MaterialPageRoute&lt;/code&gt;, sementara &lt;strong&gt;guard owner&lt;/strong&gt; memakai &lt;strong&gt;GetX&lt;/strong&gt; (&lt;code&gt;Get.to&lt;/code&gt;, &lt;code&gt;Get.dialog&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Stack teknologi dan dependensi utama
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Paket / pilihan&lt;/th&gt;
&lt;th&gt;Berkas acuan&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;Flutter (SDK Dart &lt;code&gt;^3.11.1&lt;/code&gt; di &lt;code&gt;pubspec.yaml&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sarypos/pubspec.yaml&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend klien&lt;/td&gt;
&lt;td&gt;&lt;code&gt;supabase_flutter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib/data/sources/supabase_klien.dart&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Navigasi/GetX&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;get&lt;/code&gt; — terutama &lt;code&gt;GetMaterialApp&lt;/code&gt;, &lt;code&gt;Get.to&lt;/code&gt;, &lt;code&gt;Get.dialog&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;lib/main.dart&lt;/code&gt;, &lt;code&gt;lib/core/penjaga_rute_owner.dart&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Konfigurasi rahasia&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;flutter_dotenv&lt;/code&gt; + &lt;code&gt;--dart-define&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib/config/supabase_konfigurasi.dart&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preferensi lokal&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;shared_preferences&lt;/code&gt; (tema, “lanjut kasir tanpa owner”)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;lib/core/pengatur_tema.dart&lt;/code&gt;, &lt;code&gt;lib/core/pengatur_sesi.dart&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;google_fonts&lt;/code&gt;, &lt;code&gt;intl&lt;/code&gt;, &lt;code&gt;flutter_typeahead&lt;/code&gt;, &lt;code&gt;image_picker&lt;/code&gt;, &lt;code&gt;file_picker&lt;/code&gt;, &lt;code&gt;barcode&lt;/code&gt;, &lt;code&gt;http&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pubspec.yaml&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ekspor&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pdf&lt;/code&gt;, &lt;code&gt;printing&lt;/code&gt;, &lt;code&gt;share_plus&lt;/code&gt;, &lt;code&gt;csv&lt;/code&gt;, &lt;code&gt;path_provider&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib/core/ekspor/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;State management yang dipakai di kode:&lt;/strong&gt; tidak ada Provider/Bloc/Riverpod; yang dominan adalah &lt;strong&gt;ChangeNotifier + ListenableBuilder/InheritedNotifier&lt;/strong&gt; dan &lt;strong&gt;GetX sebagian&lt;/strong&gt; untuk dialog/rute owner.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Titik masuk aplikasi dan pemilihan “home”
&lt;/h2&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/lib/main.dart&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Alur singkat:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;WidgetsFlutterBinding.ensureInitialized()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Muat &lt;code&gt;.env&lt;/code&gt; (bukan web; error ditelan jika berkas tidak ada)&lt;/li&gt;
&lt;li&gt;Preload font Plus Jakarta Sans&lt;/li&gt;
&lt;li&gt;&lt;code&gt;inisialisasiSupabase()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PengaturTema.muatAwal()&lt;/code&gt; lalu &lt;code&gt;runApp(MainApp(...))&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MainApp&lt;/code&gt; membungkus anak dengan &lt;code&gt;WarisanTema&lt;/code&gt; → &lt;code&gt;WarisanSesi&lt;/code&gt; → &lt;code&gt;WarisanNotifikasiInApp&lt;/code&gt; → &lt;code&gt;GetMaterialApp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;home&lt;/code&gt; ditentukan oleh &lt;code&gt;_pilihHome()&lt;/code&gt;: loading → error koneksi → pembuka owner pertama → &lt;code&gt;HalamanUtamaDenganNav&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Future&amp;lt;void&amp;gt; main() async {
  WidgetsFlutterBinding.ensureInitialized();
  if (!kIsWeb) {
    try {
      await dotenv.load(fileName: '.env');
    } catch (_) {}
  }
  try {
    await GoogleFonts.pendingFonts([GoogleFonts.plusJakartaSans()]);
  } catch (_) {}

  await inisialisasiSupabase();

  final pengaturTema = PengaturTema();
  await pengaturTema.muatAwal();

  runApp(MainApp(pengaturTema: pengaturTema));
}
// ...
              child: GetMaterialApp(
                title: 'SaryPOS',
                theme: temaSaryposTerang(),
                darkTheme: temaSaryposGelap(),
                themeMode: widget.pengaturTema.modeMaterial,
                home: _pilihHome(),
                builder: (context, child) {
                  return Stack(
                    fit: StackFit.expand,
                    children: [
                      child ?? const SizedBox.shrink(),
                      const Positioned(
                        top: 0,
                        left: 0,
                        right: 0,
                        child: SafeArea(
                          bottom: false,
                          child: Padding(
                            padding: EdgeInsets.fromLTRB(16, 8, 16, 0),
                            child: BannerNotifikasiInApp(),
                          ),
                        ),
                      ),
                    ],
                  );
                },
              ),
// ...
  Widget _pilihHome() {
    if (_sesi.sedangMemeriksaSesi) {
      return const Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              CircularProgressIndicator(),
              SizedBox(height: 16),
              Text('Menyiapkan SaryPOS…'),
            ],
          ),
        ),
      );
    }
    if (_sesi.sedangMengalamiError) {
      return HalamanErrorKoneksi(
        pesan: _sesi.pesanErrorSesi ?? 'Gagal menyiapkan SaryPOS.',
        cobaLagi: _sesi.muatUlangSesi,
      );
    }
    if (_sesi.perluHalamanPembukaOwner) {
      return HalamanPembukaOwnerPertama(pengatur: _sesi);
    }
    return const HalamanUtamaDenganNav();
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Properti / perilaku penting untuk ditanya dosen:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tidak ada &lt;code&gt;routes&lt;/code&gt;/&lt;code&gt;onGenerateRoute&lt;/code&gt;&lt;/strong&gt; — satu &lt;code&gt;home&lt;/code&gt; dinamis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Banner notifikasi&lt;/strong&gt; selalu di atas konten lewat &lt;code&gt;Stack&lt;/code&gt; di &lt;code&gt;builder&lt;/code&gt; &lt;code&gt;GetMaterialApp&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Konfigurasi Supabase (URL, anon key, service role)
&lt;/h2&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/lib/config/supabase_konfigurasi.dart&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SUPABASE_URL&lt;/code&gt; dan &lt;code&gt;SUPABASE_ANON_KEY&lt;/code&gt; &lt;strong&gt;wajib&lt;/strong&gt; (dari &lt;code&gt;String.fromEnvironment&lt;/code&gt; atau &lt;code&gt;.env&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SUPABASE_SERVICE_ROLE_KEY&lt;/code&gt; &lt;strong&gt;opsional&lt;/strong&gt;; jika ada, dipakai untuk operasi admin tertentu (mis. form karyawan) — &lt;strong&gt;ini topik sensitif keamanan&lt;/strong&gt; untuk diskusi dengan dosen (anon key untuk klien umum; service role sebaiknya tidak tersebar di aplikasi produksi).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;String _ambilKonfigurasiWajib({
  required String namaKunci,
  required String nilaiDartDefine,
}) {
  final nilaiDefine = nilaiDartDefine.trim();
  if (nilaiDefine.isNotEmpty) {
    return nilaiDefine;
  }

  final nilaiEnv = (dotenv.env[namaKunci] ?? '').trim();
  if (nilaiEnv.isNotEmpty) {
    return nilaiEnv;
  }

  throw StateError(
    'Konfigurasi `$namaKunci` belum tersedia. '
    'Isi di .env atau kirim lewat --dart-define.',
  );
}
// ...
String? get supabaseServiceRoleKey {
  final nilaiDefine = _supabaseServiceRoleDartDefine.trim();
  if (nilaiDefine.isNotEmpty) {
    return nilaiDefine;
  }
  final nilaiEnv = (dotenv.env['SUPABASE_SERVICE_ROLE_KEY'] ?? '').trim();
  if (nilaiEnv.isNotEmpty) {
    return nilaiEnv;
  }
  return null;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Sesi pengguna (owner, kasir tanpa owner, retry)
&lt;/h2&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/lib/core/pengatur_sesi.dart&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Getter penting untuk logika UI:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;perluHalamanPembukaOwner&lt;/code&gt; — true jika belum ada owner aktif, belum login, dan pengguna belum memilih “lanjut kasir tanpa owner”.&lt;/li&gt;
&lt;li&gt;Retry &lt;strong&gt;3 kali&lt;/strong&gt;, timeout &lt;strong&gt;5 detik&lt;/strong&gt; per percobaan (konstanta di kelas).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class PengaturSesi extends ChangeNotifier {
  PengaturSesi() {
    _inisialisasi();
  }

  static const int _maksPercobaanMuatSesi = 3;
  static const Duration _timeoutPercobaanMuatSesi = Duration(seconds: 5);

  static const _kunciPrefLanjutKasir = 'sarypos_lanjut_tanpa_owner';
// ...
  bool get perluHalamanPembukaOwner =&amp;gt;
      _adaOwnerAktif == false &amp;amp;&amp;amp; _pengguna == null &amp;amp;&amp;amp; !_lanjutKasirTanpaOwner;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. Warisan state: &lt;code&gt;InheritedNotifier&lt;/code&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  6.1 &lt;code&gt;WarisanSesi&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/lib/core/warisan_sesi.dart&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class WarisanSesi extends InheritedNotifier&amp;lt;PengaturSesi&amp;gt; {
  const WarisanSesi({
    super.key,
    required PengaturSesi pengatur,
    required super.child,
  }) : super(notifier: pengatur);

  static PengaturSesi dari(BuildContext context) {
    final w = context.dependOnInheritedWidgetOfExactType&amp;lt;WarisanSesi&amp;gt;();
    assert(w != null, 'WarisanSesi tidak ditemukan di pohon widget');
    return w!.notifier!;
  }

  static PengaturSesi? mungkinDari(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType&amp;lt;WarisanSesi&amp;gt;()?.notifier;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Jawaban singkat ujian:&lt;/strong&gt; &lt;code&gt;dependOnInheritedWidgetOfExactType&lt;/code&gt; membuat widget &lt;strong&gt;rebuild&lt;/strong&gt; saat notifier (&lt;code&gt;PengaturSesi&lt;/code&gt;) memanggil &lt;code&gt;notifyListeners&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.2 &lt;code&gt;WarisanTema&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/lib/core/warisan_tema.dart&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class WarisanTema extends InheritedNotifier&amp;lt;PengaturTema&amp;gt; {
  const WarisanTema({
    super.key,
    required PengaturTema pengatur,
    required super.child,
  }) : super(notifier: pengatur);

  static PengaturTema dari(BuildContext context) {
    final w = context.dependOnInheritedWidgetOfExactType&amp;lt;WarisanTema&amp;gt;();
    assert(w != null, 'WarisanTema tidak ditemukan di pohon widget');
    return w!.notifier!;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  7. Penjaga akses: login dan owner
&lt;/h2&gt;

&lt;h3&gt;
  
  
  7.1 Cegah aksi jika belum login
&lt;/h3&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/lib/core/penjaga_aksi_masuk.dart&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bool cegahJikaBelumLogin(
  BuildContext context, {
  String? pesan,
}) {
  final s = WarisanSesi.dari(context);
  if (s.sedangMemeriksaSesi) {
    return true;
  }
  if (s.pengguna != null) {
    return false;
  }
  tampilkanSnackbarSarypos(
    context,
    tipe: TipeSnackbarSarypos.info,
    pesan:
        pesan ??
        'Masuk ke akun terlebih dahulu untuk mengakses fitur ini.',
  );
  return true;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Properti perilaku:&lt;/strong&gt; mengembalikan &lt;code&gt;true&lt;/code&gt; artinya &lt;strong&gt;aksi harus dibatalkan&lt;/strong&gt; (caller biasanya &lt;code&gt;if (cegahJikaBelumLogin(context)) return;&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  7.2 Hanya owner yang boleh membuka halaman tertentu
&lt;/h3&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/lib/core/penjaga_rute_owner.dart&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Future&amp;lt;T?&amp;gt; dorongJikaOwner&amp;lt;T extends Object?&amp;gt;(
  BuildContext context,
  WidgetBuilder builder, {
  String pesanDitolak =
      'Hanya pemilik toko (owner) yang dapat membuka halaman ini.',
}) async {
  final sesi = WarisanSesi.dari(context);
  final p = sesi.pengguna;
  if (p == null || !p.isOwner) {
    if (!context.mounted) {
      return null;
    }
    await Get.dialog&amp;lt;void&amp;gt;(
      AlertDialog(
        title: const Text('Akses Ditolak'),
        content: Text(pesanDitolak),
        actions: [
          TextButton(
            onPressed: () =&amp;gt; Get.back&amp;lt;void&amp;gt;(),
            child: const Text('Oke'),
          ),
        ],
      ),
    );
    return null;
  }

  if (!context.mounted) {
    return null;
  }
  return Get.to&amp;lt;T&amp;gt;(() =&amp;gt; builder(context));
}

bool penggunaAdalahOwner(BuildContext context) {
  return WarisanSesi.dari(context).pengguna?.isOwner ?? false;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Catatan ujian:&lt;/strong&gt; pengecekan owner memakai &lt;code&gt;PenggunaModel.isOwner&lt;/code&gt; (&lt;code&gt;peran == 'owner'&lt;/code&gt; di model).&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Shell navigasi utama (tab Beranda / Kasir / Saya)
&lt;/h2&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/lib/features/dashboard/halaman_utama_dengan_nav.dart&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;IndexedStack&lt;/code&gt; menjaga &lt;strong&gt;state tiap tab&lt;/strong&gt; tetap hidup saat pindah tab.&lt;/li&gt;
&lt;li&gt;AppBar menampilkan ikon log aktivitas &lt;strong&gt;hanya&lt;/strong&gt; jika tab Beranda &lt;strong&gt;dan&lt;/strong&gt; pengguna owner.&lt;/li&gt;
&lt;li&gt;FAB tengah mengarah ke tab Kasir.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  @override
  Widget build(BuildContext context) {
    final isKasirAktif = _indeksSaatIni == 1;
    final pemilikBeranda =
        _indeksSaatIni == 0 &amp;amp;&amp;amp;
        (WarisanSesi.dari(context).pengguna?.isOwner ?? false);
    return Scaffold(
      extendBody: true,
      appBar: AppBarSarypos(
        judul: _judulTab[_indeksSaatIni],
        aksi: pemilikBeranda
            ? [
                IconButton(
                  onPressed: () async {
                    await dorongJikaOwner(
                      context,
                      (_) =&amp;gt; const HalamanLogAktivitas(),
                    );
                  },
                  icon: const Icon(Icons.notifications_none_outlined),
                  tooltip: 'Log Aktivitas',
                ),
              ]
            : null,
      ),
      body: IndexedStack(
        index: _indeksSaatIni,
        children: [
          HalamanDashboard(key: _kunciDashboard),
          const HalamanKasirMenu(),
          const HalamanUserPengaturan(),
        ],
      ),
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  9. Tema dan palet warna merek
&lt;/h2&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/lib/config/theme/sarypos_theme.dart&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kelas &lt;code&gt;WarnaSarypos&lt;/code&gt;&lt;/strong&gt; memusatkan warna merek (merah Sary, teal, emas, abu hangat, hijau sukses). Fungsi &lt;code&gt;temaSaryposTerang()&lt;/code&gt; / &lt;code&gt;temaSaryposGelap()&lt;/code&gt; membangun &lt;code&gt;ThemeData&lt;/code&gt; Material 3 dengan teks &lt;strong&gt;Plus Jakarta Sans&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class WarnaSarypos {
  static const Color saryRed = Color(0xFFD3291E);
  static const Color deepTeal = Color(0xFF1C4546);
  static const Color saryGold = Color(0xFFE4AF1A);
  static const Color cleanWhite = Color(0xFFFEFEFE);
  static const Color warmGray = Color(0xFFCDC5BB);
  static const Color darkStone = Color(0xFF6E6B61);

  static const Color hijauSukses = Color(0xFF1E8E3E);
// ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Transisi halaman:&lt;/strong&gt; &lt;code&gt;PageTransitionsTheme&lt;/code&gt; memakai &lt;code&gt;CupertinoPageTransitionsBuilder&lt;/code&gt; untuk Android/iOS/macOS (geser seperti iOS).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const PageTransitionsTheme _transisiHalamanStandar = PageTransitionsTheme(
  builders: &amp;lt;TargetPlatform, PageTransitionsBuilder&amp;gt;{
    TargetPlatform.android: CupertinoPageTransitionsBuilder(),
    TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
    TargetPlatform.macOS: CupertinoPageTransitionsBuilder(),
    TargetPlatform.linux: FadeUpwardsPageTransitionsBuilder(),
    TargetPlatform.windows: FadeUpwardsPageTransitionsBuilder(),
    TargetPlatform.fuchsia: FadeUpwardsPageTransitionsBuilder(),
  },
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  10. Widget reusable di &lt;code&gt;lib/widgets/&lt;/code&gt; — properti dan implementasi
&lt;/h2&gt;

&lt;h3&gt;
  
  
  10.1 &lt;code&gt;CardSarypos&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/lib/widgets/card_sarypos.dart&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Jenis:&lt;/strong&gt; &lt;code&gt;StatelessWidget&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Properti konstruktor:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Properti&lt;/th&gt;
&lt;th&gt;Tipe&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Fungsi&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;child&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;wajib&lt;/td&gt;
&lt;td&gt;Isi kartu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;margin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;EdgeInsetsGeometry?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;Margin &lt;code&gt;Card&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;borderRadius&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BorderRadius?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;12 px all&lt;/td&gt;
&lt;td&gt;Sudut membulat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;elevation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;double?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;dari &lt;code&gt;cardTheme&lt;/code&gt; atau 2&lt;/td&gt;
&lt;td&gt;Bayangan; jika ≤0, “datar”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;onTap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VoidCallback?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;Jika ada, dibungkus &lt;code&gt;InkWell&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tampilkanKonturTipis&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;Garis tipis saat elevasi datar&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class CardSarypos extends StatelessWidget {
  const CardSarypos({
    super.key,
    required this.child,
    this.margin,
    this.borderRadius,
    this.elevation,
    this.onTap,
    this.tampilkanKonturTipis = false,
  });

  final Widget child;
  final EdgeInsetsGeometry? margin;
  final BorderRadius? borderRadius;

  final double? elevation;
  final VoidCallback? onTap;

  final bool tampilkanKonturTipis;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Ide desain:&lt;/strong&gt; gradien latar dari &lt;code&gt;cleanWhite&lt;/code&gt; / &lt;code&gt;surfaceContainerHighest&lt;/code&gt; ke aksen teal transparan, dengan “blob” dekoratif emas/merah sangat halus.&lt;/p&gt;
&lt;h3&gt;
  
  
  10.2 &lt;code&gt;CardRingkasan&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/lib/widgets/card_ringkasan.dart&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Membungkus konten dengan &lt;code&gt;CardSarypos(elevation: 0)&lt;/code&gt; dan layout baris: teks kiri, ikon bundar kanan.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Properti&lt;/th&gt;
&lt;th&gt;Tipe&lt;/th&gt;
&lt;th&gt;Keterangan&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;judul&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;String&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Label ringkas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nilaiUtama&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;String&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Angka/teks besar (mis. format rupiah)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ikon&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IconData&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ikon ringkasan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;warnaAksen&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Color?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Opsional; default teal (terang) / secondary (gelap)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;margin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;EdgeInsetsGeometry?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Default &lt;code&gt;EdgeInsets.zero&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class CardRingkasan extends StatelessWidget {
  const CardRingkasan({
    super.key,
    required this.judul,
    required this.nilaiUtama,
    required this.ikon,
    this.warnaAksen,
    this.margin,
  });

  final String judul;
  final String nilaiUtama;
  final IconData ikon;
  final Color? warnaAksen;
  final EdgeInsetsGeometry? margin;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  10.3 &lt;code&gt;AppBarSarypos&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/lib/widgets/appbar_sarypos.dart&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Mengimplementasikan &lt;code&gt;PreferredSizeWidget&lt;/code&gt; (&lt;code&gt;preferredSize&lt;/code&gt; = tinggi toolbar standar).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Properti&lt;/th&gt;
&lt;th&gt;Tipe&lt;/th&gt;
&lt;th&gt;Keterangan&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;judul&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;String&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Judul utama AppBar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;aksi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;List&amp;lt;Widget&amp;gt;?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Aksi kustom; digabung dengan tombol siklus tema&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Perilaku tema:&lt;/strong&gt; membaca &lt;code&gt;WarisanTema.dari(context)&lt;/code&gt;, siklus &lt;code&gt;ikutiSistem&lt;/code&gt; → &lt;code&gt;terang&lt;/code&gt; → &lt;code&gt;gelap&lt;/code&gt; lewat &lt;code&gt;pengaturTema.atur(...)&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class AppBarSarypos extends StatelessWidget implements PreferredSizeWidget {
  const AppBarSarypos({super.key, required this.judul, this.aksi});

  final String judul;
  final List&amp;lt;Widget&amp;gt;? aksi;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  10.4 &lt;code&gt;EmptyStateGenerik&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/lib/widgets/empty_state_generik.dart&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Memakai &lt;code&gt;LayoutBuilder&lt;/code&gt; untuk menyesuaikan padding dan ukuran ikon jika ruang vertikal sangat kecil (&lt;code&gt;maxHeight &amp;lt; 90&lt;/code&gt;).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Properti&lt;/th&gt;
&lt;th&gt;Tipe&lt;/th&gt;
&lt;th&gt;Keterangan&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ikon&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IconData?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Opsional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;judul&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;String?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Opsional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pesan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;String&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Wajib — pesan utama&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;labelTombol&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;String?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Perlu dipasangkan dengan &lt;code&gt;onTekanTombol&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;onTekanTombol&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VoidCallback?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Menampilkan &lt;code&gt;ElevatedButton&lt;/code&gt; jika keduanya non-null&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class EmptyStateGenerik extends StatelessWidget {
  const EmptyStateGenerik({
    super.key,
    this.ikon,
    this.judul,
    required this.pesan,
    this.labelTombol,
    this.onTekanTombol,
  });

  final IconData? ikon;
  final String? judul;
  final String pesan;
  final String? labelTombol;
  final VoidCallback? onTekanTombol;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  10.5 &lt;code&gt;tampilkanSnackbarSarypos&lt;/code&gt; dan &lt;code&gt;TipeSnackbarSarypos&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/lib/widgets/snackbar_sarypos.dart&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Bukan widget, tetapi &lt;strong&gt;API imperatif&lt;/strong&gt; untuk feedback konsisten.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enum: &lt;code&gt;sukses&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;, &lt;code&gt;info&lt;/code&gt;, &lt;code&gt;peringatan&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Memanggil &lt;code&gt;ScaffoldMessenger.of(context).clearSnackBars()&lt;/code&gt; lalu &lt;code&gt;showSnackBar&lt;/code&gt; dengan konten gradien custom (bukan warna flat default)
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;enum TipeSnackbarSarypos { sukses, error, info, peringatan }

void tampilkanSnackbarSarypos(
  BuildContext context, {
  required TipeSnackbarSarypos tipe,
  required String pesan,
}) {
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  10.6 Skeleton loading (&lt;code&gt;SkeletonBox&lt;/code&gt;, &lt;code&gt;SkeletonLine&lt;/code&gt;, &lt;code&gt;SkeletonCircle&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/lib/widgets/skeleton_sarypos.dart&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;SkeletonBox&lt;/code&gt; memakai &lt;code&gt;_ShimmerSkeletonCore&lt;/code&gt; (&lt;code&gt;AnimationController.repeat&lt;/code&gt;) untuk gelombang sorot.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Widget&lt;/th&gt;
&lt;th&gt;Properti utama&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SkeletonBox&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;width&lt;/code&gt;, &lt;code&gt;height&lt;/code&gt;, &lt;code&gt;borderRadius&lt;/code&gt; (default 12), &lt;code&gt;baseColor?&lt;/code&gt;, &lt;code&gt;highlightColor?&lt;/code&gt;, &lt;code&gt;duration&lt;/code&gt; (default 1300 ms)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SkeletonLine&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;width&lt;/code&gt;, &lt;code&gt;height&lt;/code&gt; (12), &lt;code&gt;borderRadius&lt;/code&gt; (999)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SkeletonCircle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;diameter&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class SkeletonBox extends StatelessWidget {
  const SkeletonBox({
    super.key,
    required this.width,
    required this.height,
    this.borderRadius = 12,
    this.baseColor,
    this.highlightColor,
    this.duration = const Duration(milliseconds: 1300),
  });

  final double width;
  final double height;
  final double borderRadius;
  final Color? baseColor;
  final Color? highlightColor;
  final Duration duration;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  10.7 &lt;code&gt;BannerNotifikasiInApp&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/lib/widgets/banner_notifikasi_in_app.dart&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Tanpa properti publik; membaca &lt;code&gt;WarisanNotifikasiInApp.mungkinDari(context)&lt;/code&gt; dan &lt;code&gt;ListenableBuilder&lt;/code&gt; pada pengelola. Tap menutup lewat &lt;code&gt;pengelola.tutup()&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  10.8 &lt;code&gt;PengelolaNotifikasiInApp&lt;/code&gt; (inti logika banner)
&lt;/h3&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/lib/core/pengelola_notifikasi_in_app.dart&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tampilkan(tipe, pesan, durasi)&lt;/code&gt; — deduplikasi pesan identik dalam &lt;strong&gt;2 detik&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Timer otomatis menghapus notifikasi setelah &lt;code&gt;durasi&lt;/code&gt; (default 4 detik)
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class PengelolaNotifikasiInApp extends ChangeNotifier {
  ItemNotifikasiInApp? _aktif;
  Timer? _timer;
  String? _pesanTerakhir;
  DateTime? _waktuPesanTerakhir;

  ItemNotifikasiInApp? get aktif =&amp;gt; _aktif;

  void tampilkan({
    required TipeNotifikasiInApp tipe,
    required String pesan,
    Duration durasi = const Duration(seconds: 4),
  }) {
    final sekarang = DateTime.now();
    if (_pesanTerakhir == pesan &amp;amp;&amp;amp;
        _waktuPesanTerakhir != null &amp;amp;&amp;amp;
        sekarang.difference(_waktuPesanTerakhir!) &amp;lt;
            const Duration(seconds: 2)) {
      return;
    }
    _pesanTerakhir = pesan;
    _waktuPesanTerakhir = sekarang;

    _timer?.cancel();
    _aktif = ItemNotifikasiInApp(
      id: '${sekarang.microsecondsSinceEpoch}',
      tipe: tipe,
      pesan: pesan,
    );
    notifyListeners();

    _timer = Timer(durasi, () {
      _aktif = null;
      notifyListeners();
    });
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  10.9 &lt;code&gt;HalamanErrorKoneksi&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/lib/widgets/halaman_error_koneksi.dart&lt;/code&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Properti&lt;/th&gt;
&lt;th&gt;Tipe&lt;/th&gt;
&lt;th&gt;Keterangan&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pesan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;String&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Detail error untuk pengguna&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cobaLagi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Future&amp;lt;void&amp;gt; Function()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Async callback tombol “Coba Lagi”&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class HalamanErrorKoneksi extends StatelessWidget {
  const HalamanErrorKoneksi({
    super.key,
    required this.pesan,
    required this.cobaLagi,
  });

  final String pesan;
  final Future&amp;lt;void&amp;gt; Function() cobaLagi;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  11. Lapisan data: contoh model dan alur transaksi
&lt;/h2&gt;
&lt;h3&gt;
  
  
  11.1 Model pengguna dan peran owner
&lt;/h3&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/lib/data/models/pengguna_model.dart&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class PenggunaModel {
  const PenggunaModel({
    required this.id,
    required this.idAuth,
    required this.namaLengkap,
    required this.email,
    required this.peran,
    required this.aktif,
    this.sandiLogin,
  });

  final String id;
  final String idAuth;
  final String namaLengkap;
  final String email;
  final String peran;
  final bool aktif;
  final String? sandiLogin;

  bool get isOwner =&amp;gt; peran == 'owner';

  factory PenggunaModel.dariBaris(Map&amp;lt;String, dynamic&amp;gt; row) {
    return PenggunaModel(
      id: row['id'].toString(),
      idAuth: row['id_auth'].toString(),
      namaLengkap: row['nama_lengkap']?.toString() ?? '',
      email: row['email']?.toString() ?? '',
      peran: row['peran']?.toString() ?? 'karyawan',
      aktif: row['aktif'] == true,
      sandiLogin: row['sandi_login']?.toString(),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  11.2 Penyimpanan transaksi + detail + kurangi stok
&lt;/h3&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/lib/data/sources/transaksi_sumber.dart&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Alur: hitung subtotal dari &lt;code&gt;ItemKeranjang&lt;/code&gt; → validasi &lt;code&gt;totalAkhir&lt;/code&gt; → &lt;code&gt;insert&lt;/code&gt; ke tabel &lt;code&gt;transaksi&lt;/code&gt; → &lt;code&gt;insert&lt;/code&gt; batch ke &lt;code&gt;detail_transaksi&lt;/code&gt; → loop &lt;code&gt;kurangiStokSetelahPenjualan&lt;/code&gt; per item.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Future&amp;lt;String?&amp;gt; simpanTransaksi({
    String? idPengguna,
    required List&amp;lt;ItemKeranjang&amp;gt; itemKeranjang,
    required String metodePembayaran,
    int? totalAkhir,
  }) async {
    if (itemKeranjang.isEmpty) {
      return null;
    }

    final subtotal = itemKeranjang.fold&amp;lt;int&amp;gt;(
      0,
      (sebelumnya, item) =&amp;gt; sebelumnya + item.subtotal,
    );
    final total = totalAkhir ?? subtotal;
    if (total &amp;lt; 0 || total &amp;gt; subtotal) {
      throw ArgumentError(
        'totalAkhir harus antara 0 dan subtotal ($subtotal), dapat: $total',
      );
    }
    final potongan = subtotal - total;

    final barisTransaksi = &amp;lt;String, dynamic&amp;gt;{
      'waktu': DateTime.now().toUtc().toIso8601String(),
      'subtotal': subtotal,
      'potongan': potongan,
      'total': total,
      'metode_pembayaran': metodePembayaran,
    };
    if (idPengguna != null) {
      barisTransaksi['id_pengguna'] = idPengguna;
    }

    final responsTransaksi = await supabaseKlien
        .from('transaksi')
        .insert(barisTransaksi)
        .select('id')
        .single();
// ...
    await supabaseKlien.from('detail_transaksi').insert(detail);

    for (final item in itemKeranjang) {
      await _stokSumber.kurangiStokSetelahPenjualan(
        produkId: item.produk.id,
        kuantitasTerjual: item.kuantitas,
      );
    }

    return transaksiId;
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  11.3 Peta berkas sumber data (ringkas)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Berkas&lt;/th&gt;
&lt;th&gt;Tabel / bucket (umum)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pengguna_sumber.dart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pengguna&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;profil_karyawan_sumber.dart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;profil_karyawan&lt;/code&gt;, bucket &lt;code&gt;karyawan-foto&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;produk_dan_stok_sumber.dart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;produk&lt;/code&gt;, &lt;code&gt;stok&lt;/code&gt;, bucket &lt;code&gt;produk-gambar&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transaksi_sumber.dart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;transaksi&lt;/code&gt;, &lt;code&gt;detail_transaksi&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dashboard_sumber.dart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;agregat dari &lt;code&gt;transaksi&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;laporan_sumber.dart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;transaksi&lt;/code&gt; (+ relasi ke &lt;code&gt;pengguna&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;log_aktivitas_sumber.dart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;log_aktivitas&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;supabase_klien.dart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;inisialisasi + tes koneksi ke &lt;code&gt;pengguna&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  12. Modul fitur (&lt;code&gt;lib/features/&lt;/code&gt;) — cakupan untuk demo
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Folder&lt;/th&gt;
&lt;th&gt;Layar utama&lt;/th&gt;
&lt;th&gt;Catatan ujian&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;auth/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pembuka owner pertama, login, daftar owner&lt;/td&gt;
&lt;td&gt;Alur belum ada owner di DB vs lanjut kasir&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dashboard/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Dashboard, shell nav&lt;/td&gt;
&lt;td&gt;Carousel, pintasan ke produk/stok/laporan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pos/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Menu kasir, POS, pencatatan transaksi&lt;/td&gt;
&lt;td&gt;Keranjang, diskon, notifikasi in-app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;produk/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Daftar &amp;amp; form produk&lt;/td&gt;
&lt;td&gt;Upload gambar ke storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stok/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stok&lt;/td&gt;
&lt;td&gt;Penyesuaian kuantitas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;karyawan/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Daftar &amp;amp; form&lt;/td&gt;
&lt;td&gt;Operasi admin (kaitkan dengan service role)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;laporan/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Laporan penjualan&lt;/td&gt;
&lt;td&gt;PDF/CSV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;log_aktivitas/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Log aktivitas&lt;/td&gt;
&lt;td&gt;Owner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pengaturan/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Profil/saya, tentang, panel transaksi terakhir karyawan&lt;/td&gt;
&lt;td&gt;Perbedaan menu owner vs karyawan&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  13. Pengujian otomatis
&lt;/h2&gt;

&lt;p&gt;Berkas: &lt;code&gt;sarypos/test/skeleton_sarypos_test.dart&lt;/code&gt; — hanya uji widget skeleton. &lt;strong&gt;Tidak ada&lt;/strong&gt; test integrasi Supabase atau alur POS; ini jawaban jujur jika dosen menanyakan cakupan test.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. Pertanyaan yang sering muncul saat pengujian (beserta jawaban singkat)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mengapa memakai &lt;code&gt;IndexedStack&lt;/code&gt; untuk tab?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Agar state tiap tab (mis. scroll posisi) tidak hilang saat ganti tab; hanya tab aktif yang “terlihat”, sisanya tetap di pohon widget.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bagaimana cara aplikasi tahu user owner?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Dari baris &lt;code&gt;pengguna&lt;/code&gt; di Supabase: field &lt;code&gt;peran&lt;/code&gt;; di kode &lt;code&gt;PenggunaModel.isOwner&lt;/code&gt; membandingkan dengan &lt;code&gt;'owner'&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Di mana pembatasan fitur owner diimplementasikan?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Kombinasi &lt;code&gt;dorongJikaOwner&lt;/code&gt;, &lt;code&gt;penggunaAdalahOwner&lt;/code&gt;, &lt;code&gt;cegahJikaBelumLogin&lt;/code&gt;, dan kondisi UI di &lt;code&gt;HalamanUtamaDenganNav&lt;/code&gt; / halaman pengaturan.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bagaimana state tema diwariskan?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;WarisanTema&lt;/code&gt; (&lt;code&gt;InheritedNotifier&amp;lt;PengaturTema&amp;gt;&lt;/code&gt;); &lt;code&gt;AppBarSarypos&lt;/code&gt; memanggil &lt;code&gt;WarisanTema.dari(context)&lt;/code&gt; untuk mengubah tema.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;GetX dipakai untuk apa saja?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Terutama &lt;code&gt;GetMaterialApp&lt;/code&gt; dan navigasi/dialog di &lt;code&gt;dorongJikaOwner&lt;/code&gt;; tidak ada pola reactive massal (&lt;code&gt;Obx&lt;/code&gt; massal).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bagaimana alur simpan transaksi?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;TransaksiSumber.simpanTransaksi&lt;/code&gt;: insert header &lt;code&gt;transaksi&lt;/code&gt;, insert lines &lt;code&gt;detail_transaksi&lt;/code&gt;, lalu kurangi stok per produk.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Di mana konfigurasi Supabase?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;supabase_konfigurasi.dart&lt;/code&gt;; wajib URL + anon key dari &lt;code&gt;.env&lt;/code&gt; atau &lt;code&gt;--dart-define&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Apa risiko &lt;code&gt;SUPABASE_SERVICE_ROLE_KEY&lt;/code&gt; di aplikasi?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Kunci ini melewati RLS; untuk produksi seharusnya tidak dibundel di klien. Untuk proyek kuliah, siapkan penjelasan pembatasan dan skenario ideal (Edge Function/server).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  15. Teks pegangan (ringkas, untuk dibaca sebelum masuk ruang ujian)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Akar widget:&lt;/strong&gt; &lt;code&gt;main.dart&lt;/code&gt; → &lt;code&gt;MainApp&lt;/code&gt; → &lt;code&gt;GetMaterialApp&lt;/code&gt; + &lt;code&gt;Stack&lt;/code&gt; banner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiga warisan:&lt;/strong&gt; Tema (&lt;code&gt;WarisanTema&lt;/code&gt;), Sesi (&lt;code&gt;WarisanSesi&lt;/code&gt;), Notifikasi (&lt;code&gt;WarisanNotifikasiInApp&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Home dinamis:&lt;/strong&gt; loading → error (&lt;code&gt;HalamanErrorKoneksi&lt;/code&gt;) → pembuka owner → &lt;code&gt;HalamanUtamaDenganNav&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tab:&lt;/strong&gt; Beranda = &lt;code&gt;HalamanDashboard&lt;/code&gt;, Kasir = &lt;code&gt;HalamanKasirMenu&lt;/code&gt;, Saya = &lt;code&gt;HalamanUserPengaturan&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kartu UI:&lt;/strong&gt; &lt;code&gt;CardSarypos&lt;/code&gt; = wadah visual; &lt;code&gt;CardRingkasan&lt;/code&gt; = pola ringkasan angka + ikon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedback:&lt;/strong&gt; snackbar = &lt;code&gt;tampilkanSnackbarSarypos&lt;/code&gt;; banner atas = &lt;code&gt;PengelolaNotifikasiInApp&lt;/code&gt; + &lt;code&gt;BannerNotifikasiInApp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guard:&lt;/strong&gt; &lt;code&gt;cegahJikaBelumLogin&lt;/code&gt; (snackbar info); &lt;code&gt;dorongJikaOwner&lt;/code&gt; (dialog + &lt;code&gt;Get.to&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data:&lt;/strong&gt; model di &lt;code&gt;lib/data/models/&lt;/code&gt;, akses Supabase di &lt;code&gt;lib/data/sources/&lt;/code&gt;, klien tunggal &lt;code&gt;supabaseKlien&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tema warna:&lt;/strong&gt; &lt;code&gt;WarnaSarypos&lt;/code&gt; + &lt;code&gt;temaSaryposTerang&lt;/code&gt;/&lt;code&gt;Gelap&lt;/code&gt; + font Plus Jakarta.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transaksi:&lt;/strong&gt; tabel &lt;code&gt;transaksi&lt;/code&gt; + &lt;code&gt;detail_transaksi&lt;/code&gt;, stok dikurangi setelah sukses insert.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bukti di kode:&lt;/strong&gt; selalu bisa buka berkas yang dicantumkan di atas dan tunjukkan blok baris yang sama dengan dokumen ini.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Dokumen ini disusun berdasarkan audit struktural codebase SaryPOS pada repositori lokal Anda; jika ada perubahan signifikan setelah commit tertentu, sesuaikan referensi baris dengan mencari ulang simbol yang sama di IDE.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>flutter</category>
      <category>mobile</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
