user management -TravelDetails
This commit is contained in:
parent
4435eeadd3
commit
75592f9879
@ -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"),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@ -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"),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
|
||||
|
||||
@ -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"),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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 ?? ""}";
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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"),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
@ -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
|
||||
// },
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
],
|
||||
),
|
||||
|
||||
|
||||
@ -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');
|
||||
// }
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
267
lib/utils/travelAgent_remarks.dart
Normal file
267
lib/utils/travelAgent_remarks.dart
Normal 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(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
61
lib/widgets/custom_user_travel.dart
Normal file
61
lib/widgets/custom_user_travel.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
77
lib/widgets/popup_userList_action.dart
Normal file
77
lib/widgets/popup_userList_action.dart
Normal 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,
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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:
|
||||
|
||||
@ -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:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user