From 81de795e934575d54d1f947f135caadb41c9a820 Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Tue, 16 Dec 2025 17:50:21 +0530 Subject: [PATCH] refactor: optimize project handling and UI state management across controllers and views --- .../dashboard/dashboard_controller.dart | 341 +++++++++--------- lib/controller/project_controller.dart | 66 ++-- lib/view/directory/contact_detail_screen.dart | 1 + lib/view/directory/directory_main_screen.dart | 1 + .../employees/employee_profile_screen.dart | 72 +--- lib/view/employees/employees_screen.dart | 4 +- lib/view/expense/expense_detail_screen.dart | 13 +- lib/view/expense/expense_screen.dart | 1 + lib/view/finance/advance_payment_screen.dart | 1 + lib/view/finance/finance_screen.dart | 4 +- .../payment_request_detail_screen.dart | 3 +- lib/view/finance/payment_request_screen.dart | 1 + 12 files changed, 230 insertions(+), 278 deletions(-) diff --git a/lib/controller/dashboard/dashboard_controller.dart b/lib/controller/dashboard/dashboard_controller.dart index ad0b041..0676449 100644 --- a/lib/controller/dashboard/dashboard_controller.dart +++ b/lib/controller/dashboard/dashboard_controller.dart @@ -12,26 +12,21 @@ import 'package:on_field_work/model/dashboard/collection_overview_model.dart'; import 'package:on_field_work/model/dashboard/purchase_invoice_model.dart'; class DashboardController extends GetxController { - // Dependencies final ProjectController projectController = Get.put(ProjectController()); - // ========================= - // 1. STATE VARIABLES (No functional change) - // ========================= - - // Attendance + // -------------------------- + // STATE VARIABLES + // -------------------------- final roleWiseData = >[].obs; final attendanceSelectedRange = '15D'.obs; final attendanceIsChartView = true.obs; final isAttendanceLoading = false.obs; - // Project Progress final projectChartData = [].obs; final projectSelectedRange = '15D'.obs; final projectIsChartView = true.obs; final isProjectLoading = false.obs; - // Overview Counts final totalProjects = 0.obs; final ongoingProjects = 0.obs; final isProjectsLoading = false.obs; @@ -44,13 +39,12 @@ class DashboardController extends GetxController { final inToday = 0.obs; final isTeamsLoading = false.obs; - // Expenses & Reports final isPendingExpensesLoading = false.obs; final pendingExpensesData = Rx(null); final isExpenseTypeReportLoading = false.obs; final expenseTypeReportData = Rx(null); - // OPTIMIZED: Use const Duration for better performance + final expenseReportStartDate = DateTime.now().subtract(const Duration(days: 15)).obs; final expenseReportEndDate = DateTime.now().obs; @@ -64,43 +58,38 @@ class DashboardController extends GetxController { final expenseTypes = [].obs; final selectedExpenseType = Rx(null); - // Teams/Employees final isLoadingEmployees = true.obs; final employees = [].obs; final uploadingStates = {}.obs; - // Collection final isCollectionOverviewLoading = true.obs; final collectionOverviewData = Rx(null); -// ========================= -// Purchase Invoice Overview -// ========================= + final isPurchaseInvoiceLoading = true.obs; final purchaseInvoiceOverviewData = Rx(null); - // Constants - final List ranges = const [ - '7D', - '15D', - '30D' - ]; // OPTIMIZED: Added const + + final List ranges = const ['7D', '15D', '30D']; static const _rangeDaysMap = { - // OPTIMIZED: Added const '7D': 7, '15D': 15, '30D': 30, '3M': 90, '6M': 180 }; - // DSO Calculation Constants (OPTIMIZED: Added const) + static const double _w0_30 = 15.0; static const double _w30_60 = 45.0; static const double _w60_90 = 75.0; static const double _w90_plus = 105.0; - // ========================= - // 2. COMPUTED PROPERTIES (No functional change) - // ========================= + // -------------------------- + // LATEST PROJECT ID (for race condition fix) + // -------------------------- + String _latestProjectId = ''; + // -------------------------- + // COMPUTED PROPERTIES + // -------------------------- int getAttendanceDays() => _rangeDaysMap[attendanceSelectedRange.value] ?? 7; int getProjectDays() => _rangeDaysMap[projectSelectedRange.value] ?? 7; @@ -116,45 +105,46 @@ class DashboardController extends GetxController { return weightedDue / data.totalDueAmount; } - // ========================= - // 3. LIFECYCLE (No functional change) - // ========================= - + // -------------------------- + // LIFECYCLE + // -------------------------- @override void onInit() { super.onInit(); logSafe('DashboardController initialized', level: LogLevel.info); - // Project Selection Listener + // -------------------------- + // Project change listener + // -------------------------- ever(projectController.selectedProjectId, (id) { if (id.isNotEmpty) { - fetchAllDashboardData(); - fetchTodaysAttendance(id); + _latestProjectId = id; // track latest project + fetchAllDashboardData(id); } }); // Expense Report Date Listener - // OPTIMIZED: Using `everAll` is already efficient for this logic everAll([expenseReportStartDate, expenseReportEndDate], (_) { - if (projectController.selectedProjectId.value.isNotEmpty) { + final id = projectController.selectedProjectId.value; + if (id.isNotEmpty) { fetchExpenseTypeReport( startDate: expenseReportStartDate.value, endDate: expenseReportEndDate.value, + projectId: id, ); } }); - // Chart Range Listeners ever(attendanceSelectedRange, (_) => fetchRoleWiseAttendance()); ever(projectSelectedRange, (_) => fetchProjectProgress()); } - // ========================= - // 4. USER ACTIONS (No functional change) - // ========================= - + // -------------------------- + // USER ACTIONS + // -------------------------- void updateAttendanceRange(String range) => attendanceSelectedRange.value = range; + void updateProjectRange(String range) => projectSelectedRange.value = range; void toggleAttendanceChartView(bool isChart) => attendanceIsChartView.value = isChart; @@ -169,7 +159,6 @@ class DashboardController extends GetxController { void updateMonthlyExpenseDuration(MonthlyExpenseDuration duration) { selectedMonthlyExpenseDuration.value = duration; - // OPTIMIZED: The map approach is highly efficient. const durationMap = { MonthlyExpenseDuration.oneMonth: 1, MonthlyExpenseDuration.threeMonths: 3, @@ -182,7 +171,8 @@ class DashboardController extends GetxController { fetchMonthlyExpenses(); } - Future refreshDashboard() => fetchAllDashboardData(); + Future refreshDashboard() => + fetchAllDashboardData(projectController.selectedProjectId.value); Future refreshAttendance() => fetchRoleWiseAttendance(); Future refreshProjects() => fetchProjectProgress(); Future refreshTasks() async { @@ -190,197 +180,194 @@ class DashboardController extends GetxController { if (id.isNotEmpty) await fetchDashboardTasks(projectId: id); } - // ========================= - // 5. DATA FETCHING (API) - // ========================= - - /// Wrapper to reduce try-finally boilerplate for loading states - // OPTIMIZED: Renamed variable to avoid shadowing standard library. + // -------------------------- + // HELPER: Execute API call + // -------------------------- Future _executeApiCall( RxBool loaderRx, Future Function() apiLogic) async { loaderRx.value = true; try { await apiLogic(); } catch (e, stack) { - // OPTIMIZED: Added logging of error for better debugging logSafe('API Call Failed: $e', level: LogLevel.error, stackTrace: stack); } finally { loaderRx.value = false; } } - Future fetchAllDashboardData() async { - final String projectId = projectController.selectedProjectId.value; + // -------------------------- + // API FETCHES + // -------------------------- + Future fetchAllDashboardData(String projectId) async { if (projectId.isEmpty) return; + _latestProjectId = projectId; - // OPTIMIZED: Ensure MasterData is fetched only once if possible, but kept in Future.wait for robustness await Future.wait([ - fetchRoleWiseAttendance(), - fetchProjectProgress(), + fetchRoleWiseAttendance(projectId), + fetchProjectProgress(projectId), fetchDashboardTasks(projectId: projectId), fetchDashboardTeams(projectId: projectId), - fetchPendingExpenses(), + fetchPendingExpenses(projectId), fetchExpenseTypeReport( startDate: expenseReportStartDate.value, endDate: expenseReportEndDate.value, + projectId: projectId, ), - fetchMonthlyExpenses(), + fetchMonthlyExpenses(projectId: projectId), fetchMasterData(), - fetchCollectionOverview(), - fetchPurchaseInvoiceOverview(), + fetchCollectionOverview(projectId), + fetchPurchaseInvoiceOverview(projectId), fetchTodaysAttendance(projectId), ]); } - Future fetchCollectionOverview() async { - final projectId = projectController.selectedProjectId.value; - if (projectId.isEmpty) return; + // -------------------------- + // Each fetch now ignores stale project responses + // -------------------------- - await _executeApiCall(isCollectionOverviewLoading, () async { - final response = - await ApiService.getCollectionOverview(projectId: projectId); - // OPTIMIZED: Used null-aware assignment - collectionOverviewData.value = - (response?.success == true) ? response!.data : null; - }); - } - - Future fetchTodaysAttendance(String projectId) async { - await _executeApiCall(isLoadingEmployees, () async { - final response = await ApiService.getAttendanceForDashboard(projectId); - if (response != null) { - employees.value = response; - // OPTIMIZED: Use `putIfAbsent` and ensure the map holds an RxBool - for (var emp in employees) { - uploadingStates.putIfAbsent(emp.id, () => false.obs); - } - } else { - employees.clear(); - } - }); - } - - Future fetchMasterData() async { - // OPTIMIZATION: Use _executeApiCall for consistency - await _executeApiCall(false.obs, () async { - // Use a local RxBool since there's no dedicated loader state - final data = await ApiService.getMasterExpenseTypes(); - if (data is List) { - expenseTypes.value = - data.map((e) => ExpenseTypeModel.fromJson(e)).toList(); - } - }); - } - - Future fetchMonthlyExpenses({String? categoryId}) async { - await _executeApiCall(isMonthlyExpenseLoading, () async { - final response = await ApiService.getDashboardMonthlyExpensesApi( - categoryId: categoryId, - months: selectedMonthsCount.value, - ); - // OPTIMIZED: Used null-aware assignment - monthlyExpenseList.value = - (response?.success == true) ? response!.data : []; - }); - } - - Future fetchPurchaseInvoiceOverview() async { - final projectId = projectController.selectedProjectId.value; - if (projectId.isEmpty) return; - - await _executeApiCall(isPurchaseInvoiceLoading, () async { - final response = await ApiService.getPurchaseInvoiceOverview( - projectId: projectId, - ); - // OPTIMIZED: Used null-aware assignment - purchaseInvoiceOverviewData.value = - (response?.success == true) ? response!.data : null; - }); - } - - Future fetchPendingExpenses() async { - final id = projectController.selectedProjectId.value; - if (id.isEmpty) return; - - await _executeApiCall(isPendingExpensesLoading, () async { - final response = await ApiService.getPendingExpensesApi(projectId: id); - // OPTIMIZED: Used null-aware assignment - pendingExpensesData.value = - (response?.success == true) ? response!.data : null; - }); - } - - Future fetchRoleWiseAttendance() async { - final id = projectController.selectedProjectId.value; + Future fetchRoleWiseAttendance([String? projectId]) async { + final id = projectId ?? projectController.selectedProjectId.value; if (id.isEmpty) return; + final localId = id; await _executeApiCall(isAttendanceLoading, () async { final response = await ApiService.getDashboardAttendanceOverview( id, getAttendanceDays()); - // OPTIMIZED: Used null-aware assignment - roleWiseData.value = - response?.map((e) => Map.from(e)).toList() ?? []; + if (_latestProjectId != localId) return; // discard stale response + roleWiseData.assignAll( + response?.map((e) => Map.from(e)).toList() ?? []); }); } - Future fetchExpenseTypeReport( - {required DateTime startDate, required DateTime endDate}) async { - final id = projectController.selectedProjectId.value; - if (id.isEmpty) return; - - await _executeApiCall(isExpenseTypeReportLoading, () async { - final response = await ApiService.getExpenseTypeReportApi( - projectId: id, - startDate: startDate, - endDate: endDate, - ); - // OPTIMIZED: Used null-aware assignment - expenseTypeReportData.value = - (response?.success == true) ? response!.data : null; - }); - } - - Future fetchProjectProgress() async { - final id = projectController.selectedProjectId.value; + Future fetchProjectProgress([String? projectId]) async { + final id = projectId ?? projectController.selectedProjectId.value; if (id.isEmpty) return; + final localId = id; await _executeApiCall(isProjectLoading, () async { final response = await ApiService.getProjectProgress( projectId: id, days: getProjectDays()); + if (_latestProjectId != localId) return; if (response?.success == true) { - projectChartData.value = response!.data + projectChartData.assignAll(response!.data .map((d) => ChartTaskData.fromProjectData(d)) - .toList(); + .toList()); } else { - projectChartData.clear(); // OPTIMIZED: Clear data on failure + projectChartData.clear(); } }); } Future fetchDashboardTasks({required String projectId}) async { + final localId = projectId; await _executeApiCall(isTasksLoading, () async { final response = await ApiService.getDashboardTasks(projectId: projectId); - if (response?.success == true) { - // OPTIMIZED: Used null-aware access with default value - totalTasks.value = response!.data?.totalTasks ?? 0; - completedTasks.value = response.data?.completedTasks ?? 0; - } else { - totalTasks.value = 0; - completedTasks.value = 0; - } + if (_latestProjectId != localId) return; + totalTasks.value = response?.data?.totalTasks ?? 0; + completedTasks.value = response?.data?.completedTasks ?? 0; }); } Future fetchDashboardTeams({required String projectId}) async { + final localId = projectId; await _executeApiCall(isTeamsLoading, () async { final response = await ApiService.getDashboardTeams(projectId: projectId); - if (response?.success == true) { - // OPTIMIZED: Used null-aware access with default value - totalEmployees.value = response!.data?.totalEmployees ?? 0; - inToday.value = response.data?.inToday ?? 0; - } else { - totalEmployees.value = 0; - inToday.value = 0; + if (_latestProjectId != localId) return; + totalEmployees.value = response?.data?.totalEmployees ?? 0; + inToday.value = response?.data?.inToday ?? 0; + }); + } + + Future fetchPendingExpenses([String? projectId]) async { + final id = projectId ?? projectController.selectedProjectId.value; + if (id.isEmpty) return; + + final localId = id; + await _executeApiCall(isPendingExpensesLoading, () async { + final response = await ApiService.getPendingExpensesApi(projectId: id); + if (_latestProjectId != localId) return; + pendingExpensesData.value = + response?.success == true ? response!.data : null; + }); + } + + Future fetchExpenseTypeReport( + {required DateTime startDate, + required DateTime endDate, + String? projectId}) async { + final id = projectId ?? projectController.selectedProjectId.value; + if (id.isEmpty) return; + + final localId = id; + await _executeApiCall(isExpenseTypeReportLoading, () async { + final response = await ApiService.getExpenseTypeReportApi( + projectId: id, startDate: startDate, endDate: endDate); + if (_latestProjectId != localId) return; + expenseTypeReportData.value = + response?.success == true ? response!.data : null; + }); + } + + Future fetchMonthlyExpenses( + {String? categoryId, String? projectId}) async { + final id = projectId ?? projectController.selectedProjectId.value; + if (id.isEmpty) return; + + final localId = id; + await _executeApiCall(isMonthlyExpenseLoading, () async { + final response = await ApiService.getDashboardMonthlyExpensesApi( + categoryId: categoryId, months: selectedMonthsCount.value); + if (_latestProjectId != localId) return; + monthlyExpenseList + .assignAll(response?.success == true ? response!.data : []); + }); + } + + Future fetchMasterData() async { + await _executeApiCall(false.obs, () async { + final data = await ApiService.getMasterExpenseTypes(); + if (data is List) + expenseTypes + .assignAll(data.map((e) => ExpenseTypeModel.fromJson(e)).toList()); + }); + } + + Future fetchCollectionOverview([String? projectId]) async { + final id = projectId ?? projectController.selectedProjectId.value; + if (id.isEmpty) return; + + final localId = id; + await _executeApiCall(isCollectionOverviewLoading, () async { + final response = await ApiService.getCollectionOverview(projectId: id); + if (_latestProjectId != localId) return; + collectionOverviewData.value = + response?.success == true ? response!.data : null; + }); + } + + Future fetchPurchaseInvoiceOverview([String? projectId]) async { + final id = projectId ?? projectController.selectedProjectId.value; + if (id.isEmpty) return; + + final localId = id; + await _executeApiCall(isPurchaseInvoiceLoading, () async { + final response = + await ApiService.getPurchaseInvoiceOverview(projectId: id); + if (_latestProjectId != localId) return; + purchaseInvoiceOverviewData.value = + response?.success == true ? response!.data : null; + }); + } + + Future fetchTodaysAttendance(String projectId) async { + final localId = projectId; + await _executeApiCall(isLoadingEmployees, () async { + final response = await ApiService.getAttendanceForDashboard(projectId); + if (_latestProjectId != localId) return; + + employees.assignAll(response ?? []); + for (var emp in employees) { + uploadingStates.putIfAbsent(emp.id, () => false.obs); } }); } diff --git a/lib/controller/project_controller.dart b/lib/controller/project_controller.dart index 0ffafa9..ec6808e 100644 --- a/lib/controller/project_controller.dart +++ b/lib/controller/project_controller.dart @@ -7,14 +7,19 @@ import 'package:on_field_work/helpers/services/storage/local_storage.dart'; class ProjectController extends GetxController { RxList projects = [].obs; RxString selectedProjectId = ''.obs; - RxBool isProjectListExpanded = false.obs; - RxBool isProjectSelectionExpanded = false.obs; + RxBool isProjectSelectionExpanded = false.obs; + RxBool isProjectListExpanded = false.obs; RxBool isProjectDropdownExpanded = false.obs; + RxBool isLoading = true.obs; RxBool isLoadingProjects = true.obs; + RxMap uploadingStates = {}.obs; + // -------------------------- + // Current selected project + // -------------------------- GlobalProjectModel? get selectedProject { if (selectedProjectId.value.isEmpty) return null; return projects.firstWhereOrNull((p) => p.id == selectedProjectId.value); @@ -26,58 +31,63 @@ class ProjectController extends GetxController { fetchProjects(); } + // -------------------------- + // Clear all projects & UI states + // -------------------------- void clearProjects() { projects.clear(); selectedProjectId.value = ''; - isProjectSelectionExpanded.value = false; isProjectListExpanded.value = false; isProjectDropdownExpanded.value = false; - isLoadingProjects.value = false; isLoading.value = false; + isLoadingProjects.value = false; uploadingStates.clear(); LocalStorage.saveString('selectedProjectId', ''); - logSafe("Projects cleared and UI states reset."); - update(); } - /// Fetches projects and initializes selected project. + // -------------------------- + // Fetch projects from API + // -------------------------- Future fetchProjects() async { - isLoadingProjects.value = true; isLoading.value = true; + isLoadingProjects.value = true; - final response = await ApiService.getGlobalProjects(); + try { + final response = await ApiService.getGlobalProjects(); - if (response != null && response.isNotEmpty) { - projects.assignAll( - response.map((json) => GlobalProjectModel.fromJson(json)).toList(), - ); + if (response != null && response.isNotEmpty) { + projects.assignAll(response.map((json) => GlobalProjectModel.fromJson(json)).toList()); - String? savedId = LocalStorage.getString('selectedProjectId'); - if (savedId != null && projects.any((p) => p.id == savedId)) { - selectedProjectId.value = savedId; + // Load previously saved project + String? savedId = LocalStorage.getString('selectedProjectId'); + if (savedId != null && projects.any((p) => p.id == savedId)) { + selectedProjectId.value = savedId; + } else { + selectedProjectId.value = projects.first.id.toString(); + LocalStorage.saveString('selectedProjectId', selectedProjectId.value); + } + + logSafe("Projects fetched: ${projects.length}"); } else { - selectedProjectId.value = projects.first.id.toString(); - LocalStorage.saveString('selectedProjectId', selectedProjectId.value); + logSafe("No projects found or API call failed.", level: LogLevel.warning); } - - isProjectSelectionExpanded.value = false; - logSafe("Projects fetched: ${projects.length}"); - } else { - logSafe("No Global projects found or API call failed.", level: LogLevel.warning); + } catch (e, stack) { + logSafe("Error fetching projects: $e", level: LogLevel.error, stackTrace: stack); + } finally { + isLoading.value = false; + isLoadingProjects.value = false; } - - isLoadingProjects.value = false; - isLoading.value = false; - update(['dashboard_controller']); } Future updateSelectedProject(String projectId) async { + if (selectedProjectId.value == projectId) return; selectedProjectId.value = projectId; await LocalStorage.saveString('selectedProjectId', projectId); logSafe("Selected project updated to $projectId"); - update(['selected_project']); + isProjectSelectionExpanded.value = false; + update(); } } diff --git a/lib/view/directory/contact_detail_screen.dart b/lib/view/directory/contact_detail_screen.dart index b906cdd..957ea2d 100644 --- a/lib/view/directory/contact_detail_screen.dart +++ b/lib/view/directory/contact_detail_screen.dart @@ -70,6 +70,7 @@ class _ContactDetailScreenState extends State appBar: CustomAppBar( title: 'Contact Profile', backgroundColor: appBarColor, + projectName: " All Projects", onBackPressed: () => Get.offAllNamed('/dashboard/directory-main-page'), ), diff --git a/lib/view/directory/directory_main_screen.dart b/lib/view/directory/directory_main_screen.dart index b06ee1e..14ee4a7 100644 --- a/lib/view/directory/directory_main_screen.dart +++ b/lib/view/directory/directory_main_screen.dart @@ -46,6 +46,7 @@ class _DirectoryMainScreenState extends State appBar: CustomAppBar( title: "Directory", onBackPressed: () => Get.offNamed('/dashboard'), + projectName: " All Projects", backgroundColor: appBarColor, ), body: Stack( diff --git a/lib/view/employees/employee_profile_screen.dart b/lib/view/employees/employee_profile_screen.dart index 93f1c37..4cf02c1 100644 --- a/lib/view/employees/employee_profile_screen.dart +++ b/lib/view/employees/employee_profile_screen.dart @@ -4,6 +4,7 @@ import 'package:on_field_work/view/employees/employee_detail_screen.dart'; import 'package:on_field_work/view/document/user_document_screen.dart'; import 'package:on_field_work/helpers/widgets/custom_app_bar.dart'; import 'package:on_field_work/helpers/utils/mixins/ui_mixin.dart'; +import 'package:on_field_work/helpers/widgets/pill_tab_bar.dart'; // <-- import PillTabBar class EmployeeProfilePage extends StatefulWidget { final String employeeId; @@ -16,14 +17,11 @@ class EmployeeProfilePage extends StatefulWidget { class _EmployeeProfilePageState extends State with SingleTickerProviderStateMixin, UIMixin { - // We no longer need to listen to the TabController for setState, - // as the TabBar handles its own state updates via the controller. late TabController _tabController; @override void initState() { super.initState(); - // Initialize TabController with 2 tabs _tabController = TabController(length: 2, vsync: this); } @@ -33,11 +31,8 @@ class _EmployeeProfilePageState extends State super.dispose(); } - // --- No need for _buildSegmentedButton function anymore --- - @override Widget build(BuildContext context) { - // Accessing theme colors for consistency final Color appBarColor = contentTheme.primary; final Color primaryColor = contentTheme.primary; @@ -45,13 +40,13 @@ class _EmployeeProfilePageState extends State backgroundColor: const Color(0xFFF1F1F1), appBar: CustomAppBar( title: "Employee Profile", + projectName: " All Projects", onBackPressed: () => Get.back(), backgroundColor: appBarColor, ), body: Stack( children: [ - // === Gradient at the top behind AppBar + Toggle === - // This container ensures the background color transitions nicely + // Gradient at the top behind AppBar + Toggle Container( height: 50, decoration: BoxDecoration( @@ -65,63 +60,20 @@ class _EmployeeProfilePageState extends State ), ), ), - // === Main Content Area === SafeArea( top: false, bottom: true, child: Column( children: [ - // 🛑 NEW: The Modern TabBar Implementation 🛑 - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), - child: Container( - height: 48, // Define a specific height for the TabBar container - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(24.0), // Rounded corners for a chip-like look - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.15), - blurRadius: 4, - offset: const Offset(0, 2), - ), - ], - ), - child: TabBar( - controller: _tabController, - // Style the indicator as a subtle pill/chip - indicator: BoxDecoration( - color: primaryColor.withOpacity(0.1), // Light background color for the selection - borderRadius: BorderRadius.circular(24.0), - ), - indicatorSize: TabBarIndicatorSize.tab, - // The padding is used to slightly shrink the indicator area - indicatorPadding: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 4.0), - - // Text styling - labelColor: primaryColor, // Selected text color is primary - unselectedLabelColor: Colors.grey.shade600, // Unselected text color is darker grey - labelStyle: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 15, - ), - unselectedLabelStyle: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 15, - ), - - // Tabs (No custom widget needed, just use the built-in Tab) - tabs: const [ - Tab(text: "Details"), - Tab(text: "Documents"), - ], - // Setting this to zero removes the default underline - dividerColor: Colors.transparent, - ), - ), + PillTabBar( + controller: _tabController, + tabs: const ["Details", "Documents"], + icons: const [Icons.person, Icons.folder], + selectedColor: primaryColor, + unselectedColor: Colors.grey.shade600, + indicatorColor: primaryColor, + height: 48, ), - - // 🛑 TabBarView (The Content) 🛑 Expanded( child: TabBarView( controller: _tabController, @@ -144,4 +96,4 @@ class _EmployeeProfilePageState extends State ), ); } -} \ No newline at end of file +} diff --git a/lib/view/employees/employees_screen.dart b/lib/view/employees/employees_screen.dart index bac73c1..c2cf5dd 100644 --- a/lib/view/employees/employees_screen.dart +++ b/lib/view/employees/employees_screen.dart @@ -7,7 +7,6 @@ import 'package:on_field_work/helpers/widgets/my_text.dart'; import 'package:on_field_work/model/employees/add_employee_bottom_sheet.dart'; import 'package:on_field_work/controller/employee/employees_screen_controller.dart'; import 'package:on_field_work/helpers/widgets/avatar.dart'; -import 'package:on_field_work/controller/project_controller.dart'; import 'package:on_field_work/helpers/widgets/my_custom_skeleton.dart'; import 'package:on_field_work/helpers/utils/launcher_utils.dart'; import 'package:on_field_work/view/employees/assign_employee_bottom_sheet.dart'; @@ -91,8 +90,7 @@ class _EmployeesScreenState extends State with UIMixin { appBar: CustomAppBar( title: "Employees", backgroundColor: appBarColor, - projectName: Get.find().selectedProject?.name ?? - 'Select Project', + projectName: " All Projects", onBackPressed: () => Get.offNamed('/dashboard'), ), body: Stack( diff --git a/lib/view/expense/expense_detail_screen.dart b/lib/view/expense/expense_detail_screen.dart index 2c15bc4..eb6e3f6 100644 --- a/lib/view/expense/expense_detail_screen.dart +++ b/lib/view/expense/expense_detail_screen.dart @@ -42,7 +42,7 @@ class _ExpenseDetailScreenState extends State super.initState(); controller = Get.put(ExpenseDetailController(), tag: widget.expenseId); // EmployeeInfo loading and permission checking is now handled inside controller.init() - controller.init(widget.expenseId); + controller.init(widget.expenseId); } @override @@ -61,6 +61,7 @@ class _ExpenseDetailScreenState extends State backgroundColor: const Color(0xFFF7F7F7), appBar: CustomAppBar( title: "Expense Details", + projectName: " All Projects", backgroundColor: appBarColor, onBackPressed: () => Get.toNamed('/dashboard/expense-main-page'), ), @@ -270,9 +271,8 @@ class _ExpenseDetailScreenState extends State controller.parsePermissionIds(rawPermissions); final isSubmitStatus = next.id == submitStatusId; - final isCreatedByCurrentUser = - controller.employeeInfo?.id == expense.createdBy.id; // Use controller's employeeInfo - + final isCreatedByCurrentUser = controller.employeeInfo?.id == + expense.createdBy.id; if (isSubmitStatus) return isCreatedByCurrentUser; return permissionController.hasAnyPermission(parsedPermissions); }).map((next) { @@ -311,8 +311,7 @@ class _ExpenseDetailScreenState extends State context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( - borderRadius: - BorderRadius.vertical(top: Radius.circular(5))), + borderRadius: BorderRadius.vertical(top: Radius.circular(5))), builder: (context) => ReimbursementBottomSheet( expenseId: expense.id, statusId: next.id, @@ -785,4 +784,4 @@ class _InvoiceTotals extends StatelessWidget { ], ); } -} \ No newline at end of file +} diff --git a/lib/view/expense/expense_screen.dart b/lib/view/expense/expense_screen.dart index 1d257e4..2bbe692 100644 --- a/lib/view/expense/expense_screen.dart +++ b/lib/view/expense/expense_screen.dart @@ -95,6 +95,7 @@ class _ExpenseMainScreenState extends State backgroundColor: Colors.white, appBar: CustomAppBar( title: "Expense & Reimbursement", + projectName: " All Projects", backgroundColor: appBarColor, onBackPressed: () => Get.toNamed('/dashboard/finance'), ), diff --git a/lib/view/finance/advance_payment_screen.dart b/lib/view/finance/advance_payment_screen.dart index 0926452..de12a8f 100644 --- a/lib/view/finance/advance_payment_screen.dart +++ b/lib/view/finance/advance_payment_screen.dart @@ -54,6 +54,7 @@ class _AdvancePaymentScreenState extends State backgroundColor: const Color(0xFFF5F5F5), appBar: CustomAppBar( title: "Advance Payments", + projectName: " All Projects", onBackPressed: () => Get.offNamed('/dashboard/finance'), backgroundColor: appBarColor, ), diff --git a/lib/view/finance/finance_screen.dart b/lib/view/finance/finance_screen.dart index 4f50dde..a8b8513 100644 --- a/lib/view/finance/finance_screen.dart +++ b/lib/view/finance/finance_screen.dart @@ -14,7 +14,6 @@ import 'package:on_field_work/helpers/utils/permission_constants.dart'; import 'package:on_field_work/model/dynamicMenu/dynamic_menu_model.dart'; import 'package:on_field_work/helpers/widgets/custom_app_bar.dart'; - class FinanceScreen extends StatefulWidget { const FinanceScreen({super.key}); @@ -59,7 +58,8 @@ class _FinanceScreenState extends State backgroundColor: const Color(0xFFF8F9FA), appBar: CustomAppBar( title: "Finance", - onBackPressed: () => Get.offAllNamed( '/dashboard' ), + projectName: " All Projects", + onBackPressed: () => Get.offAllNamed('/dashboard'), backgroundColor: appBarColor, ), body: Stack( diff --git a/lib/view/finance/payment_request_detail_screen.dart b/lib/view/finance/payment_request_detail_screen.dart index 1f17f53..4ee88d9 100644 --- a/lib/view/finance/payment_request_detail_screen.dart +++ b/lib/view/finance/payment_request_detail_screen.dart @@ -114,6 +114,7 @@ class _PaymentRequestDetailScreenState extends State backgroundColor: Colors.white, appBar: CustomAppBar( title: "Payment Request Details", + projectName: " All Projects", backgroundColor: appBarColor, ), body: Stack( @@ -217,7 +218,7 @@ class _PaymentRequestDetailScreenState extends State return const SizedBox.shrink(); } - if (!_checkedPermission && employeeInfo != null) { + if (!_checkedPermission && employeeInfo != null) { _checkedPermission = true; _checkPermissionToSubmit(request); } diff --git a/lib/view/finance/payment_request_screen.dart b/lib/view/finance/payment_request_screen.dart index 86bec30..964de46 100644 --- a/lib/view/finance/payment_request_screen.dart +++ b/lib/view/finance/payment_request_screen.dart @@ -104,6 +104,7 @@ class _PaymentRequestMainScreenState extends State backgroundColor: Colors.white, appBar: CustomAppBar( title: "Payment Requests", + projectName: " All Projects", onBackPressed: () => Get.offNamed('/dashboard/finance'), backgroundColor: appBarColor, ),