Kasir: indikator loading produk + empty/error state + tombol Coba lagi, refresh retry, login tanpa loading global

This commit is contained in:
Onie
2026-10-18 16:30:44 +07:00
parent aba36f5c60
commit 0174ca4f64
3 changed files with 193 additions and 29 deletions

View File

@@ -56,6 +56,7 @@ class _KasirScreenState extends State<KasirScreen> {
p.name.toLowerCase().contains(query) ||
p.barcode.toLowerCase().contains(query))
.toList();
final isInitialProductsLoading = state.productsLoading && !state.dataReady;
return Column(
children: [
@@ -283,8 +284,30 @@ class _KasirScreenState extends State<KasirScreen> {
),
),
Expanded(
child: _isGrid
? GridView.builder(
child: isInitialProductsLoading
? const _ProductsLoading()
: products.isEmpty
? state.productsError != null
? _EmptyState(
icon: Icons.cloud_off_outlined,
title: 'Gagal memuat produk',
subtitle:
'Periksa koneksi lalu coba lagi. Detail: ${state.productsError}',
actionLabel: 'Coba lagi',
)
: _EmptyState(
icon: state.products.isEmpty
? Icons.inventory_2_outlined
: Icons.search_off,
title: state.products.isEmpty
? 'Belum ada produk'
: 'Tidak ditemukan',
subtitle: state.products.isEmpty
? 'Tambahkan produk lewat tab Produk.'
: 'Coba kata kunci lain.',
)
: _isGrid
? GridView.builder(
padding: const EdgeInsets.fromLTRB(12, 4, 12, 12),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
@@ -1762,3 +1785,103 @@ class _QtyButton extends StatelessWidget {
);
}
}
class _EmptyState extends StatelessWidget {
final IconData icon;
final String title;
final String subtitle;
final String? actionLabel;
const _EmptyState({
required this.icon,
required this.title,
required this.subtitle,
this.actionLabel,
});
@override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 56, color: kMuted),
const SizedBox(height: 16),
Text(
title,
style: const TextStyle(
color: kText,
fontSize: 16,
fontWeight: FontWeight.w700,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
subtitle,
style: const TextStyle(color: kMuted, fontSize: 13),
textAlign: TextAlign.center,
),
if (actionLabel != null) ...[
const SizedBox(height: 16),
OutlinedButton.icon(
onPressed: () => context.read<AppState>().retryProducts(),
icon: const Icon(Icons.refresh),
label: Text(actionLabel!),
),
],
],
),
),
);
}
}
class _ProductsLoading extends StatelessWidget {
const _ProductsLoading();
@override
Widget build(BuildContext context) {
return ListView.separated(
padding: const EdgeInsets.fromLTRB(12, 4, 12, 12),
itemCount: 6,
separatorBuilder: (_, __) => const SizedBox(height: 8),
itemBuilder: (context, i) {
return Container(
height: 72,
decoration: BoxDecoration(
color: kSurface2,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: kBorder),
),
child: Row(
children: [
Container(
width: 56,
height: 56,
margin: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: kBorder,
borderRadius: BorderRadius.circular(8),
),
),
const Expanded(
child: Center(
child: SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: kAccent,
),
),
),
),
],
),
);
},
);
}
}