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 {
String? checkOutTime;
String? status;
String? requestedDatetime;
String? employeeName;
RequestList(
{this.id,
......@@ -53,7 +54,9 @@ class RequestList {
this.chechOutType,
this.checkOutTime,
this.status,
this.requestedDatetime});
this.requestedDatetime,
this.employeeName,
});
RequestList.fromJson(Map<String, dynamic> json) {
id = json['id'];
......@@ -66,6 +69,7 @@ class RequestList {
checkOutTime = json['check_out_time'];
status = json['status'];
requestedDatetime = json['requested_datetime'];
employeeName = json['employee_name'];
}
Map<String, dynamic> toJson() {
......@@ -80,6 +84,7 @@ class RequestList {
data['check_out_time'] = this.checkOutTime;
data['status'] = this.status;
data['requested_datetime'] = this.requestedDatetime;
data['employee_name'] = this.employeeName;
return data;
}
}
......@@ -38,6 +38,8 @@ class RequestList {
String? toPeriod;
String? status;
String? leaveType;
String? rowColor;
String? employeeName;
RequestList(
......@@ -50,6 +52,8 @@ class RequestList {
toPeriod = json['to_period'];
status = json['status'];
leaveType = json["leave_type"];
rowColor = json["row_colur"];
employeeName = json["employee_name"];
}
Map<String, dynamic> toJson() {
......@@ -60,6 +64,8 @@ class RequestList {
data['to_period'] = this.toPeriod;
data['status'] = this.status;
data["leave_type"] = this.leaveType;
data["row_colur"] = this.rowColor;
data["employee_name"] = this.employeeName;
return data;
}
}
......@@ -15,7 +15,7 @@ class LeaveApplicationDetailsProvider extends ChangeNotifier {
bool get isSubmitting => _isSubmitting;
CommonResponse? _StatusResponse;
CommonResponse? get addResponse => _StatusResponse;
CommonResponse? get Response => _StatusResponse;
leaveApplicationDetailsResponse? get response => _response;
bool get isLoading => _isLoading;
......
......@@ -46,7 +46,7 @@ class AttendanceDetailsProvider extends ChangeNotifier {
_isLoading = false;
notifyListeners();
}
Future<void> rejectAttendanceRequest(
Future<void> rejectApproveAttendanceRequest(
BuildContext context, {
required String mode,
required String type,
......@@ -60,8 +60,9 @@ class AttendanceDetailsProvider extends ChangeNotifier {
try {
final homeProvider = Provider.of<HomescreenNotifier>(context, listen: false);
print("############################+++++++++++++++++##########");
final result = await ApiCalling.attendanceRequestRejectAPI(
final result = await ApiCalling.attendanceRequestApproveRejectAPI(
homeProvider.session,
homeProvider.empId,
mode,
......
......@@ -123,6 +123,8 @@ class Attendancelistprovider extends ChangeNotifier {
return null; // everything ok
}
CommonResponse? _RejectResponse;
CommonResponse? get RejectResponse => _RejectResponse;
/// Fetch attendance request list with filters
......@@ -231,6 +233,49 @@ class Attendancelistprovider extends ChangeNotifier {
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
void updateFiltersFromSheet(
mode,
......
......@@ -254,9 +254,9 @@ class LeaveApplicationListProvider extends ChangeNotifier {
}
/// Show Cupertino DatePicker for leave form
void showDatePickerDialog(BuildContext context,
{bool isFromDate = true}) {
DateTime? currentDate = DateTime.now();
void showDatePickerDialog(BuildContext context, {bool isFromDate = true}) {
DateTime now = DateTime.now();
DateTime? currentDate = now;
showCupertinoModalPopup<void>(
context: context,
......@@ -287,10 +287,10 @@ class LeaveApplicationListProvider extends ChangeNotifier {
onPressed: () {
if (isFromDate) {
fromDateField.text =
_formatDate(currentDate ?? DateTime.now());
_formatDate(currentDate ?? now);
} else {
toDateField.text =
_formatDate(currentDate ?? DateTime.now());
_formatDate(currentDate ?? now);
}
Navigator.pop(context);
},
......@@ -301,7 +301,9 @@ class LeaveApplicationListProvider extends ChangeNotifier {
Expanded(
child: CupertinoDatePicker(
dateOrder: DatePickerDateOrder.dmy,
initialDateTime: currentDate,
initialDateTime: now,
minimumDate: DateTime(now.year, now.month, now.day),
maximumDate: DateTime(now.year + 5), // limit
mode: CupertinoDatePickerMode.date,
onDateTimeChanged: (DateTime newDate) {
currentDate = newDate;
......
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:intl/intl.dart';
import '../Utils/app_colors.dart';
import '../Utils/dropdownTheme.dart';
......@@ -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(
mainAxisAlignment: MainAxisAlignment.end,
children: [
......
......@@ -1863,21 +1863,43 @@ class _MyHomePageState extends State<MyHomePage> {
profile.employeeeID,
profile.mobileNUmber,
];
final itemText = textHeadings[index]?.toString() ?? "-";
return SizedBox(
height: 40,
child: Align(
alignment: Alignment.centerLeft,
child: Text(
"${textHeadings[index].toString()}",
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 14,
color: AppColors.semi_black,
child: InkWell(
onTap: () {
showJobDescriptionSheet(context, [
"Statewise End to end sales activities reg booking and dispatches and payment collection and branch visit every month & quarterly basis.",
"Conducting monthly/Quarterly/Annually– sales meeting, review of targets and achievements of total team.",
"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> {
);
}
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) {
return showModalBottomSheet(
useSafeArea: true,
......
......@@ -125,187 +125,190 @@ class _AddLeaveRequestState extends State<AddLeaveRequest> {
}
Widget _scaffold(BuildContext context, LeaveApplicationListProvider provider) {
return Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: AppColors.scaffold_bg_color,
appBar: appbarNew(context, widget.pageTitleName, 0xFFFFFFFF),
body: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
margin: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.circular(20),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// From Date
TextWidget(context, "From Date"),
GestureDetector(
onTap: () {
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);
return SafeArea(
top: false,
child: Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: AppColors.scaffold_bg_color,
appBar: appbarNew(context, widget.pageTitleName, 0xFFFFFFFF),
body: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
margin: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.circular(20),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// From Date
TextWidget(context, "From Date"),
GestureDetector(
onTap: () {
provider.showDatePickerDialog(context, isFromDate: true);
if (fromDateError != null) {
setState(() => fromDateError = null);
}
}
},
child: textFieldNew(context, provider.fromTimeField,
"Select Time", enabled: false),
),
errorWidget(context, fromTimeError),
},
child: textFieldNew(context, provider.fromDateField,
"Select Date", enabled: false),
),
errorWidget(context, fromDateError),
/// To Date
TextWidget(context, "To Date"),
GestureDetector(
onTap: () {
provider.showDatePickerDialog(context, isFromDate: false);
if (toDateError != null) {
setState(() => toDateError = null);
}
},
child: textFieldNew(context, provider.toDateField, "Select Date",
enabled: false),
),
errorWidget(context, toDateError),
/// 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.fromTimeField,
"Select Time", enabled: false),
),
errorWidget(context, fromTimeError),
/// To Time
TextWidget(context, "To Time"),
GestureDetector(
onTap: () async {
TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (picked != null) {
provider.toTimeField.text = picked.format(context);
if (toTimeError != null) {
setState(() => toTimeError = null);
/// To Date
TextWidget(context, "To Date"),
GestureDetector(
onTap: () {
provider.showDatePickerDialog(context, isFromDate: false);
if (toDateError != null) {
setState(() => toDateError = null);
}
}
},
child: textFieldNew(context, provider.toTimeField, "Select Time",
enabled: false),
),
errorWidget(context, toTimeError),
},
child: textFieldNew(context, provider.toDateField, "Select Date",
enabled: false),
),
errorWidget(context, toDateError),
/// Leave Type
TextWidget(context, "Leave Type"),
Container(
margin: const EdgeInsets.only(bottom: 6),
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: AppColors.text_field_color,
borderRadius: BorderRadius.circular(14),
/// To Time
TextWidget(context, "To Time"),
GestureDetector(
onTap: () async {
TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
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(
child: DropdownButton<String>(
isExpanded: true,
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, toTimeError),
/// Leave Type
TextWidget(context, "Leave Type"),
Container(
margin: const EdgeInsets.only(bottom: 6),
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: AppColors.text_field_color,
borderRadius: BorderRadius.circular(14),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
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
TextWidget(context, "Reason"),
textFieldNew(context, provider.reasonController, "Enter Reason",
maxLines: 2),
errorWidget(context, reasonError),
/// Reason
TextWidget(context, "Reason"),
textFieldNew(context, provider.reasonController, "Enter Reason",
maxLines: 2),
errorWidget(context, reasonError),
const SizedBox(height: 70),
],
const SizedBox(height: 70),
],
),
),
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
bottomNavigationBar: InkResponse(
onTap: () {
if (validateForm(provider)) {
provider.addLeaveRequest(
context,
fromDate: provider.fromDateField.text,
fromTime: provider.fromTimeField.text,
toDate: provider.toDateField.text,
toTime: provider.toTimeField.text,
leaveType: leaveType!,
reason: provider.reasonController.text,
);
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
bottomNavigationBar: InkResponse(
onTap: () {
if (validateForm(provider)) {
provider.addLeaveRequest(
context,
fromDate: provider.fromDateField.text,
fromTime: provider.fromTimeField.text,
toDate: provider.toDateField.text,
toTime: provider.toTimeField.text,
leaveType: leaveType!,
reason: provider.reasonController.text,
);
// Reset after submit
setState(() {
provider.fromDateField.clear();
provider.fromTimeField.clear();
provider.toDateField.clear();
provider.toTimeField.clear();
provider.reasonController.clear();
leaveType = null;
fromDateError = null;
fromTimeError = null;
toDateError = null;
toTimeError = null;
leaveTypeError = null;
reasonError = null;
});
// Reset after submit
setState(() {
provider.fromDateField.clear();
provider.fromTimeField.clear();
provider.toDateField.clear();
provider.toTimeField.clear();
provider.reasonController.clear();
leaveType = null;
fromDateError = null;
fromTimeError = null;
toDateError = null;
toTimeError = null;
leaveTypeError = null;
reasonError = null;
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Leave request submitted successfully!"),
backgroundColor: Colors.black87,
),
);
}
},
child: Container(
height: 45,
alignment: Alignment.center,
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: AppColors.app_blue,
borderRadius: BorderRadius.circular(15),
),
child: provider.isSubmitting
? const CircularProgressIndicator(color: Colors.white)
: const Text(
"Submit",
style: TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
color: Colors.white),
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Leave request submitted successfully!"),
backgroundColor: Colors.black87,
),
);
}
},
child: Container(
height: 45,
alignment: Alignment.center,
margin: const EdgeInsets.symmetric(horizontal: 18, vertical: 10),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
decoration: BoxDecoration(
color: AppColors.app_blue,
borderRadius: BorderRadius.circular(15),
),
child: provider.isSubmitting
? const CircularProgressIndicator(color: Colors.white)
: const Text(
"Submit",
style: TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
color: Colors.white),
),
),
),
),
......
......@@ -55,12 +55,36 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
}
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(() {
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 {
try {
LocationPermission permission = await Geolocator.checkPermission();
......@@ -164,7 +188,7 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
context,
process: "Live",
type: selectedType ?? "",
loc: locationController.text,
loc: _rawCoordinates ?? "", // send actual coordinates
checkDate: DateTime.now().toString().split(" ").first,
checkInTime:
selectedType == "Check In" ? TimeOfDay.now().format(context) : null,
......@@ -198,210 +222,213 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
automaticallyImplyLeading: false,
return SafeArea(
top: false,
child: Scaffold(
backgroundColor: Colors.white,
elevation: 0,
title: Row(
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25,
),
),
const SizedBox(width: 10),
Text(
"Add Live Attendance",
style: TextStyle(
fontSize: 18,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.white,
elevation: 0,
title: Row(
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25,
),
),
),
],
),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Type Dropdown
const Text("Type",
const SizedBox(width: 10),
Text(
"Add Live Attendance",
style: TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w500)),
const SizedBox(height: 6),
Container(
padding: const EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8),
fontSize: 18,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
),
),
child: DropdownButtonHideUnderline(
child: DropdownButton2<String>(
isExpanded: true,
hint: const Text(
"Select Type",
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w400),
),
value: selectedType,
items: types
.map((e) => DropdownMenuItem<String>(
value: e,
child: Text(
e,
style: const TextStyle(
fontSize: 14, fontFamily: "JakartaMedium"),
],
),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Type Dropdown
const Text("Type",
style: TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w500)),
const SizedBox(height: 6),
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(
"Select Type",
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w400),
),
))
.toList(),
onChanged: (val) => setState(() => selectedType = val),
iconStyleData: ddtheme.iconStyleData,
dropdownStyleData: ddtheme.dropdownStyleData,
value: selectedType,
items: types
.map((e) => DropdownMenuItem<String>(
value: e,
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) ...[
const SizedBox(height: 4),
Text(typeError!,
if (typeError != null) ...[
const SizedBox(height: 4),
Text(typeError!,
style: const TextStyle(
color: Colors.red,
fontSize: 13,
fontFamily: "JakartaMedium")),
],
const SizedBox(height: 16),
/// Location
Text(locationHeading,
style: const TextStyle(
color: Colors.red,
fontSize: 13,
fontFamily: "JakartaMedium")),
],
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(
color: Colors.red,
fontSize: 13,
fontFamily: "JakartaMedium")),
],
const SizedBox(height: 16),
/// Location
Text(locationHeading,
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!,
const SizedBox(height: 16),
/// Description
Text(descriptionHeading,
style: const TextStyle(
color: Colors.red,
fontSize: 13,
fontFamily: "JakartaMedium")),
],
fontSize: 15,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w500)),
const SizedBox(height: 6),
TextField(
controller: descriptionController,
maxLines: 3,
decoration: _inputDecoration("Write Description"),
),
const SizedBox(height: 16),
/// Description
Text(descriptionHeading,
style: const TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w500)),
const SizedBox(height: 6),
TextField(
controller: descriptionController,
maxLines: 3,
decoration: _inputDecoration("Write Description"),
),
const SizedBox(height: 20),
/// Attach Proof
InkResponse(
onTap: () => _showPicker(context),
child: Container(
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),
const SizedBox(height: 20),
/// Attach Proof
InkResponse(
onTap: () => _showPicker(context),
child: Container(
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(
proofButtonText,
style: const TextStyle(
fontSize: 16,
color: Colors.blue,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w500,
),
),
),
),
child: Center(
child: Text(
proofButtonText,
),
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,
color: Colors.blue,
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> {
}
void _submitForm(BuildContext context) async {
// reset errors first
// Reset errors first
dateError = null;
typeError = null;
checkInTimeError = checkInLocError = checkInDescError = checkInProofError = null;
......@@ -176,15 +176,27 @@ class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> {
final provider = Provider.of<Attendancelistprovider>(context, listen: false);
// --- Date Validation ---
// --- Date Validation (allow today, yesterday, day before yesterday) ---
if (provider.dateController.text.isEmpty) {
dateError = "Please select a date";
} else {
try {
final enteredDate = DateFormat("dd MMM yyyy").parse(provider.dateController.text);
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) {
dateError = "Invalid date format (use dd MMM yyyy)";
......@@ -225,15 +237,15 @@ class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> {
checkOutDescError,
checkOutProofError
].any((e) => e != null)) {
setState(() {});
setState(() {}); // refresh UI to show error messages
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 = "";
try {
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) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Error formatting date: $e")),
......@@ -280,7 +292,7 @@ class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> {
: selectedType == "Check Out"
? checkOutLocation.text
: "${checkInLocation.text}, ${checkOutLocation.text}",
checkDate: formattedDate, // Use the formatted date here
checkDate: formattedDate,
checkInTime: finalCheckInTime,
checkInLoc: finalCheckInLoc,
checkInProof: finalCheckInProof,
......@@ -290,14 +302,13 @@ class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> {
note: finalNote,
);
// Check the response from provider
// --- Response handling ---
if (provider.addResponse != null && provider.addResponse!.error == "0") {
// Success case
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(provider.addResponse!.message ?? "Attendance Submitted Successfully")),
);
// --- Reset fields ---
// Reset fields
setState(() {
selectedType = null;
provider.dateController.clear();
......@@ -313,19 +324,20 @@ class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> {
_fetchInitialLocation();
} else {
// Error case - show appropriate message
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")) {
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(
SnackBar(content: Text(errorMessage), backgroundColor: Colors.red),
);
}
}
// it's date picker need to take day before yesterday, yesterday and today
......
......@@ -23,311 +23,340 @@ class AttendanceRequestDetailScreen extends StatefulWidget {
class _AttendanceRequestDetailScreenState
extends State<AttendanceRequestDetailScreen> {
bool _actionSubmitted = false;
late AttendanceDetailsProvider provider;
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) =>
AttendanceDetailsProvider()..fetchAttendanceRequestDetail(context, widget.attendanceListId),
child: Consumer<AttendanceDetailsProvider>(
builder: (context, provider, child) {
// Get screen dimensions for responsive scaling
final screenWidth = MediaQuery.of(context).size.width;
final screenHeight = MediaQuery.of(context).size.height;
// Scale factors based on screen size
final scaleFactor = screenWidth / 360; // Base width for scaling
final textScaleFactor = MediaQuery.of(context).textScaleFactor.clamp(1.0, 1.2);
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Color(0xFFFFFFFF),
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25 * scaleFactor,
return SafeArea(
top: false,
child: ChangeNotifierProvider(
create: (_) =>
AttendanceDetailsProvider()..fetchAttendanceRequestDetail(context, widget.attendanceListId),
child: Consumer<AttendanceDetailsProvider>(
builder: (context, provider, child) {
// Get screen dimensions for responsive scaling
final screenWidth = MediaQuery.of(context).size.width;
final screenHeight = MediaQuery.of(context).size.height;
// Scale factors based on screen size
final scaleFactor = screenWidth / 360; // Base width for scaling
final textScaleFactor = MediaQuery.of(context).textScaleFactor.clamp(1.0, 1.2);
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Color(0xFFFFFFFF),
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25 * scaleFactor,
),
),
),
SizedBox(width: 10 * scaleFactor),
InkResponse(
onTap: () => Navigator.pop(context, true),
child: Text(
"Attendance Details",
style: TextStyle(
fontSize: 18,
height: 1.1,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
SizedBox(width: 10 * scaleFactor),
InkResponse(
onTap: () => Navigator.pop(context, true),
child: Text(
"Attendance Details",
style: TextStyle(
fontSize: 18,
height: 1.1,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
),
),
),
),
],
],
),
),
),
backgroundColor: Color(0xFFF6F6F8),
body: Builder(
builder: (context) {
if (provider.isLoading) {
return const Center(child: CircularProgressIndicator(color: Colors.blue,));
}
if (provider.errorMessage != null) {
return Center(child: Text(provider.errorMessage!));
}
if (provider.response?.requestDetails == null) {
return const Center(child: Text("No details found"));
}
final details = provider.response!.requestDetails!;
/// scr
return SingleChildScrollView(
padding: EdgeInsets.all(16.0 * scaleFactor),
child: Column(
children: [
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16 * scaleFactor),
),
elevation: 2,
child: Padding(
padding: EdgeInsets.all(16.0 * scaleFactor),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.only(bottom: 0.5 * scaleFactor),
padding: EdgeInsets.all(12 * scaleFactor),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12 * scaleFactor),
),
child: Row(
children: [
/// Left Avatar
Container(
height: 48 * scaleFactor,
width: 48 * scaleFactor,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFFEDF8FF), // icon bg
),
child: Center(
child: SvgPicture.asset(
height: 28 * scaleFactor,
width: 28 * scaleFactor,
"assets/svg/hrm/attendanceList.svg",
fit: BoxFit.contain,
backgroundColor: Color(0xFFF6F6F8),
body: Builder(
builder: (context) {
if (provider.isLoading) {
return const Center(child: CircularProgressIndicator(color: Colors.blue,));
}
// if (provider.errorMessage != null) {
// return Center(child: Text(provider.errorMessage!));
// }
if (provider.response?.requestDetails == null) {
return const Center(child: Text("No details found"));
}
final details = provider.response!.requestDetails!;
/// scr
return SingleChildScrollView(
padding: EdgeInsets.all(16.0 * scaleFactor),
child: Column(
children: [
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16 * scaleFactor),
),
elevation: 0,
child: Padding(
padding: EdgeInsets.all(16.0 * scaleFactor),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.only(bottom: 0.5 * scaleFactor),
padding: EdgeInsets.symmetric(horizontal: 2.5 * scaleFactor, vertical: 12 * scaleFactor),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12 * scaleFactor),
),
child: Row(
children: [
/// Left Avatar
Container(
height: 44 * scaleFactor,
width: 44 * scaleFactor,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFFEDF8FF), // icon bg
),
child: Center(
child: SvgPicture.asset(
height: 24 * scaleFactor,
width: 24 * scaleFactor,
"assets/svg/hrm/attendanceList.svg",
fit: BoxFit.contain,
),
),
),
),
SizedBox(width: 12 * scaleFactor),
/// Middle text
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
details.type ?? "-",
style: TextStyle(
decoration: TextDecoration.underline,
decorationStyle:
TextDecorationStyle.dotted,
decorationColor: AppColors.grey_thick,
height: 1.2,
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.semi_black,
SizedBox(width: 12 * scaleFactor),
/// Middle text
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
details.type ?? "-",
style: TextStyle(
decoration: TextDecoration.underline,
decorationStyle:
TextDecorationStyle.dotted,
decorationColor: AppColors.grey_thick,
height: 1.2,
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.semi_black,
),
),
),
SizedBox(height: 2 * scaleFactor),
Text(
details.date ?? "-",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.app_blue,
SizedBox(height: 2 * scaleFactor),
Text(
details.date ?? "-",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.app_blue,
),
),
),
],
],
),
),
),
/// Right side (Live/Manual)
Container(
height: 30 * scaleFactor,
padding: EdgeInsets.symmetric(
horizontal: 12 * scaleFactor,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6 * scaleFactor),
color: getDecorationColor(details.status)
),
child: Center(
child: Text(
details.status ?? "-",
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: getTextColor(details.status.toString()),
/// Right side (Live/Manual)
Container(
height: 30 * scaleFactor,
padding: EdgeInsets.symmetric(
horizontal: 12 * scaleFactor,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6 * scaleFactor),
color: getDecorationColor(details.status)
),
child: Center(
child: Text(
details.status ?? "-",
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: getTextColor(details.status.toString()),
),
),
),
),
),
],
],
),
),
),
// Employee Details
_buildSectionHeader("Employee Details", scaleFactor),
_buildDetailTile("Employee Name", details.employeeName, scaleFactor),
_buildDetailTile("Created Employee", details.createdEmpName, scaleFactor),
// Check In/Out
_buildSectionHeader("Check In/Out Details", scaleFactor),
_buildDate_TimeTile("Check In Date & Time", details.date, details.checkInTime, scaleFactor),
_buildDate_TimeTile("Check Out Date & Time", details.date, details.checkOutTime, scaleFactor),
_buildDetailTile("Original Check In", details.checkInTime, scaleFactor),
_buildDetailTile("Original Check Out", "--", scaleFactor),
_buildDetailTile("Original Check In Location", details.checkInLocation, scaleFactor),
_buildDetailTile("Original Check Out Location", details.checkOutLocation, scaleFactor),
buildLocationTile("Location", details.location, scaleFactor),
// Proofs
if ((details.checkInProofDirFilePath != null && details.checkInProofDirFilePath!.isNotEmpty) ||
(details.checkOutProofDirFilePath != null && details.checkOutProofDirFilePath!.isNotEmpty)) ...[
_buildSectionHeader("Proofs", scaleFactor),
if (details.checkInProofDirFilePath != null && details.checkInProofDirFilePath!.isNotEmpty)
_buildProofLink(context, "Check In Proof", details.checkInProofDirFilePath, scaleFactor),
if (details.checkOutProofDirFilePath != null && details.checkOutProofDirFilePath!.isNotEmpty)
_buildProofLink(context, "Check Out Proof", details.checkOutProofDirFilePath, scaleFactor),
// Employee Details
_buildSectionHeader("Employee Details", scaleFactor),
_buildDetailTile("Employee Name", details.employeeName, scaleFactor),
_buildDetailTile("Created Employee", details.createdEmpName, scaleFactor),
// Check In/Out
_buildSectionHeader("Check In/Out Details", scaleFactor),
_buildDate_TimeTile("Check In Date & Time", details.date, details.checkInTime, scaleFactor),
_buildDate_TimeTile("Check Out Date & Time", details.date, details.checkOutTime, scaleFactor),
_buildDetailTile("Original Check In", details.checkInTime, scaleFactor),
_buildDetailTile("Original Check Out", "--", scaleFactor),
_buildDetailTile("Original Check In Location", details.checkInLocation, scaleFactor),
_buildDetailTile("Original Check Out Location", details.checkOutLocation, scaleFactor),
buildLocationTile("Location", details.location, scaleFactor),
// Proofs
if ((details.checkInProofDirFilePath != null && details.checkInProofDirFilePath!.isNotEmpty) ||
(details.checkOutProofDirFilePath != null && details.checkOutProofDirFilePath!.isNotEmpty)) ...[
_buildSectionHeader("Proofs", scaleFactor),
if (details.checkInProofDirFilePath != null && details.checkInProofDirFilePath!.isNotEmpty)
_buildProofLink(context, "Check In Proof", details.checkInProofDirFilePath, scaleFactor),
if (details.checkOutProofDirFilePath != null && details.checkOutProofDirFilePath!.isNotEmpty)
_buildProofLink(context, "Check Out Proof", details.checkOutProofDirFilePath, scaleFactor),
],
// Remarks & Approvals
_buildSectionHeader("Remarks & Approvals", scaleFactor),
_buildDetailTile("Level 1 Approved By", details.level1EmpName, scaleFactor),
_buildDetailTile("Level 2 Approved By", details.level2EmpName, scaleFactor),
_buildDetailTile("Level 1 Remark", details.level1Remarks, scaleFactor),
_buildDetailTile("Level 2 Remark", details.level2Remarks, scaleFactor),
///remain data
_buildSectionHeader("Other Details", scaleFactor),
_buildDetailTile("Check In Type", details.checkInType, scaleFactor),
_buildDetailTile("Check Out Type", details.chechOutType, scaleFactor),
_buildDetailTile("Check Out Time", details.checkOutTime, scaleFactor),
// Attendance Info
_buildDetailTile("ID", details.id, scaleFactor),
_buildDetailTile("Attendance Type", details.attendanceType, scaleFactor),
_buildDetailTile("Note", details.note, scaleFactor),
_buildDetailTile("Created Datetime", details.requestedDatetime, scaleFactor),
],
// Remarks & Approvals
_buildSectionHeader("Remarks & Approvals", scaleFactor),
_buildDetailTile("Level 1 Approved By", details.level1EmpName, scaleFactor),
_buildDetailTile("Level 2 Approved By", details.level2EmpName, scaleFactor),
_buildDetailTile("Level 1 Remark", details.level1Remarks, scaleFactor),
_buildDetailTile("Level 2 Remark", details.level2Remarks, scaleFactor),
///remain data
_buildSectionHeader("Other Details", scaleFactor),
_buildDetailTile("Check In Type", details.checkInType, scaleFactor),
_buildDetailTile("Check Out Type", details.chechOutType, scaleFactor),
_buildDetailTile("Check Out Time", details.checkOutTime, scaleFactor),
// Attendance Info
_buildDetailTile("ID", details.id, scaleFactor),
_buildDetailTile("Attendance Type", details.attendanceType, scaleFactor),
_buildDetailTile("Note", details.note, scaleFactor),
_buildDetailTile("Created Datetime", details.requestedDatetime, scaleFactor),
],
),
),
),
),
SizedBox(height: 30 * scaleFactor),
SizedBox(height: 30 * scaleFactor),
],
),
);
},
),
bottomNavigationBar: (widget.mode == "apr_lvl1"
&& !_actionSubmitted
&& provider.response?.requestDetails?.status != "Level 1 Approved"
&& provider.response?.requestDetails?.status != "Rejected")
? Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xffFFFFFF),
Color(0x00FFFFFF),
],
),
);
},
),
bottomNavigationBar: widget.mode == "apr_lvl1"
? Container(
decoration: const BoxDecoration(color: Colors.white),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
height: 80,
child: Row(
children: [
/// Reject Button
Expanded(
child: InkWell(
onTap: () {
showRemarkSheet(
context: context,
actionType: "Reject",
onSubmit: (remark) {
provider.rejectAttendanceRequest(
context,
mode: widget.mode,
type: "Rejected",
remarks: remark,
id: provider.response!.requestDetails!.id!,
);
},
);
},
child: Container(
alignment: Alignment.center,
height: 45,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: const Color(0xFFFFFFFF),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset("assets/svg/finance/level_reject_ic.svg"),
const SizedBox(width: 6),
const Text("Reject"),
],
),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
height: 61,
child: Column(
children: [
Row(
children: [
/// Reject Button
Expanded(
child: InkWell(
onTap: () {
showRemarkSheet(
context: context,
actionType: "Reject",
onSubmit: (remark) {
provider.rejectApproveAttendanceRequest(
context,
mode: widget.mode,
type: "Rejected",
remarks: remark,
id: provider.response!.requestDetails!.id!,
);
},
).then((_) {
provider.fetchAttendanceRequestDetail(context, widget.attendanceListId); // or setState(() {}) if needed
});
},
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset("assets/svg/finance/level_reject_ic.svg"),
const SizedBox(width: 6),
const Text("Reject"),
],
),
),
),
),
),
),
),
const SizedBox(width: 10),
/// Approve Button
Expanded(
child: InkWell(
onTap: () {
showRemarkSheet(
context: context,
actionType: "Approve",
onSubmit: (remark) {
provider.rejectAttendanceRequest(
context,
mode: widget.mode,
type: "Approved",
remarks: remark,
id: provider.response!.requestDetails!.id!,
);
},
);
},
child: Container(
alignment: Alignment.center,
height: 45,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: const Color(0xFFFFFFFF),
/// Vertical Divider
Container(
width: 1,
height: 45,
color: Colors.grey.shade300,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset("assets/svg/finance/level_approve_ic.svg"),
const SizedBox(width: 6),
const Text("Approve"),
],
/// Approve Button
Expanded(
child: InkWell(
onTap: () {
showRemarkSheet(
context: context,
actionType: "Approve",
onSubmit: (remark) async {
await provider.rejectApproveAttendanceRequest(
context,
mode: widget.mode,
type: "Approved",
remarks: remark,
id: provider.response!.requestDetails!.id!,
);
},
).then((_) {
provider.fetchAttendanceRequestDetail(context, widget.attendanceListId); // or setState(() {}) if needed
});
},
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset("assets/svg/finance/level_approve_ic.svg"),
const SizedBox(width: 6),
const Text("Approve"),
],
),
),
),
),
),
],
),
),
],
),
)
: const SizedBox.shrink(),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
},
)
SizedBox(height: 0,)
],
),
)
: const SizedBox.shrink(),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
},
)
),
);
}
......@@ -411,24 +440,28 @@ class _AttendanceRequestDetailScreenState
),
const SizedBox(height: 6),
TextField(
controller: remarkController,
maxLines: 3,
onChanged: (val) {
if (remarkError != null && val.isNotEmpty) {
updateState(() => remarkError = null);
}
},
decoration: InputDecoration(
hintText: "Enter your remark here...",
filled: true,
fillColor: Colors.grey.shade100,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
contentPadding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 12,
Container(
margin: const EdgeInsets.only(bottom: 6),
decoration: BoxDecoration(
color: AppColors.text_field_color,
borderRadius: BorderRadius.circular(14),
),
child: TextField(
controller: remarkController,
maxLines: 3,
onChanged: (val) {
if (remarkError != null && val.isNotEmpty) {
updateState(() => remarkError = null);
}
},
decoration: InputDecoration(
hintText: "Enter your remark here...",
hintStyle: TextStyle(
color: Colors.grey.shade500, // Customize this color
fontSize: 14, // Optional: tweak font size
),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
),
),
),
......@@ -444,7 +477,7 @@ class _AttendanceRequestDetailScreenState
child: Container(
height: 45,
decoration: BoxDecoration(
color: Colors.red.shade100,
color: Color(0x12AAAAAA),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
......@@ -478,7 +511,6 @@ class _AttendanceRequestDetailScreenState
);
}
},
child: Container(
height: 45,
decoration: BoxDecoration(
......@@ -553,20 +585,23 @@ class _AttendanceRequestDetailScreenState
/// for location
/// Location Tile
Widget buildLocationTile(String label, String? value, double scaleFactor) {
return FutureBuilder<String>(
future: getReadableLocation(value),
builder: (context, snapshot) {
final locationText = snapshot.data ?? "-";
final locationText = snapshot.connectionState == ConnectionState.done
? (snapshot.data ?? value ?? "-")
: value ?? "-";
return Padding(
padding: EdgeInsets.symmetric(vertical: 6 * scaleFactor),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start, // aligns top when wrapping
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Label
Expanded(
flex: 5, // ratio (adjust same as your Date/Time tile)
flex: 5,
child: Text(
label,
style: TextStyle(
......@@ -579,14 +614,13 @@ class _AttendanceRequestDetailScreenState
// Value (Clickable Location)
Expanded(
flex: 5, // take remaining space
flex: 5,
child: GestureDetector(
onTap: () async {
final uri = Uri.parse(
"https://www.google.com/maps/search/?api=1&query=$value");
if (await canLaunchUrl(uri)) {
await launchUrl(uri,
mode: LaunchMode.externalApplication);
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
},
child: Text(
......@@ -609,20 +643,40 @@ class _AttendanceRequestDetailScreenState
);
}
/// Convert coordinates -> human readable location
/// Convert coordinates -> full human readable location
Future<String> getReadableLocation(String? value) async {
if (value == null) return "-";
if (value == null || value.isEmpty) return "-";
try {
List<Location> locations = await locationFromAddress(value);
List<Placemark> placemarks = await placemarkFromCoordinates(
locations[0].latitude,
locations[0].longitude,
);
return placemarks.first.locality ?? value;
// Expecting "lat,lng"
final parts = value.split(',');
if (parts.length != 2) return value;
final lat = double.tryParse(parts[0].trim());
final lng = double.tryParse(parts[1].trim());
if (lat == null || lng == null) return value;
final placemarks = await placemarkFromCoordinates(lat, lng);
final place = placemarks.first;
// Include more details
final address = [
place.name,
place.street, // e.g. "A-46, Lata Enclave"
place.subLocality, // e.g. "Madhura Nagar"
place.locality, // e.g. "Hyderabad"
place.administrativeArea, // e.g. "Telangana"
place.postalCode, // e.g. "500038"
place.country // e.g. "India"
].where((e) => e != null && e.isNotEmpty).join(", ");
return address;
} catch (e) {
return value; // fallback to raw coordinates
}
}
/// for date and time
Widget _buildDate_TimeTile(String label, String? date, String? time, double scaleFactor) {
return Padding(
......@@ -718,8 +772,16 @@ class _AttendanceRequestDetailScreenState
context,
MaterialPageRoute(
builder:
(context) => Image.network(filePath),
// Fileviewer(fileName: label, fileUrl: "assets/images/capa.svg"),
(
context,
) => Fileviewer(
fileName:
filePath ??
"",
fileUrl:
filePath ??
"",
),
),
);
},
......
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:flutter_svg/svg.dart';
import 'package:generp/Notifiers/hrmProvider/AttendanceDetailsProvider.dart';
import 'package:generp/Utils/GlobalConstants.dart';
import 'package:generp/screens/hrm/AddManualAttendance.dart';
import 'package:generp/screens/hrm/AttendanceRequestDetail.dart';
import 'package:provider/provider.dart';
import '../../Notifiers/HomeScreenNotifier.dart';
import '../../Notifiers/hrmProvider/attendanceListProvider.dart';
import '../../Utils/app_colors.dart';
import '../../Utils/commonWidgets.dart';
......@@ -33,6 +36,11 @@ class _AttendanceListScreenState extends State<AttendanceListScreen> {
@override
Widget build(BuildContext context) {
String _truncate(String text, int maxLength) {
if (text.length <= maxLength) return text;
return text.substring(0, maxLength).trim() + '...';
}
return SafeArea(
top: false,
child: ChangeNotifierProvider(
......@@ -46,6 +54,7 @@ class _AttendanceListScreenState extends State<AttendanceListScreen> {
builder: (context, child) {
return Consumer<Attendancelistprovider>(
builder: (context, provider, child) {
final requestProvider = Provider.of<AttendanceDetailsProvider>(context, listen: false);
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
......@@ -80,8 +89,8 @@ class _AttendanceListScreenState extends State<AttendanceListScreen> {
final provider = Provider.of<Attendancelistprovider>(context, listen: false);
provider.updateFiltersFromSheet(
context,
widget.mode,
context,
type: result['type'] ?? "All",
selectedValue: result['selectedValue'] ?? "This Month",
customRange: result['dateRange'],
......@@ -134,9 +143,9 @@ class _AttendanceListScreenState extends State<AttendanceListScreen> {
if (provider.isLoading) {
return const Center(child: CircularProgressIndicator(color: Colors.blue));
}
if (provider.errorMessage != null) {
return Center(child: Text(provider.errorMessage!));
}
// if (provider.errorMessage != null) {
// return Center(child: Text(provider.errorMessage!));
// }
if (provider.response?.requestList == null ||
provider.response!.requestList!.isEmpty) {
return const Center(
......@@ -154,105 +163,221 @@ class _AttendanceListScreenState extends State<AttendanceListScreen> {
itemBuilder: (context, index) {
final item = list[index];
return InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () {
/// navigation flow
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AttendanceRequestDetailScreen(
attendanceListId: item.id,
mode: widget.mode,
),
final canSwipe = widget.mode == "apr_lvl1" &&
item.status != "Level 1 Approved" &&
item.status != "Rejected";
final homeProvider = Provider.of<HomescreenNotifier>(context, listen: false);
return Slidable(
key: ValueKey(item.id),
// Left swipe (Reject)
startActionPane: canSwipe
? ActionPane(
motion: const ScrollMotion(),
dragDismissible: false,
children: [
SlidableAction(
onPressed: (_) {
showRemarkSheet(
context: context,
actionType: "Reject",
onSubmit: (remark) async {
await provider.rejectApproveAttendanceRequest(
session: homeProvider.session,
empId: homeProvider.empId,
mode: widget.mode,
type: "Rejected",
remarks: remark,
id: item.id ?? "0",
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Attendance request rejected successfully.")),
);
// refresh list
provider.fetchAttendanceRequests(context, widget.mode);
},
).then((_) {
provider.fetchAttendanceRequests(context, widget.mode); // or setState(() {}) if needed
});
},
backgroundColor: const Color(0xFFFFE5E5),
foregroundColor: const Color(0xFFEF3739),
icon: Icons.clear,
label: 'Reject',
),
],
)
: null,
// Right swipe (Approve)
endActionPane: canSwipe
? ActionPane(
motion: const ScrollMotion(),
dragDismissible: false,
children: [
SlidableAction(
onPressed: (context) {
showRemarkSheet(
context: context,
actionType: "Approve",
onSubmit: (remark) async {
await provider.rejectApproveAttendanceRequest(
session: homeProvider.session,
empId: homeProvider.empId,
mode: widget.mode,
type: "Approved",
remarks: remark,
id: item.id ?? "0",
);
},
).then((_) {
provider.fetchAttendanceRequests(context, widget.mode);
});
print("######################################");
},
backgroundColor: const Color(0xFFE9FFE8),
foregroundColor: const Color(0xFF4CB443),
icon: Icons.check,
label: 'Approve',
),
);
},
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8.5, vertical: 5),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8.5),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
/// Left Avatar Circle
Container(
height: 48,
width: 50,
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: _getAvatarColor(item.status),
shape: BoxShape.circle,
),
child: Center(
child: Text(
getText(item.status),
style: TextStyle(
color: _getTextColor(item.status),
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
],
)
: null,
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AttendanceRequestDetailScreen(
attendanceListId: item.id,
mode: widget.mode,
),
),
const SizedBox(width: 10),
/// Middle Section
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.type ?? "-",
maxLines: 1,
overflow: TextOverflow.ellipsis,
);
},
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8.5, vertical: 5),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8.5),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
/// Left Avatar Circle
Container(
height: 48,
width: 50,
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: _getAvatarColor(item.status),
shape: BoxShape.circle,
),
child: Center(
child: Text(
getText(item.status),
style: TextStyle(
fontFamily: "JakartaRegular",
color: _getTextColor(item.status),
fontSize: 14,
color: AppColors.semi_black,
fontWeight: FontWeight.bold,
),
),
Text(
item.date ?? "-",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.grey_semi,
),
),
const SizedBox(width: 10),
/// Middle Section
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.mode == "apr_lvl1"
? _truncate(item.employeeName ?? "-", 20)
: item.type ?? "-",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.semi_black,
),
),
),
],
Row(
children: [
Text(
widget.mode == "apr_lvl1"
? item.type ?? "-"
: item.type ?? "-",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.grey_semi,
),
),
const SizedBox(width: 2),
Text(
" - ${item.date}" ?? "-",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.grey_semi,
),
),
],
),
],
),
),
),
/// Right Status (Live / Manual)
Text(
item.attendanceType ?? "-",
textAlign: TextAlign.right,
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: (item.attendanceType ?? "").toLowerCase() == "live"
? Colors.green
: Colors.orange,
/// Right Status (Live / Manual)
Text(
item.attendanceType ?? "-",
textAlign: TextAlign.right,
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: (item.attendanceType ?? "").toLowerCase() == "live"
? Colors.green
: Colors.orange,
),
),
),
],
],
),
),
),
);
},
);
},
),
)
],
),
bottomNavigationBar: Container(
bottomNavigationBar: widget.mode == "apr_lvl1"
? null
: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
alignment: Alignment.bottomCenter,
height: 54,
decoration: const BoxDecoration(color: Colors.white),
height: 61,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xffFFFFFF),
Color(0x00FFFFFF),
],
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
......@@ -269,7 +394,7 @@ class _AttendanceListScreenState extends State<AttendanceListScreen> {
),
),
).then((_) {
provider.fetchAttendanceRequests(context,widget.mode);
provider.fetchAttendanceRequests(context, widget.mode);
});
},
child: Row(
......@@ -314,6 +439,7 @@ class _AttendanceListScreenState extends State<AttendanceListScreen> {
],
),
),
);
},
......@@ -323,6 +449,190 @@ class _AttendanceListScreenState extends State<AttendanceListScreen> {
);
}
Future<void> showRemarkSheet({
required BuildContext context,
required String actionType, // "Approved" or "Rejected"
required Function(String remark) onSubmit,
}) {
final remarkController = TextEditingController();
String? remarkError;
return showModalBottomSheet(
useSafeArea: true,
isDismissible: true,
isScrollControlled: true,
showDragHandle: true,
backgroundColor: Colors.white,
enableDrag: true,
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
void updateState(VoidCallback fn) {
setState(fn);
}
bool validateFields() {
String? newRemarkError =
remarkController.text.trim().isEmpty ? "Remark required" : null;
if (remarkError != newRemarkError) {
updateState(() {
remarkError = newRemarkError;
});
}
return newRemarkError == null;
}
Widget errorText(String? msg) => msg == null
? const SizedBox()
: Padding(
padding: const EdgeInsets.only(top: 4, left: 4),
child: Text(
msg,
style: const TextStyle(
color: Colors.red,
fontSize: 12,
fontFamily: "JakartaMedium",
),
),
);
return SafeArea(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"$actionType Attendance Request",
style: const TextStyle(
fontSize: 16,
color: Colors.black87,
fontFamily: "JakartaMedium",
),
),
const SizedBox(height: 16),
Text(
"Remark",
style: const TextStyle(
fontSize: 14,
color: Colors.black87,
fontFamily: "JakartaMedium",
),
),
const SizedBox(height: 6),
Container(
margin: const EdgeInsets.only(bottom: 6),
decoration: BoxDecoration(
color: AppColors.text_field_color,
borderRadius: BorderRadius.circular(14),
),
child: TextField(
controller: remarkController,
maxLines: 3,
onChanged: (val) {
if (remarkError != null && val.isNotEmpty) {
updateState(() => remarkError = null);
}
},
decoration: InputDecoration(
hintText: "Enter your remark here...",
hintStyle: TextStyle(
color: Colors.grey.shade500, // Customize this color
fontSize: 14, // Optional: tweak font size
),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
),
),
),
errorText(remarkError),
const SizedBox(height: 20),
Row(
children: [
Expanded(
child: InkResponse(
onTap: () => Navigator.pop(context),
child: Container(
height: 45,
decoration: BoxDecoration(
color: Color(0x12AAAAAA),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Text(
"Cancel",
style: TextStyle(
color: Colors.red,
fontFamily: "JakartaMedium",
),
),
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: InkResponse(
onTap: () async {
if (validateFields()) {
final remark = remarkController.text.trim();
// Call provider
await onSubmit(remark);
// SnackBar here
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Request submitted successfully"),
backgroundColor: Colors.green,
behavior: SnackBarBehavior.floating,
),
);
}
},
child: Container(
height: 45,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Text(
"Submit",
style: TextStyle(
color: Colors.white,
fontFamily: "JakartaMedium",
),
),
),
),
),
),
],
),
],
),
),
),
);
},
);
},
);
}
/// Avatar color generator
Color _getAvatarColor(value) {
......
......@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:generp/screens/hrm/Attendancelist.dart';
import 'package:provider/provider.dart';
import '../../Utils/app_colors.dart';
import 'AttendanceRequestDetail.dart';
import 'LeaveApplicationScreen.dart';
......@@ -39,186 +38,189 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: const Color(0xFFCEEDFF),
title: Row(
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25,
return SafeArea(
top: false,
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: const Color(0xFFCEEDFF),
title: Row(
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25,
),
),
),
const SizedBox(width: 10),
Text(
"HRM",
style: TextStyle(
fontSize: 18,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
const SizedBox(width: 10),
Text(
"HRM",
style: TextStyle(
fontSize: 18,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
),
),
),
],
],
),
),
),
backgroundColor: const Color(0xffF6F6F8),
body: SingleChildScrollView(
child: Column(
children: [
/// Background
Stack(
children: [
Container(
width: double.infinity,
height: 490,
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,
),
backgroundColor: const Color(0xffF6F6F8),
body: SingleChildScrollView(
child: Column(
children: [
/// Background
Stack(
children: [
Container(
width: double.infinity,
height: 490,
color: const Color(0xffF6F6F8),
),
),
Container(
width: double.infinity,
padding: const EdgeInsets.only(top: 1, bottom: 30),
child: Image.asset(
"assets/images/vector.png",
height: 230,
Container(
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,
),
),
),
),
/// Content
Column(
children: [
/// Top Illustration & Button
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,
padding: const EdgeInsets.only(top: 60, bottom: 30),
child: Column(
children: [
SvgPicture.asset(
"assets/images/capa.svg",
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
fit: BoxFit.fitWidth,
),
),
/// Content
Column(
children: [
/// Top Illustration & Button
Container(
width: double.infinity,
padding: const EdgeInsets.only(top: 60, bottom: 30),
child: Column(
children: [
SvgPicture.asset(
"assets/images/capa.svg",
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(
builder: (context) => OrgChartt(),
],
),
),
/// 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 ?? "",
),
);
},
);
},
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> {
Expanded(
flex: 1,
child: Container(
height: constraints.maxHeight * 0.5, // Responsive size
width: constraints.maxHeight * 0.5, // Responsive size
height: constraints.maxHeight * 0.39,
width: constraints.maxHeight * 0.39,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFFEDF8FF),
......@@ -296,8 +298,8 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
child: Center(
child: SvgPicture.asset(
assetIcon,
height: constraints.maxHeight * 0.3, // Responsive size
width: constraints.maxHeight * 0.3, // Responsive size
height: constraints.maxHeight * 0.19,
width: constraints.maxHeight * 0.19,
),
),
),
......
......@@ -20,306 +20,334 @@ class LeaveApplicationDetailScreen extends StatefulWidget {
}
class _LeaveApplicationDetailScreenState extends State<LeaveApplicationDetailScreen> {
bool _actionSubmitted = false;
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => LeaveApplicationDetailsProvider()..fetchLeaveApplicationDetails(context, widget.leaveRequestId),
child: Consumer<LeaveApplicationDetailsProvider>(
builder: (context, provider, child) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: const Color(0xFFFFFFFF),
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25,
return SafeArea(
top: false,
child: ChangeNotifierProvider(
create: (_) => LeaveApplicationDetailsProvider()..fetchLeaveApplicationDetails(context, widget.leaveRequestId),
child: Consumer<LeaveApplicationDetailsProvider>(
builder: (context, provider, child) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: const Color(0xFFFFFFFF),
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25,
),
),
),
const SizedBox(width: 10),
InkResponse(
onTap: () => Navigator.pop(context, true),
child: Text(
"Leave Application Details",
style: TextStyle(
fontSize: 18,
height: 1.1,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
const SizedBox(width: 10),
InkResponse(
onTap: () => Navigator.pop(context, true),
child: Text(
"Leave Application Details",
style: TextStyle(
fontSize: 18,
height: 1.1,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
),
),
),
),
],
],
),
),
),
backgroundColor: const Color(0xFFF6F6F8),
body: Builder(
builder: (context) {
if (provider.isLoading) {
return const Center(child: CircularProgressIndicator(color: Colors.blue));
}
if (provider.errorMessage != null) {
return Center(child: Text(provider.errorMessage!));
}
if (provider.response?.requestDetails == null) {
return const Center(child: Text("No details found"));
}
final details = provider.response!.requestDetails!;
/// Screen content
return SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Header with status
Container(
margin: const EdgeInsets.only(bottom: 0.5),
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 2),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
/// Left Avatar
Container(
height: 48,
width: 48,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFFEDF8FF), // icon bg
),
child: Center(
child: SvgPicture.asset(
height: 28,
width: 28,
"assets/svg/hrm/leaveApplication.svg", // Use appropriate icon
fit: BoxFit.contain,
backgroundColor: const Color(0xFFF6F6F8),
body: Builder(
builder: (context) {
if (provider.isLoading) {
return const Center(child: CircularProgressIndicator(color: Colors.blue));
}
if (provider.response?.requestDetails == null) {
return const Center(child: Text("No details found"));
}
// Get screen dimensions for responsive scaling
final screenWidth = MediaQuery.of(context).size.width;
final screenHeight = MediaQuery.of(context).size.height;
// Scale factors based on screen size
final scaleFactor = screenWidth / 360; // Base width for scaling
final details = provider.response!.requestDetails!;
/// Screen content
return SingleChildScrollView(
padding: EdgeInsets.all(16.0 * scaleFactor),
child: Column(
children: [
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16 * scaleFactor),
),
elevation: 0,
child: Padding(
padding: EdgeInsets.all(10.0 * scaleFactor),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Header with status
Container(
margin: EdgeInsets.only(bottom: 0.5 * scaleFactor),
padding: EdgeInsets.symmetric(
vertical: 10 * scaleFactor,
horizontal: 2 * scaleFactor,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12 * scaleFactor),
),
child: Row(
children: [
/// Left Avatar
Container(
height: 48 * scaleFactor,
width: 48 * scaleFactor,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFFEDF8FF),
),
child: Center(
child: SvgPicture.asset(
"assets/svg/hrm/leaveApplication.svg",
height: 28 * scaleFactor,
width: 28 * scaleFactor,
fit: BoxFit.contain,
),
),
),
),
const SizedBox(width: 12),
/// Middle text
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
details.leaveType ?? "-",
style: TextStyle(
decoration: TextDecoration.underline,
decorationStyle:
TextDecorationStyle.dotted,
decorationColor: AppColors.grey_thick,
height: 1.2,
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.semi_black,
SizedBox(width: 12 * scaleFactor),
/// Middle text
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
details.leaveType ?? "-",
style: TextStyle(
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.dotted,
decorationColor: AppColors.grey_thick,
height: 1.2,
fontFamily: "JakartaRegular",
fontSize: 12 * scaleFactor,
color: AppColors.semi_black,
),
),
),
const SizedBox(height: 2),
Text(
"Applied: ${details.appliedDate ?? "-"}",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.app_blue,
SizedBox(height: 2 * scaleFactor),
Text(
"Applied: ${details.appliedDate ?? "-"}",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 12 * scaleFactor,
color: AppColors.app_blue,
),
),
),
],
],
),
),
),
/// Right side status badge
Container(
height: 30,
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: _getStatusBackgroundColor(details.status),
),
child: Center(
child: Text(
details.status ?? "-",
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 12,
color: _getStatusTextColor(details.status),
/// Right side status badge
Container(
height: 28 * scaleFactor,
padding: EdgeInsets.symmetric(
horizontal: 5 * scaleFactor,
vertical: 1 * scaleFactor,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8 * scaleFactor),
color: _getStatusBackgroundColor(details.status),
),
child: Center(
child: Text(
details.status ?? "-",
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 10 * scaleFactor,
color: _getStatusTextColor(details.status),
),
),
),
),
),
],
],
),
),
),
/// Leave Details
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
_buildSectionHeader("Leave Details"),
_buildDetailTile("Application ID", details.id),
_buildDetailTile("Applied Date", details.appliedDate),
_buildDetailTile("Leave Type", details.leaveType),
_buildDateRangeTile("Leave Period", details.fromDate, details.toDate),
_buildTimeRangeTile("Time Period", details.fromTime, details.toTime),
_buildDetailTile("Reason", details.reason),
/// Approval Details
_buildSectionHeader("Approval Details"),
_buildDetailTile("Requested To", details.requestedTo),
_buildDetailTile("Approved By", details.approvedBy),
_buildDetailTile("Approved Date", details.approvedDate),
_buildDetailTile("Approval Remarks", details.approvalRemarks),
/// Additional Information
_buildSectionHeader("Additional Information"),
_buildDetailTile("Status", details.status),
_buildDetailTile("From Time", details.fromTime),
_buildDetailTile("To Time", details.toTime),
],
/// Leave Details
Padding(
padding: EdgeInsets.all(8.0 * scaleFactor),
child: Column(
children: [
_buildSectionHeader("Leave Details", ),
_buildDetailTile("Application ID", details.id, scaleFactor),
_buildDetailTile("Applied Date", details.appliedDate, scaleFactor),
_buildDetailTile("Leave Type", details.leaveType, scaleFactor),
_buildDateRangeTile("Leave Period", details.fromDate, details.toDate, scaleFactor),
_buildTimeRangeTile("Time Period", details.fromTime, details.toTime, scaleFactor),
_buildDetailTile("Reason", details.reason, scaleFactor),
/// Approval Details
_buildSectionHeader("Approval Details", ),
_buildDetailTile("Requested To", details.requestedTo, scaleFactor),
_buildDetailTile("Approved By", details.approvedBy, scaleFactor),
_buildDetailTile("Approved Date", details.approvedDate, scaleFactor),
_buildDetailTile("Approval Remarks", details.approvalRemarks, scaleFactor),
/// Additional Information
_buildSectionHeader("Additional Information", ),
_buildDetailTile("Status", details.status, scaleFactor),
_buildDetailTile("From Time", details.fromTime, scaleFactor),
_buildDetailTile("To Time", details.toTime, scaleFactor),
],
),
),
),
],
],
),
),
),
),
const SizedBox(height: 30),
SizedBox(height: 30 * scaleFactor),
],
),
);
},
),
bottomNavigationBar: (widget.mode == "teamleader"
&& !_actionSubmitted
&& provider.response?.requestDetails?.status != "Approved"
&& provider.response?.requestDetails?.status != "Rejected")
? Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xffFFFFFF),
Color(0x00FFFFFF),
],
),
);
},
),
bottomNavigationBar: widget.mode == "teamleader"
? Container(
decoration: const BoxDecoration(color: Colors.white),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
height: 80,
child: Row(
children: [
/// Reject Button
Expanded(
child: InkWell(
onTap: () {
showRemarkSheet(
context: context,
actionType: "Reject",
onSubmit: (remark) {
provider.leaveRequestRejectApprove(
context,
mode: widget.mode,
type: "Rejected",
remarks: remark,
id: provider.response!.requestDetails!.id!,
);
},
);
},
child: Container(
alignment: Alignment.center,
height: 45,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: const Color(0xFFFFFFFF),
border: Border.all(
color: const Color(0xFFE0E0E0),
width: 0.5,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset("assets/svg/finance/level_reject_ic.svg"),
const SizedBox(width: 6),
const Text(
"Reject",
style: TextStyle(
color: Colors.black87,
fontSize: 14,
fontFamily: "JakartaMedium",
),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
height: 61,
child: Column(
children: [
Row(
children: [
/// Reject Button
Expanded(
child: InkWell(
onTap: () {
showRemarkSheet(
context: context,
actionType: "Reject",
onSubmit: (remark) {
provider.leaveRequestRejectApprove(
context,
mode: widget.mode,
type: "Rejected",
remarks: remark,
id: provider.response!.requestDetails!.id!,
);
},
).then((_) {
provider.fetchLeaveApplicationDetails(context, widget.leaveRequestId);
});
},
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset("assets/svg/finance/level_reject_ic.svg"),
const SizedBox(width: 6),
const Text(
"Reject",
style: TextStyle(
color: Colors.black87,
fontSize: 14,
fontFamily: "JakartaMedium",
),
),
],
),
),
],
),
),
),
),
const SizedBox(width: 10),
/// Approve Button
Expanded(
child: InkWell(
onTap: () {
showRemarkSheet(
context: context,
actionType: "Approve",
onSubmit: (remark) {
provider.leaveRequestRejectApprove(
context,
mode: widget.mode,
type: "Approved",
remarks: remark,
id: provider.response!.requestDetails!.id!,
);
},
);
},
child: Container(
alignment: Alignment.center,
height: 45,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: const Color(0xFFFFFFFF),
border: Border.all(
color: const Color(0xFFE0E0E0),
width: 0.5,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset("assets/svg/finance/level_approve_ic.svg"),
const SizedBox(width: 6),
const Text(
"Approve",
style: TextStyle(
color: Colors.black87,
fontSize: 14,
fontFamily: "JakartaMedium",
/// Vertical Divider
Container(
width: 1,
height: 45,
color: Colors.grey.shade300,
),
/// Approve Button
Expanded(
child: InkWell(
onTap: () {
showRemarkSheet(
context: context,
actionType: "Approve",
onSubmit: (remark) async {
await provider.leaveRequestRejectApprove(
context,
mode: widget.mode,
type: "Approved",
remarks: remark,
id: provider.response!.requestDetails!.id!,
);
},
).then((_) {
provider.fetchLeaveApplicationDetails(context, widget.leaveRequestId);
});
},
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset("assets/svg/finance/level_approve_ic.svg"),
const SizedBox(width: 6),
const Text(
"Approve",
style: TextStyle(
color: Colors.black87,
fontSize: 14,
fontFamily: "JakartaMedium",
),
),
],
),
),
],
),
),
),
],
),
),
],
),
)
: const SizedBox.shrink(),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
},
SizedBox(height: 2,)
],
),
)
: const SizedBox.shrink(),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
},
),
),
);
}
......@@ -404,30 +432,38 @@ class _LeaveApplicationDetailScreenState extends State<LeaveApplicationDetailScr
),
const SizedBox(height: 6),
TextField(
controller: remarkController,
maxLines: 3,
onChanged: (val) {
if (remarkError != null && val.isNotEmpty) {
updateState(() => remarkError = null);
}
},
decoration: InputDecoration(
hintText: "Enter your remark here...",
filled: true,
fillColor: Colors.grey.shade100,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
Container(
margin: const EdgeInsets.only(bottom: 6),
decoration: BoxDecoration(
color: AppColors.text_field_color,
borderRadius: BorderRadius.circular(14),
),
child: TextField(
controller: remarkController,
maxLines: 3,
style: TextStyle(
color: Colors.black, // Entered text color
fontSize: 14, // Optional: adjust font size
),
contentPadding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 12,
onChanged: (val) {
if (remarkError != null && val.isNotEmpty) {
updateState(() => remarkError = null);
}
},
decoration: InputDecoration(
hintText: "Enter your remark here...",
hintStyle: TextStyle(
color: Colors.grey.shade500, // Customize this color
fontSize: 14, // Optional: tweak font size
),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
),
),
),
errorText(remarkError),
const SizedBox(height: 20),
const SizedBox(height: 5),
Row(
children: [
......@@ -437,7 +473,7 @@ class _LeaveApplicationDetailScreenState extends State<LeaveApplicationDetailScr
child: Container(
height: 45,
decoration: BoxDecoration(
color: Colors.red.shade100,
color: Color(0x12AAAAAA),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
......@@ -458,10 +494,14 @@ class _LeaveApplicationDetailScreenState extends State<LeaveApplicationDetailScr
onTap: () async {
if (validateFields()) {
final remark = remarkController.text.trim();
// Call provider
await onSubmit(remark);
// SnackBar here
Navigator.pop(context);
if (mounted) {
setState(() {
_actionSubmitted = true;
});
}
// Show snackbar
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Request submitted successfully"),
......@@ -472,6 +512,7 @@ class _LeaveApplicationDetailScreenState extends State<LeaveApplicationDetailScr
}
},
child: Container(
height: 45,
decoration: BoxDecoration(
......@@ -493,6 +534,7 @@ class _LeaveApplicationDetailScreenState extends State<LeaveApplicationDetailScr
),
],
),
SizedBox(height: 2,)
],
),
),
......@@ -506,31 +548,38 @@ class _LeaveApplicationDetailScreenState extends State<LeaveApplicationDetailScr
/// Reusable Row Widget for details
Widget _buildDetailTile(String label, String? value) {
Widget _buildDetailTile(String label, String? value, double scaleFactor) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 3),
padding: EdgeInsets.symmetric(vertical: 3 * scaleFactor),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start, // Align top if value wraps
children: [
// Label
Expanded(
flex: 6,
flex: 5,
child: Text(
label,
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
fontSize: 12 * scaleFactor,
color: AppColors.semi_black,
),
),
),
const SizedBox(width: 4),
// Value
Expanded(
flex: 0,
flex: 5,
child: Text(
value ?? "-",
style: const TextStyle(
fontSize: 14,
color: Color(0xff818181),
style: TextStyle(
fontSize: 12 * scaleFactor,
color: const Color(0xFF818181),
fontWeight: FontWeight.w400,
),
softWrap: true,
overflow: TextOverflow.visible,
),
),
],
......@@ -538,32 +587,40 @@ class _LeaveApplicationDetailScreenState extends State<LeaveApplicationDetailScr
);
}
/// For date range display
Widget _buildDateRangeTile(String label, String? fromDate, String? toDate) {
Widget _buildDateRangeTile(String label, String? fromDate, String? toDate, double scaleFactor) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 3),
padding: EdgeInsets.symmetric(vertical: 3 * scaleFactor),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Label
Expanded(
flex: 6,
flex: 5,
child: Text(
label,
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
fontSize: 12 * scaleFactor,
color: AppColors.semi_black,
),
),
),
const SizedBox(width: 4),
// Value
Expanded(
flex: 0,
flex: 5,
child: Text(
'${fromDate ?? "-"} to ${toDate ?? "-"}',
style: const TextStyle(
fontSize: 14,
color: Color(0xff818181),
style: TextStyle(
fontSize: 12 * scaleFactor,
color: const Color(0xFF818181),
fontWeight: FontWeight.w400,
),
softWrap: true,
overflow: TextOverflow.visible,
),
),
],
......@@ -571,38 +628,46 @@ class _LeaveApplicationDetailScreenState extends State<LeaveApplicationDetailScr
);
}
/// For time range display
Widget _buildTimeRangeTile(String label, String? fromTime, String? toTime) {
Widget _buildTimeRangeTile(String label, String? fromTime, String? toTime, double scaleFactor) {
if ((fromTime == null || fromTime.isEmpty) && (toTime == null || toTime.isEmpty)) {
return const SizedBox.shrink(); // Hide if no time data
}
return Padding(
padding: const EdgeInsets.symmetric(vertical: 3),
padding: EdgeInsets.symmetric(vertical: 3 * scaleFactor),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Label
Expanded(
flex: 6,
flex: 5,
child: Text(
label,
style: const TextStyle(
fontSize: 14,
color: Color(0xff2D2D2D),
style: TextStyle(
fontSize: 12 * scaleFactor,
color: const Color(0xff2D2D2D),
fontStyle: FontStyle.normal,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
),
),
),
const SizedBox(width: 4),
// Value
Expanded(
flex: 0,
flex: 5,
child: Text(
'${fromTime ?? "-"} to ${toTime ?? "-"}',
style: const TextStyle(
fontSize: 14,
color: Color(0xff818181),
style: TextStyle(
fontSize: 12 * scaleFactor,
color: const Color(0xff818181),
fontWeight: FontWeight.w400,
),
softWrap: true,
overflow: TextOverflow.visible,
),
),
],
......@@ -610,6 +675,7 @@ class _LeaveApplicationDetailScreenState extends State<LeaveApplicationDetailScr
);
}
/// Section header with dotted line
Widget _buildSectionHeader(String title) {
return Padding(
......
......@@ -31,278 +31,292 @@ class _LeaveApplicationListScreenState extends State<LeaveApplicationListScreen>
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) {
final provider = LeaveApplicationListProvider();
Future.microtask(() {
provider.fetchLeaveApplications(context, widget.mode);
});
return provider;
},
builder: (context, child) {
return Consumer<LeaveApplicationListProvider>(
builder: (context, provider, child) {
return Scaffold(
appBar: appbar2New(
context,
"Leave Application List",
provider.resetForm,
Row(
children: [
InkResponse(
onTap: () async {
var cf = Commondaterangefilter();
var result = await cf.showFilterBottomSheet(context);
if (result != null) {
var dateRange = result['dateRange'] as DateTimeRange?;
var formatted = result['formatted'] as List<String>;
if (formatted.isNotEmpty) {
provider.setDateRangeFilter("Custom", customRange: dateRange);
provider.fetchLeaveApplications(
context,
widget.mode,
dateRange: "Custom",
customRange: dateRange,
);
return SafeArea(
top: false,
child: ChangeNotifierProvider(
create: (_) {
final provider = LeaveApplicationListProvider();
Future.microtask(() {
provider.fetchLeaveApplications(context, widget.mode);
});
return provider;
},
builder: (context, child) {
return Consumer<LeaveApplicationListProvider>(
builder: (context, provider, child) {
return Scaffold(
appBar: appbar2New(
context,
"Leave Application List",
provider.resetForm,
Row(
children: [
InkResponse(
onTap: () async {
var cf = Commondaterangefilter();
var result = await cf.showFilterBottomSheet(context);
if (result != null) {
var dateRange = result['dateRange'] as DateTimeRange?;
var formatted = result['formatted'] as List<String>;
if (formatted.isNotEmpty) {
provider.setDateRangeFilter("Custom", customRange: dateRange);
provider.fetchLeaveApplications(
context,
widget.mode,
dateRange: "Custom",
customRange: dateRange,
);
}
}
}
},
child: SvgPicture.asset("assets/svg/filter_ic.svg", height: 25),
),
],
},
child: SvgPicture.asset("assets/svg/filter_ic.svg", height: 25),
),
],
),
0xFFFFFFFF,
),
0xFFFFFFFF,
),
backgroundColor: const Color(0xFFF6F6F8),
body: Column(
children: [
/// Filter chips (if you want visible filter indicators)
// if (provider.selectedStatus != "All" || provider.selectedDateRange != "This Month")
// Container(
// padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
// color: Colors.white,
// child: Row(
// children: [
// if (provider.selectedStatus != "All")
// Chip(
// label: Text('Status: ${provider.selectedStatus}'),
// onDeleted: () {
// provider.setStatusFilter("All");
// provider.fetchLeaveApplications(context);
// },
// ),
// if (provider.selectedDateRange != "This Month")
// Chip(
// label: Text('Date: ${provider.selectedDateRange}'),
// onDeleted: () {
// provider.setDateRangeFilter("This Month");
// provider.fetchLeaveApplications(context);
// },
// ),
// ],
// ),
// ),
backgroundColor: const Color(0xFFF6F6F8),
body: Column(
children: [
/// Filter chips (if you want visible filter indicators)
// if (provider.selectedStatus != "All" || provider.selectedDateRange != "This Month")
// Container(
// padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
// color: Colors.white,
// child: Row(
// children: [
// if (provider.selectedStatus != "All")
// Chip(
// label: Text('Status: ${provider.selectedStatus}'),
// onDeleted: () {
// provider.setStatusFilter("All");
// provider.fetchLeaveApplications(context);
// },
// ),
// if (provider.selectedDateRange != "This Month")
// Chip(
// label: Text('Date: ${provider.selectedDateRange}'),
// onDeleted: () {
// provider.setDateRangeFilter("This Month");
// provider.fetchLeaveApplications(context);
// },
// ),
// ],
// ),
// ),
/// Leave application list
Expanded(
child: Builder(
builder: (context) {
if (provider.isLoading) {
return const Center(child: CircularProgressIndicator(color: Colors.blue));
}
if (provider.errorMessage != null) {
return Center(child: Text(provider.errorMessage!));
}
if (provider.response?.requestList == null ||
provider.response!.requestList!.isEmpty) {
return const Center(child: Text("No leave applications found"));
}
final list = provider.response!.requestList!;
return ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: list.length,
itemBuilder: (context, index) {
final item = list[index];
// Parse the full string into a DateTime object
DateTime parsedFromDate = DateFormat("dd MMM yyyy, hh:mm a").parse(item.fromPeriod.toString());
String dateFromMonth = DateFormat("dd MMM").format(parsedFromDate);
// Parse the full string into a DateTime object
DateTime parsedToDate = DateFormat("dd MMM yyyy, hh:mm a").parse(item.toPeriod.toString());
/// Leave application list
Expanded(
child: Builder(
builder: (context) {
if (provider.isLoading) {
return const Center(child: CircularProgressIndicator(color: Colors.blue));
}
if (provider.errorMessage != null) {
return Center(child: Text(provider.errorMessage!));
}
if (provider.response?.requestList == null ||
provider.response!.requestList!.isEmpty) {
return const Center(child: Text("No leave applications found"));
}
String dateToMonth = DateFormat("dd MMM yyyy").format(parsedToDate);
final list = provider.response!.requestList!;
return ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
itemCount: list.length,
itemBuilder: (context, index) {
final item = list[index];
// Parse the full string into a DateTime object
DateTime parsedFromDate = DateFormat("dd MMM yyyy, hh:mm a").parse(item.fromPeriod.toString());
String dateFromMonth = DateFormat("dd MMM").format(parsedFromDate);
// Parse the full string into a DateTime object
DateTime parsedToDate = DateFormat("dd MMM yyyy, hh:mm a").parse(item.toPeriod.toString());
return InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LeaveApplicationDetailScreen(
leaveRequestId: item.id.toString(),
mode: widget.mode,
),
),
).then((_) {
provider.fetchLeaveApplications(context,widget.mode);
});
},
String dateToMonth = DateFormat("dd MMM yyyy").format(parsedToDate);
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8.5, vertical: 5),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
/// Left Status Circle
Container(
height: 48,
width: 48,
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: _getStatusBackgroundColor(item.status),
shape: BoxShape.circle,
),
child: Center(
child: Text(
_getStatusInitials(item.status),
style: TextStyle(
color: _getStatusTextColor(item.status),
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
return InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LeaveApplicationDetailScreen(
leaveRequestId: item.id.toString(),
mode: widget.mode,
),
),
const SizedBox(width: 12),
).then((_) {
provider.fetchLeaveApplications(context,widget.mode);
});
},
/// Middle Section - Leave Details
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.leaveType ?? "-",
maxLines: 1,
overflow: TextOverflow.ellipsis,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8.5, vertical: 5),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
decoration: BoxDecoration(
color: Colors.white,//Color(int.parse(item.rowColor!.replaceFirst('#', '0xff'))),
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
/// Left Status Circle
Container(
height: 48,
width: 48,
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: _getStatusBackgroundColor(item.status),
shape: BoxShape.circle,
),
child: Center(
child: Text(
_getStatusInitials(item.status),
style: TextStyle(
fontFamily: "JakartaRegular",
color: _getStatusTextColor(item.status),
fontSize: 14,
color: AppColors.semi_black,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Row(
children: [
Text(
dateFromMonth ?? "-",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.grey_semi,
),
),
),
const SizedBox(width: 12),
/// Middle Section - Leave Details
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.mode == "teamleader"
? item.employeeName ?? "-"
: item.leaveType ?? "-",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.semi_black,
),
Text(
" - ${dateToMonth}" ?? "-",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.grey_semi,
),
const SizedBox(height: 4),
Row(
children: [
Text(
dateFromMonth ?? "-",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.grey_semi,
),
),
),
],
),
// const SizedBox(height: 2),
// Text(
// "Period: ${item.fromPeriod ?? "-"} to ${item.toPeriod ?? "-"}",
// style: const TextStyle(
// fontSize: 12.5,
// color: Color(0xff818181),
// fontFamily: "Plus Jakarta Sans",
// ),
// ),
],
Text(
" - ${dateToMonth}" ?? "-",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.grey_semi,
),
),
],
),
// const SizedBox(height: 2),
// Text(
// "Period: ${item.fromPeriod ?? "-"} to ${item.toPeriod ?? "-"}",
// style: const TextStyle(
// fontSize: 12.5,
// color: Color(0xff818181),
// fontFamily: "Plus Jakarta Sans",
// ),
// ),
],
),
),
),
// /// Right Status
// Container(
// padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
// decoration: BoxDecoration(
// color: _getStatusBackgroundColor(item.status),
// borderRadius: BorderRadius.circular(10),
// ),
// child: Text(
// item.status ?? "-",
// style: TextStyle(
// fontFamily: "JakartaMedium",
// fontSize: 13,
// color: _getStatusTextColor(item.status),
// ),
// ),
// ),
],
/// Right Status
if (widget.mode == "teamleader")
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Color(0x00FFFFFF),
borderRadius: BorderRadius.circular(10),
),
child: Text(
item.leaveType ?? "-",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 13,
color: AppColors.app_blue,
),
),
),
],
),
),
),
);
},
);
},
),
)
],
),
floatingActionButtonLocation:
FloatingActionButtonLocation.centerFloat,
floatingActionButton: InkResponse(
onTap: () {
HapticFeedback.selectionClick();
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ChangeNotifierProvider(
create: (_) => LeaveApplicationListProvider(),
child: AddLeaveRequest(pageTitleName: "Add Leave Request"),
);
},
);
},
),
),
).then((_) {
provider.fetchLeaveApplications(context, widget.mode);
});
SizedBox(height: 28,)
],
),
// show add bill screen here
},
child: Container(
height: 45,
alignment: Alignment.center,
margin: EdgeInsets.symmetric(horizontal: 20),
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: AppColors.app_blue,
borderRadius: BorderRadius.circular(15),
),
child: Text(
"Add Leave Request",
style: TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
color: Colors.white,
bottomNavigationBar: widget.mode == "teamleader"
? null
: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
color: Colors.white,
child: InkResponse(
onTap: () {
HapticFeedback.selectionClick();
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ChangeNotifierProvider(
create: (_) => LeaveApplicationListProvider(),
child: AddLeaveRequest(pageTitleName: "Add Leave Request"),
),
),
).then((_) {
provider.fetchLeaveApplications(context, widget.mode);
});
// show add bill screen here
},
child: Container(
height: 45,
alignment: Alignment.center,
margin: EdgeInsets.symmetric(horizontal: 14),
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 5),
decoration: BoxDecoration(
color: AppColors.app_blue,
borderRadius: BorderRadius.circular(15),
),
child: Text(
"Add Leave Request",
style: TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
color: Colors.white,
),
),
),
),
),
),
);
},
);
},
);
},
);
},
),
);
}
/// Get status background color
Color _getStatusBackgroundColor(String? status) {
switch (status?.toLowerCase()) {
......@@ -316,13 +330,14 @@ class _LeaveApplicationListScreenState extends State<LeaveApplicationListScreen>
}
}
/// Get status text color
Color _getStatusTextColor(String? status) {
switch (status?.toLowerCase()) {
case 'approved':
return AppColors.approved_text_color;
case 'rejected':
return AppColors.rejected_text_color;
return Colors.redAccent.shade200;
case 'requested':
default:
return AppColors.requested_text_color;
......
......@@ -17,285 +17,288 @@ class RewardListScreen extends StatefulWidget {
class _RewardListScreenState extends State<RewardListScreen> {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) =>
RewardListProvider()..fetchRewardList(context),
child: Consumer<RewardListProvider>(
builder: (context, provider, child) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.white,
title: Row(
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25,
return SafeArea(
top: false,
child: ChangeNotifierProvider(
create: (_) =>
RewardListProvider()..fetchRewardList(context),
child: Consumer<RewardListProvider>(
builder: (context, provider, child) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.white,
title: Row(
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25,
),
),
),
const SizedBox(width: 10),
Text(
"Reward List",
style: TextStyle(
fontSize: 18,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
const SizedBox(width: 10),
Text(
"Reward List",
style: TextStyle(
fontSize: 18,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
),
),
),
],
],
),
// actions: [
// InkResponse(
// onTap: () {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => RewardSearchScreen(),
// settings: const RouteSettings(
// name: 'AddLiveAttendanceScreen',
// ),
// ),
// ).then((_) {
// });
// },
// child: SvgPicture.asset(
// "assets/svg/search_ic.svg",
// height: 25,
// ),
// ),
// const SizedBox(width: 20),
// ],
),
// actions: [
// InkResponse(
// onTap: () {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => RewardSearchScreen(),
// settings: const RouteSettings(
// name: 'AddLiveAttendanceScreen',
// ),
// ),
// ).then((_) {
// });
// },
// child: SvgPicture.asset(
// "assets/svg/search_ic.svg",
// height: 25,
// ),
// ),
// const SizedBox(width: 20),
// ],
),
backgroundColor: Color(0xFFF6F6F8),
body: Builder(
builder: (context) {
if (provider.isLoading) {
return const Center(child: CircularProgressIndicator(
color: Colors.blue,));
}
if (provider.errorMessage != null) {
return Center(child: Text(provider.errorMessage!));
}
if (provider.response == null) {
return const Center(child: Text("No details found"));
}
final rewardDetail = provider.response!;
final rewardResponse = provider.response!;
final rewards = rewardResponse.rewardsList; // main list object
final achieved = rewardResponse.achievedAmount ?? "0";
final disbursed = rewardResponse.disbursedAmount ?? "0";
final balance = rewardResponse.balanceAmount ?? "0";
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
backgroundColor: Color(0xFFF6F6F8),
body: Builder(
builder: (context) {
if (provider.isLoading) {
return const Center(child: CircularProgressIndicator(
color: Colors.blue,));
}
if (provider.errorMessage != null) {
return Center(child: Text(provider.errorMessage!));
}
if (provider.response == null) {
return const Center(child: Text("No details found"));
}
final rewardDetail = provider.response!;
final rewardResponse = provider.response!;
final rewards = rewardResponse.rewardsList; // main list object
final achieved = rewardResponse.achievedAmount ?? "0";
final disbursed = rewardResponse.disbursedAmount ?? "0";
final balance = rewardResponse.balanceAmount ?? "0";
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
/// --- Top Summary Cards ---
Stack(
children: [
Container(
height: 110,
width: double.infinity,
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: const Color(0xffd9ffd6),
borderRadius: BorderRadius.circular(18),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"₹${achieved}", // Achieved Amount from response
style: const TextStyle(
fontSize: 20,
color: Color(0xff0D9C00),
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500,
/// --- Top Summary Cards ---
Stack(
children: [
Container(
height: 110,
width: double.infinity,
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: const Color(0xffd9ffd6),
borderRadius: BorderRadius.circular(18),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"₹${achieved}", // Achieved Amount from response
style: const TextStyle(
fontSize: 20,
color: Color(0xff0D9C00),
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500,
),
),
),
const SizedBox(height: 10),
const Text(
"Achievement Amount",
style: TextStyle(
fontSize: 14,
color: Color(0xff2D2D2D),
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w400,
const SizedBox(height: 10),
const Text(
"Achievement Amount",
style: TextStyle(
fontSize: 14,
color: Color(0xff2D2D2D),
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w400,
),
),
),
],
],
),
),
),
// Positioned SVG Icon
Positioned(
bottom: 8,
right: 12,
child: Container(
height: 42,
width: 42,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xA0FFFFFF), // icon bg
),
child: Center(
child: SvgPicture.asset(
height: 25,
width: 25,
"assets/svg/hrm/achievement_ic.svg",
fit: BoxFit.contain,
// Positioned SVG Icon
Positioned(
bottom: 8,
right: 12,
child: Container(
height: 42,
width: 42,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xA0FFFFFF), // icon bg
),
child: Center(
child: SvgPicture.asset(
height: 25,
width: 25,
"assets/svg/hrm/achievement_ic.svg",
fit: BoxFit.contain,
),
),
),
),
),
],
),
const SizedBox(height: 12),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: Container(
height: 110,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xffe8ddff),
borderRadius: BorderRadius.circular(16),
),
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"₹${disbursed}", // Disbursed Amount
style: const TextStyle(
fontSize: 20,
color: Color(0xff493272),
fontWeight: FontWeight.w500,
Row(
children: [
Expanded(
child: Container(
height: 110,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xffe8ddff),
borderRadius: BorderRadius.circular(16),
),
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"₹${disbursed}", // Disbursed Amount
style: const TextStyle(
fontSize: 20,
color: Color(0xff493272),
fontWeight: FontWeight.w500,
),
),
),
const SizedBox(height: 8),
const Text(
"Disbursed \nAmount",
style: TextStyle(
fontSize: 14,
color: Color(0xff2D2D2D),
fontWeight: FontWeight.w400,
const SizedBox(height: 8),
const Text(
"Disbursed \nAmount",
style: TextStyle(
fontSize: 14,
color: Color(0xff2D2D2D),
fontWeight: FontWeight.w400,
),
),
),
],
),
Positioned(
bottom: 2,
right: 2,
child: Container(
height: 42,
width: 42,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xA0FFFFFF), // icon bg
),
child: Center(
child: SvgPicture.asset(
height: 25,
width: 25,
"assets/svg/hrm/location_ic.svg",
fit: BoxFit.contain,
],
),
Positioned(
bottom: 2,
right: 2,
child: Container(
height: 42,
width: 42,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xA0FFFFFF), // icon bg
),
child: Center(
child: SvgPicture.asset(
height: 25,
width: 25,
"assets/svg/hrm/location_ic.svg",
fit: BoxFit.contain,
),
),
),
),
),
],
],
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: Container(
height: 110,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xfffffbc3),
borderRadius: BorderRadius.circular(16),
),
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"₹${balance}", // Balance Amount
style: const TextStyle(
fontSize: 18,
color: Color(0xff605C00),
const SizedBox(width: 12),
Expanded(
child: Container(
height: 110,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xfffffbc3),
borderRadius: BorderRadius.circular(16),
),
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"₹${balance}", // Balance Amount
style: const TextStyle(
fontSize: 18,
color: Color(0xff605C00),
),
),
),
const SizedBox(height: 8),
const Text(
"Balance \nAmount",
style: TextStyle(
fontSize: 14,
color: Color(0xff2D2D2D),
fontWeight: FontWeight.w400,
const SizedBox(height: 8),
const Text(
"Balance \nAmount",
style: TextStyle(
fontSize: 14,
color: Color(0xff2D2D2D),
fontWeight: FontWeight.w400,
),
),
),
],
),
Positioned(
bottom: 2,
right: 2,
child: Container(
height: 42,
width: 42,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xA0FFFFFF), // icon bg
),
child: Center(
child: SvgPicture.asset(
height: 25,
width: 25,
"assets/svg/hrm/ballance_ic.svg",
fit: BoxFit.contain,
],
),
Positioned(
bottom: 2,
right: 2,
child: Container(
height: 42,
width: 42,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xA0FFFFFF), // icon bg
),
child: Center(
child: SvgPicture.asset(
height: 25,
width: 25,
"assets/svg/hrm/ballance_ic.svg",
fit: BoxFit.contain,
),
),
),
),
),
],
],
),
),
),
),
],
),
],
),
const SizedBox(height: 20),
const SizedBox(height: 20),
/// --- Reward List Card ---
if (rewards != null)
_rewardListCard(
title: rewards.description ?? "-", // rewardsList fields
dateTime: rewards.dateTime ?? "-",
achieved: achieved,
disbursed: disbursed,
balance: balance,
enteredBy: rewards.enteredBy ?? "-",
)
else
const Text("No rewards available"),
],
),
);
}
),
);
}
)
/// --- Reward List Card ---
if (rewards != null)
_rewardListCard(
title: rewards.description ?? "-", // rewardsList fields
dateTime: rewards.dateTime ?? "-",
achieved: achieved,
disbursed: disbursed,
balance: balance,
enteredBy: rewards.enteredBy ?? "-",
)
else
const Text("No rewards available"),
],
),
);
}
),
);
}
)
),
);
}
......@@ -315,13 +318,13 @@ class _RewardListScreenState extends State<RewardListScreen> {
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 6,
offset: const Offset(0, 3),
)
],
// boxShadow: [
// BoxShadow(
// color: Colors.grey.withOpacity(0.1),
// blurRadius: 6,
// offset: const Offset(0, 3),
// )
// ],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
......
......@@ -19,299 +19,316 @@ class TourExpensesDetailsScreen extends StatefulWidget {
class _TourExpensesDetailsScreenState extends State<TourExpensesDetailsScreen>{
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => TourExpensesDetailsProvider()
..fetchTourExpensesDetails(context, widget.tourBillId),
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Color(0xFFFFFFFF),
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25,
return SafeArea(
top: false,
child: ChangeNotifierProvider(
create: (_) => TourExpensesDetailsProvider()
..fetchTourExpensesDetails(context, widget.tourBillId),
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Color(0xFFFFFFFF),
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25,
),
),
),
SizedBox(width: 10),
InkResponse(
onTap: () => Navigator.pop(context, true),
child: Text(
"Tour Expenses",
style: TextStyle(
fontSize: 18,
height: 1.1,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
SizedBox(width: 10),
InkResponse(
onTap: () => Navigator.pop(context, true),
child: Text(
"Tour Expenses",
style: TextStyle(
fontSize: 18,
height: 1.1,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
),
),
),
),
],
],
),
),
),
backgroundColor: AppColors.scaffold_bg_color,
body: Consumer<TourExpensesDetailsProvider>(
builder: (context, provider, child) {
if (provider.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (provider.errorMessage != null) {
return Center(child: Text(provider.errorMessage!));
}
final response = provider.response;
if (response == null) {
return const Center(child: Text("No data available"));
}
debugPrint("==================requestDetails: ${response.requestDetails?.approvalStatus}");
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
backgroundColor: AppColors.scaffold_bg_color,
body: Consumer<TourExpensesDetailsProvider>(
builder: (context, provider, child) {
if (provider.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (provider.errorMessage != null) {
return Center(child: Text(provider.errorMessage!));
}
final response = provider.response;
if (response == null) {
return const Center(child: Text("No data available"));
}
debugPrint("==================requestDetails: ${response.requestDetails?.approvalStatus}");
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Header Card at the very top
_expenseHeaderCard(
title: response.requestDetails?.placeOfVisit ?? "Tour",
date: response.tourExpenses?.fromDate ?? "-",
status: (response.requestDetails?.approvalStatus?.isNotEmpty ?? false)
? response.requestDetails!.approvalStatus!
: "No Status",
details: [
{"key": "TL Pending Approval Amount", "value": "-"},
{"key": "Total Approved Amount", "value": response.tourExpenses?.appliedAmount ?? "-"},
{"key": "Total Balance Amount", "value": "-"},
{"key": "HR Expiring Amount (Within 24Hrs)", "value": "-"},
{"key": "HR Pending Approval Amount", "value": "-"},
{"key": "Total Disbursed Amount", "value": "-"},
],
),
const SizedBox(height: 16),
/// Tour Expense Card (Main Summary)
if (response.requestDetails != null && response.tourExpenses != null) ...[
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.only(left: 30.0),
child: Text("Tour Summary",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: AppColors.grey_thick,
),
),
/// Header Card at the very top
_expenseHeaderCard(
title: response.requestDetails?.placeOfVisit ?? "Tour",
date: response.tourExpenses?.fromDate ?? "-",
status: (response.requestDetails?.approvalStatus?.isNotEmpty ?? false)
? response.requestDetails!.approvalStatus!
: "No Status",
details: [
{"key": "TL Pending Approval Amount", "value": "-"},
{"key": "Total Approved Amount", "value": response.tourExpenses?.appliedAmount ?? "-"},
{"key": "Total Balance Amount", "value": "-"},
{"key": "HR Expiring Amount (Within 24Hrs)", "value": "-"},
{"key": "HR Pending Approval Amount", "value": "-"},
{"key": "Total Disbursed Amount", "value": "-"},
],
),
const SizedBox(height: 8),
SizedBox(
height: 220, // adjust height to match your card
child: ListView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric( // horizontal margin for centering
horizontal: MediaQuery.of(context).size.width * 0.05,
),
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.85,
child: _tourExpenseCard(
employeeName: response.requestDetails?.employeeName ?? "-",
placeOfVisit: response.requestDetails?.placeOfVisit ?? "-",
daAmount: response.tourExpenses?.da ?? "0",
totalAmount: response.tourExpenses?.appliedAmount ?? "0",
fromDate: response.tourExpenses?.fromDate ?? "-",
toDate: response.tourExpenses?.toDate ?? "-",
remarks: response.tourExpenses?.extraNote ?? "-",
),
const SizedBox(height: 16),
/// Tour Expense Card (Main Summary)
if (response.requestDetails != null && response.tourExpenses != null) ...[
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.only(left: 30.0),
child: Text("Tour Summary",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: AppColors.grey_thick,
),
],
),
),
],
const SizedBox(height: 10),
/// Travel Expenses Cards
if (response.travelExpenses != null &&
response.travelExpenses!.isNotEmpty) ...[
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.only(left: 30.0),
child: Text(
"Travel Expenses",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: AppColors.grey_thick,
),
),
),
const SizedBox(height: 8),
SizedBox(
height: 216,
child: ListView.separated(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * 0.04,
),
itemCount: response.travelExpenses!.length,
separatorBuilder: (_, __) => const SizedBox(width: 12),
itemBuilder: (context, index) {
final t = response.travelExpenses![index];
return SizedBox(
width: MediaQuery.of(context).size.width * 0.90, // card width
child: _travelExpenseCard(
travelType: t.travelType ?? "-",
amount: t.fare ?? "0",
from: t.froma ?? "-",
to: t.toa ?? "-",
onViewTap: () {
debugPrint("Open: ${t.imageDirFilePath}");
//Fileviewer(fileName: "", fileUrl: t.imageDirFilePath.toString())
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) => Image.network(t.imageDirFilePath.toString()),
// Fileviewer(fileName: label, fileUrl: "assets/images/capa.svg"),
),
);
},
const SizedBox(height: 8),
SizedBox(
height: 220, // adjust height to match your card
child: ListView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric( // horizontal margin for centering
horizontal: MediaQuery.of(context).size.width * 0.05,
),
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.85,
child: _tourExpenseCard(
employeeName: response.requestDetails?.employeeName ?? "-",
placeOfVisit: response.requestDetails?.placeOfVisit ?? "-",
daAmount: response.tourExpenses?.da ?? "0",
totalAmount: response.tourExpenses?.appliedAmount ?? "0",
fromDate: response.tourExpenses?.fromDate ?? "-",
toDate: response.tourExpenses?.toDate ?? "-",
remarks: response.tourExpenses?.extraNote ?? "-",
),
),
);
},
],
),
),
)
],
const SizedBox(height: 10),
],
/// Hotel Expenses Cards
if (response.hotelExpenses != null &&
response.hotelExpenses!.isNotEmpty) ...[
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.only(left: 30.0),
child: Text("Hotel Expenses",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: AppColors.grey_thick,
/// Travel Expenses Cards
if (response.travelExpenses != null &&
response.travelExpenses!.isNotEmpty) ...[
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.only(left: 30.0),
child: Text(
"Travel Expenses",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: AppColors.grey_thick,
),
),
),
),
const SizedBox(height: 8),
SizedBox(
height: 216,
child: ListView.separated(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * 0.04,
),
itemCount: response.hotelExpenses!.length,
separatorBuilder: (_, __) => const SizedBox(width: 12),
itemBuilder: (context, index) {
final h = response.hotelExpenses![index];
return SizedBox(
width: MediaQuery.of(context).size.width * 0.90,
child: _hotelExpenseCard(
hotelName: h.hotelName ?? "-",
amount: h.amount ?? "0",
fromDate: h.fromDate ?? "-",
toDate: h.toDate ?? "-",
onViewTap: () {
debugPrint("Open: ${h.imageDirFilePath}");
showDialog(
context: context,
builder: (_) => Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
const SizedBox(height: 8),
SizedBox(
height: 216,
child: ListView.separated(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * 0.04,
),
itemCount: response.travelExpenses!.length,
separatorBuilder: (_, __) => const SizedBox(width: 12),
itemBuilder: (context, index) {
final t = response.travelExpenses![index];
return SizedBox(
width: MediaQuery.of(context).size.width * 0.90, // card width
child: _travelExpenseCard(
travelType: t.travelType ?? "-",
amount: t.fare ?? "0",
from: t.froma ?? "-",
to: t.toa ?? "-",
onViewTap: () {
debugPrint("Open: ${t.imageDirFilePath}");
//Fileviewer(fileName: "", fileUrl: t.imageDirFilePath.toString())
Navigator.push(
context,
MaterialPageRoute(
builder:
(
context,
) => Fileviewer(
fileName:
t.imageDirFilePath ??
"",
fileUrl:
t.imageDirFilePath ??
"",
),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(h.imageDirFilePath.toString())
);
},
),
);
},
),
)
],
const SizedBox(height: 10),
/// Hotel Expenses Cards
if (response.hotelExpenses != null &&
response.hotelExpenses!.isNotEmpty) ...[
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.only(left: 30.0),
child: Text("Hotel Expenses",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: AppColors.grey_thick,
),
),
),
const SizedBox(height: 8),
SizedBox(
height: 216,
child: ListView.separated(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * 0.04,
),
itemCount: response.hotelExpenses!.length,
separatorBuilder: (_, __) => const SizedBox(width: 12),
itemBuilder: (context, index) {
final h = response.hotelExpenses![index];
return SizedBox(
width: MediaQuery.of(context).size.width * 0.90,
child: _hotelExpenseCard(
hotelName: h.hotelName ?? "-",
amount: h.amount ?? "0",
fromDate: h.fromDate ?? "-",
toDate: h.toDate ?? "-",
onViewTap: () {
debugPrint("Open: ${h.imageDirFilePath}");
Navigator.push(
context,
MaterialPageRoute(
builder:
(
context,
) => Fileviewer(
fileName:
h.imageDirFilePath ??
"",
fileUrl:
h.imageDirFilePath ??
"",
),
),
),
);
},
),
);
},
);
},
),
);
},
),
),
),
],
const SizedBox(height: 10),
/// Other Expenses Cards
if (response.otherExpenses != null &&
response.otherExpenses!.isNotEmpty) ...[
],
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.only(left: 30.0),
child: Text("Other Expenses",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: AppColors.grey_thick,
/// Other Expenses Cards
if (response.otherExpenses != null &&
response.otherExpenses!.isNotEmpty) ...[
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.only(left: 30.0),
child: Text("Other Expenses",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: AppColors.grey_thick,
),
),
),
),
const SizedBox(height: 8),
SizedBox(
height: 216,
child: ListView.separated(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * 0.04,
),
itemCount: response.otherExpenses!.length,
separatorBuilder: (_, __) => const SizedBox(width: 12),
itemBuilder: (context, index) {
final o = response.otherExpenses![index];
return SizedBox(
width: MediaQuery.of(context).size.width * 0.90,
child: _otherExpenseCard(
description: o.otherDesc ?? "-",
amount: o.otherAmount ?? "0",
date: o.otherDate ?? "-",
onViewTap: () {
debugPrint("Open: ${o.imageDirFilePath}");
showDialog(
context: context,
builder: (_) => Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(o.imageDirFilePath.toString())
const SizedBox(height: 8),
SizedBox(
height: 216,
child: ListView.separated(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * 0.04,
),
itemCount: response.otherExpenses!.length,
separatorBuilder: (_, __) => const SizedBox(width: 12),
itemBuilder: (context, index) {
final o = response.otherExpenses![index];
return SizedBox(
width: MediaQuery.of(context).size.width * 0.90,
child: _otherExpenseCard(
description: o.otherDesc ?? "-",
amount: o.otherAmount ?? "0",
date: o.otherDate ?? "-",
onViewTap: () {
debugPrint("Open: ${o.imageDirFilePath}");
Navigator.push(
context,
MaterialPageRoute(
builder:
(
context,
) => Fileviewer(
fileName:
o.imageDirFilePath ??
"",
fileUrl:
o.imageDirFilePath ??
"",
),
),
),
);
},
),
);
},
);
},
),
);
},
),
),
),
],
],
const SizedBox(height: 25),
],
),
);
},
const SizedBox(height: 25),
],
),
);
},
),
),
),
);
......@@ -334,13 +351,13 @@ class _TourExpensesDetailsScreenState extends State<TourExpensesDetailsScreen>{
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 6,
offset: const Offset(0, 3),
)
],
// boxShadow: [
// BoxShadow(
// color: Colors.grey.withOpacity(0.1),
// blurRadius: 6,
// offset: const Offset(0, 3),
// )
// ],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
......@@ -407,13 +424,13 @@ class _TourExpensesDetailsScreenState extends State<TourExpensesDetailsScreen>{
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 6,
offset: const Offset(0, 3),
)
],
// boxShadow: [
// BoxShadow(
// color: Colors.grey.withOpacity(0.1),
// blurRadius: 6,
// offset: const Offset(0, 3),
// )
// ],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
......@@ -477,13 +494,13 @@ class _TourExpensesDetailsScreenState extends State<TourExpensesDetailsScreen>{
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 6,
offset: const Offset(0, 3),
)
],
// boxShadow: [
// BoxShadow(
// color: Colors.grey.withOpacity(0.1),
// blurRadius: 6,
// offset: const Offset(0, 3),
// )
// ],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
......@@ -548,13 +565,13 @@ class _TourExpensesDetailsScreenState extends State<TourExpensesDetailsScreen>{
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 6,
offset: const Offset(0, 3),
)
],
// boxShadow: [
// BoxShadow(
// color: Colors.grey.withOpacity(0.1),
// blurRadius: 6,
// offset: const Offset(0, 3),
// )
// ],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
......@@ -681,7 +698,7 @@ class _TourExpensesDetailsScreenState extends State<TourExpensesDetailsScreen>{
bottomRight: Radius.circular(30),
),
),
elevation: 2,
elevation: 0,
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
......
......@@ -168,38 +168,36 @@ class _TourExpensesListScreenState extends State<TourExpensesListScreen> {
],
),
floatingActionButtonLocation:
FloatingActionButtonLocation.centerFloat,
floatingActionButton: InkResponse(
onTap: () {
HapticFeedback.selectionClick();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const AddBillScreen(pageTitleName: "Add Bill",),
settings: const RouteSettings(
name: 'AddTourExpBillScreen'),
bottomNavigationBar: Container(
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 10),
color: Colors.white,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xff1487c9), // App blue
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
).then((_) {
provider.fetchTourExpenses(context, "1");
});
// show add bill screen here
},
child: Container(
height: 45,
alignment: Alignment.center,
margin: EdgeInsets.symmetric(horizontal: 20),
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: AppColors.app_blue,
borderRadius: BorderRadius.circular(15),
padding: const EdgeInsets.symmetric(vertical: 14),
elevation: 0, // Optional: remove shadow
),
child: Text(
onPressed: () {
HapticFeedback.selectionClick();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const AddBillScreen(pageTitleName: "Add Bill"),
settings: const RouteSettings(name: 'AddTourExpBillScreen'),
),
).then((_) {
provider.fetchTourExpenses(context, "1");
});
},
child: const Text(
"Add Bill",
style: TextStyle(
fontSize: 15,
fontSize: 16,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
......
......@@ -5048,7 +5048,7 @@ class ApiCalling {
}
}
static Future<CommonResponse?> attendanceRequestRejectAPI(
static Future<CommonResponse?> attendanceRequestApproveRejectAPI(
session,
empId,
mode,
......@@ -5067,7 +5067,7 @@ class ApiCalling {
};
final res = await post(data, AttendanceRequestRejectUrl, {});
if (res != null) {
print(data);
print("Attendance App Reje:${data}");
debugPrint(res.body);
return CommonResponse.fromJson(jsonDecode(res.body));
} else {
......@@ -5368,6 +5368,7 @@ class ApiCalling {
'requested_date_from': (dateFrom),
'requested_date_to': (dateTo),
'mode': (mode),
};
final res = await post(data, LeaveApplicationListUrl, {});
if (res != null) {
......@@ -5463,7 +5464,7 @@ class ApiCalling {
'remarks': (remarks).toString(),
'id': (id).toString(),
};
final res = await post(data, AttendanceRequestRejectUrl, {});
final res = await post(data, LeaveRequestRejectAprroveUrl, {});
if (res != null) {
print(data);
debugPrint(res.body);
......
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