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,7 +211,11 @@ class _DashboardScreenState extends State<DashboardScreen> with RouteAware {
), ),
), ),
), ),
body: SingleChildScrollView( body: RefreshIndicator(
onRefresh: _refreshDashboard,
color: AppColors.primaryBrown,
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Column( child: Column(
children: [ children: [
Padding( Padding(
@ -208,10 +235,12 @@ class _DashboardScreenState extends State<DashboardScreen> with RouteAware {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [ children: [
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment:
CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'Penggunaan Bahan', 'Penggunaan Bahan',
@ -318,7 +347,8 @@ class _DashboardScreenState extends State<DashboardScreen> with RouteAware {
style: AppTextStyles.bodySmall style: AppTextStyles.bodySmall
.copyWith( .copyWith(
color: color:
AppColors.textTertiary, AppColors
.textTertiary,
), ),
), ),
], ],
@ -357,7 +387,9 @@ class _DashboardScreenState extends State<DashboardScreen> with RouteAware {
Expanded( Expanded(
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: () { onPressed: () {
Navigator.of(context).pushNamed('/prediction'); Navigator.of(
context,
).pushNamed('/prediction');
}, },
icon: const Icon(Icons.trending_up, size: 20), icon: const Icon(Icons.trending_up, size: 20),
label: const Text( label: const Text(
@ -384,7 +416,9 @@ class _DashboardScreenState extends State<DashboardScreen> with RouteAware {
Expanded( Expanded(
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: () { onPressed: () {
Navigator.of(context).pushNamed('/transaction'); Navigator.of(
context,
).pushNamed('/transaction');
}, },
icon: const Icon(Icons.check_circle, size: 20), icon: const Icon(Icons.check_circle, size: 20),
label: const Text( label: const Text(
@ -416,6 +450,7 @@ class _DashboardScreenState extends State<DashboardScreen> with RouteAware {
], ],
), ),
), ),
),
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: BottomNavigationBar(
currentIndex: _controller.selectedIndex, currentIndex: _controller.selectedIndex,
onTap: (index) { onTap: (index) {

View File

@ -27,6 +27,10 @@ class _PredictionScreenState extends State<PredictionScreen> {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(error))); ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(error)));
} }
Future<void> _refreshRecipes() {
return _loadRecipes();
}
Future<void> _submitProduction() async { Future<void> _submitProduction() async {
if (_controller.isSubmitting) return; if (_controller.isSubmitting) return;
final result = await _controller.submitProduction(); final result = await _controller.submitProduction();
@ -203,6 +207,24 @@ class _PredictionScreenState extends State<PredictionScreen> {
], ],
), ),
), ),
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.3),
borderRadius: BorderRadius.circular(10),
),
child: IconButton(
tooltip: 'Refresh',
icon: const Icon(
Icons.refresh,
color: Colors.white,
size: 20,
),
onPressed: _refreshRecipes,
padding: EdgeInsets.zero,
),
),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@ -267,7 +289,10 @@ class _PredictionScreenState extends State<PredictionScreen> {
), ),
), ),
), ),
body: body: RefreshIndicator(
onRefresh: _refreshRecipes,
color: const Color(0xFFA89080),
child:
_controller.isLoading _controller.isLoading
? Center( ? Center(
child: Column( child: Column(
@ -300,6 +325,7 @@ class _PredictionScreenState extends State<PredictionScreen> {
), ),
) )
: SingleChildScrollView( : SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Column( child: Column(
children: [ children: [
Padding( Padding(
@ -321,7 +347,8 @@ class _PredictionScreenState extends State<PredictionScreen> {
], ],
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment:
CrossAxisAlignment.start,
children: [ children: [
const Text( const Text(
'Rencana Produksi', 'Rencana Produksi',
@ -351,13 +378,13 @@ class _PredictionScreenState extends State<PredictionScreen> {
color: const Color(0xFFE5E7EB), color: const Color(0xFFE5E7EB),
width: 1, width: 1,
), ),
borderRadius: BorderRadius.circular( borderRadius:
10, BorderRadius.circular(10),
),
), ),
child: DropdownButtonHideUnderline( child: DropdownButtonHideUnderline(
child: DropdownButton<String>( child: DropdownButton<String>(
value: _controller.selectedRecipe, value:
_controller.selectedRecipe,
hint: const Padding( hint: const Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: 12, horizontal: 12,
@ -406,7 +433,8 @@ class _PredictionScreenState extends State<PredictionScreen> {
) )
.toList(), .toList(),
onChanged: onChanged:
_controller.setSelectedRecipe, _controller
.setSelectedRecipe,
), ),
), ),
), ),
@ -430,7 +458,8 @@ class _PredictionScreenState extends State<PredictionScreen> {
controller: _productionController, controller: _productionController,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
onChanged: onChanged:
_controller.setProductionQuantity, _controller
.setProductionQuantity,
decoration: InputDecoration( decoration: InputDecoration(
hintText: '0', hintText: '0',
hintStyle: const TextStyle( hintStyle: const TextStyle(
@ -515,7 +544,8 @@ class _PredictionScreenState extends State<PredictionScreen> {
color: Color(0xFFE5E7EB), color: Color(0xFFE5E7EB),
width: 1, width: 1,
), ),
padding: const EdgeInsets.symmetric( padding:
const EdgeInsets.symmetric(
horizontal: 20, horizontal: 20,
), ),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -651,7 +681,9 @@ class _PredictionScreenState extends State<PredictionScreen> {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.05), color: Colors.black.withOpacity(
0.05,
),
blurRadius: 10, blurRadius: 10,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@ -672,7 +704,9 @@ class _PredictionScreenState extends State<PredictionScreen> {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.05), color: Colors.black.withOpacity(
0.05,
),
blurRadius: 10, blurRadius: 10,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@ -686,14 +720,16 @@ class _PredictionScreenState extends State<PredictionScreen> {
final isLast = final isLast =
entry.key == entry.key ==
displayIngredients.length - 1; displayIngredients.length - 1;
final ingredient = entry.value.key; final ingredient =
entry.value.key;
final details = entry.value.value; final details = entry.value.value;
final isSelected = _controller final isSelected = _controller
.isIngredientSelected( .isIngredientSelected(
ingredient, ingredient,
); );
final unit = final unit =
(details['unit'] as String?) ?? (details['unit']
as String?) ??
_controller.getIngredientUnit( _controller.getIngredientUnit(
ingredient, ingredient,
); );
@ -721,7 +757,8 @@ class _PredictionScreenState extends State<PredictionScreen> {
ingredient, ingredient,
); );
final isSufficient = final isSufficient =
stockKg >= requiredInStockUnit; stockKg >=
requiredInStockUnit;
final statusColor = final statusColor =
isSelected isSelected
? (isSufficient ? (isSufficient
@ -742,7 +779,8 @@ class _PredictionScreenState extends State<PredictionScreen> {
return Column( return Column(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.all( padding:
const EdgeInsets.all(
14, 14,
), ),
child: Column( child: Column(
@ -754,7 +792,9 @@ class _PredictionScreenState extends State<PredictionScreen> {
children: [ children: [
Checkbox( Checkbox(
value: isSelected, value: isSelected,
onChanged: (value) { onChanged: (
value,
) {
_controller _controller
.setIngredientSelection( .setIngredientSelection(
ingredient, ingredient,
@ -939,7 +979,9 @@ class _PredictionScreenState extends State<PredictionScreen> {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.05), color: Colors.black.withOpacity(
0.05,
),
blurRadius: 10, blurRadius: 10,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@ -984,7 +1026,9 @@ class _PredictionScreenState extends State<PredictionScreen> {
fontSize: 12, fontSize: 12,
fontWeight: fontWeight:
FontWeight.w700, FontWeight.w700,
color: Color(0xFFDC2626), color: Color(
0xFFDC2626,
),
), ),
), ),
], ],
@ -1014,7 +1058,9 @@ class _PredictionScreenState extends State<PredictionScreen> {
>(Colors.white), >(Colors.white),
), ),
) )
: const Icon(Icons.check_circle), : const Icon(
Icons.check_circle,
),
label: Text( label: Text(
_controller.isSubmitting _controller.isSubmitting
? 'Memproses...' ? 'Memproses...'
@ -1184,6 +1230,7 @@ class _PredictionScreenState extends State<PredictionScreen> {
], ],
), ),
), ),
),
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: BottomNavigationBar(
currentIndex: 3, currentIndex: 3,
type: BottomNavigationBarType.fixed, type: BottomNavigationBarType.fixed,

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,8 +125,19 @@ 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(
onRefresh: _loadAccount,
color: AppColors.primaryBrown,
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
@ -167,6 +178,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
), ),
), ),
), ),
),
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: BottomNavigationBar(
currentIndex: _controller.selectedIndex, currentIndex: _controller.selectedIndex,
onTap: (index) { onTap: (index) {

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,8 +518,19 @@ 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(
onRefresh: _refreshProducts,
color: AppColors.primaryBrown,
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
@ -585,7 +610,9 @@ class _TransactionScreenState extends State<TransactionScreen> {
label: const Text('Tambah ke Keranjang'), label: const Text('Tambah ke Keranjang'),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBrown, backgroundColor: AppColors.primaryBrown,
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(
vertical: 12,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
@ -600,8 +627,12 @@ class _TransactionScreenState extends State<TransactionScreen> {
icon: const Icon(Icons.add_circle_outline), icon: const Icon(Icons.add_circle_outline),
label: const Text('Tambah Produk Baru'), label: const Text('Tambah Produk Baru'),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(
side: BorderSide(color: AppColors.secondaryBlue), vertical: 12,
),
side: BorderSide(
color: AppColors.secondaryBlue,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
@ -657,7 +688,9 @@ class _TransactionScreenState extends State<TransactionScreen> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.bgLight, color: AppColors.bgLight,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
border: Border.all(color: AppColors.grey300), border: Border.all(
color: AppColors.grey300,
),
), ),
child: Column( child: Column(
children: [ children: [
@ -672,16 +705,19 @@ class _TransactionScreenState extends State<TransactionScreen> {
children: [ children: [
Text( Text(
item.productName, item.productName,
style: AppTextStyles.labelLarge style: AppTextStyles
.labelLarge
.copyWith( .copyWith(
color: color:
AppColors.textPrimary, AppColors
.textPrimary,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'Rp ${item.unitPrice} / unit', 'Rp ${item.unitPrice} / unit',
style: AppTextStyles.labelSmall style: AppTextStyles
.labelSmall
.copyWith( .copyWith(
color: color:
AppColors AppColors
@ -695,7 +731,8 @@ class _TransactionScreenState extends State<TransactionScreen> {
'Rp ${item.totalPrice}', 'Rp ${item.totalPrice}',
style: AppTextStyles.labelLarge style: AppTextStyles.labelLarge
.copyWith( .copyWith(
color: AppColors.statusSuccess, color:
AppColors.statusSuccess,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), ),
), ),
@ -719,9 +756,12 @@ class _TransactionScreenState extends State<TransactionScreen> {
width: 32, width: 32,
height: 32, height: 32,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primaryBrown, color:
AppColors.primaryBrown,
borderRadius: borderRadius:
BorderRadius.circular(6), BorderRadius.circular(
6,
),
), ),
child: const Icon( child: const Icon(
Icons.remove, Icons.remove,
@ -737,7 +777,8 @@ class _TransactionScreenState extends State<TransactionScreen> {
.copyWith( .copyWith(
color: color:
AppColors.textPrimary, AppColors.textPrimary,
fontWeight: FontWeight.w600, fontWeight:
FontWeight.w600,
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
@ -752,9 +793,12 @@ class _TransactionScreenState extends State<TransactionScreen> {
width: 32, width: 32,
height: 32, height: 32,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primaryBrown, color:
AppColors.primaryBrown,
borderRadius: borderRadius:
BorderRadius.circular(6), BorderRadius.circular(
6,
),
), ),
child: const Icon( child: const Icon(
Icons.add, Icons.add,
@ -767,9 +811,8 @@ class _TransactionScreenState extends State<TransactionScreen> {
), ),
GestureDetector( GestureDetector(
onTap: onTap:
() => _controller.removeFromCart( () => _controller
index, .removeFromCart(index),
),
child: Container( child: Container(
width: 32, width: 32,
height: 32, height: 32,
@ -827,7 +870,8 @@ class _TransactionScreenState extends State<TransactionScreen> {
final picked = await showDatePicker( final picked = await showDatePicker(
context: context, context: context,
initialDate: initialDate:
_controller.selectedDate ?? DateTime.now(), _controller.selectedDate ??
DateTime.now(),
firstDate: DateTime(2020), firstDate: DateTime(2020),
lastDate: DateTime.now(), lastDate: DateTime.now(),
); );
@ -1001,6 +1045,7 @@ class _TransactionScreenState extends State<TransactionScreen> {
), ),
), ),
), ),
),
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: BottomNavigationBar(
currentIndex: _controller.selectedIndex, currentIndex: _controller.selectedIndex,
onTap: (index) { onTap: (index) {