refactor: optimize project handling and UI state management across controllers and views

This commit is contained in:
Vaibhav Surve 2025-12-16 17:50:21 +05:30
parent 4e577bd7eb
commit 81de795e93
12 changed files with 230 additions and 278 deletions

View File

@ -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 = <Map<String, dynamic>>[].obs;
final attendanceSelectedRange = '15D'.obs;
final attendanceIsChartView = true.obs;
final isAttendanceLoading = false.obs;
// Project Progress
final projectChartData = <ChartTaskData>[].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<PendingExpensesData?>(null);
final isExpenseTypeReportLoading = false.obs;
final expenseTypeReportData = Rx<ExpenseTypeReportData?>(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 = <ExpenseTypeModel>[].obs;
final selectedExpenseType = Rx<ExpenseTypeModel?>(null);
// Teams/Employees
final isLoadingEmployees = true.obs;
final employees = <EmployeeModel>[].obs;
final uploadingStates = <String, RxBool>{}.obs;
// Collection
final isCollectionOverviewLoading = true.obs;
final collectionOverviewData = Rx<CollectionOverviewData?>(null);
// =========================
// Purchase Invoice Overview
// =========================
final isPurchaseInvoiceLoading = true.obs;
final purchaseInvoiceOverviewData = Rx<PurchaseInvoiceOverviewData?>(null);
// Constants
final List<String> ranges = const [
'7D',
'15D',
'30D'
]; // OPTIMIZED: Added const
final List<String> 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<String>(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<void> refreshDashboard() => fetchAllDashboardData();
Future<void> refreshDashboard() =>
fetchAllDashboardData(projectController.selectedProjectId.value);
Future<void> refreshAttendance() => fetchRoleWiseAttendance();
Future<void> refreshProjects() => fetchProjectProgress();
Future<void> 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<void> _executeApiCall(
RxBool loaderRx, Future<void> 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<void> fetchAllDashboardData() async {
final String projectId = projectController.selectedProjectId.value;
// --------------------------
// API FETCHES
// --------------------------
Future<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> fetchRoleWiseAttendance() async {
final id = projectController.selectedProjectId.value;
Future<void> 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<String, dynamic>.from(e)).toList() ?? [];
if (_latestProjectId != localId) return; // discard stale response
roleWiseData.assignAll(
response?.map((e) => Map<String, dynamic>.from(e)).toList() ?? []);
});
}
Future<void> 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<void> fetchProjectProgress() async {
final id = projectController.selectedProjectId.value;
Future<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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);
}
});
}

View File

@ -7,14 +7,19 @@ import 'package:on_field_work/helpers/services/storage/local_storage.dart';
class ProjectController extends GetxController {
RxList<GlobalProjectModel> projects = <GlobalProjectModel>[].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<String, RxBool> uploadingStates = <String, RxBool>{}.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<void> 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<void> 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();
}
}

View File

@ -70,6 +70,7 @@ class _ContactDetailScreenState extends State<ContactDetailScreen>
appBar: CustomAppBar(
title: 'Contact Profile',
backgroundColor: appBarColor,
projectName: " All Projects",
onBackPressed: () => Get.offAllNamed('/dashboard/directory-main-page'),
),

View File

@ -46,6 +46,7 @@ class _DirectoryMainScreenState extends State<DirectoryMainScreen>
appBar: CustomAppBar(
title: "Directory",
onBackPressed: () => Get.offNamed('/dashboard'),
projectName: " All Projects",
backgroundColor: appBarColor,
),
body: Stack(

View File

@ -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<EmployeeProfilePage>
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<EmployeeProfilePage>
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<EmployeeProfilePage>
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<EmployeeProfilePage>
),
),
),
// === 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<EmployeeProfilePage>
),
);
}
}
}

View File

@ -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<EmployeesScreen> with UIMixin {
appBar: CustomAppBar(
title: "Employees",
backgroundColor: appBarColor,
projectName: Get.find<ProjectController>().selectedProject?.name ??
'Select Project',
projectName: " All Projects",
onBackPressed: () => Get.offNamed('/dashboard'),
),
body: Stack(

View File

@ -42,7 +42,7 @@ class _ExpenseDetailScreenState extends State<ExpenseDetailScreen>
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<ExpenseDetailScreen>
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<ExpenseDetailScreen>
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<ExpenseDetailScreen>
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 {
],
);
}
}
}

View File

@ -95,6 +95,7 @@ class _ExpenseMainScreenState extends State<ExpenseMainScreen>
backgroundColor: Colors.white,
appBar: CustomAppBar(
title: "Expense & Reimbursement",
projectName: " All Projects",
backgroundColor: appBarColor,
onBackPressed: () => Get.toNamed('/dashboard/finance'),
),

View File

@ -54,6 +54,7 @@ class _AdvancePaymentScreenState extends State<AdvancePaymentScreen>
backgroundColor: const Color(0xFFF5F5F5),
appBar: CustomAppBar(
title: "Advance Payments",
projectName: " All Projects",
onBackPressed: () => Get.offNamed('/dashboard/finance'),
backgroundColor: appBarColor,
),

View File

@ -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<FinanceScreen>
backgroundColor: const Color(0xFFF8F9FA),
appBar: CustomAppBar(
title: "Finance",
onBackPressed: () => Get.offAllNamed( '/dashboard' ),
projectName: " All Projects",
onBackPressed: () => Get.offAllNamed('/dashboard'),
backgroundColor: appBarColor,
),
body: Stack(

View File

@ -114,6 +114,7 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
backgroundColor: Colors.white,
appBar: CustomAppBar(
title: "Payment Request Details",
projectName: " All Projects",
backgroundColor: appBarColor,
),
body: Stack(
@ -217,7 +218,7 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
return const SizedBox.shrink();
}
if (!_checkedPermission && employeeInfo != null) {
if (!_checkedPermission && employeeInfo != null) {
_checkedPermission = true;
_checkPermissionToSubmit(request);
}

View File

@ -104,6 +104,7 @@ class _PaymentRequestMainScreenState extends State<PaymentRequestMainScreen>
backgroundColor: Colors.white,
appBar: CustomAppBar(
title: "Payment Requests",
projectName: " All Projects",
onBackPressed: () => Get.offNamed('/dashboard/finance'),
backgroundColor: appBarColor,
),