Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Sichere ProgrammierungPlanning a DEX Product Without Starting With Smart Contracts(21.09.2026 um 15:26 Uhr)
Malware / Trojaner / VirenInside BambooToken’s Linux implant: shell and file control over MQTT(21.09.2026 um 12:37 Uhr)
Linux Tipps & HardeningVoid Linux (base) as a server distro?(21.09.2026 um 14:13 Uhr)
IT Security VideoBlack Hat Stories | Ryan & Isabella Barnett(21.09.2026 um 15:30 Uhr)
IT Security NachrichtenSuccess of Trump-Xi summit lies in what happens afterwards(21.09.2026 um 14:30 Uhr)
Sichere ProgrammierungPlanning a DEX Product Without Starting With Smart Contracts(21.09.2026 um 15:26 Uhr)
Malware / Trojaner / VirenInside BambooToken’s Linux implant: shell and file control over MQTT(21.09.2026 um 12:37 Uhr)
Linux Tipps & HardeningVoid Linux (base) as a server distro?(21.09.2026 um 14:13 Uhr)
IT Security VideoBlack Hat Stories | Ryan & Isabella Barnett(21.09.2026 um 15:30 Uhr)
IT Security NachrichtenSuccess of Trump-Xi summit lies in what happens afterwards(21.09.2026 um 14:30 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

Panduan Pengujian: Implementasi Widget, Properti, dan Arsitektur SaryPOS

Dokumen ini merangkum hasil audit codebase aplikasi SaryPOS (folder sarypos/) untuk persiapan demonstrasi dan tanya jawab dengan dosen. Setiap bagian menyertakan lokasi berkas dan cuplikan kode yang dapat Anda tunjukkan langsung di…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

Dokumen ini merangkum hasil audit codebase aplikasi SaryPOS (folder sarypos/) untuk persiapan demonstrasi dan tanya jawab dengan dosen. Setiap bagian menyertakan lokasi berkas dan cuplikan kode yang dapat Anda tunjukkan langsung di IDE.



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









1. Ringkasan arsitektur (kalimat untuk pembuka presentasi)



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









2. Stack teknologi dan dependensi utama
















































Area Paket / pilihan Berkas acuan
Framework Flutter (SDK Dart ^3.11.1 di pubspec.yaml) sarypos/pubspec.yaml
Backend klien supabase_flutter lib/data/sources/supabase_klien.dart
Navigasi/GetX
get — terutama GetMaterialApp, Get.to, Get.dialog

lib/main.dart, lib/core/penjaga_rute_owner.dart
Konfigurasi rahasia
flutter_dotenv + --dart-define
lib/config/supabase_konfigurasi.dart
Preferensi lokal
shared_preferences (tema, “lanjut kasir tanpa owner”)

lib/core/pengatur_tema.dart, lib/core/pengatur_sesi.dart
UI
google_fonts, intl, flutter_typeahead, image_picker, file_picker, barcode, http
pubspec.yaml
Ekspor
pdf, printing, share_plus, csv, path_provider
lib/core/ekspor/


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









3. Titik masuk aplikasi dan pemilihan “home”



Berkas: sarypos/lib/main.dart



Alur singkat:




  1. WidgetsFlutterBinding.ensureInitialized()

  2. Muat .env (bukan web; error ditelan jika berkas tidak ada)

  3. Preload font Plus Jakarta Sans

  4. inisialisasiSupabase()


  5. PengaturTema.muatAwal() lalu runApp(MainApp(...))


  6. MainApp membungkus anak dengan WarisanTemaWarisanSesiWarisanNotifikasiInAppGetMaterialApp


  7. home ditentukan oleh _pilihHome(): loading → error koneksi → pembuka owner pertama → HalamanUtamaDenganNav




Future<void> 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();
}
}






Properti / perilaku penting untuk ditanya dosen:





  • Tidak ada routes/onGenerateRoute — satu home dinamis.


  • Banner notifikasi selalu di atas konten lewat Stack di builder GetMaterialApp.









4. Konfigurasi Supabase (URL, anon key, service role)



Berkas: sarypos/lib/config/supabase_konfigurasi.dart





  • SUPABASE_URL dan SUPABASE_ANON_KEY wajib (dari String.fromEnvironment atau .env).


  • SUPABASE_SERVICE_ROLE_KEY opsional; jika ada, dipakai untuk operasi admin tertentu (mis. form karyawan) — ini topik sensitif keamanan untuk diskusi dengan dosen (anon key untuk klien umum; service role sebaiknya tidak tersebar di aplikasi produksi).




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;
}












5. Sesi pengguna (owner, kasir tanpa owner, retry)



Berkas: sarypos/lib/core/pengatur_sesi.dart



Getter penting untuk logika UI:





  • perluHalamanPembukaOwner — true jika belum ada owner aktif, belum login, dan pengguna belum memilih “lanjut kasir tanpa owner”.

  • Retry 3 kali, timeout 5 detik per percobaan (konstanta di kelas).




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 =>
_adaOwnerAktif == false && _pengguna == null && !_lanjutKasirTanpaOwner;












6. Warisan state: InheritedNotifier






6.1 WarisanSesi



Berkas: sarypos/lib/core/warisan_sesi.dart




class WarisanSesi extends InheritedNotifier<PengaturSesi> {
const WarisanSesi({
super.key,
required PengaturSesi pengatur,
required super.child,
}) : super(notifier: pengatur);

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

static PengaturSesi? mungkinDari(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<WarisanSesi>()?.notifier;
}
}






Jawaban singkat ujian: dependOnInheritedWidgetOfExactType membuat widget rebuild saat notifier (PengaturSesi) memanggil notifyListeners.






6.2 WarisanTema



Berkas: sarypos/lib/core/warisan_tema.dart




class WarisanTema extends InheritedNotifier<PengaturTema> {
const WarisanTema({
super.key,
required PengaturTema pengatur,
required super.child,
}) : super(notifier: pengatur);

static PengaturTema dari(BuildContext context) {
final w = context.dependOnInheritedWidgetOfExactType<WarisanTema>();
assert(w != null, 'WarisanTema tidak ditemukan di pohon widget');
return w!.notifier!;
}
}












7. Penjaga akses: login dan owner






7.1 Cegah aksi jika belum login



Berkas: sarypos/lib/core/penjaga_aksi_masuk.dart




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;
}






Properti perilaku: mengembalikan true artinya aksi harus dibatalkan (caller biasanya if (cegahJikaBelumLogin(context)) return;).






7.2 Hanya owner yang boleh membuka halaman tertentu



Berkas: sarypos/lib/core/penjaga_rute_owner.dart




Future<T?> dorongJikaOwner<T extends Object?>(
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<void>(
AlertDialog(
title: const Text('Akses Ditolak'),
content: Text(pesanDitolak),
actions: [
TextButton(
onPressed: () => Get.back<void>(),
child: const Text('Oke'),
),
],
),
);
return null;
}

if (!context.mounted) {
return null;
}
return Get.to<T>(() => builder(context));
}

bool penggunaAdalahOwner(BuildContext context) {
return WarisanSesi.dari(context).pengguna?.isOwner ?? false;
}






Catatan ujian: pengecekan owner memakai PenggunaModel.isOwner (peran == 'owner' di model).









8. Shell navigasi utama (tab Beranda / Kasir / Saya)



Berkas: sarypos/lib/features/dashboard/halaman_utama_dengan_nav.dart





  • IndexedStack menjaga state tiap tab tetap hidup saat pindah tab.

  • AppBar menampilkan ikon log aktivitas hanya jika tab Beranda dan pengguna owner.

  • FAB tengah mengarah ke tab Kasir.




  @override
Widget build(BuildContext context) {
final isKasirAktif = _indeksSaatIni == 1;
final pemilikBeranda =
_indeksSaatIni == 0 &&
(WarisanSesi.dari(context).pengguna?.isOwner ?? false);
return Scaffold(
extendBody: true,
appBar: AppBarSarypos(
judul: _judulTab[_indeksSaatIni],
aksi: pemilikBeranda
? [
IconButton(
onPressed: () async {
await dorongJikaOwner(
context,
(_) => 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(),
],
),












9. Tema dan palet warna merek



Berkas: sarypos/lib/config/theme/sarypos_theme.dart



Kelas WarnaSarypos memusatkan warna merek (merah Sary, teal, emas, abu hangat, hijau sukses). Fungsi temaSaryposTerang() / temaSaryposGelap() membangun ThemeData Material 3 dengan teks Plus Jakarta Sans.




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);
// ...
}






Transisi halaman: PageTransitionsTheme memakai CupertinoPageTransitionsBuilder untuk Android/iOS/macOS (geser seperti iOS).




const PageTransitionsTheme _transisiHalamanStandar = PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: CupertinoPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.macOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.linux: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.windows: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.fuchsia: FadeUpwardsPageTransitionsBuilder(),
},
);












10. Widget reusable di lib/widgets/ — properti dan implementasi






10.1 CardSarypos



Berkas: sarypos/lib/widgets/card_sarypos.dart


Jenis: StatelessWidget


Properti konstruktor:


















































Properti Tipe Default Fungsi
child Widget wajib Isi kartu
margin EdgeInsetsGeometry? null Margin Card
borderRadius BorderRadius? 12 px all Sudut membulat
elevation double? dari cardTheme atau 2 Bayangan; jika ≤0, “datar”
onTap VoidCallback? null Jika ada, dibungkus InkWell
tampilkanKonturTipis bool false Garis tipis saat elevasi datar




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;





Ide desain: gradien latar dari cleanWhite / surfaceContainerHighest ke aksen teal transparan, dengan “blob” dekoratif emas/merah sangat halus.





10.2 CardRingkasan



Berkas: sarypos/lib/widgets/card_ringkasan.dart


Membungkus konten dengan CardSarypos(elevation: 0) dan layout baris: teks kiri, ikon bundar kanan.






































Properti Tipe Keterangan
judul String Label ringkas
nilaiUtama String Angka/teks besar (mis. format rupiah)
ikon IconData Ikon ringkasan
warnaAksen Color? Opsional; default teal (terang) / secondary (gelap)
margin EdgeInsetsGeometry? Default EdgeInsets.zero




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;







10.3 AppBarSarypos



Berkas: sarypos/lib/widgets/appbar_sarypos.dart


Mengimplementasikan PreferredSizeWidget (preferredSize = tinggi toolbar standar).























Properti Tipe Keterangan
judul String Judul utama AppBar
aksi List<Widget>? Aksi kustom; digabung dengan tombol siklus tema


Perilaku tema: membaca WarisanTema.dari(context), siklus ikutiSistemteranggelap lewat pengaturTema.atur(...).




class AppBarSarypos extends StatelessWidget implements PreferredSizeWidget {
const AppBarSarypos({super.key, required this.judul, this.aksi});

final String judul;
final List<Widget>? aksi;









10.4 EmptyStateGenerik



Berkas: sarypos/lib/widgets/empty_state_generik.dart


Memakai LayoutBuilder untuk menyesuaikan padding dan ukuran ikon jika ruang vertikal sangat kecil (maxHeight < 90).






































Properti Tipe Keterangan
ikon IconData? Opsional
judul String? Opsional
pesan String Wajib — pesan utama
labelTombol String? Perlu dipasangkan dengan onTekanTombol
onTekanTombol VoidCallback? Menampilkan ElevatedButton jika keduanya non-null




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;







10.5 tampilkanSnackbarSarypos dan TipeSnackbarSarypos



Berkas: sarypos/lib/widgets/snackbar_sarypos.dart


Bukan widget, tetapi API imperatif untuk feedback konsisten.




  • Enum: sukses, error, info, peringatan

  • Memanggil ScaffoldMessenger.of(context).clearSnackBars() lalu showSnackBar dengan konten gradien custom (bukan warna flat default)



enum TipeSnackbarSarypos { sukses, error, info, peringatan }

void tampilkanSnackbarSarypos(
BuildContext context, {
required TipeSnackbarSarypos tipe,
required String pesan,
}) {







10.6 Skeleton loading (SkeletonBox, SkeletonLine, SkeletonCircle)



Berkas: sarypos/lib/widgets/skeleton_sarypos.dart


SkeletonBox memakai _ShimmerSkeletonCore (AnimationController.repeat) untuk gelombang sorot.
























Widget Properti utama
SkeletonBox
width, height, borderRadius (default 12), baseColor?, highlightColor?, duration (default 1300 ms)
SkeletonLine
width, height (12), borderRadius (999)
SkeletonCircle diameter




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;







10.7 BannerNotifikasiInApp



Berkas: sarypos/lib/widgets/banner_notifikasi_in_app.dart


Tanpa properti publik; membaca WarisanNotifikasiInApp.mungkinDari(context) dan ListenableBuilder pada pengelola. Tap menutup lewat pengelola.tutup().





10.8 PengelolaNotifikasiInApp (inti logika banner)



Berkas: sarypos/lib/core/pengelola_notifikasi_in_app.dart





  • tampilkan(tipe, pesan, durasi) — deduplikasi pesan identik dalam 2 detik

  • Timer otomatis menghapus notifikasi setelah durasi (default 4 detik)



class PengelolaNotifikasiInApp extends ChangeNotifier {
ItemNotifikasiInApp? _aktif;
Timer? _timer;
String? _pesanTerakhir;
DateTime? _waktuPesanTerakhir;

ItemNotifikasiInApp? get aktif => _aktif;

void tampilkan({
required TipeNotifikasiInApp tipe,
required String pesan,
Duration durasi = const Duration(seconds: 4),
}) {
final sekarang = DateTime.now();
if (_pesanTerakhir == pesan &&
_waktuPesanTerakhir != null &&
sekarang.difference(_waktuPesanTerakhir!) <
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();
});
}







10.9 HalamanErrorKoneksi



Berkas: sarypos/lib/widgets/halaman_error_koneksi.dart























Properti Tipe Keterangan
pesan String Detail error untuk pengguna
cobaLagi Future<void> Function() Async callback tombol “Coba Lagi”




class HalamanErrorKoneksi extends StatelessWidget {
const HalamanErrorKoneksi({
super.key,
required this.pesan,
required this.cobaLagi,
});

final String pesan;
final Future<void> Function() cobaLagi;









11. Lapisan data: contoh model dan alur transaksi





11.1 Model pengguna dan peran owner



Berkas: sarypos/lib/data/models/pengguna_model.dart




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 => peran == 'owner';

factory PenggunaModel.dariBaris(Map<String, dynamic> 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(),
);
}
}









11.2 Penyimpanan transaksi + detail + kurangi stok



Berkas: sarypos/lib/data/sources/transaksi_sumber.dart



Alur: hitung subtotal dari ItemKeranjang → validasi totalAkhirinsert ke tabel transaksiinsert batch ke detail_transaksi → loop kurangiStokSetelahPenjualan per item.




  Future<String?> simpanTransaksi({
String? idPengguna,
required List<ItemKeranjang> itemKeranjang,
required String metodePembayaran,
int? totalAkhir,
}) async {
if (itemKeranjang.isEmpty) {
return null;
}

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

final barisTransaksi = <String, dynamic>{
'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;
}









11.3 Peta berkas sumber data (ringkas)












































Berkas Tabel / bucket (umum)
pengguna_sumber.dart pengguna
profil_karyawan_sumber.dart
profil_karyawan, bucket karyawan-foto
produk_dan_stok_sumber.dart
produk, stok, bucket produk-gambar
transaksi_sumber.dart
transaksi, detail_transaksi
dashboard_sumber.dart agregat dari transaksi
laporan_sumber.dart
transaksi (+ relasi ke pengguna)
log_aktivitas_sumber.dart log_aktivitas
supabase_klien.dart inisialisasi + tes koneksi ke pengguna








12. Modul fitur (lib/features/) — cakupan untuk demo


























































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








13. Pengujian otomatis



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









14. Pertanyaan yang sering muncul saat pengujian (beserta jawaban singkat)




  1. Mengapa memakai IndexedStack untuk tab?


    Agar state tiap tab (mis. scroll posisi) tidak hilang saat ganti tab; hanya tab aktif yang “terlihat”, sisanya tetap di pohon widget.


  2. Bagaimana cara aplikasi tahu user owner?


    Dari baris pengguna di Supabase: field peran; di kode PenggunaModel.isOwner membandingkan dengan 'owner'.


  3. Di mana pembatasan fitur owner diimplementasikan?


    Kombinasi dorongJikaOwner, penggunaAdalahOwner, cegahJikaBelumLogin, dan kondisi UI di HalamanUtamaDenganNav / halaman pengaturan.


  4. Bagaimana state tema diwariskan?


    WarisanTema (InheritedNotifier<PengaturTema>); AppBarSarypos memanggil WarisanTema.dari(context) untuk mengubah tema.


  5. GetX dipakai untuk apa saja?


    Terutama GetMaterialApp dan navigasi/dialog di dorongJikaOwner; tidak ada pola reactive massal (Obx massal).


  6. Bagaimana alur simpan transaksi?


    TransaksiSumber.simpanTransaksi: insert header transaksi, insert lines detail_transaksi, lalu kurangi stok per produk.


  7. Di mana konfigurasi Supabase?


    supabase_konfigurasi.dart; wajib URL + anon key dari .env atau --dart-define.


  8. Apa risiko SUPABASE_SERVICE_ROLE_KEY di aplikasi?


    Kunci ini melewati RLS; untuk produksi seharusnya tidak dibundel di klien. Untuk proyek kuliah, siapkan penjelasan pembatasan dan skenario ideal (Edge Function/server).










15. Teks pegangan (ringkas, untuk dibaca sebelum masuk ruang ujian)





  • Akar widget: main.dartMainAppGetMaterialApp + Stack banner.


  • Tiga warisan: Tema (WarisanTema), Sesi (WarisanSesi), Notifikasi (WarisanNotifikasiInApp).


  • Home dinamis: loading → error (HalamanErrorKoneksi) → pembuka owner → HalamanUtamaDenganNav.


  • Tab: Beranda = HalamanDashboard, Kasir = HalamanKasirMenu, Saya = HalamanUserPengaturan.


  • Kartu UI: CardSarypos = wadah visual; CardRingkasan = pola ringkasan angka + ikon.


  • Feedback: snackbar = tampilkanSnackbarSarypos; banner atas = PengelolaNotifikasiInApp + BannerNotifikasiInApp.


  • Guard: cegahJikaBelumLogin (snackbar info); dorongJikaOwner (dialog + Get.to).


  • Data: model di lib/data/models/, akses Supabase di lib/data/sources/, klien tunggal supabaseKlien.


  • Tema warna: WarnaSarypos + temaSaryposTerang/Gelap + font Plus Jakarta.


  • Transaksi: tabel transaksi + detail_transaksi, stok dikurangi setelah sukses insert.


  • Bukti di kode: selalu bisa buka berkas yang dicantumkan di atas dan tunjukkan blok baris yang sama dengan dokumen ini.






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.

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Panduan Pengujian: Implementasi Widget, Properti, dan Arsitektur SaryPOS

Thematisch verwandte Begriffe: Panduan, Pengujian, Implementasi, Widget · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-94142 | A security vulnerability has been detected in BioStar Temperature Monito…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick