diff --git a/lib/view/finance/finance_screen.dart b/lib/view/finance/finance_screen.dart index 1bfde08..49d3e8a 100644 --- a/lib/view/finance/finance_screen.dart +++ b/lib/view/finance/finance_screen.dart @@ -3,8 +3,6 @@ import 'package:flutter_lucide/flutter_lucide.dart'; import 'package:get/get.dart'; import 'package:marco/controller/project_controller.dart'; import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; -import 'package:marco/helpers/widgets/my_card.dart'; -import 'package:marco/helpers/widgets/my_container.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_text.dart'; @@ -15,49 +13,254 @@ class FinanceScreen extends StatefulWidget { State createState() => _FinanceScreenState(); } -class _FinanceScreenState extends State with UIMixin { +class _FinanceScreenState extends State + with UIMixin, TickerProviderStateMixin { final projectController = Get.find(); + late AnimationController _animationController; + late Animation _fadeAnimation; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: const Duration(milliseconds: 800), + vsync: this, + ); + _fadeAnimation = CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + ); + _animationController.forward(); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Colors.white, - appBar: AppBar( - title: const Text("Finance"), - centerTitle: true, - backgroundColor: Colors.white, - elevation: 0.5, - foregroundColor: Colors.black, + backgroundColor: const Color(0xFFF8F9FA), + appBar: PreferredSize( + preferredSize: const Size.fromHeight(72), + child: AppBar( + backgroundColor: const Color(0xFFF5F5F5), + elevation: 0.5, + automaticallyImplyLeading: false, + titleSpacing: 0, + title: Padding( + padding: MySpacing.xy(16, 0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IconButton( + icon: const Icon(Icons.arrow_back_ios_new, + color: Colors.black, size: 20), + onPressed: () => Get.offNamed('/dashboard'), + ), + MySpacing.width(8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + MyText.titleLarge( + 'Finance', + fontWeight: 700, + color: Colors.black, + ), + MySpacing.height(2), + GetBuilder( + builder: (projectController) { + final projectName = + projectController.selectedProject?.name ?? + 'Select Project'; + return Row( + children: [ + const Icon(Icons.work_outline, + size: 14, color: Colors.grey), + MySpacing.width(4), + Expanded( + child: MyText.bodySmall( + projectName, + fontWeight: 600, + overflow: TextOverflow.ellipsis, + color: Colors.grey[700], + ), + ), + ], + ); + }, + ), + ], + ), + ), + ], + ), + ), + ), ), - body: SingleChildScrollView( - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildFinanceStatCards(context), - ], + body: FadeTransition( + opacity: _fadeAnimation, + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildWelcomeSection(), + MySpacing.height(24), + _buildFinanceModules(), + MySpacing.height(24), + _buildQuickStatsSection(), + ], + ), ), ), ); } - Widget _buildFinanceStatCards(BuildContext context) { + Widget _buildWelcomeSection() { + final projectSelected = projectController.selectedProject != null; + + return Container( + width: double.infinity, + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + contentTheme.primary.withValues(alpha: 0.1), + contentTheme.info.withValues(alpha: 0.05), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: contentTheme.primary.withValues(alpha: 0.2), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: contentTheme.primary.withValues(alpha: 0.1), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Icon( + LucideIcons.landmark, + color: contentTheme.primary, + size: 24, + ), + ), + MySpacing.width(12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MyText.titleMedium( + 'Financial Management', + fontWeight: 700, + color: Colors.black87, + ), + MySpacing.height(2), + MyText.bodySmall( + projectSelected + ? 'Manage your project finances' + : 'Select a project to get started', + color: Colors.grey[600], + ), + ], + ), + ), + ], + ), + if (!projectSelected) ...[ + MySpacing.height(12), + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: Colors.orange.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.orange.withValues(alpha: 0.3)), + ), + child: Row( + children: [ + Icon( + LucideIcons.badge_alert, + size: 16, + color: Colors.orange[700], + ), + MySpacing.width(8), + Expanded( + child: MyText.bodySmall( + 'Please select a project to access finance modules', + color: Colors.orange[700], + fontWeight: 500, + ), + ), + ], + ), + ), + ], + ], + ), + ); + } + + Widget _buildFinanceModules() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MyText.titleMedium( + 'Finance Modules', + fontWeight: 700, + color: Colors.black87, + ), + MySpacing.height(4), + MyText.bodySmall( + 'Select a module to manage', + color: Colors.grey[600], + ), + MySpacing.height(16), + _buildModuleGrid(), + ], + ); + } + + Widget _buildModuleGrid() { final stats = [ _FinanceStatItem( LucideIcons.badge_dollar_sign, "Expense", + "Track and manage expenses", contentTheme.info, "/dashboard/expense-main-page", ), _FinanceStatItem( LucideIcons.receipt_text, "Payment Request", + "Submit payment requests", contentTheme.primary, "/dashboard/payment-request", ), _FinanceStatItem( LucideIcons.wallet, "Advance Payment", + "Manage advance payments", contentTheme.warning, "/dashboard/advance-payment", ), @@ -65,77 +268,287 @@ class _FinanceScreenState extends State with UIMixin { final projectSelected = projectController.selectedProject != null; - return LayoutBuilder( - builder: (context, constraints) { - int crossAxisCount = (constraints.maxWidth ~/ 100).clamp(2, 6); - double cardWidth = - (constraints.maxWidth - (crossAxisCount - 1) * 6) / crossAxisCount; - - return Wrap( - spacing: 6, - runSpacing: 6, - alignment: WrapAlignment.start, - children: stats - .map((stat) => _buildFinanceCard(stat, projectSelected, cardWidth)) - .toList(), + return GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + childAspectRatio: 1.1, + ), + itemCount: stats.length, + itemBuilder: (context, index) { + return _buildModernFinanceCard( + stats[index], + projectSelected, + index, ); }, ); } - Widget _buildFinanceCard( - _FinanceStatItem statItem, bool isProjectSelected, double width) { - const double cardHeight = 80; + Widget _buildModernFinanceCard( + _FinanceStatItem statItem, + bool isProjectSelected, + int index, + ) { final bool isEnabled = isProjectSelected; - return Opacity( - opacity: isEnabled ? 1.0 : 0.4, - child: IgnorePointer( - ignoring: !isEnabled, - child: InkWell( - borderRadius: BorderRadius.circular(8), - onTap: () => _onCardTap(statItem, isEnabled), - child: MyCard.bordered( - width: width, - height: cardHeight, - paddingAll: 12, - borderRadiusAll: 8, - border: Border.all(color: Colors.grey.withOpacity(0.15)), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MyContainer.rounded( - paddingAll: 6, - color: statItem.color.withOpacity(0.1), - child: Icon( - statItem.icon, - size: 18, - color: statItem.color, + return TweenAnimationBuilder( + duration: Duration(milliseconds: 400 + (index * 100)), + tween: Tween(begin: 0.0, end: 1.0), + curve: Curves.easeOutCubic, + builder: (context, value, child) { + return Transform.scale( + scale: value, + child: Opacity( + opacity: isEnabled ? 1.0 : 0.5, + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => _onCardTap(statItem, isEnabled), + borderRadius: BorderRadius.circular(16), + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: isEnabled + ? statItem.color.withValues(alpha: 0.2) + : Colors.grey.withValues(alpha: 0.2), + width: 1.5, + ), + ), + child: Stack( + children: [ + // Content + Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.all(12), + child: Icon( + statItem.icon, + size: 28, + color: statItem.color, + ), + ), + MySpacing.height(12), + MyText.titleSmall( + statItem.title, + fontWeight: 700, + color: Colors.black87, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + MySpacing.height(4), + if (isEnabled) + Row( + children: [ + MyText.bodySmall( + 'View Details', + color: statItem.color, + fontWeight: 600, + fontSize: 11, + ), + MySpacing.width(4), + Icon( + LucideIcons.arrow_right, + size: 14, + color: statItem.color, + ), + ], + ), + ], + ), + ), + // Lock icon for disabled state + if (!isEnabled) + Positioned( + top: 12, + right: 12, + child: Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(6), + ), + child: Icon( + LucideIcons.lock, + size: 14, + color: Colors.grey[600], + ), + ), + ), + ], ), ), - MySpacing.height(8), - MyText.bodySmall( - statItem.title, - fontWeight: 600, - color: Colors.black87, - textAlign: TextAlign.center, - ), - ], + ), ), ), + ); + }, + ); + } + + Widget _buildQuickStatsSection() { + final projectSelected = projectController.selectedProject != null; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MyText.titleMedium( + 'Quick Stats', + fontWeight: 700, + color: Colors.black87, ), + MySpacing.height(4), + MyText.bodySmall( + 'Overview of your finances', + color: Colors.grey[600], + ), + MySpacing.height(16), + _buildStatsRow(projectSelected), + ], + ); + } + + Widget _buildStatsRow(bool projectSelected) { + final stats = [ + _QuickStat( + icon: LucideIcons.trending_up, + label: 'Total Expenses', + value: projectSelected ? '₹0' : '--', + color: contentTheme.danger, + ), + _QuickStat( + icon: LucideIcons.clock, + label: 'Pending', + value: projectSelected ? '0' : '--', + color: contentTheme.warning, + ), + ]; + + return Row( + children: stats + .map((stat) => Expanded( + child: Padding( + padding: const EdgeInsets.only(right: 8), + child: _buildStatCard(stat, projectSelected), + ), + )) + .toList(), + ); + } + + Widget _buildStatCard(_QuickStat stat, bool isEnabled) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: stat.color.withValues(alpha: 0.2), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: stat.color.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + stat.icon, + size: 20, + color: stat.color, + ), + ), + MySpacing.height(12), + MyText.bodySmall( + stat.label, + color: Colors.grey[600], + fontSize: 11, + ), + MySpacing.height(4), + MyText.titleLarge( + stat.value, + fontWeight: 700, + color: isEnabled ? Colors.black87 : Colors.grey[400], + ), + ], ), ); } void _onCardTap(_FinanceStatItem statItem, bool isEnabled) { if (!isEnabled) { - Get.defaultDialog( - title: "No Project Selected", - middleText: "Please select a project before accessing this section.", - confirm: ElevatedButton( - onPressed: () => Get.back(), - child: const Text("OK"), + Get.dialog( + Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.orange.withValues(alpha: 0.1), + shape: BoxShape.circle, + ), + child: Icon( + LucideIcons.badge_alert, + color: Colors.orange[700], + size: 32, + ), + ), + MySpacing.height(16), + MyText.titleMedium( + "No Project Selected", + fontWeight: 700, + color: Colors.black87, + textAlign: TextAlign.center, + ), + MySpacing.height(8), + MyText.bodyMedium( + "Please select a project before accessing this section.", + color: Colors.grey[600], + textAlign: TextAlign.center, + ), + MySpacing.height(24), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () => Get.back(), + style: ElevatedButton.styleFrom( + backgroundColor: contentTheme.primary, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + elevation: 0, + ), + child: MyText.bodyMedium( + "OK", + color: Colors.white, + fontWeight: 600, + ), + ), + ), + ], + ), + ), ), ); return; @@ -148,13 +561,29 @@ class _FinanceScreenState extends State with UIMixin { class _FinanceStatItem { final IconData icon; final String title; + final String subtitle; final Color color; final String route; _FinanceStatItem( this.icon, this.title, + this.subtitle, this.color, this.route, ); } + +class _QuickStat { + final IconData icon; + final String label; + final String value; + final Color color; + + _QuickStat({ + required this.icon, + required this.label, + required this.value, + required this.color, + }); +}