"lib/git@183.82.99.133:saisrinivas/gen_erp_2025.git" did not exist on "e2ab261aa67c9b68bf4baf5523925918bbbb8036"
Commit 6d1deaf2 authored by Mohit Kumar's avatar Mohit Kumar
Browse files

AttendanceList

RewardList
TourExpenses
Implementation
parent 55280429
......@@ -10,6 +10,7 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:generp/Utils/commonWidgets.dart';
import '../Utils/commonServices.dart';
import 'genTracker/ScanEnterGeneratorIDScreen.dart';
import 'hrm/HrmDashboardScreen.dart';
import 'notifierExports.dart';
import 'screensExports.dart';
import 'package:geolocator/geolocator.dart';
......@@ -124,6 +125,8 @@ class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
double screenHeight = MediaQuery.of(context).size.height;
switch (_source.keys.toList()[0]) {
case ConnectivityResult.mobile:
connection = 'Online';
......@@ -138,10 +141,10 @@ class _MyHomePageState extends State<MyHomePage> {
return (connection == 'Online')
? Consumer2<HomescreenNotifier, ProfileNotifer>(
builder: (context, homescreen, profile, child) {
final coreRequiredRoles = ["12", "540","433", "434", ];
final coreRequiredRoles = ["12", "540","433", "434", "430"];
final requiredRoles = ["430", "430", "431", "431"];
final coreNames = ["CRM", "Orders","Service", "Gen Tracker", ];
final coreNames = ["CRM", "Orders","Service", "Gen Tracker","HRM" ];
final names = ["Attendance", "Finance", "ERP", "Whizzdom"];
final subtitles = [
"Check-in,Check-out",
......@@ -154,6 +157,7 @@ class _MyHomePageState extends State<MyHomePage> {
"assets/svg/home/home_order_ic.svg",
"assets/svg/home/home_service_ic.svg",
"assets/svg/home/home_gentracker_ic.svg",
"assets/svg/home/home_erp_ic.svg",
];
final icons = [
......@@ -167,6 +171,7 @@ class _MyHomePageState extends State<MyHomePage> {
"Orders, TPC, Dispatch",
"Visits, P.C. Wallet",
"Generator Details",
"Tour Bills, Live Attendance",
];
final coreFilteredItems = <Map<String, String>>[];
final filteredItems = <Map<String, String>>[];
......@@ -211,23 +216,23 @@ class _MyHomePageState extends State<MyHomePage> {
toolbarHeight: 0,
backgroundColor: Colors.white,
),
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppColors.scaffold_bg_color,
AppColors.scaffold_bg_color,
Color(0xFFCEEDFF),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
body: SingleChildScrollView(
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppColors.scaffold_bg_color,
AppColors.scaffold_bg_color,
Color(0xFFCEEDFF),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
),
child: Column(
children: [
Expanded(
flex: 4,
child: InkResponse(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
InkResponse(
onTap: () {
HapticFeedback.selectionClick();
_showProfileBottomSheet(
......@@ -275,9 +280,9 @@ class _MyHomePageState extends State<MyHomePage> {
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Text(
// "${profile.employeeName}",
// maxLines: 1,
......@@ -331,7 +336,7 @@ class _MyHomePageState extends State<MyHomePage> {
mainAxisAlignment:
MainAxisAlignment.start,
children: [
Container(
width: 12,
height: 12,
......@@ -353,8 +358,8 @@ class _MyHomePageState extends State<MyHomePage> {
color: Color(0xFF2D2D2D),
),
),
],
),
],
......@@ -396,7 +401,7 @@ class _MyHomePageState extends State<MyHomePage> {
),
),
),
],
),
),
......@@ -406,10 +411,7 @@ class _MyHomePageState extends State<MyHomePage> {
),
),
),
),
Expanded(
flex: 13,
child: SizedBox(
SizedBox(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
......@@ -541,8 +543,8 @@ class _MyHomePageState extends State<MyHomePage> {
// ),
// ),
// ],
if (homescreen.roleStatus.contains(
"432",
)) ...[
......@@ -556,7 +558,7 @@ class _MyHomePageState extends State<MyHomePage> {
left: 10,
bottom: 5,
top: 10
),
child: Text(
"Workforce & Operations",
......@@ -723,7 +725,7 @@ class _MyHomePageState extends State<MyHomePage> {
left: 10,
bottom: 5,
top: 10
),
padding: const EdgeInsets.only(
top: 10,
......@@ -950,7 +952,7 @@ class _MyHomePageState extends State<MyHomePage> {
);
}
},
child: Container(
padding: EdgeInsets.symmetric(
vertical: 5,
......@@ -1027,7 +1029,7 @@ class _MyHomePageState extends State<MyHomePage> {
),
],
],
// if (filteredItems.isNotEmpty) ...[
// Container(
// margin: EdgeInsets.symmetric(
......@@ -1235,7 +1237,7 @@ class _MyHomePageState extends State<MyHomePage> {
// ),
// ),
// ],
if (coreFilteredItems.isNotEmpty) ...[
Container(
margin: EdgeInsets.only(
......@@ -1263,6 +1265,7 @@ class _MyHomePageState extends State<MyHomePage> {
child: GridView.builder(
shrinkWrap: true,
itemCount: coreFilteredItems.length,
physics: NeverScrollableScrollPhysics(),
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
......@@ -1332,6 +1335,19 @@ class _MyHomePageState extends State<MyHomePage> {
),
),
);
case "HRM":
res = await Navigator.push(
context,
MaterialPageRoute(
builder:
(context) =>
HrmdashboardScreen(),
settings: RouteSettings(
name:
'CrmdashboardScreen',
),
),
);
default:
print("111");
break;
......@@ -1342,7 +1358,7 @@ class _MyHomePageState extends State<MyHomePage> {
);
}
},
child: Container(
padding: EdgeInsets.symmetric(
vertical: 5,
......@@ -1406,7 +1422,7 @@ class _MyHomePageState extends State<MyHomePage> {
Expanded(
flex: 1,
child: SvgPicture.asset(
filteredItems[ci]['icon'] ??
coreFilteredItems[ci]['icon'] ??
"-",
),
),
......@@ -1418,241 +1434,241 @@ class _MyHomePageState extends State<MyHomePage> {
),
),
],
],
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 40,
Align(
alignment: Alignment.bottomCenter,
margin: EdgeInsets.only(bottom: 20),
child: Image.asset(
fit: BoxFit.scaleDown,
"assets/images/horizontal_logo.png",
child: Container(
height: 40,
alignment: Alignment.bottomCenter,
margin: EdgeInsets.only(bottom: 20),
child: Image.asset(
fit: BoxFit.scaleDown,
"assets/images/horizontal_logo.png",
),
),
),
),
// Expanded(
// flex: 10,
// child: Container(
// padding: EdgeInsets.only(
// left: 20,
// right: 20,
// top: 0,
// bottom: 10,
// ),
// margin: EdgeInsets.only(top: 10),
// child: GridView.builder(
// itemCount: filteredItems.length,
// gridDelegate:
// SliverGridDelegateWithFixedCrossAxisCount(
// crossAxisCount: 2,
// crossAxisSpacing: 10,
// mainAxisSpacing: 10,
// ),
// itemBuilder: (context, index) {
// final item = filteredItems[index];
// return InkResponse(
// onTap: () async {
// var res;
// switch (item['name']) {
// case "Attendance":
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) =>
// AttendanceScreen(),
// settings: RouteSettings(
// arguments: 'AttendanceScreen',
// ),
// ),
// );
// break;
// case "ERP":
// bool isGpsEnabled =
// await Geolocator.isLocationServiceEnabled();
// if (isGpsEnabled) {
// if (Platform.isAndroid) {
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) => WebErpScreen(
// erp_url:
// homescreen
// .webPageUrl,
// ),
// ),
// );
// } else {
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) => WebERPIOS(
// url:
// homescreen
// .webPageUrl,
// ),
// ),
// );
// }
// } else {
// requestGpsPermission();
// }
//
// break;
// case "Gen Tracker":
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) =>
// Gentrackerdashboard(),
// settings: RouteSettings(
// arguments:
// 'Gentrackerdashboard',
// ),
// ),
// );
// break;
// case "Service Engineer":
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) =>
// Serviceengineerdashboard(),
// ),
// );
// break;
// case "Nearby":
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) =>
// Nearbygenerators(),
// ),
// );
//
// break;
// case "Inventory":
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) =>
// InventoryScreen(),
// ),
// );
// break;
// case "Whizzdom":
// bool isGpsEnabled =
// await Geolocator.isLocationServiceEnabled();
// if (isGpsEnabled) {
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (
// context,
// ) => WebWhizzdomScreen(
// whizzdom_url:
// homescreen
// .whizzdomPageUrl,
// ),
// ),
// );
// } else {
// requestGpsPermission();
// }
// break;
// case "Common":
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) =>
// Commondashboard(),
// ),
// );
// break;
// case "Finance":
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) =>
// Financedashboard(),
// settings: RouteSettings(
// arguments: 'Financedashboard',
// ),
// ),
// );
// break;
// case "Orders":
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) =>
// Ordermoduledashboard(),
// ),
// );
// case "CRM":
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) =>
// CrmdashboardScreen(),
// settings: RouteSettings(
// name: 'CrmdashboardScreen',
// ),
// ),
// );
// default:
// print("111");
// break;
// }
// if (res == true) {
// homescreen.DashboardApiFunction(
// context,
// );
// }
// },
// child: Container(
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(30),
// ),
// child: Column(
// crossAxisAlignment:
// CrossAxisAlignment.center,
// mainAxisAlignment:
// MainAxisAlignment.center,
// children: [
// SvgPicture.asset(
// item['icon']!,
// height: 45,
// ),
// SizedBox(height: 10),
// Text(item['name']!),
// ],
// ),
// ),
// );
// },
// ),
// ),
// ),
],
// Expanded(
// flex: 10,
// child: Container(
// padding: EdgeInsets.only(
// left: 20,
// right: 20,
// top: 0,
// bottom: 10,
// ),
// margin: EdgeInsets.only(top: 10),
// child: GridView.builder(
// itemCount: filteredItems.length,
// gridDelegate:
// SliverGridDelegateWithFixedCrossAxisCount(
// crossAxisCount: 2,
// crossAxisSpacing: 10,
// mainAxisSpacing: 10,
// ),
// itemBuilder: (context, index) {
// final item = filteredItems[index];
// return InkResponse(
// onTap: () async {
// var res;
// switch (item['name']) {
// case "Attendance":
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) =>
// AttendanceScreen(),
// settings: RouteSettings(
// arguments: 'AttendanceScreen',
// ),
// ),
// );
// break;
// case "ERP":
// bool isGpsEnabled =
// await Geolocator.isLocationServiceEnabled();
// if (isGpsEnabled) {
// if (Platform.isAndroid) {
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) => WebErpScreen(
// erp_url:
// homescreen
// .webPageUrl,
// ),
// ),
// );
// } else {
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) => WebERPIOS(
// url:
// homescreen
// .webPageUrl,
// ),
// ),
// );
// }
// } else {
// requestGpsPermission();
// }
//
// break;
// case "Gen Tracker":
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) =>
// Gentrackerdashboard(),
// settings: RouteSettings(
// arguments:
// 'Gentrackerdashboard',
// ),
// ),
// );
// break;
// case "Service Engineer":
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) =>
// Serviceengineerdashboard(),
// ),
// );
// break;
// case "Nearby":
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) =>
// Nearbygenerators(),
// ),
// );
//
// break;
// case "Inventory":
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) =>
// InventoryScreen(),
// ),
// );
// break;
// case "Whizzdom":
// bool isGpsEnabled =
// await Geolocator.isLocationServiceEnabled();
// if (isGpsEnabled) {
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (
// context,
// ) => WebWhizzdomScreen(
// whizzdom_url:
// homescreen
// .whizzdomPageUrl,
// ),
// ),
// );
// } else {
// requestGpsPermission();
// }
// break;
// case "Common":
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) =>
// Commondashboard(),
// ),
// );
// break;
// case "Finance":
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) =>
// Financedashboard(),
// settings: RouteSettings(
// arguments: 'Financedashboard',
// ),
// ),
// );
// break;
// case "Orders":
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) =>
// Ordermoduledashboard(),
// ),
// );
// case "CRM":
// res = await Navigator.push(
// context,
// MaterialPageRoute(
// builder:
// (context) =>
// CrmdashboardScreen(),
// settings: RouteSettings(
// name: 'CrmdashboardScreen',
// ),
// ),
// );
// default:
// print("111");
// break;
// }
// if (res == true) {
// homescreen.DashboardApiFunction(
// context,
// );
// }
// },
// child: Container(
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(30),
// ),
// child: Column(
// crossAxisAlignment:
// CrossAxisAlignment.center,
// mainAxisAlignment:
// MainAxisAlignment.center,
// children: [
// SvgPicture.asset(
// item['icon']!,
// height: 45,
// ),
// SizedBox(height: 10),
// Text(item['name']!),
// ],
// ),
// ),
// );
// },
// ),
// ),
// ),
],
),
),
),
// floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
......
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:geolocator/geolocator.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../Utils/app_colors.dart';
import '../../Utils/dropdownTheme.dart';
class AddLiveAttendanceScreen extends StatefulWidget {
const AddLiveAttendanceScreen({Key? key}) : super(key: key);
@override
State<AddLiveAttendanceScreen> createState() =>
_AddLiveAttendanceScreenState();
}
class _AddLiveAttendanceScreenState extends State<AddLiveAttendanceScreen> {
String? selectedType;
Dropdowntheme ddtheme = Dropdowntheme();
final TextEditingController locationController = TextEditingController();
final TextEditingController descriptionController = TextEditingController();
final List<String> types = ["Check In", "Check Out"];
final ImagePicker picker = ImagePicker();
XFile? proofFile; // store selected proof
// computed labels
String get locationHeading =>
selectedType == null ? "Location" : "$selectedType Location";
String get descriptionHeading =>
selectedType == null ? "Description" : "$selectedType Description";
String get proofButtonText =>
selectedType == null ? "Attach Proof" : "Attach $selectedType Proof";
bool get isSubmitEnabled =>
selectedType != null &&
locationController.text.trim().isNotEmpty &&
proofFile != null; // proof is required
@override
void initState() {
super.initState();
locationController.addListener(() {
setState(() {});
});
}
@override
void dispose() {
locationController.dispose();
descriptionController.dispose();
super.dispose();
}
void _showPicker(BuildContext context) {
showModalBottomSheet(
context: context,
builder: (BuildContext bc) {
return SafeArea(
child: Wrap(
children: <Widget>[
ListTile(
leading: const Icon(Icons.photo_camera),
title: const Text('Camera'),
onTap: () async {
Navigator.of(context).pop();
final XFile? image =
await picker.pickImage(source: ImageSource.camera);
if (image != null) {
setState(() => proofFile = image);
}
},
),
ListTile(
leading: const Icon(Icons.photo_library),
title: const Text('Gallery'),
onTap: () async {
Navigator.of(context).pop();
final XFile? image =
await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
setState(() => proofFile = image);
}
},
),
],
),
);
},
);
}
Future<String> getCurrentLocation() async {
var status = await Permission.location.request();
if (status.isGranted) {
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
);
return "${position.latitude.toStringAsFixed(7)}, ${position.longitude.toStringAsFixed(7)}";
} else {
return "Permission denied";
}
}
/// New: submit function
void submitAttendance() {
print("==== Attendance Submitted ====");
print("Type: $selectedType");
print("Location: ${locationController.text}");
print("Description: ${descriptionController.text}");
print("Proof: ${proofFile?.path ?? 'No file'}");
print("=============================");
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: const Color(0xFEFFFFFF),
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: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
),
),
],
),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Type Dropdown
const Text("Type",
style: TextStyle(
fontSize: 15,
fontFamily: "Plus Jakarta Sans",
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: "Plus Jakarta Sans",
fontWeight: FontWeight.w400),
),
value: selectedType,
items: types
.map((e) =>
DropdownMenuItem<String>(
value: e,
child: Text(
e,
style: TextStyle(
fontSize: 14,
),
overflow: TextOverflow.ellipsis,
)
))
.toList(),
onChanged: (val) => setState(() => selectedType = val),
// buttonStyleData: ddtheme.buttonStyleData,
iconStyleData: ddtheme.iconStyleData,
// menuItemStyleData: ddtheme.menuItemStyleData,
dropdownStyleData: ddtheme.dropdownStyleData,
),
),
),
const SizedBox(height: 16),
/// Location field
Text(locationHeading,
style: const TextStyle(
fontSize: 15,
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500
)),
const SizedBox(height: 6),
TextField(
controller: locationController,
readOnly: true,
onTap: () async {
String loc = await getCurrentLocation();
locationController.text = loc;
},
decoration: _inputDecoration("Tap to get location"),
),
const SizedBox(height: 16),
/// Description
Text(descriptionHeading,
style: const TextStyle(
fontSize: 15,
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500
)
),
const SizedBox(height: 6),
TextField(
controller: descriptionController,
maxLines: 3,
decoration: _inputDecoration("Write Description"),
),
const SizedBox(height: 20),
/// Attach Proof
SizedBox(
width: double.infinity,
child: OutlinedButton(
onPressed: () => _showPicker(context),
style: OutlinedButton.styleFrom(
backgroundColor: Colors.blue.shade50,
side: BorderSide(color: Colors.blue.shade200),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
padding: const EdgeInsets.symmetric(vertical: 14),
),
child: Text(proofButtonText,
style: const TextStyle(
fontSize: 16,
color: Colors.blue,
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500
)),
),
),
/// Show proof preview
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)),
IconButton(
icon: const Icon(Icons.close, color: Colors.red),
onPressed: () => setState(() => proofFile = null),
),
],
)
],
const SizedBox(height: 24),
/// Submit button
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: isSubmitEnabled ? submitAttendance : null,
style: ElevatedButton.styleFrom(
backgroundColor:
isSubmitEnabled ? Colors.blue : Colors.grey.shade400,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)),
),
child: const Text("Submit",
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w500)),
),
),
],
),
),
);
}
InputDecoration _inputDecoration(String hint) {
return InputDecoration(
hintText: hint,
hintStyle: const TextStyle(
fontSize: 14,
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w400,
color: Colors.grey,
),
filled: true,
fillColor: Colors.grey.shade100,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.blue),
),
);
}
}
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:image_picker/image_picker.dart';
import '../../Utils/app_colors.dart';
import '../../Utils/dropdownTheme.dart';
class AddManualAttendanceScreen extends StatefulWidget {
const AddManualAttendanceScreen({super.key});
@override
State<AddManualAttendanceScreen> createState() =>
_AddManualAttendanceScreenState();
}
class _AddManualAttendanceScreenState extends State<AddManualAttendanceScreen> {
final TextEditingController dateController = TextEditingController();
Dropdowntheme ddtheme = Dropdowntheme();
// Separate controllers for each section
final checkInTime = TextEditingController();
final checkInLocation = TextEditingController();
final checkInDescription = TextEditingController();
XFile? checkInProof;
final checkOutTime = TextEditingController();
final checkOutLocation = TextEditingController();
final checkOutDescription = TextEditingController();
XFile? checkOutProof;
final ImagePicker picker = ImagePicker();
String? selectedType;
final List<String> types = ["Check In", "Check Out", "Check In/Out"];
DateTime? _date;
bool get isSubmitEnabled {
if (selectedType == "Check In") {
return checkInLocation.text.trim().isNotEmpty;
} else if (selectedType == "Check Out") {
return checkOutLocation.text.trim().isNotEmpty;
} else if (selectedType == "Check In/Out") {
return checkInLocation.text.trim().isNotEmpty &&
checkOutLocation.text.trim().isNotEmpty;
}
return false;
}
@override
void initState() {
super.initState();
checkInLocation.addListener(() => setState(() {}));
checkOutLocation.addListener(() => setState(() {}));
}
// ===== Print all submitted values =====
void _submitForm() {
print("===== Manual Attendance Submitted =====");
print("Date: ${dateController.text}");
print("Type: $selectedType");
print("Time: ${checkInTime.text}");
print("Location: ${checkInLocation.text}");
print("Description: ${checkInDescription.text}");
print("Proof: ${checkInProof != null ? checkOutProof!.path : 'No file attached'}");
print("=======================================");
print("Date: ${dateController.text}");
print("Type: $selectedType");
print("Time: ${checkOutTime.text}");
print("Location: ${checkOutLocation.text}");
print("Description: ${checkOutDescription.text}");
print("Proof: ${checkInProof != null ? checkOutProof!.path : 'No file attached'}");
}
// ===== Pick File =====
Future<void> _pickFile(bool isCheckIn) async {
final XFile? file = await picker.pickImage(source: ImageSource.gallery);
if (file != null) {
setState(() {
if (isCheckIn) {
checkInProof = file;
} else {
checkOutProof = file;
}
});
}
}
void setDate(DateTime newDate) {
_date = newDate;
dateController.text = "${newDate.day}-${newDate.month}-${newDate.year}";
}
Future<void> _pickTime(TextEditingController controller) async {
final TimeOfDay? picked =
await showTimePicker(context: context, initialTime: TimeOfDay.now());
if (picked != null) {
controller.text = picked.format(context);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: const Color(0xFEFFFFFF),
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 Manual Attendance",
style: TextStyle(
fontSize: 18,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
)),
],
),
),
body: Padding(
padding: const EdgeInsets.all(18),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Date
_buildLabel("Date"),
const SizedBox(height: 6),
TextField(
controller: dateController,
readOnly: true,
onTap: () => setDate(DateTime.now()),
decoration: _inputDecoration("Select Date")
.copyWith(suffixIcon: const Icon(Icons.calendar_today)),
),
const SizedBox(height: 16),
/// Type Dropdown
_buildLabel("Type"),
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"),
value: selectedType,
items: types
.map((e) => DropdownMenuItem<String>(
value: e,
child: Text(e),
))
.toList(),
onChanged: (val) => setState(() => selectedType = val),
// buttonStyleData: ddtheme.buttonStyleData,
iconStyleData: ddtheme.iconStyleData,
// menuItemStyleData: ddtheme.menuItemStyleData,
dropdownStyleData: ddtheme.dropdownStyleData,
),
),
),
const SizedBox(height: 20),
/// Conditional Sections
if (selectedType == "Check In")
_buildSection(
title: "Check In",
timeController: checkInTime,
locationController: checkInLocation,
descriptionController: checkInDescription,
proofFile: checkInProof,
onPickProof: () => _pickFile(true),
),
if (selectedType == "Check Out")
_buildSection(
title: "Check Out",
timeController: checkOutTime,
locationController: checkOutLocation,
descriptionController: checkOutDescription,
proofFile: checkOutProof,
onPickProof: () => _pickFile(false),
),
if (selectedType == "Check In/Out") ...[
_buildSection(
title: "Check In",
timeController: checkInTime,
locationController: checkInLocation,
descriptionController: checkInDescription,
proofFile: checkInProof,
onPickProof: () => _pickFile(true),
),
_buildSection(
title: "Check Out",
timeController: checkOutTime,
locationController: checkOutLocation,
descriptionController: checkOutDescription,
proofFile: checkOutProof,
onPickProof: () => _pickFile(false),
),
],
const SizedBox(height: 24),
/// Submit Button
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
backgroundColor:
isSubmitEnabled ? Colors.blue : Colors.grey.shade400,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)),
),
onPressed: isSubmitEnabled
? () {
_submitForm();
print("Submit pressed for $selectedType");
}
: null,
child: const Text("Submit",
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w500,
)),
),
),
],
),
),
),
);
}
/// Reusable Section
Widget _buildSection({
required String title,
required TextEditingController timeController,
required TextEditingController locationController,
required TextEditingController descriptionController,
required XFile? proofFile,
required VoidCallback onPickProof,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLabel("$title Time"),
const SizedBox(height: 6),
TextField(
controller: timeController,
readOnly: true,
onTap: () => _pickTime(timeController),
decoration: _inputDecoration("Select Time")
.copyWith(suffixIcon: const Icon(Icons.access_time)),
),
const SizedBox(height: 16),
_buildLabel("$title Location"),
const SizedBox(height: 6),
TextField(
controller: locationController,
decoration: _inputDecoration("Enter Location"),
),
const SizedBox(height: 16),
_buildLabel("$title Description"),
const SizedBox(height: 6),
TextField(
controller: descriptionController,
maxLines: 3,
decoration: _inputDecoration("Write Description"),
),
const SizedBox(height: 18),
/// Proof
SizedBox(
width: double.infinity,
child: OutlinedButton(
onPressed: onPickProof,
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
backgroundColor: Colors.blue.shade50,
side: BorderSide(color: Colors.blue.shade200),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
),
child: Text("Attach $title Proof",
style: const TextStyle(
fontSize: 16,
color: Colors.blue,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w500)),
),
),
// Show proof preview
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)),
IconButton(
icon: const Icon(Icons.close, color: Colors.red),
onPressed: () => setState(() => proofFile = null),
),
],
)
],
const SizedBox(height: 24),
],
);
}
Widget _buildLabel(String text) => Text(text,
style: const TextStyle(
fontSize: 15,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w500));
InputDecoration _inputDecoration(String hint) {
return InputDecoration(
hintText: hint,
hintStyle: const TextStyle(
fontSize: 14,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
color: Colors.grey),
filled: true,
fillColor: Colors.grey.shade100,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.blue)),
);
}
}
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
import '../../Utils/app_colors.dart';
import '../../Utils/commonServices.dart';
import '../../Utils/commonWidgets.dart';
import '../../Utils/dropdownTheme.dart';
class AddBillScreen extends StatefulWidget {
final String pageTitleName;
const AddBillScreen({super.key, required this.pageTitleName});
@override
State<AddBillScreen> createState() => _AddBillScreenState();
}
class _AddBillScreenState extends State<AddBillScreen> {
Dropdowntheme ddtheme = Dropdowntheme();
List<FocusNode> focusNodes = List.generate(8, (index) => FocusNode());
Map _source = {ConnectivityResult.mobile: true};
final MyConnectivity _connectivity = MyConnectivity.instance;
TextEditingController placeController = TextEditingController();
TextEditingController dateController = TextEditingController();
TextEditingController noteController = TextEditingController();
String? selectedDAAmount;
String? selectedTourType;
List<Map<String, String>> travelExpenses = [
{"title": "Bike", "amount": "1800", "icon": "assets/svg/ic_bike.svg"},
{"title": "Taxi", "amount": "300", "icon": "assets/svg/ic_taxi.svg"},
];
List<Map<String, String>> hotelExpenses = [
{"title": "Hotel Sharada", "amount": "1800", "icon": "assets/svg/ic_hotel.svg"},
{"title": "Hotel Nikitan", "amount": "1800", "icon": "assets/svg/ic_hotel.svg"},
];
List<Map<String, String>> otherExpenses = [
{"title": "Book", "amount": "1800", "icon": "assets/svg/ic_book.svg"},
];
@override
void initState() {
super.initState();
_connectivity.initialise();
_connectivity.myStream.listen((source) {
setState(() => _source = source);
});
}
@override
void dispose() {
placeController.dispose();
dateController.dispose();
noteController.dispose();
focusNodes.map((e) => e.dispose());
_connectivity.disposeStream();
super.dispose();
}
Future<bool> _onBackPressed(BuildContext context) async {
Navigator.pop(context, true);
return true;
}
@override
Widget build(BuildContext context) {
switch (_source.keys.toList()[0]) {
case ConnectivityResult.mobile:
case ConnectivityResult.wifi:
connection = 'Online';
break;
case ConnectivityResult.none:
default:
connection = 'Offline';
}
return (connection == "Online")
? Platform.isAndroid
? WillPopScope(
onWillPop: () => _onBackPressed(context),
child: SafeArea(
top: false,
bottom: true,
child: _scaffold(context),
),
)
: _scaffold(context)
: NoNetwork(context);
}
Widget _scaffold(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: AppColors.scaffold_bg_color,
appBar: appbarNew(context, widget.pageTitleName, 0xFFFFFFFF),
body: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
margin: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Place of Visit
TextWidget(context, "Place of Visit"),
textFieldNew(context, placeController, "Enter Place"),
/// DA Amount
TextWidget(context, "DA Amount"),
dropDownField(context, "Select DA Amount", ["100", "200", "300"], selectedDAAmount, (val) {
setState(() => selectedDAAmount = val);
}),
/// Tour Type
TextWidget(context, "Tour Type"),
dropDownField(context, "Select Tour", ["Business", "Personal"], selectedTourType, (val) {
setState(() => selectedTourType = val);
}),
/// Tour Date
TextWidget(context, "Tour Date"),
GestureDetector(
onTap: () async {
DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2022),
lastDate: DateTime(2100),
);
if (picked != null) {
dateController.text = DateFormat("dd MMM yyyy").format(picked);
setState(() {});
}
},
child: textFieldNew(context, dateController, "Enter Date", enabled: false),
),
/// Note
TextWidget(context, "Note"),
textFieldNew(context, noteController, "Write Note", maxLines: 3),
const SizedBox(height: 16),
/// Travel Expenses
sectionHeader("Travel Expenses", onAddTap: () {
// TODO: Add Travel Expense
}),
expenseList(travelExpenses),
/// Hotel Expenses
sectionHeader("Hotel Expenses", onAddTap: () {
// TODO: Add Hotel Expense
}),
expenseList(hotelExpenses),
/// Other Expenses
sectionHeader("Other Expenses", onAddTap: () {
// TODO: Add Other Expense
}),
expenseList(otherExpenses),
const SizedBox(height: 80),
],
),
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
bottomNavigationBar: InkResponse(
onTap: () {
// TODO: Submit API Call
},
child: Container(
height: 45,
alignment: Alignment.center,
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 15),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: AppColors.app_blue,
borderRadius: BorderRadius.circular(15),
),
child: const Text(
"Submit",
style: TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
color: Colors.white,
),
),
),
),
);
}
/// --- Custom Widgets Below ---
Widget textFieldNew(BuildContext context, TextEditingController controller, String hint,
{bool enabled = true, int maxLines = 1}) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: AppColors.text_field_color,
borderRadius: BorderRadius.circular(14),
),
child: TextFormField(
controller: controller,
enabled: enabled,
maxLines: maxLines,
decoration: _inputDecoration(hint)
),
);
}
Widget dropDownField(BuildContext context, String hint, List<String> items, String? value, Function(String?) onChanged) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: AppColors.text_field_color,
borderRadius: BorderRadius.circular(14),
),
child: DropdownButtonHideUnderline(
child: DropdownButton2<String>(
isExpanded: true,
hint: Text(hint, style: const TextStyle(fontSize: 14, color: Colors.grey)),
value: value,
items: items.map((e) => DropdownMenuItem(value: e, child: Text(e))).toList(),
onChanged: onChanged,
iconStyleData: ddtheme.iconStyleData,
// menuItemStyleData: ddtheme.menuItemStyleData,
dropdownStyleData: ddtheme.dropdownStyleData,
),
),
);
}
Widget sectionHeader(String title, {VoidCallback? onAddTap}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
const SizedBox(height: 6),
Container(
height: 45,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade400, width: 0.7, style: BorderStyle.solid),
borderRadius: BorderRadius.circular(12),
),
child: InkWell(
onTap: onAddTap,
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(Icons.add, color: Colors.blue),
SizedBox(width: 6),
Text("Add Expenses", style: TextStyle(color: Colors.blue, fontSize: 14)),
],
),
),
),
),
const SizedBox(height: 10),
],
);
}
Widget expenseList(List<Map<String, String>> items) {
return Container(
height: 84,
margin: const EdgeInsets.only(bottom: 12),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: items.length,
itemBuilder: (context, index) {
final exp = items[index];
return Container(
width: 120,
margin: const EdgeInsets.only(right: 10),
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: const Color(0xFFE6F6FF),
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(exp["icon"] ?? "assets/svg/ic_default.svg", height: 22),
const SizedBox(height: 6),
Text(exp["title"] ?? "-", style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500)),
Text("₹${exp["amount"]}", style: const TextStyle(fontSize: 12, color: Colors.blue)),
],
),
);
},
),
);
}
InputDecoration _inputDecoration(String hint) {
return InputDecoration(
hintText: hint,
hintStyle: TextStyle(
fontSize: 14,
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w400,
color: Color(0xFFB4BEC0),
),
filled: true,
fillColor: Colors.grey.shade100,
enabledBorder: InputBorder.none,
disabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
);
}
}
import 'package:dotted_line/dotted_line.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:geocoding/geocoding.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../Notifiers/hrmProvider/attendanceDetailsProvider.dart';
import '../../Notifiers/HomeScreenNotifier.dart';
import '../../Utils/app_colors.dart';
import '../finance/FileViewer.dart';
/// screen for attendance details
class AttendanceRequestDetailScreen extends StatefulWidget {
final attendanceListId;
const AttendanceRequestDetailScreen({super.key, required this.attendanceListId});
@override
State<AttendanceRequestDetailScreen> createState() =>
_AttendanceRequestDetailScreenState();
}
class _AttendanceRequestDetailScreenState
extends State<AttendanceRequestDetailScreen> {
late AttendanceDetailsProvider provider;
// @override
// void initState() {
// super.initState();
//
// /// fetch API after widget is built
// WidgetsBinding.instance.addPostFrameCallback((_) {
// final home = Provider.of<HomescreenNotifier>(context, listen: false);
// provider = Provider.of<AttendanceDetailsProvider>(context, listen: false);
//
// provider.fetchAttendanceRequestDetail(
// context,
// widget.attendanceListId
// );
// });
// }
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) =>
AttendanceDetailsProvider()..fetchAttendanceRequestDetail(context, widget.attendanceListId),
child: Consumer<AttendanceDetailsProvider>(
builder: (context, provider, child) {
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,
),
),
SizedBox(width: 10),
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: const EdgeInsets.all(16.0),
child: Column(
children: [
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(bottom: 0.5),
padding: const EdgeInsets.all(12),
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/attendanceList.svg",
fit: BoxFit.contain,
),
),
),
const SizedBox(width: 12),
/// Middle text
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
details.type ?? "-",
style: const TextStyle(
fontSize: 14,
fontStyle: FontStyle.normal,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
),
),
const SizedBox(height: 2),
Text(
details.date ?? "-",
style: const TextStyle(
fontSize: 12,
fontStyle: FontStyle.normal,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
color: Color(0xff818181),
),
),
],
),
),
/// Right side (Live/Manual)
Container(
height: 30,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
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"),
_buildDetailTile("Employee Name", details.employeeName),
_buildDetailTile("Created Employee", details.createdEmpName),
// Check In/Out
_buildSectionHeader("Check In/Out Details"),
_buildDate_TimeTile("Check In Date & Time", details.date, details.checkInTime),
_buildDate_TimeTile("Check Out Date & Time", details.date, details.checkOutTime),
_buildDetailTile("Original Check In", details.checkInTime),
_buildDetailTile("Original Check Out", "--"),
_buildDetailTile("Original Check In Location", details.checkInLocation),
_buildDetailTile("Original Check Out Location", details.checkOutLocation),
buildLocationTile("Location", details.location),
// Proofs
_buildSectionHeader("Proofs"),
_buildProofLink(context,"Check In Proof", details.checkInProofDirFilePath),
_buildProofLink(context,"Check Out Proof", details.checkOutProofDirFilePath),
// Remarks & Approvals
_buildSectionHeader("Remarks & Approvals"),
_buildDetailTile("Level 1 Approved By", details.level1EmpName),
_buildDetailTile("Level 2 Approved By", details.level2EmpName),
_buildDetailTile("Level 1 Remark", details.level1Remarks),
_buildDetailTile("Level 2 Remark", details.level2Remarks),
///remain data
_buildSectionHeader("Other Details"),
_buildDetailTile("Check In Type", details.checkInType),
_buildDetailTile("Check Out Type", details.chechOutType),
_buildDetailTile("Check Out Time", details.checkOutTime),
// Attendance Info
_buildDetailTile("ID", details.id),
_buildDetailTile("Attendance Type", details.attendanceType),
_buildDetailTile("Note", details.note),
_buildDetailTile("Created Datetime", details.requestedDatetime),
],
),
),
),
SizedBox(height: 30,)
],
),
);
},
),
);
},
)
);
}
/// Reusable Row Widget for details
Widget _buildDetailTile(String label, String? value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 3),
child: Row(
children: [
Expanded(
flex: 6,
child: Text(
label,
style: const TextStyle(
fontSize: 14,
color: Color(0xff2D2D2D),
fontStyle: FontStyle.normal,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
),
),
),
Expanded(
flex: 0,
child: Text(value ?? "-",
style: const TextStyle(
fontSize: 14,
color: Color(0xff818181),
fontStyle: FontStyle.normal,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
)
),
),
],
),
);
}
/// for location
Widget buildLocationTile(String label, String? value) {
return FutureBuilder<String>(
future: getReadableLocation(value),
builder: (context, snapshot) {
final locationText = snapshot.data ?? "-";
return Padding(
padding: const EdgeInsets.symmetric(vertical: 3),
child: Row(
children: [
Expanded(
flex: 6,
child: Text(
label,
style: const TextStyle(
fontSize: 14,
color: Color(0xff2D2D2D),
fontStyle: FontStyle.normal,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
),
),
),
Expanded(
flex: 0,
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);
}
},
child: Text(
locationText,
style: const TextStyle(
fontSize: 14,
color: Colors.blue,
decoration: TextDecoration.underline,
fontStyle: FontStyle.normal,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
),
),
),
),
],
),
);
},
);
}
Future<String> getReadableLocation(String? value) async {
if (value == null) return "-";
try {
List<Location> locations = await locationFromAddress(value);
List<Placemark> placemarks = await placemarkFromCoordinates(
locations[0].latitude,
locations[0].longitude,
);
return placemarks.first.locality ?? value;
} catch (e) {
return value; // fallback to raw coordinates
}
}
/// for date and time
Widget _buildDate_TimeTile(String label, String? date, String? time) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 3),
child: Row(
children: [
Expanded(
flex: 5,
child: Text(
label,
style: const TextStyle(
fontSize: 14,
color: Color(0xff2D2D2D),
fontStyle: FontStyle.normal,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
),
),
),
Expanded(
flex: 0,
child: Row(
children: [
Text('$date, ' ?? "-",
style: const TextStyle(
fontSize: 14,
color: Color(0xff818181),
fontStyle: FontStyle.normal,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
)
),
Text(time ?? "-",
style: const TextStyle(
fontSize: 14,
color: Color(0xff818181),
fontStyle: FontStyle.normal,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
)
),
],
)
),
],
),
);
}
///////////////////////
Widget _buildSectionHeader(String title) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
Text(
title,
style: const TextStyle(
fontStyle: FontStyle.normal,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
const SizedBox(width: 10),
Expanded(
child: DottedLine(
dashLength: 4,
dashGapLength: 2,
lineThickness: 1,
dashColor: Color(0xff888888),
)
),
],
),
);
}
/// Proof section (image/file path)
Widget _buildProofLink(BuildContext context, String label, String? filePath) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
children: [
Expanded(
flex: 5,
child: Text(
label,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
),
),
),
Expanded(
flex: 0,
child: filePath != null
? InkWell(
onTap: () {
showDialog(
context: context,
builder: (_) => Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Fileviewer(fileName: label, fileUrl: filePath,),
),
),
);
},
child: const Text(
"View",
style: TextStyle(
fontSize: 14,
color: Colors.blue,
fontStyle: FontStyle.normal,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
decoration: TextDecoration.underline
),
),
)
: const Text("-"),
),
],
),
);
}
Color getTextColor(value) {
var color = AppColors.approved_text_color;
switch (value) {
case 'Requested':
return AppColors.requested_text_color;
case 'Level 1 Approved':
return AppColors.approved_text_color;
case 'Level 1 Rejected':
return AppColors.rejected_text_color;
case 'Level 2 Approved':
return AppColors.approved_text_color;
case 'Level 2 Rejected':
return AppColors.rejected_text_color;
case 'Processed':
return AppColors.processed_text_color;
case 'Payment Rejected':
return AppColors.rejected_text_color;
}
return color;
}
Color getDecorationColor(value) {
var color = AppColors.approved_bg_color;
switch (value) {
case 'Requested':
return AppColors.requested_bg_color;
case 'Level 1 Approved':
return AppColors.approved_bg_color;
case 'Level 1 Rejected':
return AppColors.rejected_bg_color;
case 'Level 2 Approved':
return AppColors.approved_bg_color;
case 'Level 2 Rejected':
return AppColors.rejected_bg_color;
case 'Processed':
return AppColors.processed_bg_color;
case 'Payment Rejected':
return AppColors.rejected_bg_color;
}
return color;
}
}
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.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/hrmProvider/attendanceListProvider.dart';
import '../../Models/hrmModels/attendanceRequestListResponse.dart';
import '../../Utils/app_colors.dart';
import '../commonDateRangeFilter.dart';
import 'AddLiveAttendance.dart';
import 'package:intl/intl.dart';
Map<String, String> getDateRange(String selectedDate) {
final now = DateTime.now();
final formatter = DateFormat("yyyy-MM-dd");
late DateTime from;
late DateTime to;
switch (selectedDate) {
case "All":
from = now;
to = now;
break;
case "Today":
from = now;
to = now;
break;
case "Yesterday":
from = now.subtract(const Duration(days: 1));
to = now.subtract(const Duration(days: 1));
break;
case "This Month":
from = DateTime(now.year, now.month, 1);
to = DateTime(now.year, now.month + 1, 0);
break;
case "Past 7 days":
from = now.subtract(const Duration(days: 6));
to = now;
break;
case "Last Month":
from = DateTime(now.year, now.month - 1, 1);
to = DateTime(now.year, now.month, 0);
break;
case "Custom":
// For custom, you should open a date picker dialog
from = now; // Placeholder
to = now; // Placeholder
break;
default:
from = now;
to = now;
}
return {
"from": formatter.format(from),
"to": formatter.format(to),
};
}
class Attendancelist extends StatefulWidget {
const Attendancelist({super.key});
@override
State<Attendancelist> createState() => _AttendancelistState();
}
class _AttendancelistState extends State<Attendancelist> {
String selectedType = "All";
String selectedDate = "Today";
final List<String> typeOptions = ["All", "Check In", "Check Out","Check In/Out"];
final List<String> dateOptions = [
"Today",
"Yesterday",
"This Month",
"Past 7 days",
"Last Month",
"Custom",
];
@override
Widget build(BuildContext context) {
final dateRange = getDateRange(selectedDate);
final fromDate = dateRange["from"]!;
final toDate = dateRange["to"]!;
final type = (selectedType == "All") ? "" : selectedType;
return SafeArea(
top: false,
child: ChangeNotifierProvider(
create: (_) =>
Attendancelistprovider()..fetchAttendanceRequests(context, type, "", ""),
child: Consumer<Attendancelistprovider>(
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(
"Attendance List",
style: TextStyle(
fontSize: 18,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
),
),
],
),
actions: [
InkResponse(
onTap: () {
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (context) {
return StatefulBuilder(
builder: (context, setModalState) {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Filter Attendance",
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold,
color: Colors.blue,
fontFamily: "Plus Jakarta Sans"
),
),
const SizedBox(height: 16),
/// Type Dropdown
Text("Type", style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
fontFamily: "Plus Jakarta Sans"
)
),
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",
style: TextStyle(fontSize: 14),
overflow: TextOverflow.ellipsis,
),
value: selectedType,
items: typeOptions
.map((type) => DropdownMenuItem<String>(
value: type,
child: Text(type),
))
.toList(),
onChanged: (val) {
setModalState(() {
selectedType = val!;
});
},
buttonStyleData: const ButtonStyleData(
height: 52,
padding: EdgeInsets.zero,
),
dropdownStyleData: DropdownStyleData(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
),
),
),
),
),
const SizedBox(height: 18),
/// Date Dropdown
const Text("Date", style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
fontFamily: "Plus Jakarta Sans"
)),
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 Date",
style: TextStyle(fontSize: 14),
overflow: TextOverflow.ellipsis,
),
value: selectedDate,
items: dateOptions
.map((date) => DropdownMenuItem<String>(
value: date,
child: Text(date),
))
.toList(),
onChanged: (val) {
setModalState(() {
selectedDate = val!;
});
},
buttonStyleData: const ButtonStyleData(
height: 52,
padding: EdgeInsets.zero,
),
dropdownStyleData: DropdownStyleData(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
),
),
),
),
),
const SizedBox(height: 26),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
onPressed: () {
// on apply
provider.fetchAttendanceRequests(context, selectedType, "", "");
Navigator.pop(context);
},
child: const Text(
"Apply",
style: TextStyle(
fontFamily: "Plus Jakarta Sans",
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: OutlinedButton(
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
side: BorderSide(color: Colors.grey.shade400),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
onPressed: () {
// on cancel
Navigator.pop(context);
},
child: const Text(
"Cancel",
style: TextStyle(
fontFamily: "Plus Jakarta Sans",
color: Colors.black87,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
const SizedBox(height: 22),
],
),
);
},
);
},
);
},
child: SvgPicture.asset(
"assets/svg/filter_ic.svg",
height: 25,
),
),
const SizedBox(width: 20),
],
),
backgroundColor: const Color(0xFFF6F6F8),
body: Column(
children: [
/// Attendance 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 requests found"));
}
final list = provider.response!.requestList!;
return ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: list.length,
itemBuilder: (context, index) {
final item = list[index];
final initials = _generateInitials(item);
return InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () {
/// navigation flow
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AttendanceRequestDetailScreen(
attendanceListId: item.id,
),
),
);
},
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,
),
),
),
),
const SizedBox(width: 10),
/// Middle Section
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.type ?? "-",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 15,
color: Color(0xff2D2D2D),
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w400
),
),
Text(
item.date ?? "-",
style: const TextStyle(
fontSize: 12.5,
color: Color(0xff818181),
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w400
),
),
],
),
),
/// Right Status (Live / Manual)
Text(
item.attendanceType ?? "-",
textAlign: TextAlign.right,
style: TextStyle(
fontFamily: "Plus Jakarta Sans",
fontSize: 14,
fontWeight: FontWeight.w500,
color: (item.attendanceType ?? "").toLowerCase() == "live"
? Colors.green
: Colors.orange,
),
),
],
),
),
);
},
);
},
),
)
],
),
bottomNavigationBar: Container(
alignment: Alignment.bottomCenter,
height: 65,
decoration: const BoxDecoration(color: Colors.white),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: InkResponse(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddLiveAttendanceScreen(),
settings: const RouteSettings(
name: 'AddLiveAttendanceScreen',
),
),
).then((_) {
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset("assets/svg/hrm/live.svg"),
const SizedBox(width: 10),
Text("Live Request",
style: TextStyle(color: AppColors.semi_black)),
],
),
),
),
const SizedBox(width: 10),
SvgPicture.asset("assets/svg/crm/vertical_line_ic.svg"),
const SizedBox(width: 10),
Expanded(
child: InkResponse(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const AddManualAttendanceScreen(),
settings: const RouteSettings(
name: 'AddManualAttendanceScreen'),
),
).then((_) {
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset("assets/svg/hrm/manual.svg"),
const SizedBox(width: 10),
Text("Manual Request",
style: TextStyle(color: AppColors.semi_black)),
],
),
),
),
],
),
),
);
},
),
),
);
}
/// Generate avatar initials like L1A, L1R
String _generateInitials(RequestList item) {
final attType = (item.attendanceType?.isNotEmpty ?? false)
? item.attendanceType![0].toUpperCase()
: "U";
final type = (item.type?.isNotEmpty ?? false)
? item.type![0].toUpperCase()
: "X";
return "$attType$type";
}
/// Avatar color generator
Color _getAvatarColor(value) {
var color = AppColors.approved_bg_color;
switch (value) {
case 'Requested':
return AppColors.requested_bg_color;
case 'Level 1 Approved':
return AppColors.approved_bg_color;
case 'Level 1 Rejected':
return AppColors.rejected_bg_color;
case 'Level 2 Approved':
return AppColors.approved_bg_color;
case 'Level 2 Rejected':
return AppColors.rejected_bg_color;
case 'Updated':
return AppColors.processed_bg_color;
case 'Payment Rejected':
return AppColors.rejected_bg_color;
}
return color;
}
Color _getTextColor(value) {
var color = AppColors.approved_text_color;
switch (value) {
case 'Requested':
return AppColors.requested_text_color;
case 'Level 1 Approved':
return AppColors.approved_text_color;
case 'Level 1 Rejected':
return AppColors.rejected_text_color;
case 'Level 2 Approved':
return AppColors.approved_text_color;
case 'Level 2 Rejected':
return AppColors.rejected_text_color;
case 'Updated':
return AppColors.processed_text_color;
}
return color;
}
getText(value) {
switch (value) {
case 'Requested':
return "R";
case 'Level 1 Approved':
return "L1A";
case 'Level 1 Rejected':
return "L1R";
case 'Level 2 Approved':
return "L2A";
case 'Level 2 Rejected':
return "L2R";
case 'Updated':
return "U";
default:
return "Requested";
}
}
}
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:generp/screens/hrm/RewardListScreen.dart';
import '../../Utils/app_colors.dart';
import 'AttendanceRequestDetail.dart';
import 'LeaveApplicationScreen.dart';
import 'TourExpensesListScreen.dart';
import 'attendancelist.dart';
class HrmdashboardScreen extends StatefulWidget {
const HrmdashboardScreen({super.key});
@override
State<HrmdashboardScreen> createState() => _HrmdashboardScreenState();
}
class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: const Color(0xFFCEEDFF),
// elevation: 2.0,
title: SizedBox(
child: 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(
"HRM",
style: TextStyle(
fontSize: 18,
height: 1.1,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
),
),
),
],
),
),
),
backgroundColor: Color(0xffF6F6F8),
body: SingleChildScrollView(
child: Column(
children: [
/// Background elements
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,
),
),
),
Container(
width: double.infinity,
padding: const EdgeInsets.only(top: 1, bottom: 30),
child: Image.asset(
"assets/images/vector.png",
height: 230,
width: double.infinity,
fit: BoxFit.fitWidth,
),
),
Column(
children: [
/// Top Section with Gradient
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),
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: 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),
],
),
),
],
),
),
/// Bottom Grid Section
Padding(
padding: const EdgeInsets.all(15),
child: GridView.count(
crossAxisCount: 2, // items per row
crossAxisSpacing: 8.5,
mainAxisSpacing: 16,
childAspectRatio: 2.0, // tiles height
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",
txtColor: const Color(0xff1487C9),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const LeaveApplicationScreen(),
),
);
},
),
_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)
Widget _buildTile({
required String label,
required String subtitle,
required String assetIcon, // SVG/PNG asset instead of IconData
required Color txtColor,
VoidCallback? onTap,
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(20),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
child: Row(
children: [
/// Left side text
Expanded(
flex: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 15,
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500,
color: txtColor,
),
),
const SizedBox(height: 5),
Text(
subtitle,
style: const TextStyle(
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
fontSize: 12,
height: 1.4,
color: Color(0xff818181),
),
),
],
),
),
/// Right side icon (SVG/PNG)
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: 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,
assetIcon,
fit: BoxFit.contain,
),
),
),
),
),
],
),
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class LeaveApplicationScreen extends StatefulWidget {
const LeaveApplicationScreen({Key? key}) : super(key: key);
@override
State<LeaveApplicationScreen> createState() => _LeaveApplicationScreenState();
}
class _LeaveApplicationScreenState extends State<LeaveApplicationScreen> {
final TextEditingController _reasonController = TextEditingController();
DateTime? _startDate;
DateTime? _endDate;
Future<void> _pickDate({required bool isStart}) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2020),
lastDate: DateTime(2100),
);
if (picked != null) {
setState(() {
if (isStart) {
_startDate = picked;
} else {
_endDate = picked;
}
});
}
}
@override
Widget build(BuildContext context) {
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),
const Text(
"Leave Application",
style: TextStyle(
fontSize: 18,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
],
),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Align(
alignment: Alignment.topRight,
child: Text(
"Dummy Screen !",
style: TextStyle(
fontSize: 10,
height: 1,
fontWeight: FontWeight.bold,
),
),
),
const Text(
"Apply for Leave",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
TextField(
controller: _reasonController,
decoration: const InputDecoration(
labelText: "Reason for Leave",
border: OutlineInputBorder(),
),
maxLines: 3,
),
const SizedBox(height: 20),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () => _pickDate(isStart: true),
child: Text(_startDate == null
? "Select Start Date"
: "Start: ${_startDate!.toLocal()}".split(' ')[0]),
),
),
const SizedBox(width: 10),
Expanded(
child: ElevatedButton(
onPressed: () => _pickDate(isStart: false),
child: Text(_endDate == null
? "Select End Date"
: "End: ${_endDate!.toLocal()}".split(' ')[0]),
),
),
],
),
const Spacer(),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
// Handle submission logic here
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Leave application submitted")),
);
},
child: const Text("Submit Application"),
),
),
],
),
),
);
}
}
import 'package:dotted_line/dotted_line.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:generp/screens/hrm/RewardSearchScreen.dart';
import 'package:provider/provider.dart';
import '../../Notifiers/hrmProvider/rewardListProvider.dart';
import '../../Utils/app_colors.dart';
class RewardListScreen extends StatefulWidget {
const RewardListScreen({Key? key}) : super(key: key);
@override
State<RewardListScreen> createState() => _RewardListScreenState();
}
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,
),
),
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),
],
),
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 ---
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: 12),
Row(
children: [
Expanded(
child: Container(
height: 110,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xffe8ddff),
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"₹${disbursed}", // Disbursed Amount
style: const TextStyle(
fontSize: 20,
color: Color(0xff493272),
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500),
),
const SizedBox(height: 8),
const Text("Disbursed Amount",
style: TextStyle(
fontSize: 14,
color: Color(0xff2D2D2D),
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w400)),
],
),
),
),
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: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"₹${balance}", // Balance Amount
style: const TextStyle(
fontSize: 18,
color: Color(0xff605C00),
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500),
),
const SizedBox(height: 8),
const Text("Balance Amount",
style: TextStyle(
fontSize: 14,
color: Color(0xff2D2D2D),
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w400)),
],
),
),
),
],
),
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"),
],
),
);
}
),
);
}
)
);
}
/// Reusable Reward Card Function
Widget _rewardListCard({
required String title,
required String dateTime,
required String achieved,
required String disbursed,
required String balance,
required String enteredBy,
}) {
return Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 6,
offset: const Offset(0, 3),
)
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Header Row
Row(
children: [
CircleAvatar(
radius: 22.5,
backgroundColor: Color(0xffEDF8FF),
child: SvgPicture.asset(
height: 28,
width: 28,
"assets/svg/hrm/rewardList.svg",
fit: BoxFit.contain,
),
),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 14.5,
color: Color(0xff2D2D2D),
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w400),
),
Text(
dateTime,
style: const TextStyle(
fontSize: 12.5,
color: Color(0xff818181),
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w400),
),
],
),
),
],
),
const SizedBox(height: 12),
/// Amount Details
Padding(
padding: const EdgeInsets.all(2.0),
child: Row(
children: [
const Text(
"Amount Details",
style: TextStyle(
fontSize: 15,
color: Color(0xff2D2D2D),
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w600),
),
const SizedBox(width: 10),
Expanded(
child: DottedLine(
dashLength: 4,
dashGapLength: 2,
lineThickness: 1,
dashColor: Color(0xff999999),
)
),
],
),
),
const SizedBox(height: 6),
_buildKeyValue("Achieved Amount", achieved),
_buildKeyValue("Disbursed Amount", disbursed),
_buildKeyValue("Balance Amount", balance),
const SizedBox(height: 10),
/// Employee Details
Padding(
padding: const EdgeInsets.all(2.0),
child: Row(
children: [
const Text(
"Employee Details",
style: TextStyle(
fontSize: 15,
color: Color(0xff2D2D2D),
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w600),
),
const SizedBox(width: 10),
Expanded(
child: DottedLine(
dashLength: 4,
dashGapLength: 2,
lineThickness: 1,
dashColor: Color(0xff999999),
)
),
],
),
),
_buildKeyValue("Entered By", enteredBy),
],
),
);
}
/// Key-Value Row
Widget _buildKeyValue(String key, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 3.5, horizontal: 2),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(key, style: const TextStyle(
fontSize: 14,
color: Color(0xff2D2D2D),
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w400)
),
Text(value, style: const TextStyle(
fontSize: 14,
color: Color(0xff2D2D2D),
fontFamily: "Plus Jakarta Sans",
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w400)
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import '../../Utils/app_colors.dart';
class RewardSearchScreen extends StatefulWidget {
const RewardSearchScreen({super.key});
@override
State<RewardSearchScreen> createState() => _RewardSearchScreenState();
}
class _RewardSearchScreenState extends State<RewardSearchScreen> {
final TextEditingController _searchController = TextEditingController();
// Dummy data
final List<String> accounts = [
"Siddha Bank",
"Siddharth Shivam",
"Sidheshwar Temple",
"Axis Bank",
"SBI",
];
final List<String> inquiries = [
"Inquiry Siddha Bank",
"Inquiry Sidharth",
"Inquiry Sidheshwar",
"Customer SBI",
];
final List<String> leads = [
"Lead Siddha Bank",
"Lead Shivam",
"Lead Sidheshwar",
"Lead HDFC",
];
// Filtered data
String query = "";
@override
Widget build(BuildContext context) {
final filteredAccounts =
accounts.where((e) => e.toLowerCase().contains(query.toLowerCase())).toList();
final filteredInquiries =
inquiries.where((e) => e.toLowerCase().contains(query.toLowerCase())).toList();
final filteredLeads =
leads.where((e) => e.toLowerCase().contains(query.toLowerCase())).toList();
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: const Color(0xFFFFFFFF),
title: Row(
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25,
),
),
const SizedBox(width: 10),
const Text(
"Search",
style: TextStyle(
fontSize: 18,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
],
),
),
backgroundColor: Color(0xFFF6F6F8),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Search Field
TextField(
controller: _searchController,
onChanged: (value) {
setState(() {
query = value;
});
},
decoration: InputDecoration(
hintText: "Search",
prefixIcon: const Icon(Icons.search, color: Colors.black54),
filled: true,
fillColor: const Color(0xffFFFFFF),
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
),
),
const SizedBox(height: 20),
if (query.isNotEmpty)
Text(
'Result for “$query”',
style: const TextStyle(
fontSize: 18,
color: Color(0xff1487C9),
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 20),
if (query.isNotEmpty && filteredAccounts.isNotEmpty)
_buildSection("Accounts", filteredAccounts),
if (query.isNotEmpty && filteredInquiries.isNotEmpty)
_buildSection("Inquiries", filteredInquiries),
if (query.isNotEmpty && filteredLeads.isNotEmpty)
_buildSection("Leads", filteredLeads),
if (query.isNotEmpty &&
filteredAccounts.isEmpty &&
filteredInquiries.isEmpty &&
filteredLeads.isEmpty)
const Center(
child: Text(
"No results found",
style: TextStyle(fontSize: 14, color: Colors.grey),
),
),
],
),
),
);
}
/// Reusable Section Widget
Widget _buildSection(String title, List<String> items) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
fontFamily: "Plus Jakarta Sans",
color: Color(0xff2D2D2D),
),
),
const SizedBox(height: 10),
Column(
children: items
.map((e) => Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 5,
offset: const Offset(0, 3),
)
],
),
child: Row(
children: [
Container(
height: 32,
width: 32,
decoration: BoxDecoration(
color: const Color(0xffE8F3FF),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(Icons.search,
color: Color(0xff0066FF), size: 20),
),
const SizedBox(width: 10),
Expanded(
child: Text(
e,
style: const TextStyle(
fontSize: 14,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
color: Color(0xff2D2D2D),
),
overflow: TextOverflow.ellipsis,
),
),
],
),
))
.toList(),
)
],
);
}
}
import 'package:dotted_line/dotted_line.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart';
import '../../Notifiers/hrmProvider/tourExpensesDetailsProvider.dart';
import '../../Utils/app_colors.dart';
import '../finance/FileViewer.dart';
class TourExpensesDetailsScreen extends StatelessWidget {
final String tourBillId;
const TourExpensesDetailsScreen({Key? key, required this.tourBillId})
: super(key: key);
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => TourExpensesDetailsProvider()
..fetchTourExpensesDetails(context, 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,
),
),
),
],
),
),
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"));
}
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 ?? "-",
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,
),
),
),
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),
/// 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())
showDialog(
context: context,
builder: (_) => Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(t.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}");
showDialog(
context: context,
builder: (_) => Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(h.imageDirFilePath.toString())
),
),
);
},
),
);
},
),
),
],
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,
),
),
),
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: 25),
],
),
);
},
),
),
);
}
/// Attach your reusable card functions here
Widget _tourExpenseCard({
required String employeeName,
required String placeOfVisit,
required String daAmount,
required String totalAmount,
required String fromDate,
required String toDate,
required String remarks,
}) {
// paste your same implementation here
return Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 6,
offset: const Offset(0, 3),
)
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
CircleAvatar(
radius: 22.5,
backgroundColor: const Color(0xffFFF3E0),
child: SvgPicture.asset(
"assets/svg/hrm/travel_ic.svg",
),
),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(employeeName,
style: TextStyle(
fontSize: 14.5,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
)
),
Text(placeOfVisit,
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: AppColors.app_blue,
),
),
],
),
],
),
const SizedBox(height: 12),
_buildSectionHeader("Amount Details"),
_buildKeyValue("DA Amount", daAmount),
_buildKeyValue("Total Amount", totalAmount),
const SizedBox(height: 10),
_buildSectionHeader("Tour Time"),
_buildKeyValue("From Date", fromDate),
_buildKeyValue("To Date", toDate),
const SizedBox(height: 10),
_buildSectionHeader("Remarks"),
_buildKeyValue("Extra Note", remarks),
],
),
);
}
Widget _travelExpenseCard({
required String travelType,
required String amount,
required String from,
required String to,
required VoidCallback onViewTap,
}) {
// paste your travel card code here
return Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 6,
offset: const Offset(0, 3),
)
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
CircleAvatar(
radius: 20,
backgroundColor: const Color(0xffFFF3E0),
child: SvgPicture.asset(
"assets/svg/hrm/travel_ic.svg",
),
),
const SizedBox(width: 8),
Text(travelType,
style: TextStyle(
fontSize: 14.5,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
)
),
],
),
Text("₹$amount",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: AppColors.app_blue,
),
),
],
),
const SizedBox(height: 15),
_buildSectionHeader("Travel Details"),
const SizedBox(height: 4),
_buildKeyValue("From", from),
const SizedBox(height: 2),
_buildKeyValue("To", to),
const SizedBox(height: 2),
_buildKeyValue("Image", "View", isLink: true, onTap: onViewTap),
],
),
);
}
Widget _hotelExpenseCard({
required String hotelName,
required String amount,
required String fromDate,
required String toDate,
required VoidCallback onViewTap,
}) {
// paste your hotel card code here
return Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 6,
offset: const Offset(0, 3),
)
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
CircleAvatar(
radius: 20,
backgroundColor: const Color(0xffFCE4EC),
child:
SvgPicture.asset(
"assets/svg/hrm/hotel_ic.svg",
),
),
const SizedBox(width: 8),
Text(hotelName,
style: TextStyle(
fontSize: 14.5,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
)
),
],
),
Text("₹$amount",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: AppColors.app_blue,
),
),
],
),
const SizedBox(height: 15),
_buildSectionHeader("Living Details"),
const SizedBox(height: 4),
_buildKeyValue("From", fromDate),
const SizedBox(height: 2),
_buildKeyValue("To", toDate),
const SizedBox(height: 2),
_buildKeyValue("Image", "View", isLink: true, onTap: onViewTap),
],
),
);
}
Widget _otherExpenseCard({
required String description,
required String amount,
required String date,
required VoidCallback onViewTap,
}) {
// paste your other expense card code here
return Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 6,
offset: const Offset(0, 3),
)
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
CircleAvatar(
radius: 20,
backgroundColor: const Color(0xffEDE7F6),
child: SvgPicture.asset(
"assets/svg/hrm/books_ic.svg",
),
),
const SizedBox(width: 8),
Text(
description,
style: TextStyle(
fontSize: 14.5,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w600,
color: AppColors.semi_black,
)
),
],
),
Text("₹$amount",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: AppColors.app_blue,
),
),
],
),
const SizedBox(height: 15),
_buildSectionHeader("Other Details"),
const SizedBox(height: 4),
_buildKeyValue("Date", date),
const SizedBox(height: 2),
_buildKeyValue("Description", description),
const SizedBox(height: 2),
_buildKeyValue("Image", "View", isLink: true, onTap: onViewTap),
],
),
);
}
Widget _buildSectionHeader(String title) {
return Row(
children: [
Text(title,
style: const TextStyle(
fontSize: 14,
fontFamily: "JakartaSemiBold",
)
),
const SizedBox(width: 16),
Expanded(
child: DottedLine(
dashGapLength: 3,
dashGapColor:
Colors.white,
dashColor: AppColors.grey_semi,
dashLength: 2,
lineThickness: 0.5,
),
)
],
);
}
Widget _buildKeyValue(String key, String value,
{bool isLink = false, VoidCallback? onTap}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 3.5, horizontal: 2),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(key,
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.semi_black,
),
),
isLink
? GestureDetector(
onTap: onTap,
child: const Text("View",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.blue)),
)
: Text(value,
style: const TextStyle(
fontSize: 14,
color: Color(0xFF818181),
),
),
],
),
);
}
Widget _expenseHeaderCard({
required String title,
required String date,
required status,
required List<Map<String, String>> details,
}) {
bool showMore = false;
return StatefulBuilder(
builder: (context, setState) {
return Card(
margin: EdgeInsets.symmetric(horizontal: 0, vertical: 1.2),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(30),
bottomRight: Radius.circular(30),
),
),
elevation: 2,
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(30),
bottomRight: Radius.circular(30),
),
),
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 14),
child: Column(
children: [
/// Header Row
Row(
children: [
Container(
height: 45,
width: 45,
padding: const EdgeInsets.all(7.5),
decoration: const BoxDecoration(
color: Color(0xFFE6F6FF),
shape: BoxShape.circle,
),
child: SvgPicture.asset("assets/svg/hrm/tour_main_ic.svg"),
),
const SizedBox(width: 10),
Expanded(
flex: 5,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
style: const TextStyle(
fontSize: 14,
fontFamily: "JakartaRegular",
color: Color(0xff2D2D2D)
)
),
const SizedBox(height: 3),
Text(date,
style: const TextStyle(
fontSize: 12,
color: Color(0xff818181)
)
),
],
),
),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: _getStatusBgColor(status),
),
child: Text(status,
style: TextStyle(
fontSize: 14,
fontFamily: "JakartaRegular",
color: _getStatusTxtColor(status))),
)
],
),
const SizedBox(height: 10),
/// Expanded Section
if (showMore) ...[
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
const Text("Amount Details",
style: TextStyle(
fontSize: 14,
fontFamily: "JakartaSemiBold",
)
),
const SizedBox(width: 10),
Expanded(
child: DottedLine(
dashGapLength: 4,
dashGapColor: Colors.white,
dashColor: AppColors.grey_semi,
dashLength: 2,
lineThickness: 0.5,
),
),
],
),
),
const SizedBox(height: 6),
Column(
children: details.map((d) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 4,
child: Text(d["key"] ?? "-",
style: TextStyle(
fontSize: 14,
color: AppColors.semi_black,
fontFamily: "JakartaRegular",
)
)
),
Expanded(
flex: 3,
child: Text(d["value"] ?? "-",
textAlign: TextAlign.right,
style: const TextStyle(
fontSize: 14,
fontFamily: "JakartaRegular",
color: Color(0xff818181)
)
)
),
],
),
);
}).toList(),
),
],
/// Toggle Button
InkWell(
onTap: () => setState(() => showMore = !showMore),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(showMore ? "Hide Details" : "View Details",
style: TextStyle(
fontSize: 14,
fontFamily: "JakartaMedium",
fontWeight: FontWeight.w500,
color: AppColors.app_blue,
)
),
const SizedBox(width: 6),
Transform.flip(
flipY: showMore,
child: SvgPicture.asset(
"assets/svg/arrow_dropdown.svg",
height: 25,
width: 20,
color: AppColors.app_blue,
),
)
],
),
),
)
],
),
),
);
},
);
}
/// Avatar color generator
Color _getStatusBgColor(value) {
var color = AppColors.approved_bg_color;
switch (value) {
case 'HR Approved':
return AppColors.approved_bg_color;
case 'Expired at HR':
return AppColors.rejected_bg_color;
case 'Expired at TL':
return AppColors.rejected_bg_color;
}
return color;
}
Color _getStatusTxtColor(value) {
var color = AppColors.approved_text_color;
switch (value) {
case 'HR Approved':
return AppColors.approved_text_color;
case 'Expired at HR':
return AppColors.rejected_text_color;
case 'Expired at TL':
return AppColors.rejected_text_color;
}
return color;
}
getText(value) {
switch (value) {
case 'HR Approved':
return "A";
case 'Expired at HR':
return "E";
case 'Expired at TL':
return "E";
case 'Updated':
return "U";
default:
return "Requested";
}
}
}
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:generp/screens/hrm/TourExpensesDetailsScreen.dart';
import 'package:provider/provider.dart';
import '../../Utils/app_colors.dart';
import '../../Models/hrmModels/tourExpensesListResponse.dart';
import '../../Notifiers/hrmProvider/tourExpensesProvider.dart';
import 'AddTourExpBillScreen.dart';
class TourExpensesListScreen extends StatefulWidget {
const TourExpensesListScreen({super.key});
@override
State<TourExpensesListScreen> createState() => _TourExpensesListScreenState();
}
class _TourExpensesListScreenState extends State<TourExpensesListScreen> {
@override
Widget build(BuildContext context) {
return SafeArea(
top: false,
child: ChangeNotifierProvider(
create: (_) => TourExpensesProvider()..fetchTourExpenses(context, "1"),
child: Consumer<TourExpensesProvider>(
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),
const Text(
"Tour Expenses",
style: TextStyle(
fontSize: 18,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
],
),
),
backgroundColor: const Color(0xFFF6F6F8),
body: Column(
children: [
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?.tourList == null ||
provider.response!.tourList!.isEmpty) {
return const Center(child: Text("No Tour Expenses Found"));
}
final list = provider.response!.tourList!;
return ListView.builder(
padding: const EdgeInsets.all(12),
itemCount: list.length,
itemBuilder: (context, index) {
final TourList item = list[index];
return InkWell(
onTap: () {
/// navigation flow
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TourExpensesDetailsScreen(
tourBillId: item.id.toString(),
),
),
);
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 6),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
/// Left Avatar Circle
Container(
height: 46,
width: 46,
decoration: BoxDecoration(
color: _getAvatarColor(item.approvalStatus),
shape: BoxShape.circle,
),
child: Center(
child: Text(
getText(item.approvalStatus),
style: TextStyle(
fontSize: 15,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w500,
color: _getAvatarTxtColor(item.approvalStatus),
),
),
),
),
const SizedBox(width: 12),
/// Middle Section
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.placeOfVisit ?? "-",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 14,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
color: Color(0xff2d2d2d),
),
),
Text(
item.appliedDate ?? "-",
style: const TextStyle(
fontSize: 12,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
color: Color(0xff818181),
),
),
],
),
),
/// Right Section (Applied Amount)
Text(
"₹${item.appliedAmount ?? '0'}",
style: const TextStyle(
fontSize: 14,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w500,
color: Color(0xff1487c9),
)
),
],
),
),
);
},
);
},
),
),
],
),
floatingActionButtonLocation:
FloatingActionButtonLocation.centerFloat,
floatingActionButton: InkResponse(
onTap: () {
HapticFeedback.selectionClick();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const AddBillScreen(pageTitleName: "Add Bill",),
settings: const RouteSettings(
name: 'AddTourExpBillScreen'),
),
).then((_) {
});
// 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 Bill",
style: TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
color: Colors.white,
),
),
),
),
// /// Bottom Add Bill Button
// bottomNavigationBar: Container(
// padding: const EdgeInsets.all(18),
// color: Colors.white,
// child: ElevatedButton(
// style: ElevatedButton.styleFrom(
// backgroundColor: Color(0xff1487c9),
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(15),
// ),
// padding: const EdgeInsets.symmetric(vertical: 14),
// ),
// onPressed: () {
// // to work
// },
// child: const Text(
// "Add Bill",
// style: TextStyle(
// fontSize: 16,
// fontFamily: "Plus Jakarta Sans",
// fontWeight: FontWeight.w500,
// color: Colors.white,
// ),
// ),
// ),
// ),
);
},
),
),
);
}
/// Avatar color generator
Color _getAvatarColor(value) {
var color = AppColors.approved_bg_color;
switch (value) {
case 'HR Approved':
return AppColors.approved_bg_color;
case 'Expired at HR':
return AppColors.rejected_bg_color;
case 'Expired at TL':
return AppColors.rejected_bg_color;
}
return color;
}
Color _getAvatarTxtColor(value) {
var color = AppColors.approved_text_color;
switch (value) {
case 'HR Approved':
return AppColors.approved_text_color;
case 'Expired at HR':
return AppColors.rejected_text_color;
case 'Expired at TL':
return AppColors.rejected_text_color;
}
return color;
}
getText(value) {
switch (value) {
case 'HR Approved':
return "A";
case 'Expired at HR':
return "E";
case 'Expired at TL':
return "E";
case 'Updated':
return "U";
default:
return "Requested";
}
}
}
......@@ -56,3 +56,10 @@ export 'package:generp/Notifiers/crmProvider/addProspectLeadsProvider.dart';
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/attendanceListProvider.dart';
export 'package:generp/Notifiers/hrmProvider/AttendanceDetailsProvider.dart';
export 'package:generp/Notifiers/hrmProvider/tourExpensesProvider.dart';
export 'package:generp/Notifiers/hrmProvider/tourExpensesDetailsProvider.dart';
export 'package:generp/Notifiers/hrmProvider/rewardListProvider.dart';
......@@ -54,3 +54,5 @@ export 'package:generp/screens/crm/QuotationDetails.dart';
export 'package:generp/screens/crm/contactDetails.dart';
export 'package:generp/screens/crm/editAccountDetails.dart';
export 'package:generp/screens/crm/productDetails.dart';
// hrm screen export
export 'package:generp/screens/hrm/Attendancelist.dart';
......@@ -25,6 +25,10 @@ import 'package:generp/Models/crmModels/crmProspectDetailsAddLeadsResponse.dart'
import 'package:generp/Models/crmModels/crmProspectDetailsResponse.dart';
import 'package:generp/Models/financeModels/addDirectPaymentResponse.dart';
import 'package:generp/Models/financeModels/paymentRequisitionPaymentsListResponse.dart';
import 'package:generp/Models/hrmModels/attendanceRequestListResponse.dart';
import 'package:generp/Models/hrmModels/rewardListResponse.dart';
import 'package:generp/Models/hrmModels/tourExpensesDetailsResponse.dart';
import 'package:generp/Models/hrmModels/tourExpensesListResponse.dart';
import 'package:generp/Models/ordersModels/PendingTPCAgentListResponse.dart';
import 'package:generp/Models/ordersModels/TPCAgentDetailsResponse.dart';
import 'package:generp/Models/ordersModels/TPCListResponse.dart';
......@@ -92,6 +96,7 @@ import '../Models/financeModels/paymentRequisitionPaymentsDetailsResponse.dart';
import '../Models/financeModels/paymentRequisitionPaymentsReceiptsDetailsResponse.dart';
import '../Models/financeModels/paymentRequisitionPaymentsReceiptsListResponse.dart';
import '../Models/generatorComplaintResponse.dart';
import '../Models/hrmModels/attendanceRequestDetailsResponse.dart';
import '../Models/loadGeneratorDetailsResponse.dart';
import '../Models/financeModels/financeDashboardPagesResponse.dart';
import '../Models/ordersModels/AddOrderPaymentSelectAccountResponse.dart';
......@@ -4898,6 +4903,143 @@ class ApiCalling {
}
}
///hrm modules
///
static Future<attendanceRequestListResponse?> attendanceRequestListAPI(
empId,
session,
type,
from,
to,
) async {
try {
Map<String, String> data = {
'emp_id': (empId).toString(),
'session_id': (session).toString(),
'type': (type),
'from': (from),
'to': (to),
};
final res = await post(data, AttendanceRequestListUrl, {});
if (res != null) {
print(data);
debugPrint(res.body);
return attendanceRequestListResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint('hello bev=bug $e ');
return null;
}
}
static Future<attendanceRequestDetailsResponse?> attendanceRequestDetailAPI(
empId,
session,
attendanceRequestId,
) async {
try {
Map<String, String> data = {
'emp_id': (empId).toString(),
'session_id': (session).toString(),
'attendance_request_id': (attendanceRequestId),
};
final res = await post(data, AttendanceRequestDetailsUrl, {});
if (res != null) {
print(data);
debugPrint(res.body);
return attendanceRequestDetailsResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint('hello bev=bug $e ');
return null;
}
}
//reward list
static Future<rewardListResponse?> rewardListAPI(
empId,
session,
) async {
try {
Map<String, String> data = {
'session_id': (session).toString(),
'emp_id': (empId).toString(),
};
final res = await post(data, RewardListUrl, {});
if (res != null) {
print(data);
debugPrint(res.body);
return rewardListResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint('hello bev=bug $e ');
return null;
}
}
//tour exp
static Future<tourExpensesListResponse?> tourExpensesListAPI(
empId,
session,
pageNumber,
) async {
try {
Map<String, String> data = {
'session_id': (session).toString(),
'emp_id': (empId).toString(),
'page_number': (pageNumber),
};
final res = await post(data, TourExpensesListUrl, {});
if (res != null) {
print(data);
debugPrint(res.body);
return tourExpensesListResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint('hello bev=bug $e ');
return null;
}
}
static Future<tourExpensesDetailsResponse?> tourExpensesDetailAPI(
session,
empId,
tourBillId,
) async {
try {
Map<String, String> data = {
'session_id': (session).toString(),
'emp_id': (empId).toString(),
'tour_bill_id': (tourBillId),
};
final res = await post(data, TourExpensesDetailsUrl, {});
if (res != null) {
print(data);
debugPrint(res.body);
return tourExpensesDetailsResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint('hello bev=bug $e ');
return null;
}
}
// static Future<CommonResponse?> TpcIssueListApprovalAPI(
// empId,
// session,
......
......@@ -178,6 +178,18 @@ const crmDashboardFollowUpUrl = "${baseUrl_test}crm_dashboard_followup_list";
const crmDashboardQuotationsUrl = "${baseUrl_test}crm_dashboard_quotations_list";
///HRM
//Attendance
const AttendanceRequestListUrl ="${baseUrl_test}attendance_request_list";
const AttendanceRequestDetailsUrl ="${baseUrl_test}attendance_request_details";
const AddAttendanceRequestUrl ="${baseUrl_test}add_attendance_request";
// reward list
const RewardListUrl ="${baseUrl_test}hrm_emp_self_rewards";
// Tour Expenses hrm_emp_self_rewards
const TourExpensesListUrl ="${baseUrl_test}tour_bill_list";
const TourExpensesDetailsUrl ="${baseUrl_test}tour_bill_details";
......
......@@ -126,6 +126,7 @@ flutter:
- assets/svg/service/
- assets/svg/crm/
- assets/svg/order/
- assets/svg/hrm/
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
......
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