marco.pms.mobileapp/lib/view/finance/finance_screen.dart
Vaibhav Surve 5c53a3f4be Refactor project structure and rename from 'marco' to 'on field work'
- Updated import paths across multiple files to reflect the new package name.
- Changed application name and identifiers in CMakeLists.txt, Runner.rc, and other configuration files.
- Modified web index.html and manifest.json to update the app title and name.
- Adjusted macOS and Windows project settings to align with the new application name.
- Ensured consistency in naming across all relevant files and directories.
2025-11-22 14:20:37 +05:30

296 lines
9.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:flutter_lucide/flutter_lucide.dart';
import 'package:get/get.dart';
import 'package:on_field_work/controller/project_controller.dart';
import 'package:on_field_work/controller/dynamicMenu/dynamic_menu_controller.dart';
import 'package:on_field_work/helpers/utils/mixins/ui_mixin.dart';
import 'package:on_field_work/helpers/widgets/my_card.dart';
import 'package:on_field_work/helpers/widgets/my_spacing.dart';
import 'package:on_field_work/helpers/widgets/my_text.dart';
import 'package:on_field_work/helpers/widgets/dashbaord/expense_breakdown_chart.dart';
import 'package:on_field_work/helpers/widgets/dashbaord/expense_by_status_widget.dart';
import 'package:on_field_work/helpers/widgets/dashbaord/monthly_expense_dashboard_chart.dart';
import 'package:on_field_work/controller/dashboard/dashboard_controller.dart';
import 'package:on_field_work/helpers/utils/permission_constants.dart';
import 'package:on_field_work/model/dynamicMenu/dynamic_menu_model.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>();
final DynamicMenuController menuController = Get.put(DynamicMenuController());
late AnimationController _animationController;
late Animation<double> _fadeAnimation;
final DashboardController dashboardController =
Get.put(DashboardController(), permanent: true);
@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: Obx(() {
if (menuController.isLoading.value) {
return const Center(child: CircularProgressIndicator());
}
if (menuController.hasError.value || menuController.menuItems.isEmpty) {
return const Center(
child: Text(
"Failed to load menus. Please try again later.",
style: TextStyle(color: Colors.red),
),
);
}
// Filter allowed Finance menus dynamically
final financeMenuIds = [
MenuItems.expenseReimbursement,
MenuItems.paymentRequests,
MenuItems.advancePaymentStatements,
];
final financeMenus = menuController.menuItems
.where((m) => financeMenuIds.contains(m.id) && m.available)
.toList();
if (financeMenus.isEmpty) {
return const Center(
child: Text(
"You dont have access to the Finance section.",
style: TextStyle(color: Colors.grey),
),
);
}
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
_buildFinanceModulesCompact(financeMenus),
MySpacing.height(24),
ExpenseByStatusWidget(controller: dashboardController),
MySpacing.height(24),
ExpenseTypeReportChart(),
MySpacing.height(24),
MonthlyExpenseDashboardChart(),
],
),
);
}),
),
);
}
// --- Finance Modules (Compact Dashboard-style) ---
Widget _buildFinanceModulesCompact(List<MenuItem> financeMenus) {
// Map menu IDs to icon + color
final Map<String, _FinanceCardMeta> financeCardMeta = {
MenuItems.expenseReimbursement: _FinanceCardMeta(LucideIcons.badge_dollar_sign, contentTheme.info),
MenuItems.paymentRequests: _FinanceCardMeta(LucideIcons.receipt_text, contentTheme.primary),
MenuItems.advancePaymentStatements: _FinanceCardMeta(LucideIcons.wallet, contentTheme.warning),
};
// Build the stat items using API-provided mobileLink
final stats = financeMenus.map((menu) {
final meta = financeCardMeta[menu.id]!;
// --- Log the routing info ---
debugPrint(
"[Finance Card] ID: ${menu.id}, Title: ${menu.name}, Route: ${menu.mobileLink}");
return _FinanceStatItem(
meta.icon,
menu.name,
meta.color,
menu.mobileLink, // Each card navigates to its own route
);
}).toList();
final projectSelected = projectController.selectedProject != null;
return LayoutBuilder(builder: (context, constraints) {
// Determine number of columns dynamically
int crossAxisCount = (constraints.maxWidth ~/ 80).clamp(2, 4);
double cardWidth = (constraints.maxWidth - (crossAxisCount - 1) * 6) / crossAxisCount;
return Wrap(
spacing: 6,
runSpacing: 6,
alignment: WrapAlignment.end,
children: stats
.map((stat) => _buildFinanceModuleCard(stat, projectSelected, cardWidth))
.toList(),
);
});
}
Widget _buildFinanceModuleCard(
_FinanceStatItem stat, bool isProjectSelected, double width) {
return Opacity(
opacity: isProjectSelected ? 1.0 : 0.4, // Dim if no project selected
child: IgnorePointer(
ignoring: !isProjectSelected,
child: InkWell(
onTap: () => _onCardTap(stat, isProjectSelected),
borderRadius: BorderRadius.circular(5),
child: MyCard.bordered(
width: width,
height: 60,
paddingAll: 4,
borderRadiusAll: 5,
border: Border.all(color: Colors.grey.withOpacity(0.15)),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: stat.color.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
),
child: Icon(
stat.icon,
size: 16,
color: stat.color,
),
),
MySpacing.height(4),
Flexible(
child: Text(
stat.title,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 10,
overflow: TextOverflow.ellipsis,
),
maxLines: 2,
softWrap: true,
),
),
],
),
),
),
),
);
}
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"),
),
);
} else {
// Navigate to the card's specific route
Get.toNamed(statItem.route);
}
}
}
class _FinanceStatItem {
final IconData icon;
final String title;
final Color color;
final String route;
_FinanceStatItem(this.icon, this.title, this.color, this.route);
}
class _FinanceCardMeta {
final IconData icon;
final Color color;
_FinanceCardMeta(this.icon, this.color);
}