Commit 39774c76 authored by Sai Srinivas's avatar Sai Srinivas
Browse files

08-09-2025 Mohit Kumar

HRM Module Test Cases and UI
parent 1b1bfdfb
...@@ -42,6 +42,7 @@ class RequestList { ...@@ -42,6 +42,7 @@ class RequestList {
String? checkOutTime; String? checkOutTime;
String? status; String? status;
String? requestedDatetime; String? requestedDatetime;
String? employeeName;
RequestList( RequestList(
{this.id, {this.id,
...@@ -53,7 +54,9 @@ class RequestList { ...@@ -53,7 +54,9 @@ class RequestList {
this.chechOutType, this.chechOutType,
this.checkOutTime, this.checkOutTime,
this.status, this.status,
this.requestedDatetime}); this.requestedDatetime,
this.employeeName,
});
RequestList.fromJson(Map<String, dynamic> json) { RequestList.fromJson(Map<String, dynamic> json) {
id = json['id']; id = json['id'];
...@@ -66,6 +69,7 @@ class RequestList { ...@@ -66,6 +69,7 @@ class RequestList {
checkOutTime = json['check_out_time']; checkOutTime = json['check_out_time'];
status = json['status']; status = json['status'];
requestedDatetime = json['requested_datetime']; requestedDatetime = json['requested_datetime'];
employeeName = json['employee_name'];
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
...@@ -80,6 +84,7 @@ class RequestList { ...@@ -80,6 +84,7 @@ class RequestList {
data['check_out_time'] = this.checkOutTime; data['check_out_time'] = this.checkOutTime;
data['status'] = this.status; data['status'] = this.status;
data['requested_datetime'] = this.requestedDatetime; data['requested_datetime'] = this.requestedDatetime;
data['employee_name'] = this.employeeName;
return data; return data;
} }
} }
...@@ -38,6 +38,8 @@ class RequestList { ...@@ -38,6 +38,8 @@ class RequestList {
String? toPeriod; String? toPeriod;
String? status; String? status;
String? leaveType; String? leaveType;
String? rowColor;
String? employeeName;
RequestList( RequestList(
...@@ -50,6 +52,8 @@ class RequestList { ...@@ -50,6 +52,8 @@ class RequestList {
toPeriod = json['to_period']; toPeriod = json['to_period'];
status = json['status']; status = json['status'];
leaveType = json["leave_type"]; leaveType = json["leave_type"];
rowColor = json["row_colur"];
employeeName = json["employee_name"];
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
...@@ -60,6 +64,8 @@ class RequestList { ...@@ -60,6 +64,8 @@ class RequestList {
data['to_period'] = this.toPeriod; data['to_period'] = this.toPeriod;
data['status'] = this.status; data['status'] = this.status;
data["leave_type"] = this.leaveType; data["leave_type"] = this.leaveType;
data["row_colur"] = this.rowColor;
data["employee_name"] = this.employeeName;
return data; return data;
} }
} }
...@@ -15,7 +15,7 @@ class LeaveApplicationDetailsProvider extends ChangeNotifier { ...@@ -15,7 +15,7 @@ class LeaveApplicationDetailsProvider extends ChangeNotifier {
bool get isSubmitting => _isSubmitting; bool get isSubmitting => _isSubmitting;
CommonResponse? _StatusResponse; CommonResponse? _StatusResponse;
CommonResponse? get addResponse => _StatusResponse; CommonResponse? get Response => _StatusResponse;
leaveApplicationDetailsResponse? get response => _response; leaveApplicationDetailsResponse? get response => _response;
bool get isLoading => _isLoading; bool get isLoading => _isLoading;
......
...@@ -46,7 +46,7 @@ class AttendanceDetailsProvider extends ChangeNotifier { ...@@ -46,7 +46,7 @@ class AttendanceDetailsProvider extends ChangeNotifier {
_isLoading = false; _isLoading = false;
notifyListeners(); notifyListeners();
} }
Future<void> rejectAttendanceRequest( Future<void> rejectApproveAttendanceRequest(
BuildContext context, { BuildContext context, {
required String mode, required String mode,
required String type, required String type,
...@@ -60,8 +60,9 @@ class AttendanceDetailsProvider extends ChangeNotifier { ...@@ -60,8 +60,9 @@ class AttendanceDetailsProvider extends ChangeNotifier {
try { try {
final homeProvider = Provider.of<HomescreenNotifier>(context, listen: false); final homeProvider = Provider.of<HomescreenNotifier>(context, listen: false);
print("############################+++++++++++++++++##########");
final result = await ApiCalling.attendanceRequestRejectAPI( final result = await ApiCalling.attendanceRequestApproveRejectAPI(
homeProvider.session, homeProvider.session,
homeProvider.empId, homeProvider.empId,
mode, mode,
......
...@@ -123,6 +123,8 @@ class Attendancelistprovider extends ChangeNotifier { ...@@ -123,6 +123,8 @@ class Attendancelistprovider extends ChangeNotifier {
return null; // everything ok return null; // everything ok
} }
CommonResponse? _RejectResponse;
CommonResponse? get RejectResponse => _RejectResponse;
/// Fetch attendance request list with filters /// Fetch attendance request list with filters
...@@ -231,6 +233,49 @@ class Attendancelistprovider extends ChangeNotifier { ...@@ -231,6 +233,49 @@ class Attendancelistprovider extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
Future<void> rejectApproveAttendanceRequest({
required String session,
required String empId,
required String mode,
required String type,
required String remarks,
required String id,
}) async {
_isSubmitting = true;
_errorMessage = null;
_RejectResponse = null;
notifyListeners();
try {
final result = await ApiCalling.attendanceRequestApproveRejectAPI(
session,
empId,
mode,
type,
remarks,
id,
);
print("*********************************object");
if (result != null) {
_RejectResponse = result;
if (result.error != null && result.error!.isNotEmpty) {
_errorMessage = result.error;
} else {
debugPrint("Attendance request $type successfully.");
}
} else {
_errorMessage = "Failed to process attendance request!";
}
} catch (e) {
_errorMessage = "Error processing attendance request: $e";
}
_isSubmitting = false;
notifyListeners();
}
/// Apply filters coming from bottom sheet /// Apply filters coming from bottom sheet
void updateFiltersFromSheet( void updateFiltersFromSheet(
mode, mode,
......
...@@ -254,9 +254,9 @@ class LeaveApplicationListProvider extends ChangeNotifier { ...@@ -254,9 +254,9 @@ class LeaveApplicationListProvider extends ChangeNotifier {
} }
/// Show Cupertino DatePicker for leave form /// Show Cupertino DatePicker for leave form
void showDatePickerDialog(BuildContext context, void showDatePickerDialog(BuildContext context, {bool isFromDate = true}) {
{bool isFromDate = true}) { DateTime now = DateTime.now();
DateTime? currentDate = DateTime.now(); DateTime? currentDate = now;
showCupertinoModalPopup<void>( showCupertinoModalPopup<void>(
context: context, context: context,
...@@ -287,10 +287,10 @@ class LeaveApplicationListProvider extends ChangeNotifier { ...@@ -287,10 +287,10 @@ class LeaveApplicationListProvider extends ChangeNotifier {
onPressed: () { onPressed: () {
if (isFromDate) { if (isFromDate) {
fromDateField.text = fromDateField.text =
_formatDate(currentDate ?? DateTime.now()); _formatDate(currentDate ?? now);
} else { } else {
toDateField.text = toDateField.text =
_formatDate(currentDate ?? DateTime.now()); _formatDate(currentDate ?? now);
} }
Navigator.pop(context); Navigator.pop(context);
}, },
...@@ -301,7 +301,9 @@ class LeaveApplicationListProvider extends ChangeNotifier { ...@@ -301,7 +301,9 @@ class LeaveApplicationListProvider extends ChangeNotifier {
Expanded( Expanded(
child: CupertinoDatePicker( child: CupertinoDatePicker(
dateOrder: DatePickerDateOrder.dmy, dateOrder: DatePickerDateOrder.dmy,
initialDateTime: currentDate, initialDateTime: now,
minimumDate: DateTime(now.year, now.month, now.day),
maximumDate: DateTime(now.year + 5), // limit
mode: CupertinoDatePickerMode.date, mode: CupertinoDatePickerMode.date,
onDateTimeChanged: (DateTime newDate) { onDateTimeChanged: (DateTime newDate) {
currentDate = newDate; currentDate = newDate;
......
import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:intl/intl.dart';
import '../Utils/app_colors.dart'; import '../Utils/app_colors.dart';
import '../Utils/dropdownTheme.dart'; import '../Utils/dropdownTheme.dart';
...@@ -351,27 +352,30 @@ class CommonFilter2 { ...@@ -351,27 +352,30 @@ class CommonFilter2 {
], ],
), ),
), ),
if (tempSelectedValue == 'Custom') ...[
const SizedBox(height: 16),
Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.all(12),
child: buildCalendar(setState),
),
if (tempSelectedDateRange != null)
Padding(
padding: const EdgeInsets.only(top: 12.0),
child: Text(
'Selected: ${formatDate(tempSelectedDateRange!.start)} to ${formatDate(tempSelectedDateRange!.end)}',
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
),
),
],
const SizedBox(height: 20),
if (tempSelectedValue == 'Custom') ...[
const SizedBox(height: 16),
Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.all(12),
child: buildCalendar(setState),
),
if (tempSelectedDateRange != null)
Padding(
padding: const EdgeInsets.only(top: 12.0),
child: Text(
'Selected: ${DateFormat("dd MMM yyyy").format(tempSelectedDateRange!.start)} to ${DateFormat("dd MMM yyyy").format(tempSelectedDateRange!.end)}',
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
),
),
],
const SizedBox(height: 20),
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
......
...@@ -1863,21 +1863,43 @@ class _MyHomePageState extends State<MyHomePage> { ...@@ -1863,21 +1863,43 @@ class _MyHomePageState extends State<MyHomePage> {
profile.employeeeID, profile.employeeeID,
profile.mobileNUmber, profile.mobileNUmber,
]; ];
final itemText = textHeadings[index]?.toString() ?? "-";
return SizedBox( return SizedBox(
height: 40, height: 40,
child: Align( child: Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: InkWell(
"${textHeadings[index].toString()}", onTap: () {
textAlign: TextAlign.left, showJobDescriptionSheet(context, [
style: TextStyle( "Statewise End to end sales activities reg booking and dispatches and payment collection and branch visit every month & quarterly basis.",
fontSize: 14, "Conducting monthly/Quarterly/Annually– sales meeting, review of targets and achievements of total team.",
color: AppColors.semi_black, "Team CRM Tracking, Order Update Track and as well as payment entry in CRM by Team.",
"If required special Price to be taken from Prasad, Madhavi Madam/MD Sir.",
"Preparation of MIS reports on monthly basis (Rating wise data, employee wise data, TIV etc.).",
"Dispatch co-ordination with factory team- Anuradha / Sai Ram (commercial clearance with Susmitha / Rajeevi).",
"Commercial / Technical Support to BDE team order finalisation. If required client visit.",
"Team tour bills approvals in CRM.",
"Level -1 approvals to be given to sales team orders.",
"Outstanding payment collection followed on regular basis.",
]);
},
// no click for others
child: Text(
itemText,
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 14,
color: index == 2 ? AppColors.semi_black : AppColors.semi_black, // highlight clickable
decoration: index == 2 ? TextDecoration.underline : null,
),
), ),
), ),
), ),
); );
}, },
), ),
), ),
], ],
...@@ -1943,6 +1965,116 @@ class _MyHomePageState extends State<MyHomePage> { ...@@ -1943,6 +1965,116 @@ class _MyHomePageState extends State<MyHomePage> {
); );
} }
Future<void> showJobDescriptionSheet(
BuildContext context,
List<String> jobPoints,
) {
return showModalBottomSheet(
useSafeArea: true,
isDismissible: true,
isScrollControlled: true,
showDragHandle: true,
enableDrag: true,
backgroundColor: Colors.white,
context: context,
builder: (context) {
return SafeArea(
child: Container(
margin: const EdgeInsets.only(
bottom: 15,
left: 15,
right: 15,
top: 30,
),
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
/// Heading
Text(
"Job Description",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 16,
color: AppColors.app_blue, // same as Logout "Yes, Logout" button
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 15),
/// Bullet points list
...jobPoints.map(
(point) => Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"• ",
style: TextStyle(
fontSize: 14,
color: AppColors.semi_black,
),
),
Expanded(
child: Text(
point,
style: TextStyle(
fontSize: 14,
color: AppColors.semi_black,
fontFamily: "JakartaRegular",
height: 1.4, // line spacing
),
),
),
],
),
),
),
const SizedBox(height: 20),
/// Close button
InkWell(
onTap: () => Navigator.pop(context),
child: Container(
alignment: Alignment.center,
height: 45,
margin: const EdgeInsets.symmetric(
horizontal: 5.0,
vertical: 5.0,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15.0),
),
child: Center(
child: Text(
"Close",
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.app_blue,
fontFamily: "JakartaMedium",
fontSize: 15,
),
),
),
),
),
],
),
),
),
);
},
);
}
Future<void> _showLogoutBottomSheet(BuildContext context) { Future<void> _showLogoutBottomSheet(BuildContext context) {
return showModalBottomSheet( return showModalBottomSheet(
useSafeArea: true, useSafeArea: true,
......
...@@ -125,187 +125,190 @@ class _AddLeaveRequestState extends State<AddLeaveRequest> { ...@@ -125,187 +125,190 @@ class _AddLeaveRequestState extends State<AddLeaveRequest> {
} }
Widget _scaffold(BuildContext context, LeaveApplicationListProvider provider) { Widget _scaffold(BuildContext context, LeaveApplicationListProvider provider) {
return Scaffold( return SafeArea(
resizeToAvoidBottomInset: true, top: false,
backgroundColor: AppColors.scaffold_bg_color, child: Scaffold(
appBar: appbarNew(context, widget.pageTitleName, 0xFFFFFFFF), resizeToAvoidBottomInset: true,
body: SingleChildScrollView( backgroundColor: AppColors.scaffold_bg_color,
child: Container( appBar: appbarNew(context, widget.pageTitleName, 0xFFFFFFFF),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), body: SingleChildScrollView(
margin: const EdgeInsets.all(12), child: Container(
decoration: BoxDecoration( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
color: AppColors.white, margin: const EdgeInsets.all(12),
borderRadius: BorderRadius.circular(20), decoration: BoxDecoration(
), color: AppColors.white,
child: Column( borderRadius: BorderRadius.circular(20),
crossAxisAlignment: CrossAxisAlignment.start, ),
children: [ child: Column(
/// From Date crossAxisAlignment: CrossAxisAlignment.start,
TextWidget(context, "From Date"), children: [
GestureDetector( /// From Date
onTap: () { TextWidget(context, "From Date"),
provider.showDatePickerDialog(context, isFromDate: true); GestureDetector(
if (fromDateError != null) { onTap: () {
setState(() => fromDateError = null); provider.showDatePickerDialog(context, isFromDate: true);
} if (fromDateError != null) {
}, setState(() => fromDateError = null);
child: textFieldNew(context, provider.fromDateField,
"Select Date", enabled: false),
),
errorWidget(context, fromDateError),
/// From Time
TextWidget(context, "From Time"),
GestureDetector(
onTap: () async {
TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (picked != null) {
provider.fromTimeField.text = picked.format(context);
if (fromTimeError != null) {
setState(() => fromTimeError = null);
} }
} },
}, child: textFieldNew(context, provider.fromDateField,
child: textFieldNew(context, provider.fromTimeField, "Select Date", enabled: false),
"Select Time", enabled: false), ),
), errorWidget(context, fromDateError),
errorWidget(context, fromTimeError),
/// To Date /// From Time
TextWidget(context, "To Date"), TextWidget(context, "From Time"),
GestureDetector( GestureDetector(
onTap: () { onTap: () async {
provider.showDatePickerDialog(context, isFromDate: false); TimeOfDay? picked = await showTimePicker(
if (toDateError != null) { context: context,
setState(() => toDateError = null); initialTime: TimeOfDay.now(),
} );
}, if (picked != null) {
child: textFieldNew(context, provider.toDateField, "Select Date", provider.fromTimeField.text = picked.format(context);
enabled: false), if (fromTimeError != null) {
), setState(() => fromTimeError = null);
errorWidget(context, toDateError), }
}
},
child: textFieldNew(context, provider.fromTimeField,
"Select Time", enabled: false),
),
errorWidget(context, fromTimeError),
/// To Time /// To Date
TextWidget(context, "To Time"), TextWidget(context, "To Date"),
GestureDetector( GestureDetector(
onTap: () async { onTap: () {
TimeOfDay? picked = await showTimePicker( provider.showDatePickerDialog(context, isFromDate: false);
context: context, if (toDateError != null) {
initialTime: TimeOfDay.now(), setState(() => toDateError = null);
);
if (picked != null) {
provider.toTimeField.text = picked.format(context);
if (toTimeError != null) {
setState(() => toTimeError = null);
} }
} },
}, child: textFieldNew(context, provider.toDateField, "Select Date",
child: textFieldNew(context, provider.toTimeField, "Select Time", enabled: false),
enabled: false), ),
), errorWidget(context, toDateError),
errorWidget(context, toTimeError),
/// Leave Type /// To Time
TextWidget(context, "Leave Type"), TextWidget(context, "To Time"),
Container( GestureDetector(
margin: const EdgeInsets.only(bottom: 6), onTap: () async {
padding: const EdgeInsets.symmetric(horizontal: 12), TimeOfDay? picked = await showTimePicker(
decoration: BoxDecoration( context: context,
color: AppColors.text_field_color, initialTime: TimeOfDay.now(),
borderRadius: BorderRadius.circular(14), );
if (picked != null) {
provider.toTimeField.text = picked.format(context);
if (toTimeError != null) {
setState(() => toTimeError = null);
}
}
},
child: textFieldNew(context, provider.toTimeField, "Select Time",
enabled: false),
), ),
child: DropdownButtonHideUnderline( errorWidget(context, toTimeError),
child: DropdownButton<String>(
isExpanded: true, /// Leave Type
hint: const Text("Select Leave Type", TextWidget(context, "Leave Type"),
style: TextStyle(fontSize: 14, color: Colors.grey)), Container(
value: leaveType, margin: const EdgeInsets.only(bottom: 6),
items: leaveTypes padding: const EdgeInsets.symmetric(horizontal: 12),
.map((e) => decoration: BoxDecoration(
DropdownMenuItem(value: e, child: Text(e))) color: AppColors.text_field_color,
.toList(), borderRadius: BorderRadius.circular(14),
onChanged: (val) { ),
setState(() { child: DropdownButtonHideUnderline(
leaveType = val; child: DropdownButton<String>(
if (leaveTypeError != null) { isExpanded: true,
leaveTypeError = null; hint: const Text("Select Leave Type",
} style: TextStyle(fontSize: 14, color: Colors.grey)),
}); value: leaveType,
}, items: leaveTypes
.map((e) =>
DropdownMenuItem(value: e, child: Text(e)))
.toList(),
onChanged: (val) {
setState(() {
leaveType = val;
if (leaveTypeError != null) {
leaveTypeError = null;
}
});
},
),
), ),
), ),
), errorWidget(context, leaveTypeError),
errorWidget(context, leaveTypeError),
/// Reason /// Reason
TextWidget(context, "Reason"), TextWidget(context, "Reason"),
textFieldNew(context, provider.reasonController, "Enter Reason", textFieldNew(context, provider.reasonController, "Enter Reason",
maxLines: 2), maxLines: 2),
errorWidget(context, reasonError), errorWidget(context, reasonError),
const SizedBox(height: 70), const SizedBox(height: 70),
], ],
),
), ),
), ),
), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, bottomNavigationBar: InkResponse(
bottomNavigationBar: InkResponse( onTap: () {
onTap: () { if (validateForm(provider)) {
if (validateForm(provider)) { provider.addLeaveRequest(
provider.addLeaveRequest( context,
context, fromDate: provider.fromDateField.text,
fromDate: provider.fromDateField.text, fromTime: provider.fromTimeField.text,
fromTime: provider.fromTimeField.text, toDate: provider.toDateField.text,
toDate: provider.toDateField.text, toTime: provider.toTimeField.text,
toTime: provider.toTimeField.text, leaveType: leaveType!,
leaveType: leaveType!, reason: provider.reasonController.text,
reason: provider.reasonController.text, );
);
// Reset after submit // Reset after submit
setState(() { setState(() {
provider.fromDateField.clear(); provider.fromDateField.clear();
provider.fromTimeField.clear(); provider.fromTimeField.clear();
provider.toDateField.clear(); provider.toDateField.clear();
provider.toTimeField.clear(); provider.toTimeField.clear();
provider.reasonController.clear(); provider.reasonController.clear();
leaveType = null; leaveType = null;
fromDateError = null; fromDateError = null;
fromTimeError = null; fromTimeError = null;
toDateError = null; toDateError = null;
toTimeError = null; toTimeError = null;
leaveTypeError = null; leaveTypeError = null;
reasonError = null; reasonError = null;
}); });
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
content: Text("Leave request submitted successfully!"), content: Text("Leave request submitted successfully!"),
backgroundColor: Colors.black87, backgroundColor: Colors.black87,
), ),
); );
} }
}, },
child: Container( child: Container(
height: 45, height: 45,
alignment: Alignment.center, alignment: Alignment.center,
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), margin: const EdgeInsets.symmetric(horizontal: 18, vertical: 10),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.app_blue, color: AppColors.app_blue,
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(15),
), ),
child: provider.isSubmitting child: provider.isSubmitting
? const CircularProgressIndicator(color: Colors.white) ? const CircularProgressIndicator(color: Colors.white)
: const Text( : const Text(
"Submit", "Submit",
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
fontFamily: "JakartaMedium", fontFamily: "JakartaMedium",
color: Colors.white), color: Colors.white),
),
), ),
), ),
), ),
......
...@@ -55,12 +55,36 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> { ...@@ -55,12 +55,36 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
} }
Future<void> _autoFetchLocation() async { Future<void> _autoFetchLocation() async {
String loc = await getCurrentLocation(); Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
);
// Save raw coordinates separately (for submission)
final coords = "${position.latitude},${position.longitude}";
// Convert to address for display
final placemarks =
await placemarkFromCoordinates(position.latitude, position.longitude);
String displayAddress;
if (placemarks.isNotEmpty) {
final place = placemarks.first;
displayAddress =
"${place.name}, ${place.locality}, ${place.administrativeArea}, ${place.country}";
} else {
displayAddress = coords; // fallback
}
setState(() { setState(() {
locationController.text = loc; locationController.text = displayAddress; // what user sees
_rawCoordinates = coords; // keep coords hidden for backend
}); });
} }
// Add this field at the top of your State class:
String? _rawCoordinates;
Future<String> getCurrentLocation() async { Future<String> getCurrentLocation() async {
try { try {
LocationPermission permission = await Geolocator.checkPermission(); LocationPermission permission = await Geolocator.checkPermission();
...@@ -164,7 +188,7 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> { ...@@ -164,7 +188,7 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
context, context,
process: "Live", process: "Live",
type: selectedType ?? "", type: selectedType ?? "",
loc: locationController.text, loc: _rawCoordinates ?? "", // send actual coordinates
checkDate: DateTime.now().toString().split(" ").first, checkDate: DateTime.now().toString().split(" ").first,
checkInTime: checkInTime:
selectedType == "Check In" ? TimeOfDay.now().format(context) : null, selectedType == "Check In" ? TimeOfDay.now().format(context) : null,
...@@ -198,210 +222,213 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> { ...@@ -198,210 +222,213 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return SafeArea(
backgroundColor: Colors.white, top: false,
appBar: AppBar( child: Scaffold(
automaticallyImplyLeading: false,
backgroundColor: Colors.white, backgroundColor: Colors.white,
elevation: 0, appBar: AppBar(
title: Row( automaticallyImplyLeading: false,
children: [ backgroundColor: Colors.white,
InkResponse( elevation: 0,
onTap: () => Navigator.pop(context, true), title: Row(
child: SvgPicture.asset( children: [
"assets/svg/appbar_back_button.svg", InkResponse(
height: 25, onTap: () => Navigator.pop(context, true),
), child: SvgPicture.asset(
), "assets/svg/appbar_back_button.svg",
const SizedBox(width: 10), height: 25,
Text( ),
"Add Live Attendance",
style: TextStyle(
fontSize: 18,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
), ),
), const SizedBox(width: 10),
], Text(
), "Add Live Attendance",
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Type Dropdown
const Text("Type",
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 18,
fontFamily: "JakartaMedium", fontFamily: "JakartaMedium",
fontWeight: FontWeight.w500)), fontWeight: FontWeight.w600,
const SizedBox(height: 6), color: AppColors.semi_black,
Container( ),
padding: const EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8),
), ),
child: DropdownButtonHideUnderline( ],
child: DropdownButton2<String>( ),
isExpanded: true, ),
hint: const Text( body: SingleChildScrollView(
"Select Type", padding: const EdgeInsets.all(18),
overflow: TextOverflow.ellipsis, child: Column(
style: TextStyle( crossAxisAlignment: CrossAxisAlignment.start,
fontSize: 15, children: [
fontFamily: "JakartaMedium", /// Type Dropdown
fontWeight: FontWeight.w400), const Text("Type",
), style: TextStyle(
value: selectedType, fontSize: 15,
items: types fontFamily: "JakartaMedium",
.map((e) => DropdownMenuItem<String>( fontWeight: FontWeight.w500)),
value: e, const SizedBox(height: 6),
child: Text( Container(
e, padding: const EdgeInsets.symmetric(horizontal: 2),
style: const TextStyle( decoration: BoxDecoration(
fontSize: 14, fontFamily: "JakartaMedium"), color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8),
),
child: DropdownButtonHideUnderline(
child: DropdownButton2<String>(
isExpanded: true,
hint: const Text(
"Select Type",
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w400),
), ),
)) value: selectedType,
.toList(), items: types
onChanged: (val) => setState(() => selectedType = val), .map((e) => DropdownMenuItem<String>(
iconStyleData: ddtheme.iconStyleData, value: e,
dropdownStyleData: ddtheme.dropdownStyleData, child: Text(
e,
style: const TextStyle(
fontSize: 14, fontFamily: "JakartaMedium"),
overflow: TextOverflow.ellipsis,
),
))
.toList(),
onChanged: (val) => setState(() => selectedType = val),
iconStyleData: ddtheme.iconStyleData,
dropdownStyleData: ddtheme.dropdownStyleData,
),
), ),
), ),
), if (typeError != null) ...[
if (typeError != null) ...[ const SizedBox(height: 4),
const SizedBox(height: 4), Text(typeError!,
Text(typeError!, style: const TextStyle(
color: Colors.red,
fontSize: 13,
fontFamily: "JakartaMedium")),
],
const SizedBox(height: 16),
/// Location
Text(locationHeading,
style: const TextStyle( style: const TextStyle(
color: Colors.red, fontSize: 15,
fontSize: 13, fontFamily: "JakartaMedium",
fontFamily: "JakartaMedium")), fontWeight: FontWeight.w500)),
], const SizedBox(height: 6),
TextField(
controller: locationController,
decoration: _inputDecoration("Enter location"),
),
if (locationError != null) ...[
const SizedBox(height: 4),
Text(locationError!,
style: const TextStyle(
color: Colors.red,
fontSize: 13,
fontFamily: "JakartaMedium")),
],
const SizedBox(height: 16), const SizedBox(height: 16),
/// Location /// Description
Text(locationHeading, Text(descriptionHeading,
style: const TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w500)),
const SizedBox(height: 6),
TextField(
controller: locationController,
decoration: _inputDecoration("Enter location"),
),
if (locationError != null) ...[
const SizedBox(height: 4),
Text(locationError!,
style: const TextStyle( style: const TextStyle(
color: Colors.red, fontSize: 15,
fontSize: 13, fontFamily: "JakartaMedium",
fontFamily: "JakartaMedium")), fontWeight: FontWeight.w500)),
], const SizedBox(height: 6),
TextField(
controller: descriptionController,
maxLines: 3,
decoration: _inputDecoration("Write Description"),
),
const SizedBox(height: 16), const SizedBox(height: 20),
/// Description /// Attach Proof
Text(descriptionHeading, InkResponse(
style: const TextStyle( onTap: () => _showPicker(context),
fontSize: 15, child: Container(
fontFamily: "JakartaMedium", width: double.infinity,
fontWeight: FontWeight.w500)), padding: const EdgeInsets.symmetric(vertical: 14),
const SizedBox(height: 6), decoration: BoxDecoration(
TextField( color: Colors.blue.shade50,
controller: descriptionController, border: Border.all(color: Colors.blue.shade200),
maxLines: 3, borderRadius: BorderRadius.circular(14),
decoration: _inputDecoration("Write Description"), ),
), child: Center(
child: Text(
const SizedBox(height: 20), proofButtonText,
style: const TextStyle(
/// Attach Proof fontSize: 16,
InkResponse( color: Colors.blue,
onTap: () => _showPicker(context), fontFamily: "JakartaMedium",
child: Container( fontWeight: FontWeight.w500,
width: double.infinity, ),
padding: const EdgeInsets.symmetric(vertical: 14), ),
decoration: BoxDecoration( ),
color: Colors.blue.shade50,
border: Border.all(color: Colors.blue.shade200),
borderRadius: BorderRadius.circular(14),
), ),
child: Center( ),
child: Text( if (proofError != null) ...[
proofButtonText, const SizedBox(height: 4),
Text(proofError!,
style: const TextStyle( style: const TextStyle(
color: Colors.red,
fontSize: 13,
fontFamily: "JakartaMedium")),
],
if (proofFile != null) ...[
const SizedBox(height: 10),
Row(
children: [
const Icon(Icons.check_circle, color: Colors.green),
const SizedBox(width: 8),
Expanded(
child: Text("Attached: ${proofFile!.name}",
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontFamily: "JakartaMedium", fontSize: 14))),
IconButton(
icon: const Icon(Icons.close, color: Colors.red),
onPressed: () => setState(() => proofFile = null),
),
],
)
],
const SizedBox(height: 24),
/// Submit Button
InkResponse(
onTap:
isSubmitEnabled && !isSubmitting ? () => submitAttendance(context) : null,
child: Container(
height: 48,
alignment: Alignment.center,
decoration: BoxDecoration(
color: isSubmitEnabled
? AppColors.app_blue
: Colors.grey.shade400,
borderRadius: BorderRadius.circular(12),
),
child: isSubmitting
? const CircularProgressIndicator(
color: Colors.white, strokeWidth: 2)
: const Text(
"Submit",
style: TextStyle(
fontSize: 16, fontSize: 16,
color: Colors.blue,
fontFamily: "JakartaMedium", fontFamily: "JakartaMedium",
fontWeight: FontWeight.w500, color: Colors.white,
), ),
), ),
), ),
), ),
),
if (proofError != null) ...[
const SizedBox(height: 4),
Text(proofError!,
style: const TextStyle(
color: Colors.red,
fontSize: 13,
fontFamily: "JakartaMedium")),
],
if (proofFile != null) ...[
const SizedBox(height: 10),
Row(
children: [
const Icon(Icons.check_circle, color: Colors.green),
const SizedBox(width: 8),
Expanded(
child: Text("Attached: ${proofFile!.name}",
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontFamily: "JakartaMedium", fontSize: 14))),
IconButton(
icon: const Icon(Icons.close, color: Colors.red),
onPressed: () => setState(() => proofFile = null),
),
],
)
], ],
),
const SizedBox(height: 24),
/// Submit Button
InkResponse(
onTap:
isSubmitEnabled && !isSubmitting ? () => submitAttendance(context) : null,
child: Container(
height: 48,
alignment: Alignment.center,
decoration: BoxDecoration(
color: isSubmitEnabled
? AppColors.app_blue
: Colors.grey.shade400,
borderRadius: BorderRadius.circular(12),
),
child: isSubmitting
? const CircularProgressIndicator(
color: Colors.white, strokeWidth: 2)
: const Text(
"Submit",
style: TextStyle(
fontSize: 16,
fontFamily: "JakartaMedium",
color: Colors.white,
),
),
),
),
],
), ),
), ),
); );
......
...@@ -168,7 +168,7 @@ class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> { ...@@ -168,7 +168,7 @@ class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> {
} }
void _submitForm(BuildContext context) async { void _submitForm(BuildContext context) async {
// reset errors first // Reset errors first
dateError = null; dateError = null;
typeError = null; typeError = null;
checkInTimeError = checkInLocError = checkInDescError = checkInProofError = null; checkInTimeError = checkInLocError = checkInDescError = checkInProofError = null;
...@@ -176,15 +176,27 @@ class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> { ...@@ -176,15 +176,27 @@ class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> {
final provider = Provider.of<Attendancelistprovider>(context, listen: false); final provider = Provider.of<Attendancelistprovider>(context, listen: false);
// --- Date Validation --- // --- Date Validation (allow today, yesterday, day before yesterday) ---
if (provider.dateController.text.isEmpty) { if (provider.dateController.text.isEmpty) {
dateError = "Please select a date"; dateError = "Please select a date";
} else { } else {
try { try {
final enteredDate = DateFormat("dd MMM yyyy").parse(provider.dateController.text); final enteredDate = DateFormat("dd MMM yyyy").parse(provider.dateController.text);
provider.setSelectedDate(enteredDate); provider.setSelectedDate(enteredDate);
if (!provider.isDateValid()) {
dateError = "Date must be today or yesterday"; final today = DateTime.now();
final yesterday = today.subtract(const Duration(days: 1));
final dayBeforeYesterday = today.subtract(const Duration(days: 2));
// Normalize dates (ignore time part)
bool isValid = enteredDate.year == today.year &&
enteredDate.month == today.month &&
(enteredDate.day == today.day ||
enteredDate.day == yesterday.day ||
enteredDate.day == dayBeforeYesterday.day);
if (!isValid) {
dateError = "Date must be today, yesterday, or the day before yesterday";
} }
} catch (e) { } catch (e) {
dateError = "Invalid date format (use dd MMM yyyy)"; dateError = "Invalid date format (use dd MMM yyyy)";
...@@ -225,15 +237,15 @@ class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> { ...@@ -225,15 +237,15 @@ class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> {
checkOutDescError, checkOutDescError,
checkOutProofError checkOutProofError
].any((e) => e != null)) { ].any((e) => e != null)) {
setState(() {}); setState(() {}); // refresh UI to show error messages
return; return;
} }
// --- Format date for server (convert from "03 Sep 2025" to "2025-09-03" or whatever format server expects) --- // --- Format date for server ---
String formattedDate = ""; String formattedDate = "";
try { try {
final parsedDate = DateFormat("dd MMM yyyy").parse(provider.dateController.text); final parsedDate = DateFormat("dd MMM yyyy").parse(provider.dateController.text);
formattedDate = DateFormat("yyyy-MM-dd").format(parsedDate); // Change format as per server requirement formattedDate = DateFormat("yyyy-MM-dd").format(parsedDate);
} catch (e) { } catch (e) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Error formatting date: $e")), SnackBar(content: Text("Error formatting date: $e")),
...@@ -280,7 +292,7 @@ class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> { ...@@ -280,7 +292,7 @@ class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> {
: selectedType == "Check Out" : selectedType == "Check Out"
? checkOutLocation.text ? checkOutLocation.text
: "${checkInLocation.text}, ${checkOutLocation.text}", : "${checkInLocation.text}, ${checkOutLocation.text}",
checkDate: formattedDate, // Use the formatted date here checkDate: formattedDate,
checkInTime: finalCheckInTime, checkInTime: finalCheckInTime,
checkInLoc: finalCheckInLoc, checkInLoc: finalCheckInLoc,
checkInProof: finalCheckInProof, checkInProof: finalCheckInProof,
...@@ -290,14 +302,13 @@ class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> { ...@@ -290,14 +302,13 @@ class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> {
note: finalNote, note: finalNote,
); );
// Check the response from provider // --- Response handling ---
if (provider.addResponse != null && provider.addResponse!.error == "0") { if (provider.addResponse != null && provider.addResponse!.error == "0") {
// Success case
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(provider.addResponse!.message ?? "Attendance Submitted Successfully")), SnackBar(content: Text(provider.addResponse!.message ?? "Attendance Submitted Successfully")),
); );
// --- Reset fields --- // Reset fields
setState(() { setState(() {
selectedType = null; selectedType = null;
provider.dateController.clear(); provider.dateController.clear();
...@@ -313,19 +324,20 @@ class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> { ...@@ -313,19 +324,20 @@ class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> {
_fetchInitialLocation(); _fetchInitialLocation();
} else { } else {
// Error case - show appropriate message
String errorMessage = provider.errorMessage ?? "Failed to submit attendance"; String errorMessage = provider.errorMessage ?? "Failed to submit attendance";
// Handle specific server error for Check Out without Check In
if (errorMessage.contains("Check In is not Available")) { if (errorMessage.contains("Check In is not Available")) {
errorMessage = "Cannot submit Check Out without a Check In record for this date"; errorMessage = "Cannot submit Check Out without a Check In record for this date";
} }
if (errorMessage.contains("2")){
errorMessage = "Only One manual Request can be added in a month !";
}
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(errorMessage), backgroundColor: Colors.red), SnackBar(content: Text(errorMessage), backgroundColor: Colors.red),
); );
} }
} }
// it's date picker need to take day before yesterday, yesterday and today
......
This diff is collapsed.
...@@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; ...@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:generp/screens/hrm/Attendancelist.dart'; import 'package:generp/screens/hrm/Attendancelist.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../Utils/app_colors.dart'; import '../../Utils/app_colors.dart';
import 'AttendanceRequestDetail.dart'; import 'AttendanceRequestDetail.dart';
import 'LeaveApplicationScreen.dart'; import 'LeaveApplicationScreen.dart';
...@@ -39,186 +38,189 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> { ...@@ -39,186 +38,189 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return SafeArea(
appBar: AppBar( top: false,
automaticallyImplyLeading: false, child: Scaffold(
backgroundColor: const Color(0xFFCEEDFF), appBar: AppBar(
title: Row( automaticallyImplyLeading: false,
children: [ backgroundColor: const Color(0xFFCEEDFF),
InkResponse( title: Row(
onTap: () => Navigator.pop(context, true), children: [
child: SvgPicture.asset( InkResponse(
"assets/svg/appbar_back_button.svg", onTap: () => Navigator.pop(context, true),
height: 25, child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25,
),
), ),
), const SizedBox(width: 10),
const SizedBox(width: 10), Text(
Text( "HRM",
"HRM", style: TextStyle(
style: TextStyle( fontSize: 18,
fontSize: 18, fontFamily: "Plus Jakarta Sans",
fontFamily: "Plus Jakarta Sans", fontWeight: FontWeight.w600,
fontWeight: FontWeight.w600, color: AppColors.semi_black,
color: AppColors.semi_black, ),
), ),
), ],
], ),
), ),
), backgroundColor: const Color(0xffF6F6F8),
backgroundColor: const Color(0xffF6F6F8), body: SingleChildScrollView(
body: SingleChildScrollView( child: Column(
child: Column( children: [
children: [ /// Background
/// Background Stack(
Stack( children: [
children: [ Container(
Container( width: double.infinity,
width: double.infinity, height: 490,
height: 490, color: const Color(0xffF6F6F8),
color: const Color(0xffF6F6F8),
),
Container(
width: double.infinity,
height: 490,
padding: const EdgeInsets.only(top: 1, bottom: 30),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [
Color(0xFFCEEDFF),
Color(0xFFf9f9fb),
Color(0xffF6F6F8),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
), ),
), Container(
Container(
width: double.infinity,
padding: const EdgeInsets.only(top: 1, bottom: 30),
child: Image.asset(
"assets/images/vector.png",
height: 230,
width: double.infinity, width: double.infinity,
fit: BoxFit.fitWidth, height: 490,
padding: const EdgeInsets.only(top: 1, bottom: 30),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [
Color(0xFFCEEDFF),
Color(0xFFf9f9fb),
Color(0xffF6F6F8),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
), ),
), Container(
width: double.infinity,
/// Content padding: const EdgeInsets.only(top: 1, bottom: 30),
Column( child: Image.asset(
children: [ "assets/images/vector.png",
/// Top Illustration & Button height: 230,
Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.only(top: 60, bottom: 30), fit: BoxFit.fitWidth,
child: Column( ),
children: [ ),
SvgPicture.asset(
"assets/images/capa.svg", /// Content
height: 146, Column(
width: 400, children: [
), /// Top Illustration & Button
const SizedBox(height: 32), Container(
Container( width: double.infinity,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.only(top: 60, bottom: 30),
horizontal: 20, vertical: 8), child: Column(
decoration: BoxDecoration( children: [
border: Border.all( SvgPicture.asset(
color: const Color(0xFF1487C9), // border color "assets/images/capa.svg",
width: 1.2, // thickness of the border height: 146,
width: 400,
),
const SizedBox(height: 32),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 8),
decoration: BoxDecoration(
border: Border.all(
color: const Color(0xFF1487C9), // border color
width: 1.2, // thickness of the border
),
color: const Color(0xffEDF8FF),
borderRadius: BorderRadius.circular(30),
),
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => OrgChartt(),
),
);
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(
"assets/svg/hrm/groupIc.svg",
height: 29,
width: 29,
fit: BoxFit.contain,
),
const SizedBox(width: 7),
const Text(
"Organization Structure",
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, fontStyle: FontStyle.normal, fontFamily: "Plus Jakarta Sans"),
),
const Icon(Icons.chevron_right, color: Colors.black54),
],
),
), ),
color: const Color(0xffEDF8FF),
borderRadius: BorderRadius.circular(30),
), ),
child: InkWell( ],
onTap: () { ),
Navigator.push( ),
context,
MaterialPageRoute( /// Grid Section
builder: (context) => OrgChartt(), LayoutBuilder(
builder: (context, constraints) {
return Padding(
padding: const EdgeInsets.all(14),
child: Consumer<HrmAccessiblePagesProvider>(
builder: (context, provider, child) {
if (provider.isLoading) {
return const Center(
child: CircularProgressIndicator());
}
if (provider.errorMessage != null) {
return Center(
child: Text(provider.errorMessage!));
}
final pages = (provider.response?.pagesAccessible ?? [])
.where((page) =>
allowedPages.contains(page.pageName))
.toList();
return GridView.builder(
itemCount: pages.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: (constraints.maxWidth / 180).floor().clamp(2, 4),
crossAxisSpacing: 8.5,
mainAxisSpacing: 16,
childAspectRatio: 1.7,
), ),
itemBuilder: (context, index) {
final page = pages[index];
return _buildTile(
label: page.pageName ?? "",
subtitle: _getSubtitle(page.pageName ?? ""),
assetIcon: _getIcon(page.pageName ?? ""),
txtColor: const Color(0xff1487C9),
onTap: () => _handleNavigation(
context,
page.pageName ?? "",
page.mode ?? "",
),
);
},
); );
}, },
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(
"assets/svg/hrm/groupIc.svg",
height: 29,
width: 29,
fit: BoxFit.contain,
),
const SizedBox(width: 7),
const Text(
"Organization Structure",
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, fontStyle: FontStyle.normal, fontFamily: "Plus Jakarta Sans"),
),
const Icon(Icons.chevron_right, color: Colors.black54),
],
),
), ),
), );
], },
), ),
), ],
),
/// Grid Section ],
LayoutBuilder( ),
builder: (context, constraints) { ],
return Padding( ),
padding: const EdgeInsets.all(14),
child: Consumer<HrmAccessiblePagesProvider>(
builder: (context, provider, child) {
if (provider.isLoading) {
return const Center(
child: CircularProgressIndicator());
}
if (provider.errorMessage != null) {
return Center(
child: Text(provider.errorMessage!));
}
final pages = (provider.response?.pagesAccessible ?? [])
.where((page) =>
allowedPages.contains(page.pageName))
.toList();
return GridView.builder(
itemCount: pages.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: (constraints.maxWidth / 180).floor().clamp(2, 4),
crossAxisSpacing: 8.5,
mainAxisSpacing: 16,
childAspectRatio: 1.7,
),
itemBuilder: (context, index) {
final page = pages[index];
return _buildTile(
label: page.pageName ?? "",
subtitle: _getSubtitle(page.pageName ?? ""),
assetIcon: _getIcon(page.pageName ?? ""),
txtColor: const Color(0xff1487C9),
onTap: () => _handleNavigation(
context,
page.pageName ?? "",
page.mode ?? "",
),
);
},
);
},
),
);
},
),
],
),
],
),
],
), ),
), ),
); );
...@@ -287,8 +289,8 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> { ...@@ -287,8 +289,8 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
Expanded( Expanded(
flex: 1, flex: 1,
child: Container( child: Container(
height: constraints.maxHeight * 0.5, // Responsive size height: constraints.maxHeight * 0.39,
width: constraints.maxHeight * 0.5, // Responsive size width: constraints.maxHeight * 0.39,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: const Color(0xFFEDF8FF), color: const Color(0xFFEDF8FF),
...@@ -296,8 +298,8 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> { ...@@ -296,8 +298,8 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
child: Center( child: Center(
child: SvgPicture.asset( child: SvgPicture.asset(
assetIcon, assetIcon,
height: constraints.maxHeight * 0.3, // Responsive size height: constraints.maxHeight * 0.19,
width: constraints.maxHeight * 0.3, // Responsive size width: constraints.maxHeight * 0.19,
), ),
), ),
), ),
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment