Forex CRUD- User Mnagement Delegationt

This commit is contained in:
venbaittech 2025-05-21 11:25:31 +05:30
parent 4559eb7262
commit c560963e50
9 changed files with 1024 additions and 260 deletions

View File

@ -1,33 +1,266 @@
import 'dart:convert';
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import '../../config/apiUrl.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
import '../../widgets/custom_text_forex.dart';
import 'forex_list.dart';
class ForexData extends StatefulWidget {
final Future<List<dynamic>> Function() fetchGetForex;
final bool isDesktop;
final Color? layoutColor;
const ForexData({super.key, required this.isDesktop, this.layoutColor});
final int? forexId; // <-- Add this
final Map<String, dynamic>? forexData;
const ForexData(
{super.key,
required this.isDesktop,
this.layoutColor,
required this.fetchGetForex,
this.forexId,
this.forexData});
@override
ForexDataState createState() => ForexDataState();
}
class ForexDataState extends State<ForexData> {
final ApiService apiService = ApiService();
Map<String, String> countryMap = {};
late List<dynamic>? apiCountryData;
late List<dynamic>? apiAirlineCountryData;
Map<String, dynamic>? apiData;
final Map<String, TextEditingController> controllers = {};
Map<String, String> errorMessages = {};
List<dynamic> countryList = [];
String? selectedCountry;
String? selectedCountryName;
String? selectedCurrency;
String? selectedDuration;
String? selectedPerdiemAmount;
String? userId;
int? forexDataId;
late String isActive = "1";
List<String> dataHeader = [
"country_code",
"country",
"currency",
"perdiemAmount"
];
Map<String, dynamic> forex_Detials() {
final data = {
// "forex_perdiem_id": int.parse(forexId),
"country_code": selectedCountry,
"country_name": selectedCountryName,
"currency": controllers["currency"]?.text,
"perdiem_amount": controllers["perdiemAmount"]?.text,
"is_active": 1,
"created_by": userId,
"is_active": isActive,
};
return data;
}
@override
void initState() {
super.initState();
apiCountryData = null;
apiData = null;
for (var field in dataHeader) {
controllers[field] = TextEditingController();
}
fetchCountries();
if (widget.forexId != null) {
print('Editing Forex ID: ${widget.forexId}');
updateForexDetails();
}
_clearError();
}
void _clearError() {
setState(() {
errorMessages.clear();
});
}
@override
void dispose() {
for (var controller in controllers.values) {
controller.dispose();
}
super.dispose();
}
void updateForexDetails() {
print("Updateeee - ${widget.forexData}");
final data = widget.forexData;
if (data == null) return;
setState(() {
selectedCountry = data['country_code']; // For dropdown
selectedCountryName =
data['country_name']; // For dropdown label or display
selectedCurrency = data['currency']; // Optional if used elsewhere
controllers['currency']?.text = data['currency'] ?? '';
controllers['perdiemAmount']?.text = data['perdiem_amount'].toString();
isActive = data["is_active"];
final forexId = int.tryParse(data['forex_perdiem_id'].toString());
forexDataId = forexId;
});
}
Future<void> fetchCountries() async {
try {
List<dynamic> countries = await apiService.fetchCountryList();
setState(() {
apiCountryData = countries;
});
} catch (e) {
print('Error fetching country list: $e');
}
}
void toggleStatus() {
setState(() {
isActive = isActive == "1" ? "0" : "1";
});
}
bool validateData() {
errorMessages.clear();
final data = {
"country_code": selectedCountry,
"country": selectedCountryName,
"currency": controllers["currency"]?.text,
"perdiemAmount": controllers["perdiemAmount"]?.text,
};
final requiredFields = [
"country_code",
"country",
"currency",
"perdiemAmount"
];
// Check validation for each field
for (String field in requiredFields) {
if (data[field] == null || data[field].toString().trim().isEmpty) {
errorMessages[field] = "Required";
}
}
return errorMessages.isEmpty;
}
Future<void> handleSubmit() async {
userId = await getUserId();
setState(() {
// This triggers UI rebuild with error messages
if (validateData()) {
postForexData();
}
});
final forexData1 = forex_Detials();
print("ForexDAta - $forexData1");
}
Future<void> postForexData({int isActive = 1}) async {
// final remarksData = getData();
final forexData = forex_Detials();
print("forexDataPOSDf - $forexData");
final String apiUrldata;
if (forexDataId != null) {
print("feforexDataId - $forexDataId");
apiUrldata = '$apiUrl/api/updateForexPerdiem/$forexDataId';
forexData["id"] = forexDataId;
forexData["updated_by"] = userId;
} else {
apiUrldata = '$apiUrl/api/createForexPerdiem';
forexData["created_by"] = userId;
}
print("Remarks Data - remarksData");
// final String apiUrldata = '$apiUrl/api/createForexPerdiem';
// api/updateForexPerdiem/39
final token = await getToken(); // Fetch token
if (token == null) {
throw Exception('Token not found. Please log in.');
}
// if (selectedPlanId != null && selectedPlanId!.isNotEmpty) {
// planData['plan_id'] = selectedPlanId; // Add plan_id for update
// }
try {
final uri = Uri.parse(apiUrldata);
final headers = {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
};
final body = jsonEncode(forexData);
final response = forexDataId != null
? await http.put(uri, headers: headers, body: body)
: await http.post(uri, headers: headers, body: body);
// final response = await http.post(
// Uri.parse(apiUrldata),
// headers: {
// 'Authorization': 'Bearer $token',
// 'Content-Type': 'application/json',
// },
// body: jsonEncode(forexData), // Convert map to JSON
// );
if (response.statusCode == 200 || response.statusCode == 201) {
print("Forex Details Created successfully!");
print("Response: ${response.body}");
// _clearError();
_clearError();
await widget.fetchGetForex();
// dispose();
Navigator.of(context).pop();
} else {
print("Failed to submit plan. Status: ${response.statusCode}");
print("Error: ${response.body}");
}
} catch (e) {
print(" Error submitting plan: $e");
}
}
@override
Widget build(BuildContext context) {
late Map<String, String> countryMap; // Mapping country_code -> country_name
late List<String> countryCodes; // List of country codes
countryList = [];
// countryList = widget.apiCountryData ?? [];
// countryList = [];
countryList = apiCountryData ?? [];
// Map country codes to country names
countryMap = {
@ -52,13 +285,18 @@ class ForexDataState extends State<ForexData> {
Row(
children: [
Text(
'Create Forex Details',
style: GoogleFonts.poppins(fontSize: 18, color: Colors.black),
'Create Perdiem Amount',
style: GoogleFonts.poppins(fontSize: 15, color: Colors.black),
),
const Spacer(),
],
),
const SizedBox(height: 10),
const SizedBox(height: 2),
Divider(
thickness: 0.2,
color: Colors.blueGrey.shade100,
),
const SizedBox(height: 5),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -80,10 +318,23 @@ class ForexDataState extends State<ForexData> {
selectedItem: countryMap[selectedCountry],
popupProps: PopupProps.menu(
showSearchBox: true, // Enables search functionality
menuProps: const MenuProps(
backgroundColor: Colors.white,
),
constraints: BoxConstraints(maxHeight: 250),
itemBuilder: (context, item, isSelected) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 6.0),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search Country...",
contentPadding: EdgeInsets.symmetric(horizontal: 10),
hintStyle: GoogleFonts.poppins(fontSize: 11),
contentPadding: EdgeInsets.symmetric(horizontal: 4),
),
),
),
@ -101,7 +352,7 @@ class ForexDataState extends State<ForexData> {
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Country",
style: TextStyle(fontSize: 12),
style: GoogleFonts.poppins(fontSize: 11),
),
),
onChanged: (String? newValue) {
@ -110,18 +361,19 @@ class ForexDataState extends State<ForexData> {
selectedCountry = countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
selectedCountryName = newValue;
});
},
),
),
),
// if (errorMessages["country_code"] != null) ...[
// SizedBox(height: 5), // Space before error message
// Text(
// "Select Country",
// style: TextStyle(color: Colors.red, fontSize: 12),
// ),
// ],
if (errorMessages["country_code"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["country_code"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
const SizedBox(height: 10),
@ -129,7 +381,7 @@ class ForexDataState extends State<ForexData> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Currency *",
"Currency",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
@ -144,24 +396,26 @@ class ForexDataState extends State<ForexData> {
// ? MediaQuery.of(context).size.width * 0.330
// : MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Text(
// "cur",
// "${selectedCurrency}",
selectedCurrency ?? "Currency",
// selectedCurrency?.isNotEmpty == true ? selectedCurrency! : "Currency",
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
height: 40,
child: TextField(
controller: controllers["currency"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Currency",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
)),
),
if (errorMessages["currency"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["currency"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
// if (isDesktop)SizedBox(width: 8,) else SizedBox(height: 8,),
@ -185,33 +439,64 @@ class ForexDataState extends State<ForexData> {
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 35,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
// "amo",
selectedPerdiemAmount ?? "Amount",
// selectedPerdiemAmount?.isNotEmpty == true ? selectedPerdiemAmount! : "Amount",
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
// decoration: const InputDecoration(
// labelText: "To",
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
// floatingLabelBehavior: FloatingLabelBehavior.never,
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 16),
// ),
),
),
),
height: 40,
child: TextField(
controller: controllers["perdiemAmount"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Perdiem Amount",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
)),
),
if (errorMessages["perdiemAmount"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["perdiemAmount"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
SizedBox(
height: 15,
),
if (forexDataId != null)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Change Status ",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
Tooltip(
message:
isActive == "1" ? "Tap to deactivate" : "Tap to activate",
child: GestureDetector(
onTap: toggleStatus,
child: Text(
isActive == "1" ? "Active" : "Inactive",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
color: isActive == "1" ? Colors.green : Colors.red,
),
),
),
)
],
),
if (forexDataId != null)
SizedBox(
height: 15,
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
@ -238,8 +523,9 @@ class ForexDataState extends State<ForexData> {
SizedBox(
child: ElevatedButton(
onPressed: () {
handleSubmit();
// You can get text from commentController.text
Navigator.of(context).pop(); // Close the modal
// Navigator.of(context).pop(); // Close the modal
},
style: ElevatedButton.styleFrom(
backgroundColor: widget.layoutColor,
@ -247,7 +533,7 @@ class ForexDataState extends State<ForexData> {
borderRadius: BorderRadius.circular(8),
),
),
child: Text('Submit',
child: Text('Save',
style: GoogleFonts.poppins(
fontSize: 11, color: Colors.white)),
),

View File

@ -25,6 +25,9 @@ class ForexDataList extends StatefulWidget {
}
class ForexDataListState extends State<ForexDataList> {
final GlobalKey<ForexDataListState> forexListKey =
GlobalKey<ForexDataListState>();
final ApiService apiService = ApiService();
late Future<List<dynamic>> futureForex;
@ -62,6 +65,19 @@ class ForexDataListState extends State<ForexDataList> {
// futurePlans = fetchPlans();
}
Future<List<dynamic>> refreshData() {
print("Calling Refresh Data");
futureForex = fetchGetForex();
return futureForex.then((users) {
setState(() {
allForex = users;
});
return users;
});
}
void loadInitialData() async {
String? layoutString = await getLayoutColor();
String? bodyStringColor = await getBodyColor();
@ -360,7 +376,7 @@ class ForexDataListState extends State<ForexDataList> {
Row(
children: [
Text(
'Forex Details',
'Perdiem Amount Details',
style: GoogleFonts.poppins(
fontSize: isDesktop ? 16 : 14,
fontWeight: FontWeight.w600,
@ -371,7 +387,7 @@ class ForexDataListState extends State<ForexDataList> {
),
if (isDesktop)
SizedBox(
width: MediaQuery.of(context).size.width * 0.22,
width: MediaQuery.of(context).size.width * 0.16,
),
if (isDesktop)
@ -431,11 +447,8 @@ class ForexDataListState extends State<ForexDataList> {
context: context,
builder: (context) => ForexData(
isDesktop: isDesktop,
// planId: plan.planId,
// planId: plan
// .planId
// .toString(),
layoutColor: layoutColor!,
fetchGetForex: refreshData,
// role:
// "Travel Agent"
),
@ -446,7 +459,7 @@ class ForexDataListState extends State<ForexDataList> {
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add Forex",
"Add Perdiem",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 13 : 11,
),
@ -537,7 +550,7 @@ class ForexDataListState extends State<ForexDataList> {
// ),
const SizedBox(height: 8),
Text(
"No Plans Available For This User",
"No Perdiem Available ",
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize: 20,
@ -546,7 +559,7 @@ class ForexDataListState extends State<ForexDataList> {
),
const SizedBox(height: 20),
Text(
"Please Create Plan",
"Please Create Perdiem Amount",
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize: 16, color: Colors.grey),
@ -617,6 +630,13 @@ class ForexDataListState extends State<ForexDataList> {
fontSize: 13,
fontWeight: FontWeight.w600),
)),
DataColumn(
label: Text(
'Status',
style: GoogleFonts.poppins(
fontSize: 13,
fontWeight: FontWeight.w600),
)),
DataColumn(
label: Text(
'Actions',
@ -658,186 +678,64 @@ class ForexDataListState extends State<ForexDataList> {
softWrap: true,
overflow: TextOverflow.ellipsis)),
DataCell(
UserActionsMenu(
user: forex,
getUserDetails: (id) =>
apiService.getSingleUser(id),
Text(
forex['is_active'] == "1"
? 'Active'
: 'Inactive',
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
// color: forex['is_active'] == "1"
// ? Colors.green
// : Colors.grey,
),
softWrap: true,
overflow: TextOverflow.ellipsis,
),
// PopupMenuButton<int>(
// color: Colors.white,
// padding: EdgeInsets.zero,
// offset: Offset(0, 30),
// icon: Icon(
// Icons.more_vert,
// color: Color(0xFF475569),
// size: 14,
// ),
// itemBuilder: (context) => [
// CustomPopupMenuEntry(
// child: Container(
// padding: EdgeInsets.symmetric(
// horizontal: 8, vertical: 8),
// child: Row(
// mainAxisSize: MainAxisSize.min,
// mainAxisAlignment:
// MainAxisAlignment.center,
// children: [
// IconButton(
// icon: Icon(
// Icons.remove_red_eye,
// color:
// Color(0xFF475569),
// size: 18),
// onPressed: () async {
// print(
// "USerDAta1 - $user");
// // Fetch the user data properly with await
// Map<String, dynamic>
// usersData =
// await apiService
// .getSingleUser(user[
// 'user_id']
// is String
// ? int.parse(user[
// 'user_id'])
// : user[
// 'user_id']);
//
// print(
// "USerDAta2 - $usersData");
//
// // userSingleData =
// // await apiService
// // .getSingleUser(user[
// // 'user_id']);
//
// context.go(
// "/CreateUserDetails",
// extra: {
// "selectedUser":
// usersData,
// "isViewMode": true
// },
// );
// }),
// IconButton(
// icon: Image.asset(
// 'assets/images/IconsImg/edit.png',
// width: 20,
// height: 15),
// onPressed: () async {
// // Fetch the user data properly with await
// Map<String, dynamic>
// usersData =
// await apiService
// .getSingleUser(user[
// 'user_id']
// is String
// ? int.parse(user[
// 'user_id'])
// : user[
// 'user_id']);
//
// print(
// "USerDAta2 - $usersData");
// context.go(
// "/CreateUserDetails",
// extra: {
// "selectedUser":
// usersData,
// "isViewMode": false
// },
// );
// },
// ),
// ],
// ),
// ),
// ),
// ],
// ),
// Row(
// children: [
// MouseRegion(
// cursor: user['is_active'] == "0"
// ? SystemMouseCursors.forbidden
// : SystemMouseCursors.click,
// child: IconButton(
// icon: Icon(Icons.remove_red_eye,
// size: 18,
// color: user['is_active'] == "0"
// ? Colors.grey
// : Color(0xFF475569)),
// onPressed: user['is_active'] == "0"
// ? null
// : () {
// context.go(
// "/CreateUserDetails",
// extra: {
// "selectedUser": user,
// "isViewMode": true
// },
// );
// },
// ),
// ),
//
// MouseRegion(
// cursor: user['is_active'] == "0"
// ? SystemMouseCursors.forbidden
// : SystemMouseCursors.click,
// child: GestureDetector(
// onTap: user['is_active'] == "0"
// ? null
// : () {
//
// },
// child: Image.asset(
// 'assets/images/IconsImg/edit.png',
// width: 20,
// height: 15),
// ),
// ),
//
// // MouseRegion(
// // cursor: user['is_active'] == "0"
// // ? SystemMouseCursors
// // .forbidden
// // : SystemMouseCursors.click,
// // child: IconButton(
// // icon: Icon(Icons.edit,
// // color:
// // user['is_active'] ==
// // "0"
// // ? Colors.grey
// // : Colors.green),
// // onPressed:
// // user['is_active'] == "0"
// // ? null
// // : () {
// // print(
// // "USER: $user");
// //
// // // final userJson = jsonEncode(
// // // user); // Convert user map to string
// // // final encodedUser =
// // // Uri.encodeComponent(
// // // userJson);
// //
// // context.go(
// // "/CreateUserDetails",
// // extra: {
// // "selectedUser":
// // user,
// // "isViewMode":
// // false
// // },
// // );
// // },
// // ),
// // ),
// ],
),
DataCell(
// UserActionsMenu(
// user: forex,
// getUserDetails: (id) =>
// apiService.getSingleUser(id),
// ),
GestureDetector(
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15),
onTap: () async {
// final userId = getUserId(user['user_id']);
// final usersData = await getUserDetails(userId);
//
final forexId = int.tryParse(
forex['forex_perdiem_id']
.toString());
if (forexId != null) {
print("ForexId -- $forexId");
final data = await apiService
.getForexDetailsFind(forexId);
print("ForexId -- $data");
showDialog(
context: context,
builder: (context) => ForexData(
isDesktop: isDesktop,
forexId: forexId, // Pass the ID
forexData: data,
layoutColor: layoutColor!,
// fetchGetForex: fetchGetForex,
fetchGetForex: refreshData,
// role:
// "Travel Agent"
),
);
} else {
print("Invalid Forex ID");
}
},
),
),
]);
}).toList(),

View File

@ -0,0 +1,56 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:frontend/utils/auth_utils.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:responsive_builder/responsive_builder.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../config/apiUrl.dart';
import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart';
import '../../utils/pagination.dart';
import '../../widgets/custom_popup.dart';
import '../../widgets/popup_userList_action.dart';
class Template extends StatefulWidget {
@override
TemplateState createState() => TemplateState();
}
class TemplateState extends State<Template> {
final ApiService apiService = ApiService();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
backgroundColor: Color(0xFFf5f5f5),
appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false),
body: Padding(
padding: isDesktop
? EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding
vertical: MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
: EdgeInsets.all(0),
child: Row(
children: [Text("Template ")],
),
),
);
});
}
}

View File

@ -23,16 +23,11 @@ class templatesList extends StatefulWidget {
class _templatesListState extends State<templatesList> {
final ApiService apiService = ApiService();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
@ -45,22 +40,17 @@ class _templatesListState extends State<templatesList> {
body: Padding(
padding: isDesktop
? EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding
vertical: MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
horizontal: MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding
vertical: MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
: EdgeInsets.all(0),
child: Row(
children: [
],
children: [Text("Template Lsit")],
),
),
);
});
}
}

View File

@ -94,6 +94,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
String? selectedFirstApprover;
String? selectedSecondApprover;
String? selectedThirdApprover;
String? selectedSubstituteApprover;
String? selectedFileNames;
Uint8List? passportDocumentBytes;
@ -125,6 +126,8 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
"secondApproval",
"thirdApproval",
"employeeCode",
"delegationStartDate",
"delegationEndDate",
"dateOfIssue",
"dateOfExpiry",
"changePassword"
@ -151,7 +154,8 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
"postal_code": controllers["postalCode"]?.text,
"country_code": selectedCountry,
"employee_code": controllers["employeeCode"]?.text,
"delegation_start_date": controllers["delegationStartDate"]?.text,
"delegation_end_date": controllers["delegationEndDate"]?.text,
"user_type": selectedUserType,
"role_id": selectedRole,
"department_id": selectedDepartment,
@ -161,6 +165,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
"first_approver": selectedFirstApprover,
"second_approver": selectedSecondApprover,
"third_approver": selectedThirdApprover,
"delegated_to_user_id": selectedSubstituteApprover,
"passport_number": controllers["passportNumber"]?.text,
"place_of_issue": controllers["placeOfIssue"]?.text,
"passport_document": passportFile,
@ -213,6 +218,12 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
controllers["employeeCode"]?.text =
apiselectedUser?["employee_code"] ?? "";
controllers["delegationStartDate"]?.text =
apiselectedUser?["delegation_start_date"] ?? "";
controllers["delegationEndDate"]?.text =
apiselectedUser?["delegation_end_date"] ?? "";
controllers["passportNumber"]?.text =
apiselectedUser?["passport_number"] ?? "";
@ -271,6 +282,15 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
selectedThirdApprover =
apiselectedUser?["third_approver"]?.toString() ?? "";
}
if (apiselectedUser?["delegated_to_user_id"] != null) {
print(
"UPDADele- ${apiselectedUser?["delegated_to_user_id"]?.toString()}");
selectedSubstituteApprover =
apiselectedUser?["delegated_to_user_id"]?.toString() ?? "";
print("UPDADele1- $selectedSubstituteApprover");
}
try {
final raw = apiselectedUser!["agent_supported_service_ids"];
@ -315,6 +335,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
@override
void initState() {
super.initState();
prepareForNewEntry();
selectedTab = "personal";
// WidgetsFlutterBinding.ensureInitialized();
@ -510,6 +531,12 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
super.dispose();
}
void prepareForNewEntry() {
for (var controller in controllers.values) {
controller.clear();
}
}
void handleNext() async {
print("USR Detail Next");
printFormData();
@ -595,7 +622,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
"last_name",
"email",
"mobile_no",
"employeeCode"
// "employeeCode"
];
if (apiselectedUser == null) {
@ -750,7 +777,9 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
print("Response body: ${response.body}");
if (response.statusCode == 200 || response.statusCode == 201) {
dispose();
print("✅ User submitted successfully!");
print("📨 Response: ${response.body}");
context.go('/listUser');
} else {
@ -973,6 +1002,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
selectedFirstApprover: selectedFirstApprover,
selectedSecondApprover: selectedSecondApprover,
selectedThirdApprover: selectedThirdApprover,
selectedSubstituteApprover: selectedSubstituteApprover,
onLevelChanged: (gender) {
setState(() {
selectedLevel = gender;
@ -998,6 +1028,11 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
selectedThirdApprover = role;
});
},
onFirstSubsApproverChanged: (role) {
setState(() {
selectedSubstituteApprover = role;
});
},
);
case "travel":
return TravellerDetails(

View File

@ -21,12 +21,14 @@ class OfficeDetails extends StatefulWidget {
final ValueChanged<String?>? onFirstApproverChanged;
final ValueChanged<String?>? onSecondApproverChanged;
final ValueChanged<String?>? onThirdApproverChanged;
final ValueChanged<String?>? onFirstSubsApproverChanged;
final String? selectedLevel;
final String? selectedDepartment;
final String? selectedFirstApprover;
final String? selectedSecondApprover;
final String? selectedThirdApprover;
final String? selectedSubstituteApprover;
const OfficeDetails({
Key? key,
@ -44,6 +46,8 @@ class OfficeDetails extends StatefulWidget {
this.onFirstApproverChanged,
this.onSecondApproverChanged,
this.onThirdApproverChanged,
this.selectedSubstituteApprover,
this.onFirstSubsApproverChanged,
}) : super(key: key);
@override
@ -57,6 +61,8 @@ class _OfficeDetailsState extends State<OfficeDetails> {
late List<String> countryCodes; // List of country codes
late List<dynamic>? apiCountryData;
bool isResetTrue = false;
late List<dynamic>? apiCostData;
late List<dynamic>? apiRoleData;
late List<dynamic>? apiUserData;
@ -84,6 +90,7 @@ class _OfficeDetailsState extends State<OfficeDetails> {
String? selectedFirstApprover;
String? selectedSecondApprover;
String? selectedThirdApprover;
String? selectedSubstituteApprover;
String? selectedFileNames;
Uint8List? passportDocumentBytes;
@ -139,6 +146,7 @@ class _OfficeDetailsState extends State<OfficeDetails> {
selectedFirstApprover = widget.selectedFirstApprover;
selectedSecondApprover = widget.selectedSecondApprover;
selectedThirdApprover = widget.selectedThirdApprover;
selectedSubstituteApprover = widget.selectedSubstituteApprover;
fetchDepartment();
fetchUsers();
@ -151,6 +159,27 @@ class _OfficeDetailsState extends State<OfficeDetails> {
});
}
// void handleReset() {
// setState(() {
// selectedSubstituteApprover = "";
// widget.controllers["delegationEndDate"]?.text = "";
// widget.controllers["delegationStartDate"]?.text = "";
// });
// }
void handleReset() {
setState(() {
isResetTrue = true;
// selectedSubstituteApprover = null;
// widget.onFirstSubsApproverChanged?.call(null);
// widget.controllers["delegationStartDate"]?.clear();
// widget.controllers["delegationEndDate"]?.clear();
//
// print("Start Date: ${widget.controllers["delegationStartDate"]?.text}");
// print("End Date: ${widget.controllers["delegationEndDate"]?.text}");
});
}
Future<void> fetchUsers() async {
try {
List<dynamic> users = await apiService.fetchUsers();
@ -224,6 +253,7 @@ class _OfficeDetailsState extends State<OfficeDetails> {
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 10,
@ -234,6 +264,30 @@ class _OfficeDetailsState extends State<OfficeDetails> {
height: 10,
),
_buildSecondRow(widget.isDesktop),
if (widget.isDesktop)
SizedBox(
height: 10,
),
Divider(
thickness: 0.2,
color: Colors.blueGrey.shade100,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Delegation",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
],
),
SizedBox(
height: 15,
),
_buildThirdRow(widget.isDesktop),
],
),
);
@ -293,6 +347,38 @@ class _OfficeDetailsState extends State<OfficeDetails> {
);
}
Widget _buildThirdRow(bool isDesktop) {
return Container(
color: Colors.white,
child: widget.isDesktop
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildApproverSubstitute1(isDesktop),
Spacer(),
buildDelegationStartDateField(isDesktop),
Spacer(), // Space after Last Name
buildDelegationEndDateField(isDesktop),
SizedBox(
width: 15,
),
buildReset(isDesktop)
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildApproverSubstitute1(isDesktop),
SizedBox(height: 8), // Vertical space
buildDelegationStartDateField(isDesktop),
SizedBox(height: 8),
buildDelegationEndDateField(isDesktop),
buildReset(isDesktop)
],
),
);
}
Widget buildEmpCodeField() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -848,4 +934,344 @@ class _OfficeDetailsState extends State<OfficeDetails> {
// ),
);
}
Widget buildApproverSubstitute1(bool isDesktop) {
return Container(
color: Colors.white,
// child: Expanded(
// Allow first column to take available space
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Delegate To",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: apiUserData == null
? Center(
child: Transform.scale(
scale: 0.5,
child: CircularProgressIndicator(),
),
)
: DropdownSearch<String>(
// selectedItem: userMap[selectedSubstituteApprover],
selectedItem: selectedSubstituteApprover != null
? userMap[selectedSubstituteApprover]
: null,
enabled: !widget.isViewMode,
popupProps: PopupProps.menu(
showSearchBox: true,
fit: FlexFit.loose, // Allows flexible height
constraints: BoxConstraints(maxHeight: 250),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search User...",
contentPadding:
EdgeInsets.symmetric(horizontal: 10),
),
),
),
items: apiUserData!.map((user) {
return "${user['first_name']} ${user['last_name']}";
}).toList(),
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 1,
),
),
),
dropdownBuilder: (context, selectedItem) => Align(
// Center-align selected item
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select",
style: GoogleFonts.poppins(
fontSize: 12,
),
),
),
// onChanged: (String? newValue) {
// setState(() {
// selectedFirstApprover = userMap.entries
// .firstWhere(
// (entry) => entry.value == newValue)
// .key;
//
// // if (selectedCountry!.isNotEmpty) {
// // errorMessages.remove("country_code");
// // }
// });
// widget.onFirstApproverChanged?.call(newValue);
// },
onChanged: (String? newValue) {
if (newValue == null) return;
final approverId = userMap.entries
.firstWhere(
(entry) => entry.value == newValue)
.key;
setState(() {
selectedSubstituteApprover = approverId;
});
widget.onFirstSubsApproverChanged?.call(
approverId); // not newValue, but approverId
},
),
),
),
],
),
],
),
],
),
// ),
);
}
Widget buildDelegationStartDateField(bool isDesktop) {
DateTime? _selectedCheckOutDate;
DateTime? _selectedEndDate;
Future<void> _selectCheckOutDate(BuildContext context) async {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
// Parse date from notifier if available, else use today
DateTime initialDate;
initialDate = today;
// // Use previously selected date if valid
// if (_selectedCheckOutDate != null &&
// _selectedCheckOutDate!.isAfter(today)) {
// initialDate = _selectedCheckOutDate!;
// }
//
// final pickedDate = await showDatePicker(
// context: context,
// initialDate: initialDate,
// firstDate: initialDate,
// lastDate: DateTime(2100),
// );
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: _selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(today)
? _selectedCheckOutDate!
: today,
firstDate: today,
lastDate: DateTime(2100),
);
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
setState(() {
_selectedCheckOutDate = pickedDate;
widget.controllers["delegationStartDate"]?.text =
DateFormat('dd-MM-yyyy').format(pickedDate);
});
}
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Start Date",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
// onTap: () async{
// _selectCheckOutDate(context);
//
// },
onTap: () async {
await _selectCheckOutDate(context);
},
child: AbsorbPointer(
child: TextField(
controller: widget.controllers["delegationStartDate"],
style: const TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Select Date",
labelStyle:
const TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 16),
suffixIcon: const Icon(Icons.calendar_today,
size: 16, color: Colors.grey),
),
),
),
),
),
),
// if (errorMessages["start_date"] != null) ...[
// SizedBox(height: 5), // Space before error message
// Text(
// "Select Start Date",
// style: TextStyle(color: Colors.red, fontSize: 12),
// ),
// ],
],
);
}
Widget buildDelegationEndDateField(bool isDesktop) {
DateTime? _selectedEndDate;
Future<void> _selectForexEndDate(BuildContext context) async {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
// Parse date from notifier if available, else use today
DateTime initialDate;
initialDate = today;
//
// final pickedDate = await showDatePicker(
// context: context,
// initialDate: initialDate,
// firstDate: initialDate,
// lastDate: DateTime(2100),
// );
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate:
_selectedEndDate != null && _selectedEndDate!.isAfter(today)
? _selectedEndDate!
: today,
firstDate: today,
lastDate: DateTime(2100),
);
if (pickedDate != null && pickedDate != _selectedEndDate) {
setState(() {
_selectedEndDate = pickedDate;
widget.controllers["delegationEndDate"]?.text =
DateFormat('dd-MM-yyyy').format(pickedDate);
// textControllers["_forexEndDate"]?.text =
// DateFormat('dd-MM-yyyy').format(initialDate);
});
}
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"End Date",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
width: isDesktop ? MediaQuery.of(context).size.width * 0.18 : null,
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
// onTap: () async{
// _selectCheckOutDate(context);
//
// },
onTap: () async {
await _selectForexEndDate(context);
},
child: AbsorbPointer(
child: TextField(
controller: widget.controllers["delegationEndDate"],
style: const TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Select Date",
labelStyle:
const TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 16),
suffixIcon: const Icon(Icons.calendar_today,
size: 16, color: Colors.grey),
),
),
),
),
),
),
// if (widget.errorMessages["employeeCode"] != null) ...[
// SizedBox(height: 5), // Space before error message
// Text(
// widget.errorMessages["employeeCode"]!,
// style: TextStyle(color: Colors.red, fontSize: 12),
// ),
// ],
],
);
}
Widget buildReset(bool isDesktop) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 8),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white, // Keep original color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 3),
),
onPressed: () {
handleReset();
},
child: Text(
"Reset",
style: GoogleFonts.poppins(fontSize: 11),
))
],
);
}
}

View File

@ -370,6 +370,12 @@ class _CustomAppBarState extends State<CustomAppBar> {
break;
case '/PolicyList':
context.go('/PolicyList');
case '/getPerdiem':
context.go('/getPerdiem');
case '/templateList':
context.go('/templateList');
case '/template':
context.go('/template');
case '/CreateUserDetails':
context.go(
"/CreateUserDetails",
@ -506,6 +512,13 @@ final List<Map<String, dynamic>> menuItems = [
},
{'value': '/group', 'icon': Icons.group, 'label': 'Group'},
{'value': '/PolicyList', 'icon': Icons.policy, 'label': 'Policy'},
{'value': '/getPerdiem', 'icon': Icons.ac_unit_sharp, 'label': 'Forex'},
{
'value': '/templateList',
'icon': Icons.ac_unit_sharp,
'label': 'Template List'
},
{'value': '/template', 'icon': Icons.ac_unit_sharp, 'label': 'Template'},
{
'value': '/CreateUserDetails',
'icon': Icons.account_circle,

View File

@ -5,6 +5,7 @@ import 'package:frontend/Screens/authentication/login/login_page.dart';
import 'package:frontend/Screens/authentication/loginPage1.dart';
import 'package:frontend/Screens/dashboard/home_page.dart';
import 'package:frontend/Screens/forex/forex_list.dart';
import 'package:frontend/Screens/myTemplates/templatesList.dart';
import 'package:frontend/Screens/organization/orgSetup.dart';
import 'package:frontend/Screens/organization/org_List.dart';
import 'package:frontend/Screens/plans/create_plans.dart';
@ -20,6 +21,7 @@ import '../Screens/allTrips/travel_agent_list.dart';
import '../Screens/approvals/approval_list.dart';
import '../Screens/group/group.dart';
import '../Screens/group/groupList.dart';
import '../Screens/myTemplates/template.dart';
import '../Screens/userManagement/create_user/create_user.dart';
final GoRouter router = GoRouter(
@ -96,6 +98,18 @@ final GoRouter router = GoRouter(
path: '/group',
builder: (context, state) => GroupList(),
),
GoRoute(
path: '/getPerdiem',
builder: (context, state) => ForexDataList(),
),
GoRoute(
path: '/templateList',
builder: (context, state) => templatesList(),
),
GoRoute(
path: '/template',
builder: (context, state) => Template(),
),
GoRoute(
path: '/approvallist',
builder: (context, state) => ApprovalList(),

View File

@ -732,4 +732,50 @@ class ApiService {
throw Exception('Failed to load plans');
}
}
Future<Map<String, dynamic>> getForexDetailsFind(int userId) async {
print('Single USer 1 - $userId');
// final String apiUrldata = '$apiUrl/api/users/find/$userId';
final String apiUrldata =
'$apiUrl/api/findForexPerdiem?forex_perdiem_id=$userId';
final token = await getToken();
if (token == null) {
throw Exception('Token not found. Please log in.');
}
final response = await http.get(
Uri.parse(apiUrldata),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
);
if (response.statusCode == 200) {
try {
final data = json.decode(response.body);
print("forexDat - $data");
if (!data.containsKey('data') || data['data'] is! List) {
throw Exception(
"Invalid response format: 'data' field is missing or not a List");
}
final List<dynamic> forexList = data['data'];
if (forexList.isEmpty) {
throw Exception('No forex data found.');
}
return forexList.first as Map<String, dynamic>;
} catch (e) {
throw Exception('Error parsing response: $e');
}
} else {
throw Exception('Failed to load plans');
}
}
}