feat: Enhance organization selection and fetching logic with reactive state management

This commit is contained in:
Vaibhav Surve 2025-09-24 15:37:06 +05:30
parent 1d9c416f68
commit 85d3dedbef
6 changed files with 46 additions and 27 deletions

View File

@ -4,37 +4,49 @@ import 'package:marco/helpers/services/api_service.dart';
import 'package:marco/model/attendance/organization_per_project_list_model.dart'; import 'package:marco/model/attendance/organization_per_project_list_model.dart';
class OrganizationController extends GetxController { class OrganizationController extends GetxController {
/// List of organizations assigned to the selected project
List<Organization> organizations = []; List<Organization> organizations = [];
Organization? selectedOrganization;
/// Currently selected organization (reactive)
Rxn<Organization> selectedOrganization = Rxn<Organization>();
/// Loading state for fetching organizations
final isLoadingOrganizations = false.obs; final isLoadingOrganizations = false.obs;
/// Fetch organizations assigned to a given project
Future<void> fetchOrganizations(String projectId) async { Future<void> fetchOrganizations(String projectId) async {
try { try {
isLoadingOrganizations.value = true; isLoadingOrganizations.value = true;
final response = await ApiService.getAssignedOrganizations(projectId); final response = await ApiService.getAssignedOrganizations(projectId);
if (response != null) { if (response != null && response.data.isNotEmpty) {
organizations = response.data; organizations = response.data;
logSafe("Organizations fetched: ${organizations.length}"); logSafe("Organizations fetched: ${organizations.length}");
} else { } else {
logSafe("Failed to fetch organizations for project $projectId", organizations = [];
level: LogLevel.error); logSafe("No organizations found for project $projectId",
level: LogLevel.warning);
} }
} catch (e, stackTrace) {
logSafe("Failed to fetch organizations: $e",
level: LogLevel.error, error: e, stackTrace: stackTrace);
organizations = [];
} finally { } finally {
isLoadingOrganizations.value = false; isLoadingOrganizations.value = false;
update();
} }
} }
/// Select an organization
void selectOrganization(Organization? org) { void selectOrganization(Organization? org) {
selectedOrganization = org; selectedOrganization.value = org;
update();
} }
/// Clear the selection (set to "All Organizations")
void clearSelection() { void clearSelection() {
selectedOrganization = null; selectedOrganization.value = null;
update();
} }
/// Current selection name for UI
String get currentSelection => String get currentSelection =>
selectedOrganization?.name ?? "All Organizations"; selectedOrganization.value?.name ?? "All Organizations";
} }

View File

@ -24,8 +24,8 @@ class AttendanceActionColors {
ButtonActions.rejected: Colors.orange, ButtonActions.rejected: Colors.orange,
ButtonActions.approved: Colors.green, ButtonActions.approved: Colors.green,
ButtonActions.requested: Colors.yellow, ButtonActions.requested: Colors.yellow,
ButtonActions.approve: Colors.blueAccent, ButtonActions.approve: Colors.green,
ButtonActions.reject: Colors.pink, ButtonActions.reject: Colors.red,
}; };
} }

View File

@ -25,7 +25,7 @@ class OrganizationSelector extends StatelessWidget {
required List<String> items, required List<String> items,
}) { }) {
return PopupMenuButton<String>( return PopupMenuButton<String>(
color: Colors.white, color: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
onSelected: (name) async { onSelected: (name) async {
Organization? org = name == "All Organizations" Organization? org = name == "All Organizations"
@ -45,8 +45,7 @@ class OrganizationSelector extends StatelessWidget {
height: height, height: height,
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color: Colors.white,
Colors.white,
border: Border.all(color: Colors.grey.shade300), border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
@ -97,6 +96,7 @@ class OrganizationSelector extends StatelessWidget {
...controller.organizations.map((e) => e.name) ...controller.organizations.map((e) => e.name)
]; ];
// Listen to selectedOrganization.value
return _popupSelector( return _popupSelector(
currentValue: controller.currentSelection, currentValue: controller.currentSelection,
items: orgNames, items: orgNames,

View File

@ -1,11 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:marco/controller/permission_controller.dart'; import 'package:marco/controller/permission_controller.dart';
import 'package:marco/controller/attendance/attendance_screen_controller.dart'; import 'package:marco/controller/attendance/attendance_screen_controller.dart';
import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_text.dart';
import 'package:marco/helpers/utils/permission_constants.dart'; import 'package:marco/helpers/utils/permission_constants.dart';
import 'package:marco/helpers/utils/base_bottom_sheet.dart'; import 'package:marco/helpers/utils/base_bottom_sheet.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:marco/helpers/utils/date_time_utils.dart';
class AttendanceFilterBottomSheet extends StatefulWidget { class AttendanceFilterBottomSheet extends StatefulWidget {
final AttendanceController controller; final AttendanceController controller;
@ -37,9 +37,11 @@ class _AttendanceFilterBottomSheetState
String getLabelText() { String getLabelText() {
final startDate = widget.controller.startDateAttendance; final startDate = widget.controller.startDateAttendance;
final endDate = widget.controller.endDateAttendance; final endDate = widget.controller.endDateAttendance;
if (startDate != null && endDate != null) { if (startDate != null && endDate != null) {
final start = DateFormat('dd/MM/yyyy').format(startDate); final start =
final end = DateFormat('dd/MM/yyyy').format(endDate); DateTimeUtils.formatDate(startDate, 'dd MMM yyyy');
final end = DateTimeUtils.formatDate(endDate, 'dd MMM yyyy');
return "$start - $end"; return "$start - $end";
} }
return "Date Range"; return "Date Range";
@ -96,7 +98,7 @@ class _AttendanceFilterBottomSheetState
onSelected: (name) { onSelected: (name) {
if (name == "All Organizations") { if (name == "All Organizations") {
setState(() { setState(() {
widget.controller.selectedOrganization = null; widget.controller.selectedOrganization = null;
}); });
} else { } else {
final selectedOrg = widget.controller.organizations final selectedOrg = widget.controller.organizations
@ -154,7 +156,7 @@ class _AttendanceFilterBottomSheetState
padding: const EdgeInsets.only(top: 12, bottom: 12), padding: const EdgeInsets.only(top: 12, bottom: 12),
child: Align( child: Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: MyText.titleSmall("Select Organization", fontWeight: 600), child: MyText.titleSmall("Choose Organization", fontWeight: 600),
), ),
), ),
Obx(() { Obx(() {
@ -231,7 +233,7 @@ class _AttendanceFilterBottomSheetState
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
child: BaseBottomSheet( child: BaseBottomSheet(
title: "Attendance Filter", title: "Attendance Filter",
submitText: "Apply", submitText: "Apply",
onCancel: () => Navigator.pop(context), onCancel: () => Navigator.pop(context),
onSubmit: () => Navigator.pop(context, { onSubmit: () => Navigator.pop(context, {
'selectedTab': tempSelectedTab, 'selectedTab': tempSelectedTab,

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_text.dart';
import 'package:marco/helpers/utils/attendance_actions.dart';
import 'package:marco/helpers/utils/base_bottom_sheet.dart'; import 'package:marco/helpers/utils/base_bottom_sheet.dart';
class AttendanceLogViewButton extends StatelessWidget { class AttendanceLogViewButton extends StatelessWidget {
@ -219,7 +218,7 @@ class AttendanceLogViewButton extends StatelessWidget {
child: ElevatedButton( child: ElevatedButton(
onPressed: () => _showLogsBottomSheet(context), onPressed: () => _showLogsBottomSheet(context),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AttendanceActionColors.colors[ButtonActions.checkIn], backgroundColor: Colors.indigo,
textStyle: const TextStyle(fontSize: 12), textStyle: const TextStyle(fontSize: 12),
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.symmetric(horizontal: 12),
), ),

View File

@ -48,7 +48,7 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
Future<void> _initEmployees() async { Future<void> _initEmployees() async {
final projectId = Get.find<ProjectController>().selectedProject?.id; final projectId = Get.find<ProjectController>().selectedProject?.id;
final orgId = _organizationController.selectedOrganization?.id; final orgId = _organizationController.selectedOrganization.value?.id;
if (projectId != null) { if (projectId != null) {
await _organizationController.fetchOrganizations(projectId); await _organizationController.fetchOrganizations(projectId);
@ -71,7 +71,7 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
Future<void> _refreshEmployees() async { Future<void> _refreshEmployees() async {
try { try {
final projectId = Get.find<ProjectController>().selectedProject?.id; final projectId = Get.find<ProjectController>().selectedProject?.id;
final orgId = _organizationController.selectedOrganization?.id; final orgId = _organizationController.selectedOrganization.value?.id;
final allSelected = _employeeController.isAllEmployeeSelected.value; final allSelected = _employeeController.isAllEmployeeSelected.value;
_employeeController.selectedProjectId = allSelected ? null : projectId; _employeeController.selectedProjectId = allSelected ? null : projectId;
@ -300,17 +300,23 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
controller: _organizationController, controller: _organizationController,
height: 36, height: 36,
onSelectionChanged: (org) async { onSelectionChanged: (org) async {
// Make sure the selectedOrganization is updated immediately
_organizationController.selectOrganization(org);
final projectId = final projectId =
Get.find<ProjectController>().selectedProject?.id; Get.find<ProjectController>().selectedProject?.id;
if (_employeeController.isAllEmployeeSelected.value) { if (_employeeController.isAllEmployeeSelected.value) {
await _employeeController.fetchAllEmployees( await _employeeController.fetchAllEmployees(
organizationId: org?.id); organizationId: _organizationController
.selectedOrganization.value?.id);
} else if (projectId != null) { } else if (projectId != null) {
await _employeeController.fetchEmployeesByProject( await _employeeController.fetchEmployeesByProject(
projectId, projectId,
organizationId: org?.id); organizationId: _organizationController
.selectedOrganization.value?.id);
} }
_employeeController.update(['employee_screen_controller']); _employeeController.update(['employee_screen_controller']);
}, },
), ),