......@@ -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,7 +222,9 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
return SafeArea(
top: false,
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
automaticallyImplyLeading: false,
......@@ -404,6 +430,7 @@ class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
],
),
),
),
);
}
......
......@@ -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,7 +237,19 @@ class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> {
checkOutDescError,
checkOutProofError
].any((e) => e != null)) {
setState(() {});
setState(() {}); // refresh UI to show error messages
return;
}
// --- Format date for server ---
String formattedDate = "";
try {
final parsedDate = DateFormat("dd MMM yyyy").parse(provider.dateController.text);
formattedDate = DateFormat("yyyy-MM-dd").format(parsedDate);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Error formatting date: $e")),
);
return;
}
......@@ -268,7 +292,7 @@ class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> {
: selectedType == "Check Out"
? checkOutLocation.text
: "${checkInLocation.text}, ${checkOutLocation.text}",
checkDate: provider.dateController.text,
checkDate: formattedDate,
checkInTime: finalCheckInTime,
checkInLoc: finalCheckInLoc,
checkInProof: finalCheckInProof,
......@@ -278,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();
......@@ -301,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
......
......@@ -12,8 +12,9 @@ import '../finance/FileViewer.dart';
/// screen for attendance details
class AttendanceRequestDetailScreen extends StatefulWidget {
final String mode;
final attendanceListId;
const AttendanceRequestDetailScreen({super.key, required this.attendanceListId});
const AttendanceRequestDetailScreen({super.key, required this.attendanceListId, required this.mode});
@override
State<AttendanceRequestDetailScreen> createState() =>
......@@ -22,11 +23,15 @@ class AttendanceRequestDetailScreen extends StatefulWidget {
class _AttendanceRequestDetailScreenState
extends State<AttendanceRequestDetailScreen> {
bool _actionSubmitted = false;
late AttendanceDetailsProvider provider;
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
return SafeArea(
top: false,
child: ChangeNotifierProvider(
create: (_) =>
AttendanceDetailsProvider()..fetchAttendanceRequestDetail(context, widget.attendanceListId),
child: Consumer<AttendanceDetailsProvider>(
......@@ -77,9 +82,9 @@ class _AttendanceRequestDetailScreenState
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?.requestDetails == null) {
return const Center(child: Text("No details found"));
}
......@@ -95,7 +100,7 @@ class _AttendanceRequestDetailScreenState
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16 * scaleFactor),
),
elevation: 2,
elevation: 0,
child: Padding(
padding: EdgeInsets.all(16.0 * scaleFactor),
child: Column(
......@@ -103,7 +108,7 @@ class _AttendanceRequestDetailScreenState
children: [
Container(
margin: EdgeInsets.only(bottom: 0.5 * scaleFactor),
padding: EdgeInsets.all(12 * scaleFactor),
padding: EdgeInsets.symmetric(horizontal: 2.5 * scaleFactor, vertical: 12 * scaleFactor),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12 * scaleFactor),
......@@ -112,16 +117,16 @@ class _AttendanceRequestDetailScreenState
children: [
/// Left Avatar
Container(
height: 48 * scaleFactor,
width: 48 * scaleFactor,
height: 44 * scaleFactor,
width: 44 * scaleFactor,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFFEDF8FF), // icon bg
),
child: Center(
child: SvgPicture.asset(
height: 28 * scaleFactor,
width: 28 * scaleFactor,
height: 24 * scaleFactor,
width: 24 * scaleFactor,
"assets/svg/hrm/attendanceList.svg",
fit: BoxFit.contain,
),
......@@ -238,12 +243,307 @@ class _AttendanceRequestDetailScreenState
);
},
),
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),
],
),
),
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"),
],
),
),
),
),
/// 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.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"),
],
),
),
),
),
],
),
SizedBox(height: 0,)
],
),
)
: const SizedBox.shrink(),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
},
)
),
);
}
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",
),
),
),
),
),
),
],
),
],
),
),
),
);
},
);
},
);
}
/// Reusable Row Widget for details
Widget _buildDetailTile(String label, String? value, double scaleFactor) {
return Padding(
......@@ -285,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(
......@@ -311,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(
......@@ -341,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(
......@@ -450,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';
......@@ -13,14 +16,15 @@ import '../CommonFilter2.dart';
import '../commonDateRangeFilter.dart';
import 'AddLiveAttendance.dart';
class Attendancelist extends StatefulWidget {
const Attendancelist({super.key});
class AttendanceListScreen extends StatefulWidget {
final mode;
const AttendanceListScreen({super.key,required this.mode});
@override
State<Attendancelist> createState() => _AttendancelistState();
State<AttendanceListScreen> createState() => _AttendanceListScreenState();
}
class _AttendancelistState extends State<Attendancelist> {
class _AttendanceListScreenState extends State<AttendanceListScreen> {
// @override
// void initState() {
// super.initState();
......@@ -32,19 +36,25 @@ class _AttendancelistState extends State<Attendancelist> {
@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(
create: (_) {
final provider = Attendancelistprovider();
Future.microtask(() {
provider.fetchAttendanceRequests(context);
provider.fetchAttendanceRequests(context, widget.mode);
});
return provider;
},
builder: (context, child) {
return Consumer<Attendancelistprovider>(
builder: (context, provider, child) {
final requestProvider = Provider.of<AttendanceDetailsProvider>(context, listen: false);
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
......@@ -79,6 +89,7 @@ class _AttendancelistState extends State<Attendancelist> {
final provider = Provider.of<Attendancelistprovider>(context, listen: false);
provider.updateFiltersFromSheet(
widget.mode,
context,
type: result['type'] ?? "All",
selectedValue: result['selectedValue'] ?? "This Month",
......@@ -132,9 +143,9 @@ class _AttendancelistState extends State<Attendancelist> {
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(
......@@ -152,15 +163,101 @@ class _AttendancelistState extends State<Attendancelist> {
itemBuilder: (context, index) {
final item = list[index];
return InkWell(
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',
),
],
)
: null,
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () {
/// navigation flow
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AttendanceRequestDetailScreen(
attendanceListId: item.id,
mode: widget.mode,
),
),
);
......@@ -202,7 +299,9 @@ class _AttendancelistState extends State<Attendancelist> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.type ?? "-",
widget.mode == "apr_lvl1"
? _truncate(item.employeeName ?? "-", 20)
: item.type ?? "-",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
......@@ -211,8 +310,21 @@ class _AttendancelistState extends State<Attendancelist> {
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 ?? "-",
" - ${item.date}" ?? "-",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
......@@ -221,6 +333,8 @@ class _AttendancelistState extends State<Attendancelist> {
),
],
),
],
),
),
/// Right Status (Live / Manual)
......@@ -238,18 +352,32 @@ class _AttendancelistState extends State<Attendancelist> {
],
),
),
),
);
},
);
},
),
)
],
),
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,
......@@ -266,7 +394,7 @@ class _AttendancelistState extends State<Attendancelist> {
),
),
).then((_) {
provider.fetchAttendanceRequests(context);
provider.fetchAttendanceRequests(context, widget.mode);
});
},
child: Row(
......@@ -294,7 +422,7 @@ class _AttendancelistState extends State<Attendancelist> {
name: 'AddManualAttendanceScreen'),
),
).then((_) {
provider.fetchAttendanceRequests(context);
provider.fetchAttendanceRequests(context, widget.mode);
});
},
child: Row(
......@@ -311,6 +439,7 @@ class _AttendancelistState extends State<Attendancelist> {
],
),
),
);
},
......@@ -320,6 +449,190 @@ class _AttendancelistState extends State<Attendancelist> {
);
}
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) {
......
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:generp/screens/hrm/OrganizationStructureScreen.dart';
import 'package:generp/screens/hrm/RewardListScreen.dart';
import 'package:generp/screens/hrm/Attendancelist.dart';
import 'package:provider/provider.dart';
import '../../Utils/app_colors.dart';
import 'AttendanceRequestDetail.dart';
import 'LeaveApplicationScreen.dart';
import 'TourExpensesListScreen.dart';
import 'attendancelist.dart';
import 'RewardListScreen.dart';
import 'OrganizationStructureScreen.dart';
import '../../Notifiers/hrmProvider/hrmAccessiblePagesProvider.dart';
import 'oggchart.dart';
class HrmdashboardScreen extends StatefulWidget {
......@@ -17,17 +19,32 @@ class HrmdashboardScreen extends StatefulWidget {
}
class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
final allowedPages = [
"Team Leave Request Approval",
"Team Attendance Approval",
"Leave Request List",
"Tour Bill List",
"Rewards List",
"Attendance Request List",
];
@override
void initState() {
super.initState();
Future.microtask(() =>
Provider.of<HrmAccessiblePagesProvider>(context, listen: false)
.fetchAccessiblePages(context));
}
@override
Widget build(BuildContext context) {
return Scaffold(
return SafeArea(
top: false,
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: const Color(0xFFCEEDFF),
// elevation: 2.0,
title: SizedBox(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
title: Row(
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
......@@ -37,28 +54,23 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
),
),
const SizedBox(width: 10),
InkResponse(
onTap: () => Navigator.pop(context, true),
child: Text(
Text(
"HRM",
style: TextStyle(
fontSize: 18,
height: 1.1,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
),
),
),
],
),
),
),
backgroundColor: Color(0xffF6F6F8),
backgroundColor: const Color(0xffF6F6F8),
body: SingleChildScrollView(
child: Column(
children: [
/// Background elements
/// Background
Stack(
children: [
Container(
......@@ -75,7 +87,7 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
colors: [
Color(0xFFCEEDFF),
Color(0xFFf9f9fb),
Color(0xffF6F6F8)
Color(0xffF6F6F8),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
......@@ -93,25 +105,21 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
),
),
/// Content
Column(
children: [
/// Top Section with Gradient
/// Top Illustration & Button
Container(
width: double.infinity,
padding: const EdgeInsets.only(top: 60, bottom: 30),
child: Column(
children: [
/// Illustration
SvgPicture.asset(
"assets/images/capa.svg",
height: 146,
width: 400,
fit: BoxFit.contain,
),
const SizedBox(height: 32),
/// Organization Structure Button
Container(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 8),
......@@ -154,174 +162,144 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
],
),
),
/// Bottom Grid Section
// Bottom Grid Section
/// Grid Section
LayoutBuilder(
builder: (context, constraints) {
final itemWidth = 180.0; // Fixed desired width for each item
final availableWidth = constraints.maxWidth;
final crossAxisCount = (availableWidth / itemWidth).floor().clamp(2, 4);
return Padding(
padding: const EdgeInsets.all(14),
child: GridView.count(
crossAxisCount: crossAxisCount,
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,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
children: [
_buildTile(
label: "Attendance List",
subtitle: "Real-time request",
assetIcon: "assets/svg/hrm/attendanceList.svg",
txtColor: const Color(0xff1487C9),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const Attendancelist(),
),
);
},
),
_buildTile(
label: "Leave Application",
subtitle: "Apply & Track",
assetIcon: "assets/svg/hrm/leaveApplication.svg",
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: () {
Navigator.push(
onTap: () => _handleNavigation(
context,
MaterialPageRoute(
builder: (context) => const LeaveApplicationListScreen(),
page.pageName ?? "",
page.mode ?? "",
),
);
},
),
_buildTile(
label: "Rewards List",
subtitle: "Track earned rewards",
assetIcon: "assets/svg/hrm/rewardList.svg",
txtColor: const Color(0xff1487C9),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const RewardListScreen(),
),
);
},
),
_buildTile(
label: "Tour Expenses",
subtitle: "Submit and manage claims",
assetIcon: "assets/svg/hrm/tourExp.svg",
txtColor: const Color(0xff1487C9),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const TourExpensesListScreen(),
),
);
},
),
],
),
);
},
),
],
),
],
),
],
),
),
);
}
/// Reusable Tile Widget (Row style)
/// Reusable Tile Widget (Row style) - Updated to match design
/// Card builder
Widget _buildTile({
required String label,
required String subtitle,
required String assetIcon, // SVG/PNG asset
required String assetIcon,
required Color txtColor,
VoidCallback? onTap,
}) {
return LayoutBuilder(
builder: (context, constraints) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(14),
child: Container(
padding: EdgeInsets.symmetric(
vertical: 5,
horizontal: 15,
),
margin: EdgeInsets.symmetric(
vertical: 7,
horizontal: 5,
vertical: constraints.maxHeight * 0.05,
horizontal: constraints.maxWidth * 0.05,
),
margin: const EdgeInsets.symmetric(vertical: 7, horizontal: 5),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
/// Left side text
Expanded(
flex: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
Flexible(
child: Text(
label,
style: TextStyle(
fontSize: 14,
color: AppColors.app_blue,
fontFamily: "JakartaMedium",
),
softWrap: true,
overflow: TextOverflow.visible,
),
SizedBox(height: 4),
Text(
),
const SizedBox(height: 4),
Flexible(
child: Text(
subtitle,
style: TextStyle(
fontSize: 12,
color: AppColors.grey_semi,
fontFamily: "JakartaMedium",
),
softWrap: true,
overflow: TextOverflow.visible,
),
),
],
),
),
SizedBox(width: 10),
/// Right side icon (SVG/PNG)
Expanded(
flex: 1,
child: Container(
height: 42,
width: 42,
height: constraints.maxHeight * 0.39,
width: constraints.maxHeight * 0.39,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFFEDF8FF), // icon bg
color: const Color(0xFFEDF8FF),
),
child: Center(
child: SvgPicture.asset(
height: 25,
width: 25,
assetIcon,
fit: BoxFit.contain,
height: constraints.maxHeight * 0.19,
width: constraints.maxHeight * 0.19,
),
),
),
......@@ -330,5 +308,114 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
),
),
);
},
);
}
/// Mapping subtitles
String _getSubtitle(String pageName) {
switch (pageName) {
case "Attendance Request List":
return "Real-time request";
case "Leave Request List":
return "Apply & Track";
case "Rewards List":
return "Track earned rewards";
case "Tour Bill List":
return "Submit and manage claims";
case "Team Leave Request Approval":
return "";
case "Team Attendance Approval":
return "";
default:
return "";
}
}
/// Mapping icons
String _getIcon(String pageName) {
switch (pageName) {
case "Attendance Request List":
return "assets/svg/hrm/attendanceList.svg";
case "Leave Request List":
return "assets/svg/hrm/leaveApplication.svg";
case "Rewards List":
return "assets/svg/hrm/rewardList.svg";
case "Tour Bill List":
return "assets/svg/hrm/tourExp.svg";
case "Team Leave Request Approval":
return "assets/svg/hrm/leaveApplication.svg";
case "Team Attendance Approval":
return "assets/svg/hrm/attendanceList.svg";
default:
return "assets/svg/hrm/groupIc.svg";
}
}
/// Navigation mapping
void _handleNavigation(
BuildContext context,
String pageName,
String mode,
) {
switch (pageName) {
case "Attendance Request List":
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AttendanceListScreen(mode: mode),
),
);
break;
case "Leave Request List":
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LeaveApplicationListScreen(
mode: mode,
),
),
);
break;
case "Rewards List":
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RewardListScreen(),
),
);
break;
case "Tour Bill List":
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TourExpensesListScreen(),
),
);
break;
case "Team Leave Request Approval":
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LeaveApplicationListScreen(
mode: mode,
),
),
);
break;
case "Team Attendance Approval":
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AttendanceListScreen(mode: mode),
),
);
break;
}
}
}
......@@ -12,16 +12,20 @@ import '../finance/FileViewer.dart';
/// Screen for leave application details
class LeaveApplicationDetailScreen extends StatefulWidget {
final String leaveRequestId;
const LeaveApplicationDetailScreen({super.key, required this.leaveRequestId});
final String mode;
const LeaveApplicationDetailScreen({super.key, required this.leaveRequestId, required this.mode});
@override
State<LeaveApplicationDetailScreen> createState() => _LeaveApplicationDetailScreenState();
}
class _LeaveApplicationDetailScreenState extends State<LeaveApplicationDetailScreen> {
bool _actionSubmitted = false;
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
return SafeArea(
top: false,
child: ChangeNotifierProvider(
create: (_) => LeaveApplicationDetailsProvider()..fetchLeaveApplicationDetails(context, widget.leaveRequestId),
child: Consumer<LeaveApplicationDetailsProvider>(
builder: (context, provider, child) {
......@@ -63,58 +67,63 @@ class _LeaveApplicationDetailScreenState extends State<LeaveApplicationDetailScr
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"));
}
// 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: const EdgeInsets.all(16.0),
padding: EdgeInsets.all(16.0 * scaleFactor),
child: Column(
children: [
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
borderRadius: BorderRadius.circular(16 * scaleFactor),
),
elevation: 2,
elevation: 0,
child: Padding(
padding: const EdgeInsets.all(10.0),
padding: EdgeInsets.all(10.0 * scaleFactor),
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),
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),
borderRadius: BorderRadius.circular(12 * scaleFactor),
),
child: Row(
children: [
/// Left Avatar
Container(
height: 48,
width: 48,
height: 48 * scaleFactor,
width: 48 * scaleFactor,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFFEDF8FF), // icon bg
color: const Color(0xFFEDF8FF),
),
child: Center(
child: SvgPicture.asset(
height: 28,
width: 28,
"assets/svg/hrm/leaveApplication.svg", // Use appropriate icon
"assets/svg/hrm/leaveApplication.svg",
height: 28 * scaleFactor,
width: 28 * scaleFactor,
fit: BoxFit.contain,
),
),
),
const SizedBox(width: 12),
SizedBox(width: 12 * scaleFactor),
/// Middle text
Expanded(
......@@ -125,21 +134,20 @@ class _LeaveApplicationDetailScreenState extends State<LeaveApplicationDetailScr
details.leaveType ?? "-",
style: TextStyle(
decoration: TextDecoration.underline,
decorationStyle:
TextDecorationStyle.dotted,
decorationStyle: TextDecorationStyle.dotted,
decorationColor: AppColors.grey_thick,
height: 1.2,
fontFamily: "JakartaRegular",
fontSize: 14,
fontSize: 12 * scaleFactor,
color: AppColors.semi_black,
),
),
const SizedBox(height: 2),
SizedBox(height: 2 * scaleFactor),
Text(
"Applied: ${details.appliedDate ?? "-"}",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
fontSize: 12 * scaleFactor,
color: AppColors.app_blue,
),
),
......@@ -149,19 +157,23 @@ class _LeaveApplicationDetailScreenState extends State<LeaveApplicationDetailScr
/// Right side status badge
Container(
height: 30,
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1),
height: 28 * scaleFactor,
padding: EdgeInsets.symmetric(
horizontal: 5 * scaleFactor,
vertical: 1 * scaleFactor,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(8 * scaleFactor),
color: _getStatusBackgroundColor(details.status),
),
child: Center(
child: Text(
details.status ?? "-",
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 12,
fontSize: 10 * scaleFactor,
color: _getStatusTextColor(details.status),
),
),
......@@ -173,74 +185,402 @@ class _LeaveApplicationDetailScreenState extends State<LeaveApplicationDetailScr
/// Leave Details
Padding(
padding: const EdgeInsets.all(8.0),
padding: EdgeInsets.all(8.0 * scaleFactor),
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),
_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),
_buildDetailTile("Approved By", details.approvedBy),
_buildDetailTile("Approved Date", details.approvedDate),
_buildDetailTile("Approval Remarks", details.approvalRemarks),
_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),
_buildDetailTile("From Time", details.fromTime),
_buildDetailTile("To Time", details.toTime),
_buildSectionHeader("Additional Information", ),
_buildDetailTile("Status", details.status, scaleFactor),
_buildDetailTile("From Time", details.fromTime, scaleFactor),
_buildDetailTile("To Time", details.toTime, scaleFactor),
],
),
),
],
),
),
),
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),
],
),
),
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",
),
),
],
),
),
),
),
/// 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(height: 30),
),
],
),
SizedBox(height: 2,)
],
),
)
: const SizedBox.shrink(),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
},
),
),
);
}
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,
style: TextStyle(
color: Colors.black, // Entered text color
fontSize: 14, // Optional: adjust font size
),
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: 5),
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();
await onSubmit(remark);
Navigator.pop(context);
if (mounted) {
setState(() {
_actionSubmitted = true;
});
}
// Show snackbar
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",
),
),
),
),
),
),
],
),
SizedBox(height: 2,)
],
),
),
),
);
},
);
},
);
}
/// 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,
),
),
],
......@@ -248,32 +588,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,
),
),
],
......@@ -281,38 +629,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,
),
),
],
......@@ -320,6 +676,7 @@ class _LeaveApplicationDetailScreenState extends State<LeaveApplicationDetailScr
);
}
/// Section header with dotted line
Widget _buildSectionHeader(String title) {
return Padding(
......
......@@ -11,7 +11,8 @@ import 'AddLeaveRequestScreen.dart';
import 'LeaveApplicationDetailScreen.dart';
class LeaveApplicationListScreen extends StatefulWidget {
const LeaveApplicationListScreen({super.key});
final mode;
const LeaveApplicationListScreen({super.key, required this.mode});
@override
State<LeaveApplicationListScreen> createState() => _LeaveApplicationListScreenState();
......@@ -30,11 +31,13 @@ class _LeaveApplicationListScreenState extends State<LeaveApplicationListScreen>
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
return SafeArea(
top: false,
child: ChangeNotifierProvider(
create: (_) {
final provider = LeaveApplicationListProvider();
Future.microtask(() {
provider.fetchLeaveApplications(context);
provider.fetchLeaveApplications(context, widget.mode);
});
return provider;
},
......@@ -59,6 +62,7 @@ class _LeaveApplicationListScreenState extends State<LeaveApplicationListScreen>
provider.setDateRangeFilter("Custom", customRange: dateRange);
provider.fetchLeaveApplications(
context,
widget.mode,
dateRange: "Custom",
customRange: dateRange,
);
......@@ -118,7 +122,7 @@ class _LeaveApplicationListScreenState extends State<LeaveApplicationListScreen>
final list = provider.response!.requestList!;
return ListView.builder(
padding: const EdgeInsets.all(8),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
itemCount: list.length,
itemBuilder: (context, index) {
final item = list[index];
......@@ -138,10 +142,11 @@ class _LeaveApplicationListScreenState extends State<LeaveApplicationListScreen>
MaterialPageRoute(
builder: (context) => LeaveApplicationDetailScreen(
leaveRequestId: item.id.toString(),
mode: widget.mode,
),
),
).then((_) {
provider.fetchLeaveApplications(context);
provider.fetchLeaveApplications(context,widget.mode);
});
},
......@@ -149,7 +154,7 @@ class _LeaveApplicationListScreenState extends State<LeaveApplicationListScreen>
margin: const EdgeInsets.symmetric(horizontal: 8.5, vertical: 5),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
decoration: BoxDecoration(
color: Colors.white,
color: Colors.white,//Color(int.parse(item.rowColor!.replaceFirst('#', '0xff'))),
borderRadius: BorderRadius.circular(16),
),
child: Row(
......@@ -182,7 +187,9 @@ class _LeaveApplicationListScreenState extends State<LeaveApplicationListScreen>
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.leaveType ?? "-",
widget.mode == "teamleader"
? item.employeeName ?? "-"
: item.leaveType ?? "-",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
......@@ -225,22 +232,23 @@ class _LeaveApplicationListScreenState extends State<LeaveApplicationListScreen>
),
),
// /// 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,
),
),
),
],
),
),
......@@ -249,13 +257,17 @@ class _LeaveApplicationListScreenState extends State<LeaveApplicationListScreen>
);
},
),
)
),
SizedBox(height: 28,)
],
),
floatingActionButtonLocation:
FloatingActionButtonLocation.centerFloat,
floatingActionButton: InkResponse(
bottomNavigationBar: widget.mode == "teamleader"
? null
: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
color: Colors.white,
child: InkResponse(
onTap: () {
HapticFeedback.selectionClick();
Navigator.push(
......@@ -267,7 +279,7 @@ class _LeaveApplicationListScreenState extends State<LeaveApplicationListScreen>
),
),
).then((_) {
provider.fetchLeaveApplications(context);
provider.fetchLeaveApplications(context, widget.mode);
});
// show add bill screen here
......@@ -275,8 +287,8 @@ class _LeaveApplicationListScreenState extends State<LeaveApplicationListScreen>
child: Container(
height: 45,
alignment: Alignment.center,
margin: EdgeInsets.symmetric(horizontal: 20),
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
margin: EdgeInsets.symmetric(horizontal: 14),
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 5),
decoration: BoxDecoration(
color: AppColors.app_blue,
borderRadius: BorderRadius.circular(15),
......@@ -291,15 +303,20 @@ class _LeaveApplicationListScreenState extends State<LeaveApplicationListScreen>
),
),
),
),
);
},
);
},
),
);
}
/// Get status background color
Color _getStatusBackgroundColor(String? status) {
switch (status?.toLowerCase()) {
......@@ -313,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,7 +17,9 @@ class RewardListScreen extends StatefulWidget {
class _RewardListScreenState extends State<RewardListScreen> {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
return SafeArea(
top: false,
child: ChangeNotifierProvider(
create: (_) =>
RewardListProvider()..fetchRewardList(context),
child: Consumer<RewardListProvider>(
......@@ -296,6 +298,7 @@ class _RewardListScreenState extends State<RewardListScreen> {
);
}
)
),
);
}
......@@ -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,7 +19,9 @@ class TourExpensesDetailsScreen extends StatefulWidget {
class _TourExpensesDetailsScreenState extends State<TourExpensesDetailsScreen>{
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
return SafeArea(
top: false,
child: ChangeNotifierProvider(
create: (_) => TourExpensesDetailsProvider()
..fetchTourExpensesDetails(context, widget.tourBillId),
child: Scaffold(
......@@ -179,8 +181,16 @@ class _TourExpensesDetailsScreenState extends State<TourExpensesDetailsScreen>{
context,
MaterialPageRoute(
builder:
(context) => Image.network(t.imageDirFilePath.toString()),
// Fileviewer(fileName: label, fileUrl: "assets/images/capa.svg"),
(
context,
) => Fileviewer(
fileName:
t.imageDirFilePath ??
"",
fileUrl:
t.imageDirFilePath ??
"",
),
),
);
},
......@@ -228,16 +238,19 @@ class _TourExpensesDetailsScreenState extends State<TourExpensesDetailsScreen>{
toDate: h.toDate ?? "-",
onViewTap: () {
debugPrint("Open: ${h.imageDirFilePath}");
showDialog(
context: context,
builder: (_) => Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(h.imageDirFilePath.toString())
Navigator.push(
context,
MaterialPageRoute(
builder:
(
context,
) => Fileviewer(
fileName:
h.imageDirFilePath ??
"",
fileUrl:
h.imageDirFilePath ??
"",
),
),
);
......@@ -285,16 +298,19 @@ class _TourExpensesDetailsScreenState extends State<TourExpensesDetailsScreen>{
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())
Navigator.push(
context,
MaterialPageRoute(
builder:
(
context,
) => Fileviewer(
fileName:
o.imageDirFilePath ??
"",
fileUrl:
o.imageDirFilePath ??
"",
),
),
);
......@@ -314,6 +330,7 @@ class _TourExpensesDetailsScreenState extends State<TourExpensesDetailsScreen>{
},
),
),
),
);
}
......@@ -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: () {
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),
),
padding: const EdgeInsets.symmetric(vertical: 14),
elevation: 0, // Optional: remove shadow
),
onPressed: () {
HapticFeedback.selectionClick();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const AddBillScreen(pageTitleName: "Add Bill",),
settings: const RouteSettings(
name: 'AddTourExpBillScreen'),
builder: (context) => const AddBillScreen(pageTitleName: "Add Bill"),
settings: const RouteSettings(name: 'AddTourExpBillScreen'),
),
).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),
),
child: Text(
child: const Text(
"Add Bill",
style: TextStyle(
fontSize: 15,
fontSize: 16,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
......
......@@ -57,6 +57,7 @@ export 'package:generp/Notifiers/crmProvider/followUpUpdateProvider.dart';
export 'package:generp/Notifiers/crmProvider/appointmentCalendarProvider.dart';
export 'package:generp/Notifiers/crmProvider/addNewLeadsandProspectsProvider.dart';
export 'package:generp/Notifiers/hrmProvider/hrmAccessiblePagesProvider.dart';
export 'package:generp/Notifiers/hrmProvider/attendanceListProvider.dart';
export 'package:generp/Notifiers/hrmProvider/AttendanceDetailsProvider.dart';
export 'package:generp/Notifiers/hrmProvider/tourExpensesProvider.dart';
......
......@@ -4995,11 +4995,13 @@ class ApiCalling {
type,
from,
to,
mode
) async {
try {
Map<String, String> data = {
'emp_id': (empId).toString(),
'session_id': (session).toString(),
'mode': (mode),
'type': (type),
'from': (from),
'to': (to),
......@@ -5019,6 +5021,7 @@ class ApiCalling {
}
}
static Future<attendanceRequestDetailsResponse?> attendanceRequestDetailAPI(
empId,
session,
......@@ -5045,6 +5048,38 @@ class ApiCalling {
}
}
static Future<CommonResponse?> attendanceRequestApproveRejectAPI(
session,
empId,
mode,
type,
remarks,
id,
) async {
try {
Map<String, String> data = {
'session_id': (session).toString(),
'emp_id': (empId).toString(),
'mode': (mode).toString(),
'type': (type).toString(),
'remarks': (remarks).toString(),
'id': (id).toString(),
};
final res = await post(data, AttendanceRequestRejectUrl, {});
if (res != null) {
print("Attendance App Reje:${data}");
debugPrint(res.body);
return CommonResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint('hello bev=bug $e ');
return null;
}
}
static Future<CommonResponse?> addAttendanceRequestAPI({
required String sessionId,
required String empId,
......@@ -5317,12 +5352,14 @@ class ApiCalling {
// Leave Application api
// Leave Application api
static Future<leaveApplicationLIstResponse?> leaveApplicationListAPI(
session,
empId,
dateFrom,
dateTo
dateTo,
mode
) async {
try {
Map<String, String> data = {
......@@ -5330,6 +5367,8 @@ class ApiCalling {
'emp_id': (empId).toString(),
'requested_date_from': (dateFrom),
'requested_date_to': (dateTo),
'mode': (mode),
};
final res = await post(data, LeaveApplicationListUrl, {});
if (res != null) {
......@@ -5408,6 +5447,38 @@ class ApiCalling {
}
}
static Future<CommonResponse?> leaveRequestRejectApproveAPI(
session,
empId,
mode,
type,
remarks,
id,
) async {
try {
Map<String, String> data = {
'session_id': (session).toString(),
'emp_id': (empId).toString(),
'mode': (mode).toString(),
'type': (type).toString(),
'remarks': (remarks).toString(),
'id': (id).toString(),
};
final res = await post(data, LeaveRequestRejectAprroveUrl, {});
if (res != null) {
print(data);
debugPrint(res.body);
return CommonResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint('hello bev=bug $e ');
return null;
}
}
// static Future<CommonResponse?> TpcIssueListApprovalAPI(
// empId,
......
......@@ -180,13 +180,16 @@ const crmDashboardQuotationsUrl = "${baseUrl_test}crm_dashboard_quotations_list"
const ogcharturl = "${baseUrl_test}organisation_structures";
const JobDesciptionUrl ="${baseUrl_test}job_description";
///HRM
//Attendance
const HrmAccessiblePagesUrl ="${baseUrl_test}hrm_accessible_pages";
const AttendanceRequestListUrl ="${baseUrl_test}attendance_request_list";
const AttendanceRequestDetailsUrl ="${baseUrl_test}attendance_request_details";
const AddAttendanceRequestUrl ="${baseUrl_test}add_attendance_request";
const AttendanceRequestRejectUrl ="${baseUrl_test}attendance_approve_reject";
const AttendanceRequestAproveUrl ="${baseUrl_test}attendance_approve_reject";
// reward list
const RewardListUrl ="${baseUrl_test}hrm_emp_self_rewards";
// Tour Expenses hrm_emp_self_rewards
......@@ -198,6 +201,7 @@ const AddTourExpensesUrl ="${baseUrl_test}add_tour_bill";
const LeaveApplicationListUrl ="${baseUrl_test}leave_request_list";
const LeaveApplicationDetailsUrl ="${baseUrl_test}leave_request_details";
const LeaveRequestAdditionUrl ="${baseUrl_test}add_leave_request";
const LeaveRequestRejectAprroveUrl ="${baseUrl_test}leaves_approve_reject";
......