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';
class OrganizationController extends GetxController {
/// List of organizations assigned to the selected project
List<Organization> organizations = [];
Organization? selectedOrganization;
/// Currently selected organization (reactive)
Rxn<Organization> selectedOrganization = Rxn<Organization>();
/// Loading state for fetching organizations
final isLoadingOrganizations = false.obs;
/// Fetch organizations assigned to a given project
Future<void> fetchOrganizations(String projectId) async {
try {
isLoadingOrganizations.value = true;
final response = await ApiService.getAssignedOrganizations(projectId);
if (response != null) {
if (response != null && response.data.isNotEmpty) {
organizations = response.data;
logSafe("Organizations fetched: ${organizations.length}");
} else {
logSafe("Failed to fetch organizations for project $projectId",
level: LogLevel.error);
organizations = [];
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 {
isLoadingOrganizations.value = false;
update();
}
}
/// Select an organization
void selectOrganization(Organization? org) {
selectedOrganization = org;
update();
selectedOrganization.value = org;
}
/// Clear the selection (set to "All Organizations")
void clearSelection() {
selectedOrganization = null;
update();
selectedOrganization.value = null;
}
/// Current selection name for UI
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.approved: Colors.green,
ButtonActions.requested: Colors.yellow,
ButtonActions.approve: Colors.blueAccent,
ButtonActions.reject: Colors.pink,
ButtonActions.approve: Colors.green,
ButtonActions.reject: Colors.red,
};
}

View File

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

View File

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

View File

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

View File

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