MIF_E31232435/lib/screens/prediction_screen.dart

879 lines
38 KiB
Dart

import 'package:flutter/material.dart';
import 'package:finalproject/theme/colors.dart';
import 'package:finalproject/theme/text_styles.dart';
class PredictionScreen extends StatefulWidget {
const PredictionScreen({Key? key}) : super(key: key);
@override
State<PredictionScreen> createState() => _PredictionScreenState();
}
class _PredictionScreenState extends State<PredictionScreen> {
String? _selectedRecipe;
int _productionQuantity = 0;
bool _isCalculated = false;
// Produk yang bisa dibuat
final List<String> recipes = [
'Donat',
'Roti Putih',
'Kue Brownies',
'Kue Tart',
];
// Resep untuk setiap produk (ingredient: gram/butir per unit)
final Map<String, Map<String, int>> recipeDetails = {
'Donat': {
'Tepung Terigu 1kg': 500,
'Telur 1kg': 2,
'Gula Pasir 1kg': 100,
'Mentega 500gr': 50,
'Baking Powder': 5,
},
'Roti Putih': {
'Tepung Terigu 1kg': 800,
'Telur 1kg': 3,
'Gula Pasir 1kg': 80,
'Mentega 500gr': 80,
'Susu Bubuk': 50,
'Baking Powder': 8,
},
'Kue Brownies': {
'Tepung Terigu 1kg': 300,
'Cokelat Bubuk 250gr': 100,
'Telur 1kg': 4,
'Gula Pasir 1kg': 200,
'Mentega 500gr': 150,
'Baking Powder': 5,
},
'Kue Tart': {
'Tepung Terigu 1kg': 400,
'Telur 1kg': 5,
'Gula Pasir 1kg': 150,
'Mentega 500gr': 200,
'Keju Parut 250gr': 100,
'Susu Bubuk': 80,
},
};
// Stok saat ini (sama dengan di product list)
final Map<String, int> currentStock = {
'Tepung Terigu 1kg': 45000, // gram
'Telur 1kg': 12, // butir
'Gula Pasir 1kg': 28000, // gram
'Susu Bubuk': 8000, // gram
'Cokelat Bubuk 250gr': 22000, // gram
'Mentega 500gr': 15000, // gram
'Keju Parut 250gr': 3000, // gram
'Baking Powder': 60000, // gram
};
// Satuan untuk setiap ingredient
final Map<String, String> ingredientUnits = {
'Tepung Terigu 1kg': 'gr',
'Telur 1kg': 'butir',
'Gula Pasir 1kg': 'gr',
'Susu Bubuk': 'gr',
'Cokelat Bubuk 250gr': 'gr',
'Mentega 500gr': 'gr',
'Keju Parut 250gr': 'gr',
'Baking Powder': 'gr',
};
Map<String, int> get requiredIngredients {
if (_selectedRecipe == null || _productionQuantity == 0) {
return {};
}
final recipe = recipeDetails[_selectedRecipe]!;
return recipe.map((ingredient, amount) =>
MapEntry(ingredient, amount * _productionQuantity));
}
Map<String, int> get insufficientStock {
final required = requiredIngredients;
final insufficient = <String, int>{};
required.forEach((ingredient, neededAmount) {
final available = currentStock[ingredient] ?? 0;
if (available < neededAmount) {
insufficient[ingredient] = neededAmount - available;
}
});
return insufficient;
}
bool get isStockSufficient => insufficientStock.isEmpty;
Color _getStatusColor(String ingredient) {
final required = requiredIngredients[ingredient] ?? 0;
final available = currentStock[ingredient] ?? 0;
return available >= required ? Color(0xFF10B981) : Color(0xFFDC2626);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFF5F5F5),
// Brown Header
appBar: PreferredSize(
preferredSize: const Size.fromHeight(200),
child: Container(
decoration: BoxDecoration(
color: Color(0xFFA89080),
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24),
),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Back button + Title
Row(
children: [
GestureDetector(
onTap: () => Navigator.pop(context),
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.3),
borderRadius: BorderRadius.circular(10),
),
child: const Icon(Icons.arrow_back,
color: Colors.white, size: 20),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'Prediksi Kebutuhan Bahan',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
SizedBox(height: 2),
Text(
'Kalkulasi bahan berdasarkan rencana produksi',
style: TextStyle(
color: Colors.white70,
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
],
),
),
],
),
const SizedBox(height: 12),
// Info Box
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.15),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: Colors.white.withOpacity(0.2),
width: 1,
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(6),
),
child: const Icon(Icons.calculate,
color: Colors.white, size: 18),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'Sistem Kalkulasi Otomatis',
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 4),
Text(
'Pilih produk dan jumlah untuk lihat kebutuhan bahan',
style: TextStyle(
color: Colors.white70,
fontSize: 11,
fontWeight: FontWeight.w400,
height: 1.3,
),
),
],
),
),
],
),
),
],
),
),
),
),
),
body: SingleChildScrollView(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Input Form Section
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Rencana Produksi',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF1F2937),
),
),
const SizedBox(height: 16),
// Product Selection
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Pilih Produk',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Color(0xFF1F2937),
),
),
const SizedBox(height: 8),
Container(
decoration: BoxDecoration(
border: Border.all(
color: Color(0xFFE5E7EB),
width: 1,
),
borderRadius: BorderRadius.circular(10),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: _selectedRecipe,
hint: const Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
child: Text(
'-- Pilih Produk --',
style: TextStyle(
color: Color(0xFF9CA3AF),
fontSize: 14,
),
),
),
isExpanded: true,
icon: const Padding(
padding: EdgeInsets.only(right: 12),
child: Icon(Icons.expand_more,
color: Color(0xFF9CA3AF)),
),
items: recipes
.map((recipe) =>
DropdownMenuItem(
value: recipe,
child: Padding(
padding:
const EdgeInsets.symmetric(
horizontal: 12),
child: Text(recipe),
),
))
.toList(),
onChanged: (value) {
setState(() {
_selectedRecipe = value;
_isCalculated = false;
});
},
),
),
),
],
),
const SizedBox(height: 16),
// Quantity Input
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Jumlah Produksi',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Color(0xFF1F2937),
),
),
const SizedBox(height: 8),
TextField(
keyboardType: TextInputType.number,
onChanged: (value) {
setState(() {
_productionQuantity =
int.tryParse(value) ?? 0;
_isCalculated = false;
});
},
decoration: InputDecoration(
hintText: '0',
hintStyle:
const TextStyle(color: Color(0xFF9CA3AF)),
suffixText: 'pcs',
suffixStyle: const TextStyle(
color: Color(0xFF9CA3AF),
fontSize: 12,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(
color: Color(0xFFE5E7EB),
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(
color: Color(0xFFE5E7EB),
),
),
contentPadding:
const EdgeInsets.symmetric(
horizontal: 12, vertical: 12),
),
),
],
),
const SizedBox(height: 20),
// Calculate Button
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: (_selectedRecipe != null &&
_productionQuantity > 0)
? () {
setState(() => _isCalculated = true);
}
: null,
icon: const Icon(Icons.calculate, size: 18),
label: const Text(
'Hitung Kebutuhan',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFFA89080),
foregroundColor: Colors.white,
padding:
const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
),
),
const SizedBox(width: 10),
OutlinedButton(
onPressed: () {
setState(() {
_selectedRecipe = null;
_productionQuantity = 0;
_isCalculated = false;
});
},
style: OutlinedButton.styleFrom(
side: const BorderSide(
color: Color(0xFFE5E7EB),
width: 1,
),
padding:
const EdgeInsets.symmetric(horizontal: 20),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text(
'Reset',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Color(0xFF1F2937),
),
),
),
],
),
],
),
),
const SizedBox(height: 24),
// Results Section
if (_isCalculated) ...[
// Status Card
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isStockSufficient
? Color(0xFF10B981).withOpacity(0.1)
: Color(0xFFDC2626).withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: isStockSufficient
? Color(0xFF10B981).withOpacity(0.3)
: Color(0xFFDC2626).withOpacity(0.3),
width: 1,
),
),
child: Row(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: isStockSufficient
? Color(0xFF10B981).withOpacity(0.2)
: Color(0xFFDC2626).withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
isStockSufficient
? Icons.check_circle
: Icons.warning_amber,
color: isStockSufficient
? Color(0xFF10B981)
: Color(0xFFDC2626),
size: 24,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
isStockSufficient
? 'Stok Cukup'
: 'Stok Belum Cukup',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: isStockSufficient
? Color(0xFF10B981)
: Color(0xFFDC2626),
),
),
const SizedBox(height: 2),
Text(
isStockSufficient
? 'Semua bahan tersedia untuk produksi'
: 'Ada bahan yang perlu ditambah',
style: const TextStyle(
fontSize: 12,
color: Color(0xFF6B7280),
),
),
],
),
),
],
),
),
const SizedBox(height: 16),
// Ingredients Table
Text(
'Kebutuhan Bahan',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: Color(0xFF1F2937),
),
),
const SizedBox(height: 12),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: requiredIngredients.entries
.toList()
.asMap()
.entries
.map((entry) {
final isLast =
entry.key == requiredIngredients.length - 1;
final ingredient = entry.value.key;
final neededAmount = entry.value.value;
final availableAmount =
currentStock[ingredient] ?? 0;
final unit = ingredientUnits[ingredient] ?? 'gr';
final isSufficient =
availableAmount >= neededAmount;
return Column(
children: [
Padding(
padding: const EdgeInsets.all(14),
child: Column(
children: [
Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
ingredient
.replaceAll(
RegExp(r' \d+(kg|gr)'),
'')
.trim(),
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Color(0xFF1F2937),
),
),
const SizedBox(height: 6),
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment
.start,
children: [
Text(
'Kebutuhan',
style: TextStyle(
fontSize: 11,
color: Color(
0xFF9CA3AF),
),
),
Text(
'$neededAmount $unit',
style:
const TextStyle(
fontSize: 12,
fontWeight:
FontWeight.w700,
color: Color(
0xFF1F2937),
),
),
],
),
),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment
.start,
children: [
Text(
'Stok Saat Ini',
style: TextStyle(
fontSize: 11,
color: Color(
0xFF9CA3AF),
),
),
Text(
'$availableAmount $unit',
style:
const TextStyle(
fontSize: 12,
fontWeight:
FontWeight.w700,
color: Color(
0xFF1F2937),
),
),
],
),
),
],
),
],
),
),
const SizedBox(width: 10),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
decoration: BoxDecoration(
color: _getStatusColor(ingredient)
.withOpacity(0.15),
borderRadius:
BorderRadius.circular(8),
border: Border.all(
color: _getStatusColor(ingredient)
.withOpacity(0.3),
width: 1,
),
),
child: Text(
isSufficient ? 'Aman' : 'Kurang',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
color: _getStatusColor(
ingredient),
),
),
),
],
),
],
),
),
if (!isLast)
Container(
height: 1,
color: Color(0xFFF3F4F6),
),
],
);
}).toList(),
),
),
const SizedBox(height: 16),
// Recommendation Card
if (!isStockSufficient) ...[
Text(
'Rekomendasi Penambahan Stok',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: Color(0xFF1F2937),
),
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Color(0xFFDC2626).withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Color(0xFFDC2626).withOpacity(0.2),
width: 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: insufficientStock.entries
.map((entry) {
final ingredient = entry.key;
final deficitAmount = entry.value;
final unit =
ingredientUnits[ingredient] ?? 'gr';
return Padding(
padding:
const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
ingredient
.replaceAll(
RegExp(r' \d+(kg|gr)'), '')
.trim(),
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Color(0xFF1F2937),
),
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: Color(0xFFDC2626)
.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Text(
'+$deficitAmount $unit',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w700,
color: Color(0xFFDC2626),
),
),
),
],
),
);
}).toList(),
),
),
],
] else
Container(
width: double.infinity,
padding: const EdgeInsets.all(32),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Color(0xFF9CA3AF).withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(Icons.calculate,
color: Color(0xFF9CA3AF), size: 32),
),
const SizedBox(height: 16),
const Text(
'Belum Ada Kalkulasi',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF1F2937),
),
),
const SizedBox(height: 8),
const Text(
'Pilih produk dan masukkan jumlah produksi untuk melihat kebutuhan bahan',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13,
color: Color(0xFF9CA3AF),
height: 1.5,
),
),
],
),
),
],
),
),
],
),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: 3,
type: BottomNavigationBarType.fixed,
onTap: (index) {
switch (index) {
case 0:
Navigator.popUntil(context, (route) => route.isFirst);
break;
case 1:
Navigator.pushNamed(context, '/products');
break;
case 2:
Navigator.pushNamed(context, '/transaction');
break;
case 4:
Navigator.pushNamed(context, '/reports');
break;
}
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home_outlined),
label: 'Dashboard',
),
BottomNavigationBarItem(
icon: Icon(Icons.shopping_bag_outlined),
label: 'Produk',
),
BottomNavigationBarItem(
icon: Icon(Icons.shopping_cart_outlined),
label: 'Transaksi',
),
BottomNavigationBarItem(
icon: Icon(Icons.trending_up_outlined),
label: 'Prediksi',
),
BottomNavigationBarItem(
icon: Icon(Icons.description_outlined),
label: 'Laporan',
),
],
),
);
}
}