Kasir: indikator loading produk + empty/error state + tombol Coba lagi, refresh retry, login tanpa loading global
This commit is contained in:
@@ -43,6 +43,8 @@ class AppState extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> retryProducts() => refresh();
|
||||
|
||||
// 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.
|
||||
final SupabaseClient _admin = SupabaseClient(supabaseUrl, supabaseServiceKey);
|
||||
@@ -69,6 +71,9 @@ class AppState extends ChangeNotifier {
|
||||
List<Map<String, dynamic>> kasirAccounts = [];
|
||||
bool loading = true;
|
||||
String? error;
|
||||
bool dataReady = false;
|
||||
bool productsLoading = false;
|
||||
String? productsError;
|
||||
|
||||
static const int _pageSize = 30;
|
||||
int _visibleProductCount = _pageSize;
|
||||
@@ -196,7 +201,12 @@ class AppState extends ChangeNotifier {
|
||||
_salesFirstLoad = false;
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
/// Refresh produk + riwayat dengan retry terbatas untuk kegagalan sementara.
|
||||
/// Nyalakan indikator loading hanya saat data pertama belum siap, supaya
|
||||
/// Kasir bisa menampilkan status memuat/gagal/kosong dengan jelas.
|
||||
/// Nonaktifkan indikator apa pun yang menyebabkan masalah refresh berulang.
|
||||
/// Kirim sinyal hanya pada pemuatan awal; polling latar tidak mengganggu.
|
||||
Future<void> refresh({bool notifyLoading = true, int retries = 1}) async {
|
||||
final authOk = await _ensureSessionAndAuth();
|
||||
if (!authOk) {
|
||||
// Auth mati: jangan lanjut query (pasti 401). Cukup notify agar
|
||||
@@ -204,9 +214,34 @@ class AppState extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
await _loadProducts();
|
||||
await _loadSales();
|
||||
notifyListeners();
|
||||
|
||||
final showProductsLoading = notifyLoading && !dataReady;
|
||||
if (showProductsLoading) {
|
||||
productsLoading = true;
|
||||
notifyListeners();
|
||||
}
|
||||
try {
|
||||
await _loadProducts();
|
||||
await _loadSales();
|
||||
dataReady = true;
|
||||
} catch (e) {
|
||||
productsError ??= e.toString();
|
||||
if (retries > 0) {
|
||||
// Coba sekali lagi untuk kegagalan sementara (misal koneksi putus).
|
||||
productsError = null;
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
await refresh(notifyLoading: notifyLoading, retries: retries - 1);
|
||||
return;
|
||||
}
|
||||
debugPrint('Gagal refresh: $e');
|
||||
} finally {
|
||||
if (showProductsLoading) {
|
||||
productsLoading = false;
|
||||
}
|
||||
if (notifyLoading) {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final OfflineQueueService _offlineQueue = OfflineQueueService();
|
||||
@@ -313,17 +348,20 @@ class AppState extends ChangeNotifier {
|
||||
);
|
||||
userId = res.user?.id;
|
||||
_sessionExpired = false;
|
||||
await _loadProfile();
|
||||
// Muat produk/riwayat langsung agar user tidak menunggu poll berikut.
|
||||
// Gagal load tak mematikan proses login.
|
||||
try {
|
||||
await refresh();
|
||||
} catch (_) {}
|
||||
dataReady = false;
|
||||
productsLoading = true;
|
||||
productsError = null;
|
||||
notifyListeners();
|
||||
await _loadProfile();
|
||||
await refresh();
|
||||
return null;
|
||||
} on AuthException catch (e) {
|
||||
productsLoading = false;
|
||||
notifyListeners();
|
||||
return e.message;
|
||||
} catch (e) {
|
||||
productsLoading = false;
|
||||
notifyListeners();
|
||||
return e.toString();
|
||||
}
|
||||
}
|
||||
@@ -357,6 +395,10 @@ class AppState extends ChangeNotifier {
|
||||
_profile = null;
|
||||
cart.clear();
|
||||
_discount = 0;
|
||||
products = [];
|
||||
dataReady = false;
|
||||
productsLoading = false;
|
||||
productsError = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user