import 'package:finalproject/theme/colors.dart'; import 'package:finalproject/theme/text_styles.dart'; import 'package:finalproject/utils/route_observer.dart'; import 'package:flutter/material.dart'; import 'dashboard_controller.dart'; class DashboardScreen extends StatefulWidget { const DashboardScreen({super.key}); @override State createState() => _DashboardScreenState(); } class _DashboardScreenState extends State with RouteAware { late final DashboardController _controller; @override void initState() { super.initState(); _controller = DashboardController(); _controller.loadDashboard(); } @override void didChangeDependencies() { super.didChangeDependencies(); final route = ModalRoute.of(context); if (route != null) { routeObserver.subscribe(this, route); } } @override void didPopNext() { _controller.loadDashboard(showLoading: false); } Future _refreshDashboard() { return _controller.loadDashboard(showLoading: false); } @override Widget build(BuildContext context) { final topPadding = MediaQuery.of(context).padding.top; final headerHeight = 240 + topPadding; return AnimatedBuilder( animation: _controller, builder: (context, _) { return Scaffold( backgroundColor: AppColors.bgLight, appBar: PreferredSize( preferredSize: Size.fromHeight(headerHeight), child: Container( decoration: BoxDecoration( color: AppColors.primaryBrown, borderRadius: const BorderRadius.only( bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24), ), ), child: SafeArea( child: Column( children: [ Padding( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 6, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.end, 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( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: Colors.white.withValues( alpha: 0.3, ), borderRadius: BorderRadius.circular(10), ), child: IconButton( icon: const Icon( Icons.notifications_none, color: Colors.white, size: 20, ), onPressed: () {}, padding: EdgeInsets.zero, ), ), if (_controller.lowStockItems.isNotEmpty) Positioned( right: 4, top: 4, child: Container( width: 22, height: 22, decoration: BoxDecoration( color: AppColors.statusError, borderRadius: BorderRadius.circular( 11, ), border: Border.all( color: AppColors.primaryBrown, width: 2, ), ), child: Center( child: Text( _controller.lowStockItems.length .toString(), style: const TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold, ), ), ), ), ), ], ), ], ), const SizedBox(height: 2), const Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Selamat Datang', style: TextStyle( color: Colors.white70, fontSize: 13, fontWeight: FontWeight.w400, ), ), SizedBox(height: 2), Text( 'Admin Sulastri', style: TextStyle( color: Colors.white, fontSize: 22, fontWeight: FontWeight.w700, letterSpacing: 0.5, ), ), ], ), ], ), ), Padding( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), child: Row( children: [ Expanded( child: _buildStatCard( title: 'Bahan Digunakan Hari Ini', value: '${_formatNumber(_controller.totalBahanDigunakanHariIni)} ${_controller.bahanDigunakanSatuan}', change: _controller.bahanDigunakanKeterangan, icon: Icons.inventory_2_outlined, iconBgColor: AppColors.statusSuccess, isLoading: _controller.isBahanDigunakanLoading, hasError: _controller.bahanDigunakanError != null, ), ), const SizedBox(width: 12), Expanded( child: _buildStatCard( title: 'Produk', value: _controller.totalProduk.toString(), change: 'Aktif', icon: Icons.shopping_bag, iconBgColor: AppColors.secondaryBlue, ), ), ], ), ), ], ), ), ), ), body: RefreshIndicator( onRefresh: _refreshDashboard, color: AppColors.primaryBrown, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: Column( children: [ Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 8), 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( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Penggunaan Bahan', style: AppTextStyles.headlineSmall .copyWith( color: AppColors.textPrimary, ), ), 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: [ 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), ], ), ), ], ), ), ), bottomNavigationBar: BottomNavigationBar( currentIndex: _controller.selectedIndex, onTap: (index) { _controller.setSelectedIndex(index); switch (index) { case 0: break; case 1: Navigator.of(context).pushNamed('/products'); break; case 2: Navigator.of(context).pushNamed('/transaction'); break; case 3: Navigator.of(context).pushNamed('/prediction'); break; case 4: Navigator.of(context).pushNamed('/reports'); break; case 5: Navigator.of(context).pushNamed('/settings'); break; } }, type: BottomNavigationBarType.fixed, backgroundColor: Colors.white, 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: 'Stok Masuk', ), BottomNavigationBarItem( icon: Icon(Icons.trending_up_outlined), label: 'Prediksi', ), BottomNavigationBarItem( icon: Icon(Icons.description_outlined), label: 'Laporan', ), BottomNavigationBarItem( icon: Icon(Icons.settings_outlined), label: 'Pengaturan', ), ], ), ); }, ); } Widget _buildStatCard({ required String title, required String value, required String change, required IconData icon, required Color iconBgColor, bool isLoading = false, bool hasError = false, }) { return Container( constraints: const BoxConstraints(minHeight: 120), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.bgWhite, borderRadius: BorderRadius.circular(14), boxShadow: [AppColors.shadowLight], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, maxLines: 2, overflow: TextOverflow.ellipsis, style: AppTextStyles.bodySmall.copyWith( color: AppColors.textTertiary, fontWeight: FontWeight.w500, fontSize: 11, height: 1.2, ), ), const SizedBox(height: 4), if (isLoading) const SizedBox( width: 22, height: 22, child: CircularProgressIndicator(strokeWidth: 2.4), ) else FittedBox( fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text( value, maxLines: 1, style: AppTextStyles.titleLarge.copyWith( color: hasError ? AppColors.textSecondary : AppColors.textPrimary, fontWeight: FontWeight.w700, fontSize: 18, height: 1.1, ), ), ), const SizedBox(height: 4), Text( change, maxLines: 1, overflow: TextOverflow.ellipsis, style: AppTextStyles.bodySmall.copyWith( color: hasError ? AppColors.statusError : AppColors.textTertiary, fontWeight: FontWeight.w600, fontSize: 11, height: 1.2, ), ), ], ), ), Container( width: 40, height: 40, decoration: BoxDecoration( color: iconBgColor.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(10), ), child: Center(child: Icon(icon, color: iconBgColor, size: 22)), ), ], ), ], ), ); } String _formatNumber(num value) { if (value % 1 == 0) { return value.toInt().toString(); } return value.toStringAsFixed(1); } Widget _buildUsageRow(Map item) { final total = (item['total'] as num?)?.toDouble() ?? 0.0; final unit = item['unit']?.toString() ?? 'kg'; final max = _controller.penggunaanBahan.fold( 0, (value, element) => (((element['total'] as num?)?.toDouble() ?? 0.0) > value) ? (element['total'] as num?)!.toDouble() : value, ); final progress = max == 0 ? 0.0 : total / max; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( item['name']?.toString() ?? '-', style: AppTextStyles.labelLarge.copyWith( color: AppColors.textPrimary, fontWeight: FontWeight.w600, ), ), ), Text( '${total.toStringAsFixed(0)} $unit', style: AppTextStyles.labelSmall.copyWith( color: AppColors.textSecondary, ), ), ], ), const SizedBox(height: 6), ClipRRect( borderRadius: BorderRadius.circular(6), child: LinearProgressIndicator( value: progress, minHeight: 8, backgroundColor: AppColors.grey200, valueColor: AlwaysStoppedAnimation(AppColors.secondaryBlue), ), ), ], ); } @override void dispose() { routeObserver.unsubscribe(this); _controller.dispose(); super.dispose(); } }