Kasir: indikator loading produk + empty/error state + tombol Coba lagi, refresh retry, login tanpa loading global
This commit is contained in:
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user