UI_STAFF_ATTENDANCE
This commit is contained in:
parent
c6a212bc65
commit
659dba0f14
File diff suppressed because one or more lines are too long
@ -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();
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)]),
|
||||
),
|
||||
|
||||
@ -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,
|
||||
);
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
113
lib/presentation/widgets/custom_month_filter.dart
Normal file
113
lib/presentation/widgets/custom_month_filter.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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",
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user