842 lines
37 KiB
Dart
842 lines
37 KiB
Dart
import 'package:flutter/material.dart';
|
|
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/widgets/my_card.dart';
|
|
import 'package:marco/helpers/widgets/my_container.dart';
|
|
import 'package:marco/helpers/widgets/my_flex.dart';
|
|
import 'package:marco/helpers/widgets/my_flex_item.dart';
|
|
import 'package:marco/helpers/widgets/my_spacing.dart';
|
|
import 'package:marco/helpers/widgets/my_text.dart';
|
|
import 'package:marco/controller/dashboard/attendance_screen_controller.dart';
|
|
import 'package:marco/controller/permission_controller.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:marco/helpers/widgets/avatar.dart';
|
|
import 'package:marco/model/attendance/log_details_view.dart';
|
|
import 'package:marco/model/attendance/attendence_action_button.dart';
|
|
import 'package:marco/model/attendance/regualrize_action_button.dart';
|
|
import 'package:marco/model/attendance/attendence_filter_sheet.dart';
|
|
import 'package:marco/controller/project_controller.dart';
|
|
|
|
class AttendanceScreen extends StatefulWidget {
|
|
AttendanceScreen({super.key});
|
|
|
|
@override
|
|
State<AttendanceScreen> createState() => _AttendanceScreenState();
|
|
}
|
|
|
|
class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
|
|
final AttendanceController attendanceController =
|
|
Get.put(AttendanceController());
|
|
final PermissionController permissionController =
|
|
Get.put(PermissionController());
|
|
|
|
String selectedTab = 'todaysAttendance';
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
final projectController = Get.find<ProjectController>();
|
|
final attendanceController = Get.find<AttendanceController>();
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
// Listen for future changes in selected project
|
|
ever<String?>(projectController.selectedProjectId!, (projectId) async {
|
|
if (projectId != null && projectId.isNotEmpty) {
|
|
try {
|
|
await attendanceController.loadAttendanceData(projectId);
|
|
attendanceController.update(['attendance_dashboard_controller']);
|
|
} catch (e) {
|
|
debugPrint("Error updating data on project change: $e");
|
|
}
|
|
}
|
|
});
|
|
|
|
// Load data initially if project is already selected
|
|
final initialProjectId = projectController.selectedProjectId?.value;
|
|
if (initialProjectId != null && initialProjectId.isNotEmpty) {
|
|
try {
|
|
await attendanceController.loadAttendanceData(initialProjectId);
|
|
attendanceController.update(['attendance_dashboard_controller']);
|
|
} catch (e) {
|
|
debugPrint("Error loading initial data: $e");
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: PreferredSize(
|
|
preferredSize: const Size.fromHeight(80),
|
|
child: AppBar(
|
|
backgroundColor: const Color(0xFFF5F5F5),
|
|
elevation: 0.5,
|
|
foregroundColor: Colors.black,
|
|
titleSpacing: 0,
|
|
leading: IconButton(
|
|
icon: const Icon(Icons.arrow_back_ios_new,
|
|
color: Colors.black, size: 20),
|
|
onPressed: () {
|
|
Get.offNamed('/dashboard');
|
|
},
|
|
),
|
|
title: Padding(
|
|
padding: const EdgeInsets.only(top: 12.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
MyText.titleLarge(
|
|
'Attendance',
|
|
fontWeight: 700,
|
|
color: Colors.black,
|
|
),
|
|
const SizedBox(height: 4),
|
|
GetBuilder<ProjectController>(
|
|
builder: (projectController) {
|
|
final projectName =
|
|
projectController.selectedProject?.name ??
|
|
'Select Project';
|
|
return MyText.bodySmall(
|
|
projectName,
|
|
fontWeight: 600,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
color: Colors.grey[700],
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
body: SafeArea(
|
|
child: SingleChildScrollView(
|
|
padding: MySpacing.x(0),
|
|
child: GetBuilder<AttendanceController>(
|
|
init: attendanceController,
|
|
tag: 'attendance_dashboard_controller',
|
|
builder: (controller) {
|
|
final selectedProjectId =
|
|
Get.find<ProjectController>().selectedProjectId?.value;
|
|
|
|
final bool noProjectSelected =
|
|
selectedProjectId == null || selectedProjectId.isEmpty;
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
MySpacing.height(flexSpacing),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
MyText.bodyMedium("Filter", fontWeight: 600),
|
|
Tooltip(
|
|
message: 'Filter Project',
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(24),
|
|
onTap: () async {
|
|
final result = await showModalBottomSheet<
|
|
Map<String, dynamic>>(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
backgroundColor: Colors.white,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(
|
|
top: Radius.circular(12)),
|
|
),
|
|
builder: (context) => AttendanceFilterBottomSheet(
|
|
controller: attendanceController,
|
|
permissionController: permissionController,
|
|
selectedTab: selectedTab,
|
|
),
|
|
);
|
|
|
|
if (result != null) {
|
|
final selectedProjectId =
|
|
Get.find<ProjectController>()
|
|
.selectedProjectId
|
|
?.value;
|
|
|
|
final selectedView =
|
|
result['selectedTab'] as String?;
|
|
|
|
if (selectedProjectId != null) {
|
|
try {
|
|
await attendanceController
|
|
.fetchEmployeesByProject(
|
|
selectedProjectId);
|
|
await attendanceController
|
|
.fetchAttendanceLogs(selectedProjectId);
|
|
await attendanceController
|
|
.fetchRegularizationLogs(
|
|
selectedProjectId);
|
|
await attendanceController
|
|
.fetchProjectData(selectedProjectId);
|
|
} catch (_) {}
|
|
attendanceController.update(
|
|
['attendance_dashboard_controller']);
|
|
}
|
|
|
|
if (selectedView != null &&
|
|
selectedView != selectedTab) {
|
|
setState(() {
|
|
selectedTab = selectedView;
|
|
});
|
|
}
|
|
}
|
|
},
|
|
child: MouseRegion(
|
|
cursor: SystemMouseCursors.click,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Icon(
|
|
Icons.filter_list_alt,
|
|
color: Colors.blueAccent,
|
|
size: 28,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 4),
|
|
MyText.bodyMedium("Refresh", fontWeight: 600),
|
|
Tooltip(
|
|
message: 'Refresh Data',
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(24),
|
|
onTap: () async {
|
|
final projectId = Get.find<ProjectController>()
|
|
.selectedProjectId
|
|
?.value;
|
|
if (projectId != null && projectId.isNotEmpty) {
|
|
try {
|
|
await attendanceController
|
|
.loadAttendanceData(projectId);
|
|
attendanceController.update(
|
|
['attendance_dashboard_controller']);
|
|
} catch (e) {
|
|
debugPrint("Error refreshing data: $e");
|
|
}
|
|
}
|
|
},
|
|
child: MouseRegion(
|
|
cursor: SystemMouseCursors.click,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Icon(
|
|
Icons.refresh,
|
|
color: Colors.green,
|
|
size: 28,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
MySpacing.height(flexSpacing),
|
|
MyFlex(children: [
|
|
MyFlexItem(
|
|
sizes: 'lg-12 md-12 sm-12',
|
|
child: noProjectSelected
|
|
? Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24.0),
|
|
child: MyText.titleMedium(
|
|
'No Records Found',
|
|
fontWeight: 600,
|
|
color: Colors.grey[600],
|
|
),
|
|
),
|
|
)
|
|
: selectedTab == 'todaysAttendance'
|
|
? employeeListTab()
|
|
: selectedTab == 'attendanceLogs'
|
|
? employeeLog()
|
|
: regularizationScreen(),
|
|
),
|
|
]),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
String _formatDate(DateTime date) {
|
|
return "${date.day}/${date.month}/${date.year}";
|
|
}
|
|
|
|
Widget employeeListTab() {
|
|
return Obx(() {
|
|
final isLoading = attendanceController.isLoadingEmployees.value;
|
|
final employees = attendanceController.employees;
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: MyText.titleMedium(
|
|
"Today's Attendance",
|
|
fontWeight: 600,
|
|
),
|
|
),
|
|
MyText.bodySmall(
|
|
_formatDate(DateTime.now()),
|
|
fontWeight: 600,
|
|
color: Colors.grey[700],
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (isLoading)
|
|
const SizedBox(
|
|
height: 120,
|
|
child: Center(child: CircularProgressIndicator()),
|
|
)
|
|
else if (employees.isEmpty)
|
|
SizedBox(
|
|
height: 120,
|
|
child: Center(
|
|
child: MyText.bodySmall(
|
|
"No Employees Assigned to This Project",
|
|
fontWeight: 600,
|
|
),
|
|
),
|
|
)
|
|
else
|
|
MyCard.bordered(
|
|
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: [
|
|
Avatar(
|
|
firstName: employee.firstName,
|
|
lastName: employee.lastName,
|
|
size: 31,
|
|
),
|
|
MySpacing.width(16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Wrap(
|
|
crossAxisAlignment:
|
|
WrapCrossAlignment.center,
|
|
spacing:
|
|
6, // spacing between name and designation
|
|
children: [
|
|
MyText.bodyMedium(
|
|
employee.name,
|
|
fontWeight: 600,
|
|
overflow: TextOverflow.visible,
|
|
maxLines: null,
|
|
),
|
|
MyText.bodySmall(
|
|
'(${employee.designation})',
|
|
fontWeight: 600,
|
|
overflow: TextOverflow.visible,
|
|
maxLines: null,
|
|
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,
|
|
),
|
|
],
|
|
);
|
|
}),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
});
|
|
}
|
|
|
|
Widget employeeLog() {
|
|
return Obx(() {
|
|
final logs = List.of(attendanceController.attendanceLogs);
|
|
logs.sort((a, b) {
|
|
final aDate = a.checkIn ?? DateTime(0);
|
|
final bDate = b.checkIn ?? DateTime(0);
|
|
return bDate.compareTo(aDate);
|
|
});
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: MyText.titleMedium(
|
|
"Attendance Logs",
|
|
fontWeight: 600,
|
|
),
|
|
),
|
|
Obx(() {
|
|
if (attendanceController.isLoading.value) {
|
|
return const SizedBox(
|
|
height: 20,
|
|
width: 20,
|
|
child: LinearProgressIndicator(),
|
|
);
|
|
}
|
|
final dateFormat = DateFormat('dd MMM yyyy');
|
|
final dateRangeText = attendanceController
|
|
.startDateAttendance !=
|
|
null &&
|
|
attendanceController.endDateAttendance != null
|
|
? '${dateFormat.format(attendanceController.endDateAttendance!)} - ${dateFormat.format(attendanceController.startDateAttendance!)}'
|
|
: 'Select date range';
|
|
|
|
return MyText.bodySmall(
|
|
dateRangeText,
|
|
fontWeight: 600,
|
|
color: Colors.grey[700],
|
|
overflow: TextOverflow.ellipsis,
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
),
|
|
if (attendanceController.isLoadingAttendanceLogs.value)
|
|
const SizedBox(
|
|
height: 120,
|
|
child: Center(child: CircularProgressIndicator()),
|
|
)
|
|
else if (logs.isEmpty)
|
|
SizedBox(
|
|
height: 120,
|
|
child: Center(
|
|
child: MyText.bodySmall(
|
|
"No Attendance Logs Found for this Project",
|
|
fontWeight: 600,
|
|
),
|
|
),
|
|
)
|
|
else
|
|
MyCard.bordered(
|
|
borderRadiusAll: 4,
|
|
border: Border.all(color: Colors.grey.withOpacity(0.2)),
|
|
shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom),
|
|
paddingAll: 8,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
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 showDateHeader =
|
|
index == 0 || currentDate != previousDate;
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
if (showDateHeader)
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
child: MyText.bodyMedium(
|
|
currentDate,
|
|
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),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Flexible(
|
|
child: MyText.bodyMedium(
|
|
employee.name,
|
|
fontWeight: 600,
|
|
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),
|
|
(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),
|
|
thickness: 1,
|
|
height: 1,
|
|
),
|
|
],
|
|
);
|
|
}),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
});
|
|
}
|
|
|
|
Widget regularizationScreen() {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 4.0),
|
|
child: MyText.titleMedium(
|
|
"Regularization Requests",
|
|
fontWeight: 600,
|
|
),
|
|
),
|
|
Obx(() {
|
|
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(
|
|
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: 8,
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Avatar(
|
|
firstName: employee.firstName,
|
|
lastName: employee.lastName,
|
|
size: 31,
|
|
),
|
|
MySpacing.width(16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
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) ...[
|
|
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,
|
|
),
|
|
const SizedBox(width: 8),
|
|
if (employee.checkIn != null) ...[
|
|
AttendanceLogViewButton(
|
|
employee: employee,
|
|
attendanceController:
|
|
attendanceController,
|
|
),
|
|
],
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
if (index != employees.length - 1)
|
|
Divider(
|
|
color: Colors.grey.withOpacity(0.3),
|
|
thickness: 1,
|
|
height: 1,
|
|
),
|
|
],
|
|
);
|
|
}),
|
|
),
|
|
);
|
|
}),
|
|
],
|
|
);
|
|
}
|
|
}
|