feat: Add organization selection and related API integration in attendance module
This commit is contained in:
parent
8d3c900262
commit
6863769b8a
@ -15,7 +15,7 @@ import 'package:marco/model/employees/employee_model.dart';
|
|||||||
import 'package:marco/model/attendance/attendance_log_model.dart';
|
import 'package:marco/model/attendance/attendance_log_model.dart';
|
||||||
import 'package:marco/model/regularization_log_model.dart';
|
import 'package:marco/model/regularization_log_model.dart';
|
||||||
import 'package:marco/model/attendance/attendance_log_view_model.dart';
|
import 'package:marco/model/attendance/attendance_log_view_model.dart';
|
||||||
|
import 'package:marco/model/attendance/organization_per_project_list_model.dart';
|
||||||
import 'package:marco/controller/project_controller.dart';
|
import 'package:marco/controller/project_controller.dart';
|
||||||
|
|
||||||
class AttendanceController extends GetxController {
|
class AttendanceController extends GetxController {
|
||||||
@ -26,9 +26,13 @@ class AttendanceController extends GetxController {
|
|||||||
List<AttendanceLogModel> attendanceLogs = [];
|
List<AttendanceLogModel> attendanceLogs = [];
|
||||||
List<RegularizationLogModel> regularizationLogs = [];
|
List<RegularizationLogModel> regularizationLogs = [];
|
||||||
List<AttendanceLogViewModel> attendenceLogsView = [];
|
List<AttendanceLogViewModel> attendenceLogsView = [];
|
||||||
|
// ------------------ Organizations ------------------
|
||||||
|
List<Organization> organizations = [];
|
||||||
|
Organization? selectedOrganization;
|
||||||
|
final isLoadingOrganizations = false.obs;
|
||||||
|
|
||||||
// States
|
// States
|
||||||
String selectedTab = 'Employee List';
|
String selectedTab = 'todaysAttendance';
|
||||||
DateTime? startDateAttendance;
|
DateTime? startDateAttendance;
|
||||||
DateTime? endDateAttendance;
|
DateTime? endDateAttendance;
|
||||||
|
|
||||||
@ -45,11 +49,16 @@ class AttendanceController extends GetxController {
|
|||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
_initializeDefaults();
|
_initializeDefaults();
|
||||||
|
|
||||||
|
// 🔹 Fetch organizations for the selected project
|
||||||
|
final projectId = Get.find<ProjectController>().selectedProject?.id;
|
||||||
|
if (projectId != null) {
|
||||||
|
fetchOrganizations(projectId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _initializeDefaults() {
|
void _initializeDefaults() {
|
||||||
_setDefaultDateRange();
|
_setDefaultDateRange();
|
||||||
fetchProjects();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setDefaultDateRange() {
|
void _setDefaultDateRange() {
|
||||||
@ -104,29 +113,15 @@ class AttendanceController extends GetxController {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fetchProjects() async {
|
Future<void> fetchTodaysAttendance(String? projectId) async {
|
||||||
isLoadingProjects.value = true;
|
|
||||||
|
|
||||||
final response = await ApiService.getProjects();
|
|
||||||
if (response != null && response.isNotEmpty) {
|
|
||||||
projects = response.map((e) => ProjectModel.fromJson(e)).toList();
|
|
||||||
logSafe("Projects fetched: ${projects.length}");
|
|
||||||
} else {
|
|
||||||
projects = [];
|
|
||||||
logSafe("Failed to fetch projects or no projects available.",
|
|
||||||
level: LogLevel.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoadingProjects.value = false;
|
|
||||||
update(['attendance_dashboard_controller']);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> fetchEmployeesByProject(String? projectId) async {
|
|
||||||
if (projectId == null) return;
|
if (projectId == null) return;
|
||||||
|
|
||||||
isLoadingEmployees.value = true;
|
isLoadingEmployees.value = true;
|
||||||
|
|
||||||
final response = await ApiService.getEmployeesByProject(projectId);
|
final response = await ApiService.getTodaysAttendance(
|
||||||
|
projectId,
|
||||||
|
organizationId: selectedOrganization?.id,
|
||||||
|
);
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
employees = response.map((e) => EmployeeModel.fromJson(e)).toList();
|
employees = response.map((e) => EmployeeModel.fromJson(e)).toList();
|
||||||
for (var emp in employees) {
|
for (var emp in employees) {
|
||||||
@ -141,6 +136,20 @@ class AttendanceController extends GetxController {
|
|||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> fetchOrganizations(String projectId) async {
|
||||||
|
isLoadingOrganizations.value = true;
|
||||||
|
final response = await ApiService.getAssignedOrganizations(projectId);
|
||||||
|
if (response != null) {
|
||||||
|
organizations = response.data;
|
||||||
|
logSafe("Organizations fetched: ${organizations.length}");
|
||||||
|
} else {
|
||||||
|
logSafe("Failed to fetch organizations for project $projectId",
|
||||||
|
level: LogLevel.error);
|
||||||
|
}
|
||||||
|
isLoadingOrganizations.value = false;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------ Attendance Capture ------------------
|
// ------------------ Attendance Capture ------------------
|
||||||
|
|
||||||
Future<bool> captureAndUploadAttendance(
|
Future<bool> captureAndUploadAttendance(
|
||||||
@ -262,8 +271,12 @@ class AttendanceController extends GetxController {
|
|||||||
|
|
||||||
isLoadingAttendanceLogs.value = true;
|
isLoadingAttendanceLogs.value = true;
|
||||||
|
|
||||||
final response = await ApiService.getAttendanceLogs(projectId,
|
final response = await ApiService.getAttendanceLogs(
|
||||||
dateFrom: dateFrom, dateTo: dateTo);
|
projectId,
|
||||||
|
dateFrom: dateFrom,
|
||||||
|
dateTo: dateTo,
|
||||||
|
organizationId: selectedOrganization?.id,
|
||||||
|
);
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
attendanceLogs =
|
attendanceLogs =
|
||||||
response.map((e) => AttendanceLogModel.fromJson(e)).toList();
|
response.map((e) => AttendanceLogModel.fromJson(e)).toList();
|
||||||
@ -306,7 +319,10 @@ class AttendanceController extends GetxController {
|
|||||||
|
|
||||||
isLoadingRegularizationLogs.value = true;
|
isLoadingRegularizationLogs.value = true;
|
||||||
|
|
||||||
final response = await ApiService.getRegularizationLogs(projectId);
|
final response = await ApiService.getRegularizationLogs(
|
||||||
|
projectId,
|
||||||
|
organizationId: selectedOrganization?.id,
|
||||||
|
);
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
regularizationLogs =
|
regularizationLogs =
|
||||||
response.map((e) => RegularizationLogModel.fromJson(e)).toList();
|
response.map((e) => RegularizationLogModel.fromJson(e)).toList();
|
||||||
@ -354,14 +370,28 @@ class AttendanceController extends GetxController {
|
|||||||
Future<void> fetchProjectData(String? projectId) async {
|
Future<void> fetchProjectData(String? projectId) async {
|
||||||
if (projectId == null) return;
|
if (projectId == null) return;
|
||||||
|
|
||||||
await Future.wait([
|
await fetchOrganizations(projectId);
|
||||||
fetchEmployeesByProject(projectId),
|
|
||||||
fetchAttendanceLogs(projectId,
|
|
||||||
dateFrom: startDateAttendance, dateTo: endDateAttendance),
|
|
||||||
fetchRegularizationLogs(projectId),
|
|
||||||
]);
|
|
||||||
|
|
||||||
logSafe("Project data fetched for project ID: $projectId");
|
// Call APIs depending on the selected tab only
|
||||||
|
switch (selectedTab) {
|
||||||
|
case 'todaysAttendance':
|
||||||
|
await fetchTodaysAttendance(projectId);
|
||||||
|
break;
|
||||||
|
case 'attendanceLogs':
|
||||||
|
await fetchAttendanceLogs(
|
||||||
|
projectId,
|
||||||
|
dateFrom: startDateAttendance,
|
||||||
|
dateTo: endDateAttendance,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'regularizationRequests':
|
||||||
|
await fetchRegularizationLogs(projectId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
logSafe(
|
||||||
|
"Project data fetched for project ID: $projectId, tab: $selectedTab");
|
||||||
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------ UI Interaction ------------------
|
// ------------------ UI Interaction ------------------
|
||||||
|
@ -14,7 +14,7 @@ class ApiEndpoints {
|
|||||||
// Attendance Module API Endpoints
|
// Attendance Module API Endpoints
|
||||||
static const String getProjects = "/project/list";
|
static const String getProjects = "/project/list";
|
||||||
static const String getGlobalProjects = "/project/list/basic";
|
static const String getGlobalProjects = "/project/list/basic";
|
||||||
static const String getEmployeesByProject = "/attendance/project/team";
|
static const String getTodaysAttendance = "/attendance/project/team";
|
||||||
static const String getAttendanceLogs = "/attendance/project/log";
|
static const String getAttendanceLogs = "/attendance/project/log";
|
||||||
static const String getAttendanceLogView = "/attendance/log/attendance";
|
static const String getAttendanceLogView = "/attendance/log/attendance";
|
||||||
static const String getRegularizationLogs = "/attendance/regularize";
|
static const String getRegularizationLogs = "/attendance/regularize";
|
||||||
@ -90,4 +90,7 @@ class ApiEndpoints {
|
|||||||
|
|
||||||
/// Logs Module API Endpoints
|
/// Logs Module API Endpoints
|
||||||
static const String uploadLogs = "/log";
|
static const String uploadLogs = "/log";
|
||||||
|
|
||||||
|
static const String getAssignedOrganizations =
|
||||||
|
"/project/get/assigned/organization";
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import 'package:marco/model/document/master_document_tags.dart';
|
|||||||
import 'package:marco/model/document/master_document_type_model.dart';
|
import 'package:marco/model/document/master_document_type_model.dart';
|
||||||
import 'package:marco/model/document/document_details_model.dart';
|
import 'package:marco/model/document/document_details_model.dart';
|
||||||
import 'package:marco/model/document/document_version_model.dart';
|
import 'package:marco/model/document/document_version_model.dart';
|
||||||
|
import 'package:marco/model/attendance/organization_per_project_list_model.dart';
|
||||||
|
|
||||||
class ApiService {
|
class ApiService {
|
||||||
static const Duration timeout = Duration(seconds: 30);
|
static const Duration timeout = Duration(seconds: 30);
|
||||||
@ -247,6 +248,36 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get Organizations assigned to a Project
|
||||||
|
static Future<OrganizationListResponse?> getAssignedOrganizations(
|
||||||
|
String projectId) async {
|
||||||
|
final endpoint = "${ApiEndpoints.getAssignedOrganizations}/$projectId";
|
||||||
|
logSafe("Fetching organizations assigned to projectId: $projectId");
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await _getRequest(endpoint);
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
logSafe("Assigned Organizations request failed: null response",
|
||||||
|
level: LogLevel.error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final jsonResponse =
|
||||||
|
_parseResponseForAllData(response, label: "Assigned Organizations");
|
||||||
|
|
||||||
|
if (jsonResponse != null) {
|
||||||
|
return OrganizationListResponse.fromJson(jsonResponse);
|
||||||
|
}
|
||||||
|
} catch (e, stack) {
|
||||||
|
logSafe("Exception during getAssignedOrganizations: $e",
|
||||||
|
level: LogLevel.error);
|
||||||
|
logSafe("StackTrace: $stack", level: LogLevel.debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
static Future<bool> postLogsApi(List<Map<String, dynamic>> logs) async {
|
static Future<bool> postLogsApi(List<Map<String, dynamic>> logs) async {
|
||||||
const endpoint = "${ApiEndpoints.uploadLogs}";
|
const endpoint = "${ApiEndpoints.uploadLogs}";
|
||||||
logSafe("Posting logs... count=${logs.length}");
|
logSafe("Posting logs... count=${logs.length}");
|
||||||
@ -1733,23 +1764,49 @@ class ApiService {
|
|||||||
_getRequest(ApiEndpoints.getGlobalProjects).then((res) =>
|
_getRequest(ApiEndpoints.getGlobalProjects).then((res) =>
|
||||||
res != null ? _parseResponse(res, label: 'Global Projects') : null);
|
res != null ? _parseResponse(res, label: 'Global Projects') : null);
|
||||||
|
|
||||||
static Future<List<dynamic>?> getEmployeesByProject(String projectId) async =>
|
static Future<List<dynamic>?> getTodaysAttendance(
|
||||||
_getRequest(ApiEndpoints.getEmployeesByProject,
|
String projectId, {
|
||||||
queryParams: {"projectId": projectId})
|
String? organizationId,
|
||||||
.then((res) =>
|
}) async {
|
||||||
res != null ? _parseResponse(res, label: 'Employees') : null);
|
final query = {
|
||||||
|
"projectId": projectId,
|
||||||
|
if (organizationId != null) "organizationId": organizationId,
|
||||||
|
};
|
||||||
|
|
||||||
|
return _getRequest(ApiEndpoints.getTodaysAttendance, queryParams: query)
|
||||||
|
.then((res) =>
|
||||||
|
res != null ? _parseResponse(res, label: 'Employees') : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<List<dynamic>?> getRegularizationLogs(
|
||||||
|
String projectId, {
|
||||||
|
String? organizationId,
|
||||||
|
}) async {
|
||||||
|
final query = {
|
||||||
|
"projectId": projectId,
|
||||||
|
if (organizationId != null) "organizationId": organizationId,
|
||||||
|
};
|
||||||
|
|
||||||
|
return _getRequest(ApiEndpoints.getRegularizationLogs, queryParams: query)
|
||||||
|
.then((res) => res != null
|
||||||
|
? _parseResponse(res, label: 'Regularization Logs')
|
||||||
|
: null);
|
||||||
|
}
|
||||||
|
|
||||||
static Future<List<dynamic>?> getAttendanceLogs(
|
static Future<List<dynamic>?> getAttendanceLogs(
|
||||||
String projectId, {
|
String projectId, {
|
||||||
DateTime? dateFrom,
|
DateTime? dateFrom,
|
||||||
DateTime? dateTo,
|
DateTime? dateTo,
|
||||||
|
String? organizationId,
|
||||||
}) async {
|
}) async {
|
||||||
final query = {
|
final query = {
|
||||||
"projectId": projectId,
|
"projectId": projectId,
|
||||||
if (dateFrom != null)
|
if (dateFrom != null)
|
||||||
"dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom),
|
"dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom),
|
||||||
if (dateTo != null) "dateTo": DateFormat('yyyy-MM-dd').format(dateTo),
|
if (dateTo != null) "dateTo": DateFormat('yyyy-MM-dd').format(dateTo),
|
||||||
|
if (organizationId != null) "organizationId": organizationId,
|
||||||
};
|
};
|
||||||
|
|
||||||
return _getRequest(ApiEndpoints.getAttendanceLogs, queryParams: query).then(
|
return _getRequest(ApiEndpoints.getAttendanceLogs, queryParams: query).then(
|
||||||
(res) =>
|
(res) =>
|
||||||
res != null ? _parseResponse(res, label: 'Attendance Logs') : null);
|
res != null ? _parseResponse(res, label: 'Attendance Logs') : null);
|
||||||
@ -1759,13 +1816,6 @@ class ApiService {
|
|||||||
_getRequest("${ApiEndpoints.getAttendanceLogView}/$id").then((res) =>
|
_getRequest("${ApiEndpoints.getAttendanceLogView}/$id").then((res) =>
|
||||||
res != null ? _parseResponse(res, label: 'Log Details') : null);
|
res != null ? _parseResponse(res, label: 'Log Details') : null);
|
||||||
|
|
||||||
static Future<List<dynamic>?> getRegularizationLogs(String projectId) async =>
|
|
||||||
_getRequest(ApiEndpoints.getRegularizationLogs,
|
|
||||||
queryParams: {"projectId": projectId})
|
|
||||||
.then((res) => res != null
|
|
||||||
? _parseResponse(res, label: 'Regularization Logs')
|
|
||||||
: null);
|
|
||||||
|
|
||||||
static Future<bool> uploadAttendanceImage(
|
static Future<bool> uploadAttendanceImage(
|
||||||
String id,
|
String id,
|
||||||
String employeeId,
|
String employeeId,
|
||||||
|
@ -193,7 +193,7 @@ class _AttendanceActionButtonState extends State<AttendanceActionButton> {
|
|||||||
controller.uploadingStates[uniqueLogKey]?.value = false;
|
controller.uploadingStates[uniqueLogKey]?.value = false;
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
await controller.fetchEmployeesByProject(selectedProjectId);
|
await controller.fetchTodaysAttendance(selectedProjectId);
|
||||||
await controller.fetchAttendanceLogs(selectedProjectId);
|
await controller.fetchAttendanceLogs(selectedProjectId);
|
||||||
await controller.fetchRegularizationLogs(selectedProjectId);
|
await controller.fetchRegularizationLogs(selectedProjectId);
|
||||||
await controller.fetchProjectData(selectedProjectId);
|
await controller.fetchProjectData(selectedProjectId);
|
||||||
|
@ -5,6 +5,7 @@ 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';
|
||||||
|
|
||||||
class AttendanceFilterBottomSheet extends StatefulWidget {
|
class AttendanceFilterBottomSheet extends StatefulWidget {
|
||||||
final AttendanceController controller;
|
final AttendanceController controller;
|
||||||
@ -44,6 +45,59 @@ class _AttendanceFilterBottomSheetState
|
|||||||
return "Date Range";
|
return "Date Range";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _popupSelector({
|
||||||
|
required String currentValue,
|
||||||
|
required List<String> items,
|
||||||
|
required ValueChanged<String> onSelected,
|
||||||
|
}) {
|
||||||
|
return PopupMenuButton<String>(
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
onSelected: onSelected,
|
||||||
|
itemBuilder: (context) => items
|
||||||
|
.map((e) => PopupMenuItem<String>(
|
||||||
|
value: e,
|
||||||
|
child: MyText(e),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade100,
|
||||||
|
border: Border.all(color: Colors.grey.shade300),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: MyText(
|
||||||
|
currentValue,
|
||||||
|
style: const TextStyle(color: Colors.black87),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Icon(Icons.arrow_drop_down, color: Colors.grey),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildOrganizationSelector(BuildContext context) {
|
||||||
|
return _popupSelector(
|
||||||
|
currentValue:
|
||||||
|
widget.controller.selectedOrganization?.name ?? "Select Organization",
|
||||||
|
items: widget.controller.organizations.map((e) => e.name).toList(),
|
||||||
|
onSelected: (name) {
|
||||||
|
final selectedOrg = widget.controller.organizations
|
||||||
|
.firstWhere((org) => org.name == name);
|
||||||
|
setState(() {
|
||||||
|
widget.controller.selectedOrganization = selectedOrg;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
List<Widget> buildMainFilters() {
|
List<Widget> buildMainFilters() {
|
||||||
final hasRegularizationPermission = widget.permissionController
|
final hasRegularizationPermission = widget.permissionController
|
||||||
.hasPermission(Permissions.regularizeAttendance);
|
.hasPermission(Permissions.regularizeAttendance);
|
||||||
@ -61,7 +115,7 @@ class _AttendanceFilterBottomSheetState
|
|||||||
|
|
||||||
final List<Widget> widgets = [
|
final List<Widget> widgets = [
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(bottom: 4),
|
padding: const EdgeInsets.only(bottom: 4),
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: MyText.titleSmall("View", fontWeight: 600),
|
child: MyText.titleSmall("View", fontWeight: 600),
|
||||||
@ -82,11 +136,41 @@ class _AttendanceFilterBottomSheetState
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 🔹 Organization filter
|
||||||
|
widgets.addAll([
|
||||||
|
const Divider(),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 12, bottom: 12),
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: MyText.titleSmall("Select Organization", fontWeight: 600),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Obx(() {
|
||||||
|
if (widget.controller.isLoadingOrganizations.value) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
} else if (widget.controller.organizations.isEmpty) {
|
||||||
|
return Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
|
child: MyText.bodyMedium(
|
||||||
|
"No organizations found",
|
||||||
|
fontWeight: 500,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _buildOrganizationSelector(context);
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 🔹 Date Range only for attendanceLogs
|
||||||
if (tempSelectedTab == 'attendanceLogs') {
|
if (tempSelectedTab == 'attendanceLogs') {
|
||||||
widgets.addAll([
|
widgets.addAll([
|
||||||
const Divider(),
|
const Divider(),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 12, bottom: 4),
|
padding: const EdgeInsets.only(top: 12, bottom: 4),
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: MyText.titleSmall("Date Range", fontWeight: 600),
|
child: MyText.titleSmall("Date Range", fontWeight: 600),
|
||||||
@ -99,7 +183,7 @@ class _AttendanceFilterBottomSheetState
|
|||||||
context,
|
context,
|
||||||
widget.controller,
|
widget.controller,
|
||||||
);
|
);
|
||||||
setState(() {}); // rebuild UI after date range is updated
|
setState(() {});
|
||||||
},
|
},
|
||||||
child: Ink(
|
child: Ink(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -139,6 +223,7 @@ class _AttendanceFilterBottomSheetState
|
|||||||
onCancel: () => Navigator.pop(context),
|
onCancel: () => Navigator.pop(context),
|
||||||
onSubmit: () => Navigator.pop(context, {
|
onSubmit: () => Navigator.pop(context, {
|
||||||
'selectedTab': tempSelectedTab,
|
'selectedTab': tempSelectedTab,
|
||||||
|
'selectedOrganization': widget.controller.selectedOrganization?.id,
|
||||||
}),
|
}),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
106
lib/model/attendance/organization_per_project_list_model.dart
Normal file
106
lib/model/attendance/organization_per_project_list_model.dart
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
class OrganizationListResponse {
|
||||||
|
final bool success;
|
||||||
|
final String message;
|
||||||
|
final List<Organization> data;
|
||||||
|
final dynamic errors;
|
||||||
|
final int statusCode;
|
||||||
|
final String timestamp;
|
||||||
|
|
||||||
|
OrganizationListResponse({
|
||||||
|
required this.success,
|
||||||
|
required this.message,
|
||||||
|
required this.data,
|
||||||
|
this.errors,
|
||||||
|
required this.statusCode,
|
||||||
|
required this.timestamp,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory OrganizationListResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return OrganizationListResponse(
|
||||||
|
success: json['success'] ?? false,
|
||||||
|
message: json['message'] ?? '',
|
||||||
|
data: (json['data'] as List<dynamic>?)
|
||||||
|
?.map((e) => Organization.fromJson(e))
|
||||||
|
.toList() ??
|
||||||
|
[],
|
||||||
|
errors: json['errors'],
|
||||||
|
statusCode: json['statusCode'] ?? 0,
|
||||||
|
timestamp: json['timestamp'] ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'success': success,
|
||||||
|
'message': message,
|
||||||
|
'data': data.map((e) => e.toJson()).toList(),
|
||||||
|
'errors': errors,
|
||||||
|
'statusCode': statusCode,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Organization {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final String email;
|
||||||
|
final String contactPerson;
|
||||||
|
final String address;
|
||||||
|
final String contactNumber;
|
||||||
|
final int sprid;
|
||||||
|
final String createdAt;
|
||||||
|
final dynamic createdBy;
|
||||||
|
final dynamic updatedBy;
|
||||||
|
final dynamic updatedAt;
|
||||||
|
final bool isActive;
|
||||||
|
|
||||||
|
Organization({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.email,
|
||||||
|
required this.contactPerson,
|
||||||
|
required this.address,
|
||||||
|
required this.contactNumber,
|
||||||
|
required this.sprid,
|
||||||
|
required this.createdAt,
|
||||||
|
this.createdBy,
|
||||||
|
this.updatedBy,
|
||||||
|
this.updatedAt,
|
||||||
|
required this.isActive,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Organization.fromJson(Map<String, dynamic> json) {
|
||||||
|
return Organization(
|
||||||
|
id: json['id'] ?? '',
|
||||||
|
name: json['name'] ?? '',
|
||||||
|
email: json['email'] ?? '',
|
||||||
|
contactPerson: json['contactPerson'] ?? '',
|
||||||
|
address: json['address'] ?? '',
|
||||||
|
contactNumber: json['contactNumber'] ?? '',
|
||||||
|
sprid: json['sprid'] ?? 0,
|
||||||
|
createdAt: json['createdAt'] ?? '',
|
||||||
|
createdBy: json['createdBy'],
|
||||||
|
updatedBy: json['updatedBy'],
|
||||||
|
updatedAt: json['updatedAt'],
|
||||||
|
isActive: json['isActive'] ?? false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
'email': email,
|
||||||
|
'contactPerson': contactPerson,
|
||||||
|
'address': address,
|
||||||
|
'contactNumber': contactNumber,
|
||||||
|
'sprid': sprid,
|
||||||
|
'createdAt': createdAt,
|
||||||
|
'createdBy': createdBy,
|
||||||
|
'updatedBy': updatedBy,
|
||||||
|
'updatedAt': updatedAt,
|
||||||
|
'isActive': isActive,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -47,6 +47,7 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
|
|||||||
|
|
||||||
Future<void> _loadData(String projectId) async {
|
Future<void> _loadData(String projectId) async {
|
||||||
try {
|
try {
|
||||||
|
attendanceController.selectedTab = 'todaysAttendance';
|
||||||
await attendanceController.loadAttendanceData(projectId);
|
await attendanceController.loadAttendanceData(projectId);
|
||||||
attendanceController.update(['attendance_dashboard_controller']);
|
attendanceController.update(['attendance_dashboard_controller']);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -56,7 +57,24 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
|
|||||||
|
|
||||||
Future<void> _refreshData() async {
|
Future<void> _refreshData() async {
|
||||||
final projectId = projectController.selectedProjectId.value;
|
final projectId = projectController.selectedProjectId.value;
|
||||||
if (projectId.isNotEmpty) await _loadData(projectId);
|
if (projectId.isEmpty) return;
|
||||||
|
|
||||||
|
// Call only the relevant API for current tab
|
||||||
|
switch (selectedTab) {
|
||||||
|
case 'todaysAttendance':
|
||||||
|
await attendanceController.fetchTodaysAttendance(projectId);
|
||||||
|
break;
|
||||||
|
case 'attendanceLogs':
|
||||||
|
await attendanceController.fetchAttendanceLogs(
|
||||||
|
projectId,
|
||||||
|
dateFrom: attendanceController.startDateAttendance,
|
||||||
|
dateTo: attendanceController.endDateAttendance,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'regularizationRequests':
|
||||||
|
await attendanceController.fetchRegularizationLogs(projectId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildAppBar() {
|
Widget _buildAppBar() {
|
||||||
@ -195,15 +213,26 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
|
|||||||
final selectedProjectId =
|
final selectedProjectId =
|
||||||
projectController.selectedProjectId.value;
|
projectController.selectedProjectId.value;
|
||||||
final selectedView = result['selectedTab'] as String?;
|
final selectedView = result['selectedTab'] as String?;
|
||||||
|
final selectedOrgId =
|
||||||
|
result['selectedOrganization'] as String?;
|
||||||
|
|
||||||
|
if (selectedOrgId != null) {
|
||||||
|
attendanceController.selectedOrganization =
|
||||||
|
attendanceController.organizations
|
||||||
|
.firstWhere((o) => o.id == selectedOrgId);
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedProjectId.isNotEmpty) {
|
if (selectedProjectId.isNotEmpty) {
|
||||||
try {
|
try {
|
||||||
await attendanceController
|
await attendanceController.fetchTodaysAttendance(
|
||||||
.fetchEmployeesByProject(selectedProjectId);
|
selectedProjectId,
|
||||||
await attendanceController
|
);
|
||||||
.fetchAttendanceLogs(selectedProjectId);
|
await attendanceController.fetchAttendanceLogs(
|
||||||
await attendanceController
|
selectedProjectId,
|
||||||
.fetchRegularizationLogs(selectedProjectId);
|
);
|
||||||
|
await attendanceController.fetchRegularizationLogs(
|
||||||
|
selectedProjectId,
|
||||||
|
);
|
||||||
await attendanceController
|
await attendanceController
|
||||||
.fetchProjectData(selectedProjectId);
|
.fetchProjectData(selectedProjectId);
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
@ -214,6 +243,11 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
|
|||||||
|
|
||||||
if (selectedView != null && selectedView != selectedTab) {
|
if (selectedView != null && selectedView != selectedTab) {
|
||||||
setState(() => selectedTab = selectedView);
|
setState(() => selectedTab = selectedView);
|
||||||
|
attendanceController.selectedTab = selectedView;
|
||||||
|
if (selectedProjectId.isNotEmpty) {
|
||||||
|
await attendanceController
|
||||||
|
.fetchProjectData(selectedProjectId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -408,4 +408,5 @@ class ExpenseFilterBottomSheet extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user