Vaibhav_Task-#100 #1

Merged
vaibhav.surve merged 3 commits from Vaibhav_Task-#100 into main 2025-04-25 07:56:08 +00:00
7 changed files with 759 additions and 373 deletions

View File

@ -1,83 +1,106 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:marco/helpers/services/api_service.dart';
import 'package:marco/model/attendance_model.dart';
import 'package:marco/model/project_model.dart'; // Assuming you have a ProjectModel for the projects.
import 'package:marco/model/employee_model.dart'; // Assuming you have an EmployeeModel for the employees.
import 'package:marco/model/AttendanceLogModel.dart';
import 'package:marco/model/project_model.dart';
import 'package:marco/model/employee_model.dart';
import 'package:marco/model/attendance_log_model.dart';
import 'package:marco/model/regularization_log_model.dart';
import 'package:marco/model/attendance_log_view_model.dart';
class AttendanceController extends GetxController {
List<AttendanceModel> attendances = [];
List<ProjectModel> projects = []; // List of projects
String? selectedProjectId; // Currently selected project ID
List<EmployeeModel> employees = []; // Employees of the selected project
List<ProjectModel> projects = [];
String? selectedProjectId;
List<EmployeeModel> employees = [];
DateTime? startDateAttendance;
DateTime? endDateAttendance;
List<AttendanceLogModel> attendanceLogs = [];
List<RegularizationLogModel> regularizationLogs = [];
List<AttendanceLogViewModel> attendenceLogsView = [];
@override
void onInit() {
super.onInit();
fetchProjects(); // Fetch projects when initializing
_initializeDefaults();
}
void _initializeDefaults() {
_setDefaultDateRange();
fetchProjects();
}
void _setDefaultDateRange() {
final today = DateTime.now();
startDateAttendance = today.subtract(const Duration(days: 7));
endDateAttendance = today;
}
// Fetch projects from API
Future<void> fetchProjects() async {
var response = await ApiService.getProjects(); // Call the project API
final response = await ApiService.getProjects();
if (response != null) {
projects = response
.map<ProjectModel>((json) => ProjectModel.fromJson(json))
.toList();
// Set default to the first project if available
if (projects.isNotEmpty) {
selectedProjectId = projects.first.id.toString();
await fetchEmployeesByProject(
selectedProjectId); // Fetch employees for the first project
}
update([
'attendance_dashboard_controller'
]); // Notify GetBuilder with your tag
if (response != null && response.isNotEmpty) {
projects = response.map((json) => ProjectModel.fromJson(json)).toList();
selectedProjectId = projects.first.id.toString();
await _fetchProjectData(selectedProjectId);
update(['attendance_dashboard_controller']);
} else {
print("No projects data found or failed to fetch data.");
}
}
// Fetch employees by project ID
Future<void> _fetchProjectData(String? projectId) async {
if (projectId == null) return;
await Future.wait([
fetchEmployeesByProject(projectId),
fetchAttendanceLogs(projectId,
dateFrom: startDateAttendance, dateTo: endDateAttendance),
fetchRegularizationLogs(projectId),
]);
}
Future<void> fetchEmployeesByProject(String? projectId) async {
if (projectId == null) return;
var response = await ApiService.getEmployeesByProject(int.parse(projectId));
final response =
await ApiService.getEmployeesByProject(int.parse(projectId));
if (response != null) {
employees = response
.map<EmployeeModel>((json) => EmployeeModel.fromJson(json))
.toList();
update(); // Trigger UI rebuild
employees = response.map((json) => EmployeeModel.fromJson(json)).toList();
update();
} else {
print("Failed to fetch employees for project $projectId.");
}
}
Future<bool> captureAndUploadAttendance(int employeeId, int projectId,
{String comment = "Marked via mobile app"}) async {
Future<bool> captureAndUploadAttendance(
int id,
int employeeId,
int projectId, {
String comment = "Marked via mobile app",
required int action,
}) async {
try {
final XFile? image = await ImagePicker().pickImage(
final image = await ImagePicker().pickImage(
source: ImageSource.camera,
imageQuality: 80,
);
if (image == null) return false;
final position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
String imageName = ApiService.generateImageName(
employeeId,
employees.length + 1,
desiredAccuracy: LocationAccuracy.high,
);
final imageName =
ApiService.generateImageName(employeeId, employees.length + 1);
return await ApiService.uploadAttendanceImage(
id,
employeeId,
image,
position.latitude,
@ -85,26 +108,97 @@ class AttendanceController extends GetxController {
imageName: imageName,
projectId: projectId,
comment: comment,
action: action,
);
} catch (e) {
print("Error capturing or uploading: $e");
print("Error capturing or uploading attendance: $e");
return false;
}
}
List<AttendanceLogModel> attendanceLogs = [];
Future<void> fetchAttendanceLogs(String? projectId) async {
Future<void> selectDateRangeForAttendance(
BuildContext context,
AttendanceController controller,
) async {
final picked = await showDateRangePicker(
context: context,
firstDate: DateTime(2022),
lastDate: DateTime.now(),
initialDateRange: DateTimeRange(
start: startDateAttendance ??
DateTime.now().subtract(const Duration(days: 7)),
end: endDateAttendance ?? DateTime.now(),
),
);
if (picked != null) {
startDateAttendance = picked.start;
endDateAttendance = picked.end;
await controller.fetchAttendanceLogs(
controller.selectedProjectId,
dateFrom: picked.start,
dateTo: picked.end,
);
}
}
Future<void> fetchAttendanceLogs(
String? projectId, {
DateTime? dateFrom,
DateTime? dateTo,
}) async {
if (projectId == null) return;
var response = await ApiService.getAttendanceLogs(int.parse(projectId));
final response = await ApiService.getAttendanceLogs(
int.parse(projectId),
dateFrom: dateFrom,
dateTo: dateTo,
);
if (response != null) {
attendanceLogs = response
.map<AttendanceLogModel>((json) => AttendanceLogModel.fromJson(json))
attendanceLogs =
response.map((json) => AttendanceLogModel.fromJson(json)).toList();
print("Attendance logs fetched: ${response}");
update();
} else {
print("Failed to fetch attendance logs for project $projectId.");
}
}
Future<void> fetchRegularizationLogs(
String? projectId, {
DateTime? dateFrom,
DateTime? dateTo,
}) async {
if (projectId == null) return;
final response =
await ApiService.getRegularizationLogs(int.parse(projectId));
if (response != null) {
regularizationLogs = response
.map((json) => RegularizationLogModel.fromJson(json))
.toList();
update();
} else {
print("Failed to fetch logs for project $projectId.");
print("Failed to fetch regularization logs for project $projectId.");
}
}
Future<void> fetchLogsView(String? id) async {
if (id == null) return;
final response =
await ApiService.getAttendanceLogView(int.parse(id));
if (response != null) {
attendenceLogsView = response
.map((json) => AttendanceLogViewModel.fromJson(json))
.toList();
update();
} else {
print("Failed to fetch regularization logs for project $id.");
}
}
}

View File

@ -4,195 +4,175 @@ import 'package:image_picker/image_picker.dart';
import 'package:marco/helpers/services/storage/local_storage.dart';
import 'package:intl/intl.dart';
class ApiService {
static const String baseUrl = "https://api.marcoaiot.com/api";
// Fetch the list of projects
// ===== Common Helpers =====
static Future<String?> _getToken() async {
final token = LocalStorage.getJwtToken();
if (token == null) {
print("No JWT token found. Please log in.");
}
return token;
}
static Map<String, String> _headers(String token) => {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
};
static Future<http.Response?> _getRequest(String endpoint,
{Map<String, String>? queryParams}) async {
final token = await _getToken();
if (token == null) return null;
final uri =
Uri.parse("$baseUrl$endpoint").replace(queryParameters: queryParams);
print('GET request: $uri');
try {
return await http.get(uri, headers: _headers(token));
} catch (e) {
print("HTTP GET Exception: $e");
return null;
}
}
static dynamic _parseResponse(http.Response response, {String label = ''}) {
print("$label Response: ${response.body}");
if (response.statusCode == 200) {
final json = jsonDecode(response.body);
if (json['success'] == true) return json['data'];
print("API Error: ${json['message']}");
} else {
print("HTTP Error [$label]: ${response.statusCode}");
}
return null;
}
// ===== API Calls =====
static Future<List<dynamic>?> getProjects() async {
try {
String? jwtToken = LocalStorage.getJwtToken();
if (jwtToken == null) {
print("No JWT token found. Please log in.");
return null;
}
final response = await http.get(
Uri.parse("$baseUrl/project/list"),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $jwtToken', // Add Authorization header
},
);
if (response.statusCode == 200) {
final json = jsonDecode(response.body);
print("Response body: ${response.body}");
if (json['success'] == true) {
return json['data']; // Return the data if success is true
} else {
print("Error: ${json['message']}");
}
} else {
print("Error fetching projects: ${response.statusCode}");
print("Response body: ${response.body}");
}
} catch (e) {
print("Exception while fetching projects: $e");
}
return null;
final response = await _getRequest("/project/list");
return response != null
? _parseResponse(response, label: 'Projects')
: null;
}
// Fetch employees by project ID
static Future<List<dynamic>?> getEmployeesByProject(int projectId) async {
try {
String? jwtToken = LocalStorage.getJwtToken();
if (jwtToken == null) {
print("No JWT token found. Please log in.");
return null;
}
final response = await http.get(
Uri.parse(
"$baseUrl/attendance/project/team?projectId=$projectId"), // Ensure correct endpoint
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $jwtToken',
},
);
if (response.statusCode == 200) {
final json = jsonDecode(response.body);
print("Response body: ${response.body}");
if (json['success'] == true) {
return json['data']; // Return employee data
} else {
print("Error: ${json['message']}");
}
} else {
print("Error fetching employees: ${response.statusCode}");
print("Response body: ${response.body}");
}
} catch (e) {
print("Exception while fetching employees: $e");
}
return null;
final response = await _getRequest("/attendance/project/team",
queryParams: {"projectId": "$projectId"});
return response != null
? _parseResponse(response, label: 'Employees')
: null;
}
static String generateImageName(int employeeId, int count) {
final now = DateTime.now();
final formattedDate = "${now.year.toString().padLeft(4, '0')}"
"${now.month.toString().padLeft(2, '0')}"
"${now.day.toString().padLeft(2, '0')}_"
"${now.hour.toString().padLeft(2, '0')}"
"${now.minute.toString().padLeft(2, '0')}"
"${now.second.toString().padLeft(2, '0')}";
final imageNumber = count.toString().padLeft(3, '0');
return "${employeeId}_${formattedDate}_$imageNumber.jpg";
static Future<List<dynamic>?> getAttendanceLogs(
int projectId, {
DateTime? dateFrom,
DateTime? dateTo,
}) async {
final query = {
"projectId": "$projectId",
if (dateFrom != null)
"dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom),
if (dateTo != null) "dateTo": DateFormat('yyyy-MM-dd').format(dateTo),
};
final response =
await _getRequest("/attendance/project/team", queryParams: query);
return response != null
? _parseResponse(response, label: 'Attendance Logs')
: null;
}
static Future<List<dynamic>?> getAttendanceLogView(int id) async {
final response = await _getRequest("/attendance/log/attendance/$id");
return response != null
? _parseResponse(response, label: 'Attendance Log Details')
: null;
}
static Future<List<dynamic>?> getRegularizationLogs(int projectId) async {
final response = await _getRequest("/attendance/regularize",
queryParams: {"projectId": "$projectId"});
return response != null
? _parseResponse(response, label: 'Regularization')
: null;
}
// ===== Upload Image =====
static Future<bool> uploadAttendanceImage(
int employeeId, XFile imageFile, double latitude, double longitude,
{required String imageName,
required int projectId,
String comment = "",
int action = 0}) async {
try {
String? jwtToken = LocalStorage.getJwtToken();
if (jwtToken == null) {
print("No JWT token found. Please log in.");
return false;
}
int id,
int employeeId,
XFile imageFile,
double latitude,
double longitude, {
required String imageName,
required int projectId,
String comment = "",
required int action, // action passed here
}) async {
final token = await _getToken();
if (token == null) return false;
try {
final bytes = await imageFile.readAsBytes();
final base64Image = base64Encode(bytes);
final fileSize = await imageFile.length();
final contentType = "image/${imageFile.path.split('.').last}";
final imageObject = {
"FileName": imageName,
"Base64Data": base64Image,
"ContentType": contentType,
"FileSize": fileSize,
"Description": "Employee attendance photo"
"fileName": '$imageName',
"contentType": '$contentType',
"fileSize": fileSize,
"description": "Employee attendance photo",
"base64Data": '$base64Image',
};
final now = DateTime.now();
// You can now include the attendance record directly in the main body
final body = {
"id": id,
"employeeId": employeeId,
"projectId": projectId,
"markTime": DateFormat('hh:mm a').format(now),
"comment": comment,
"action": action,
"date": DateFormat('yyyy-MM-dd').format(now),
"latitude": '$latitude',
"longitude": '$longitude',
"image": imageObject
};
print("Attendance Image Upload Body: $body");
final response = await http.post(
Uri.parse("$baseUrl/attendance/record"),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $jwtToken',
},
body: jsonEncode({
"ID": null,
"employeeId": employeeId,
"projectId": projectId,
"markTime": DateFormat('hh:mm a').format(now),
"comment": comment,
"action": action,
"date": DateFormat('yyyy-MM-dd').format(now),
"latitude": latitude,
"longitude": longitude,
"image": [imageObject], // Directly included in the body
}),
Uri.parse("$baseUrl/attendance/record-image"),
headers: _headers(token),
body: jsonEncode(body),
);
print('body: ${jsonEncode({
"employeeId": employeeId,
"projectId": projectId,
"markTime": DateFormat('hh:mm a').format(now),
"comment": comment,
"action": action,
"date": DateFormat('yyyy-MM-dd').format(now),
"latitude": latitude,
"longitude": longitude,
"image": [imageObject],
})}');
print('uploadAttendanceImage: $baseUrl/attendance/record');
if (response.statusCode == 200) {
final json = jsonDecode(response.body);
return json['success'] == true;
print("Attendance Image Upload Response: ${response.body}");
final json = jsonDecode(response.body);
if (response.statusCode == 200 && json['success'] == true) {
return true;
} else {
print("Error uploading image: ${response.statusCode}");
print("Response: ${response.body}");
print("Failed to upload image. API Error: ${json['message']}");
}
} catch (e) {
print("Exception during image upload: $e");
}
return false;
}
static Future<List<dynamic>?> getAttendanceLogs(int projectId) async {
try {
String? jwtToken = LocalStorage.getJwtToken();
if (jwtToken == null) {
print("No JWT token found. Please log in.");
return null;
}
// ===== Utilities =====
final response = await http.get(
Uri.parse(
"$baseUrl/attendance/project/team?projectId=$projectId"),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $jwtToken',
},
);
if (response.statusCode == 200) {
final json = jsonDecode(response.body);
if (json['success'] == true) {
return json['data'];
} else {
print("Error: ${json['message']}");
}
} else {
print("Error fetching logs: ${response.statusCode}");
}
} catch (e) {
print("Exception while fetching logs: $e");
}
return null;
static String generateImageName(int employeeId, int count) {
final now = DateTime.now();
final dateStr = DateFormat('yyyyMMdd_HHmmss').format(now);
final imageNumber = count.toString().padLeft(3, '0');
return "${employeeId}_${dateStr}_$imageNumber.jpg";
}
}

View File

@ -4,6 +4,7 @@ class AttendanceLogModel {
final DateTime? checkIn;
final DateTime? checkOut;
final int activity;
final int id;
AttendanceLogModel({
required this.name,
@ -11,6 +12,7 @@ class AttendanceLogModel {
this.checkIn,
this.checkOut,
required this.activity,
required this.id,
});
factory AttendanceLogModel.fromJson(Map<String, dynamic> json) {
@ -20,6 +22,7 @@ class AttendanceLogModel {
checkIn: json['checkInTime'] != null ? DateTime.tryParse(json['checkInTime']) : null,
checkOut: json['checkOutTime'] != null ? DateTime.tryParse(json['checkOutTime']) : null,
activity: json['activity'] ?? 0,
id: json['id'] != null ? json['id'] : null,
);
}
}

View File

@ -0,0 +1,24 @@
import 'package:intl/intl.dart';
class AttendanceLogViewModel {
final DateTime? activityTime;
final String? imageUrl;
final String? description;
AttendanceLogViewModel({
this.activityTime,
this.imageUrl,
this.description,
});
factory AttendanceLogViewModel.fromJson(Map<String, dynamic> json) {
return AttendanceLogViewModel(
activityTime: json['activityTime'] != null ? DateTime.tryParse(json['activityTime']) : null,
imageUrl: json['imageUrl'],
description: json['description'],
);
}
String? get formattedDate => activityTime != null ? DateFormat('yyyy-MM-dd').format(activityTime!) : null;
String? get formattedTime => activityTime != null ? DateFormat('hh:mm a').format(activityTime!) : null;
}

View File

@ -1,28 +1,34 @@
class EmployeeModel {
final int id;
final int employeeId;
final String name;
final String designation;
final String checkIn;
final String checkOut;
final int actions;
final int activity;
int action;
EmployeeModel({
required this.id,
required this.employeeId,
required this.name,
required this.designation,
required this.checkIn,
required this.checkOut,
required this.actions,
required this.action,
required this.activity,
});
factory EmployeeModel.fromJson(Map<String, dynamic> json) {
return EmployeeModel(
id: json['employeeId'] ?? 0,
id: json['id'] ?? 0,
employeeId: json['employeeId'] ?? 0,
name: '${json['firstName']} ${json['lastName']}',
designation: json['jobRoleName'] ?? '',
checkIn: json['checkIn'] ?? '-', // Make sure your API returns this field
checkOut: json['checkOut'] ?? '-',
actions: json['actions'] ?? 0, // Make sure your API returns this field
action: json['action'] ?? 0, // Make sure your API returns this field
activity: json['activity'] ?? 0,
);
}
}

View File

@ -0,0 +1,25 @@
class RegularizationLogModel {
final String name;
final String role;
final DateTime? checkIn;
final DateTime? checkOut;
final int activity;
RegularizationLogModel({
required this.name,
required this.role,
this.checkIn,
this.checkOut,
required this.activity,
});
factory RegularizationLogModel.fromJson(Map<String, dynamic> json) {
return RegularizationLogModel(
name: "${json['firstName'] ?? ''} ${json['lastName'] ?? ''}".trim(),
role: json['jobRoleName'] ?? '',
checkIn: json['checkInTime'] != null ? DateTime.tryParse(json['checkInTime']) : null,
checkOut: json['checkOutTime'] != null ? DateTime.tryParse(json['checkOutTime']) : null,
activity: json['activity'] ?? 0,
);
}
}

View File

@ -4,7 +4,6 @@ import 'package:get/get.dart';
import 'package:marco/helpers/theme/app_theme.dart';
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
import 'package:marco/helpers/utils/my_shadow.dart';
import 'package:marco/helpers/utils/utils.dart';
import 'package:marco/helpers/widgets/my_breadcrumb.dart';
import 'package:marco/helpers/widgets/my_breadcrumb_item.dart';
import 'package:marco/helpers/widgets/my_card.dart';
@ -60,12 +59,84 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MySpacing.height(flexSpacing),
// Move project selection here
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: MyContainer.bordered(
padding: MySpacing.xy(8, 8),
child: PopupMenuButton<String>(
onSelected: (value) {
setState(() {
attendanceController.selectedProjectId =
value;
attendanceController
.fetchEmployeesByProject(value);
attendanceController
.fetchAttendanceLogs(value);
attendanceController
.fetchAttendanceLogs(value);
});
},
itemBuilder: (BuildContext context) {
if (attendanceController.projects.isEmpty) {
return [
PopupMenuItem<String>(
value: '',
child: MyText.bodySmall('No Data',
fontWeight: 600),
)
];
}
return attendanceController.projects
.map((project) {
return PopupMenuItem<String>(
value: project.id.toString(),
height: 32,
child: MyText.bodySmall(
project.name,
color: theme.colorScheme.onSurface,
fontWeight: 600,
),
);
}).toList();
},
color: theme.cardTheme.color,
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
MyText.labelSmall(
attendanceController.selectedProjectId !=
null
? attendanceController.projects
.firstWhereOrNull((proj) =>
proj.id.toString() ==
attendanceController
.selectedProjectId)
?.name ??
'Select a Project'
: 'Select a Project',
color: theme.colorScheme.onSurface,
),
Icon(LucideIcons.chevron_down,
size: 20,
color: theme.colorScheme.onSurface),
],
),
),
),
),
],
),
MySpacing.height(flexSpacing),
MyFlex(
children: [
MyFlexItem(
child: DefaultTabController(
length: 2,
length: 3,
child: MyCard.bordered(
borderRadiusAll: 4,
border:
@ -85,6 +156,7 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
tabs: const [
Tab(text: 'Employee List'),
Tab(text: 'Logs'),
Tab(text: 'Regularization'),
],
),
MySpacing.height(16),
@ -93,7 +165,8 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
child: TabBarView(
children: [
employeeListTab(),
reportsTab(),
reportsTab(context),
regularizationTab(context),
],
),
),
@ -115,135 +188,10 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
}
Widget employeeListTab() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: MyContainer.bordered(
padding: MySpacing.xy(4, 8),
child: PopupMenuButton<String>(
onSelected: (value) {
setState(() {
attendanceController.selectedProjectId = value;
attendanceController.fetchEmployeesByProject(value);
attendanceController.fetchAttendanceLogs(value);
});
},
itemBuilder: (BuildContext context) {
return attendanceController.projects.map((project) {
return PopupMenuItem<String>(
value: project.id.toString(),
height: 32,
child: MyText.bodySmall(
project.name,
color: theme.colorScheme.onSurface,
fontWeight: 600,
),
);
}).toList();
},
color: theme.cardTheme.color,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
MyText.labelSmall(
attendanceController.selectedProjectId != null
? attendanceController.projects
.firstWhereOrNull((proj) =>
proj.id.toString() ==
attendanceController.selectedProjectId)
?.name ??
'Select a Project'
: 'Select a Project',
color: theme.colorScheme.onSurface,
),
Icon(LucideIcons.chevron_down,
size: 16, color: theme.colorScheme.onSurface),
],
),
),
),
),
],
),
MySpacing.height(24),
attendanceController.employees.isEmpty
? const Center(child: CircularProgressIndicator())
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: DataTable(
sortAscending: true,
columnSpacing: 15,
onSelectAll: (_) => {},
headingRowColor: WidgetStatePropertyAll(
contentTheme.primary.withAlpha(40)),
dataRowMaxHeight: 60,
showBottomBorder: true,
clipBehavior: Clip.antiAliasWithSaveLayer,
border: TableBorder.all(
borderRadius: BorderRadius.circular(4),
style: BorderStyle.solid,
width: 0.4,
color: Colors.grey,
),
columns: [
DataColumn(
label: MyText.labelLarge('Name',
color: contentTheme.primary)),
DataColumn(
label: MyText.labelLarge('Designation',
color: contentTheme.primary)),
DataColumn(
label: MyText.labelLarge('Actions',
color: contentTheme.primary)),
],
rows: attendanceController.employees
.mapIndexed((index, employee) => DataRow(cells: [
DataCell(MyText.bodyMedium(employee.name,
fontWeight: 600)),
DataCell(MyText.bodyMedium(employee.designation,
fontWeight: 600)),
DataCell(
ElevatedButton(
onPressed: () async {
final success = await attendanceController
.captureAndUploadAttendance(
employee.id,
int.parse(attendanceController
.selectedProjectId ??
"0"),
comment: "Checked in via app",
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
success
? 'Image uploaded successfully!'
: 'Image upload failed.',
),
),
);
},
child: const Text('Check In'),
),
),
]))
.toList(),
),
),
],
);
}
Widget reportsTab() {
if (attendanceController.attendanceLogs.isEmpty) {
attendanceController
.fetchAttendanceLogs(attendanceController.selectedProjectId);
return const Center(child: CircularProgressIndicator());
if (attendanceController.employees.isEmpty) {
return Center(
child: MyText.bodySmall("No Employees Found", fontWeight: 600),
);
}
return SingleChildScrollView(
@ -266,42 +214,348 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
DataColumn(
label: MyText.labelLarge('Name', color: contentTheme.primary)),
DataColumn(
label: MyText.labelLarge('Role', color: contentTheme.primary)),
label: MyText.labelLarge('Designation',
color: contentTheme.primary)),
DataColumn(
label:
MyText.labelLarge('Check-In', color: contentTheme.primary)),
DataColumn(
label:
MyText.labelLarge('Check-Out', color: contentTheme.primary)),
DataColumn(
label: MyText.labelLarge('Action', color: contentTheme.primary)),
label: MyText.labelLarge('Actions', color: contentTheme.primary)),
],
rows: attendanceController.attendanceLogs
.mapIndexed((index, log) => DataRow(cells: [
DataCell(MyText.bodyMedium(log.name, fontWeight: 600)),
DataCell(MyText.bodyMedium(log.role, fontWeight: 600)),
DataCell(MyText.bodyMedium(
log.checkIn != null
? DateFormat('dd MMM yyyy hh:mm a').format(log.checkIn!)
: '-',
fontWeight: 600,
)),
DataCell(MyText.bodyMedium(
log.checkOut != null
? DateFormat('dd MMM yyyy hh:mm a')
.format(log.checkOut!)
: '-',
fontWeight: 600,
)),
DataCell(IconButton(
icon: Icon(Icons.info_outline, color: contentTheme.primary),
onPressed: () {
// Action logic here
},
)),
]))
.toList(),
rows: attendanceController.employees.mapIndexed((index, employee) {
// Set actionText directly from employee's action
String actionText = "";
int? activity =
employee.activity; // Assuming employee has an 'action' field
// Set action text based on employee's activity value
if (activity == 1) {
actionText = "Check In";
} else if (activity == 0) {
actionText = "Check Out";
} else if (activity == 4) {
// Activity 4 logic
actionText = "Check In";
}
return DataRow(cells: [
DataCell(MyText.bodyMedium(employee.name, fontWeight: 600)),
DataCell(MyText.bodyMedium(employee.designation, fontWeight: 600)),
DataCell(ElevatedButton(
onPressed: () async {
if (attendanceController.selectedProjectId == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Please select a project first")),
);
return;
}
// Determine the updated action based on current activity
int updatedAction;
String actionText;
if (activity == 0 || activity == 4) {
// The user is currently checked in (activity == 0), so they need to check out
updatedAction = 0;
actionText = "Check In";
} else {
// The user is currently checked out (activity == 1), so they need to check in
updatedAction = 1;
actionText = "Check Out";
}
// Call the method to capture attendance with the updated action
final success =
await attendanceController.captureAndUploadAttendance(
employee.id, // Pass the employee's ID
employee.employeeId,
int.parse(attendanceController
.selectedProjectId!), // Pass the selected project ID
comment: actionText, // Action text (Check In / Check Out)
action: updatedAction,
);
// Show success or failure message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
success
? 'Attendance marked successfully!'
: 'Image upload failed.',
),
),
);
if (success) {
// Fetch the updated list of employees and logs after the attendance upload
attendanceController.fetchEmployeesByProject(
attendanceController.selectedProjectId!);
attendanceController.fetchAttendanceLogs(
attendanceController.selectedProjectId!);
// You can add more fetch calls if necessary, such as regularization logs.
}
},
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(
vertical: 4, horizontal: 6), // Adjust padding
minimumSize: Size(60, 20), // Adjust minimum size for the button
textStyle: TextStyle(fontSize: 12), // Smaller font size
),
child: Text(
activity == 0 || activity == 4 ? 'Check In' : 'Check Out'),
)),
]);
}).toList(),
),
);
}
Widget reportsTab(BuildContext context) {
final attendanceController = Get.find<AttendanceController>();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextButton.icon(
icon: Icon(Icons.date_range),
label: Text("Select Date Range for Attendance"),
onPressed: () => attendanceController.selectDateRangeForAttendance(
context, attendanceController),
),
),
if (attendanceController.attendanceLogs.isEmpty)
Expanded(
child: Center(
child: MyText.bodySmall("No Attendance Records Found",
fontWeight: 600),
),
)
else
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: DataTable(
sortAscending: true,
columnSpacing: 15,
headingRowColor:
WidgetStatePropertyAll(contentTheme.primary.withAlpha(40)),
dataRowMaxHeight: 60,
showBottomBorder: true,
clipBehavior: Clip.antiAliasWithSaveLayer,
border: TableBorder.all(
borderRadius: BorderRadius.circular(4),
style: BorderStyle.solid,
width: 0.4,
color: Colors.grey,
),
columns: [
DataColumn(
label: MyText.labelLarge('Name',
color: contentTheme.primary)),
DataColumn(
label: MyText.labelLarge('Role',
color: contentTheme.primary)),
DataColumn(
label: MyText.labelLarge('Check-In',
color: contentTheme.primary)),
DataColumn(
label: MyText.labelLarge('Check-Out',
color: contentTheme.primary)),
DataColumn(
label: MyText.labelLarge('Action',
color: contentTheme.primary)),
],
rows: attendanceController.attendanceLogs
.mapIndexed((index, log) => DataRow(cells: [
DataCell(
MyText.bodyMedium(log.name, fontWeight: 600)),
DataCell(
MyText.bodyMedium(log.role, fontWeight: 600)),
DataCell(MyText.bodyMedium(
log.checkIn != null
? DateFormat('dd MMM yyyy hh:mm a')
.format(log.checkIn!)
: '-',
fontWeight: 600,
)),
DataCell(MyText.bodyMedium(
log.checkOut != null
? DateFormat('dd MMM yyyy hh:mm a')
.format(log.checkOut!)
: '-',
fontWeight: 600,
)),
DataCell(
ElevatedButton(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(
vertical: 4,
horizontal: 6), // Adjust padding
minimumSize:
Size(60, 20), // Adjust minimum size
textStyle: TextStyle(
fontSize: 12), // Smaller font size
),
onPressed: () async {
// Call fetchLogsView to load the log data
await attendanceController.fetchLogsView(log.id
.toString()); // Assuming `log.id` is available
// Open the bottom sheet to display the log details
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(16)),
),
backgroundColor: theme.cardTheme.color,
builder: (context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
MyText.titleMedium(
"Attendance Log Details",
fontWeight: 700),
const SizedBox(height: 16),
// Display the log details
if (attendanceController
.attendenceLogsView.isNotEmpty)
...attendanceController
.attendenceLogsView
.map((log) => Column(
crossAxisAlignment:
CrossAxisAlignment
.start,
children: [
MyText.bodyMedium(
"Date: ${log.formattedDate ?? '-'}",
fontWeight: 600,
),
MyText.bodyMedium(
"Time: ${log.formattedTime ?? '-'}",
fontWeight: 600,
),
MyText.bodyMedium(
"Description: ${log.description ?? '-'}",
fontWeight: 600,
),
const Divider(
thickness: 1,
height: 24),
],
)),
Align(
alignment: Alignment.centerRight,
child: ElevatedButton(
onPressed: () =>
Navigator.pop(context),
child: const Text("Close"),
),
)
],
),
);
},
);
},
child: const Text('View'),
),
)
]))
.toList(),
),
),
),
],
);
}
Widget regularizationTab(BuildContext context) {
final attendanceController = Get.find<AttendanceController>();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
),
if (attendanceController.regularizationLogs.isEmpty)
Expanded(
child: Center(
child: MyText.bodySmall("No Regularization Records Found",
fontWeight: 600),
),
)
else
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: DataTable(
sortAscending: true,
columnSpacing: 15,
headingRowColor:
WidgetStatePropertyAll(contentTheme.primary.withAlpha(40)),
dataRowMaxHeight: 60,
showBottomBorder: true,
clipBehavior: Clip.antiAliasWithSaveLayer,
border: TableBorder.all(
borderRadius: BorderRadius.circular(4),
style: BorderStyle.solid,
width: 0.4,
color: Colors.grey,
),
columns: [
DataColumn(
label: MyText.labelLarge('Name',
color: contentTheme.primary)),
DataColumn(
label: MyText.labelLarge('Role',
color: contentTheme.primary)),
DataColumn(
label: MyText.labelLarge('Check-In',
color: contentTheme.primary)),
DataColumn(
label: MyText.labelLarge('Check-Out',
color: contentTheme.primary)),
DataColumn(
label: MyText.labelLarge('Action',
color: contentTheme.primary)),
],
rows: attendanceController.regularizationLogs
.mapIndexed((index, log) => DataRow(cells: [
DataCell(
MyText.bodyMedium(log.name, fontWeight: 600)),
DataCell(
MyText.bodyMedium(log.role, fontWeight: 600)),
DataCell(MyText.bodyMedium(
log.checkIn != null
? DateFormat('dd MMM yyyy hh:mm a')
.format(log.checkIn!)
: '-',
fontWeight: 600,
)),
DataCell(MyText.bodyMedium(
log.checkOut != null
? DateFormat('dd MMM yyyy hh:mm a')
.format(log.checkOut!)
: '-',
fontWeight: 600,
)),
DataCell(IconButton(
icon: Icon(Icons.info_outline,
color: contentTheme.primary),
onPressed: () {
// Add action logic
},
)),
]))
.toList(),
),
),
),
],
);
}
}