UI_STAFF_ATTENDANCE

This commit is contained in:
venbaittech 2025-10-16 15:49:04 +05:30
parent c6a212bc65
commit 659dba0f14
14 changed files with 1563 additions and 40 deletions

File diff suppressed because one or more lines are too long

View File

@ -11,6 +11,8 @@ import '../../presentation/providers/manager_provider.dart';
import '../../presentation/screens/Enquiry/enquiry/tabs.dart';
import '../../presentation/screens/Enquiry/policy_claims_endros/claims.dart';
import '../../presentation/screens/Enquiry/policy_claims_endros/endrosment.dart';
import '../../presentation/screens/StaffAttendance/attendanceAllDetails.dart';
import '../../presentation/screens/StaffAttendance/individual_Attendance.dart';
import '../../presentation/screens/UserManagement/Agent/agent.dart';
import '../../presentation/screens/UserManagement/Agent/agentList.dart';
import '../../presentation/screens/UserManagement/Profile/profile_mobile.dart';
@ -160,6 +162,15 @@ final GoRouter appRouter = GoRouter(
path: AppRoutes.enquiryHandlerLst,
builder: (context, state) => const EnquiryHandler(),
),
GoRoute(
path: AppRoutes.allStaffAttendance,
builder: (context, state) => const AttendanceAllDetails(),
),
GoRoute(
path: AppRoutes.individualAttendance,
builder: (context, state) => const IndividualAttendanceDetails(),
),
],
redirect: (context, state) async {
final loggedIn = await AuthService.isLoggedIn();

View File

@ -13,6 +13,8 @@ class AppRoutes {
static const String tabEnquiry = '/tabEnquiry';
static const String enquiryHandlerLst = '/enquiryHandlerLst';
static const String allStaffAttendance = '/allStaffAttendance';
static const String individualAttendance = '/individualAttendance';
static const String claimlist = '/claimList';
static const String endosement = '/endosement';

View File

@ -586,6 +586,55 @@ class ApiService {
return response;
}
Future<Map<String, dynamic>> fetchAttndanceOFAllStaffList({
required String month,
}) async {
if (_token == null) {
await _initializeToken();
}
final String query;
// if (role == 'manager') {
// query = 'manager_id=$id';
// }
final url = Uri.parse(
'${Env.apiUrl}staff/monthlyLoginCount?month=${month ?? ''}',
);
final headers = {
'Authorization': 'Bearer $_token' ?? '',
'App-Signature': 'nhance-partner-2025-signature-35468846JRhH551HK',
};
final response = await _makeGetRequest(url, headers);
return response;
}
Future<Map<String, dynamic>> fetchAttndanceOfIndiviualStaffList({
String? month,
id,
}) async {
if (_token == null) {
await _initializeToken();
}
final String query;
final url = Uri.parse(
'${Env.apiUrl}staff/staffLoggedDays?month=${month ?? ''}&staff_id=$id',
);
final headers = {
'Authorization': 'Bearer $_token' ?? '',
'App-Signature': 'nhance-partner-2025-signature-35468846JRhH551HK',
};
final response = await _makeGetRequest(url, headers);
return response;
}
Future<Map<String, dynamic>> findSingleEnquiryData(id) async {
// print(_token);
if (_token == null) {

View File

@ -16,14 +16,33 @@ Future<void> _restoreManagerId(WidgetRef ref) async {
final int? savedUserId = prefs.getInt('userId');
final String? savedUserRole = prefs.getString('userRole');
final String? savedAttendanceIndvStaffId = prefs.getString(
'attendanceIndvStaff',
);
final String? savedAttendanceIndvStaffName = prefs.getString(
'attendanceIndvStaffName',
);
if (savedManagerId != null && savedUserId != null) {
ref.read(managerIdProvider.notifier).state = savedManagerId;
ref.read(userIdProvider.notifier).state = savedUserId;
ref.read(userRoleProvider.notifier).state = savedUserRole;
print("Manager ID restored: $savedManagerId");
print("User ID restored: $savedUserId");
print("Role restored: $savedUserRole");
}
if (savedAttendanceIndvStaffId != null &&
savedAttendanceIndvStaffName != null) {
ref.read(staffIndiviualAttendanceIdProvider.notifier).state =
savedAttendanceIndvStaffId;
ref.read(staffIndiviualAttendanceNameIdProvider.notifier).state =
savedAttendanceIndvStaffName;
print("Staff ID restored: $savedAttendanceIndvStaffId");
print("Staff Name restored: $savedAttendanceIndvStaffName");
}
}
Future<void> main() async {

View File

@ -97,12 +97,12 @@ class MainLayout extends StatelessWidget {
body: Row(
children: [
const SizedBox(
width: 90, // fixed width for drawer
width: 100, // fixed width for drawer
child: DrawerMenu(),
),
Expanded(
child: Container(
padding: EdgeInsets.all(10.0),
padding: EdgeInsets.all(2.0),
color: Colors.white,
child: Column(children: [Expanded(child: body)]),
),

View File

@ -5,3 +5,9 @@ final managerIdProvider = StateProvider<int?>((ref) => null);
final userIdProvider = StateProvider<int?>((ref) => null);
final enquiryIdProvider = StateProvider<String?>((ref) => null);
final navFromEnqStaffProvider = StateProvider<String?>((ref) => null);
final staffIndiviualAttendanceIdProvider = StateProvider<String?>(
(ref) => null,
);
final staffIndiviualAttendanceNameIdProvider = StateProvider<String?>(
(ref) => null,
);

View File

@ -0,0 +1,662 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart';
import 'package:intl/intl.dart';
import 'package:nhance_partner/presentation/providers/userRoleProvider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../../core/routing/routes.dart';
import '../../../../core/services/api_service.dart';
import '../../../../data/utils/Pagination.dart';
import '../../../data/utils/validators.dart';
import '../../layouts/main_layout.dart';
import '../../layouts/responsive_layout.dart';
import '../../providers/manager_provider.dart';
import '../../themes/indicators/date_field_theme.dart';
import '../../themes/indicators/export_btn.dart';
import '../../themes/indicators/month_field_theme.dart';
import '../../themes/indicators/search_field_theme.dart';
import '../../widgets/custom_Stdate_EnDate_Filter.dart';
import '../../widgets/custom_action_popup.dart';
import '../../widgets/custom_month_filter.dart';
class AttendanceAllDetails extends ConsumerStatefulWidget {
const AttendanceAllDetails({super.key});
@override
ConsumerState<AttendanceAllDetails> createState() =>
AttendanceAllDetailsState();
}
class AttendanceAllDetailsState extends ConsumerState<AttendanceAllDetails> {
int currentPage = 1;
int itemsPerPage = 10;
late ApiService apiService;
dynamic roleId;
dynamic userId;
final _formKey = GlobalKey<FormState>();
// final monthController = TextEditingController();
final _monthFormKey = GlobalKey<FormState>();
// List<Map<String, dynamic>> dataVal = [];
List<Map<String, dynamic>> getStaffData = [];
List<Map<String, dynamic>> originalData = [];
List<Map<String, dynamic>> filteredData = [];
bool isLoading = false;
Map<String, TextEditingController> controllers = {};
List<String> tabHeader = ['startDate', 'endDate', 'month'];
@override
void initState() {
super.initState();
apiService = ApiService();
for (String field in tabHeader) {
controllers[field] = TextEditingController();
}
Future.microtask(() {
final id = ref.read(managerIdProvider);
roleId = ref.read(userRoleProvider);
userId = ref.read(userIdProvider);
print("A56 => r : $roleId | mId: $id | uId: $userId ");
// Staff - sanjeev.p@venbainfotech.com - T E S T S - 9 evarukku 8th id is manager
// Manager - venbalap08@gmail.com - T E S T M - 8 evarukku 8th id is manager
// Agent - venba2026@gmail.com - T E S T A - 5 evarkku 8th id is manager
// so here i used userId
if (userId != null) {
getStaffList(userId, roleId);
}
});
}
void filterDateRange1() {
print("filterDateRange");
// if (!_formKey.currentState!.validate()) return;
// final fromDate = controllers['startDate']?.text ?? '';
// final toDate = controllers['endDate']?.text ?? '';
print("filterDateRange1");
// Call with selected dates
getStaffList(userId, roleId);
}
void filterMonth() {
print('Month');
// Validate the form first
// if (!_formKey.currentState!.validate()) {
// // stop execution if validation fails
// return;
// }
final fromDateText = controllers['month']?.text ?? '';
print('monthmonth - $fromDateText');
if (fromDateText.isEmpty) {
print('No month selected');
return;
}
try {
// Convert "10-2025" "2025-10"
final parsedDate = DateFormat('MM-yyyy').parse(fromDateText);
final apiMonth = DateFormat('yyyy-MM').format(parsedDate);
print('Converted month for API: $apiMonth');
// Call your API with the converted format
getStaffList(userId, roleId, month: apiMonth);
} catch (e) {
print('Error parsing month: $e');
}
}
void refrshfilterDateRange() {
setState(() {
controllers['month']!.clear();
controllers['month']?.text = '';
// Reset the FormField validation
_formKey.currentState?.reset();
_monthFormKey.currentState?.reset();
});
getStaffList(userId, roleId);
}
Future<void> getStaffList(int managerId, role, {String month = ''}) async {
final monthForApi = month.isNotEmpty
? month
: DateFormat('yyyy-MM').format(DateTime.now());
print('A72 => Fns called => $managerId | $role | month: $monthForApi');
setState(() {
isLoading = true;
});
try {
final response = await apiService.fetchAttndanceOFAllStaffList(
month: monthForApi,
// toDate: controllers['endDate']?.text ?? '',
);
if (response['status'] == 'success') {
final data = response['data'];
final fromDate = response['month'] ?? '';
print('FromDate : $fromDate');
print('Frdata : $data');
setState(() {
print('test 1');
if (fromDate != null && fromDate.isNotEmpty) {
// Parse the string to DateTime first
final parsedDate = DateFormat('yyyy-MM').parse(fromDate);
final monthText = DateFormat('MM-yyyy').format(parsedDate);
controllers['month']?.text = monthText;
}
// controllers['endDate']?.text = toDate;
// if (data is List) {
// getStaffData = List<Map<String, dynamic>>.from(data);
// } else if (data is Map) {
// getStaffData = [Map<String, dynamic>.from(data)];
// } else {
// getStaffData = [];
// }
// if (data is List) {
// // Safely map each item to Map<String, dynamic>
// // getStaffData = data.map<Map<String, dynamic>>((item) {
// // if (item is Map<String, dynamic>) return item;
// // if (item is Map) return Map<String, dynamic>.from(item);
// // return <String, dynamic>{}; // fallback empty map
// // }).toList();
// // Convert JSArray<dynamic> safely to List<Map<String, dynamic>>
//
// } else if (data is Map) {
// getStaffData = [Map<String, dynamic>.from(data)];
// } else {
// getStaffData = [];
// }
print('test 2');
getStaffData = (data as List)
.map<Map<String, dynamic>>(
(item) => Map<String, dynamic>.from(item),
)
.toList();
print('test 3');
originalData = getStaffData;
filteredData = List.from(originalData);
print('testfilteredData $filteredData');
});
} else {
getStaffData = [];
originalData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
List<dynamic> get _paginatedData {
print('test 31');
// Sort descending by id first
final sortedData = [...filteredData]
..sort((a, b) => int.parse(b['staff_id']) - int.parse(a['staff_id']));
print('test 311- $sortedData');
print('test 311');
if (sortedData.isEmpty) return [];
// Ensure currentPage is valid
final maxPage = (sortedData.length / itemsPerPage).ceil();
final safePage = currentPage.clamp(1, maxPage);
final startIndex = (safePage - 1) * itemsPerPage;
final endIndex = (startIndex + itemsPerPage).clamp(0, sortedData.length);
return sortedData.sublist(startIndex, endIndex);
}
Future<void> handleEdit(item) async {
// Navigator.pop(context);
print('EDITStaff - ${item['staff_id']}');
// dynamic id = data['id'];
// context.go('/tabEnquiry/$id');
final prefs = await SharedPreferences.getInstance();
// Remove old value (if any)
await prefs.remove('attendanceIndvStaff');
await prefs.remove('attendanceIndvStaffName');
// setState(() {
final id = item['staff_id'].toString();
final name = item['staff_name'].toString();
// Save the new id
await prefs.setString('attendanceIndvStaff', id.toString());
await prefs.setString('attendanceIndvStaffName', name.toString());
ref.read(staffIndiviualAttendanceIdProvider.notifier).state = id;
ref.read(staffIndiviualAttendanceNameIdProvider.notifier).state = name;
// });
// update provider
// context.go(AppRoutes.tabEnquiry);
context.go(AppRoutes.individualAttendance);
}
void filterData(String query) {
print("FilterDAta - $query");
setState(() {
print('test 31f');
filteredData = getStaffData.where((item) {
// final isActiveStatus = item['is_active'] == "1" ? "active" : "inactive";
return (item['staff_name'] ?? '-').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['month'] ?? '-').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['login_count'] ?? '-').toLowerCase().contains(
query.toLowerCase(),
);
}).toList();
});
}
final TextEditingController _searchStaffController = TextEditingController();
String _formatDate(String rawDate) {
try {
final dateTime = DateTime.parse(rawDate);
return DateFormat('dd-MM-yyyy HH:mm').format(dateTime); // 24-hour format
} catch (e) {
return rawDate; // fallback if parsing fails
}
}
@override
Widget build(BuildContext context) {
return MainLayout(
title: "Staff Attendance",
body: Container(
// color: Colors.yellow.shade50,
width: MediaQuery.of(context).size.width,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
height: 30,
width: MediaQuery.of(context).size.width,
child: GestureDetector(
onTap: () {
context.go(AppRoutes.dashboard);
},
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Tooltip(
message: 'Back',
child: IconButton(
icon: Icon(
Icons.arrow_left_sharp,
size: ResponsiveLayout.isMobile(context) ? 25 : 25,
color: const Color(0xFF425B5B),
),
onPressed: () {
context.go(AppRoutes.dashboard);
},
splashRadius: 18,
hoverColor: Colors.black12,
padding: const EdgeInsets.all(4),
constraints: const BoxConstraints(),
),
),
const SizedBox(width: 5), // spacing between icon and text
Text(
"Staff Attendance",
style: GoogleFonts.inter(
fontSize: ResponsiveLayout.isMobile(context) ? 14 : 18,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
SizedBox(height: ResponsiveLayout.isMobile(context) ? 5 : 5),
Expanded(
child: Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.all(8.0),
child: _buildContent(context),
),
),
Container(
// height: 20,
width: MediaQuery.of(context).size.width,
// color: Colors.green.shade50,
child: PaginationControls(
currentPage: currentPage,
itemsPerPage: itemsPerPage,
// totalItems: dataVal.length,
totalItems: filteredData.length,
// activeColor: layoutColor, // your theme color
onPageChanged: (page) {
setState(() {
currentPage = page;
});
},
onItemsPerPageChanged: (items) {
setState(() {
itemsPerPage = items;
currentPage = 1;
});
},
),
),
],
),
),
);
}
Widget _buildContent(BuildContext context) {
return Column(
children: [
Container(
// height: 40,
// color: Colors.pink,
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.start,
children: [
MonthFilterRow(
monthController: controllers['month']!,
formKey: _monthFormKey,
onFilter: () {
print(
'Filter triggered with month: ${controllers['month']!.text}',
);
filterMonth();
},
onRefresh: () {
controllers['month']!.clear();
refrshfilterDateRange();
print('Month filter cleared');
},
),
Spacer(),
ThemedSearchField(
hintText: 'Search',
backgroundColor: Color(0xFFF6F8F8),
onChanged: filterData,
controller: _searchStaffController,
txtwidth: ResponsiveLayout.isMobile(context)
? MediaQuery.of(context).size.width * 0.6
: MediaQuery.of(context).size.width * 0.2,
),
ResponsiveLayout.isMobile(context)
? Spacer()
: SizedBox(width: 10),
ExportBtn(
sheetName: "Staff Attendance",
fileName: "staff_attendance_list",
data: filteredData,
txt: !ResponsiveLayout.isMobile(context) ? true : false,
headers: ["staff_id", "staff_name", "month", "login_count"],
),
],
),
),
SizedBox(height: 10),
Container(
decoration: BoxDecoration(
color: Color(0xFFEDF6F5),
borderRadius: BorderRadius.circular(6),
),
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
child: Row(
children: [
Expanded(flex: 1, child: Text('S.No.', style: _headerStyle)),
Expanded(flex: 2, child: Text('Staff Name', style: _headerStyle)),
Expanded(flex: 2, child: Text('Month', style: _headerStyle)),
Expanded(
flex: 2,
child: Text('Login Count', style: _headerStyle),
),
Expanded(flex: 2, child: Text('Action', style: _headerStyle)),
],
),
),
ResponsiveLayout.isMobile(context)
? _buildDataTable(context)
: Expanded(child: _buildDataTable(context)),
],
);
}
Widget _buildDataTable(BuildContext context) {
print('test 32');
if (filteredData.isEmpty) {
return const SizedBox(
height: 50,
child: Center(child: Text('No available data')),
);
}
final sortedData = [..._paginatedData];
print('test 4');
// Desktop: keep ListView.builder
return ListView.builder(
itemCount: sortedData.length + 1,
itemBuilder: (context, index) {
if (index == 0) return _buildHeader();
final startIndex = (currentPage - 1) * itemsPerPage;
final item = sortedData[index - 1];
final sno = startIndex + index;
return _buildDataRow(item, sno);
},
);
}
Widget _buildHeader() {
return SizedBox.shrink();
}
Widget _buildDataRow(Map<String, dynamic> item, sno) {
print('test 5');
return Container(
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 16),
// margin: const EdgeInsets.only(top: 10),
decoration: BoxDecoration(
color: Colors.white,
// color: Color(0xFFE0F7F9),
border: const Border(
bottom: BorderSide(color: Color(0xFFEAEAEA), width: 1),
),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Expanded(flex: 1, child: Text('$sno' ?? '-', style: _dataBold)),
Expanded(
flex: 2,
child: Text(
item['staff_name']?.toString() ?? '-',
style: _dataBold,
),
),
Expanded(
flex: 2,
child: Text(item['month']?.toString() ?? '-', style: _dataBold),
),
Expanded(
flex: 2,
child: Text(
item['login_count']?.toString() ?? '-',
style: _dataBold,
),
),
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Tooltip(
message: 'Edit',
child: IconButton(
icon: Image.asset(
"assets/miscellaneous/Edit.png",
height: 15,
width: 15,
),
onPressed: () {
handleEdit(item);
},
splashRadius: 28,
hoverColor: Colors.black12,
padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(),
),
),
],
),
),
),
],
),
);
}
Widget buildStartDate(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Start Date', style: _textStyle),
SizedBox(height: 5),
ThemedMonthField(
hintText: "Select Month",
txtwidth: MediaQuery.of(context).size.width * 0.27,
txtheight: 40,
backgroundColor: const Color(0xFFECECEC),
validator: (value) => Validators.requiredField(value, "date"),
borderColor: Colors.grey.shade300,
controller: controllers['startDate']!,
onDateSelected: (date) {
print("Picked Date: $date");
controllers['startDate']?.text = DateFormat('MM-yyyy').format(date);
// controllers['date']?.text = date as String;
},
),
],
);
}
Widget buildEndDate(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('End Date', style: _textStyle),
SizedBox(height: 5),
ThemedDateField(
hintText: "Select Date",
txtwidth: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.16,
// txtheight: 50,
// backgroundColor: const Color(0xFFECECEC),
validator: (value) {
if (value == null || value.isEmpty) {
return "End Date is required";
}
final fromText = controllers['startDate']?.text ?? '';
if (fromText.isNotEmpty) {
final startDate = DateFormat('dd-MM-yyyy').parse(fromText);
final endDate = DateFormat('dd-MM-yyyy').parse(value);
if (endDate.isBefore(startDate)) {
return "End Date cannot be earlier than Start Date";
}
}
return null; // no error
},
controller: controllers['endDate']!,
lastDate: DateTime.now(),
onDateSelected: (date) {
print("Picked Date: $date");
controllers['endDate']?.text = DateFormat(
'dd-MM-yyyy',
).format(date);
// controllers['date']?.text = date as String;
},
),
],
);
}
static final _dataBold = GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Color(0xFF000000),
);
static final _dataSub = GoogleFonts.inter(
fontSize: 10,
fontWeight: FontWeight.w300,
color: Color(0xFF585757),
);
// static const _headerStyle = GoogleFonts.inter(
// color: Colors.black,
// fontWeight: FontWeight.bold,
// );
static final _headerStyle = GoogleFonts.inter(
color: Colors.black,
fontWeight: FontWeight.bold,
);
static final _cardheaderStyle = GoogleFonts.inter(
color: Colors.black,
fontWeight: FontWeight.w600,
fontSize: 12,
);
static final _cardBodyStyle = GoogleFonts.inter(
color: Color(0xFF545454),
fontWeight: FontWeight.w400,
fontSize: 12,
);
static const _textStyle = TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
);
}

View File

@ -0,0 +1,602 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart';
import 'package:intl/intl.dart';
import 'package:nhance_partner/presentation/providers/userRoleProvider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../../core/routing/routes.dart';
import '../../../../core/services/api_service.dart';
import '../../../../data/utils/Pagination.dart';
import '../../../data/utils/validators.dart';
import '../../layouts/main_layout.dart';
import '../../layouts/responsive_layout.dart';
import '../../providers/manager_provider.dart';
import '../../themes/indicators/date_field_theme.dart';
import '../../themes/indicators/export_btn.dart';
import '../../themes/indicators/month_field_theme.dart';
import '../../themes/indicators/search_field_theme.dart';
import '../../widgets/custom_Stdate_EnDate_Filter.dart';
import '../../widgets/custom_action_popup.dart';
import '../../widgets/custom_month_filter.dart';
class IndividualAttendanceDetails extends ConsumerStatefulWidget {
const IndividualAttendanceDetails({super.key});
@override
ConsumerState<IndividualAttendanceDetails> createState() =>
IndividualAttendanceDetailsState();
}
class IndividualAttendanceDetailsState
extends ConsumerState<IndividualAttendanceDetails> {
int currentPage = 1;
int itemsPerPage = 10;
late ApiService apiService;
dynamic staffId;
dynamic staffName;
dynamic userId;
final _formKey = GlobalKey<FormState>();
// final monthController = TextEditingController();
final _monthFormKey = GlobalKey<FormState>();
// List<Map<String, dynamic>> dataVal = [];
List<Map<String, dynamic>> getStaffData = [];
List<Map<String, dynamic>> originalData = [];
List<Map<String, dynamic>> filteredData = [];
bool isLoading = false;
Map<String, TextEditingController> controllers = {};
List<String> tabHeader = ['startDate', 'endDate', 'month'];
@override
void initState() {
super.initState();
apiService = ApiService();
for (String field in tabHeader) {
controllers[field] = TextEditingController();
}
Future.microtask(() {
final id = ref.read(managerIdProvider);
staffId = ref.read(staffIndiviualAttendanceIdProvider);
staffName = ref.read(staffIndiviualAttendanceNameIdProvider);
userId = ref.read(userIdProvider);
print(
"A56 => r : $staffId | mId: $id | uId: $userId |staffName : $staffName",
);
// Staff - sanjeev.p@venbainfotech.com - T E S T S - 9 evarukku 8th id is manager
// Manager - venbalap08@gmail.com - T E S T M - 8 evarukku 8th id is manager
// Agent - venba2026@gmail.com - T E S T A - 5 evarkku 8th id is manager
// so here i used userId
if (staffId != null && staffName != null) {
getStaffList(userId, staffId);
}
});
}
void filterDateRange1() {
print("filterDateRange");
// if (!_formKey.currentState!.validate()) return;
// final fromDate = controllers['startDate']?.text ?? '';
// final toDate = controllers['endDate']?.text ?? '';
print("filterDateRange1");
// Call with selected dates
getStaffList(userId, staffId);
}
void filterMonth() {
print('Month');
// Validate the form first
// if (!_formKey.currentState!.validate()) {
// // stop execution if validation fails
// return;
// }
final fromDateText = controllers['month']?.text ?? '';
print('monthmonth - $fromDateText');
if (fromDateText.isEmpty) {
print('No month selected');
return;
}
try {
// Convert "10-2025" "2025-10"
final parsedDate = DateFormat('MM-yyyy').parse(fromDateText);
final apiMonth = DateFormat('yyyy-MM').format(parsedDate);
print('Converted month for API: $apiMonth');
// Call your API with the converted format
getStaffList(userId, staffId, month: apiMonth);
} catch (e) {
print('Error parsing month: $e');
}
}
void refrshfilterDateRange() {
setState(() {
controllers['month']!.clear();
controllers['month']?.text = '';
// Reset the FormField validation
_formKey.currentState?.reset();
_monthFormKey.currentState?.reset();
});
getStaffList(userId, staffId);
}
Future<void> getStaffList(int managerId, role, {String month = ''}) async {
final monthForApi = month.isNotEmpty
? month
: DateFormat('yyyy-MM').format(DateTime.now());
print('A72 => Fns called => $managerId | $role | month: $monthForApi');
setState(() {
isLoading = true;
});
try {
final response = await apiService.fetchAttndanceOfIndiviualStaffList(
month: monthForApi,
id: staffId,
// toDate: controllers['endDate']?.text ?? '',
);
if (response['status'] == 'success') {
final data = response['data'];
final fromDate = response['month'] ?? '';
print('FromDate : $fromDate');
print('Frdata : $data');
setState(() {
print('test 1');
if (fromDate != null && fromDate.isNotEmpty) {
// Parse the string to DateTime first
final parsedDate = DateFormat('yyyy-MM').parse(fromDate);
final monthText = DateFormat('MM-yyyy').format(parsedDate);
controllers['month']?.text = monthText;
}
print('test 2');
getStaffData = (data as List)
.map<Map<String, dynamic>>(
(item) => Map<String, dynamic>.from(item),
)
.toList();
print('test 3');
originalData = getStaffData;
filteredData = List.from(originalData);
print('testfilteredData $filteredData');
});
} else {
getStaffData = [];
originalData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
List<dynamic> get _paginatedData {
print('test 31');
// Sort descending by id first
final sortedData = [...filteredData]
..sort((a, b) => int.parse(b['id']) - int.parse(a['id']));
print('test 311- $sortedData');
print('test 311');
if (sortedData.isEmpty) return [];
// Ensure currentPage is valid
final maxPage = (sortedData.length / itemsPerPage).ceil();
final safePage = currentPage.clamp(1, maxPage);
final startIndex = (safePage - 1) * itemsPerPage;
final endIndex = (startIndex + itemsPerPage).clamp(0, sortedData.length);
return sortedData.sublist(startIndex, endIndex);
}
Future<void> handleEdit(item) async {
// Navigator.pop(context);
print('EDITStaff - ${item['id']}');
// dynamic id = data['id'];
// context.go('/tabEnquiry/$id');
final prefs = await SharedPreferences.getInstance();
// Remove old value (if any)
await prefs.remove('enqAgentDataId');
// setState(() {
final id = item['id'].toString();
// Save the new id
await prefs.setString('enqAgentDataId', id.toString());
ref.read(enquiryIdProvider.notifier).state = id;
// });
// update provider
context.go(AppRoutes.tabEnquiry);
}
void filterData(String query) {
print("FilterDAta - $query");
setState(() {
print('test 31f');
filteredData = getStaffData.where((item) {
// final isActiveStatus = item['is_active'] == "1" ? "active" : "inactive";
return (item['login_date'] ?? '-').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['logout_date'] ?? '-').toLowerCase().contains(
query.toLowerCase(),
);
}).toList();
});
}
final TextEditingController _searchStaffController = TextEditingController();
String _formatDate(String rawDate) {
try {
final dateTime = DateTime.parse(rawDate);
return DateFormat('dd-MM-yyyy HH:mm').format(dateTime); // 24-hour format
} catch (e) {
return rawDate; // fallback if parsing fails
}
}
@override
Widget build(BuildContext context) {
return MainLayout(
title: "Staff Attendance",
body: Container(
// color: Colors.yellow.shade50,
width: MediaQuery.of(context).size.width,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
height: 30,
width: MediaQuery.of(context).size.width,
child: GestureDetector(
onTap: () {
context.go(AppRoutes.allStaffAttendance);
},
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Tooltip(
message: 'Back',
child: IconButton(
icon: Icon(
Icons.arrow_left_sharp,
size: ResponsiveLayout.isMobile(context) ? 25 : 25,
color: const Color(0xFF425B5B),
),
onPressed: () async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('attendanceIndvStaff');
await prefs.remove('attendanceIndvStaffName');
context.go(AppRoutes.allStaffAttendance);
},
splashRadius: 18,
hoverColor: Colors.black12,
padding: const EdgeInsets.all(4),
constraints: const BoxConstraints(),
),
),
const SizedBox(width: 5), // spacing between icon and text
Text(
"Attendance For $staffName",
style: GoogleFonts.inter(
fontSize: ResponsiveLayout.isMobile(context) ? 14 : 18,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
SizedBox(height: ResponsiveLayout.isMobile(context) ? 5 : 5),
Expanded(
child: Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.all(8.0),
child: _buildContent(context),
),
),
Container(
// height: 20,
width: MediaQuery.of(context).size.width,
// color: Colors.green.shade50,
child: PaginationControls(
currentPage: currentPage,
itemsPerPage: itemsPerPage,
// totalItems: dataVal.length,
totalItems: filteredData.length,
// activeColor: layoutColor, // your theme color
onPageChanged: (page) {
setState(() {
currentPage = page;
});
},
onItemsPerPageChanged: (items) {
setState(() {
itemsPerPage = items;
currentPage = 1;
});
},
),
),
],
),
),
);
}
Widget _buildContent(BuildContext context) {
return Column(
children: [
Container(
// height: 40,
// color: Colors.pink,
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.start,
children: [
MonthFilterRow(
monthController: controllers['month']!,
formKey: _monthFormKey,
onFilter: () {
print(
'Filter triggered with month: ${controllers['month']!.text}',
);
filterMonth();
},
onRefresh: () {
controllers['month']!.clear();
refrshfilterDateRange();
print('Month filter cleared');
},
),
Spacer(),
ThemedSearchField(
hintText: 'Search',
backgroundColor: Color(0xFFF6F8F8),
onChanged: filterData,
controller: _searchStaffController,
txtwidth: ResponsiveLayout.isMobile(context)
? MediaQuery.of(context).size.width * 0.6
: MediaQuery.of(context).size.width * 0.2,
),
ResponsiveLayout.isMobile(context)
? Spacer()
: SizedBox(width: 10),
ExportBtn(
sheetName: "Staff Individual Attendance",
fileName: "staff_individual_attendance_list",
data: filteredData,
txt: !ResponsiveLayout.isMobile(context) ? true : false,
headers: ["id", "login_date", "logout_date"],
),
],
),
),
SizedBox(height: 10),
Container(
decoration: BoxDecoration(
color: Color(0xFFEDF6F5),
borderRadius: BorderRadius.circular(6),
),
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
child: Row(
children: [
Expanded(flex: 1, child: Text('S.No.', style: _headerStyle)),
Expanded(flex: 2, child: Text('Login Date', style: _headerStyle)),
Expanded(
flex: 2,
child: Text('Logout Date', style: _headerStyle),
),
],
),
),
ResponsiveLayout.isMobile(context)
? _buildDataTable(context)
: Expanded(child: _buildDataTable(context)),
],
);
}
Widget _buildDataTable(BuildContext context) {
print('test 32');
if (filteredData.isEmpty) {
return const SizedBox(
height: 50,
child: Center(child: Text('No available data')),
);
}
final sortedData = [..._paginatedData];
print('test 4');
// Desktop: keep ListView.builder
return ListView.builder(
itemCount: sortedData.length + 1,
itemBuilder: (context, index) {
if (index == 0) return _buildHeader();
final startIndex = (currentPage - 1) * itemsPerPage;
final item = sortedData[index - 1];
final sno = startIndex + index;
return _buildDataRow(item, sno);
},
);
}
Widget _buildHeader() {
return SizedBox.shrink();
}
Widget _buildDataRow(Map<String, dynamic> item, sno) {
print('test 5');
return Container(
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 16),
// margin: const EdgeInsets.only(top: 10),
decoration: BoxDecoration(
color: Colors.white,
// color: Color(0xFFE0F7F9),
border: const Border(
bottom: BorderSide(color: Color(0xFFEAEAEA), width: 1),
),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Expanded(flex: 1, child: Text('$sno' ?? '-', style: _dataBold)),
Expanded(
flex: 2,
child: Text(item['login_date'] ?? '-', style: _dataBold),
),
Expanded(
flex: 2,
child: Text(item['logout_date'] ?? '-', style: _dataBold),
),
],
),
);
}
Widget buildStartDate(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Start Date', style: _textStyle),
SizedBox(height: 5),
ThemedMonthField(
hintText: "Select Month",
txtwidth: MediaQuery.of(context).size.width * 0.27,
txtheight: 40,
backgroundColor: const Color(0xFFECECEC),
validator: (value) => Validators.requiredField(value, "date"),
borderColor: Colors.grey.shade300,
controller: controllers['startDate']!,
onDateSelected: (date) {
print("Picked Date: $date");
controllers['startDate']?.text = DateFormat('MM-yyyy').format(date);
// controllers['date']?.text = date as String;
},
),
],
);
}
Widget buildEndDate(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('End Date', style: _textStyle),
SizedBox(height: 5),
ThemedDateField(
hintText: "Select Date",
txtwidth: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.16,
// txtheight: 50,
// backgroundColor: const Color(0xFFECECEC),
validator: (value) {
if (value == null || value.isEmpty) {
return "End Date is required";
}
final fromText = controllers['startDate']?.text ?? '';
if (fromText.isNotEmpty) {
final startDate = DateFormat('dd-MM-yyyy').parse(fromText);
final endDate = DateFormat('dd-MM-yyyy').parse(value);
if (endDate.isBefore(startDate)) {
return "End Date cannot be earlier than Start Date";
}
}
return null; // no error
},
controller: controllers['endDate']!,
lastDate: DateTime.now(),
onDateSelected: (date) {
print("Picked Date: $date");
controllers['endDate']?.text = DateFormat(
'dd-MM-yyyy',
).format(date);
// controllers['date']?.text = date as String;
},
),
],
);
}
static final _dataBold = GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Color(0xFF000000),
);
static final _dataSub = GoogleFonts.inter(
fontSize: 10,
fontWeight: FontWeight.w300,
color: Color(0xFF585757),
);
// static const _headerStyle = GoogleFonts.inter(
// color: Colors.black,
// fontWeight: FontWeight.bold,
// );
static final _headerStyle = GoogleFonts.inter(
color: Colors.black,
fontWeight: FontWeight.bold,
);
static final _cardheaderStyle = GoogleFonts.inter(
color: Colors.black,
fontWeight: FontWeight.w600,
fontSize: 12,
);
static final _cardBodyStyle = GoogleFonts.inter(
color: Color(0xFF545454),
fontWeight: FontWeight.w400,
fontSize: 12,
);
static const _textStyle = TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
);
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:jwt_decode/jwt_decode.dart';
import 'package:nhance_partner/presentation/providers/userRoleProvider.dart';
import '../../../../core/services/api_service.dart';
import '../../../../data/services/auth_service.dart';
@ -25,6 +26,7 @@ class ProfileState extends ConsumerState<Profile> {
dynamic roleIdRaw;
dynamic roleId;
dynamic role;
bool isProfile = true;
bool isLoading = false;
@ -36,14 +38,6 @@ class ProfileState extends ConsumerState<Profile> {
apiService = ApiService();
// getAgentList();
_initializeToken();
// Future.microtask(() {
// final id = ref.read(managerIdProvider);
// if (id != null) {
// getAgentList(id);
//
// // api/staff/managerIncentiveFileList?manager_id=1
// }
// });
}
Future<void> _initializeToken() async {
@ -55,8 +49,12 @@ class ProfileState extends ConsumerState<Profile> {
});
print('profileDatadecodedTokenProfile : $decodedToken');
print('profileDatadecodedTokenProfile : $profileData');
await getList();
Future.microtask(() {
role = ref.read(userRoleProvider);
if (role != null) {
getList();
}
});
}
Future<void> getList() async {
@ -71,7 +69,7 @@ class ProfileState extends ConsumerState<Profile> {
if (managerId != null) {
getIncenctiveFileList(managerId);
}
} else if (roleId != 1 && roleId != 2) {
} else if (roleId != 1 && roleId != 2 && roleId != 3) {
final agentIdRaw = profileData?['id'];
final agentId = agentIdRaw is String
? int.tryParse(agentIdRaw)
@ -185,9 +183,7 @@ class ProfileState extends ConsumerState<Profile> {
? EdgeInsets.all(8.0)
: null,
child: isLoading
? const Center(
child: Text("No incentive files found"),
)
? const Center(child: CircularProgressIndicator())
: Column(
children: [
// const SizedBox(height: 25),
@ -212,7 +208,8 @@ class ProfileState extends ConsumerState<Profile> {
style: _headerStyle,
),
Text(
profileData?['emp_id'] ?? '-',
// profileData?['role'] ?? '-',
role ?? '-',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,

View File

@ -8,6 +8,7 @@ import '../../../../data/services/auth_service.dart';
import '../../../layouts/main_layout.dart';
import '../../../layouts/responsive_layout.dart';
import '../../../providers/manager_provider.dart';
import '../../../providers/userRoleProvider.dart';
class ProfilePopUp extends ConsumerStatefulWidget {
const ProfilePopUp({super.key});
@ -25,6 +26,7 @@ class ProfilePopUpState extends ConsumerState<ProfilePopUp> {
dynamic roleIdRaw;
dynamic roleId;
dynamic role;
bool isProfile = true;
bool isLoading = false;
@ -56,10 +58,21 @@ class ProfilePopUpState extends ConsumerState<ProfilePopUp> {
print('profileDatadecodedTokenProfile : $decodedToken');
print('profileDatadecodedTokenProfile : $profileData');
await getList();
// await getList();
Future.microtask(() {
role = ref.read(userRoleProvider);
if (role != null) {
getList();
}
});
}
Future<void> getList() async {
setState(() {
isLoading = true;
});
roleIdRaw = profileData?['role_id'];
roleId = roleIdRaw is String ? int.tryParse(roleIdRaw) : roleIdRaw as int?;
@ -80,6 +93,10 @@ class ProfilePopUpState extends ConsumerState<ProfilePopUp> {
getIncenctiveFileList(agentId);
}
}
setState(() {
isLoading = false;
});
}
Future<void> getIncenctiveFileList(id) async {
@ -187,7 +204,7 @@ class ProfilePopUpState extends ConsumerState<ProfilePopUp> {
? EdgeInsets.all(16.0)
: null,
child: isLoading
? const Center(child: Text("No incentive files found"))
? const Center(child: CircularProgressIndicator())
: Column(
children: [
const SizedBox(height: 25),
@ -210,7 +227,7 @@ class ProfilePopUpState extends ConsumerState<ProfilePopUp> {
style: _headerStyle,
),
Text(
profileData?['emp_id'] ?? '-',
role ?? '-',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,

View File

@ -0,0 +1,113 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../themes/indicators/month_field_theme.dart';
class MonthFilterRow extends StatelessWidget {
final TextEditingController monthController;
final VoidCallback onFilter;
final VoidCallback onRefresh;
final GlobalKey<FormState> formKey;
final bool isMobile;
const MonthFilterRow({
super.key,
required this.monthController,
required this.onFilter,
required this.onRefresh,
required this.formKey,
this.isMobile = false,
});
@override
Widget build(BuildContext context) {
final spacing = 10.0;
final fieldAndButtons = [
buildMonthField(context),
SizedBox(width: spacing),
Tooltip(
message: 'Filter',
child: IconButton(
icon: const Icon(Icons.filter_alt_outlined),
onPressed: () {
if (formKey.currentState!.validate()) onFilter();
},
),
),
Tooltip(
message: 'Refresh',
child: IconButton(
icon: const Icon(Icons.refresh),
onPressed: onRefresh,
),
),
];
return Form(
key: formKey,
child: isMobile
? Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
buildMonthField(context),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Tooltip(
message: 'Filter',
child: IconButton(
icon: const Icon(Icons.filter_alt_outlined),
onPressed: () {
if (formKey.currentState!.validate()) onFilter();
},
),
),
Tooltip(
message: 'Refresh',
child: IconButton(
icon: const Icon(Icons.refresh),
onPressed: onRefresh,
),
),
],
),
],
)
: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: fieldAndButtons,
),
);
}
Widget buildMonthField(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Month', style: _textStyle),
const SizedBox(height: 5),
ThemedMonthField(
hintText: "Select Month",
txtwidth: MediaQuery.of(context).size.width * 0.2,
txtheight: 40,
// backgroundColor: const Color(0xFFECECEC),
backgroundColor: const Color(0xFFFFFFFF),
validator: (value) =>
value == null || value.isEmpty ? "Month is required" : null,
borderColor: Colors.grey.shade100,
controller: monthController,
onDateSelected: (date) {
monthController.text = DateFormat('MM-yyyy').format(date);
},
),
],
);
}
static const _textStyle = TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
);
}

View File

@ -156,6 +156,16 @@ class DrawerMenuState extends ConsumerState<DrawerMenu> {
label: "Reports",
popupKey: 'Reports',
),
if (roleId == 'manager')
_buildMenuItem(
context: context,
icon: Icon(Icons.access_time, size: 30, color: Colors.black),
label: "Staff Attendance",
onTap: () {
context.go(AppRoutes.allStaffAttendance);
},
),
],
),
);
@ -215,17 +225,41 @@ class DrawerLabel extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
// Split the text by space (e.g. "Staff Attendance" ["Staff", "Attendance"])
final parts = text.split(' ');
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
text,
children: parts.map((line) {
return Text(
line,
style: GoogleFonts.inter(color: Colors.white, fontSize: 11.5),
),
const SizedBox(height: 20),
],
textAlign: TextAlign.center,
);
}).toList(),
);
}
// Widget build(BuildContext context) {
// return Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// SizedBox(
// width: 70,
// child: Align(
// alignment: Alignment.center,
// child: Text(
// text,
// style: GoogleFonts.inter(color: Colors.white, fontSize: 11.5),
// softWrap: true,
// maxLines: 2,
// ),
// ),
// ),
// const SizedBox(height: 20),
// ],
// );
// }
}
// Popup item

View File

@ -85,16 +85,18 @@ class _MobileBottomMenuState extends ConsumerState<MobileBottomMenu> {
}
break;
// case 3:
// // User options only for manager
// if (roleId == 'manager') {
// _showOptionsSheet([
// {'label': 'Agent', 'route': AppRoutes.agentLst},
// {'label': 'Staff', 'route': AppRoutes.staffLst},
// ]);
// }
// break;
case 3:
// User options only for manager
if (roleId == 'manager') {
_showOptionsSheet([
{'label': 'Agent', 'route': AppRoutes.agentLst},
{'label': 'Staff', 'route': AppRoutes.staffLst},
]);
} else {
context.go(AppRoutes.profile);
}
context.go(AppRoutes.profile);
break;
}
},
@ -135,14 +137,23 @@ class _MobileBottomMenuState extends ConsumerState<MobileBottomMenu> {
),
label: "Enquiry",
),
// if (roleId == 'manager') ...[
// BottomNavigationBarItem(
// icon: Icon(
// Icons.person_add_alt,
// size: 24,
// color: _currentIndex == 3 ? Colors.white : Colors.black,
// ),
// label: "User",
// ),
// ],
BottomNavigationBarItem(
icon: Icon(
Icons.person_add_alt,
Icons.person_outline,
size: 24,
color: _currentIndex == 3 ? Colors.white : Colors.black,
),
label: "User",
label: "Profile",
),
],
);