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 '../../utils/auth_utils.dart';
import '../../utils/pagination.dart';
import '../../utils/travelAgent_remarks.dart';
import '../../widgets/custom_popup.dart';
import '../../widgets/popup_listPlan_action.dart';
@ -530,20 +531,17 @@ class _ListAllPlansState extends State<ListAllPlans> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.error_outline,
color: Colors.redAccent, size: 60),
const SizedBox(height: 1),
Text(
"Oops!",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.redAccent),
// style: TextStyle(
// fontSize: 22,
// const Icon(Icons.error_outline,
// color: Colors.redAccent, size: 60),
// const SizedBox(height: 1),
// Text(
// "Oops!",
// style: GoogleFonts.poppins(
// fontSize: 12,
// fontWeight: FontWeight.bold,
// color: Colors.redAccent)
),
// color: Colors.redAccent),
//
// ),
const SizedBox(height: 15),
Text(
"No Trips",
@ -703,6 +701,8 @@ class _ListAllPlansState extends State<ListAllPlans> {
),
),
DataCell(
Row(
children: [
PopupMenuButton<int>(
color: Colors.white,
padding: EdgeInsets.zero,
@ -723,7 +723,8 @@ class _ListAllPlansState extends State<ListAllPlans> {
MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.remove_red_eye,
icon: Icon(
Icons.remove_red_eye,
color: Color(0xFF475569),
size: 18),
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(),
@ -839,6 +859,25 @@ class _ListAllPlansState extends State<ListAllPlans> {
deletePlan: deletePlan,
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:core';
import 'package:frontend/data/models/plan.dart';
import 'package:frontend/utils/travelAgent_remarks.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
@ -456,17 +457,24 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: const [
children: [
SizedBox(height: 20),
Icon(Icons.error_outline,
color: Colors.redAccent, size: 60),
SizedBox(height: 1),
Text("Oops!",
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.redAccent)),
// Icon(Icons.error_outline,
// color: Colors.redAccent, size: 60),
// SizedBox(height: 1),
// Text("Oops!",
// style: TextStyle(
// fontSize: 22,
// fontWeight: FontWeight.bold,
// color: Colors.redAccent)),
SizedBox(height: 20),
isDesktop
? SizedBox(
width:
MediaQuery.of(context).size.width * 5.5,
)
: SizedBox.shrink(),
Text("No Trips Pending For Your Approvals",
textAlign: TextAlign.center,
style: TextStyle(
@ -605,6 +613,9 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
))),
DataCell(
Container(
padding: const EdgeInsets.all(3),
// width:
// 150, // Set your desired fixed size (equal width and height)
width: double
.infinity, // Set your desired fixed size (equal width and height)
height: 25,
@ -628,6 +639,8 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
),
DataCell(
Row(
children: [
PopupMenuButton<int>(
color: Colors.white,
padding: EdgeInsets.zero,
@ -645,9 +658,10 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.remove_red_eye,
icon: Icon(
Icons.remove_red_eye,
color: Color(0xFF475569),
size: 14),
size: 16),
onPressed: () {
Navigator.pop(
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: [
@ -787,6 +820,23 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
deletePlan: deletePlan,
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 '../../utils/auth_utils.dart';
import '../../utils/pagination.dart';
import '../../utils/travelAgent_remarks.dart';
import '../../widgets/custom_popup.dart';
import '../../widgets/popup_listPlan_action.dart';
@ -505,14 +506,14 @@ class _ApprovalListState extends State<ApprovalList> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: const [
Icon(Icons.error_outline,
color: Colors.redAccent, size: 60),
SizedBox(height: 1),
Text("Oops!",
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.redAccent)),
// Icon(Icons.error_outline,
// color: Colors.redAccent, size: 60),
// SizedBox(height: 1),
// Text("Oops!",
// style: TextStyle(
// fontSize: 22,
// fontWeight: FontWeight.bold,
// color: Colors.redAccent)),
SizedBox(height: 15),
Text("No Trips Pending For Your Approvals",
textAlign: TextAlign.center,
@ -670,6 +671,8 @@ class _ApprovalListState extends State<ApprovalList> {
),
DataCell(
Row(
children: [
PopupMenuButton<int>(
color: Colors.white,
padding: EdgeInsets.zero,
@ -690,8 +693,10 @@ class _ApprovalListState extends State<ApprovalList> {
MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.remove_red_eye,
color: Color(0xFF475569),
icon: Icon(
Icons.remove_red_eye,
color:
Color(0xFF475569),
size: 18),
onPressed: () {
Navigator.pop(
@ -708,8 +713,10 @@ class _ApprovalListState extends State<ApprovalList> {
height: 15),
onPressed: () {
Navigator.pop(context);
ApiService.viewPlanForApprover(
context, plan.planId,
ApiService
.viewPlanForApprover(
context,
plan.planId,
isViewMode: false,
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: [
// 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 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:frontend/config/apiUrl.dart';
import 'package:http/http.dart' as http;
import 'package:go_router/go_router.dart';
@ -115,11 +116,28 @@ class _LoginWidgetState extends State<LoginWidget> {
context.go('/listPlan');
}
} 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(
SnackBar(
content: Text(
"Login Failed: ${jsonDecode(response.body)['message']}")),
content: Text("Login Failed: $errorMessage"),
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) {
ScaffoldMessenger.of(context).showSnackBar(

View File

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

View File

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

View File

@ -4,6 +4,8 @@ import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../../services/apiService.dart';
class ForexListWidget extends StatelessWidget {
final List<Map<String, dynamic>> forexList;
final Function(bool, Map<String, dynamic>, String) onOpen;
@ -27,6 +29,7 @@ class ForexListWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isDesktop = MediaQuery.of(context).size.width > 1024;
final ApiService apiService = ApiService();
return Padding(
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 =
forexList.where((item) => item["is_active"] == "1").toList();
print("filteredList- $filteredList");
@ -227,6 +234,23 @@ class ForexListWidget extends StatelessWidget {
child: Image.asset('assets/images/IconsImg/delete.png',
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(

View File

@ -15,6 +15,7 @@ import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
import '../../utils/pagination.dart';
import '../../utils/travelAgent_remarks.dart';
import '../../widgets/custom_popup.dart';
import '../../widgets/popup_listPlan_action.dart';
@ -557,14 +558,14 @@ class _ListPlansState extends State<ListPlans> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: const [
Icon(Icons.error_outline,
color: Colors.redAccent, size: 60),
SizedBox(height: 1),
Text("Oops!",
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.redAccent)),
// Icon(Icons.error_outline,
// color: Colors.redAccent, size: 60),
// SizedBox(height: 1),
// Text("Oops!",
// style: TextStyle(
// fontSize: 22,
// fontWeight: FontWeight.bold,
// color: Colors.redAccent)),
SizedBox(height: 15),
Text("No Trips",
textAlign: TextAlign.center,
@ -743,6 +744,8 @@ class _ListPlansState extends State<ListPlans> {
),
),
DataCell(
Row(
children: [
PopupMenuButton<int>(
color: Colors.white,
padding: EdgeInsets.zero,
@ -760,7 +763,8 @@ class _ListPlansState extends State<ListPlans> {
MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.remove_red_eye,
icon: Icon(
Icons.remove_red_eye,
color: Color(0xFF475569),
size: 18),
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: [
@ -910,6 +933,23 @@ class _ListPlansState extends State<ListPlans> {
deletePlan: deletePlan,
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 =
GlobalKey<PersonalDetailsState>();
final GlobalKey<TravellerDetailsState> travellerDetailsKey =
GlobalKey<TravellerDetailsState>();
Map<String, dynamic>? travelDetailsData;
late TabController _tabController;
@ -124,6 +128,8 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
Color? layoutColor;
Color? bodyColor;
bool setSelectesUserType = false;
Map<String, dynamic> get userDetials {
Map<String, dynamic> data = {
"first_name": controllers["Fname"]?.text,
@ -159,11 +165,18 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
"created_by": userId,
"is_active": "1",
"org_id": orgId,
"travel_details": travelDetailsData ?? {},
// "passport_fileData": passportFile,
};
return data;
}
void handleUserTypeChange(bool isTravelAgent) {
setState(() {
setSelectesUserType = isTravelAgent;
});
}
Future<void> updateData() async {
// Ensure apiselectedUser is not null before printing
if (apiselectedUser != null) {
@ -474,8 +487,14 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
print("USR Detail Submit");
printFormData();
travelDetailsData = travellerDetailsKey.currentState?.travel_Detials;
print("TRAVEL DETAILS FROM CHILD: $travelDetailsData");
Map<String, dynamic> data = userDetials;
print("USERDETAILS : $data");
if (!isValidData(data)) {
print("USERDETAILS : $userDetials");
print("Validation Failed: Required fields are missing.");
@ -487,7 +506,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
final tabs = {
"personal": "Personal Details",
"office": "Office Details",
// "travel": "Travel Details",
"travel": "Travel Details",
};
final tabKeys = tabs.keys.toList(); // ["personal", "office", "travel"]
@ -511,8 +530,16 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
print("USR Detail Submit");
printFormData();
travelDetailsData = travellerDetailsKey.currentState?.travel_Detials;
print("TRAVEL DETAILS FROM CHILD: $travelDetailsData");
Map<String, dynamic> data = userDetials;
print("USERDETAILS : $data");
// Map<String, dynamic> data = userDetials;
if (!isValidData(data)) {
print("USERDETAILS : $userDetials");
print("Validation Failed: Required fields are missing.");
@ -760,14 +787,12 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
height: isDesktop
? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height,
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(0.0),
child: _buildUserDetails(isDesktop),
),
),
),
),
Container(
color: Colors.white,
child: Padding(
@ -775,13 +800,15 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
child: isDesktop
? Row(
mainAxisAlignment: MainAxisAlignment.end,
children: selectedTab == "office"
children: (selectedTab == "travel" ||
setSelectesUserType == true)
? _buildSubmit(isDesktop, layoutColor!)
: _buildNext(isDesktop, layoutColor!),
)
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: selectedTab == "office"
children: (selectedTab == "travel" ||
setSelectesUserType == true)
? _buildSubmit(isDesktop, layoutColor!)
: _buildNext(isDesktop, layoutColor!),
)),
@ -819,7 +846,8 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
),
Container(
// color: Colors.yellow.shade50,
height: MediaQuery.of(context).size.height * 0.67,
height: MediaQuery.of(context).size.height * 0.64,
child: Row(
children: [
Expanded(child: buildTabContents(isDesktop, isViewMode)),
@ -846,6 +874,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
selectedCountry: selectedCountry,
selectedRole: selectedRole,
apiselectedUser: isapiselectedUser,
onUserTypeChanged: handleUserTypeChange,
onGenderChanged: (gender) {
setState(() {
selectedGender = gender;
@ -900,7 +929,13 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
},
);
case "travel":
return TravellerDetails();
return TravellerDetails(
key: travellerDetailsKey,
isDesktop: isDesktop,
isViewMode: isViewMode,
controllers: controllers,
errorMessages: errorMessages,
);
default:
return PersonalDetails(
personalDetailsKey: personalDetailsKey,
@ -910,7 +945,9 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
errorMessages: errorMessages,
selectedGender: selectedGender,
selectedCountry: selectedCountry,
apiselectedUser: isapiselectedUser);
apiselectedUser: isapiselectedUser,
onUserTypeChanged: handleUserTypeChange,
);
}
}
@ -918,7 +955,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
final tabs = {
"personal": "Personal Details",
"office": "Office Details",
// "travel": "Travel Details",
"travel": "Travel Details",
};
return Row(

View File

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

View File

@ -1,12 +1,15 @@
import 'dart:convert';
import 'dart:io' as html;
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import '../../../services/apiService.dart';
import '../../../utils/auth_utils.dart';
import '../../../widgets/custom_user_form.dart';
class PersonalDetails extends StatefulWidget {
@ -20,14 +23,15 @@ class PersonalDetails extends StatefulWidget {
final ValueChanged<String?>? onGenderChanged;
final ValueChanged<String?>? onCountryChanged;
final ValueChanged<String?>? onRoleChanged;
final void Function(bool)? onUserTypeChanged;
final String? selectedGender;
final String? selectedCountry;
final String? selectedRole;
// const PersonalDetails(this.isDesktop, this.isViewMode, {super.key},);
const PersonalDetails({
Key? key,
const PersonalDetails(
{Key? key,
this.selectedGender,
this.selectedCountry,
this.selectedRole,
@ -40,7 +44,8 @@ class PersonalDetails extends StatefulWidget {
this.onGenderChanged,
this.onCountryChanged,
this.onRoleChanged,
}) : super(key: key);
this.onUserTypeChanged})
: super(key: key);
@override
PersonalDetailsState createState() => PersonalDetailsState();
@ -53,11 +58,16 @@ class PersonalDetailsState extends State<PersonalDetails> {
late List<String> countryCodes; // List of country codes
late List<dynamic>? apiCountryData;
late List<dynamic>? apiRoleData;
List<dynamic>? apiAllServices;
Map<String, dynamic>? selectedOrg;
List<Map<String, dynamic>> selectedServiceIds = [];
String? get selectedGenderValue => selectedGender;
String? get selectedCountryValue => selectedCountry;
String? get selectedRoleValue => selectedRole;
late bool isTravelAgent = false;
String? selectedTab;
String? selectedCountry;
@ -121,6 +131,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
selectedRole = widget.selectedRole;
fetchRoles();
fetchCountries();
WidgetsBinding.instance.addPostFrameCallback((_) {
loadAllServices();
getOrganizationData();
});
}
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
Widget build(BuildContext context) {
return Container(
@ -169,6 +225,8 @@ class PersonalDetailsState extends State<PersonalDetails> {
),
),
),
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -188,11 +246,194 @@ class PersonalDetailsState extends State<PersonalDetails> {
height: 10,
),
_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) {
return Container(
color: Colors.white,
@ -227,11 +468,6 @@ class PersonalDetailsState extends State<PersonalDetails> {
}
Widget _buildSecondRow(bool isDesktop) {
List<Widget> fields = [
buildEmailField(),
buildMobileField(),
buildAlternateMobileField(),
];
return Container(
color: Colors.white,
child: widget.isDesktop
@ -318,11 +554,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("First Name",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w200,
color: Colors.black)),
Text(
"First Name",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
@ -330,7 +566,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12, color: Colors.black),
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["Fname"],
enabled: !widget.isViewMode,
onChanged: (value) {
@ -338,7 +574,10 @@ class PersonalDetailsState extends State<PersonalDetails> {
},
decoration: InputDecoration(
labelText: "FirstName",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
labelStyle: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
@ -404,11 +643,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Gender",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w200,
color: Colors.black)),
Text(
"Gender",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
width: widget.isDesktop
@ -435,18 +674,18 @@ class PersonalDetailsState extends State<PersonalDetails> {
border: InputBorder.none,
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: [
DropdownMenuItem(value: "Male", child: Text("Male")),
DropdownMenuItem(value: "Female", child: Text("Female")),
],
hint: Text(
selectedGender ?? "Select Gender",
style: TextStyle(fontSize: 12, color: Colors.black),
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
),
disabledHint: Text(
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(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Date of Birth",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w200,
color: Colors.black)),
Text(
"Date of Birth",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
width: widget.isDesktop
@ -550,10 +789,13 @@ class PersonalDetailsState extends State<PersonalDetails> {
child: AbsorbPointer(
child: TextField(
controller: widget.controllers["dob"],
style: const TextStyle(fontSize: 12, color: Colors.black),
decoration: const InputDecoration(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
decoration: InputDecoration(
labelText: "Select Date",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
labelStyle: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
@ -573,11 +815,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Email",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w200,
color: Colors.black)),
Text(
"Email",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
@ -585,7 +827,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12, color: Colors.black),
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["email"],
onChanged: (value) {
_clearError("email");
@ -593,7 +835,8 @@ class PersonalDetailsState extends State<PersonalDetails> {
enabled: !widget.isViewMode,
decoration: InputDecoration(
labelText: "Email",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
@ -616,11 +859,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Mobile Number",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w200,
color: Colors.black)),
Text(
"Mobile Number",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
@ -628,7 +871,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12, color: Colors.black),
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["mobileNumber"],
enabled: !widget.isViewMode,
onChanged: (value) {
@ -641,7 +884,8 @@ class PersonalDetailsState extends State<PersonalDetails> {
],
decoration: InputDecoration(
labelText: "Mobile Number",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
@ -665,9 +909,9 @@ class PersonalDetailsState extends State<PersonalDetails> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Alternate Mobile Number",
style: TextStyle(
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w200,
fontWeight: FontWeight.w400,
color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
@ -676,7 +920,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12, color: Colors.black),
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["alternateMobile"],
enabled: !widget.isViewMode,
keyboardType: TextInputType.numberWithOptions(decimal: true),
@ -686,7 +930,8 @@ class PersonalDetailsState extends State<PersonalDetails> {
],
decoration: InputDecoration(
labelText: "Alternate Number",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
@ -726,11 +971,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Country",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w200,
color: Colors.black)),
Text(
"Country",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
@ -765,7 +1010,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Country",
style: TextStyle(fontSize: 12, color: Colors.black),
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
),
),
// onChanged: widget.isViewMode
@ -811,11 +1056,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Postal Code",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w200,
color: Colors.black)),
Text(
"Postal Code",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
@ -823,7 +1068,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12, color: Colors.black),
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["postalCode"],
enabled: !widget.isViewMode,
keyboardType: TextInputType.numberWithOptions(decimal: true),
@ -833,7 +1078,10 @@ class PersonalDetailsState extends State<PersonalDetails> {
],
decoration: InputDecoration(
labelText: "Postal Code",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
labelStyle: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
@ -849,11 +1097,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Role ",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w200,
color: Colors.black)),
Text(
"Role ",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false, // Dropdown doesn't use focus
@ -862,7 +1110,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
height: 45, // Set appropriate height
child: DropdownButtonFormField<String>(
value: widget.isViewMode ? null : selectedRole,
style: TextStyle(fontSize: 12, color: Colors.black),
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding:
@ -873,6 +1121,9 @@ class PersonalDetailsState extends State<PersonalDetails> {
: (newValue) {
setState(() {
selectedRole = newValue;
isTravelAgent = selectedRole == "Travel Agent";
// Pass the result back to parent
widget.onUserTypeChanged?.call(isTravelAgent);
});
widget.onRoleChanged?.call(newValue);
},
@ -884,7 +1135,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
hint: Text("Select"),
disabledHint: Text(
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(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Address",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w200,
color: Colors.black)),
Text(
"Address",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
@ -909,14 +1160,15 @@ class PersonalDetailsState extends State<PersonalDetails> {
child: SizedBox(
height: 80,
child: TextField(
style: TextStyle(fontSize: 12, color: Colors.black),
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["address"],
enabled: !widget.isViewMode,
maxLines: 2,
keyboardType: TextInputType.multiline,
decoration: InputDecoration(
labelText: "Address",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 16),
@ -933,11 +1185,11 @@ class PersonalDetailsState extends State<PersonalDetails> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Password",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w200,
color: Colors.black)),
Text(
"Password",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
@ -945,7 +1197,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12, color: Colors.black),
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["password"],
enabled: !widget.isViewMode,
onChanged: (value) {
@ -953,7 +1205,8 @@ class PersonalDetailsState extends State<PersonalDetails> {
},
decoration: InputDecoration(
labelText: "Password",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
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 '../../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 UserListScreen extends StatefulWidget {
@override
@ -19,7 +21,10 @@ class UserListScreen extends StatefulWidget {
}
class _UserListScreenState extends State<UserListScreen> {
final ApiService apiService = ApiService();
late Future<List<dynamic>> futureUsers;
late Map<String, dynamic> userSingleData;
List<dynamic>? apiCountryData;
String? selectedUserId;
String? orgId;
@ -542,16 +547,16 @@ class _UserListScreenState extends State<UserListScreen> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.error_outline,
color: Colors.redAccent, size: 60),
const SizedBox(height: 16),
Text(
"Oops!",
style: GoogleFonts.poppins(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.redAccent),
),
// const Icon(Icons.error_outline,
// color: Colors.redAccent, size: 60),
// const SizedBox(height: 16),
// Text(
// "Oops!",
// style: GoogleFonts.poppins(
// fontSize: 20,
// fontWeight: FontWeight.bold,
// color: Colors.redAccent),
// ),
const SizedBox(height: 8),
Text(
"No Plans Available For This User",
@ -683,61 +688,104 @@ class _UserListScreenState extends State<UserListScreen> {
fontWeight: FontWeight.w400),
))),
DataCell(
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: () {
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
},
);
},
),
],
),
),
),
],
UserActionsMenu(
user: user,
getUserDetails: (id) =>
apiService.getSingleUser(id),
),
// 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(
@ -858,65 +906,85 @@ class _UserListScreenState extends State<UserListScreen> {
color: Colors.black87,
fontWeight: FontWeight.w700),
),
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: () {
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
},
);
},
),
],
),
),
),
],
UserActionsMenu(
user: user,
getUserDetails: (id) =>
apiService.getSingleUser(id),
),
// 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,
() => context.go('/listAllPlan'),
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")
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, () {
if (userData?["role"] == "Travel Agent") {
isSelected:
selectedTab == "My Approvals";
context.go('/listTravelAgentPlan');
} else {
isSelected:
selectedTab == "My Approvals";
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');
}
}
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
source: sdk
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:
dependency: "direct main"
description:

View File

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