user management -TravelDetails

This commit is contained in:
venbaittech 2025-05-13 19:42:23 +05:30
parent 4435eeadd3
commit 75592f9879
20 changed files with 4095 additions and 624 deletions

View File

@ -15,6 +15,7 @@ import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart'; import '../../services/apiService.dart';
import '../../utils/auth_utils.dart'; import '../../utils/auth_utils.dart';
import '../../utils/pagination.dart'; import '../../utils/pagination.dart';
import '../../utils/travelAgent_remarks.dart';
import '../../widgets/custom_popup.dart'; import '../../widgets/custom_popup.dart';
import '../../widgets/popup_listPlan_action.dart'; import '../../widgets/popup_listPlan_action.dart';
@ -530,20 +531,17 @@ class _ListAllPlansState extends State<ListAllPlans> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Icon(Icons.error_outline, // const Icon(Icons.error_outline,
color: Colors.redAccent, size: 60), // color: Colors.redAccent, size: 60),
const SizedBox(height: 1), // const SizedBox(height: 1),
Text( // Text(
"Oops!", // "Oops!",
style: GoogleFonts.poppins( // style: GoogleFonts.poppins(
fontSize: 12, // fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.redAccent),
// style: TextStyle(
// fontSize: 22,
// fontWeight: FontWeight.bold, // fontWeight: FontWeight.bold,
// color: Colors.redAccent) // color: Colors.redAccent),
), //
// ),
const SizedBox(height: 15), const SizedBox(height: 15),
Text( Text(
"No Trips", "No Trips",
@ -703,6 +701,8 @@ class _ListAllPlansState extends State<ListAllPlans> {
), ),
), ),
DataCell( DataCell(
Row(
children: [
PopupMenuButton<int>( PopupMenuButton<int>(
color: Colors.white, color: Colors.white,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
@ -723,7 +723,8 @@ class _ListAllPlansState extends State<ListAllPlans> {
MainAxisAlignment.center, MainAxisAlignment.center,
children: [ children: [
IconButton( IconButton(
icon: Icon(Icons.remove_red_eye, icon: Icon(
Icons.remove_red_eye,
color: Color(0xFF475569), color: Color(0xFF475569),
size: 18), size: 18),
onPressed: () { onPressed: () {
@ -772,6 +773,25 @@ class _ListAllPlansState extends State<ListAllPlans> {
), ),
], ],
), ),
IconButton(
icon: const Icon(
Icons.comment,
color: Color(0xFF475569),
size: 11,
),
onPressed: () {
showDialog(
context: context,
builder: (context) => CommentModal(
// planId: plan.planId,
planId: plan.planId.toString(),
layoutColorForUser:
layoutColor!,
role: "Admin"),
);
}),
],
),
), ),
]); ]);
}).toList(), }).toList(),
@ -839,6 +859,25 @@ class _ListAllPlansState extends State<ListAllPlans> {
deletePlan: deletePlan, deletePlan: deletePlan,
apiService: apiService, apiService: apiService,
), ),
IconButton(
icon: const Icon(
Icons.comment,
color: Color(0xFF475569),
size: 11,
),
onPressed: () {
showDialog(
context: context,
builder: (context) =>
CommentModal(
// planId: plan.planId,
planId: plan.planId
.toString(),
layoutColorForUser:
layoutColor!,
role: "Admin"),
);
}),
], ],
), ),
], ],

View File

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:core'; import 'dart:core';
import 'package:frontend/data/models/plan.dart'; import 'package:frontend/data/models/plan.dart';
import 'package:frontend/utils/travelAgent_remarks.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
@ -456,17 +457,24 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: const [ children: [
SizedBox(height: 20), SizedBox(height: 20),
Icon(Icons.error_outline, // Icon(Icons.error_outline,
color: Colors.redAccent, size: 60), // color: Colors.redAccent, size: 60),
SizedBox(height: 1), // SizedBox(height: 1),
Text("Oops!", // Text("Oops!",
style: TextStyle( // style: TextStyle(
fontSize: 22, // fontSize: 22,
fontWeight: FontWeight.bold, // fontWeight: FontWeight.bold,
color: Colors.redAccent)), // color: Colors.redAccent)),
SizedBox(height: 20), SizedBox(height: 20),
isDesktop
? SizedBox(
width:
MediaQuery.of(context).size.width * 5.5,
)
: SizedBox.shrink(),
Text("No Trips Pending For Your Approvals", Text("No Trips Pending For Your Approvals",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
@ -605,6 +613,9 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
))), ))),
DataCell( DataCell(
Container( Container(
padding: const EdgeInsets.all(3),
// width:
// 150, // Set your desired fixed size (equal width and height)
width: double width: double
.infinity, // Set your desired fixed size (equal width and height) .infinity, // Set your desired fixed size (equal width and height)
height: 25, height: 25,
@ -628,6 +639,8 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
), ),
DataCell( DataCell(
Row(
children: [
PopupMenuButton<int>( PopupMenuButton<int>(
color: Colors.white, color: Colors.white,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
@ -645,9 +658,10 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
MainAxisAlignment.center, MainAxisAlignment.center,
children: [ children: [
IconButton( IconButton(
icon: Icon(Icons.remove_red_eye, icon: Icon(
Icons.remove_red_eye,
color: Color(0xFF475569), color: Color(0xFF475569),
size: 14), size: 16),
onPressed: () { onPressed: () {
Navigator.pop( Navigator.pop(
context); // Close popup manually context); // Close popup manually
@ -694,6 +708,25 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
), ),
], ],
), ),
IconButton(
icon: const Icon(
Icons.comment,
color: Color(0xFF475569),
size: 11,
),
onPressed: () {
showDialog(
context: context,
builder: (context) => CommentModal(
// planId: plan.planId,
planId: plan.planId.toString(),
layoutColorForUser:
layoutColor!,
role: "Travel Agent"),
);
}),
],
),
), ),
// DataCell(Row(children: [ // DataCell(Row(children: [
@ -787,6 +820,23 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
deletePlan: deletePlan, deletePlan: deletePlan,
apiService: apiService, apiService: apiService,
), ),
IconButton(
icon: const Icon(
Icons.comment,
color: Color(0xFF475569),
size: 11,
),
onPressed: () {
showDialog(
context: context,
builder: (context) => CommentModal(
// planId: plan.planId,
planId: plan.planId.toString(),
layoutColorForUser:
layoutColor!,
role: "Travel Agent"),
);
}),
], ],
), ),

View File

@ -15,6 +15,7 @@ import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart'; import '../../services/apiService.dart';
import '../../utils/auth_utils.dart'; import '../../utils/auth_utils.dart';
import '../../utils/pagination.dart'; import '../../utils/pagination.dart';
import '../../utils/travelAgent_remarks.dart';
import '../../widgets/custom_popup.dart'; import '../../widgets/custom_popup.dart';
import '../../widgets/popup_listPlan_action.dart'; import '../../widgets/popup_listPlan_action.dart';
@ -505,14 +506,14 @@ class _ApprovalListState extends State<ApprovalList> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: const [ children: const [
Icon(Icons.error_outline, // Icon(Icons.error_outline,
color: Colors.redAccent, size: 60), // color: Colors.redAccent, size: 60),
SizedBox(height: 1), // SizedBox(height: 1),
Text("Oops!", // Text("Oops!",
style: TextStyle( // style: TextStyle(
fontSize: 22, // fontSize: 22,
fontWeight: FontWeight.bold, // fontWeight: FontWeight.bold,
color: Colors.redAccent)), // color: Colors.redAccent)),
SizedBox(height: 15), SizedBox(height: 15),
Text("No Trips Pending For Your Approvals", Text("No Trips Pending For Your Approvals",
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -670,6 +671,8 @@ class _ApprovalListState extends State<ApprovalList> {
), ),
DataCell( DataCell(
Row(
children: [
PopupMenuButton<int>( PopupMenuButton<int>(
color: Colors.white, color: Colors.white,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
@ -690,8 +693,10 @@ class _ApprovalListState extends State<ApprovalList> {
MainAxisAlignment.center, MainAxisAlignment.center,
children: [ children: [
IconButton( IconButton(
icon: Icon(Icons.remove_red_eye, icon: Icon(
color: Color(0xFF475569), Icons.remove_red_eye,
color:
Color(0xFF475569),
size: 18), size: 18),
onPressed: () { onPressed: () {
Navigator.pop( Navigator.pop(
@ -708,8 +713,10 @@ class _ApprovalListState extends State<ApprovalList> {
height: 15), height: 15),
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(context);
ApiService.viewPlanForApprover( ApiService
context, plan.planId, .viewPlanForApprover(
context,
plan.planId,
isViewMode: false, isViewMode: false,
isApprover: true); isApprover: true);
}, },
@ -740,6 +747,25 @@ class _ApprovalListState extends State<ApprovalList> {
), ),
], ],
), ),
IconButton(
icon: const Icon(
Icons.comment,
color: Color(0xFF475569),
size: 11,
),
onPressed: () {
showDialog(
context: context,
builder: (context) => CommentModal(
// planId: plan.planId,
planId: plan.planId.toString(),
layoutColorForUser:
layoutColor!,
role: "Approver"),
);
}),
],
),
), ),
// DataCell(Row(children: [ // DataCell(Row(children: [
// IconButton( // IconButton(
@ -911,6 +937,23 @@ class _ApprovalListState extends State<ApprovalList> {
), ),
], ],
), ),
IconButton(
icon: const Icon(
Icons.comment,
color: Color(0xFF475569),
size: 11,
),
onPressed: () {
showDialog(
context: context,
builder: (context) => CommentModal(
// planId: plan.planId,
planId: plan.planId.toString(),
layoutColorForUser:
layoutColor!,
role: "Approver"),
);
}),
], ],
), ),

View File

@ -1,5 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:frontend/config/apiUrl.dart'; import 'package:frontend/config/apiUrl.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@ -115,11 +116,28 @@ class _LoginWidgetState extends State<LoginWidget> {
context.go('/listPlan'); context.go('/listPlan');
} }
} else { } else {
final body = jsonDecode(response.body);
final messages = body['messages'];
String errorMessage = 'Unknown error';
if (messages is Map && messages.isNotEmpty) {
errorMessage = messages.values.first.toString();
}
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text( content: Text("Login Failed: $errorMessage"),
"Login Failed: ${jsonDecode(response.body)['message']}")), backgroundColor: Colors.red,
),
); );
// Fluttertoast.showToast(
// msg: "Login Failed: $errorMessage",
// toastLength: Toast.LENGTH_LONG,
// gravity: ToastGravity.TOP_RIGHT,
// backgroundColor: Colors.red,
// textColor: Colors.white,
// fontSize: 16.0,
// );
} }
} catch (e) { } catch (e) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(

View File

@ -369,8 +369,8 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
return ListTile( return ListTile(
title: Text( title: Text(
"${user.firstName ?? "Unknown"} ${user.lastName ?? ""}"), "${user.firstName ?? "Unknown"} ${user.lastName ?? ""}"),
subtitle: Text( // subtitle: Text(
"ID: ${userType == "user" ? user.userId : user.travellerId}"), // "ID: ${userType == "user" ? user.userId : user.travellerId}"),
onTap: () { onTap: () {
String selectedUser = String selectedUser =
"${user.firstName ?? "Unknown"} ${user.lastName ?? ""}"; "${user.firstName ?? "Unknown"} ${user.lastName ?? ""}";

View File

@ -423,6 +423,7 @@ class _ForexScreenState extends State<ForexScreen> {
// Handle field changes // Handle field changes
void _onFieldChanged() { void _onFieldChanged() {
if (_isForexDataDurationComplete()) { if (_isForexDataDurationComplete()) {
print("Calculate 1");
CalculateDuration(); CalculateDuration();
} }
@ -432,16 +433,24 @@ class _ForexScreenState extends State<ForexScreen> {
} }
void CalculateDuration() { void CalculateDuration() {
print("Calculate 2");
final data = getForexData; final data = getForexData;
final startDateString = data["start_date"]; final startDateString = data["start_date"];
final endDateString = data["end_date"]; final endDateString = data["end_date"];
if (startDateString == null || endDateString == null) return; if (startDateString == null || endDateString == null) return;
print(
"Calculate 3 - StarrtDAte: $startDateString --EndDate: $endDateString");
try { try {
// Parse the dates from string // Parse the dates from string
final startDate = DateFormat('d/M/yyyy').parse(startDateString); // final startDate = DateFormat('dd/mm/yyyy').parse(startDateString);
final endDate = DateFormat('d/M/yyyy').parse(endDateString); // final endDate = DateFormat('dd/mm/yyyy').parse(endDateString);
final startDate = DateFormat('dd-MM-yyyy').parse(startDateString);
final endDate = DateFormat('dd-MM-yyyy').parse(endDateString);
print("Calculate 4");
// Calculate difference // Calculate difference
final durationInDays = final durationInDays =
@ -450,8 +459,10 @@ class _ForexScreenState extends State<ForexScreen> {
// You can now use durationInDays however you want: // You can now use durationInDays however you want:
print("Duration: $durationInDays days"); print("Duration: $durationInDays days");
// Optionally, you might want to update some data: // Convert int to String
selectedDuration = durationInDays as String?; setState(() {
selectedDuration = durationInDays.toString();
});
} catch (e) { } catch (e) {
print('Error parsing dates: $e'); print('Error parsing dates: $e');
} }
@ -501,8 +512,23 @@ class _ForexScreenState extends State<ForexScreen> {
(quotedAmount * 15) ~/ 100; // Calculate 15% (integer division) (quotedAmount * 15) ~/ 100; // Calculate 15% (integer division)
remainingAmount = quotedAmount - fifteenPercent; // Subtract from total remainingAmount = quotedAmount - fifteenPercent; // Subtract from total
textControllers["_cash"]?.text = fifteenPercent.toString(); // Only set text if the field is empty (user hasn't typed)
textControllers["_card"]?.text = remainingAmount.toString(); if (textControllers["_cash"] != null &&
textControllers["_cash"]!.text.trim().isEmpty) {
textControllers["_cash"]!.text = fifteenPercent.toString();
} else {
print("_cash already has user input, not overwriting");
}
if (textControllers["_card"] != null &&
textControllers["_card"]!.text.trim().isEmpty) {
textControllers["_card"]!.text = remainingAmount.toString();
} else {
print("_card already has user input, not overwriting");
}
// textControllers["_cash"]?.text = fifteenPercent.toString();
// textControllers["_card"]?.text = remainingAmount.toString();
print("15% Amount: $fifteenPercent"); print("15% Amount: $fifteenPercent");
print("Remaining Amount: $remainingAmount"); print("Remaining Amount: $remainingAmount");
} else { } else {
@ -518,11 +544,19 @@ class _ForexScreenState extends State<ForexScreen> {
int? qouteAmount = int.tryParse(selectedQuotedAmount ?? "0"); int? qouteAmount = int.tryParse(selectedQuotedAmount ?? "0");
int? calculateAmnt = cashAmount! + enteredAmount!; int? calculateAmnt = cashAmount! + enteredAmount!;
int checkValidAmount = cashAmount + enteredAmount;
print(
"checkValidAmount - $checkValidAmount - $enteredAmount - $cashAmount");
print( print(
"CAsh - $cashAmount- CaRd- $enteredAmount - $calculateAmnt - selectedQuotedAmount - $qouteAmount"); "CAsh - $cashAmount- CaRd- $enteredAmount - $calculateAmnt - selectedQuotedAmount - $qouteAmount");
if (enteredAmount == null || calculateAmnt > qouteAmount!) { if (enteredAmount == null || calculateAmnt > qouteAmount!) {
errorMessages["deposit_on_card"] = "Amount cannot exceed $qouteAmount"; errorMessages["deposit_on_card"] = "Amount cannot exceed $qouteAmount";
} else if (checkValidAmount < qouteAmount) {
errorMessages["deposit_on_card"] =
"Enter Valid Amount"; // Clear error if valid
} else { } else {
errorMessages["deposit_on_card"] = ""; // Clear error if valid errorMessages["deposit_on_card"] = ""; // Clear error if valid
} }
@ -532,18 +566,29 @@ class _ForexScreenState extends State<ForexScreen> {
} }
void _validateCashAmount(String value) { void _validateCashAmount(String value) {
errorMessages["deposit_on_card"] = " ";
print("_validateCashAmount - $value - $fifteenPercent"); print("_validateCashAmount - $value - $fifteenPercent");
int? enteredAmount = int.tryParse(value); int? enteredAmount = int.tryParse(value);
int? quotedAmount = int.tryParse(selectedQuotedAmount!); int? quotedAmount = int.tryParse(selectedQuotedAmount!);
int? difference = quotedAmount! - enteredAmount!; int? difference = quotedAmount! - enteredAmount!;
int? cashAmount = int.tryParse(textControllers["_cash"]!.text ?? "0");
int? cardAmount = int.tryParse(textControllers["_card"]!.text ?? "0");
int checkValidAmount = cardAmount! + enteredAmount;
print(
"checkValidAmountCash - $checkValidAmount -cash- $enteredAmount -Card - $cardAmount - quotedAmount- $quotedAmount");
print("Difference - $difference"); print("Difference - $difference");
print('CardAmount - $cardAmount');
textControllers["_card"]?.text = difference.toString(); textControllers["_card"]?.text = difference.toString();
if (enteredAmount == null || enteredAmount > fifteenPercent) { if (enteredAmount == null || enteredAmount > fifteenPercent) {
errorMessages["deposit_on_cash"] = "Amount cannot exceed $fifteenPercent"; errorMessages["deposit_on_cash"] = "Amount cannot exceed $fifteenPercent";
} else if (checkValidAmount == quotedAmount) {
errorMessages["deposit_on_card"] = " "; // Clear error if valid
} else { } else {
errorMessages["deposit_on_cash"] = ""; // Clear error if valid errorMessages["deposit_on_cash"] = ""; // Clear error if valid
} }
@ -682,8 +727,8 @@ class _ForexScreenState extends State<ForexScreen> {
if (flightFirstTripDateNotifier.value != null) { if (flightFirstTripDateNotifier.value != null) {
try { try {
initialDate = DateTime.parse(flightFirstTripDateNotifier.value!); initialDate = DateTime.parse(flightFirstTripDateNotifier.value!);
textControllers["_forexStartDate"]?.text = // textControllers["_forexStartDate"]?.text =
DateFormat('yyyy-MM-dd').format(initialDate); // DateFormat('yyyy-MM-dd').format(initialDate);
textControllers["_forexStartDate"]?.text = textControllers["_forexStartDate"]?.text =
DateFormat('dd-MM-yyyy').format(initialDate); DateFormat('dd-MM-yyyy').format(initialDate);
} catch (e) { } catch (e) {
@ -938,7 +983,7 @@ class _ForexScreenState extends State<ForexScreen> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Durartion", "Duration",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,

View File

@ -4,6 +4,8 @@ import 'package:http/http.dart' as http;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import '../../services/apiService.dart';
class ForexListWidget extends StatelessWidget { class ForexListWidget extends StatelessWidget {
final List<Map<String, dynamic>> forexList; final List<Map<String, dynamic>> forexList;
final Function(bool, Map<String, dynamic>, String) onOpen; final Function(bool, Map<String, dynamic>, String) onOpen;
@ -27,6 +29,7 @@ class ForexListWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDesktop = MediaQuery.of(context).size.width > 1024; final isDesktop = MediaQuery.of(context).size.width > 1024;
final ApiService apiService = ApiService();
return Padding( return Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
@ -133,7 +136,11 @@ class ForexListWidget extends StatelessWidget {
); );
} }
Widget _buildData(BuildContext context, bool isDesktop) { Widget _buildData(
BuildContext context,
bool isDesktop,
) {
final ApiService apiService = ApiService();
List<Map<String, dynamic>> filteredList = List<Map<String, dynamic>> filteredList =
forexList.where((item) => item["is_active"] == "1").toList(); forexList.where((item) => item["is_active"] == "1").toList();
print("filteredList- $filteredList"); print("filteredList- $filteredList");
@ -227,6 +234,23 @@ class ForexListWidget extends StatelessWidget {
child: Image.asset('assets/images/IconsImg/delete.png', child: Image.asset('assets/images/IconsImg/delete.png',
width: 20, height: 15), width: 20, height: 15),
), ),
item['forex_id'] != null
? IconButton(
icon: Icon(Icons.download,
color: Color(0xFF114D8B), size: 18),
onPressed: () {
print("PDF- ${item['forex_id']}");
final forexIdString = item['forex_id'];
final id = int.tryParse(forexIdString ?? '');
if (id != null) {
apiService.getForexPdfDownload(id);
} else {
print("Invalid forex_id: $forexIdString");
}
},
)
: SizedBox.shrink(), // or Container()
], ],
), ),
Divider( Divider(

View File

@ -15,6 +15,7 @@ import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart'; import '../../services/apiService.dart';
import '../../utils/auth_utils.dart'; import '../../utils/auth_utils.dart';
import '../../utils/pagination.dart'; import '../../utils/pagination.dart';
import '../../utils/travelAgent_remarks.dart';
import '../../widgets/custom_popup.dart'; import '../../widgets/custom_popup.dart';
import '../../widgets/popup_listPlan_action.dart'; import '../../widgets/popup_listPlan_action.dart';
@ -557,14 +558,14 @@ class _ListPlansState extends State<ListPlans> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: const [ children: const [
Icon(Icons.error_outline, // Icon(Icons.error_outline,
color: Colors.redAccent, size: 60), // color: Colors.redAccent, size: 60),
SizedBox(height: 1), // SizedBox(height: 1),
Text("Oops!", // Text("Oops!",
style: TextStyle( // style: TextStyle(
fontSize: 22, // fontSize: 22,
fontWeight: FontWeight.bold, // fontWeight: FontWeight.bold,
color: Colors.redAccent)), // color: Colors.redAccent)),
SizedBox(height: 15), SizedBox(height: 15),
Text("No Trips", Text("No Trips",
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -743,6 +744,8 @@ class _ListPlansState extends State<ListPlans> {
), ),
), ),
DataCell( DataCell(
Row(
children: [
PopupMenuButton<int>( PopupMenuButton<int>(
color: Colors.white, color: Colors.white,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
@ -760,7 +763,8 @@ class _ListPlansState extends State<ListPlans> {
MainAxisAlignment.center, MainAxisAlignment.center,
children: [ children: [
IconButton( IconButton(
icon: Icon(Icons.remove_red_eye, icon: Icon(
Icons.remove_red_eye,
color: Color(0xFF475569), color: Color(0xFF475569),
size: 18), size: 18),
onPressed: () { onPressed: () {
@ -809,6 +813,25 @@ class _ListPlansState extends State<ListPlans> {
), ),
], ],
), ),
IconButton(
icon: const Icon(
Icons.comment,
color: Color(0xFF475569),
size: 11,
),
onPressed: () {
showDialog(
context: context,
builder: (context) => CommentModal(
// planId: plan.planId,
planId: plan.planId.toString(),
layoutColorForUser:
layoutColor!,
role: "User"),
);
}),
],
),
), ),
// DataCell(Row(children: [ // DataCell(Row(children: [
@ -910,6 +933,23 @@ class _ListPlansState extends State<ListPlans> {
deletePlan: deletePlan, deletePlan: deletePlan,
apiService: apiService, apiService: apiService,
), ),
IconButton(
icon: const Icon(
Icons.comment,
color: Color(0xFF475569),
size: 11,
),
onPressed: () {
showDialog(
context: context,
builder: (context) => CommentModal(
// planId: plan.planId,
planId: plan.planId.toString(),
layoutColorForUser:
layoutColor!,
role: "User"),
);
}),
], ],
), ),

View File

@ -41,6 +41,10 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
final GlobalKey<PersonalDetailsState> personalDetailsKey = final GlobalKey<PersonalDetailsState> personalDetailsKey =
GlobalKey<PersonalDetailsState>(); GlobalKey<PersonalDetailsState>();
final GlobalKey<TravellerDetailsState> travellerDetailsKey =
GlobalKey<TravellerDetailsState>();
Map<String, dynamic>? travelDetailsData;
late TabController _tabController; late TabController _tabController;
@ -124,6 +128,8 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
Color? layoutColor; Color? layoutColor;
Color? bodyColor; Color? bodyColor;
bool setSelectesUserType = false;
Map<String, dynamic> get userDetials { Map<String, dynamic> get userDetials {
Map<String, dynamic> data = { Map<String, dynamic> data = {
"first_name": controllers["Fname"]?.text, "first_name": controllers["Fname"]?.text,
@ -159,11 +165,18 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
"created_by": userId, "created_by": userId,
"is_active": "1", "is_active": "1",
"org_id": orgId, "org_id": orgId,
"travel_details": travelDetailsData ?? {},
// "passport_fileData": passportFile, // "passport_fileData": passportFile,
}; };
return data; return data;
} }
void handleUserTypeChange(bool isTravelAgent) {
setState(() {
setSelectesUserType = isTravelAgent;
});
}
Future<void> updateData() async { Future<void> updateData() async {
// Ensure apiselectedUser is not null before printing // Ensure apiselectedUser is not null before printing
if (apiselectedUser != null) { if (apiselectedUser != null) {
@ -474,8 +487,14 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
print("USR Detail Submit"); print("USR Detail Submit");
printFormData(); printFormData();
travelDetailsData = travellerDetailsKey.currentState?.travel_Detials;
print("TRAVEL DETAILS FROM CHILD: $travelDetailsData");
Map<String, dynamic> data = userDetials; Map<String, dynamic> data = userDetials;
print("USERDETAILS : $data");
if (!isValidData(data)) { if (!isValidData(data)) {
print("USERDETAILS : $userDetials"); print("USERDETAILS : $userDetials");
print("Validation Failed: Required fields are missing."); print("Validation Failed: Required fields are missing.");
@ -487,7 +506,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
final tabs = { final tabs = {
"personal": "Personal Details", "personal": "Personal Details",
"office": "Office Details", "office": "Office Details",
// "travel": "Travel Details", "travel": "Travel Details",
}; };
final tabKeys = tabs.keys.toList(); // ["personal", "office", "travel"] final tabKeys = tabs.keys.toList(); // ["personal", "office", "travel"]
@ -511,8 +530,16 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
print("USR Detail Submit"); print("USR Detail Submit");
printFormData(); printFormData();
travelDetailsData = travellerDetailsKey.currentState?.travel_Detials;
print("TRAVEL DETAILS FROM CHILD: $travelDetailsData");
Map<String, dynamic> data = userDetials; Map<String, dynamic> data = userDetials;
print("USERDETAILS : $data");
// Map<String, dynamic> data = userDetials;
if (!isValidData(data)) { if (!isValidData(data)) {
print("USERDETAILS : $userDetials"); print("USERDETAILS : $userDetials");
print("Validation Failed: Required fields are missing."); print("Validation Failed: Required fields are missing.");
@ -760,14 +787,12 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
height: isDesktop height: isDesktop
? MediaQuery.of(context).size.height * 0.98 ? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height, : MediaQuery.of(context).size.height,
child: SingleChildScrollView(
child: Padding( child: Padding(
padding: EdgeInsets.all(0.0), padding: EdgeInsets.all(0.0),
child: _buildUserDetails(isDesktop), child: _buildUserDetails(isDesktop),
), ),
), ),
), ),
),
Container( Container(
color: Colors.white, color: Colors.white,
child: Padding( child: Padding(
@ -775,13 +800,15 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
child: isDesktop child: isDesktop
? Row( ? Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: selectedTab == "office" children: (selectedTab == "travel" ||
setSelectesUserType == true)
? _buildSubmit(isDesktop, layoutColor!) ? _buildSubmit(isDesktop, layoutColor!)
: _buildNext(isDesktop, layoutColor!), : _buildNext(isDesktop, layoutColor!),
) )
: Row( : Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: selectedTab == "office" children: (selectedTab == "travel" ||
setSelectesUserType == true)
? _buildSubmit(isDesktop, layoutColor!) ? _buildSubmit(isDesktop, layoutColor!)
: _buildNext(isDesktop, layoutColor!), : _buildNext(isDesktop, layoutColor!),
)), )),
@ -819,7 +846,8 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
), ),
Container( Container(
// color: Colors.yellow.shade50, // color: Colors.yellow.shade50,
height: MediaQuery.of(context).size.height * 0.67,
height: MediaQuery.of(context).size.height * 0.64,
child: Row( child: Row(
children: [ children: [
Expanded(child: buildTabContents(isDesktop, isViewMode)), Expanded(child: buildTabContents(isDesktop, isViewMode)),
@ -846,6 +874,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
selectedCountry: selectedCountry, selectedCountry: selectedCountry,
selectedRole: selectedRole, selectedRole: selectedRole,
apiselectedUser: isapiselectedUser, apiselectedUser: isapiselectedUser,
onUserTypeChanged: handleUserTypeChange,
onGenderChanged: (gender) { onGenderChanged: (gender) {
setState(() { setState(() {
selectedGender = gender; selectedGender = gender;
@ -900,7 +929,13 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
}, },
); );
case "travel": case "travel":
return TravellerDetails(); return TravellerDetails(
key: travellerDetailsKey,
isDesktop: isDesktop,
isViewMode: isViewMode,
controllers: controllers,
errorMessages: errorMessages,
);
default: default:
return PersonalDetails( return PersonalDetails(
personalDetailsKey: personalDetailsKey, personalDetailsKey: personalDetailsKey,
@ -910,7 +945,9 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
errorMessages: errorMessages, errorMessages: errorMessages,
selectedGender: selectedGender, selectedGender: selectedGender,
selectedCountry: selectedCountry, selectedCountry: selectedCountry,
apiselectedUser: isapiselectedUser); apiselectedUser: isapiselectedUser,
onUserTypeChanged: handleUserTypeChange,
);
} }
} }
@ -918,7 +955,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
final tabs = { final tabs = {
"personal": "Personal Details", "personal": "Personal Details",
"office": "Office Details", "office": "Office Details",
// "travel": "Travel Details", "travel": "Travel Details",
}; };
return Row( return Row(

View File

@ -4,6 +4,7 @@ import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import '../../../services/apiService.dart'; import '../../../services/apiService.dart';
@ -294,11 +295,11 @@ class _OfficeDetailsState extends State<OfficeDetails> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Employee Code", Text(
style: TextStyle( "Employee Code",
fontSize: 12, style: GoogleFonts.poppins(
fontWeight: FontWeight.w200, fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
color: Colors.black)), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldUserWrapper( CustomTextFieldUserWrapper(
isFocused: false, isFocused: false,
@ -306,12 +307,13 @@ class _OfficeDetailsState extends State<OfficeDetails> {
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: TextField( child: TextField(
style: TextStyle(fontSize: 12, color: Colors.black), style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["employeeCode"], controller: widget.controllers["employeeCode"],
enabled: !widget.isViewMode, enabled: !widget.isViewMode,
decoration: InputDecoration( decoration: InputDecoration(
labelText: "Employee Code", labelText: "Employee Code",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey), labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
@ -327,11 +329,11 @@ class _OfficeDetailsState extends State<OfficeDetails> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Department", Text(
style: TextStyle( "Department",
fontSize: 12, style: GoogleFonts.poppins(
fontWeight: FontWeight.w200, fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
color: Colors.black)), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldUserWrapper( CustomTextFieldUserWrapper(
isFocused: false, // Dropdown doesn't use focus isFocused: false, // Dropdown doesn't use focus
@ -340,7 +342,7 @@ class _OfficeDetailsState extends State<OfficeDetails> {
height: 45, // Set appropriate height height: 45, // Set appropriate height
child: DropdownButtonFormField<String>( child: DropdownButtonFormField<String>(
value: widget.isViewMode ? null : selectedDepartment, value: widget.isViewMode ? null : selectedDepartment,
style: TextStyle(fontSize: 12, color: Colors.black), style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
decoration: InputDecoration( decoration: InputDecoration(
border: InputBorder.none, border: InputBorder.none,
contentPadding: contentPadding:
@ -363,7 +365,7 @@ class _OfficeDetailsState extends State<OfficeDetails> {
hint: Text("Select"), hint: Text("Select"),
disabledHint: Text( disabledHint: Text(
selectedDepartment ?? "Select Department", selectedDepartment ?? "Select Department",
style: TextStyle(fontSize: 12, color: Colors.black), style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
), ),
), ),
), ),
@ -376,11 +378,11 @@ class _OfficeDetailsState extends State<OfficeDetails> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Group", Text(
style: TextStyle( "Group",
fontSize: 12, style: GoogleFonts.poppins(
fontWeight: FontWeight.w200, fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
color: Colors.black)), ),
SizedBox(height: 5), SizedBox(height: 5),
// CustomTextFieldUserWrapper( // CustomTextFieldUserWrapper(
// isFocused: false, // isFocused: false,
@ -474,7 +476,9 @@ class _OfficeDetailsState extends State<OfficeDetails> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
selectedItem ?? "Select", selectedItem ?? "Select",
style: TextStyle(fontSize: 12), style: GoogleFonts.poppins(
fontSize: 12,
),
), ),
), ),
@ -512,11 +516,13 @@ class _OfficeDetailsState extends State<OfficeDetails> {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("First Approver", Text(
style: TextStyle( "First Approver",
style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w200, fontWeight: FontWeight.w400,
color: Colors.black)), color: Colors.black),
),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldUserWrapper( CustomTextFieldUserWrapper(
isFocused: false, isFocused: false,
@ -561,7 +567,9 @@ class _OfficeDetailsState extends State<OfficeDetails> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
selectedItem ?? "Select", selectedItem ?? "Select",
style: TextStyle(fontSize: 12), style: GoogleFonts.poppins(
fontSize: 12,
),
), ),
), ),
// onChanged: (String? newValue) { // onChanged: (String? newValue) {
@ -620,11 +628,13 @@ class _OfficeDetailsState extends State<OfficeDetails> {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Second Approver", Text(
style: TextStyle( "Second Approver",
style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w200, fontWeight: FontWeight.w400,
color: Colors.black)), color: Colors.black),
),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldUserWrapper( CustomTextFieldUserWrapper(
isFocused: false, isFocused: false,
@ -669,7 +679,9 @@ class _OfficeDetailsState extends State<OfficeDetails> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
selectedItem ?? "Select", selectedItem ?? "Select",
style: TextStyle(fontSize: 12), style: GoogleFonts.poppins(
fontSize: 12,
),
), ),
), ),
@ -727,11 +739,13 @@ class _OfficeDetailsState extends State<OfficeDetails> {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Third Approver", Text(
style: TextStyle( "Third Approver",
style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w200, fontWeight: FontWeight.w400,
color: Colors.black)), color: Colors.black),
),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldUserWrapper( CustomTextFieldUserWrapper(
isFocused: false, isFocused: false,
@ -776,7 +790,9 @@ class _OfficeDetailsState extends State<OfficeDetails> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
selectedItem ?? "Select", selectedItem ?? "Select",
style: TextStyle(fontSize: 12), style: GoogleFonts.poppins(
fontSize: 12,
),
), ),
), ),
onChanged: (String? newValue) { onChanged: (String? newValue) {

View File

@ -1,12 +1,15 @@
import 'dart:convert';
import 'dart:io' as html; import 'dart:io' as html;
import 'package:dropdown_search/dropdown_search.dart'; import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import '../../../services/apiService.dart'; import '../../../services/apiService.dart';
import '../../../utils/auth_utils.dart';
import '../../../widgets/custom_user_form.dart'; import '../../../widgets/custom_user_form.dart';
class PersonalDetails extends StatefulWidget { class PersonalDetails extends StatefulWidget {
@ -20,14 +23,15 @@ class PersonalDetails extends StatefulWidget {
final ValueChanged<String?>? onGenderChanged; final ValueChanged<String?>? onGenderChanged;
final ValueChanged<String?>? onCountryChanged; final ValueChanged<String?>? onCountryChanged;
final ValueChanged<String?>? onRoleChanged; final ValueChanged<String?>? onRoleChanged;
final void Function(bool)? onUserTypeChanged;
final String? selectedGender; final String? selectedGender;
final String? selectedCountry; final String? selectedCountry;
final String? selectedRole; final String? selectedRole;
// const PersonalDetails(this.isDesktop, this.isViewMode, {super.key},); // const PersonalDetails(this.isDesktop, this.isViewMode, {super.key},);
const PersonalDetails({ const PersonalDetails(
Key? key, {Key? key,
this.selectedGender, this.selectedGender,
this.selectedCountry, this.selectedCountry,
this.selectedRole, this.selectedRole,
@ -40,7 +44,8 @@ class PersonalDetails extends StatefulWidget {
this.onGenderChanged, this.onGenderChanged,
this.onCountryChanged, this.onCountryChanged,
this.onRoleChanged, this.onRoleChanged,
}) : super(key: key); this.onUserTypeChanged})
: super(key: key);
@override @override
PersonalDetailsState createState() => PersonalDetailsState(); PersonalDetailsState createState() => PersonalDetailsState();
@ -53,11 +58,16 @@ class PersonalDetailsState extends State<PersonalDetails> {
late List<String> countryCodes; // List of country codes late List<String> countryCodes; // List of country codes
late List<dynamic>? apiCountryData; late List<dynamic>? apiCountryData;
late List<dynamic>? apiRoleData; late List<dynamic>? apiRoleData;
List<dynamic>? apiAllServices;
Map<String, dynamic>? selectedOrg;
List<Map<String, dynamic>> selectedServiceIds = [];
String? get selectedGenderValue => selectedGender; String? get selectedGenderValue => selectedGender;
String? get selectedCountryValue => selectedCountry; String? get selectedCountryValue => selectedCountry;
String? get selectedRoleValue => selectedRole; String? get selectedRoleValue => selectedRole;
late bool isTravelAgent = false;
String? selectedTab; String? selectedTab;
String? selectedCountry; String? selectedCountry;
@ -121,6 +131,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
selectedRole = widget.selectedRole; selectedRole = widget.selectedRole;
fetchRoles(); fetchRoles();
fetchCountries(); fetchCountries();
WidgetsBinding.instance.addPostFrameCallback((_) {
loadAllServices();
getOrganizationData();
});
} }
void _clearError(String field) { void _clearError(String field) {
@ -158,6 +173,47 @@ class PersonalDetailsState extends State<PersonalDetails> {
} }
} }
Future<void> getOrganizationData() async {
try {
print("getUpdatedServices");
final result = await apiService.fetchOrganization();
print("UUPdatedServices - $result");
setState(() {
final servicesRaw = selectedOrg?['services_ids'];
List<dynamic> services;
if (servicesRaw is String) {
try {
services = jsonDecode(servicesRaw);
} catch (e) {
print('❌ Failed to decode services_ids: $e');
services = [];
}
} else if (servicesRaw is List) {
services = servicesRaw;
} else {
services = [];
}
selectedServiceIds = services.map<Map<String, dynamic>>((item) {
// force cast or copy to a regular map
final map = Map<String, dynamic>.from(item);
return {
"service_id": map['service_id'].toString(),
};
}).toList();
});
// orgId = await getOrgId();
print("selectedOrg - $selectedOrg");
} catch (e) {
print('Error fetching updatedServices list: $e');
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
@ -169,6 +225,8 @@ class PersonalDetailsState extends State<PersonalDetails> {
), ),
), ),
), ),
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -188,11 +246,194 @@ class PersonalDetailsState extends State<PersonalDetails> {
height: 10, height: 10,
), ),
_buildForthRow(widget.isDesktop), _buildForthRow(widget.isDesktop),
SizedBox(
height: 10,
),
if (selectedRole == "5") _buildServices(widget.isDesktop)
], ],
), ),
),
); );
} }
Future<void> loadAllServices() async {
try {
final result = await apiService.fetchAllServices();
setState(() {
apiAllServices = result;
});
print("Fetched services: $apiAllServices");
} catch (e) {
print('Error fetching role list: $e');
}
}
Widget _buildServices(bool isDesktop) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Services",
style: GoogleFonts.poppins(
fontSize: 14, fontWeight: FontWeight.w400, color: Colors.black),
// style: TextStyle(
// fontFamily: "Archivo",
// fontSize: 14,
// fontWeight: FontWeight.w600,
// color: Color(0xFF212121)),
),
SizedBox(
height: 10,
),
Container(
decoration: BoxDecoration(
border: Border.all(color: Color(0xFFF4F4FB)),
borderRadius: BorderRadius.circular(1),
// color: bodyColor,
// color: Color(0xFFF5F5F5),
color: Colors.white),
padding: EdgeInsets.only(left: 5, right: 5, top: 15, bottom: 5),
child: isDesktop
? Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// mainAxisSize: MainAxisSize.min,
children: _buildOptions(),
)
: Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: _buildOptions(),
),
),
),
),
],
);
}
List<Widget> _buildOptions() {
if (apiAllServices == null) return [];
return apiAllServices!.map((service) {
return Padding(
padding: const EdgeInsets.only(right: 20.0),
child: _buildOption(service),
);
}).toList();
}
Widget _buildOption(Map<String, dynamic> service) {
String name = service['name'];
String iconUrl = service['icon']; // Can be empty string
IconData fallbackIcon = _getLocalIconForService(name);
String serviceId = service['service_id'].toString();
// bool isSelected = selectedServiceIds.contains(serviceId);
bool isSelected =
selectedServiceIds.any((item) => item["service_id"] == serviceId);
return GestureDetector(
onTap: () {
setState(() {
String serviceId = service['service_id'].toString();
// Check if already selected
int existingIndex = selectedServiceIds
.indexWhere((item) => item["service_id"] == serviceId);
if (existingIndex != -1) {
selectedServiceIds.removeAt(existingIndex);
} else {
selectedServiceIds.add({"service_id": serviceId});
}
});
},
child: Row(children: [
iconUrl.isNotEmpty
? Image.network(
iconUrl,
width: 18,
height: 18,
errorBuilder: (context, error, stackTrace) {
return Icon(fallbackIcon,
size: 18,
color: isSelected == name
? Color(0xFF114D8B)
: Color(0xFF475569));
},
)
: Icon(fallbackIcon,
size: 18,
color:
isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569)),
SizedBox(width: 5),
SizedBox(width: 5),
Text(
name,
style: GoogleFonts.poppins(
fontSize: 12,
color: isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569),
fontWeight:
isSelected == name ? FontWeight.bold : FontWeight.w500),
// style: TextStyle(
// fontSize: 13,
// color: isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569),
// fontFamily: "Archivo",
// fontWeight:
// isSelected == name ? FontWeight.bold : FontWeight.w500),
// fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)),
),
SizedBox(width: 5),
// if (selectedListOption == title && widget.isViewMode == false)
Container(
height: 15,
width: 15,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: isSelected ? Colors.green : Colors.grey, width: 1),
),
child: Icon(
Icons.check_circle,
size: 10,
color: isSelected ? Colors.green : Colors.grey,
// color: Colors.grey,
)),
]),
);
}
IconData _getLocalIconForService(String name) {
switch (name.toLowerCase()) {
case 'flight':
return Icons.flight_takeoff_outlined;
case 'train':
return Icons.train_outlined;
case 'bus':
return Icons.bus_alert_outlined;
case 'taxi':
return Icons.local_taxi_outlined;
case 'accomodation':
return Icons.local_hotel_outlined;
case 'forex':
return Icons.attach_money_outlined;
case 'insurance':
return Icons.list_alt_outlined;
case 'visa':
return Icons.badge_outlined;
case 'miscellaneous':
return Icons.card_giftcard_outlined;
default:
return Icons.circle_notifications;
}
}
Widget _buildFirstRow(bool isDesktop) { Widget _buildFirstRow(bool isDesktop) {
return Container( return Container(
color: Colors.white, color: Colors.white,
@ -227,11 +468,6 @@ class PersonalDetailsState extends State<PersonalDetails> {
} }
Widget _buildSecondRow(bool isDesktop) { Widget _buildSecondRow(bool isDesktop) {
List<Widget> fields = [
buildEmailField(),
buildMobileField(),
buildAlternateMobileField(),
];
return Container( return Container(
color: Colors.white, color: Colors.white,
child: widget.isDesktop child: widget.isDesktop
@ -318,11 +554,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("First Name", Text(
style: TextStyle( "First Name",
fontSize: 12, style: GoogleFonts.poppins(
fontWeight: FontWeight.w200, fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
color: Colors.black)), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldUserWrapper( CustomTextFieldUserWrapper(
isFocused: false, isFocused: false,
@ -330,7 +566,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: TextField( child: TextField(
style: TextStyle(fontSize: 12, color: Colors.black), style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["Fname"], controller: widget.controllers["Fname"],
enabled: !widget.isViewMode, enabled: !widget.isViewMode,
onChanged: (value) { onChanged: (value) {
@ -338,7 +574,10 @@ class PersonalDetailsState extends State<PersonalDetails> {
}, },
decoration: InputDecoration( decoration: InputDecoration(
labelText: "FirstName", labelText: "FirstName",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey), labelStyle: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
@ -404,11 +643,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Gender", Text(
style: TextStyle( "Gender",
fontSize: 12, style: GoogleFonts.poppins(
fontWeight: FontWeight.w200, fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
color: Colors.black)), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldUserWrapper( CustomTextFieldUserWrapper(
width: widget.isDesktop width: widget.isDesktop
@ -435,18 +674,18 @@ class PersonalDetailsState extends State<PersonalDetails> {
border: InputBorder.none, border: InputBorder.none,
enabled: !widget.isViewMode, // Disables input when in view mode enabled: !widget.isViewMode, // Disables input when in view mode
), ),
style: TextStyle(fontSize: 12, color: Colors.black), style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
items: [ items: [
DropdownMenuItem(value: "Male", child: Text("Male")), DropdownMenuItem(value: "Male", child: Text("Male")),
DropdownMenuItem(value: "Female", child: Text("Female")), DropdownMenuItem(value: "Female", child: Text("Female")),
], ],
hint: Text( hint: Text(
selectedGender ?? "Select Gender", selectedGender ?? "Select Gender",
style: TextStyle(fontSize: 12, color: Colors.black), style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
), ),
disabledHint: Text( disabledHint: Text(
selectedGender ?? "Select Gender", selectedGender ?? "Select Gender",
style: TextStyle(fontSize: 12, color: Colors.black), style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
), ),
), ),
@ -522,11 +761,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Date of Birth", Text(
style: TextStyle( "Date of Birth",
fontSize: 12, style: GoogleFonts.poppins(
fontWeight: FontWeight.w200, fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
color: Colors.black)), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldUserWrapper( CustomTextFieldUserWrapper(
width: widget.isDesktop width: widget.isDesktop
@ -550,10 +789,13 @@ class PersonalDetailsState extends State<PersonalDetails> {
child: AbsorbPointer( child: AbsorbPointer(
child: TextField( child: TextField(
controller: widget.controllers["dob"], controller: widget.controllers["dob"],
style: const TextStyle(fontSize: 12, color: Colors.black), style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
decoration: const InputDecoration( decoration: InputDecoration(
labelText: "Select Date", labelText: "Select Date",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey), labelStyle: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
@ -573,11 +815,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Email", Text(
style: TextStyle( "Email",
fontSize: 12, style: GoogleFonts.poppins(
fontWeight: FontWeight.w200, fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
color: Colors.black)), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldUserWrapper( CustomTextFieldUserWrapper(
isFocused: false, isFocused: false,
@ -585,7 +827,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: TextField( child: TextField(
style: TextStyle(fontSize: 12, color: Colors.black), style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["email"], controller: widget.controllers["email"],
onChanged: (value) { onChanged: (value) {
_clearError("email"); _clearError("email");
@ -593,7 +835,8 @@ class PersonalDetailsState extends State<PersonalDetails> {
enabled: !widget.isViewMode, enabled: !widget.isViewMode,
decoration: InputDecoration( decoration: InputDecoration(
labelText: "Email", labelText: "Email",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey), labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
@ -616,11 +859,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Mobile Number", Text(
style: TextStyle( "Mobile Number",
fontSize: 12, style: GoogleFonts.poppins(
fontWeight: FontWeight.w200, fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
color: Colors.black)), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldUserWrapper( CustomTextFieldUserWrapper(
isFocused: false, isFocused: false,
@ -628,7 +871,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: TextField( child: TextField(
style: TextStyle(fontSize: 12, color: Colors.black), style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["mobileNumber"], controller: widget.controllers["mobileNumber"],
enabled: !widget.isViewMode, enabled: !widget.isViewMode,
onChanged: (value) { onChanged: (value) {
@ -641,7 +884,8 @@ class PersonalDetailsState extends State<PersonalDetails> {
], ],
decoration: InputDecoration( decoration: InputDecoration(
labelText: "Mobile Number", labelText: "Mobile Number",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey), labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
@ -665,9 +909,9 @@ class PersonalDetailsState extends State<PersonalDetails> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Alternate Mobile Number", Text("Alternate Mobile Number",
style: TextStyle( style: GoogleFonts.poppins(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w200, fontWeight: FontWeight.w400,
color: Colors.black)), color: Colors.black)),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldUserWrapper( CustomTextFieldUserWrapper(
@ -676,7 +920,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: TextField( child: TextField(
style: TextStyle(fontSize: 12, color: Colors.black), style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["alternateMobile"], controller: widget.controllers["alternateMobile"],
enabled: !widget.isViewMode, enabled: !widget.isViewMode,
keyboardType: TextInputType.numberWithOptions(decimal: true), keyboardType: TextInputType.numberWithOptions(decimal: true),
@ -686,7 +930,8 @@ class PersonalDetailsState extends State<PersonalDetails> {
], ],
decoration: InputDecoration( decoration: InputDecoration(
labelText: "Alternate Number", labelText: "Alternate Number",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey), labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
@ -726,11 +971,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Country", Text(
style: TextStyle( "Country",
fontSize: 12, style: GoogleFonts.poppins(
fontWeight: FontWeight.w200, fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
color: Colors.black)), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldUserWrapper( CustomTextFieldUserWrapper(
isFocused: false, isFocused: false,
@ -765,7 +1010,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
selectedItem ?? "Select Country", selectedItem ?? "Select Country",
style: TextStyle(fontSize: 12, color: Colors.black), style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
), ),
), ),
// onChanged: widget.isViewMode // onChanged: widget.isViewMode
@ -811,11 +1056,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Postal Code", Text(
style: TextStyle( "Postal Code",
fontSize: 12, style: GoogleFonts.poppins(
fontWeight: FontWeight.w200, fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
color: Colors.black)), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldUserWrapper( CustomTextFieldUserWrapper(
isFocused: false, isFocused: false,
@ -823,7 +1068,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: TextField( child: TextField(
style: TextStyle(fontSize: 12, color: Colors.black), style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["postalCode"], controller: widget.controllers["postalCode"],
enabled: !widget.isViewMode, enabled: !widget.isViewMode,
keyboardType: TextInputType.numberWithOptions(decimal: true), keyboardType: TextInputType.numberWithOptions(decimal: true),
@ -833,7 +1078,10 @@ class PersonalDetailsState extends State<PersonalDetails> {
], ],
decoration: InputDecoration( decoration: InputDecoration(
labelText: "Postal Code", labelText: "Postal Code",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey), labelStyle: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),
@ -849,11 +1097,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Role ", Text(
style: TextStyle( "Role ",
fontSize: 12, style: GoogleFonts.poppins(
fontWeight: FontWeight.w200, fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
color: Colors.black)), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldUserWrapper( CustomTextFieldUserWrapper(
isFocused: false, // Dropdown doesn't use focus isFocused: false, // Dropdown doesn't use focus
@ -862,7 +1110,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
height: 45, // Set appropriate height height: 45, // Set appropriate height
child: DropdownButtonFormField<String>( child: DropdownButtonFormField<String>(
value: widget.isViewMode ? null : selectedRole, value: widget.isViewMode ? null : selectedRole,
style: TextStyle(fontSize: 12, color: Colors.black), style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
decoration: InputDecoration( decoration: InputDecoration(
border: InputBorder.none, border: InputBorder.none,
contentPadding: contentPadding:
@ -873,6 +1121,9 @@ class PersonalDetailsState extends State<PersonalDetails> {
: (newValue) { : (newValue) {
setState(() { setState(() {
selectedRole = newValue; selectedRole = newValue;
isTravelAgent = selectedRole == "Travel Agent";
// Pass the result back to parent
widget.onUserTypeChanged?.call(isTravelAgent);
}); });
widget.onRoleChanged?.call(newValue); widget.onRoleChanged?.call(newValue);
}, },
@ -884,7 +1135,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
hint: Text("Select"), hint: Text("Select"),
disabledHint: Text( disabledHint: Text(
selectedRole ?? "Select Role", selectedRole ?? "Select Role",
style: TextStyle(fontSize: 12, color: Colors.black), style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
), ),
), ),
), ),
@ -897,11 +1148,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Address", Text(
style: TextStyle( "Address",
fontSize: 12, style: GoogleFonts.poppins(
fontWeight: FontWeight.w200, fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
color: Colors.black)), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldUserWrapper( CustomTextFieldUserWrapper(
isFocused: false, isFocused: false,
@ -909,14 +1160,15 @@ class PersonalDetailsState extends State<PersonalDetails> {
child: SizedBox( child: SizedBox(
height: 80, height: 80,
child: TextField( child: TextField(
style: TextStyle(fontSize: 12, color: Colors.black), style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["address"], controller: widget.controllers["address"],
enabled: !widget.isViewMode, enabled: !widget.isViewMode,
maxLines: 2, maxLines: 2,
keyboardType: TextInputType.multiline, keyboardType: TextInputType.multiline,
decoration: InputDecoration( decoration: InputDecoration(
labelText: "Address", labelText: "Address",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey), labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 16), // contentPadding: EdgeInsets.symmetric(vertical: 16),
@ -933,11 +1185,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Password", Text(
style: TextStyle( "Password",
fontSize: 12, style: GoogleFonts.poppins(
fontWeight: FontWeight.w200, fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
color: Colors.black)), ),
SizedBox(height: 5), SizedBox(height: 5),
CustomTextFieldUserWrapper( CustomTextFieldUserWrapper(
isFocused: false, isFocused: false,
@ -945,7 +1197,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: TextField( child: TextField(
style: TextStyle(fontSize: 12, color: Colors.black), style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["password"], controller: widget.controllers["password"],
enabled: !widget.isViewMode, enabled: !widget.isViewMode,
onChanged: (value) { onChanged: (value) {
@ -953,7 +1205,8 @@ class PersonalDetailsState extends State<PersonalDetails> {
}, },
decoration: InputDecoration( decoration: InputDecoration(
labelText: "Password", labelText: "Password",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey), labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 16),

File diff suppressed because it is too large Load Diff

View File

@ -10,8 +10,10 @@ import 'package:shared_preferences/shared_preferences.dart';
import '../../config/apiUrl.dart'; import '../../config/apiUrl.dart';
import '../../routes/custom_appBar.dart'; import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart'; import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart';
import '../../utils/pagination.dart'; import '../../utils/pagination.dart';
import '../../widgets/custom_popup.dart'; import '../../widgets/custom_popup.dart';
import '../../widgets/popup_userList_action.dart';
class UserListScreen extends StatefulWidget { class UserListScreen extends StatefulWidget {
@override @override
@ -19,7 +21,10 @@ class UserListScreen extends StatefulWidget {
} }
class _UserListScreenState extends State<UserListScreen> { class _UserListScreenState extends State<UserListScreen> {
final ApiService apiService = ApiService();
late Future<List<dynamic>> futureUsers; late Future<List<dynamic>> futureUsers;
late Map<String, dynamic> userSingleData;
List<dynamic>? apiCountryData; List<dynamic>? apiCountryData;
String? selectedUserId; String? selectedUserId;
String? orgId; String? orgId;
@ -542,16 +547,16 @@ class _UserListScreenState extends State<UserListScreen> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Icon(Icons.error_outline, // const Icon(Icons.error_outline,
color: Colors.redAccent, size: 60), // color: Colors.redAccent, size: 60),
const SizedBox(height: 16), // const SizedBox(height: 16),
Text( // Text(
"Oops!", // "Oops!",
style: GoogleFonts.poppins( // style: GoogleFonts.poppins(
fontSize: 20, // fontSize: 20,
fontWeight: FontWeight.bold, // fontWeight: FontWeight.bold,
color: Colors.redAccent), // color: Colors.redAccent),
), // ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
"No Plans Available For This User", "No Plans Available For This User",
@ -683,61 +688,104 @@ class _UserListScreenState extends State<UserListScreen> {
fontWeight: FontWeight.w400), fontWeight: FontWeight.w400),
))), ))),
DataCell( DataCell(
PopupMenuButton<int>( UserActionsMenu(
color: Colors.white, user: user,
padding: EdgeInsets.zero, getUserDetails: (id) =>
offset: Offset(0, 30), apiService.getSingleUser(id),
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: () {
context.go(
"/CreateUserDetails",
extra: {
"selectedUser": user,
"isViewMode": true
},
);
}),
IconButton(
icon: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15),
onPressed: () {
context.go(
"/CreateUserDetails",
extra: {
"selectedUser": user,
"isViewMode": false
},
);
},
),
],
),
),
),
],
), ),
// 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( // Row(
// children: [ // children: [
// MouseRegion( // MouseRegion(
@ -858,65 +906,85 @@ class _UserListScreenState extends State<UserListScreen> {
color: Colors.black87, color: Colors.black87,
fontWeight: FontWeight.w700), fontWeight: FontWeight.w700),
), ),
PopupMenuButton<int>(
color: Colors.white, UserActionsMenu(
padding: EdgeInsets.zero, user: user,
offset: Offset(0, 30), getUserDetails: (id) =>
icon: Icon( apiService.getSingleUser(id),
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: () {
context.go(
"/CreateUserDetails",
extra: {
"selectedUser":
user,
"isViewMode": true
},
);
}),
IconButton(
icon: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15),
onPressed: () {
context.go(
"/CreateUserDetails",
extra: {
"selectedUser":
user,
"isViewMode": false
},
);
},
),
],
),
),
),
],
), ),
// 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: () {
// print(
// "USerDAta - $user");
// // dynamic usersData = apiService
// // .getSingleUser(user[
// // 'user_id']
// // is String
// // ? int.parse(user[
// // 'user_id'])
// // : user[
// // 'user_id']);
// //
// // print(
// // "USerDAta - $usersData");
//
// context.go(
// "/CreateUserDetails",
// extra: {
// "selectedUser":
// user,
// "isViewMode": true
// },
// );
// }),
// IconButton(
// icon: Image.asset(
// 'assets/images/IconsImg/edit.png',
// width: 20,
// height: 15),
// onPressed: () {
// context.go(
// "/CreateUserDetails",
// extra: {
// "selectedUser":
// user,
// "isViewMode": false
// },
// );
// },
// ),
// ],
// ),
// ),
// ),
// ],
// ),
], ],
), ),

View File

@ -394,31 +394,54 @@ class _CustomAppBarState extends State<CustomAppBar> {
buildNavItem("All Trips", _myTravelRequestColor, buildNavItem("All Trips", _myTravelRequestColor,
() => context.go('/listAllPlan'), () => context.go('/listAllPlan'),
isSelected: selectedTab == "All Trips"), isSelected: selectedTab == "All Trips"),
if (userDetails["role"] == "Travel Agent")
const SizedBox(width: 20),
if (userDetails["role"] == "Travel Agent")
buildNavItem("My Trips", _myTravelRequestColor,
() => context.go('/listTravelAgentPlan'),
isSelected: selectedTab == "My Trips"),
...[
const SizedBox(width: 20),
buildNavItem("My Trips", _myTravelRequestColor,
() => context.go('/listPlan'),
isSelected: selectedTab == "My Trips"),
],
if (userDetails["role"] != "Travel Agent") if (userDetails["role"] != "Travel Agent")
const SizedBox(width: 20), const SizedBox(width: 20),
if (userData?["role"] == "Travel Agent")
buildNavItem(
"My Trips",
_myTravelRequestColor,
() => context.go('/listTravelAgentPlan'),
isSelected: selectedTab == "My Trips",
),
if (userData?["role"] != "Travel Agent") // for others
buildNavItem(
"My Trips",
_myTravelRequestColor,
() => context.go('/listPlan'),
isSelected: selectedTab == "My Trips",
),
// if (userDetails["role"] == "Travel Agent")
// buildNavItem("My Trips", _myTravelRequestColor,
// () => context.go('/listTravelAgentPlan'),
// isSelected: selectedTab == "My Trips"),
// ...[
// const SizedBox(width: 20),
// buildNavItem("My Trips", _myTravelRequestColor,
// () => context.go('/listPlan'),
// isSelected: selectedTab == "My Trips"),
// ],
//
const SizedBox(width: 20),
if (userData?["role"] != "Travel Agent")
buildNavItem("My Approvals", _myApprovalsColor, () { buildNavItem("My Approvals", _myApprovalsColor, () {
if (userData?["role"] == "Travel Agent") {
isSelected:
selectedTab == "My Approvals";
context.go('/listTravelAgentPlan');
} else {
isSelected: isSelected:
selectedTab == "My Approvals"; selectedTab == "My Approvals";
context.go('/ApprovalList'); context.go('/ApprovalList');
} })
}),
// if (userData?["role"] == "Travel Agent") {
// isSelected:
// selectedTab == "My Approvals";
// context.go('/listTravelAgentPlan');
// } else {
// isSelected:
// selectedTab == "My Approvals";
// context.go('/ApprovalList');
// }
], ],
), ),
), ),

View File

@ -623,4 +623,111 @@ class ApiService {
throw Exception('Failed to load plans'); throw Exception('Failed to load plans');
} }
} }
Future<void> getForexPdfDownload(forexId) async {
final String apiUrldata =
'$apiUrl/api/plans/forexDownload?forex_id=$forexId';
// final String apiUrldata = '$apiUrl/auth/googlelogin';
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 {
print("PDf Dowloaded");
// Create a blob from the response body
final blob = html.Blob([response.bodyBytes]);
// Generate a download URL for the blob
final url = html.Url.createObjectUrlFromBlob(blob);
// Create a link element to trigger the download
final anchor = html.AnchorElement(href: url)
..setAttribute('download', 'Forex_$forexId.pdf')
..click();
// Revoke the download URL to free up resources
html.Url.revokeObjectUrl(url);
} catch (e) {
throw Exception('Error parsing response: $e');
}
} else if (response.statusCode == 404) {
// showDialog(
// context: context,
// builder: (BuildContext context) {
// return AlertDialog(
// title: Text('File not found.'),
// // content: Text('File not found.'),
// actions: [
// TextButton(
// child: Text('OK'),
// onPressed: () {
// Navigator.of(context).pop(); // Close the dialog
// },
// ),
// ],
// );
// },
// );
} else {
throw Exception('Failed to load plans');
}
}
//---------------------------------------User Management-----------------------------------
Future<Map<String, dynamic>> getSingleUser(int userId) async {
print('Single USer 1 - $userId');
final String apiUrldata = '$apiUrl/api/users/find/$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(data);
if (!data.containsKey('data') || data['data'] is! Map) {
throw Exception(
"Invalid response format: 'data' field is missing or not a Map");
}
print('Single USer 1');
Map<String, dynamic> plansJson =
data['data']; // 'data' is a Map, not a List
print('Single USer 2');
return plansJson;
} catch (e) {
throw Exception('Error parsing response: $e');
}
} else {
throw Exception('Failed to load plans');
}
}
} }

View File

@ -0,0 +1,267 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:frontend/utils/auth_utils.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import '../config/apiUrl.dart';
class CommentModal extends StatefulWidget {
final String planId;
final Color layoutColorForUser;
final String role;
const CommentModal({
Key? key,
required this.planId,
required this.layoutColorForUser,
required this.role,
}) : super(key: key);
@override
CommentModalState createState() => CommentModalState();
}
class CommentModalState extends State<CommentModal> {
Map<String, dynamic>? remarksData;
int? remarksId;
dynamic userId;
bool isLoading = true;
String? errorMessage;
bool editRemarks = false;
TextEditingController commentController = TextEditingController();
Map<String, dynamic> getData() {
final map = {
"plan_id": int.parse(widget.planId),
"remarks": commentController.text,
"created_by": userId,
"is_active": 1,
};
if (remarksId != null) {
map["id"] = remarksId; // Add 'id' only if available
}
return map;
}
@override
void initState() {
super.initState();
loadRemarks();
}
Future<void> loadRemarks() async {
try {
final userIdString = await getUserId(); // getUserId returns String?
if (userIdString == null) {
throw Exception('User ID not found.');
}
userId = int.tryParse(userIdString);
if (userId == null) {
throw Exception('Invalid user ID format.');
}
final data = await getRemarks(userId);
setState(() {
remarksData = data;
isLoading = false;
});
print("Remarks -");
print("Remarks - ${jsonEncode(remarksData)}");
} catch (e) {
setState(() {
errorMessage = e.toString();
isLoading = false;
});
}
}
Future<Map<String, dynamic>> getRemarks(int userId) async {
// final String apiUrldata =
// '$apiUrl/getRemarksByPlanId?plan_id=${widget.planId}&user_id=$userId';
final String apiUrldata =
'$apiUrl/api/plans/getRemarksByPlanId?plan_id=${widget.planId}&user_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) {
print("Res1 ....");
final data = json.decode(response.body);
// Check if 'data' is empty or doesn't contain valid remarks
if (!data.containsKey('data') ||
data['data'] == null ||
data['data'].isEmpty) {
editRemarks = true; // Set the flag to true if data is empty
} else {
print("DAta available ");
updateData(data);
editRemarks = false; // Otherwise, no need to create remarks
}
print("Res1 - $data");
if (!data.containsKey('data') || data['data'] is! Map) {
throw Exception(
"Invalid response format: 'data' field is missing or not a Map");
}
return data['data'];
} else {
throw Exception('Failed to load remarks');
}
}
Future<void> postRemarksData() async {
final remarksData = getData();
print("Remarks Data - remarksData");
final String apiUrldata = '$apiUrl/api/plans/addOrEditPlanRemark';
// final String apiUrldata = '$apiUrl/api/plans/createOrEditPlan';
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 response = await http.post(
Uri.parse(apiUrldata),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
body: jsonEncode(remarksData), // Convert map to JSON
);
if (response.statusCode == 200) {
print("Plan submitted successfully!");
print("Response: ${response.body}");
} else {
print("Failed to submit plan. Status: ${response.statusCode}");
print("Error: ${response.body}");
}
} catch (e) {
print(" Error submitting plan: $e");
}
}
void updateData(dynamic data) {
if (data['data'] != null && data['data'].isNotEmpty) {
final remarksData = data['data'][0]; // Take the first item from the list
// Now update your local fields
commentController.text = remarksData['remarks'] ?? '';
// If you need to update other fields like created_by, you can do that too
userId = remarksData['created_by'] ?? userId;
remarksId = remarksData['id'];
// If plan_id needs to be updated (usually it doesn't change), you can do it too
// widget.planId = remarksData['plan_id'].toString(); // If widget.planId is mutable
setState(() {}); // Refresh the UI if needed
}
}
Widget build(BuildContext context) {
// You can also make a TextEditingController if you want to collect input
return AlertDialog(
contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 10),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Row 1: Title + Edit + Delete buttons
Row(
children: [
Text(
'Comment',
style: GoogleFonts.poppins(fontSize: 18, color: Colors.black),
),
const Spacer(),
if (widget.role == "Travel Agent")
IconButton(
icon: const Icon(Icons.edit, size: 20),
onPressed: () {
setState(() {
editRemarks = !editRemarks;
});
},
),
IconButton(
icon: const Icon(Icons.delete, size: 20),
onPressed: () {
// Handle delete pressed
},
),
],
),
const SizedBox(height: 10),
// Row 2: TextField for comment
TextField(
controller: commentController,
maxLines: 5,
enabled: editRemarks,
decoration: InputDecoration(
hintText: 'Enter your comment...',
hintStyle: GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
),
const SizedBox(height: 20),
// Row 3: OK button
editRemarks
? SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
postRemarksData();
// You can get text from commentController.text
Navigator.of(context).pop(); // Close the modal
},
style: ElevatedButton.styleFrom(
backgroundColor: widget.layoutColorForUser,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text('OK',
style: GoogleFonts.poppins(
fontSize: 13, color: Colors.white)),
),
)
: SizedBox.shrink(),
],
),
);
}
}

View File

@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
class CustomTextFieldUserTravellerWrapper extends StatefulWidget {
final Widget child;
final bool isFocused;
final bool isDesktop;
final double? width;
final Color? color;
final VoidCallback? onFocusChange; // Callback for focus handling
final EdgeInsetsGeometry padding;
const CustomTextFieldUserTravellerWrapper({
super.key,
required this.child,
required this.isFocused,
required this.isDesktop,
this.width,
this.color = Colors.white,
this.onFocusChange,
this.padding = const EdgeInsets.symmetric(horizontal: 12),
});
@override
_CustomTextFieldUserTravellerWrapperState createState() =>
_CustomTextFieldUserTravellerWrapperState();
}
class _CustomTextFieldUserTravellerWrapperState
extends State<CustomTextFieldUserTravellerWrapper> {
@override
Widget build(BuildContext context) {
return Container(
width: widget.width ?? // Use custom width if provided, else default
(widget.isDesktop
? MediaQuery.of(context).size.width * 0.24
: MediaQuery.of(context).size.width * 0.8),
padding: widget.padding,
decoration: BoxDecoration(
// color: widget.color,
// color: Color(0xFFF7F7FB),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: widget.isFocused ? Color(0xFF78B4FC) : Color(0xFFD6D5E6),
// color: widget.isFocused ? Color(0xFF78B4FC) : Color(0xFFD6D5E6),
width: widget.isFocused ? 2.0 : 0.5,
),
boxShadow: widget.isFocused
? [
BoxShadow(
color: Color.fromRGBO(120, 180, 252, 0.3),
blurRadius: 10,
spreadRadius: 2,
offset: Offset(0, 4),
),
]
: [],
),
child: widget.child,
);
}
}

View File

@ -0,0 +1,77 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../services/apiService.dart';
import 'custom_popup.dart';
class UserActionsMenu extends StatelessWidget {
final ApiService apiService = ApiService();
final Map<String, dynamic> user;
final Future<Map<String, dynamic>> Function(int userId) getUserDetails;
UserActionsMenu({
super.key,
required this.user,
required this.getUserDetails,
});
int getUserId(dynamic value) {
if (value is String) return int.parse(value);
return value;
}
@override
Widget build(BuildContext context) {
return 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: [
GestureDetector(
child: Icon(Icons.remove_red_eye,
color: Color(0xFF475569), size: 18),
onTap: () async {
Navigator.pop(context); // Close the menu
final userId = getUserId(user['user_id']);
final usersData = await getUserDetails(userId);
context.go("/CreateUserDetails", extra: {
"selectedUser": usersData,
"isViewMode": true,
});
},
),
SizedBox(width: 8),
GestureDetector(
child: Image.asset('assets/images/IconsImg/edit.png',
width: 20, height: 15),
onTap: () async {
Navigator.pop(context); // Close the menu
final userId = getUserId(user['user_id']);
final usersData = await getUserDetails(userId);
context.go("/CreateUserDetails", extra: {
"selectedUser": usersData,
"isViewMode": false,
});
},
),
],
),
),
),
],
);
}
}

View File

@ -208,6 +208,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
fluttertoast:
dependency: "direct main"
description:
name: fluttertoast
sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1"
url: "https://pub.dev"
source: hosted
version: "8.2.12"
go_router: go_router:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -49,6 +49,7 @@ dependencies:
universal_html: ^2.2.4 universal_html: ^2.2.4
super_tooltip: ^2.0.9 super_tooltip: ^2.0.9
google_fonts: ^6.2.1 google_fonts: ^6.2.1
fluttertoast: ^8.2.12
dev_dependencies: dev_dependencies: