marco.pms.mobileapp/lib/view/finance/finance_screen.dart
2025-11-08 15:46:28 +05:30

590 lines
18 KiB
Dart

import 'package:flutter/material.dart';
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_spacing.dart';
import 'package:marco/helpers/widgets/my_text.dart';
class FinanceScreen extends StatefulWidget {
const FinanceScreen({super.key});
@override
State<FinanceScreen> createState() => _FinanceScreenState();
}
class _FinanceScreenState extends State<FinanceScreen>
with UIMixin, TickerProviderStateMixin {
final projectController = Get.find<ProjectController>();
late AnimationController _animationController;
late Animation<double> _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: 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<ProjectController>(
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: 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 _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/finance/advance-payment",
),
];
final projectSelected = projectController.selectedProject != null;
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 _buildModernFinanceCard(
_FinanceStatItem statItem,
bool isProjectSelected,
int index,
) {
final bool isEnabled = isProjectSelected;
return TweenAnimationBuilder<double>(
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],
),
),
),
],
),
),
),
),
),
);
},
);
}
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.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;
}
Get.toNamed(statItem.route);
}
}
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,
});
}