Merge pull request 'feat: Enhance ReportTaskController with image picking and form field management' (#34) from Vaibhav_Bug-#411 into main
Reviewed-on: #34
This commit is contained in:
commit
d9ad7581bf
@ -1,41 +1,49 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:geolocator/geolocator.dart';
|
import 'package:geolocator/geolocator.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:logger/logger.dart';
|
import 'package:logger/logger.dart';
|
||||||
import 'dart:io';
|
|
||||||
import 'package:marco/helpers/services/api_service.dart';
|
import 'package:marco/helpers/services/api_service.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_image_compressor.dart';
|
||||||
import 'package:marco/model/attendance_model.dart';
|
import 'package:marco/model/attendance_model.dart';
|
||||||
import 'package:marco/model/project_model.dart';
|
import 'package:marco/model/project_model.dart';
|
||||||
import 'package:marco/model/employee_model.dart';
|
import 'package:marco/model/employee_model.dart';
|
||||||
import 'package:marco/model/attendance_log_model.dart';
|
import 'package:marco/model/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_log_view_model.dart';
|
import 'package:marco/model/attendance_log_view_model.dart';
|
||||||
import 'package:marco/helpers/widgets/my_image_compressor.dart';
|
|
||||||
|
|
||||||
final Logger log = Logger();
|
final Logger log = Logger();
|
||||||
|
|
||||||
class AttendanceController extends GetxController {
|
class AttendanceController extends GetxController {
|
||||||
|
// Data lists
|
||||||
List<AttendanceModel> attendances = [];
|
List<AttendanceModel> attendances = [];
|
||||||
List<ProjectModel> projects = [];
|
List<ProjectModel> projects = [];
|
||||||
String? selectedProjectId;
|
|
||||||
List<EmployeeModel> employees = [];
|
List<EmployeeModel> employees = [];
|
||||||
String selectedTab = 'Employee List';
|
|
||||||
DateTime? startDateAttendance;
|
|
||||||
DateTime? endDateAttendance;
|
|
||||||
|
|
||||||
List<AttendanceLogModel> attendanceLogs = [];
|
List<AttendanceLogModel> attendanceLogs = [];
|
||||||
List<RegularizationLogModel> regularizationLogs = [];
|
List<RegularizationLogModel> regularizationLogs = [];
|
||||||
List<AttendanceLogViewModel> attendenceLogsView = [];
|
List<AttendanceLogViewModel> attendenceLogsView = [];
|
||||||
|
|
||||||
RxBool isLoading = true.obs; // initially true
|
// Selected values
|
||||||
|
String? selectedProjectId;
|
||||||
|
String selectedTab = 'Employee List';
|
||||||
|
|
||||||
|
// Date range for attendance filtering
|
||||||
|
DateTime? startDateAttendance;
|
||||||
|
DateTime? endDateAttendance;
|
||||||
|
|
||||||
|
// Loading states
|
||||||
|
RxBool isLoading = true.obs;
|
||||||
RxBool isLoadingProjects = true.obs;
|
RxBool isLoadingProjects = true.obs;
|
||||||
RxBool isLoadingEmployees = true.obs;
|
RxBool isLoadingEmployees = true.obs;
|
||||||
RxBool isLoadingAttendanceLogs = true.obs;
|
RxBool isLoadingAttendanceLogs = true.obs;
|
||||||
RxBool isLoadingRegularizationLogs = true.obs;
|
RxBool isLoadingRegularizationLogs = true.obs;
|
||||||
RxBool isLoadingLogView = true.obs;
|
RxBool isLoadingLogView = true.obs;
|
||||||
|
|
||||||
|
// Uploading state per employee (keyed by employeeId)
|
||||||
RxMap<String, RxBool> uploadingStates = <String, RxBool>{}.obs;
|
RxMap<String, RxBool> uploadingStates = <String, RxBool>{}.obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -46,7 +54,7 @@ class AttendanceController extends GetxController {
|
|||||||
|
|
||||||
void _initializeDefaults() {
|
void _initializeDefaults() {
|
||||||
_setDefaultDateRange();
|
_setDefaultDateRange();
|
||||||
fetchProjects(); // fetchProjects will set isLoading to false after loading
|
fetchProjects();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setDefaultDateRange() {
|
void _setDefaultDateRange() {
|
||||||
@ -56,8 +64,10 @@ class AttendanceController extends GetxController {
|
|||||||
log.i("Default date range set: $startDateAttendance to $endDateAttendance");
|
log.i("Default date range set: $startDateAttendance to $endDateAttendance");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks and requests location permission, returns true if granted.
|
||||||
Future<bool> _handleLocationPermission() async {
|
Future<bool> _handleLocationPermission() async {
|
||||||
LocationPermission permission = await Geolocator.checkPermission();
|
LocationPermission permission = await Geolocator.checkPermission();
|
||||||
|
|
||||||
if (permission == LocationPermission.denied) {
|
if (permission == LocationPermission.denied) {
|
||||||
permission = await Geolocator.requestPermission();
|
permission = await Geolocator.requestPermission();
|
||||||
if (permission == LocationPermission.denied) {
|
if (permission == LocationPermission.denied) {
|
||||||
@ -65,13 +75,16 @@ class AttendanceController extends GetxController {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (permission == LocationPermission.deniedForever) {
|
if (permission == LocationPermission.deniedForever) {
|
||||||
log.e('Location permissions are permanently denied');
|
log.e('Location permissions are permanently denied');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetches projects and initializes selected project.
|
||||||
Future<void> fetchProjects() async {
|
Future<void> fetchProjects() async {
|
||||||
isLoadingProjects.value = true;
|
isLoadingProjects.value = true;
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
@ -81,11 +94,11 @@ class AttendanceController extends GetxController {
|
|||||||
if (response != null && response.isNotEmpty) {
|
if (response != null && response.isNotEmpty) {
|
||||||
projects = response.map((json) => ProjectModel.fromJson(json)).toList();
|
projects = response.map((json) => ProjectModel.fromJson(json)).toList();
|
||||||
selectedProjectId = projects.first.id.toString();
|
selectedProjectId = projects.first.id.toString();
|
||||||
log.i("Projects fetched: ${projects.length} projects loaded.");
|
log.i("Projects fetched: ${projects.length}");
|
||||||
|
|
||||||
await fetchProjectData(selectedProjectId);
|
await fetchProjectData(selectedProjectId);
|
||||||
} else {
|
} else {
|
||||||
log.w("No project data found or API call failed.");
|
log.w("No projects found or API call failed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoadingProjects.value = false;
|
isLoadingProjects.value = false;
|
||||||
@ -94,6 +107,7 @@ class AttendanceController extends GetxController {
|
|||||||
update(['attendance_dashboard_controller']);
|
update(['attendance_dashboard_controller']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetches employees, attendance logs and regularization logs for a project.
|
||||||
Future<void> fetchProjectData(String? projectId) async {
|
Future<void> fetchProjectData(String? projectId) async {
|
||||||
if (projectId == null) return;
|
if (projectId == null) return;
|
||||||
|
|
||||||
@ -107,9 +121,11 @@ class AttendanceController extends GetxController {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
|
|
||||||
log.i("Project data fetched for project ID: $projectId");
|
log.i("Project data fetched for project ID: $projectId");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetches employees for the given project.
|
||||||
Future<void> fetchEmployeesByProject(String? projectId) async {
|
Future<void> fetchEmployeesByProject(String? projectId) async {
|
||||||
if (projectId == null) return;
|
if (projectId == null) return;
|
||||||
|
|
||||||
@ -120,18 +136,22 @@ class AttendanceController extends GetxController {
|
|||||||
if (response != null) {
|
if (response != null) {
|
||||||
employees = response.map((json) => EmployeeModel.fromJson(json)).toList();
|
employees = response.map((json) => EmployeeModel.fromJson(json)).toList();
|
||||||
|
|
||||||
|
// Initialize uploading states for employees
|
||||||
for (var emp in employees) {
|
for (var emp in employees) {
|
||||||
uploadingStates[emp.id] = false.obs;
|
uploadingStates[emp.id] = false.obs;
|
||||||
}
|
}
|
||||||
log.i(
|
|
||||||
"Employees fetched: ${employees.length} employees for project $projectId");
|
log.i("Employees fetched: ${employees.length} for project $projectId");
|
||||||
update();
|
update();
|
||||||
} else {
|
} else {
|
||||||
log.e("Failed to fetch employees for project $projectId");
|
log.e("Failed to fetch employees for project $projectId");
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoadingEmployees.value = false;
|
isLoadingEmployees.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Captures image, gets location, and uploads attendance data.
|
||||||
|
/// Returns true on success.
|
||||||
Future<bool> captureAndUploadAttendance(
|
Future<bool> captureAndUploadAttendance(
|
||||||
String id,
|
String id,
|
||||||
String employeeId,
|
String employeeId,
|
||||||
@ -155,8 +175,8 @@ class AttendanceController extends GetxController {
|
|||||||
uploadingStates[employeeId]?.value = false;
|
uploadingStates[employeeId]?.value = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final compressedBytes =
|
|
||||||
await compressImageToUnder100KB(File(image.path));
|
final compressedBytes = await compressImageToUnder100KB(File(image.path));
|
||||||
if (compressedBytes == null) {
|
if (compressedBytes == null) {
|
||||||
log.e("Image compression failed.");
|
log.e("Image compression failed.");
|
||||||
uploadingStates[employeeId]?.value = false;
|
uploadingStates[employeeId]?.value = false;
|
||||||
@ -205,6 +225,7 @@ class AttendanceController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opens a date range picker for attendance filtering and fetches logs on selection.
|
||||||
Future<void> selectDateRangeForAttendance(
|
Future<void> selectDateRangeForAttendance(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
AttendanceController controller,
|
AttendanceController controller,
|
||||||
@ -217,18 +238,10 @@ class AttendanceController extends GetxController {
|
|||||||
firstDate: DateTime(2022),
|
firstDate: DateTime(2022),
|
||||||
lastDate: todayDateOnly.subtract(const Duration(days: 1)),
|
lastDate: todayDateOnly.subtract(const Duration(days: 1)),
|
||||||
initialDateRange: DateTimeRange(
|
initialDateRange: DateTimeRange(
|
||||||
start: startDateAttendance ??
|
start: startDateAttendance ?? today.subtract(const Duration(days: 7)),
|
||||||
DateTime.now().subtract(const Duration(days: 7)),
|
end: endDateAttendance ?? todayDateOnly.subtract(const Duration(days: 1)),
|
||||||
end: endDateAttendance ??
|
|
||||||
todayDateOnly.subtract(const Duration(days: 1)),
|
|
||||||
),
|
),
|
||||||
selectableDayPredicate: (DateTime day, DateTime? start, DateTime? end) {
|
|
||||||
final dayDateOnly = DateTime(day.year, day.month, day.day);
|
|
||||||
if (dayDateOnly == todayDateOnly) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
builder: (BuildContext context, Widget? child) {
|
builder: (BuildContext context, Widget? child) {
|
||||||
return Center(
|
return Center(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
@ -242,13 +255,9 @@ class AttendanceController extends GetxController {
|
|||||||
onSurface: Colors.teal.shade800,
|
onSurface: Colors.teal.shade800,
|
||||||
),
|
),
|
||||||
textButtonTheme: TextButtonThemeData(
|
textButtonTheme: TextButtonThemeData(
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(foregroundColor: Colors.teal),
|
||||||
foregroundColor: Colors.teal,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
dialogTheme: DialogTheme(
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
),
|
),
|
||||||
|
dialogTheme: const DialogTheme(backgroundColor: Colors.white),
|
||||||
),
|
),
|
||||||
child: child!,
|
child: child!,
|
||||||
),
|
),
|
||||||
@ -256,6 +265,7 @@ class AttendanceController extends GetxController {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (picked != null) {
|
if (picked != null) {
|
||||||
startDateAttendance = picked.start;
|
startDateAttendance = picked.start;
|
||||||
endDateAttendance = picked.end;
|
endDateAttendance = picked.end;
|
||||||
@ -270,6 +280,7 @@ class AttendanceController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetches attendance logs filtered by project and date range.
|
||||||
Future<void> fetchAttendanceLogs(
|
Future<void> fetchAttendanceLogs(
|
||||||
String? projectId, {
|
String? projectId, {
|
||||||
DateTime? dateFrom,
|
DateTime? dateFrom,
|
||||||
@ -294,10 +305,12 @@ class AttendanceController extends GetxController {
|
|||||||
} else {
|
} else {
|
||||||
log.e("Failed to fetch attendance logs for project $projectId");
|
log.e("Failed to fetch attendance logs for project $projectId");
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoadingAttendanceLogs.value = false;
|
isLoadingAttendanceLogs.value = false;
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Groups attendance logs by check-in date formatted as 'dd MMM yyyy'.
|
||||||
Map<String, List<AttendanceLogModel>> groupLogsByCheckInDate() {
|
Map<String, List<AttendanceLogModel>> groupLogsByCheckInDate() {
|
||||||
final groupedLogs = <String, List<AttendanceLogModel>>{};
|
final groupedLogs = <String, List<AttendanceLogModel>>{};
|
||||||
|
|
||||||
@ -309,6 +322,7 @@ class AttendanceController extends GetxController {
|
|||||||
groupedLogs.putIfAbsent(checkInDate, () => []);
|
groupedLogs.putIfAbsent(checkInDate, () => []);
|
||||||
groupedLogs[checkInDate]!.add(logItem);
|
groupedLogs[checkInDate]!.add(logItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
final sortedEntries = groupedLogs.entries.toList()
|
final sortedEntries = groupedLogs.entries.toList()
|
||||||
..sort((a, b) {
|
..sort((a, b) {
|
||||||
if (a.key == 'Unknown') return 1;
|
if (a.key == 'Unknown') return 1;
|
||||||
@ -318,13 +332,13 @@ class AttendanceController extends GetxController {
|
|||||||
return dateB.compareTo(dateA);
|
return dateB.compareTo(dateA);
|
||||||
});
|
});
|
||||||
|
|
||||||
final sortedMap =
|
final sortedMap = Map<String, List<AttendanceLogModel>>.fromEntries(sortedEntries);
|
||||||
Map<String, List<AttendanceLogModel>>.fromEntries(sortedEntries);
|
|
||||||
|
|
||||||
log.i("Logs grouped and sorted by check-in date.");
|
log.i("Logs grouped and sorted by check-in date.");
|
||||||
return sortedMap;
|
return sortedMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetches regularization logs for a project.
|
||||||
Future<void> fetchRegularizationLogs(
|
Future<void> fetchRegularizationLogs(
|
||||||
String? projectId, {
|
String? projectId, {
|
||||||
DateTime? dateFrom,
|
DateTime? dateFrom,
|
||||||
@ -338,18 +352,19 @@ class AttendanceController extends GetxController {
|
|||||||
final response = await ApiService.getRegularizationLogs(projectId);
|
final response = await ApiService.getRegularizationLogs(projectId);
|
||||||
|
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
regularizationLogs = response
|
regularizationLogs =
|
||||||
.map((json) => RegularizationLogModel.fromJson(json))
|
response.map((json) => RegularizationLogModel.fromJson(json)).toList();
|
||||||
.toList();
|
|
||||||
log.i("Regularization logs fetched: ${regularizationLogs.length}");
|
log.i("Regularization logs fetched: ${regularizationLogs.length}");
|
||||||
update();
|
update();
|
||||||
} else {
|
} else {
|
||||||
log.e("Failed to fetch regularization logs for project $projectId");
|
log.e("Failed to fetch regularization logs for project $projectId");
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoadingRegularizationLogs.value = false;
|
isLoadingRegularizationLogs.value = false;
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetches detailed attendance log view for a specific ID.
|
||||||
Future<void> fetchLogsView(String? id) async {
|
Future<void> fetchLogsView(String? id) async {
|
||||||
if (id == null) return;
|
if (id == null) return;
|
||||||
|
|
||||||
@ -359,9 +374,8 @@ class AttendanceController extends GetxController {
|
|||||||
final response = await ApiService.getAttendanceLogView(id);
|
final response = await ApiService.getAttendanceLogView(id);
|
||||||
|
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
attendenceLogsView = response
|
attendenceLogsView =
|
||||||
.map((json) => AttendanceLogViewModel.fromJson(json))
|
response.map((json) => AttendanceLogViewModel.fromJson(json)).toList();
|
||||||
.toList();
|
|
||||||
|
|
||||||
attendenceLogsView.sort((a, b) {
|
attendenceLogsView.sort((a, b) {
|
||||||
if (a.activityTime == null || b.activityTime == null) return 0;
|
if (a.activityTime == null || b.activityTime == null) return 0;
|
||||||
|
|||||||
@ -7,11 +7,17 @@ import 'package:get/get.dart';
|
|||||||
import 'package:logger/logger.dart';
|
import 'package:logger/logger.dart';
|
||||||
import 'package:marco/helpers/widgets/my_snackbar.dart';
|
import 'package:marco/helpers/widgets/my_snackbar.dart';
|
||||||
import 'package:marco/controller/task_planing/daily_task_planing_controller.dart';
|
import 'package:marco/controller/task_planing/daily_task_planing_controller.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
final Logger logger = Logger();
|
final Logger logger = Logger();
|
||||||
|
|
||||||
enum ApiStatus { idle, loading, success, failure }
|
enum ApiStatus { idle, loading, success, failure }
|
||||||
final DailyTaskPlaningController taskController = Get.put(DailyTaskPlaningController());
|
|
||||||
|
final DailyTaskPlaningController taskController =
|
||||||
|
Get.put(DailyTaskPlaningController());
|
||||||
|
final ImagePicker _picker = ImagePicker();
|
||||||
|
|
||||||
class ReportTaskController extends MyController {
|
class ReportTaskController extends MyController {
|
||||||
List<PlatformFile> files = [];
|
List<PlatformFile> files = [];
|
||||||
MyFormValidator basicValidator = MyFormValidator();
|
MyFormValidator basicValidator = MyFormValidator();
|
||||||
@ -19,74 +25,71 @@ class ReportTaskController extends MyController {
|
|||||||
Rx<ApiStatus> reportStatus = ApiStatus.idle.obs;
|
Rx<ApiStatus> reportStatus = ApiStatus.idle.obs;
|
||||||
Rx<ApiStatus> commentStatus = ApiStatus.idle.obs;
|
Rx<ApiStatus> commentStatus = ApiStatus.idle.obs;
|
||||||
|
|
||||||
|
RxList<File> selectedImages = <File>[].obs;
|
||||||
|
|
||||||
|
// Controllers for each form field
|
||||||
|
final assignedDateController = TextEditingController();
|
||||||
|
final workAreaController = TextEditingController();
|
||||||
|
final activityController = TextEditingController();
|
||||||
|
final teamSizeController = TextEditingController();
|
||||||
|
final taskIdController = TextEditingController();
|
||||||
|
final assignedController = TextEditingController();
|
||||||
|
final completedWorkController = TextEditingController();
|
||||||
|
final commentController = TextEditingController();
|
||||||
|
final assignedByController = TextEditingController();
|
||||||
|
final teamMembersController = TextEditingController();
|
||||||
|
final plannedWorkController = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
logger.i("Initializing ReportTaskController...");
|
logger.i("Initializing ReportTaskController...");
|
||||||
|
|
||||||
// Add form fields to the validator
|
basicValidator.addField('assigned_date',
|
||||||
basicValidator.addField(
|
label: "Assigned Date", controller: assignedDateController);
|
||||||
'assigned_date',
|
basicValidator.addField('work_area',
|
||||||
label: "Assigned Date",
|
label: "Work Area", controller: workAreaController);
|
||||||
controller: TextEditingController(),
|
basicValidator.addField('activity',
|
||||||
);
|
label: "Activity", controller: activityController);
|
||||||
basicValidator.addField(
|
basicValidator.addField('team_size',
|
||||||
'work_area',
|
label: "Team Size", controller: teamSizeController);
|
||||||
label: "Work Area",
|
basicValidator.addField('task_id',
|
||||||
controller: TextEditingController(),
|
label: "Task Id", controller: taskIdController);
|
||||||
);
|
basicValidator.addField('assigned',
|
||||||
basicValidator.addField(
|
label: "Assigned", controller: assignedController);
|
||||||
'activity',
|
basicValidator.addField('completed_work',
|
||||||
label: "Activity",
|
label: "Completed Work",
|
||||||
controller: TextEditingController(),
|
required: true,
|
||||||
);
|
controller: completedWorkController);
|
||||||
basicValidator.addField(
|
basicValidator.addField('comment',
|
||||||
'team_size',
|
label: "Comment", required: true, controller: commentController);
|
||||||
label: "Team Size",
|
basicValidator.addField('assigned_by',
|
||||||
controller: TextEditingController(),
|
label: "Assigned By", controller: assignedByController);
|
||||||
);
|
basicValidator.addField('team_members',
|
||||||
basicValidator.addField(
|
label: "Team Members", controller: teamMembersController);
|
||||||
'task_id',
|
basicValidator.addField('planned_work',
|
||||||
label: "Task Id",
|
label: "Planned Work", controller: plannedWorkController);
|
||||||
controller: TextEditingController(),
|
|
||||||
);
|
|
||||||
basicValidator.addField(
|
|
||||||
'assigned',
|
|
||||||
label: "Assigned",
|
|
||||||
controller: TextEditingController(),
|
|
||||||
);
|
|
||||||
basicValidator.addField(
|
|
||||||
'completed_work',
|
|
||||||
label: "Completed Work",
|
|
||||||
required: true,
|
|
||||||
controller: TextEditingController(),
|
|
||||||
);
|
|
||||||
basicValidator.addField(
|
|
||||||
'comment',
|
|
||||||
label: "Comment",
|
|
||||||
required: true,
|
|
||||||
controller: TextEditingController(),
|
|
||||||
);
|
|
||||||
basicValidator.addField(
|
|
||||||
'assigned_by',
|
|
||||||
label: "Assigned By",
|
|
||||||
controller: TextEditingController(),
|
|
||||||
);
|
|
||||||
basicValidator.addField(
|
|
||||||
'team_members',
|
|
||||||
label: "Team Members",
|
|
||||||
controller: TextEditingController(),
|
|
||||||
);
|
|
||||||
basicValidator.addField(
|
|
||||||
'planned_work',
|
|
||||||
label: "Planned Work",
|
|
||||||
controller: TextEditingController(),
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.i(
|
logger.i(
|
||||||
"Fields initialized for assigned_date, work_area, activity, team_size, assigned, completed_work, and comment.");
|
"Fields initialized for assigned_date, work_area, activity, team_size, assigned, completed_work, and comment.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClose() {
|
||||||
|
assignedDateController.dispose();
|
||||||
|
workAreaController.dispose();
|
||||||
|
activityController.dispose();
|
||||||
|
teamSizeController.dispose();
|
||||||
|
taskIdController.dispose();
|
||||||
|
assignedController.dispose();
|
||||||
|
completedWorkController.dispose();
|
||||||
|
commentController.dispose();
|
||||||
|
assignedByController.dispose();
|
||||||
|
teamMembersController.dispose();
|
||||||
|
plannedWorkController.dispose();
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> reportTask({
|
Future<void> reportTask({
|
||||||
required String projectId,
|
required String projectId,
|
||||||
required String comment,
|
required String comment,
|
||||||
@ -96,10 +99,9 @@ class ReportTaskController extends MyController {
|
|||||||
}) async {
|
}) async {
|
||||||
logger.i("Starting task report...");
|
logger.i("Starting task report...");
|
||||||
|
|
||||||
final completedWork =
|
final completedWork = completedWorkController.text.trim();
|
||||||
basicValidator.getController('completed_work')?.text.trim();
|
|
||||||
|
|
||||||
if (completedWork == null || completedWork.isEmpty) {
|
if (completedWork.isEmpty) {
|
||||||
showAppSnackbar(
|
showAppSnackbar(
|
||||||
title: "Error",
|
title: "Error",
|
||||||
message: "Completed work is required.",
|
message: "Completed work is required.",
|
||||||
@ -107,6 +109,7 @@ class ReportTaskController extends MyController {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final completedWorkInt = int.tryParse(completedWork);
|
final completedWorkInt = int.tryParse(completedWork);
|
||||||
if (completedWorkInt == null || completedWorkInt <= 0) {
|
if (completedWorkInt == null || completedWorkInt <= 0) {
|
||||||
showAppSnackbar(
|
showAppSnackbar(
|
||||||
@ -116,9 +119,9 @@ class ReportTaskController extends MyController {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final commentField = basicValidator.getController('comment')?.text.trim();
|
|
||||||
|
|
||||||
if (commentField == null || commentField.isEmpty) {
|
final commentField = commentController.text.trim();
|
||||||
|
if (commentField.isEmpty) {
|
||||||
showAppSnackbar(
|
showAppSnackbar(
|
||||||
title: "Error",
|
title: "Error",
|
||||||
message: "Comment is required.",
|
message: "Comment is required.",
|
||||||
@ -143,6 +146,7 @@ class ReportTaskController extends MyController {
|
|||||||
message: "Task reported successfully!",
|
message: "Task reported successfully!",
|
||||||
type: SnackbarType.success,
|
type: SnackbarType.success,
|
||||||
);
|
);
|
||||||
|
await taskController.fetchTaskData(projectId);
|
||||||
} else {
|
} else {
|
||||||
showAppSnackbar(
|
showAppSnackbar(
|
||||||
title: "Error",
|
title: "Error",
|
||||||
@ -168,9 +172,8 @@ class ReportTaskController extends MyController {
|
|||||||
}) async {
|
}) async {
|
||||||
logger.i("Starting task comment...");
|
logger.i("Starting task comment...");
|
||||||
|
|
||||||
final commentField = basicValidator.getController('comment')?.text.trim();
|
final commentField = commentController.text.trim();
|
||||||
|
if (commentField.isEmpty) {
|
||||||
if (commentField == null || commentField.isEmpty) {
|
|
||||||
showAppSnackbar(
|
showAppSnackbar(
|
||||||
title: "Error",
|
title: "Error",
|
||||||
message: "Comment is required.",
|
message: "Comment is required.",
|
||||||
@ -213,4 +216,27 @@ class ReportTaskController extends MyController {
|
|||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> pickImages({required bool fromCamera}) async {
|
||||||
|
if (fromCamera) {
|
||||||
|
final pickedFile = await _picker.pickImage(
|
||||||
|
source: ImageSource.camera,
|
||||||
|
imageQuality: 75,
|
||||||
|
);
|
||||||
|
if (pickedFile != null) {
|
||||||
|
selectedImages.add(File(pickedFile.path));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final pickedFiles = await _picker.pickMultiImage(imageQuality: 75);
|
||||||
|
if (pickedFiles != null && pickedFiles.isNotEmpty) {
|
||||||
|
selectedImages.addAll(pickedFiles.map((xfile) => File(xfile.path)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeImageAt(int index) {
|
||||||
|
if (index >= 0 && index < selectedImages.length) {
|
||||||
|
selectedImages.removeAt(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,20 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:logger/logger.dart';
|
import 'package:logger/logger.dart';
|
||||||
import 'package:marco/model/user_permission.dart';
|
import 'package:marco/model/user_permission.dart';
|
||||||
import 'package:marco/model/employee_info.dart';
|
import 'package:marco/model/employee_info.dart';
|
||||||
import 'package:marco/model/projects_model.dart';
|
import 'package:marco/model/projects_model.dart';
|
||||||
import 'package:marco/helpers/services/storage/local_storage.dart';
|
import 'package:marco/helpers/services/storage/local_storage.dart';
|
||||||
import 'package:marco/helpers/services/auth_service.dart';
|
import 'package:marco/helpers/services/auth_service.dart';
|
||||||
|
|
||||||
final Logger logger = Logger();
|
final Logger logger = Logger();
|
||||||
|
|
||||||
class PermissionService {
|
class PermissionService {
|
||||||
static final Map<String, Map<String, dynamic>> _userDataCache = {};
|
static final Map<String, Map<String, dynamic>> _userDataCache = {};
|
||||||
|
|
||||||
static Future<Map<String, dynamic>> fetchAllUserData(String token, {bool hasRetried = false}) async {
|
static Future<Map<String, dynamic>> fetchAllUserData(String token,
|
||||||
|
{bool hasRetried = false}) async {
|
||||||
// Return from cache if available
|
// Return from cache if available
|
||||||
if (_userDataCache.containsKey(token)) {
|
if (_userDataCache.containsKey(token)) {
|
||||||
return _userDataCache[token]!;
|
return _userDataCache[token]!;
|
||||||
@ -51,10 +52,11 @@ class PermissionService {
|
|||||||
throw Exception('Unauthorized. Token refresh failed.');
|
throw Exception('Unauthorized. Token refresh failed.');
|
||||||
}
|
}
|
||||||
|
|
||||||
final errorMessage = json.decode(response.body)['message'] ?? 'Unknown error';
|
final errorMessage =
|
||||||
|
json.decode(response.body)['message'] ?? 'Unknown error';
|
||||||
throw Exception('Failed to load data: $errorMessage');
|
throw Exception('Failed to load data: $errorMessage');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.e('Error fetching user data: $e'); // <-- Use logger here
|
logger.e('Error fetching user data: $e');
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,8 +68,11 @@ class PermissionService {
|
|||||||
Get.offAllNamed('/auth/login');
|
Get.offAllNamed('/auth/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<UserPermission> _parsePermissions(List<dynamic> featurePermissions) =>
|
static List<UserPermission> _parsePermissions(
|
||||||
featurePermissions.map((id) => UserPermission.fromJson({'id': id})).toList();
|
List<dynamic> featurePermissions) =>
|
||||||
|
featurePermissions
|
||||||
|
.map((id) => UserPermission.fromJson({'id': id}))
|
||||||
|
.toList();
|
||||||
|
|
||||||
static EmployeeInfo _parseEmployeeInfo(Map<String, dynamic> employeeData) =>
|
static EmployeeInfo _parseEmployeeInfo(Map<String, dynamic> employeeData) =>
|
||||||
EmployeeInfo.fromJson(employeeData);
|
EmployeeInfo.fromJson(employeeData);
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
import 'package:marco/model/user_permission.dart';
|
import 'package:marco/model/user_permission.dart';
|
||||||
import 'package:marco/model/employee_info.dart';
|
import 'package:marco/model/employee_info.dart';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:get/route_manager.dart';
|
||||||
|
|
||||||
class LocalStorage {
|
class LocalStorage {
|
||||||
static const String _loggedInUserKey = "user";
|
static const String _loggedInUserKey = "user";
|
||||||
static const String _themeCustomizerKey = "theme_customizer";
|
static const String _themeCustomizerKey = "theme_customizer";
|
||||||
@ -43,7 +45,7 @@ class LocalStorage {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return []; // Return empty list if no permissions are found
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<bool> removeUserPermissions() async {
|
static Future<bool> removeUserPermissions() async {
|
||||||
@ -62,7 +64,7 @@ class LocalStorage {
|
|||||||
final Map<String, dynamic> json = jsonDecode(storedJson);
|
final Map<String, dynamic> json = jsonDecode(storedJson);
|
||||||
return EmployeeInfo.fromJson(json);
|
return EmployeeInfo.fromJson(json);
|
||||||
}
|
}
|
||||||
return null; // Return null if no employee info is found
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<bool> removeEmployeeInfo() async {
|
static Future<bool> removeEmployeeInfo() async {
|
||||||
@ -130,4 +132,14 @@ class LocalStorage {
|
|||||||
static Future<bool> setRefreshToken(String refreshToken) {
|
static Future<bool> setRefreshToken(String refreshToken) {
|
||||||
return setToken(_refreshTokenKey, refreshToken);
|
return setToken(_refreshTokenKey, refreshToken);
|
||||||
}
|
}
|
||||||
|
static Future<void> logout() async {
|
||||||
|
await removeLoggedInUser();
|
||||||
|
await removeToken(_jwtTokenKey);
|
||||||
|
await removeToken(_refreshTokenKey);
|
||||||
|
await removeUserPermissions();
|
||||||
|
await removeEmployeeInfo();
|
||||||
|
Get.offAllNamed('/auth/login');
|
||||||
|
await preferences.remove(_languageKey);
|
||||||
|
await preferences.remove(_themeCustomizerKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import 'package:marco/helpers/theme/theme_customizer.dart';
|
|||||||
import 'package:marco/routes.dart';
|
import 'package:marco/routes.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:url_strategy/url_strategy.dart';
|
import 'package:url_strategy/url_strategy.dart';
|
||||||
|
import 'package:marco/helpers/services/auth_service.dart';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
@ -33,32 +34,47 @@ Future<void> main() async {
|
|||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
const MyApp({super.key});
|
const MyApp({super.key});
|
||||||
|
Future<String> _getInitialRoute() async {
|
||||||
|
return AuthService.isLoggedIn ? "/dashboard" : "/auth/login";
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Consumer<AppNotifier>(
|
return Consumer<AppNotifier>(
|
||||||
builder: (_, notifier, ___) {
|
builder: (_, notifier, ___) {
|
||||||
return GetMaterialApp(
|
return FutureBuilder<String>(
|
||||||
debugShowCheckedModeBanner: false,
|
future: _getInitialRoute(),
|
||||||
theme: AppTheme.lightTheme,
|
builder: (context, snapshot) {
|
||||||
darkTheme: AppTheme.darkTheme,
|
if (!snapshot.hasData) {
|
||||||
themeMode: ThemeCustomizer.instance.theme,
|
return MaterialApp(
|
||||||
navigatorKey: NavigationService.navigatorKey,
|
home: Center(child: CircularProgressIndicator()),
|
||||||
initialRoute: "/dashboard",
|
);
|
||||||
getPages: getPageRoute(),
|
}
|
||||||
builder: (context, child) {
|
|
||||||
NavigationService.registerContext(context);
|
return GetMaterialApp(
|
||||||
return Directionality(
|
debugShowCheckedModeBanner: false,
|
||||||
textDirection: AppTheme.textDirection,
|
theme: AppTheme.lightTheme,
|
||||||
child: child ?? Container());
|
darkTheme: AppTheme.darkTheme,
|
||||||
|
themeMode: ThemeCustomizer.instance.theme,
|
||||||
|
navigatorKey: NavigationService.navigatorKey,
|
||||||
|
initialRoute: snapshot.data!,
|
||||||
|
getPages: getPageRoute(),
|
||||||
|
builder: (context, child) {
|
||||||
|
NavigationService.registerContext(context);
|
||||||
|
return Directionality(
|
||||||
|
textDirection: AppTheme.textDirection,
|
||||||
|
child: child ?? Container(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
localizationsDelegates: [
|
||||||
|
AppLocalizationsDelegate(context),
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
],
|
||||||
|
supportedLocales: Language.getLocales(),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
localizationsDelegates: [
|
|
||||||
AppLocalizationsDelegate(context),
|
|
||||||
GlobalMaterialLocalizations.delegate,
|
|
||||||
GlobalWidgetsLocalizations.delegate,
|
|
||||||
GlobalCupertinoLocalizations.delegate,
|
|
||||||
],
|
|
||||||
supportedLocales: Language.getLocales(),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import 'package:marco/controller/permission_controller.dart';
|
|||||||
import 'package:marco/controller/dashboard/attendance_screen_controller.dart';
|
import 'package:marco/controller/dashboard/attendance_screen_controller.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.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';
|
||||||
|
|
||||||
class AttendanceFilterBottomSheet extends StatelessWidget {
|
class AttendanceFilterBottomSheet extends StatelessWidget {
|
||||||
final AttendanceController controller;
|
final AttendanceController controller;
|
||||||
@ -38,6 +39,20 @@ class AttendanceFilterBottomSheet extends StatelessWidget {
|
|||||||
permissionController.isUserAssignedToProject(project.id.toString()))
|
permissionController.isUserAssignedToProject(project.id.toString()))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
bool hasRegularizationPermission =
|
||||||
|
permissionController.hasPermission(Permissions.regularizeAttendance);
|
||||||
|
final viewOptions = [
|
||||||
|
{'label': 'Today\'s Attendance', 'value': 'todaysAttendance'},
|
||||||
|
{'label': 'Attendance Logs', 'value': 'attendanceLogs'},
|
||||||
|
{'label': 'Regularization Requests', 'value': 'regularizationRequests'},
|
||||||
|
];
|
||||||
|
final filteredViewOptions = viewOptions.where((item) {
|
||||||
|
if (item['value'] == 'regularizationRequests') {
|
||||||
|
return hasRegularizationPermission;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}).toList();
|
||||||
|
|
||||||
return StatefulBuilder(builder: (context, setState) {
|
return StatefulBuilder(builder: (context, setState) {
|
||||||
List<Widget> filterWidgets;
|
List<Widget> filterWidgets;
|
||||||
|
|
||||||
@ -104,14 +119,7 @@ class AttendanceFilterBottomSheet extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
...[
|
...filteredViewOptions.map((item) {
|
||||||
{'label': 'Today\'s Attendance', 'value': 'todaysAttendance'},
|
|
||||||
{'label': 'Attendance Logs', 'value': 'attendanceLogs'},
|
|
||||||
{
|
|
||||||
'label': 'Regularization Requests',
|
|
||||||
'value': 'regularizationRequests'
|
|
||||||
},
|
|
||||||
].map((item) {
|
|
||||||
return RadioListTile<String>(
|
return RadioListTile<String>(
|
||||||
dense: true,
|
dense: true,
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
@ -209,7 +217,7 @@ class AttendanceFilterBottomSheet extends StatelessWidget {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Color.fromARGB(255, 95, 132, 255),
|
backgroundColor: const Color.fromARGB(255, 95, 132, 255),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
|||||||
@ -58,6 +58,7 @@ class _CommentTaskBottomSheetState extends State<CommentTaskBottomSheet>
|
|||||||
data['assigned'] ?? '';
|
data['assigned'] ?? '';
|
||||||
controller.basicValidator.getController('task_id')?.text =
|
controller.basicValidator.getController('task_id')?.text =
|
||||||
data['taskId'] ?? '';
|
data['taskId'] ?? '';
|
||||||
|
controller.basicValidator.getController('comment')?.clear();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (_scrollController.hasClients) {
|
if (_scrollController.hasClients) {
|
||||||
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
|
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
|
||||||
|
|||||||
@ -10,8 +10,8 @@ import 'package:marco/helpers/widgets/my_text_style.dart';
|
|||||||
|
|
||||||
class ReportTaskBottomSheet extends StatefulWidget {
|
class ReportTaskBottomSheet extends StatefulWidget {
|
||||||
final Map<String, dynamic> taskData;
|
final Map<String, dynamic> taskData;
|
||||||
|
final VoidCallback? onReportSuccess;
|
||||||
const ReportTaskBottomSheet({super.key, required this.taskData});
|
const ReportTaskBottomSheet({super.key, required this.taskData,this.onReportSuccess,});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ReportTaskBottomSheet> createState() => _ReportTaskBottomSheetState();
|
State<ReportTaskBottomSheet> createState() => _ReportTaskBottomSheetState();
|
||||||
@ -43,6 +43,8 @@ class _ReportTaskBottomSheetState extends State<ReportTaskBottomSheet>
|
|||||||
taskData['assigned'] ?? '';
|
taskData['assigned'] ?? '';
|
||||||
controller.basicValidator.getController('task_id')?.text =
|
controller.basicValidator.getController('task_id')?.text =
|
||||||
taskData['taskId'] ?? '';
|
taskData['taskId'] ?? '';
|
||||||
|
controller.basicValidator.getController('completed_work')?.clear();
|
||||||
|
controller.basicValidator.getController('comment')?.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -222,8 +224,10 @@ class _ReportTaskBottomSheetState extends State<ReportTaskBottomSheet>
|
|||||||
0,
|
0,
|
||||||
checklist: [],
|
checklist: [],
|
||||||
reportedDate: DateTime.now(),
|
reportedDate: DateTime.now(),
|
||||||
|
);
|
||||||
);
|
if (widget.onReportSuccess != null) {
|
||||||
|
widget.onReportSuccess!();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
|
|||||||
@ -5,15 +5,11 @@ import 'package:marco/view/auth/forgot_password_screen.dart';
|
|||||||
import 'package:marco/view/auth/login_screen.dart';
|
import 'package:marco/view/auth/login_screen.dart';
|
||||||
import 'package:marco/view/auth/register_account_screen.dart';
|
import 'package:marco/view/auth/register_account_screen.dart';
|
||||||
import 'package:marco/view/auth/reset_password_screen.dart';
|
import 'package:marco/view/auth/reset_password_screen.dart';
|
||||||
// import 'package:marco/view/dashboard/ecommerce_screen.dart';
|
|
||||||
import 'package:marco/view/error_pages/coming_soon_screen.dart';
|
import 'package:marco/view/error_pages/coming_soon_screen.dart';
|
||||||
import 'package:marco/view/error_pages/error_404_screen.dart';
|
import 'package:marco/view/error_pages/error_404_screen.dart';
|
||||||
import 'package:marco/view/error_pages/error_500_screen.dart';
|
import 'package:marco/view/error_pages/error_500_screen.dart';
|
||||||
// import 'package:marco/view/dashboard/attendance_screen.dart';
|
|
||||||
// import 'package:marco/view/dashboard/attendanceScreen.dart';
|
|
||||||
import 'package:marco/view/dashboard/dashboard_screen.dart';
|
import 'package:marco/view/dashboard/dashboard_screen.dart';
|
||||||
import 'package:marco/view/dashboard/add_employee_screen.dart';
|
import 'package:marco/view/dashboard/add_employee_screen.dart';
|
||||||
import 'package:marco/view/dashboard/employee_screen.dart';
|
|
||||||
import 'package:marco/view/dashboard/daily_task_screen.dart';
|
import 'package:marco/view/dashboard/daily_task_screen.dart';
|
||||||
import 'package:marco/view/taskPlaning/report_task_screen.dart';
|
import 'package:marco/view/taskPlaning/report_task_screen.dart';
|
||||||
import 'package:marco/view/taskPlaning/comment_task_screen.dart';
|
import 'package:marco/view/taskPlaning/comment_task_screen.dart';
|
||||||
@ -33,7 +29,7 @@ getPageRoute() {
|
|||||||
var routes = [
|
var routes = [
|
||||||
GetPage(
|
GetPage(
|
||||||
name: '/',
|
name: '/',
|
||||||
page: () => AttendanceScreen(),
|
page: () => DashboardScreen(),
|
||||||
middlewares: [AuthMiddleware()]),
|
middlewares: [AuthMiddleware()]),
|
||||||
// Dashboard
|
// Dashboard
|
||||||
GetPage(
|
GetPage(
|
||||||
|
|||||||
@ -70,7 +70,6 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
|
|||||||
"Filter",
|
"Filter",
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
),
|
),
|
||||||
// Wrap with Tooltip and InkWell for interactive feedback
|
|
||||||
Tooltip(
|
Tooltip(
|
||||||
message: 'Filter Project',
|
message: 'Filter Project',
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
@ -232,158 +231,148 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
MyCard.bordered(
|
if (isLoading)
|
||||||
borderRadiusAll: 4,
|
const SizedBox(
|
||||||
border: Border.all(color: Colors.grey.withOpacity(0.2)),
|
height: 120,
|
||||||
shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom),
|
child: Center(child: CircularProgressIndicator()),
|
||||||
paddingAll: 8,
|
)
|
||||||
child: isLoading
|
else if (employees.isEmpty)
|
||||||
? Center(child: CircularProgressIndicator())
|
SizedBox(
|
||||||
: employees.isEmpty
|
height: 120,
|
||||||
? Center(
|
child: Center(
|
||||||
child: MyText.bodySmall(
|
child: MyText.bodySmall(
|
||||||
"No Employees Assigned to This Project",
|
"No Employees Assigned to This Project",
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
: Column(
|
)
|
||||||
children: List.generate(employees.length, (index) {
|
else
|
||||||
final employee = employees[index];
|
MyCard.bordered(
|
||||||
return Column(
|
borderRadiusAll: 4,
|
||||||
|
border: Border.all(color: Colors.grey.withOpacity(0.2)),
|
||||||
|
shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom),
|
||||||
|
paddingAll: 8,
|
||||||
|
child: Column(
|
||||||
|
children: List.generate(employees.length, (index) {
|
||||||
|
final employee = employees[index];
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
|
child: MyContainer(
|
||||||
|
paddingAll: 5,
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Avatar(
|
||||||
padding: EdgeInsets.only(bottom: 8),
|
firstName: employee.firstName,
|
||||||
child: MyContainer(
|
lastName: employee.lastName,
|
||||||
paddingAll: 5,
|
size: 31,
|
||||||
child: Row(
|
),
|
||||||
crossAxisAlignment:
|
MySpacing.width(16),
|
||||||
CrossAxisAlignment.start,
|
Expanded(
|
||||||
children: [
|
child: Column(
|
||||||
Avatar(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
firstName: employee.firstName,
|
children: [
|
||||||
lastName: employee.lastName,
|
Row(
|
||||||
size: 31,
|
children: [
|
||||||
),
|
MyText.bodyMedium(
|
||||||
MySpacing.width(16),
|
employee.name,
|
||||||
Expanded(
|
fontWeight: 600,
|
||||||
child: Column(
|
overflow: TextOverflow.ellipsis,
|
||||||
crossAxisAlignment:
|
maxLines: 1,
|
||||||
CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
MyText.bodyMedium(
|
|
||||||
employee.name,
|
|
||||||
fontWeight: 600,
|
|
||||||
overflow:
|
|
||||||
TextOverflow.ellipsis,
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
MySpacing.width(6),
|
|
||||||
MyText.bodySmall(
|
|
||||||
'(${employee.designation})',
|
|
||||||
fontWeight: 600,
|
|
||||||
overflow:
|
|
||||||
TextOverflow.ellipsis,
|
|
||||||
maxLines: 1,
|
|
||||||
color: Colors.grey[
|
|
||||||
700], // optional styling
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
MySpacing.height(8),
|
|
||||||
(employee.checkIn != null ||
|
|
||||||
employee.checkOut != null)
|
|
||||||
? Row(
|
|
||||||
children: [
|
|
||||||
if (employee.checkIn !=
|
|
||||||
null) ...[
|
|
||||||
Icon(
|
|
||||||
Icons
|
|
||||||
.arrow_circle_right,
|
|
||||||
size: 16,
|
|
||||||
color:
|
|
||||||
Colors.green),
|
|
||||||
MySpacing.width(4),
|
|
||||||
Expanded(
|
|
||||||
child:
|
|
||||||
MyText.bodySmall(
|
|
||||||
DateFormat(
|
|
||||||
'hh:mm a')
|
|
||||||
.format(employee
|
|
||||||
.checkIn!),
|
|
||||||
fontWeight: 600,
|
|
||||||
overflow:
|
|
||||||
TextOverflow
|
|
||||||
.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
MySpacing.width(16),
|
|
||||||
],
|
|
||||||
if (employee.checkOut !=
|
|
||||||
null) ...[
|
|
||||||
Icon(
|
|
||||||
Icons
|
|
||||||
.arrow_circle_left,
|
|
||||||
size: 16,
|
|
||||||
color: Colors.red),
|
|
||||||
MySpacing.width(4),
|
|
||||||
Expanded(
|
|
||||||
child:
|
|
||||||
MyText.bodySmall(
|
|
||||||
DateFormat(
|
|
||||||
'hh:mm a')
|
|
||||||
.format(employee
|
|
||||||
.checkOut!),
|
|
||||||
fontWeight: 600,
|
|
||||||
overflow:
|
|
||||||
TextOverflow
|
|
||||||
.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: SizedBox.shrink(),
|
|
||||||
MySpacing.height(12),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
AttendanceActionButton(
|
|
||||||
employee: employee,
|
|
||||||
attendanceController:
|
|
||||||
attendanceController,
|
|
||||||
),
|
|
||||||
if (employee.checkIn !=
|
|
||||||
null) ...[
|
|
||||||
MySpacing.width(8),
|
|
||||||
AttendanceLogViewButton(
|
|
||||||
employee: employee,
|
|
||||||
attendanceController:
|
|
||||||
attendanceController,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
MySpacing.width(6),
|
||||||
],
|
MyText.bodySmall(
|
||||||
),
|
'(${employee.designation})',
|
||||||
|
fontWeight: 600,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
color: Colors.grey[700],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
MySpacing.height(8),
|
||||||
|
(employee.checkIn != null ||
|
||||||
|
employee.checkOut != null)
|
||||||
|
? Row(
|
||||||
|
children: [
|
||||||
|
if (employee.checkIn != null) ...[
|
||||||
|
const Icon(
|
||||||
|
Icons.arrow_circle_right,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.green),
|
||||||
|
MySpacing.width(4),
|
||||||
|
Expanded(
|
||||||
|
child: MyText.bodySmall(
|
||||||
|
DateFormat('hh:mm a')
|
||||||
|
.format(
|
||||||
|
employee.checkIn!),
|
||||||
|
fontWeight: 600,
|
||||||
|
overflow:
|
||||||
|
TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MySpacing.width(16),
|
||||||
|
],
|
||||||
|
if (employee.checkOut !=
|
||||||
|
null) ...[
|
||||||
|
const Icon(
|
||||||
|
Icons.arrow_circle_left,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.red),
|
||||||
|
MySpacing.width(4),
|
||||||
|
Expanded(
|
||||||
|
child: MyText.bodySmall(
|
||||||
|
DateFormat('hh:mm a')
|
||||||
|
.format(
|
||||||
|
employee.checkOut!),
|
||||||
|
fontWeight: 600,
|
||||||
|
overflow:
|
||||||
|
TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
MySpacing.height(12),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
AttendanceActionButton(
|
||||||
|
employee: employee,
|
||||||
|
attendanceController:
|
||||||
|
attendanceController,
|
||||||
|
),
|
||||||
|
if (employee.checkIn != null) ...[
|
||||||
|
MySpacing.width(8),
|
||||||
|
AttendanceLogViewButton(
|
||||||
|
employee: employee,
|
||||||
|
attendanceController:
|
||||||
|
attendanceController,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (index != employees.length - 1)
|
|
||||||
Divider(
|
|
||||||
color: Colors.grey.withOpacity(0.3),
|
|
||||||
thickness: 1,
|
|
||||||
height: 1,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
),
|
||||||
}),
|
),
|
||||||
),
|
),
|
||||||
),
|
if (index != employees.length - 1)
|
||||||
|
Divider(
|
||||||
|
color: Colors.grey.withOpacity(0.3),
|
||||||
|
thickness: 1,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -397,6 +386,7 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
|
|||||||
final bDate = b.checkIn ?? DateTime(0);
|
final bDate = b.checkIn ?? DateTime(0);
|
||||||
return bDate.compareTo(aDate);
|
return bDate.compareTo(aDate);
|
||||||
});
|
});
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -437,179 +427,176 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
MyCard.bordered(
|
if (attendanceController.isLoadingAttendanceLogs.value)
|
||||||
borderRadiusAll: 4,
|
const SizedBox(
|
||||||
border: Border.all(color: Colors.grey.withOpacity(0.2)),
|
height: 120,
|
||||||
shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom),
|
child: Center(child: CircularProgressIndicator()),
|
||||||
paddingAll: 8,
|
)
|
||||||
child: Column(
|
else if (logs.isEmpty)
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
SizedBox(
|
||||||
children: [
|
height: 120,
|
||||||
if (attendanceController.isLoadingAttendanceLogs.value)
|
child: Center(
|
||||||
const Padding(
|
child: MyText.bodySmall(
|
||||||
padding: EdgeInsets.symmetric(vertical: 32),
|
"No Attendance Logs Found for this Project",
|
||||||
child: Center(child: CircularProgressIndicator()),
|
fontWeight: 600,
|
||||||
)
|
),
|
||||||
else if (logs.isEmpty)
|
),
|
||||||
MyText.bodySmall(
|
)
|
||||||
"No Attendance Logs Found for this Project",
|
else
|
||||||
fontWeight: 600,
|
MyCard.bordered(
|
||||||
)
|
borderRadiusAll: 4,
|
||||||
else
|
border: Border.all(color: Colors.grey.withOpacity(0.2)),
|
||||||
Column(
|
shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom),
|
||||||
children: List.generate(logs.length, (index) {
|
paddingAll: 8,
|
||||||
final employee = logs[index];
|
child: Column(
|
||||||
final currentDate = employee.checkIn != null
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
? DateFormat('dd MMM yyyy').format(employee.checkIn!)
|
children: List.generate(logs.length, (index) {
|
||||||
|
final employee = logs[index];
|
||||||
|
final currentDate = employee.checkIn != null
|
||||||
|
? DateFormat('dd MMM yyyy').format(employee.checkIn!)
|
||||||
|
: '';
|
||||||
|
final previousDate =
|
||||||
|
index > 0 && logs[index - 1].checkIn != null
|
||||||
|
? DateFormat('dd MMM yyyy')
|
||||||
|
.format(logs[index - 1].checkIn!)
|
||||||
: '';
|
: '';
|
||||||
final previousDate =
|
|
||||||
index > 0 && logs[index - 1].checkIn != null
|
|
||||||
? DateFormat('dd MMM yyyy')
|
|
||||||
.format(logs[index - 1].checkIn!)
|
|
||||||
: '';
|
|
||||||
|
|
||||||
final showDateHeader =
|
final showDateHeader =
|
||||||
index == 0 || currentDate != previousDate;
|
index == 0 || currentDate != previousDate;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (showDateHeader)
|
if (showDateHeader)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
child: MyText.bodyMedium(
|
child: MyText.bodyMedium(
|
||||||
currentDate,
|
currentDate,
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
|
child: MyContainer(
|
||||||
|
paddingAll: 8,
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Avatar(
|
||||||
|
firstName: employee.firstName,
|
||||||
|
lastName: employee.lastName,
|
||||||
|
size: 31,
|
||||||
),
|
),
|
||||||
),
|
MySpacing.width(16),
|
||||||
Padding(
|
Expanded(
|
||||||
padding: EdgeInsets.only(bottom: 8),
|
child: Column(
|
||||||
child: MyContainer(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
paddingAll: 8,
|
children: [
|
||||||
child: Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Avatar(
|
|
||||||
firstName: employee.firstName,
|
|
||||||
lastName: employee.lastName,
|
|
||||||
size: 31,
|
|
||||||
),
|
|
||||||
MySpacing.width(16),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Flexible(
|
||||||
children: [
|
child: MyText.bodyMedium(
|
||||||
Flexible(
|
employee.name,
|
||||||
child: MyText.bodyMedium(
|
fontWeight: 600,
|
||||||
employee.name,
|
overflow: TextOverflow.ellipsis,
|
||||||
fontWeight: 600,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
),
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
MySpacing.width(6),
|
|
||||||
Flexible(
|
|
||||||
child: MyText.bodySmall(
|
|
||||||
'(${employee.designation})',
|
|
||||||
fontWeight: 600,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 1,
|
|
||||||
color: Colors.grey[700],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
MySpacing.height(8),
|
MySpacing.width(6),
|
||||||
(employee.checkIn != null ||
|
Flexible(
|
||||||
employee.checkOut != null)
|
child: MyText.bodySmall(
|
||||||
? Row(
|
'(${employee.designation})',
|
||||||
children: [
|
fontWeight: 600,
|
||||||
if (employee.checkIn !=
|
overflow: TextOverflow.ellipsis,
|
||||||
null) ...[
|
maxLines: 1,
|
||||||
Icon(
|
color: Colors.grey[700],
|
||||||
Icons
|
),
|
||||||
.arrow_circle_right,
|
|
||||||
size: 16,
|
|
||||||
color: Colors.green),
|
|
||||||
MySpacing.width(4),
|
|
||||||
Expanded(
|
|
||||||
child: MyText.bodySmall(
|
|
||||||
DateFormat('hh:mm a')
|
|
||||||
.format(employee
|
|
||||||
.checkIn!),
|
|
||||||
fontWeight: 600,
|
|
||||||
overflow: TextOverflow
|
|
||||||
.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
MySpacing.width(16),
|
|
||||||
],
|
|
||||||
if (employee.checkOut !=
|
|
||||||
null) ...[
|
|
||||||
Icon(
|
|
||||||
Icons.arrow_circle_left,
|
|
||||||
size: 16,
|
|
||||||
color: Colors.red),
|
|
||||||
MySpacing.width(4),
|
|
||||||
Expanded(
|
|
||||||
child: MyText.bodySmall(
|
|
||||||
DateFormat('hh:mm a')
|
|
||||||
.format(employee
|
|
||||||
.checkOut!),
|
|
||||||
fontWeight: 600,
|
|
||||||
overflow: TextOverflow
|
|
||||||
.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: SizedBox.shrink(),
|
|
||||||
MySpacing.height(12),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
Flexible(
|
|
||||||
child: AttendanceActionButton(
|
|
||||||
employee: employee,
|
|
||||||
attendanceController:
|
|
||||||
attendanceController,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
MySpacing.width(8),
|
|
||||||
Flexible(
|
|
||||||
child: AttendanceLogViewButton(
|
|
||||||
employee: employee,
|
|
||||||
attendanceController:
|
|
||||||
attendanceController,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
MySpacing.height(8),
|
||||||
],
|
(employee.checkIn != null ||
|
||||||
|
employee.checkOut != null)
|
||||||
|
? Row(
|
||||||
|
children: [
|
||||||
|
if (employee.checkIn != null) ...[
|
||||||
|
const Icon(
|
||||||
|
Icons.arrow_circle_right,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.green),
|
||||||
|
MySpacing.width(4),
|
||||||
|
Expanded(
|
||||||
|
child: MyText.bodySmall(
|
||||||
|
DateFormat('hh:mm a')
|
||||||
|
.format(
|
||||||
|
employee.checkIn!),
|
||||||
|
fontWeight: 600,
|
||||||
|
overflow:
|
||||||
|
TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MySpacing.width(16),
|
||||||
|
],
|
||||||
|
if (employee.checkOut !=
|
||||||
|
null) ...[
|
||||||
|
const Icon(
|
||||||
|
Icons.arrow_circle_left,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.red),
|
||||||
|
MySpacing.width(4),
|
||||||
|
Expanded(
|
||||||
|
child: MyText.bodySmall(
|
||||||
|
DateFormat('hh:mm a')
|
||||||
|
.format(
|
||||||
|
employee.checkOut!),
|
||||||
|
fontWeight: 600,
|
||||||
|
overflow:
|
||||||
|
TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
MySpacing.height(12),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: AttendanceActionButton(
|
||||||
|
employee: employee,
|
||||||
|
attendanceController:
|
||||||
|
attendanceController,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MySpacing.width(8),
|
||||||
|
Flexible(
|
||||||
|
child: AttendanceLogViewButton(
|
||||||
|
employee: employee,
|
||||||
|
attendanceController:
|
||||||
|
attendanceController,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
if (index != logs.length - 1)
|
),
|
||||||
Divider(
|
),
|
||||||
color: Colors.grey.withOpacity(0.3),
|
if (index != logs.length - 1)
|
||||||
thickness: 1,
|
Divider(
|
||||||
height: 1,
|
color: Colors.grey.withOpacity(0.3),
|
||||||
),
|
thickness: 1,
|
||||||
],
|
height: 1,
|
||||||
);
|
),
|
||||||
}),
|
],
|
||||||
),
|
);
|
||||||
],
|
}),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -628,151 +615,143 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
|
|||||||
),
|
),
|
||||||
Obx(() {
|
Obx(() {
|
||||||
final employees = attendanceController.regularizationLogs;
|
final employees = attendanceController.regularizationLogs;
|
||||||
|
if (attendanceController.isLoadingRegularizationLogs.value) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 120,
|
||||||
|
child: const Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (employees.isEmpty) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 120,
|
||||||
|
child: Center(
|
||||||
|
child: MyText.bodySmall(
|
||||||
|
"No Regularization Requests Found for this Project",
|
||||||
|
fontWeight: 600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
return MyCard.bordered(
|
return MyCard.bordered(
|
||||||
borderRadiusAll: 4,
|
borderRadiusAll: 4,
|
||||||
border: Border.all(color: Colors.grey.withOpacity(0.2)),
|
border: Border.all(color: Colors.grey.withOpacity(0.2)),
|
||||||
shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom),
|
shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom),
|
||||||
paddingAll: 8,
|
paddingAll: 8,
|
||||||
child: attendanceController.isLoadingRegularizationLogs.value
|
child: Column(
|
||||||
? const Padding(
|
children: List.generate(employees.length, (index) {
|
||||||
padding: EdgeInsets.symmetric(vertical: 32.0),
|
final employee = employees[index];
|
||||||
child: Center(child: CircularProgressIndicator()),
|
return Column(
|
||||||
)
|
children: [
|
||||||
: employees.isEmpty
|
Padding(
|
||||||
? MyText.bodySmall(
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
"No Regularization Requests Found for this Project",
|
child: MyContainer(
|
||||||
fontWeight: 600,
|
paddingAll: 8,
|
||||||
)
|
child: Row(
|
||||||
: Column(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: List.generate(employees.length, (index) {
|
children: [
|
||||||
final employee = employees[index];
|
Avatar(
|
||||||
return Column(
|
firstName: employee.firstName,
|
||||||
children: [
|
lastName: employee.lastName,
|
||||||
Padding(
|
size: 31,
|
||||||
padding: EdgeInsets.only(bottom: 8),
|
),
|
||||||
child: MyContainer(
|
MySpacing.width(16),
|
||||||
paddingAll: 8,
|
Expanded(
|
||||||
child: Row(
|
child: Column(
|
||||||
crossAxisAlignment:
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
CrossAxisAlignment.start,
|
children: [
|
||||||
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Avatar(
|
Flexible(
|
||||||
firstName: employee.firstName,
|
child: MyText.bodyMedium(
|
||||||
lastName: employee.lastName,
|
employee.name,
|
||||||
size: 31,
|
fontWeight: 600,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
MySpacing.width(16),
|
MySpacing.width(6),
|
||||||
Expanded(
|
Flexible(
|
||||||
child: Column(
|
child: MyText.bodySmall(
|
||||||
crossAxisAlignment:
|
'(${employee.role})',
|
||||||
CrossAxisAlignment.start,
|
fontWeight: 600,
|
||||||
children: [
|
overflow: TextOverflow.ellipsis,
|
||||||
Row(
|
maxLines: 1,
|
||||||
children: [
|
color: Colors.grey[700],
|
||||||
Flexible(
|
|
||||||
child: MyText.bodyMedium(
|
|
||||||
employee.name,
|
|
||||||
fontWeight: 600,
|
|
||||||
overflow:
|
|
||||||
TextOverflow.ellipsis,
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
MySpacing.width(6),
|
|
||||||
Flexible(
|
|
||||||
child: MyText.bodySmall(
|
|
||||||
'(${employee.role})',
|
|
||||||
fontWeight: 600,
|
|
||||||
overflow:
|
|
||||||
TextOverflow.ellipsis,
|
|
||||||
maxLines: 1,
|
|
||||||
color: Colors.grey[700],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
MySpacing.height(8),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
if (employee.checkIn !=
|
|
||||||
null) ...[
|
|
||||||
Icon(Icons.arrow_circle_right,
|
|
||||||
size: 16,
|
|
||||||
color: Colors.green),
|
|
||||||
MySpacing.width(4),
|
|
||||||
Expanded(
|
|
||||||
child: MyText.bodySmall(
|
|
||||||
DateFormat('hh:mm a')
|
|
||||||
.format(employee
|
|
||||||
.checkIn!),
|
|
||||||
fontWeight: 600,
|
|
||||||
overflow:
|
|
||||||
TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
MySpacing.width(16),
|
|
||||||
],
|
|
||||||
if (employee.checkOut !=
|
|
||||||
null) ...[
|
|
||||||
Icon(Icons.arrow_circle_left,
|
|
||||||
size: 16,
|
|
||||||
color: Colors.red),
|
|
||||||
MySpacing.width(4),
|
|
||||||
Expanded(
|
|
||||||
child: MyText.bodySmall(
|
|
||||||
DateFormat('hh:mm a')
|
|
||||||
.format(employee
|
|
||||||
.checkOut!),
|
|
||||||
fontWeight: 600,
|
|
||||||
overflow:
|
|
||||||
TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
MySpacing.height(12),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
RegularizeActionButton(
|
|
||||||
attendanceController:
|
|
||||||
attendanceController,
|
|
||||||
log: employee,
|
|
||||||
uniqueLogKey:
|
|
||||||
employee.employeeId,
|
|
||||||
action: ButtonActions.approve,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
RegularizeActionButton(
|
|
||||||
attendanceController:
|
|
||||||
attendanceController,
|
|
||||||
log: employee,
|
|
||||||
uniqueLogKey:
|
|
||||||
employee.employeeId,
|
|
||||||
action: ButtonActions.reject,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
MySpacing.height(8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
if (employee.checkIn != null) ...[
|
||||||
|
const Icon(Icons.arrow_circle_right,
|
||||||
|
size: 16, color: Colors.green),
|
||||||
|
MySpacing.width(4),
|
||||||
|
Expanded(
|
||||||
|
child: MyText.bodySmall(
|
||||||
|
DateFormat('hh:mm a')
|
||||||
|
.format(employee.checkIn!),
|
||||||
|
fontWeight: 600,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MySpacing.width(16),
|
||||||
|
],
|
||||||
|
if (employee.checkOut != null) ...[
|
||||||
|
const Icon(Icons.arrow_circle_left,
|
||||||
|
size: 16, color: Colors.red),
|
||||||
|
MySpacing.width(4),
|
||||||
|
Expanded(
|
||||||
|
child: MyText.bodySmall(
|
||||||
|
DateFormat('hh:mm a')
|
||||||
|
.format(employee.checkOut!),
|
||||||
|
fontWeight: 600,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
MySpacing.height(12),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
RegularizeActionButton(
|
||||||
|
attendanceController:
|
||||||
|
attendanceController,
|
||||||
|
log: employee,
|
||||||
|
uniqueLogKey: employee.employeeId,
|
||||||
|
action: ButtonActions.approve,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
RegularizeActionButton(
|
||||||
|
attendanceController:
|
||||||
|
attendanceController,
|
||||||
|
log: employee,
|
||||||
|
uniqueLogKey: employee.employeeId,
|
||||||
|
action: ButtonActions.reject,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
if (index != employees.length - 1)
|
),
|
||||||
Divider(
|
],
|
||||||
color: Colors.grey.withOpacity(0.3),
|
),
|
||||||
thickness: 1,
|
|
||||||
height: 1,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
if (index != employees.length - 1)
|
||||||
|
Divider(
|
||||||
|
color: Colors.grey.withOpacity(0.3),
|
||||||
|
thickness: 1,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -272,7 +272,9 @@ class Layout extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
MyButton(
|
MyButton(
|
||||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
onPressed: () => {Get.offNamed('/auth/login')},
|
onPressed: () async {
|
||||||
|
await LocalStorage.logout();
|
||||||
|
},
|
||||||
borderRadiusAll: AppStyle.buttonRadius.medium,
|
borderRadiusAll: AppStyle.buttonRadius.medium,
|
||||||
padding: MySpacing.xy(8, 4),
|
padding: MySpacing.xy(8, 4),
|
||||||
splashColor: contentTheme.onBackground.withAlpha(20),
|
splashColor: contentTheme.onBackground.withAlpha(20),
|
||||||
|
|||||||
@ -172,13 +172,16 @@ class _LeftBarState extends State<LeftBar>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
MyContainer(
|
MyContainer(
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
Get.offNamed('/auth/login');
|
await LocalStorage.logout();
|
||||||
},
|
},
|
||||||
color: leftBarTheme.activeItemBackground,
|
color: leftBarTheme.activeItemBackground,
|
||||||
paddingAll: 8,
|
paddingAll: 8,
|
||||||
child: Icon(LucideIcons.log_out,
|
child: Icon(
|
||||||
size: 16, color: leftBarTheme.activeItemColor),
|
LucideIcons.log_out,
|
||||||
|
size: 16,
|
||||||
|
color: leftBarTheme.activeItemColor,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -468,11 +468,16 @@ class _DailyProgressReportScreenState extends State<DailyProgressReportScreen>
|
|||||||
top: Radius.circular(
|
top: Radius.circular(
|
||||||
16)),
|
16)),
|
||||||
),
|
),
|
||||||
builder: (_) => Padding(
|
builder: (BuildContext ctx) =>
|
||||||
padding: MediaQuery.of(context)
|
Padding(
|
||||||
|
padding: MediaQuery.of(ctx)
|
||||||
.viewInsets,
|
.viewInsets,
|
||||||
child: ReportTaskBottomSheet(
|
child: ReportTaskBottomSheet(
|
||||||
taskData: taskData),
|
taskData: taskData,
|
||||||
|
onReportSuccess: () {
|
||||||
|
_refreshData();
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user