Badge unseenSalesCount di tab Riwayat (admin only), reset saat tab dibuka

This commit is contained in:
Onie
2026-10-18 03:17:25 +07:00
parent 808992f9bd
commit 812fb13eca
2 changed files with 34 additions and 8 deletions

View File

@@ -35,7 +35,7 @@ class _HomePageState extends State<HomePage> {
return screens; return screens;
} }
List<BottomNavigationBarItem> _navItemsForRole(String? role) { List<BottomNavigationBarItem> _navItemsForRole(String? role, AppState state) {
final items = <BottomNavigationBarItem>[ final items = <BottomNavigationBarItem>[
const BottomNavigationBarItem( const BottomNavigationBarItem(
icon: Icon(Icons.point_of_sale), icon: Icon(Icons.point_of_sale),
@@ -48,8 +48,12 @@ class _HomePageState extends State<HomePage> {
icon: Icon(Icons.inventory_2), icon: Icon(Icons.inventory_2),
label: 'Produk', label: 'Produk',
), ),
const BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.receipt_long), icon: Badge(
isLabelVisible: state.unseenSalesCount > 0,
label: Text('${state.unseenSalesCount}'),
child: const Icon(Icons.receipt_long),
),
label: 'Riwayat', label: 'Riwayat',
), ),
const BottomNavigationBarItem( const BottomNavigationBarItem(
@@ -76,7 +80,7 @@ class _HomePageState extends State<HomePage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final state = context.watch<AppState>(); final state = context.watch<AppState>();
final screens = _screensForRole(state.userRole); final screens = _screensForRole(state.userRole);
final navItems = _navItemsForRole(state.userRole); final navItems = _navItemsForRole(state.userRole, state);
final safeIndex = _index >= screens.length ? 0 : _index; final safeIndex = _index >= screens.length ? 0 : _index;
@@ -109,7 +113,14 @@ class _HomePageState extends State<HomePage> {
bottomNavigationBar: navItems.length >= 2 bottomNavigationBar: navItems.length >= 2
? BottomNavigationBar( ? BottomNavigationBar(
currentIndex: safeIndex, currentIndex: safeIndex,
onTap: (i) => setState(() => _index = i), onTap: (i) {
setState(() => _index = i);
// Reset badge saat tab Riwayat dibuka.
final riwayatIdx = state.userRole == 'admin' ? 2 : 1;
if (i == riwayatIdx) {
context.read<AppState>().markSalesSeen();
}
},
items: navItems, items: navItems,
) )
: null, : null,

View File

@@ -33,6 +33,16 @@ class AppState extends ChangeNotifier {
StreamController<Sale>.broadcast(); StreamController<Sale>.broadcast();
final Set<String> _notifiedSaleIds = {}; final Set<String> _notifiedSaleIds = {};
// Jumlah transaksi baru yang belum dilihat admin (badge di tab Riwayat).
int unseenSalesCount = 0;
void markSalesSeen() {
if (unseenSalesCount != 0) {
unseenSalesCount = 0;
notifyListeners();
}
}
// Client dengan service_role key untuk operasi admin (buat/edit/hapus akun kasir). // Client dengan service_role key untuk operasi admin (buat/edit/hapus akun kasir).
// PERINGATAN: key ini bypass RLS — hanya untuk app POS di device terpercaya. // PERINGATAN: key ini bypass RLS — hanya untuk app POS di device terpercaya.
final SupabaseClient _admin = SupabaseClient(supabaseUrl, supabaseServiceKey); final SupabaseClient _admin = SupabaseClient(supabaseUrl, supabaseServiceKey);
@@ -159,6 +169,7 @@ class AppState extends ChangeNotifier {
return; return;
} }
final now = DateTime.now(); final now = DateTime.now();
bool newSeen = false;
for (final s in sales) { for (final s in sales) {
if (!_notifiedSaleIds.contains(s.id)) { if (!_notifiedSaleIds.contains(s.id)) {
_notifiedSaleIds.add(s.id); _notifiedSaleIds.add(s.id);
@@ -166,12 +177,16 @@ class AppState extends ChangeNotifier {
// Lewati juga transaksi sendiri — admin tahu kalau dia yang jualan. // Lewati juga transaksi sendiri — admin tahu kalau dia yang jualan.
if (!_salesFirstLoad && if (!_salesFirstLoad &&
s.userId != userId && s.userId != userId &&
now.difference(s.date).inMinutes.abs() <= 5 && now.difference(s.date).inMinutes.abs() <= 5) {
!newSaleNotifications.isClosed) { newSeen = true;
newSaleNotifications.add(s); unseenSalesCount++;
if (!newSaleNotifications.isClosed) {
newSaleNotifications.add(s);
}
} }
} }
} }
if (newSeen) notifyListeners();
_salesFirstLoad = false; _salesFirstLoad = false;
} }