marco.pms.mobileapp/lib/view/Attendence/attendance_screen.dart

356 lines
12 KiB
Dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:on_field_work/helpers/utils/mixins/ui_mixin.dart';
import 'package:on_field_work/helpers/widgets/my_flex.dart';
import 'package:on_field_work/helpers/widgets/my_flex_item.dart';
import 'package:on_field_work/helpers/widgets/my_spacing.dart';
import 'package:on_field_work/helpers/widgets/my_text.dart';
import 'package:on_field_work/controller/attendance/attendance_screen_controller.dart';
import 'package:on_field_work/controller/permission_controller.dart';
import 'package:on_field_work/controller/project_controller.dart';
import 'package:on_field_work/view/Attendence/regularization_requests_tab.dart';
import 'package:on_field_work/view/Attendence/attendance_logs_tab.dart';
import 'package:on_field_work/view/Attendence/todays_attendance_tab.dart';
import 'package:on_field_work/helpers/widgets/my_refresh_indicator.dart';
import 'package:on_field_work/helpers/widgets/custom_app_bar.dart';
import 'package:on_field_work/helpers/utils/permission_constants.dart';
import 'package:on_field_work/helpers/widgets/pill_tab_bar.dart';
import 'package:on_field_work/model/attendance/attendence_filter_sheet.dart';
class AttendanceScreen extends StatefulWidget {
const AttendanceScreen({super.key});
@override
State<AttendanceScreen> createState() => _AttendanceScreenState();
}
class _AttendanceScreenState extends State<AttendanceScreen>
with SingleTickerProviderStateMixin, UIMixin {
final attendanceController = Get.put(AttendanceController());
final permissionController = Get.put(PermissionController());
final projectController = Get.put(ProjectController());
late TabController _tabController;
late List<Map<String, String>> _tabs;
bool _tabsInitialized = false;
@override
void initState() {
super.initState();
// Watch permissions loaded
ever(permissionController.permissionsLoaded, (loaded) {
if (loaded == true && !_tabsInitialized) {
_initializeTabs();
setState(() {});
}
});
// Watch project changes to reload data
ever<String>(projectController.selectedProjectId, (projectId) async {
if (projectId.isNotEmpty && _tabsInitialized) {
await _fetchTabData(attendanceController.selectedTab);
}
});
// If permissions are already loaded at init
if (permissionController.permissionsLoaded.value) {
_initializeTabs();
}
}
void _initializeTabs() async {
final allTabs = [
{'label': "Today's", 'value': 'todaysAttendance'},
{'label': "Logs", 'value': 'attendanceLogs'},
{'label': "Regularization", 'value': 'regularizationRequests'},
];
final hasRegularizationPermission =
permissionController.hasPermission(Permissions.regularizeAttendance);
_tabs = allTabs.where((tab) {
return tab['value'] != 'regularizationRequests' ||
hasRegularizationPermission;
}).toList();
_tabController = TabController(length: _tabs.length, vsync: this);
// Keep selectedTab in sync and fetch data on tab change
_tabController.addListener(() async {
if (!_tabController.indexIsChanging) {
final selectedTab = _tabs[_tabController.index]['value']!;
attendanceController.selectedTab = selectedTab;
await _fetchTabData(selectedTab);
}
});
_tabsInitialized = true;
// Load initial data for default tab
final projectId = projectController.selectedProjectId.value;
if (projectId.isNotEmpty) {
final initialTab = _tabs[_tabController.index]['value']!;
attendanceController.selectedTab = initialTab;
await _fetchTabData(initialTab);
}
}
Future<void> _fetchTabData(String tab) async {
final projectId = projectController.selectedProjectId.value;
if (projectId.isEmpty) return;
switch (tab) {
case 'todaysAttendance':
await attendanceController.fetchTodaysAttendance(projectId);
break;
case 'attendanceLogs':
await attendanceController.fetchAttendanceLogs(
projectId,
dateFrom: attendanceController.startDateAttendance.value,
dateTo: attendanceController.endDateAttendance.value,
);
break;
case 'regularizationRequests':
await attendanceController.fetchRegularizationLogs(projectId);
break;
}
}
Future<void> _refreshData() async {
await _fetchTabData(attendanceController.selectedTab);
}
Widget _buildFilterSearchRow() {
return Padding(
padding: MySpacing.xy(8, 8),
child: Row(
children: [
Expanded(
child: SizedBox(
height: 35,
child: Obx(() {
final query = attendanceController.searchQuery.value;
return TextField(
controller: TextEditingController(text: query)
..selection = TextSelection.collapsed(offset: query.length),
onChanged: (value) {
attendanceController.searchQuery.value = value;
},
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
prefixIcon:
const Icon(Icons.search, size: 20, color: Colors.grey),
suffixIcon: query.isNotEmpty
? IconButton(
icon: const Icon(Icons.close,
size: 18, color: Colors.grey),
onPressed: () {
attendanceController.searchQuery.value = '';
},
)
: null,
hintText: 'Search by name',
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide(color: Colors.grey.shade300),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide(color: Colors.grey.shade300),
),
),
);
}),
),
),
MySpacing.width(8),
Container(
height: 35,
width: 35,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(5),
),
child: IconButton(
padding: EdgeInsets.zero,
constraints: BoxConstraints(),
icon: const Icon(Icons.tune, size: 20, color: Colors.black87),
onPressed: () async {
final result = await showModalBottomSheet<Map<String, dynamic>>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
shape: const RoundedRectangleBorder(
borderRadius:
BorderRadius.vertical(top: Radius.circular(5)),
),
builder: (context) => AttendanceFilterBottomSheet(
controller: attendanceController,
permissionController: permissionController,
selectedTab: _tabs[_tabController.index]['value']!,
),
);
if (result != null) {
final selectedProjectId =
projectController.selectedProjectId.value;
final selectedOrgId =
result['selectedOrganization'] as String?;
if (selectedOrgId != null) {
attendanceController.selectedOrganization =
attendanceController.organizations
.firstWhere((o) => o.id == selectedOrgId);
}
if (selectedProjectId.isNotEmpty) {
await _fetchTabData(attendanceController.selectedTab);
}
}
},
),
),
],
),
);
}
Widget _buildNoProjectWidget() {
return Center(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: MyText.titleMedium(
'No Records Found',
fontWeight: 600,
color: Colors.grey[600],
),
),
);
}
Widget _buildTabBarView() {
return TabBarView(
controller: _tabController,
children: _tabs.map((tab) {
switch (tab['value']) {
case 'attendanceLogs':
return AttendanceLogsTab(controller: attendanceController);
case 'regularizationRequests':
return RegularizationRequestsTab(controller: attendanceController);
case 'todaysAttendance':
default:
return TodaysAttendanceTab(controller: attendanceController);
}
}).toList(),
);
}
@override
Widget build(BuildContext context) {
final Color appBarColor = contentTheme.primary;
if (!_tabsInitialized) {
return Scaffold(
appBar: CustomAppBar(
title: "Attendance",
backgroundColor: appBarColor,
onBackPressed: () => Get.toNamed('/dashboard'),
),
body: const Center(child: CircularProgressIndicator()),
);
}
return Scaffold(
appBar: CustomAppBar(
title: "Attendance",
backgroundColor: appBarColor,
onBackPressed: () => Get.toNamed('/dashboard'),
),
body: Stack(
children: [
Container(
height: 80,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
appBarColor,
appBarColor.withOpacity(0.0),
],
),
),
),
SafeArea(
child: GetBuilder<AttendanceController>(
init: attendanceController,
tag: 'attendance_dashboard_controller',
builder: (controller) {
final selectedProjectId =
projectController.selectedProjectId.value;
final noProjectSelected = selectedProjectId.isEmpty;
return MyRefreshIndicator(
onRefresh: _refreshData,
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: MySpacing.zero,
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: PillTabBar(
controller: _tabController,
tabs: _tabs.map((e) => e['label']!).toList(),
selectedColor: contentTheme.primary,
unselectedColor: Colors.grey.shade600,
indicatorColor: contentTheme.primary,
onTap: (index) async {
final selectedTab = _tabs[index]['value']!;
attendanceController.selectedTab = selectedTab;
await _fetchTabData(selectedTab);
},
),
),
_buildFilterSearchRow(),
MyFlex(
children: [
MyFlexItem(
sizes: 'lg-12 md-12 sm-12',
child: noProjectSelected
? _buildNoProjectWidget()
: SizedBox(
height:
MediaQuery.of(context).size.height -
200,
child: _buildTabBarView(),
),
),
],
),
],
),
),
);
},
),
),
],
),
);
}
@override
void dispose() {
_tabController.dispose();
if (Get.isRegistered<AttendanceController>()) {
Get.delete<AttendanceController>();
}
super.dispose();
}
}