fix final3

This commit is contained in:
rhanarmt 2026-06-06 14:08:42 +07:00
parent c92c80a7c6
commit 1a34474c37
6 changed files with 1753 additions and 1580 deletions

View File

@ -36,6 +36,10 @@ class _DashboardScreenState extends State<DashboardScreen> with RouteAware {
_controller.loadDashboard(showLoading: false); _controller.loadDashboard(showLoading: false);
} }
Future<void> _refreshDashboard() {
return _controller.loadDashboard(showLoading: false);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final topPadding = MediaQuery.of(context).padding.top; final topPadding = MediaQuery.of(context).padding.top;
@ -69,6 +73,25 @@ class _DashboardScreenState extends State<DashboardScreen> with RouteAware {
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(10),
),
child: IconButton(
tooltip: 'Refresh',
icon: const Icon(
Icons.refresh,
color: Colors.white,
size: 20,
),
onPressed: _refreshDashboard,
padding: EdgeInsets.zero,
),
),
const SizedBox(width: 10),
Stack( Stack(
children: [ children: [
Container( Container(
@ -188,232 +211,244 @@ class _DashboardScreenState extends State<DashboardScreen> with RouteAware {
), ),
), ),
), ),
body: SingleChildScrollView( body: RefreshIndicator(
child: Column( onRefresh: _refreshDashboard,
children: [ color: AppColors.primaryBrown,
Padding( child: SingleChildScrollView(
padding: const EdgeInsets.all(16), physics: const AlwaysScrollableScrollPhysics(),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ Padding(
const SizedBox(height: 8), padding: const EdgeInsets.all(16),
Container( child: Column(
padding: const EdgeInsets.all(16), crossAxisAlignment: CrossAxisAlignment.start,
decoration: BoxDecoration( children: [
color: AppColors.bgWhite, const SizedBox(height: 8),
borderRadius: BorderRadius.circular(16), Container(
boxShadow: [AppColors.shadowLight], padding: const EdgeInsets.all(16),
), decoration: BoxDecoration(
child: Column( color: AppColors.bgWhite,
crossAxisAlignment: CrossAxisAlignment.start, borderRadius: BorderRadius.circular(16),
children: [ boxShadow: [AppColors.shadowLight],
Row( ),
mainAxisAlignment: MainAxisAlignment.spaceBetween, child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
Column( children: [
crossAxisAlignment: CrossAxisAlignment.start, Row(
children: [ mainAxisAlignment:
Text( MainAxisAlignment.spaceBetween,
'Penggunaan Bahan', children: [
style: AppTextStyles.headlineSmall Column(
.copyWith( crossAxisAlignment:
color: AppColors.textPrimary, CrossAxisAlignment.start,
),
),
const SizedBox(height: 2),
Text(
'Top 5 bahan paling sering digunakan',
style: AppTextStyles.bodySmall.copyWith(
color: AppColors.textTertiary,
),
),
],
),
Icon(
Icons.bar_chart_rounded,
color: AppColors.secondaryBlue,
size: 20,
),
],
),
const SizedBox(height: 16),
if (_controller.isLoading)
const Center(child: CircularProgressIndicator())
else
Column(
children:
_controller.penggunaanBahan.map((item) {
return Padding(
padding: const EdgeInsets.only(
bottom: 12,
),
child: _buildUsageRow(item),
);
}).toList(),
),
],
),
),
const SizedBox(height: 24),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.bgWhite,
borderRadius: BorderRadius.circular(16),
boxShadow: [AppColors.shadowLight],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.warning_amber_rounded,
color: AppColors.statusWarning,
size: 22,
),
const SizedBox(width: 10),
Text(
'Stok Menipis',
style: AppTextStyles.headlineSmall.copyWith(
color: AppColors.textPrimary,
),
),
],
),
const SizedBox(height: 16),
if (_controller.isLoading)
const Center(child: CircularProgressIndicator())
else if (_controller.lowStockItems.isEmpty)
Text(
'Tidak ada stok kritis',
style: AppTextStyles.bodySmall.copyWith(
color: AppColors.textTertiary,
),
)
else
..._controller.lowStockItems.map((item) {
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Text(
child: Column( 'Penggunaan Bahan',
crossAxisAlignment: style: AppTextStyles.headlineSmall
CrossAxisAlignment.start, .copyWith(
children: [ color: AppColors.textPrimary,
Text(
item['name'],
style: AppTextStyles.labelLarge
.copyWith(
color:
AppColors.textPrimary,
),
), ),
const SizedBox(height: 4),
Text(
'Stok: ${item['stock']}',
style: AppTextStyles.bodySmall
.copyWith(
color:
AppColors.textTertiary,
),
),
],
),
), ),
Container( const SizedBox(height: 2),
padding: const EdgeInsets.symmetric( Text(
horizontal: 12, 'Top 5 bahan paling sering digunakan',
vertical: 6, style: AppTextStyles.bodySmall.copyWith(
), color: AppColors.textTertiary,
decoration: BoxDecoration(
color: item['statusColor'],
borderRadius: BorderRadius.circular(
6,
),
),
child: Text(
item['status'],
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.white,
),
), ),
), ),
], ],
), ),
); Icon(
}), Icons.bar_chart_rounded,
color: AppColors.secondaryBlue,
size: 20,
),
],
),
const SizedBox(height: 16),
if (_controller.isLoading)
const Center(child: CircularProgressIndicator())
else
Column(
children:
_controller.penggunaanBahan.map((item) {
return Padding(
padding: const EdgeInsets.only(
bottom: 12,
),
child: _buildUsageRow(item),
);
}).toList(),
),
],
),
),
const SizedBox(height: 24),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.bgWhite,
borderRadius: BorderRadius.circular(16),
boxShadow: [AppColors.shadowLight],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.warning_amber_rounded,
color: AppColors.statusWarning,
size: 22,
),
const SizedBox(width: 10),
Text(
'Stok Menipis',
style: AppTextStyles.headlineSmall.copyWith(
color: AppColors.textPrimary,
),
),
],
),
const SizedBox(height: 16),
if (_controller.isLoading)
const Center(child: CircularProgressIndicator())
else if (_controller.lowStockItems.isEmpty)
Text(
'Tidak ada stok kritis',
style: AppTextStyles.bodySmall.copyWith(
color: AppColors.textTertiary,
),
)
else
..._controller.lowStockItems.map((item) {
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
item['name'],
style: AppTextStyles.labelLarge
.copyWith(
color:
AppColors.textPrimary,
),
),
const SizedBox(height: 4),
Text(
'Stok: ${item['stock']}',
style: AppTextStyles.bodySmall
.copyWith(
color:
AppColors
.textTertiary,
),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: item['statusColor'],
borderRadius: BorderRadius.circular(
6,
),
),
child: Text(
item['status'],
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
],
),
);
}),
],
),
),
const SizedBox(height: 24),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () {
Navigator.of(
context,
).pushNamed('/prediction');
},
icon: const Icon(Icons.trending_up, size: 20),
label: const Text(
'Lihat Prediksi',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.secondaryBlue,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
vertical: 16,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
elevation: 2,
),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
onPressed: () {
Navigator.of(
context,
).pushNamed('/transaction');
},
icon: const Icon(Icons.check_circle, size: 20),
label: const Text(
'Tambah Stok',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.statusSuccess,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
vertical: 16,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
elevation: 2,
),
),
),
], ],
), ),
), const SizedBox(height: 24),
const SizedBox(height: 24), ],
Row( ),
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () {
Navigator.of(context).pushNamed('/prediction');
},
icon: const Icon(Icons.trending_up, size: 20),
label: const Text(
'Lihat Prediksi',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.secondaryBlue,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
vertical: 16,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
elevation: 2,
),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
onPressed: () {
Navigator.of(context).pushNamed('/transaction');
},
icon: const Icon(Icons.check_circle, size: 20),
label: const Text(
'Tambah Stok',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.statusSuccess,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
vertical: 16,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
elevation: 2,
),
),
),
],
),
const SizedBox(height: 24),
],
), ),
), ],
], ),
), ),
), ),
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: BottomNavigationBar(

File diff suppressed because it is too large Load Diff

View File

@ -45,6 +45,10 @@ class _ProductListScreenState extends State<ProductListScreen> with RouteAware {
_controller.loadProducts(); _controller.loadProducts();
} }
Future<void> _refreshProducts() {
return _controller.loadProducts();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AnimatedBuilder( return AnimatedBuilder(
@ -111,6 +115,25 @@ class _ProductListScreenState extends State<ProductListScreen> with RouteAware {
), ),
], ],
), ),
const Spacer(),
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(10),
),
child: IconButton(
tooltip: 'Refresh',
icon: const Icon(
Icons.refresh,
color: Colors.white,
size: 20,
),
onPressed: _refreshProducts,
padding: EdgeInsets.zero,
),
),
], ],
), ),
), ),
@ -155,7 +178,7 @@ class _ProductListScreenState extends State<ProductListScreen> with RouteAware {
), ),
), ),
body: RefreshIndicator( body: RefreshIndicator(
onRefresh: _controller.loadProducts, onRefresh: _refreshProducts,
color: AppColors.primaryBrown, color: AppColors.primaryBrown,
child: child:
_controller.isLoading _controller.isLoading

View File

@ -61,6 +61,10 @@ class _ReportScreenState extends State<ReportScreen> {
_controller.startAutoRefresh(); _controller.startAutoRefresh();
} }
Future<void> _refreshReports() {
return _controller.loadReports();
}
double _sectionMaxHeight() { double _sectionMaxHeight() {
final height = MediaQuery.of(context).size.height * 0.35; final height = MediaQuery.of(context).size.height * 0.35;
if (height < 220) return 220; if (height < 220) return 220;
@ -82,6 +86,13 @@ class _ReportScreenState extends State<ReportScreen> {
'Laporan & Analitik', 'Laporan & Analitik',
style: AppTextStyles.headlineLarge.copyWith(color: Colors.white), style: AppTextStyles.headlineLarge.copyWith(color: Colors.white),
), ),
actions: [
IconButton(
tooltip: 'Refresh',
icon: const Icon(Icons.refresh, color: Colors.white),
onPressed: _refreshReports,
),
],
), ),
body: AnimatedBuilder( body: AnimatedBuilder(
animation: _controller, animation: _controller,
@ -98,7 +109,7 @@ class _ReportScreenState extends State<ReportScreen> {
final List<double> demandTrend = _controller.demandTrend; final List<double> demandTrend = _controller.demandTrend;
return RefreshIndicator( return RefreshIndicator(
onRefresh: () => _controller.loadReports(), onRefresh: _refreshReports,
child: SingleChildScrollView( child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),

View File

@ -125,45 +125,57 @@ class _SettingsScreenState extends State<SettingsScreen> {
'Pengaturan', 'Pengaturan',
style: AppTextStyles.headlineLarge.copyWith(color: Colors.white), style: AppTextStyles.headlineLarge.copyWith(color: Colors.white),
), ),
actions: [
IconButton(
tooltip: 'Refresh',
icon: const Icon(Icons.refresh, color: Colors.white),
onPressed: _loadAccount,
),
],
), ),
body: SingleChildScrollView( body: RefreshIndicator(
child: Padding( onRefresh: _loadAccount,
padding: const EdgeInsets.all(16.0), color: AppColors.primaryBrown,
child: Column( child: SingleChildScrollView(
crossAxisAlignment: CrossAxisAlignment.start, physics: const AlwaysScrollableScrollPhysics(),
children: [ child: Padding(
_buildSectionTitle('Akun'), padding: const EdgeInsets.all(16.0),
const SizedBox(height: 16), child: Column(
_buildAccountCard(), crossAxisAlignment: CrossAxisAlignment.start,
const SizedBox(height: 32), children: [
_buildSectionTitle('Keamanan'), _buildSectionTitle('Akun'),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildActionCard( _buildAccountCard(),
icon: Icons.lock_outlined, const SizedBox(height: 32),
title: 'Ubah Password', _buildSectionTitle('Keamanan'),
subtitle: 'Verifikasi OTP melalui email akun', const SizedBox(height: 16),
onTap: _showResetPasswordDialog, _buildActionCard(
), icon: Icons.lock_outlined,
const SizedBox(height: 32), title: 'Ubah Password',
SizedBox( subtitle: 'Verifikasi OTP melalui email akun',
width: double.infinity, onTap: _showResetPasswordDialog,
child: ElevatedButton.icon( ),
onPressed: _showLogoutDialog, const SizedBox(height: 32),
icon: const Icon(Icons.logout), SizedBox(
label: const Text('Logout'), width: double.infinity,
style: ElevatedButton.styleFrom( child: ElevatedButton.icon(
backgroundColor: AppColors.statusError, onPressed: _showLogoutDialog,
foregroundColor: Colors.white, icon: const Icon(Icons.logout),
padding: const EdgeInsets.symmetric(vertical: 14), label: const Text('Logout'),
shape: RoundedRectangleBorder( style: ElevatedButton.styleFrom(
borderRadius: BorderRadius.circular(10), backgroundColor: AppColors.statusError,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
), ),
), ),
), ),
), const SizedBox(height: 32),
const SizedBox(height: 32), _buildAppInfo(),
_buildAppInfo(), ],
], ),
), ),
), ),
), ),

View File

@ -48,6 +48,20 @@ class _TransactionScreenState extends State<TransactionScreen> {
); );
} }
Future<void> _refreshProducts() async {
try {
await _controller.loadProducts();
} catch (_) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Gagal memuat data produk'),
backgroundColor: AppColors.statusError,
),
);
}
}
Future<void> _submitAllTransactions() async { Future<void> _submitAllTransactions() async {
final result = await _controller.submitAllTransactions(); final result = await _controller.submitAllTransactions();
@ -504,115 +518,24 @@ class _TransactionScreenState extends State<TransactionScreen> {
icon: const Icon(Icons.arrow_back, color: Colors.white), icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
), ),
actions: [
IconButton(
tooltip: 'Refresh',
icon: const Icon(Icons.refresh, color: Colors.white),
onPressed: _refreshProducts,
),
],
), ),
body: SingleChildScrollView( body: RefreshIndicator(
child: Padding( onRefresh: _refreshProducts,
padding: const EdgeInsets.all(16.0), color: AppColors.primaryBrown,
child: Column( child: SingleChildScrollView(
crossAxisAlignment: CrossAxisAlignment.start, physics: const AlwaysScrollableScrollPhysics(),
children: [ child: Padding(
Container( padding: const EdgeInsets.all(16.0),
padding: const EdgeInsets.all(16), child: Column(
decoration: BoxDecoration( crossAxisAlignment: CrossAxisAlignment.start,
color: AppColors.bgWhite, children: [
borderRadius: BorderRadius.circular(16),
boxShadow: [AppColors.shadowLight],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Pilih Produk',
style: AppTextStyles.headlineSmall.copyWith(
color: AppColors.textPrimary,
),
),
const SizedBox(height: 16),
Text(
'Produk',
style: AppTextStyles.labelLarge.copyWith(
color: AppColors.textPrimary,
),
),
const SizedBox(height: 8),
DropdownButtonFormField<String>(
value: _controller.selectedProduct,
decoration: InputDecoration(
hintText: 'Pilih produk',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
filled: true,
fillColor: AppColors.bgLight,
),
items:
_controller.products
.map(
(product) => DropdownMenuItem(
value: product,
child: Text(product),
),
)
.toList(),
onChanged: _controller.setSelectedProduct,
),
const SizedBox(height: 24),
Text(
'Jumlah',
style: AppTextStyles.labelLarge.copyWith(
color: AppColors.textPrimary,
),
),
const SizedBox(height: 8),
TextField(
controller: _controller.quantityController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: 'Masukkan jumlah unit',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
filled: true,
fillColor: AppColors.bgLight,
),
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: _addToCart,
icon: const Icon(Icons.add_shopping_cart),
label: const Text('Tambah ke Keranjang'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBrown,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
onPressed: _showAddProductDialog,
icon: const Icon(Icons.add_circle_outline),
label: const Text('Tambah Produk Baru'),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
side: BorderSide(color: AppColors.secondaryBlue),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
),
),
],
),
),
const SizedBox(height: 24),
if (_controller.cartItems.isNotEmpty) ...[
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -623,282 +546,96 @@ class _TransactionScreenState extends State<TransactionScreen> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Text(
mainAxisAlignment: MainAxisAlignment.spaceBetween, 'Pilih Produk',
children: [ style: AppTextStyles.headlineSmall.copyWith(
Text( color: AppColors.textPrimary,
'Keranjang (${_controller.cartItems.length})', ),
style: AppTextStyles.headlineSmall.copyWith(
color: AppColors.textPrimary,
),
),
GestureDetector(
onTap: _controller.clearCart,
child: Text(
'Hapus Semua',
style: AppTextStyles.labelMedium.copyWith(
color: AppColors.statusError,
fontWeight: FontWeight.w600,
),
),
),
],
),
const SizedBox(height: 16),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: _controller.cartItems.length,
itemBuilder: (context, index) {
final item = _controller.cartItems[index];
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.bgLight,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: AppColors.grey300),
),
child: Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
item.productName,
style: AppTextStyles.labelLarge
.copyWith(
color:
AppColors.textPrimary,
),
),
const SizedBox(height: 4),
Text(
'Rp ${item.unitPrice} / unit',
style: AppTextStyles.labelSmall
.copyWith(
color:
AppColors
.textSecondary,
),
),
],
),
),
Text(
'Rp ${item.totalPrice}',
style: AppTextStyles.labelLarge
.copyWith(
color: AppColors.statusSuccess,
fontWeight: FontWeight.w700,
),
),
],
),
const SizedBox(height: 12),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
GestureDetector(
onTap:
() => _controller
.updateQuantity(
index,
item.quantity - 1,
),
child: Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: AppColors.primaryBrown,
borderRadius:
BorderRadius.circular(6),
),
child: const Icon(
Icons.remove,
color: Colors.white,
size: 18,
),
),
),
const SizedBox(width: 12),
Text(
item.quantity.toString(),
style: AppTextStyles.labelLarge
.copyWith(
color:
AppColors.textPrimary,
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 12),
GestureDetector(
onTap:
() => _controller
.updateQuantity(
index,
item.quantity + 1,
),
child: Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: AppColors.primaryBrown,
borderRadius:
BorderRadius.circular(6),
),
child: const Icon(
Icons.add,
color: Colors.white,
size: 18,
),
),
),
],
),
GestureDetector(
onTap:
() => _controller.removeFromCart(
index,
),
child: Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: AppColors.statusError
.withValues(alpha: 0.1),
borderRadius:
BorderRadius.circular(6),
),
child: Icon(
Icons.delete,
color: AppColors.statusError,
size: 18,
),
),
),
],
),
],
),
);
},
),
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Total Pembayaran',
style: AppTextStyles.labelLarge.copyWith(
color: AppColors.textPrimary,
),
),
Text(
'Rp ${_controller.totalPrice}',
style: AppTextStyles.headlineSmall.copyWith(
color: AppColors.primaryBrown,
fontWeight: FontWeight.w700,
),
),
],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Tanggal Stok Masuk', 'Produk',
style: AppTextStyles.labelLarge.copyWith( style: AppTextStyles.labelLarge.copyWith(
color: AppColors.textPrimary, color: AppColors.textPrimary,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
GestureDetector( DropdownButtonFormField<String>(
onTap: () async { value: _controller.selectedProduct,
final picked = await showDatePicker( decoration: InputDecoration(
context: context, hintText: 'Pilih produk',
initialDate: border: OutlineInputBorder(
_controller.selectedDate ?? DateTime.now(), borderRadius: BorderRadius.circular(10),
firstDate: DateTime(2020),
lastDate: DateTime.now(),
);
if (picked != null &&
picked != _controller.selectedDate) {
_controller.setSelectedDate(picked);
}
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
), ),
decoration: BoxDecoration( filled: true,
border: Border.all(color: AppColors.grey300), fillColor: AppColors.bgLight,
borderRadius: BorderRadius.circular(8), ),
), items:
child: Row( _controller.products
children: [ .map(
const Icon( (product) => DropdownMenuItem(
Icons.calendar_today, value: product,
color: AppColors.primaryBrown, child: Text(product),
), ),
const SizedBox(width: 12), )
Text( .toList(),
_controller.selectedDate != null onChanged: _controller.setSelectedProduct,
? '${_controller.selectedDate!.day}/${_controller.selectedDate!.month}/${_controller.selectedDate!.year}' ),
: 'Pilih tanggal', const SizedBox(height: 24),
style: AppTextStyles.bodyMedium, Text(
), 'Jumlah',
], style: AppTextStyles.labelLarge.copyWith(
color: AppColors.textPrimary,
),
),
const SizedBox(height: 8),
TextField(
controller: _controller.quantityController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: 'Masukkan jumlah unit',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
), ),
filled: true,
fillColor: AppColors.bgLight,
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: onPressed: _addToCart,
_controller.isLoading icon: const Icon(Icons.add_shopping_cart),
? null label: const Text('Tambah ke Keranjang'),
: _submitAllTransactions,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBrown, backgroundColor: AppColors.primaryBrown,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 14, vertical: 12,
), ),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
), ),
icon: ),
_controller.isLoading ),
? const SizedBox( const SizedBox(height: 16),
width: 20, SizedBox(
height: 20, width: double.infinity,
child: CircularProgressIndicator( child: OutlinedButton.icon(
strokeWidth: 2, onPressed: _showAddProductDialog,
valueColor: icon: const Icon(Icons.add_circle_outline),
AlwaysStoppedAnimation<Color>( label: const Text('Tambah Produk Baru'),
Colors.white, style: OutlinedButton.styleFrom(
), padding: const EdgeInsets.symmetric(
), vertical: 12,
) ),
: const Icon(Icons.check_circle), side: BorderSide(
label: Text( color: AppColors.secondaryBlue,
_controller.isLoading ),
? 'Menyimpan...' shape: RoundedRectangleBorder(
: 'Simpan Stok Masuk', borderRadius: BorderRadius.circular(10),
style: AppTextStyles.labelLarge, ),
), ),
), ),
), ),
@ -906,98 +643,406 @@ class _TransactionScreenState extends State<TransactionScreen> {
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
], if (_controller.cartItems.isNotEmpty) ...[
Text( Container(
'Riwayat Stok Masuk (${_controller.transactions.length})', padding: const EdgeInsets.all(16),
style: AppTextStyles.headlineSmall.copyWith( decoration: BoxDecoration(
color: AppColors.textPrimary, color: AppColors.bgWhite,
), borderRadius: BorderRadius.circular(16),
), boxShadow: [AppColors.shadowLight],
const SizedBox(height: 16), ),
if (_controller.transactions.isEmpty)
Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 32.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Icon( Row(
Icons.inbox, mainAxisAlignment: MainAxisAlignment.spaceBetween,
size: 64, children: [
color: AppColors.grey300, Text(
'Keranjang (${_controller.cartItems.length})',
style: AppTextStyles.headlineSmall.copyWith(
color: AppColors.textPrimary,
),
),
GestureDetector(
onTap: _controller.clearCart,
child: Text(
'Hapus Semua',
style: AppTextStyles.labelMedium.copyWith(
color: AppColors.statusError,
fontWeight: FontWeight.w600,
),
),
),
],
),
const SizedBox(height: 16),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: _controller.cartItems.length,
itemBuilder: (context, index) {
final item = _controller.cartItems[index];
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.bgLight,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: AppColors.grey300,
),
),
child: Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
item.productName,
style: AppTextStyles
.labelLarge
.copyWith(
color:
AppColors
.textPrimary,
),
),
const SizedBox(height: 4),
Text(
'Rp ${item.unitPrice} / unit',
style: AppTextStyles
.labelSmall
.copyWith(
color:
AppColors
.textSecondary,
),
),
],
),
),
Text(
'Rp ${item.totalPrice}',
style: AppTextStyles.labelLarge
.copyWith(
color:
AppColors.statusSuccess,
fontWeight: FontWeight.w700,
),
),
],
),
const SizedBox(height: 12),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
GestureDetector(
onTap:
() => _controller
.updateQuantity(
index,
item.quantity - 1,
),
child: Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color:
AppColors.primaryBrown,
borderRadius:
BorderRadius.circular(
6,
),
),
child: const Icon(
Icons.remove,
color: Colors.white,
size: 18,
),
),
),
const SizedBox(width: 12),
Text(
item.quantity.toString(),
style: AppTextStyles.labelLarge
.copyWith(
color:
AppColors.textPrimary,
fontWeight:
FontWeight.w600,
),
),
const SizedBox(width: 12),
GestureDetector(
onTap:
() => _controller
.updateQuantity(
index,
item.quantity + 1,
),
child: Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color:
AppColors.primaryBrown,
borderRadius:
BorderRadius.circular(
6,
),
),
child: const Icon(
Icons.add,
color: Colors.white,
size: 18,
),
),
),
],
),
GestureDetector(
onTap:
() => _controller
.removeFromCart(index),
child: Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: AppColors.statusError
.withValues(alpha: 0.1),
borderRadius:
BorderRadius.circular(6),
),
child: Icon(
Icons.delete,
color: AppColors.statusError,
size: 18,
),
),
),
],
),
],
),
);
},
),
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Total Pembayaran',
style: AppTextStyles.labelLarge.copyWith(
color: AppColors.textPrimary,
),
),
Text(
'Rp ${_controller.totalPrice}',
style: AppTextStyles.headlineSmall.copyWith(
color: AppColors.primaryBrown,
fontWeight: FontWeight.w700,
),
),
],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Belum ada Stok Masuk', 'Tanggal Stok Masuk',
style: AppTextStyles.bodyMedium.copyWith( style: AppTextStyles.labelLarge.copyWith(
color: AppColors.textSecondary, color: AppColors.textPrimary,
),
),
const SizedBox(height: 8),
GestureDetector(
onTap: () async {
final picked = await showDatePicker(
context: context,
initialDate:
_controller.selectedDate ??
DateTime.now(),
firstDate: DateTime(2020),
lastDate: DateTime.now(),
);
if (picked != null &&
picked != _controller.selectedDate) {
_controller.setSelectedDate(picked);
}
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
decoration: BoxDecoration(
border: Border.all(color: AppColors.grey300),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
const Icon(
Icons.calendar_today,
color: AppColors.primaryBrown,
),
const SizedBox(width: 12),
Text(
_controller.selectedDate != null
? '${_controller.selectedDate!.day}/${_controller.selectedDate!.month}/${_controller.selectedDate!.year}'
: 'Pilih tanggal',
style: AppTextStyles.bodyMedium,
),
],
),
),
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed:
_controller.isLoading
? null
: _submitAllTransactions,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBrown,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
vertical: 14,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
icon:
_controller.isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor:
AlwaysStoppedAnimation<Color>(
Colors.white,
),
),
)
: const Icon(Icons.check_circle),
label: Text(
_controller.isLoading
? 'Menyimpan...'
: 'Simpan Stok Masuk',
style: AppTextStyles.labelLarge,
),
), ),
), ),
], ],
), ),
), ),
) const SizedBox(height: 24),
else ],
ListView.builder( Text(
shrinkWrap: true, 'Riwayat Stok Masuk (${_controller.transactions.length})',
physics: const NeverScrollableScrollPhysics(), style: AppTextStyles.headlineSmall.copyWith(
itemCount: _controller.transactions.length, color: AppColors.textPrimary,
itemBuilder: (context, index) { ),
final tx = _controller.transactions[index]; ),
return Container( const SizedBox(height: 16),
margin: const EdgeInsets.only(bottom: 12), if (_controller.transactions.isEmpty)
padding: const EdgeInsets.all(12), Center(
decoration: BoxDecoration( child: Padding(
color: AppColors.bgWhite, padding: const EdgeInsets.symmetric(vertical: 32.0),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.grey200),
),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Icon(
mainAxisAlignment: Icons.inbox,
MainAxisAlignment.spaceBetween, size: 64,
children: [ color: AppColors.grey300,
Text(
tx.productName,
style: AppTextStyles.labelLarge.copyWith(
color: AppColors.textPrimary,
),
),
Text(
'Rp ${tx.totalPrice}',
style: AppTextStyles.labelLarge.copyWith(
color: AppColors.statusSuccess,
fontWeight: FontWeight.w700,
),
),
],
), ),
const SizedBox(height: 8), const SizedBox(height: 16),
Row( Text(
mainAxisAlignment: 'Belum ada Stok Masuk',
MainAxisAlignment.spaceBetween, style: AppTextStyles.bodyMedium.copyWith(
children: [ color: AppColors.textSecondary,
Text( ),
'${tx.quantity} unit × Rp ${tx.unitPrice}',
style: AppTextStyles.labelMedium.copyWith(
color: AppColors.textSecondary,
),
),
Text(
'${tx.date.day}/${tx.date.month}/${tx.date.year}',
style: AppTextStyles.labelSmall.copyWith(
color: AppColors.textSecondary,
),
),
],
), ),
], ],
), ),
); ),
}, )
), else
], ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: _controller.transactions.length,
itemBuilder: (context, index) {
final tx = _controller.transactions[index];
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.bgWhite,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.grey200),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
tx.productName,
style: AppTextStyles.labelLarge.copyWith(
color: AppColors.textPrimary,
),
),
Text(
'Rp ${tx.totalPrice}',
style: AppTextStyles.labelLarge.copyWith(
color: AppColors.statusSuccess,
fontWeight: FontWeight.w700,
),
),
],
),
const SizedBox(height: 8),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
'${tx.quantity} unit × Rp ${tx.unitPrice}',
style: AppTextStyles.labelMedium.copyWith(
color: AppColors.textSecondary,
),
),
Text(
'${tx.date.day}/${tx.date.month}/${tx.date.year}',
style: AppTextStyles.labelSmall.copyWith(
color: AppColors.textSecondary,
),
),
],
),
],
),
);
},
),
],
),
), ),
), ),
), ),