Policy, Approval Screens

This commit is contained in:
venbaittech 2025-04-19 17:40:19 +05:30
parent a91166727a
commit e0ed41ad35
41 changed files with 6793 additions and 2284 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 863 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -0,0 +1,119 @@
import 'package:flutter/material.dart';
/// Show confirm dialog for approval
Future<bool?> showApproveDialog(BuildContext context, Color layoutColor) {
return showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
backgroundColor: Colors.white,
title: const Text(
"Confirm Approval",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
content: const Text("Are you sure you want to approve this plan?"),
actions: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: layoutColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: layoutColor, width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () => Navigator.pop(context, false),
child: const Text("Cancel"),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: layoutColor,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: layoutColor, width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () => Navigator.pop(context, true),
child: const Text("OK"),
),
],
),
);
}
/// Show confirm dialog for rejection with remarks input
Future<String?> showRejectDialog(
BuildContext context, Color layoutColor) async {
String remarks = "";
final confirmed = await showDialog<bool>(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) => AlertDialog(
backgroundColor: Colors.white,
contentPadding: const EdgeInsets.all(36),
// title: const Text("Confirm Rejection"),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
"Confirm Rejection",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
const Text("Please enter remarks to reject the plan."),
const SizedBox(height: 10),
TextField(
maxLines: 3,
onChanged: (value) => remarks = value,
decoration: const InputDecoration(
hintText: "Remarks...",
border: OutlineInputBorder(),
),
),
],
),
actions: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: layoutColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: layoutColor, width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () => Navigator.pop(context, false),
child: const Text("Cancel"),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: layoutColor,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: layoutColor, width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
if (remarks.trim().isEmpty) return;
Navigator.pop(context, true);
},
child: const Text("OK"),
),
],
),
);
},
);
return confirmed == true ? remarks : null;
}

View File

@ -0,0 +1,510 @@
import 'dart:convert';
import 'dart:core';
import 'package:frontend/data/models/plan.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:frontend/config/apiUrl.dart';
import 'package:responsive_builder/responsive_builder.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
class ApprovalList extends StatefulWidget {
const ApprovalList({super.key});
@override
_ApprovalListState createState() => _ApprovalListState();
}
class _ApprovalListState extends State<ApprovalList> {
final ApiService apiService = ApiService();
late Future<List<Plan>> futurePlans;
String? userId;
String? orgId;
String? token;
Color? layoutColor;
Color? bodyColor;
late List<dynamic> plansJson;
@override
void initState() {
super.initState();
getToken();
WidgetsBinding.instance.addPostFrameCallback((_) {
initializeData();
loadInitialData();
});
// futurePlans = fetchPlans();
}
void loadInitialData() async {
String? layoutString = await getLayoutColor();
String? bodyStringColor = await getBodyColor();
setState(() {
layoutColor = layoutString != null
? Color(int.parse(layoutString))
: Colors.redAccent;
bodyColor = bodyStringColor != null
? Color(int.parse(bodyStringColor))
: Colors.white;
});
}
// Future<void> loadAllGroups() async {
// try {
// final result = await apiService.fetchUserApprovalList();
// // setState(() {
// // apiAllGroups = result;
// // });
// print("Fetched services: $result");
// } catch (e) {
// print('Error fetching role list: $e');
// }
// }
Future<void> initializeData() async {
token = await getToken();
userId = await getUserId();
orgId = await getOrgId();
if (token == null || userId == null) {
print("Token or USerId missing");
return;
} else {
setState(() {
futurePlans = fetchPlans();
});
}
}
Future<String?> getUserId() async {
final prefs = await SharedPreferences.getInstance();
final String? userDataString = prefs.getString('user_data');
if (userDataString != null) {
try {
final Map<String, dynamic> userData = jsonDecode(userDataString);
return userData["user_id"]?.toString();
} catch (e) {
return null;
}
}
return null;
}
Future<String?> getOrgId() async {
final prefs = await SharedPreferences.getInstance();
final String? userDataString = prefs.getString('user_data');
if (userDataString != null) {
try {
final Map<String, dynamic> userData = jsonDecode(userDataString);
return userData["org_id"]?.toString();
} catch (e) {
return null;
}
}
return null;
}
Future<String?> getToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('auth_token');
}
// Fetch API Data
Future<List<Plan>> fetchPlans() async {
// final String apiUrldata = '$apiUrl/api/plans';
// final String apiUrldata = '$apiUrl/api/plans?user_id=$userId';
// final String apiUrldata = '$apiUrl/api/plans?user_id=$userId';
// final String apiUrldata = '$apiUrl/api/plans?org_id=$orgId&user_id=$userId';
final String apiUrldata =
'$apiUrl/api/plans/findApprovalList?user_id=$userId&org_id=$orgId';
// api/plans?org_id=1&user_id=1
// 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', // Add token here
'Content-Type': 'application/json',
},
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
plansJson = data['data'];
return plansJson.map((json) => Plan.fromJson(json)).toList();
} else {
throw Exception('Failed to load plans');
}
}
// Future<Map<String, dynamic>> getViewPlan(String planId) async {
// final String apiUrldata = '$apiUrl/api/plans/find/$planId';
// print("API URL: $apiUrldata");
// // final token = await getToken();
//
// if (token == null) {
// throw Exception('Token not found. Please log in.');
// }
//
// final response = await http.put(
// Uri.parse(apiUrldata),
// headers: {
// 'Authorization': 'Bearer $token', // Add token here
// 'Content-Type': 'application/json',
// },
// );
//
// if (response.statusCode == 200) {
// final Map<String, dynamic>? resData = json.decode(response.body);
//
// return resData?["data"];
// } else {
// throw Exception('Failed to load plans');
// }
// }
// Future<Map<String, dynamic>> getViewPlan(String planId, List plansJson) async {
// try {
// final plan = plansJson.firstWhere(
// (item) => item["plan_id"].toString() == planId,
// orElse: () => null,
// );
//
// if (plan == null) {
// throw Exception("Plan with ID $planId not found.");
// }
//
// return Map<String, dynamic>.from(plan);
// } catch (e) {
// throw Exception("Error finding plan: $e");
// }
// }
void viewPlanforApprover(String planId,
{bool isViewMode = false, bool isApprover = true}) async {
try {
Map<String, dynamic> planData =
await apiService.getViewPlan(planId, plansJson);
print("ViewAAA - $planData");
context.go('/createPlan', extra: {
'planData': planData,
'isViewMode': isViewMode,
'isApprover': isApprover
});
} catch (e) {
print("Error fetching plan: $e");
}
}
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
backgroundColor: Colors.white,
appBar: isDesktop ? null : const CustomAppBar(title: 'Home'),
drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
body: Row(
children: [
if (isDesktop) CustomDrawer(isDesktop: true),
Expanded(child: buildTableLayout(isDesktop))
],
),
);
});
}
Widget buildTableLayout(isDesktop) {
return Container(
color: bodyColor,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Container(
padding: const EdgeInsets.all(10.0),
color: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 2),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const Text('List For Approval',
style: TextStyle(
fontFamily: "Archivo",
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF212121))),
],
),
],
),
SizedBox(height: 2),
Divider(
thickness: 0.2, // how "thick" the line is
color: Colors.grey, // optional
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
width: MediaQuery.of(context).size.width * 0.2,
// or use Flexible
child: TextField(
onChanged: (query) {},
decoration: InputDecoration(
hintText: "Search for a plan",
hintStyle:
TextStyle(fontSize: 14, color: Color(0xFF9E9DBD)),
prefixIcon:
Icon(Icons.search, color: Color(0xFF9E9DBD)),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey.shade300, width: 0.5),
),
focusedBorder: OutlineInputBorder(
// borderRadius: BorderRadius.circular(8),
borderSide:
BorderSide(color: Colors.blueAccent, width: 1),
),
),
),
),
// SizedBox(width: 16),
],
),
const SizedBox(height: 10),
FutureBuilder<List<Plan>>(
future: futurePlans,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError ||
!snapshot.hasData ||
snapshot.data!.isEmpty) {
return Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: const [
Icon(Icons.error_outline,
color: Colors.redAccent, size: 60),
SizedBox(height: 16),
Text("Oops!",
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.redAccent)),
SizedBox(height: 8),
Text("No Plans Available For This User",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.grey)),
SizedBox(height: 20),
Text("Please Create Plan",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16, color: Colors.grey)),
SizedBox(height: 20),
],
),
),
);
}
List<Plan> plans = snapshot.data!;
plans.sort((a, b) =>
int.parse(b.planId).compareTo(int.parse(a.planId)));
Widget table = LayoutBuilder(
builder: (context, constraints) {
double minWidth = isDesktop ? constraints.maxWidth : 1300;
return ConstrainedBox(
constraints: BoxConstraints(minWidth: minWidth),
child: DataTable(
dividerThickness: 0.5,
columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder(
horizontalInside: BorderSide(
width: 0.5, color: Colors.grey.shade200),
),
columns: const [
DataColumn(
label: Text('Plan Id',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontSize: 14,
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('Trip Planned User',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('Trip Title',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('Trip Type',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('Created On',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('Status',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('Actions',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
],
rows: plans.map((plan) {
return DataRow(cells: [
DataCell(Text(plan.planId,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
))),
DataCell(Text(
plan.userName.isNotEmpty
? plan.userName
: plan.travellerName,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
))),
DataCell(Text(plan.tripTitle,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
),
softWrap: true,
overflow: TextOverflow.ellipsis)),
DataCell(Text(plan.tripType,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
))),
DataCell(Text(plan.createdOn,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
))),
DataCell(Container(
padding: const EdgeInsets.symmetric(
vertical: 4, horizontal: 10),
decoration: BoxDecoration(
color: plan.status == "Active"
? layoutColor
: Colors.grey.shade50,
borderRadius: BorderRadius.circular(10),
),
child: Text(
plan.statusValue,
style: TextStyle(
color: plan.status == "Active"
? Colors.white
: Colors.grey,
fontSize: 13,
fontWeight: FontWeight.bold,
),
),
)),
DataCell(Row(children: [
IconButton(
icon: const Icon(
Icons.remove_red_eye,
color: Color(0xFF475569),
size: 18,
),
onPressed: () => viewPlanforApprover(
plan.planId,
isViewMode: true,
isApprover: true)),
GestureDetector(
onTap: () => viewPlanforApprover(plan.planId,
isViewMode: false, isApprover: true),
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15),
),
// IconButton(
// icon: const Icon(Icons.edit,
// color: Colors.green),
// onPressed: () => viewPlanforApprover(plan.planId,
// isViewMode: false) ),
])),
]);
}).toList(),
),
);
},
);
return Expanded(
child: isDesktop
? table
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table,
),
),
);
},
)
],
),
),
),
);
}
}

View File

@ -9,7 +9,8 @@ class LoginWidget extends StatefulWidget {
final bool isDesktop;
final bool isTablet;
const LoginWidget({Key? key, required this.isDesktop, required this.isTablet}) : super(key: key);
const LoginWidget({Key? key, required this.isDesktop, required this.isTablet})
: super(key: key);
@override
_LoginWidgetState createState() => _LoginWidgetState();
@ -21,35 +22,31 @@ class _LoginWidgetState extends State<LoginWidget> {
final TextEditingController _passwordController = TextEditingController();
bool _obscureText = true;
Future<void> storeUserDetails(String token) async{
try{
final parts = token.split('.');
if (parts.length != 3) throw Exception('Invalid token format');
Future<void> storeUserDetails(String token) async {
try {
final parts = token.split('.');
if (parts.length != 3) throw Exception('Invalid token format');
final payload = json.decode(
utf8.decode(base64Url.decode(base64Url.normalize(parts[1])))
);
final payload = json
.decode(utf8.decode(base64Url.decode(base64Url.normalize(parts[1]))));
final userData = payload['data'];
final userData = payload['data'];
final prefs = await SharedPreferences.getInstance();
await prefs.setString('auth_token', token);
await prefs.setString('user_data', jsonEncode(userData)); // Store full user data
final prefs = await SharedPreferences.getInstance();
await prefs.setString('auth_token', token);
await prefs.setString(
'user_data', jsonEncode(userData)); // Store full user data
if(userData != null){
final pref = await SharedPreferences.getInstance();
await pref.setString('auth_token', token);
await pref.setString('user_data', jsonEncode(userData));
if (userData != null) {
final pref = await SharedPreferences.getInstance();
await pref.setString('auth_token', token);
await pref.setString('user_data', jsonEncode(userData));
}
} catch (e) {
print('Error decoding token: $e');
}
}
catch(e){
print('Error decoding token: $e');
}
}
void _login(BuildContext context) async {
if (_formKey.currentState!.validate()) {
const String url = '$apiUrl/auth/login';
@ -57,7 +54,10 @@ class _LoginWidgetState extends State<LoginWidget> {
try {
final response = await http.post(
Uri.parse(url),
headers: {'Content-Type': 'application/json', 'Accept': 'application/json'},
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: jsonEncode({
'email': _emailController.text.trim(),
'password': _passwordController.text.trim(),
@ -73,7 +73,6 @@ class _LoginWidgetState extends State<LoginWidget> {
await storeUserDetails(token);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Login Successful"),
@ -83,7 +82,9 @@ class _LoginWidgetState extends State<LoginWidget> {
context.go('/home'); // Navigate to home
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Login Failed: ${jsonDecode(response.body)['message']}")),
SnackBar(
content: Text(
"Login Failed: ${jsonDecode(response.body)['message']}")),
);
}
} catch (e) {
@ -95,39 +96,69 @@ class _LoginWidgetState extends State<LoginWidget> {
}
@override
/// Layout
Widget build(BuildContext context) {
double formWidth = widget.isTablet ? 400 : 300;
return Row(
children: [
if (widget.isDesktop)
Expanded(
flex: 1,
child: Container(
color: Colors.blueAccent,
child: const Center(
child: Text(
"Welcome Back!",
style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Colors.white),
return Container(
color: Color(0xFF114D8B),
padding: const EdgeInsets.all(20),
child: Row(
children: [
if (widget.isDesktop)
Expanded(
flex: 2,
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/login/login_img1.png'),
fit: BoxFit.fill
// fit: BoxFit.cover, // or BoxFit.contain, BoxFit.fill, etc.
),
color: Colors.blueAccent,
borderRadius: BorderRadius.only(
topRight: Radius.circular(25), // Rounded top-left corner
bottomRight:
Radius.circular(25), // Rounded bottom-left corner
),
),
child: Padding(
padding: const EdgeInsets.only(left: 30),
child: Align(
alignment: Alignment.topLeft,
child: Image.asset(
'assets/images/login/TravelSpendsLogo1.png',
width: 200, // Optional: control size
height: 100,
fit: BoxFit.contain,
)),
),
),
),
Expanded(
flex: 1,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(25), // Rounded top-left corner
bottomLeft: Radius.circular(25), // Rounded bottom-left corner
),
),
child: Center(
child: Padding(
padding: const EdgeInsets.all(40),
child: _buildForm(width: formWidth), // Fixed form width
),
),
),
),
),
Expanded(
flex: 1,
child: Center(
child: Padding(
padding: const EdgeInsets.all(40),
child: _buildForm(width: formWidth), // Fixed form width
),
),
),
],
],
),
);
}
/// **Reusable Login Form**
Widget _buildForm({required double width}) {
return SizedBox(
@ -138,47 +169,140 @@ class _LoginWidgetState extends State<LoginWidget> {
mainAxisSize: MainAxisSize.min,
children: [
const Text(
"Hello! Welcome Back",
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.blueAccent),
"Sign In",
style: TextStyle(
fontSize: 26,
fontFamily: "Nunito",
fontWeight: FontWeight.w600,
color: Color(0xFF212121)),
),
const SizedBox(height: 20),
const SizedBox(height: 2),
const Text(
"Welcome to trip system management",
style: TextStyle(
fontSize: 11,
fontFamily: "Nunito",
fontWeight: FontWeight.w400,
color: Color(0xFF212121)),
),
const SizedBox(height: 18),
/// **Email Field**
_buildLabel("EMAIL"),
_buildLabel("Email Address"),
TextFormField(
controller: _emailController,
decoration: _inputDecoration("Enter your email"),
validator: (value) => value == null || value.isEmpty ? 'Required Email' : null,
decoration: _inputDecoration("Enter your email address").copyWith(
prefixIcon: Icon(
Icons.email_outlined,
size: 16,
),
),
validator: (value) =>
value == null || value.isEmpty ? 'Required Email' : null,
),
const SizedBox(height: 16),
/// **Password Field**
_buildLabel("PASSWORD"),
_buildLabel("Password"),
TextFormField(
controller: _passwordController,
obscureText: _obscureText,
decoration: _inputDecoration("Enter your password").copyWith(
prefixIcon: Icon(
Icons.key,
size: 16,
),
suffixIcon: IconButton(
icon: Icon(_obscureText ? Icons.visibility_off : Icons.visibility, color: Colors.blueAccent),
icon: Icon(
_obscureText ? Icons.visibility_off : Icons.visibility,
color: Color(0xFF12B24B),
size: 16,
),
onPressed: () => setState(() => _obscureText = !_obscureText),
),
),
validator: (value) => value == null || value.isEmpty ? 'Required Password' : null,
validator: (value) =>
value == null || value.isEmpty ? 'Required Password' : null,
),
const SizedBox(height: 20),
/// **Login Button**
ElevatedButton(
onPressed: () => _login(context),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blueAccent, // Button color
foregroundColor: Colors.white, // Text color
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () => _login(context),
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF12B24B), // Button color
foregroundColor: Colors.white, // Text color
padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18)),
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 10),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Sign In",
style: TextStyle(
fontFamily: "Nunito",
fontWeight: FontWeight.w800,
fontSize: 15),
),
SizedBox(
width: 3,
),
Icon(
Icons.arrow_forward_sharp,
color: Colors.white,
)
],
),
),
),
),
],
),
const SizedBox(height: 20),
Center(
child: Text(
"Forgot Password",
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w300,
fontFamily: "Nunito",
color: Color(0xFF212121), // Text color
decoration: TextDecoration.underline, // Underline the text
),
),
child: const Text("Login"),
),
const SizedBox(height: 20),
Container(
width: 45, // Adjust size
height: 45,
decoration: BoxDecoration(
// Background color
shape: BoxShape.rectangle,
borderRadius: BorderRadius.all(Radius.circular(15)),
border: Border.all(
color: Color(0xFF9E9DBD), width: 1), // Grey outline
),
child: Center(
child: Image.asset(
'assets/images/login/VectorG.png',
width: 20, // Optional: control size
height: 10,
fit: BoxFit.contain,
)),
),
],
),
@ -194,7 +318,11 @@ class _LoginWidgetState extends State<LoginWidget> {
padding: const EdgeInsets.only(bottom: 8),
child: Text(
text,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.blueAccent),
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
fontFamily: "Nunito",
color: Color(0xFF212121)),
),
),
);
@ -205,11 +333,16 @@ class _LoginWidgetState extends State<LoginWidget> {
return InputDecoration(
labelText: hint,
floatingLabelBehavior: FloatingLabelBehavior.never,
contentPadding: EdgeInsets.symmetric(vertical: 1.0, horizontal: 0.0),
filled: true,
fillColor: Colors.white,
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(18),
borderSide: BorderSide(
color: Color(0xFF212121), // Border color
width: 1, // Optional: adjust the width of the border
),
),
);
}

View File

@ -12,11 +12,13 @@ import '../../widgets/custom_text_traveller.dart';
class UserSelectionDialog extends StatefulWidget {
final String title;
final Color layoutColorForUser;
final void Function(String, String, bool) onSubmit;
UserSelectionDialog({
Key? key,
required this.title,
required this.layoutColorForUser,
required this.onSubmit,
}) : super(key: key);
@ -302,6 +304,10 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
prefixIcon: Icon(Icons.search),
border:
OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey, width: 1),
// borderSide: BorderSide(color: Color(0xFFF5F5F5), width: 2),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Colors.blueAccent, width: 2),
@ -309,8 +315,9 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
),
),
SizedBox(height: 10),
if (widget.title == "Others") ...[
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -324,8 +331,8 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
});
},
child: Text("Create",
style:
TextStyle(fontSize: 14, color: Colors.blueAccent)),
style: TextStyle(
fontSize: 14, color: widget.layoutColorForUser)),
),
],
),
@ -412,10 +419,11 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.blueAccent,
foregroundColor: widget.layoutColorForUser,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Colors.blueAccent, width: 2),
side: BorderSide(
color: widget.layoutColorForUser, width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
@ -427,11 +435,12 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
SizedBox(width: 10),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blueAccent,
backgroundColor: widget.layoutColorForUser,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Colors.blueAccent, width: 2),
side: BorderSide(
color: widget.layoutColorForUser, width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),

View File

@ -36,6 +36,9 @@ class Group extends StatefulWidget {
class _groupState extends State<Group> {
final ApiService apiService = ApiService();
Color? layoutColor;
Color? bodyColor;
String? orgId;
String? userId;
String? selectedGroupId;
@ -72,7 +75,11 @@ class _groupState extends State<Group> {
@override
void initState() {
super.initState();
loadAllServices();
WidgetsBinding.instance.addPostFrameCallback((_) {
loadAllServices();
loadInitialData();
});
for (var field in dataHeader) {
controllers[field] = TextEditingController();
@ -81,6 +88,21 @@ class _groupState extends State<Group> {
updateData();
}
void loadInitialData() async {
String? layoutString = await getLayoutColor();
String? bodyStringColor = await getBodyColor();
setState(() {
layoutColor = layoutString != null
? Color(int.parse(layoutString))
: Colors.redAccent;
bodyColor = bodyStringColor != null
? Color(int.parse(bodyStringColor))
: Colors.white;
});
}
Future<void> updateData() async {
// Ensure apiselectedUser is not null before printing
if (widget.group != null) {
@ -247,7 +269,8 @@ class _groupState extends State<Group> {
Widget buildOrganizationLayout(isDesktop) {
return Container(
color: Colors.white,
color: bodyColor,
// color: Colors.white,
width: double.infinity,
height: MediaQuery.of(context).size.height,
margin: const EdgeInsets.all(8),
@ -258,7 +281,8 @@ class _groupState extends State<Group> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color: Colors.white,
// color: Colors.white,
padding: const EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -269,7 +293,9 @@ class _groupState extends State<Group> {
Container(
color: Color(0xFFE9EBF6),
child: IconButton(
icon: Icon(Icons.close),
icon: Icon(
Icons.close,
),
onPressed: () {
context.go('/group');
},
@ -278,12 +304,14 @@ class _groupState extends State<Group> {
],
),
),
SizedBox(
height: 30,
),
// SizedBox(
// height: 5,
// ),
Container(
margin: const EdgeInsets.all(10),
padding: const EdgeInsets.all(20),
color: Colors.grey.shade100,
color: Colors.white,
height: MediaQuery.of(context).size.height * 0.8,
child: Column(
children: [
// Row(
@ -324,7 +352,9 @@ class _groupState extends State<Group> {
children: _buildSecondRow(isDesktop),
),
SizedBox(height: 15),
// SizedBox(height: 15),
Spacer(),
isDesktop
? Row(
mainAxisAlignment: MainAxisAlignment.end,
@ -587,10 +617,10 @@ class _groupState extends State<Group> {
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.blueAccent,
foregroundColor: layoutColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Colors.blueAccent, width: 2),
side: BorderSide(color: layoutColor ?? Colors.green, width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
@ -607,14 +637,14 @@ class _groupState extends State<Group> {
// : SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blueAccent, // Keep original color
backgroundColor: layoutColor, // Keep original color
foregroundColor: Colors.white, // Keep original color
disabledBackgroundColor:
Colors.blueAccent, // Ensure color remains when disabled
disabledForegroundColor: Colors.white,
// disabledBackgroundColor:
// Colors.blueAccent, // Ensure color remains when disabled
// disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Colors.blueAccent, width: 2),
// side: BorderSide(color: Colors.blueAccent, width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),

View File

@ -6,6 +6,7 @@ import 'package:responsive_builder/responsive_builder.dart';
import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
class GroupList extends StatefulWidget {
@override
@ -16,11 +17,32 @@ class _GroupListState extends State<GroupList> {
final ApiService apiService = ApiService();
List<dynamic>? apiAllGroups;
Color? layoutColor;
Color? bodyColor;
@override
void initState() {
super.initState();
loadAllGroups();
WidgetsBinding.instance.addPostFrameCallback((_) {
loadAllGroups();
loadInitialData();
});
}
void loadInitialData() async {
String? layoutString = await getLayoutColor();
String? bodyStringColor = await getBodyColor();
setState(() {
layoutColor = layoutString != null
? Color(int.parse(layoutString))
: Colors.redAccent;
bodyColor = bodyStringColor != null
? Color(int.parse(bodyStringColor))
: Colors.white;
});
}
Future<void> loadAllGroups() async {
@ -71,10 +93,12 @@ class _GroupListState extends State<GroupList> {
Widget buildGroupListLayout(bool isDesktop) {
return Container(
color: Colors.white,
color: bodyColor,
// color: Colors.white,
width: double.infinity,
height: MediaQuery.of(context).size.height,
margin: const EdgeInsets.all(8),
// margin: const EdgeInsets.all(8),
padding: const EdgeInsets.all(8),
child: Column(
children: [
Row(
@ -93,33 +117,44 @@ class _GroupListState extends State<GroupList> {
),
ElevatedButton(
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.blueAccent),
foregroundColor: Colors.white,
backgroundColor: layoutColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
// side: BorderSide(color: , width: 1),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () async {
// List<dynamic> users = await futureUsers;
context.go('/CreateGroup');
},
child: Row(
children: [
Icon(
Icons.add_circle,
color: Colors.white,
),
Text('New Group'),
SizedBox(
width: 5,
),
Text('New Group'),
Icon(
Icons.add_circle_outline_rounded,
color: Colors.white,
),
],
),
),
],
),
SizedBox(
height: 5,
),
Row(
children: [
Expanded(
child: Container(
height: MediaQuery.of(context).size.height * 0.88,
margin: const EdgeInsets.only(bottom: 10),
height: MediaQuery.of(context).size.height * 0.899,
padding: const EdgeInsets.all(10),
// margin: const EdgeInsets.only(bottom: 10),
color: Colors.white,
// color: Colors.red.shade100,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
@ -156,6 +191,8 @@ class _GroupListState extends State<GroupList> {
itemBuilder: (context, index) {
final group = apiAllGroups![index];
return Card(
// color: bodyColor,
color: Color(0xFFF5F5F5),
margin: EdgeInsets.symmetric(vertical: 6, horizontal: 10),
child: Padding(
padding: const EdgeInsets.all(12.0),

View File

@ -1,34 +1,87 @@
import 'package:flutter/material.dart';
class AccomodationListWidget extends StatelessWidget {
final List<Map<String,dynamic>> accommodationList;
final List<Map<String, dynamic>> accommodationList;
final Function(bool, Map<String, dynamic>, String) onOpen;
final Function(Map<String, dynamic>) onDeleteAccommodation;
final Function(Map<String, dynamic>) onDeleteAccommodation;
const AccomodationListWidget({super.key, required this.accommodationList, required this.onOpen, required this.onDeleteAccommodation});
final Function(String, bool) onAddNew;
final bool isViewMode;
const AccomodationListWidget(
{super.key,
required this.accommodationList,
required this.onOpen,
required this.onDeleteAccommodation,
required this.onAddNew,
required this.isViewMode});
@override
Widget build(BuildContext context) {
return
Padding(
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Accomodation Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Accomodation Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: isViewMode
? null
: () {
print("New data");
onAddNew("Accomodation", true);
},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add New",
style: TextStyle(fontSize: 13),
),
SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_outline_rounded,
size: 15,
color: Colors.white,
),
],
),
),
),
],
),
const SizedBox(height: 16),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
width: MediaQuery.of(context).size.width ,
width: MediaQuery.of(context).size.width,
child: DataTable(
border: TableBorder(
bottom: BorderSide(color: Colors.black12),
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
horizontalInside: BorderSide(
color: Colors.black12), // Only horizontal lines
),
columns: const [
DataColumn(label: Text('#')),
@ -48,45 +101,65 @@ class AccomodationListWidget extends StatelessWidget {
}
List<DataRow> _buildDataRows() {
List<Map<String, dynamic>> filteredList = accommodationList
.where((item) => item["is_active"] == "1")
.toList();
List<Map<String, dynamic>> filteredList =
accommodationList.where((item) => item["is_active"] == "1").toList();
print("filteredList- $filteredList");
return filteredList.asMap() .entries.map((entry) {
return filteredList.asMap().entries.map((entry) {
final Map<String, dynamic> item = entry.value;
return DataRow(cells: [
DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
DataCell(Text(item["destination_city"]!)),
DataCell(Text(item["hotel_name"]!)),
DataCell(Text(item["checkin_date"]!)),
DataCell(Text(item["checkout_date"]!)),
DataCell(Row(
children: [
IconButton(
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
onPressed: () {
// View action
},
GestureDetector(
onTap: () => onOpen(true, item, "Accomodation"),
child: Image.asset('assets/images/IconsImg/edit.png',
width: 20, height: 15),
),
SizedBox(width: 10),
GestureDetector(
onTap: () => onDeleteAccommodation(item),
child: Image.asset('assets/images/IconsImg/delete.png',
width: 20, height: 15),
),
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
icon: Icon(Icons.keyboard_arrow_down_outlined,
size: 28, color: Color(0xFF475569)),
onPressed: () {
onOpen(true, item, "Accomodation");
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
onDeleteAccommodation(item);
// Expand logic
},
),
],
)),
))
// Row(
// children: [
// IconButton(
// icon: Icon(Icons.remove_red_eye, color: Colors.blue),
// onPressed: () {
// // View action
// },
// ),
// IconButton(
// icon: Icon(Icons.edit, color: Colors.green),
// onPressed: () {
// onOpen(true, item, "Accomodation");
// },
// ),
// IconButton(
// icon: Icon(Icons.delete, color: Colors.red),
// onPressed: () {
// onDeleteAccommodation(item);
// },
// ),
// ],
// )),
//
]);
}).toList();
}

View File

@ -11,11 +11,17 @@ class BusListWidget extends StatelessWidget {
final List<Map<String, dynamic>> busList;
final Function(bool, Map<String, dynamic>, String) onOpen;
final Function(Map<String, dynamic>) onDeleteBus;
final Function(String, bool) onAddNew;
final bool isViewMode;
const BusListWidget(
{super.key,
required this.busList,
required this.onOpen,
required this.onDeleteBus});
required this.onDeleteBus,
required this.onAddNew,
required this.isViewMode});
@override
Widget build(BuildContext context) {
@ -24,9 +30,54 @@ class BusListWidget extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Bus Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Bus Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: isViewMode
? null
: () {
print("New data");
onAddNew("Bus", true);
},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add New",
style: TextStyle(fontSize: 13),
),
SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_outline_rounded,
size: 15,
color: Colors.white,
),
],
),
),
),
],
),
const SizedBox(height: 16),
Container(
@ -83,26 +134,51 @@ class BusListWidget extends StatelessWidget {
DataCell(Text(item["time"]!)),
DataCell(Row(
children: [
IconButton(
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
onPressed: () {
// View action
},
GestureDetector(
onTap: () => onOpen(true, item, "Bus"),
child: Image.asset('assets/images/IconsImg/edit.png',
width: 20, height: 15),
),
SizedBox(width: 10),
GestureDetector(
onTap: () => onDeleteBus(item),
child: Image.asset('assets/images/IconsImg/delete.png',
width: 20, height: 15),
),
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
icon: Icon(Icons.keyboard_arrow_down_outlined,
size: 28, color: Color(0xFF475569)),
onPressed: () {
onOpen(true, item, "Bus");
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
onDeleteBus(item);
// Expand logic
},
),
],
)),
)
// Row(
// children: [
// IconButton(
// icon: Icon(Icons.remove_red_eye, color: Colors.blue),
// onPressed: () {
// // View action
// },
// ),
// IconButton(
// icon: Icon(Icons.edit, color: Colors.green),
// onPressed: () {
// onOpen(true, item, "Bus");
// },
// ),
// IconButton(
// icon: Icon(Icons.delete, color: Colors.red),
// onPressed: () {
// onDeleteBus(item);
// },
// ),
// ],
// )
),
]);
}).toList();
}

View File

@ -1,11 +1,20 @@
import 'package:flutter/material.dart';
class FlightListWidget extends StatelessWidget {
final List<Map<String,dynamic>> flightList;
final Function( bool,Map<String,dynamic>, String) onOpen;
final Function(Map<String,dynamic>) onDeleteFlight;
final List<Map<String, dynamic>> flightList;
final Function(bool, Map<String, dynamic>, String) onOpen;
final Function(Map<String, dynamic>) onDeleteFlight;
const FlightListWidget({super.key, required this.flightList, required this.onOpen, required this.onDeleteFlight});
final Function(String, bool) onAddNew;
final bool isViewMode;
const FlightListWidget(
{super.key,
required this.flightList,
required this.onOpen,
required this.onDeleteFlight,
required this.onAddNew,
required this.isViewMode});
@override
Widget build(BuildContext context) {
@ -14,80 +23,151 @@ class FlightListWidget extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Flight Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Flight Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: isViewMode
? null
: () {
print("New data");
onAddNew("Flight", true);
},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add New",
style: TextStyle(fontSize: 13),
),
SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_outline_rounded,
size: 15,
color: Colors.white,
),
],
),
),
),
],
),
const SizedBox(height: 16),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
// width: 1000,
width: MediaQuery.of(context).size.width ,
child: DataTable(
border: TableBorder(
bottom: BorderSide(color: Colors.black12),
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
),
columns: const [
// DataColumn(label: Text('#')),
DataColumn(label: Text('Trip Type')),
DataColumn(label: Text('From')),
DataColumn(label: Text('To')),
DataColumn(label: Text('Actions')),
],
rows: _buildDataRows(),
),
),
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
// width: 1000,
width: MediaQuery.of(context).size.width,
child: DataTable(
border: TableBorder(
bottom: BorderSide(color: Colors.black12),
horizontalInside: BorderSide(
color: Colors.black12), // Only horizontal lines
),
columns: const [
// DataColumn(label: Text('#')),
DataColumn(label: Text('Trip Type')),
DataColumn(label: Text('From')),
DataColumn(label: Text('To')),
DataColumn(label: Text('Actions')),
],
rows: _buildDataRows(),
),
),
),
],
),
);
}
List<DataRow> _buildDataRows() {
List<Map<String, dynamic>> filteredList = flightList
.where((item) => item["is_active"] == "1")
.toList();
List<Map<String, dynamic>> filteredList =
flightList.where((item) => item["is_active"] == "1").toList();
print("filteredList- $filteredList");
return filteredList.asMap().entries.map((entry) {
Map<String,dynamic> item = entry.value;
Map<String, dynamic> item = entry.value;
print("Trip Type: ${item["trip_type"]}");
return DataRow(cells: [
// DataCell(Text(item["indx"]?.toString() ?? "N/A")),
DataCell(Text(item["trip_type"]?.toString() ?? "N/A")),
DataCell(Text(item["trips"].isNotEmpty ? item["trips"][0]["from_place"]?.toString() ?? "N/A" : "N/A")),
DataCell(Text(item["trips"].isNotEmpty ? item["trips"][0]["to_place"]?.toString() ?? "N/A" : "N/A")),
DataCell(Text(item["trips"].isNotEmpty
? item["trips"][0]["from_place"]?.toString() ?? "N/A"
: "N/A")),
DataCell(Text(item["trips"].isNotEmpty
? item["trips"][0]["to_place"]?.toString() ?? "N/A"
: "N/A")),
DataCell(Row(
children: [
IconButton(
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
onPressed: () {
// View action
},
),
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
onPressed: () {
onOpen(true, item, "Flight");
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
onDeleteFlight(item);
},
),
],
)),
DataCell(
Row(
children: [
GestureDetector(
onTap: () => onOpen(true, item, "Flight"),
child: Image.asset('assets/images/IconsImg/edit.png',
width: 20, height: 15),
),
SizedBox(width: 10),
GestureDetector(
onTap: () => onDeleteFlight(item),
child: Image.asset('assets/images/IconsImg/delete.png',
width: 20, height: 15),
),
IconButton(
icon: Icon(Icons.keyboard_arrow_down_outlined,
size: 28, color: Color(0xFF475569)),
onPressed: () {
// Expand logic
},
),
],
),
//
// Row(
// children: [
// IconButton(
// icon: Icon(Icons.remove_red_eye, color: Colors.blue),
// onPressed: () {
// // View action
// },
// ),
// IconButton(
// icon: Icon(Icons.edit, color: Colors.green),
// onPressed: () {
// onOpen(true, item, "Flight");
// },
// ),
// IconButton(
// icon: Icon(Icons.delete, color: Colors.red),
// onPressed: () {
// onDeleteFlight(item);
// },
// ),
// ],
// )
//
),
]);
}).toList();
}

View File

@ -8,12 +8,17 @@ class ForexListWidget extends StatelessWidget {
final Function(Map<String, dynamic>) onDeleteForex;
final List<dynamic>? apiCountryData;
final Function(String, bool) onAddNew;
final bool isViewMode;
const ForexListWidget(
{super.key,
required this.forexList,
required this.onOpen,
required this.onDeleteForex,
required this.apiCountryData});
required this.apiCountryData,
required this.onAddNew,
required this.isViewMode});
@override
Widget build(BuildContext context) {
@ -22,9 +27,54 @@ class ForexListWidget extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Forex List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Forex List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: isViewMode
? null
: () {
print("New data");
onAddNew("Forex", true);
},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add New",
style: TextStyle(fontSize: 13),
),
SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_outline_rounded,
size: 15,
color: Colors.white,
),
],
),
),
),
],
),
const SizedBox(height: 16),
Container(
@ -92,26 +142,51 @@ class ForexListWidget extends StatelessWidget {
DataCell(Text(item["perdiem_amount"] ?? "N/A")),
DataCell(Row(
children: [
IconButton(
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
onPressed: () {
// View action
},
GestureDetector(
onTap: () => onOpen(true, item, "Forex"),
child: Image.asset('assets/images/IconsImg/edit.png',
width: 20, height: 15),
),
SizedBox(width: 10),
GestureDetector(
onTap: () => onDeleteForex(item),
child: Image.asset('assets/images/IconsImg/delete.png',
width: 20, height: 15),
),
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
icon: Icon(Icons.keyboard_arrow_down_outlined,
size: 28, color: Color(0xFF475569)),
onPressed: () {
onOpen(true, item, "Forex");
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
onDeleteForex(item);
// Expand logic
},
),
],
)),
)
// Row(
// children: [
// IconButton(
// icon: Icon(Icons.remove_red_eye, color: Colors.blue),
// onPressed: () {
// // View action
// },
// ),
// IconButton(
// icon: Icon(Icons.edit, color: Colors.green),
// onPressed: () {
// onOpen(true, item, "Forex");
// },
// ),
// IconButton(
// icon: Icon(Icons.delete, color: Colors.red),
// onPressed: () {
// onDeleteForex(item);
// },
// ),
// ],
// )
),
]);
}).toList();
}

View File

@ -3,12 +3,22 @@ import 'dart:js_interop';
import 'package:flutter/material.dart';
class InsuranceListWidget extends StatelessWidget {
final List<Map<String,dynamic>> insuranceList;
final Function(bool, Map<String,dynamic>, String)onOpen;
final Function(Map<String,dynamic>)onDeleteInsurance;
final List<Map<String, dynamic>> insuranceList;
final Function(bool, Map<String, dynamic>, String) onOpen;
final Function(Map<String, dynamic>) onDeleteInsurance;
final Map<String, dynamic>? apiData;
const InsuranceListWidget({super.key, required this.insuranceList, required this.onOpen,required this.apiData,
required this.onDeleteInsurance});
final Function(String, bool) onAddNew;
final bool isViewMode;
const InsuranceListWidget(
{super.key,
required this.insuranceList,
required this.onOpen,
required this.apiData,
required this.onDeleteInsurance,
required this.onAddNew,
required this.isViewMode});
@override
Widget build(BuildContext context) {
@ -18,20 +28,67 @@ class InsuranceListWidget extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"Insurance Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Insurance Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding:
EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: isViewMode
? null
: () {
print("New data");
onAddNew("Insurance", true);
},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add New",
style: TextStyle(fontSize: 13),
),
SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_outline_rounded,
size: 15,
color: Colors.white,
),
],
),
),
),
],
),
const SizedBox(height: 16),
Center(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
width: MediaQuery.of(context).size.width ,
width: MediaQuery.of(context).size.width,
child: DataTable(
border: TableBorder(
bottom: BorderSide(color: Colors.black12),
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
horizontalInside: BorderSide(
color: Colors.black12), // Only horizontal lines
),
columns: const [
// DataColumn(label: Text('#')),
@ -52,58 +109,81 @@ class InsuranceListWidget extends StatelessWidget {
}
List<DataRow> _buildDataRows() {
List<Map<String, dynamic>> filteredList = insuranceList
.where((item) => item["is_active"] == "1")
.toList();
List<Map<String, dynamic>> filteredList =
insuranceList.where((item) => item["is_active"] == "1").toList();
print("filteredList- $filteredList");
List<dynamic> insurancetypeList = apiData?['insurance_type_of_insurance'] ?? [];
List<dynamic> insurancetypeList =
apiData?['insurance_type_of_insurance'] ?? [];
String getRequestForInsuranceType(String? specialRequestKey) {
if (specialRequestKey == null) return "N/A";
return insurancetypeList
.firstWhere(
(element) => element["dropdown_key"].toString() == specialRequestKey,
orElse: () => {"dropdown_value": "N/A"},
)["dropdown_value"]
(element) =>
element["dropdown_key"].toString() == specialRequestKey,
orElse: () => {"dropdown_value": "N/A"},
)["dropdown_value"]
.toString();
}
return filteredList.asMap().entries.map((entry) {
Map<String,dynamic> item = entry.value;
Map<String, dynamic> item = entry.value;
return DataRow(cells: [
// DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
// DataCell(Text(item["type_of_insurance"]!)),
DataCell( Text(getRequestForInsuranceType( item["type_of_insurance"]!.toString()))),
DataCell(Text(
getRequestForInsuranceType(item["type_of_insurance"]!.toString()))),
DataCell(Text(item["start_date"]!)),
DataCell(Text(item["end_date"]!)),
DataCell(Row(
children: [
IconButton(
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
onPressed: () {
// View action
},
GestureDetector(
onTap: () => onOpen(true, item, "Insurance"),
child: Image.asset('assets/images/IconsImg/edit.png',
width: 20, height: 15),
),
SizedBox(width: 10),
GestureDetector(
onTap: () => onDeleteInsurance(item),
child: Image.asset('assets/images/IconsImg/delete.png',
width: 20, height: 15),
),
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
icon: Icon(Icons.keyboard_arrow_down_outlined,
size: 28, color: Color(0xFF475569)),
onPressed: () {
onOpen(true, item, "Insurance");
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
onDeleteInsurance(item);
// Expand logic
},
),
],
)),
)
// Row(
// children: [
// IconButton(
// icon: Icon(Icons.remove_red_eye, color: Colors.blue),
// onPressed: () {
// // View action
// },
// ),
// IconButton(
// icon: Icon(Icons.edit, color: Colors.green),
// onPressed: () {
// onOpen(true, item, "Insurance");
// },
// ),
// IconButton(
// icon: Icon(Icons.delete, color: Colors.red),
// onPressed: () {
// onDeleteInsurance(item);
// },
// ),
// ],
// )
),
]);
}).toList();
}

View File

@ -1,14 +1,21 @@
import 'package:flutter/material.dart';
class MiscellaneousListWidget extends StatelessWidget {
final List<Map<String,dynamic>> miscellaneousList;
final List<Map<String, dynamic>> miscellaneousList;
final Function(bool, Map<String, dynamic>, String) onOpen;
final Function(Map<String, dynamic>) onDeleteMiscellaneous;
final Function(Map<String, dynamic>) onDeleteMiscellaneous;
final Map<String, dynamic>? apiData;
final Function(String, bool) onAddNew;
final bool isViewMode;
const MiscellaneousListWidget({super.key, required this.miscellaneousList, required this.onOpen,
required this.onDeleteMiscellaneous,required this.apiData});
const MiscellaneousListWidget(
{super.key,
required this.miscellaneousList,
required this.onOpen,
required this.onDeleteMiscellaneous,
required this.apiData,
required this.onAddNew,
required this.isViewMode});
@override
Widget build(BuildContext context) {
@ -18,20 +25,67 @@ class MiscellaneousListWidget extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"Miscellaneous Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Miscellaneous Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding:
EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: isViewMode
? null
: () {
print("New data");
onAddNew("Miscellaneous", true);
},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add New",
style: TextStyle(fontSize: 13),
),
SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_outline_rounded,
size: 15,
color: Colors.white,
),
],
),
),
),
],
),
const SizedBox(height: 16),
Center(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
width: MediaQuery.of(context).size.width ,
width: MediaQuery.of(context).size.width,
child: DataTable(
border: TableBorder(
bottom: BorderSide(color: Colors.black12),
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
horizontalInside: BorderSide(
color: Colors.black12), // Only horizontal lines
),
columns: const [
// DataColumn(label: Text('#')),
@ -62,23 +116,22 @@ class MiscellaneousListWidget extends StatelessWidget {
return purposeList
.firstWhere(
(element) => element["dropdown_key"].toString() == specialRequestKey,
orElse: () => {"dropdown_value": "N/A"},
)["dropdown_value"]
(element) =>
element["dropdown_key"].toString() == specialRequestKey,
orElse: () => {"dropdown_value": "N/A"},
)["dropdown_value"]
.toString();
}
List<Map<String, dynamic>> filteredList = miscellaneousList
.where((item) => item["is_active"] == "1")
.toList();
List<Map<String, dynamic>> filteredList =
miscellaneousList.where((item) => item["is_active"] == "1").toList();
print("filteredList- $filteredList");
return filteredList.asMap().entries.map( (entry) {
return filteredList.asMap().entries.map((entry) {
int index = entry.key + 1; // To start index from 1
Map<String, dynamic> item = entry.value;
print(item);
return DataRow(cells: [
// DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
// DataCell(Text(specialRequestValue)),
@ -87,28 +140,53 @@ class MiscellaneousListWidget extends StatelessWidget {
// DataCell(Text(item["created_on"] ?? "N/A")),
DataCell(Row(
children: [
IconButton(
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
onPressed: () {
// View action
},
GestureDetector(
onTap: () => onOpen(true, item, "Miscellaneous"),
child: Image.asset('assets/images/IconsImg/edit.png',
width: 20, height: 15),
),
SizedBox(width: 10),
GestureDetector(
onTap: () => onOpen(true, item, "Miscellaneous"),
child: Image.asset('assets/images/IconsImg/delete.png',
width: 20, height: 15),
),
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
icon: Icon(Icons.keyboard_arrow_down_outlined,
size: 28, color: Color(0xFF475569)),
onPressed: () {
onOpen(true, item, "Miscellaneous");
// Edit action
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
onDeleteMiscellaneous(item);
// Delete action
// Expand logic
},
),
],
)),
)
// Row(
// children: [
// IconButton(
// icon: Icon(Icons.remove_red_eye, color: Colors.blue),
// onPressed: () {
// // View action
// },
// ),
// IconButton(
// icon: Icon(Icons.edit, color: Colors.green),
// onPressed: () {
// onOpen(true, item, "Miscellaneous");
// // Edit action
// },
// ),
// IconButton(
// icon: Icon(Icons.delete, color: Colors.red),
// onPressed: () {
// onDeleteMiscellaneous(item);
// // Delete action
// },
// ),
// ],
// )
),
]);
}).toList();
}

View File

@ -1,11 +1,20 @@
import 'package:flutter/material.dart';
class TaxiListWidget extends StatelessWidget {
final List<Map<String,dynamic>> taxiList;
final Function(bool, Map<String,dynamic>, String) onOpen;
final Function(Map<String,dynamic>) onDeleteTaxi;
final List<Map<String, dynamic>> taxiList;
final Function(bool, Map<String, dynamic>, String) onOpen;
final Function(Map<String, dynamic>) onDeleteTaxi;
const TaxiListWidget({super.key, required this.taxiList, required this.onOpen, required this.onDeleteTaxi});
final Function(String, bool) onAddNew;
final bool isViewMode;
const TaxiListWidget(
{super.key,
required this.taxiList,
required this.onOpen,
required this.onDeleteTaxi,
required this.onAddNew,
required this.isViewMode});
@override
Widget build(BuildContext context) {
@ -14,20 +23,66 @@ class TaxiListWidget extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Taxi Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Taxi Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: isViewMode
? null
: () {
print("New data");
onAddNew("Taxi", true);
},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add New",
style: TextStyle(fontSize: 13),
),
SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_outline_rounded,
size: 15,
color: Colors.white,
),
],
),
),
),
],
),
const SizedBox(height: 16),
Center(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
width: MediaQuery.of(context).size.width ,
width: MediaQuery.of(context).size.width,
child: DataTable(
border: TableBorder(
bottom: BorderSide(color: Colors.black12),
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
horizontalInside: BorderSide(
color: Colors.black12), // Only horizontal lines
),
columns: const [
// DataColumn(label: Text('#')),
@ -48,14 +103,11 @@ class TaxiListWidget extends StatelessWidget {
}
List<DataRow> _buildDataRows() {
List<Map<String, dynamic>> filteredList = taxiList
.where((item) => item["is_active"] == "1")
.toList();
List<Map<String, dynamic>> filteredList =
taxiList.where((item) => item["is_active"] == "1").toList();
print("filteredList- $filteredList");
return filteredList.asMap().entries.map((entry) {
final Map<String, dynamic> item = entry.value;
return DataRow(cells: [
@ -66,26 +118,52 @@ class TaxiListWidget extends StatelessWidget {
DataCell(Text(item["car_required_for"]!)),
DataCell(Row(
children: [
IconButton(
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
onPressed: () {
// View action
},
GestureDetector(
onTap: () => onOpen(true, item, "Taxi"),
child: Image.asset('assets/images/IconsImg/edit.png',
width: 20, height: 15),
),
SizedBox(width: 10),
GestureDetector(
onTap: () => onDeleteTaxi(item),
child: Image.asset('assets/images/IconsImg/delete.png',
width: 20, height: 15),
),
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
icon: Icon(Icons.keyboard_arrow_down_outlined,
size: 28, color: Color(0xFF475569)),
onPressed: () {
onOpen(true, item, "Taxi");
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
onDeleteTaxi(item);
// Expand logic
},
),
],
)),
)
// Row(
// children: [
// IconButton(
// icon: Icon(Icons.remove_red_eye, color: Colors.blue),
// onPressed: () {
// // View action
// },
// ),
// IconButton(
// icon: Icon(Icons.edit, color: Colors.green),
// onPressed: () {
// onOpen(true, item, "Taxi");
// },
// ),
// IconButton(
// icon: Icon(Icons.delete, color: Colors.red),
// onPressed: () {
// onDeleteTaxi(item);
// },
// ),
// ],
// )
//
),
]);
}).toList();
}

View File

@ -1,10 +1,20 @@
import 'package:flutter/material.dart';
class TrainListWidget extends StatelessWidget {
final List<Map<String,dynamic>> trainList;
final Function(bool, Map<String,dynamic>, String) onOpen;
final Function(Map<String,dynamic>) onDeleteTrain;
const TrainListWidget({super.key, required this.trainList, required this.onOpen, required this.onDeleteTrain});
final List<Map<String, dynamic>> trainList;
final Function(bool, Map<String, dynamic>, String) onOpen;
final Function(Map<String, dynamic>) onDeleteTrain;
final Function(String, bool) onAddNew;
final bool isViewMode;
const TrainListWidget({
super.key,
required this.trainList,
required this.onOpen,
required this.onDeleteTrain,
required this.onAddNew,
required this.isViewMode,
});
@override
Widget build(BuildContext context) {
@ -14,20 +24,67 @@ class TrainListWidget extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"Train Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Train Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding:
EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: isViewMode
? null
: () {
print("New data");
onAddNew("Train", true);
},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add New",
style: TextStyle(fontSize: 13),
),
SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_outline_rounded,
size: 15,
color: Colors.white,
),
],
),
),
),
],
),
const SizedBox(height: 16),
Center(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
width: MediaQuery.of(context).size.width ,
width: MediaQuery.of(context).size.width,
child: DataTable(
border: TableBorder(
bottom: BorderSide(color: Colors.black12),
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
horizontalInside: BorderSide(
color: Colors.black12), // Only horizontal lines
),
columns: const [
// DataColumn(label: Text('#')),
@ -49,16 +106,12 @@ class TrainListWidget extends StatelessWidget {
}
List<DataRow> _buildDataRows() {
List<Map<String, dynamic>> filteredList = trainList
.where((item) => item["is_active"] == "1")
.toList();
List<Map<String, dynamic>> filteredList =
trainList.where((item) => item["is_active"] == "1").toList();
print("filteredList- $filteredList");
return filteredList.asMap().entries.map((entry) {
final Map<String,dynamic> item = entry.value;
final Map<String, dynamic> item = entry.value;
return DataRow(cells: [
// DataCell(Text(item["indx"]?.toString() ?? "N/A")),
@ -69,26 +122,52 @@ class TrainListWidget extends StatelessWidget {
// DataCell(Text(item["to_station"]!)),
DataCell(Row(
children: [
IconButton(
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
onPressed: () {
// View action
},
GestureDetector(
onTap: () => onOpen(true, item, "Train"),
child: Image.asset('assets/images/IconsImg/edit.png',
width: 20, height: 15),
),
SizedBox(width: 10),
GestureDetector(
onTap: () => onDeleteTrain(item),
child: Image.asset('assets/images/IconsImg/delete.png',
width: 20, height: 15),
),
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
icon: Icon(Icons.keyboard_arrow_down_outlined,
size: 28, color: Color(0xFF475569)),
onPressed: () {
onOpen(true, item, "Train");
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
onDeleteTrain(item);
// Expand logic
},
),
],
)),
)
// Row(
// children: [
// IconButton(
// icon: Icon(Icons.remove_red_eye, color: Colors.blue),
// onPressed: () {
// // View action
// },
// ),
// IconButton(
// icon: Icon(Icons.edit, color: Colors.green),
// onPressed: () {
// onOpen(true, item, "Train");
// },
// ),
// IconButton(
// icon: Icon(Icons.delete, color: Colors.red),
// onPressed: () {
// onDeleteTrain(item);
// },
// ),
// ],
// )
//
),
]);
}).toList();
}

View File

@ -3,12 +3,22 @@ import 'package:flutter/material.dart';
class VisaListWidget extends StatelessWidget {
final List<Map<String, dynamic>> visaList;
final Function(bool, Map<String, dynamic>, String) onOpen;
final Function(Map<String, dynamic>) onDeleteMiscellaneous;
final Function(Map<String, dynamic>) onDeleteMiscellaneous;
final Map<String, dynamic>? apiData;
final List<dynamic>? apiCountryData;
const VisaListWidget({super.key, required this.visaList, required this.onOpen,
required this.onDeleteMiscellaneous, required this.apiData, required this.apiCountryData});
final Function(String, bool) onAddNew;
final bool isViewMode;
const VisaListWidget(
{super.key,
required this.visaList,
required this.onOpen,
required this.onDeleteMiscellaneous,
required this.apiData,
required this.apiCountryData,
required this.onAddNew,
required this.isViewMode});
@override
Widget build(BuildContext context) {
@ -18,20 +28,67 @@ class VisaListWidget extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"Visa Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Visa Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding:
EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: isViewMode
? null
: () {
print("New data");
onAddNew("Visa", true);
},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add New",
style: TextStyle(fontSize: 13),
),
SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_outline_rounded,
size: 15,
color: Colors.white,
),
],
),
),
),
],
),
const SizedBox(height: 16),
Center(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
width: MediaQuery.of(context).size.width ,
width: MediaQuery.of(context).size.width,
child: DataTable(
border: TableBorder(
bottom: BorderSide(color: Colors.black12),
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
horizontalInside: BorderSide(
color: Colors.black12), // Only horizontal lines
),
columns: const [
// DataColumn(label: Text('#')),
@ -52,22 +109,22 @@ class VisaListWidget extends StatelessWidget {
}
List<DataRow> _buildDataRows() {
List<Map<String, dynamic>> filteredList = visaList
.where((item) => item["is_active"] == "1")
.toList();
List<Map<String, dynamic>> filteredList =
visaList.where((item) => item["is_active"] == "1").toList();
print("filteredList- $filteredList");
List<dynamic> visatypeList = apiData?['visa_type_of_visa'] ?? [];
List<dynamic> countryList = apiCountryData ?? [];
List<dynamic> countryList = apiCountryData ?? [];
String getRequestForVisa(String? specialRequestKey) {
if (specialRequestKey == null) return "N/A";
return visatypeList
.firstWhere(
(element) => element["dropdown_key"].toString() == specialRequestKey,
orElse: () => {"dropdown_value": "N/A"},
)["dropdown_value"]
(element) =>
element["dropdown_key"].toString() == specialRequestKey,
orElse: () => {"dropdown_value": "N/A"},
)["dropdown_value"]
.toString();
}
@ -77,47 +134,70 @@ class VisaListWidget extends StatelessWidget {
return countryList
.firstWhere(
(element) => element["country_code"].toString() == countryCode,
orElse: () => {"country_name": "N/A"},
)["country_name"]
orElse: () => {"country_name": "N/A"},
)["country_name"]
.toString();
}
return filteredList.asMap().entries.map((entry){
return filteredList.asMap().entries.map((entry) {
int index = entry.key + 1; // To start index from 1
Map<String, dynamic> item = entry.value;
print(item);
return DataRow(cells: [
// DataCell(Text(item["indx"]?.toString() ?? "N/A")),
// DataCell(Text(item["type_of_visa"]!)),
DataCell( Text(getRequestForVisa( item["type_of_visa"]!.toString()))),
DataCell( Text(getRequestForCountry( item["country_code"]!.toString()))),
DataCell(Text(getRequestForVisa(item["type_of_visa"]!.toString()))),
DataCell(Text(getRequestForCountry(item["country_code"]!.toString()))),
// DataCell(Text(item["country_code"]!)),
DataCell(Text(item["start_date"]!)),
DataCell(Row(
children: [
IconButton(
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
onPressed: () {
// View action
},
GestureDetector(
onTap: () => onOpen(true, item, "Visa"),
child: Image.asset('assets/images/IconsImg/edit.png',
width: 20, height: 15),
),
SizedBox(width: 10),
GestureDetector(
onTap: () => onDeleteMiscellaneous(item),
child: Image.asset('assets/images/IconsImg/delete.png',
width: 20, height: 15),
),
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
icon: Icon(Icons.keyboard_arrow_down_outlined,
size: 28, color: Color(0xFF475569)),
onPressed: () {
onOpen(true, item, "Visa");
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
onDeleteMiscellaneous(item);
// Expand logic
},
),
],
)),
)
//
// Row(
// children: [
// IconButton(
// icon: Icon(Icons.remove_red_eye, color: Colors.blue),
// onPressed: () {
// // View action
// },
// ),
// IconButton(
// icon: Icon(Icons.edit, color: Colors.green),
// onPressed: () {
// onOpen(true, item, "Visa");
// },
// ),
// IconButton(
// icon: Icon(Icons.delete, color: Colors.red),
// onPressed: () {
// onDeleteMiscellaneous(item);
// },
// ),
// ],
// )
),
]);
}).toList();
}

View File

@ -9,10 +9,12 @@ import '../../widgets/custom_text_field.dart';
class MailSetting extends StatefulWidget {
bool isDesktop;
final Function(Map<String, dynamic>) onMailDataChanged;
final Map<String, dynamic> initialMailData;
MailSetting({
super.key,
required this.isDesktop,
required this.initialMailData,
required this.onMailDataChanged,
});
@ -26,6 +28,7 @@ class _MailSettingState extends State<MailSetting> {
Map<String, String> errorMessages = {};
final Map<String, TextEditingController> controllers = {};
bool _obscurePassword = true;
List<String> dataHeader = [
"host",
@ -78,6 +81,27 @@ class _MailSettingState extends State<MailSetting> {
}
_initControllers();
updateData();
print("Updata 1");
print(widget.initialMailData['sender_email']);
}
void updateData() {
print("Updata 2");
if (widget.initialMailData.isNotEmpty) {
controllers["senderEmail"]?.text =
widget.initialMailData['sender_email'] ?? '';
controllers["userName"]?.text =
widget.initialMailData['mail_user_name'] ?? '';
controllers["password"]?.text =
widget.initialMailData['mail_password'] ?? '';
controllers["host"]?.text = widget.initialMailData['mail_host'] ?? '';
controllers["port"]?.text =
widget.initialMailData['mail_port']?.toString() ?? '';
}
}
void handleTestMailSubmit() {
@ -219,6 +243,16 @@ class _MailSettingState extends State<MailSetting> {
controller: controllers["senderEmail"],
onChanged: (value) {
_clearError("sender_email");
// Validate just this one field
if (value.trim().isEmpty) {
errorMessages["sender_email"] = "Required";
} else if (!RegExp(
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
.hasMatch(value)) {
errorMessages["sender_email"] =
"Invalid email format";
}
},
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
@ -281,7 +315,8 @@ class _MailSettingState extends State<MailSetting> {
children: [
Text(
"Test Mail",
style: TextStyle(color: Colors.blueAccent),
style: TextStyle(
color: Color(0xFF114D8B), fontWeight: FontWeight.w600),
),
],
),
@ -370,6 +405,7 @@ class _MailSettingState extends State<MailSetting> {
onChanged: (value) {
_clearError("mail_password");
},
obscureText: _obscurePassword,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "password",
@ -377,6 +413,19 @@ class _MailSettingState extends State<MailSetting> {
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword
? Icons.visibility_off
: Icons.visibility,
size: 16,
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
),
),
),
@ -514,6 +563,15 @@ class _MailSettingState extends State<MailSetting> {
controller: controllers["toEmail"],
onChanged: (value) {
_clearError("to_mail");
// Validate just this one field
if (value.trim().isEmpty) {
errorMessages["to_mail"] = "Required";
} else if (!RegExp(
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
.hasMatch(value)) {
errorMessages["to_mail"] = "Invalid email format";
}
},
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
@ -541,6 +599,18 @@ class _MailSettingState extends State<MailSetting> {
Column(
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B), // Keep original color
foregroundColor: Colors.white, // Keep original color
disabledBackgroundColor:
Color(0xFF114D8B), // Ensure color remains when disabled
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
handleTestMailSubmit();
},

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,201 @@
import 'package:flutter/material.dart';
import 'package:frontend/Screens/group/group.dart';
import 'package:go_router/go_router.dart';
import 'package:responsive_builder/responsive_builder.dart';
import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart';
class OrganizationList extends StatefulWidget {
const OrganizationList({super.key});
@override
_OrganizationListState createState() => _OrganizationListState();
}
class _OrganizationListState extends State<OrganizationList> {
final ApiService apiService = ApiService();
Map<String, dynamic>? apiAllOrganization;
@override
void initState() {
super.initState();
loadAllOrganization();
}
Future<void> loadAllOrganization() async {
try {
final result = await apiService.fetchOrganization();
setState(() {
apiAllOrganization = result;
});
print("Fetched services: $apiAllOrganization");
} catch (e) {
print('Error fetching organization list: $e');
}
}
// void deleteGroup(int groupId) {
// setState(() {
// apiAllGroups?.removeWhere((group) => group['group_id'] == groupId);
// });
// }
// Future<void> deleteGroupFromApi(int groupId) async {
// try {
// await apiService.deleteGroup(groupId); // your delete API call
// deleteGroup(groupId); // remove from UI list
// } catch (e) {
// print('Error deleting group: $e');
// }
// }'
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
backgroundColor: Colors.white,
appBar: isDesktop ? null : const CustomAppBar(title: 'Home'),
drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
body: Row(
children: [
if (isDesktop) CustomDrawer(isDesktop: true),
Expanded(child: buildGroupListLayout(isDesktop))
],
),
);
});
}
Widget buildGroupListLayout(bool isDesktop) {
return Container(
color: Colors.white,
width: double.infinity,
height: MediaQuery.of(context).size.height,
margin: const EdgeInsets.all(8),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const Text('Organization List',
style:
TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
IconButton(
icon: const Icon(Icons.keyboard_arrow_down),
onPressed: () {},
),
],
),
ElevatedButton(
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.blueAccent),
onPressed: () async {
// List<dynamic> users = await futureUsers;
// context.go('/CreateGroup');
},
child: Row(
children: [
Icon(
Icons.add_circle,
color: Colors.white,
),
SizedBox(
width: 5,
),
Text('Create Organization'),
],
),
),
],
),
Row(
children: [
Expanded(
child: Container(
height: MediaQuery.of(context).size.height * 0.88,
margin: const EdgeInsets.only(bottom: 10),
// color: Colors.red.shade100,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
children: [
buildGroupListView(isDesktop),
],
),
),
),
),
],
)
],
),
);
}
// Widget buildGroupListView(bool isDesktop) {
// return Container(
// child: Text("DAta"),
// );
// }
Widget buildGroupListView(bool isDesktop) {
if (apiAllOrganization == null || apiAllOrganization!.isEmpty) {
return Center(child: Text("No groups found."));
}
return ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: apiAllOrganization!.length,
itemBuilder: (context, index) {
final group = apiAllOrganization![index];
return Card(
margin: EdgeInsets.symmetric(vertical: 6, horizontal: 10),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Organization Name: ${group['name']}",
style: TextStyle(
fontSize: 13, fontWeight: FontWeight.bold)),
Text("Organization Name: ${group['name']}",
style: TextStyle(
fontSize: 13, fontWeight: FontWeight.bold)),
],
),
SizedBox(height: 4),
Text("Services: ${group['description'] ?? 'N/A'}"),
Text("Created By : ${group['created_by']}"),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
context.go("/CreateGroup", extra: group);
print("Edit ${group['group_id']} $group");
},
child: Text("Edit"),
),
],
),
],
),
),
);
},
);
}
}

View File

@ -1,11 +1,16 @@
import 'package:flutter/material.dart';
class ColorThemePickerWidget extends StatefulWidget {
final Color? initialLayoutColor;
final Color? initialBodyColor;
final Function(Color) onLayoutColorSelected;
final Function(Color) onBodyColorSelected;
const ColorThemePickerWidget({
Key? key,
required this.initialBodyColor,
required this.initialLayoutColor,
required this.onLayoutColorSelected,
required this.onBodyColorSelected,
}) : super(key: key);
@ -15,27 +20,37 @@ class ColorThemePickerWidget extends StatefulWidget {
}
class _ColorThemePickerWidgetState extends State<ColorThemePickerWidget> {
Color? selectedLayoutColor;
Color? selectedBodyColor;
late Color selectedLayoutColor;
late Color selectedBodyColor;
// Layout colors
final List<Color> layoutThemeColors = [
Color(0xFF448AFF), // BlueAccent
Color(0xFFF44336), // Red
Color(0xFF4CAF50), // Green
Color(0xFF12B24B), // Green
Color(0xFFFF9800), // Orange
Color(0xFF9C27B0), // Purple
];
// Body colors
final List<Color> bodyThemeColors = [
Colors.grey,
Colors.grey.shade300,
Colors.blue.shade50,
Colors.grey.shade100,
Colors.blueGrey.shade50,
Color(0xFFD9EAFF),
Color(0xFFB0BEC5), // Grey
Color(0xFFE0E0E0), // Grey Shade 300
Color(0xFFE1F5FE), // Blue Shade 50
Color(0xFFF5F5F5), // Grey Shade 100
];
@override
void initState() {
super.initState();
selectedLayoutColor = widget.initialLayoutColor ?? Color(0xFFB0BEC5);
selectedBodyColor = widget.initialBodyColor ?? Color(0xFFF44336);
print("Colors - ${widget.initialLayoutColor} -${widget.initialBodyColor}");
}
@override
Widget build(BuildContext context) {
return Row(
@ -63,7 +78,7 @@ class _ColorThemePickerWidgetState extends State<ColorThemePickerWidget> {
colors: bodyThemeColors,
onColorSelected: (color) {
setState(() {
selectedBodyColor = color.withOpacity(0.3); // low opacity
selectedBodyColor = color; // low opacity
});
widget.onBodyColorSelected(selectedBodyColor!);
},

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@ import 'package:frontend/Screens/itnerary_list/taxi_list.dart';
import 'package:frontend/Screens/itnerary_list/train_list.dart';
import 'package:responsive_builder/responsive_builder.dart';
import '../../services/apiService.dart';
import '../itnerary/accomodations.dart';
import '../itnerary/bus.dart';
import '../itnerary/flights.dart';
@ -25,19 +26,26 @@ class DynamicItinerary extends StatefulWidget {
final Map<String, dynamic>? apiData;
final List<dynamic>? apiCountryData;
final String? loginUser;
final Function(String, List<Map<String, dynamic>>) onItineraryUpdate; // Updated Signature
final Map<String,dynamic> selectedPlanData;
final bool isViewMode ;
const DynamicItinerary({super.key, required this.apiData, required this.onItineraryUpdate,
required this.apiCountryData, required this.loginUser,required this.selectedPlanData, required this.isViewMode});
final Function(String, List<Map<String, dynamic>>)
onItineraryUpdate; // Updated Signature
final Map<String, dynamic> selectedPlanData;
final bool isViewMode;
const DynamicItinerary(
{super.key,
required this.apiData,
required this.onItineraryUpdate,
required this.apiCountryData,
required this.loginUser,
required this.selectedPlanData,
required this.isViewMode});
@override
_DynamicItineraryState createState() => _DynamicItineraryState();
}
class _DynamicItineraryState extends State<DynamicItinerary> {
final ApiService apiService = ApiService();
String selectedOption = "";
String selectedListOption = "";
@ -46,13 +54,15 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
Map<String, dynamic>? selectedItem;
int? selectedIndex;
List<dynamic>? apiAllServices;
// List<Map<String, dynamic>> miscellaneousList = [];
Map<String, List<Map<String, dynamic>>> itineraryData = {
"Train": [],
"Bus": [],
"Taxi": [],
"Miscellaneous": [],
"Miscellaneous": [],
"Flight": [],
"Accomodation": [],
"Insurance": [],
@ -60,70 +70,93 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
"Forex": [],
};
// Store form values for each tab
final Map<String, Map<String, String>> formData = {
"Flight": {}, // Stores data for the Flight tab
"Train": {}, // Stores data for the Train tab
"Taxi": {}, // Stores data for the Car tab
"Bus": {}, // Stores data for the Bus tab
"Insurance": {}, // Stores data for the Bus tab
"Accomodation": {},// Stores data for the Accomodation tab
"Flight": {}, // Stores data for the Flight tab
"Train": {}, // Stores data for the Train tab
"Taxi": {}, // Stores data for the Car tab
"Bus": {}, // Stores data for the Bus tab
"Insurance": {}, // Stores data for the Bus tab
"Accomodation": {}, // Stores data for the Accomodation tab
"Miscellaneous": {},
"Forex": {},
"Visa": {},
};
@override
void initState() {
super.initState();
handleSelectedPlan();
loadAllServices();
}
void handleSelectedPlan(){
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');
}
}
void handleSelectedPlan() {
// Check if selectedPlanData has itinerary data
if (hasAnyItineraryData()) {
print("selectedPlanData HAS DATA");
setState(() {
itineraryData = {
"Train": List<Map<String, dynamic>>.from(widget.selectedPlanData['train'] ?? []),
"Bus": List<Map<String, dynamic>>.from(widget.selectedPlanData['bus'] ?? []),
"Taxi": List<Map<String, dynamic>>.from(widget.selectedPlanData['taxi'] ?? []),
"Miscellaneous": List<Map<String, dynamic>>.from(widget.selectedPlanData['miscellaneous'] ?? []),
"Flight": List<Map<String, dynamic>>.from(widget.selectedPlanData['flight'] ?? []),
"Accomodation": List<Map<String, dynamic>>.from(widget.selectedPlanData['accomodation'] ?? []),
"Insurance": List<Map<String, dynamic>>.from(widget.selectedPlanData['insurance'] ?? []),
"Visa": List<Map<String, dynamic>>.from(widget.selectedPlanData['visa'] ?? []),
"Forex": List<Map<String, dynamic>>.from(widget.selectedPlanData['forex'] ?? []),
"Train": List<Map<String, dynamic>>.from(
widget.selectedPlanData['train'] ?? []),
"Bus": List<Map<String, dynamic>>.from(
widget.selectedPlanData['bus'] ?? []),
"Taxi": List<Map<String, dynamic>>.from(
widget.selectedPlanData['taxi'] ?? []),
"Miscellaneous": List<Map<String, dynamic>>.from(
widget.selectedPlanData['miscellaneous'] ?? []),
"Flight": List<Map<String, dynamic>>.from(
widget.selectedPlanData['flight'] ?? []),
"Accomodation": List<Map<String, dynamic>>.from(
widget.selectedPlanData['accomodation'] ?? []),
"Insurance": List<Map<String, dynamic>>.from(
widget.selectedPlanData['insurance'] ?? []),
"Visa": List<Map<String, dynamic>>.from(
widget.selectedPlanData['visa'] ?? []),
"Forex": List<Map<String, dynamic>>.from(
widget.selectedPlanData['forex'] ?? []),
};
});
}
else {
} else {
print("No itinerary data available");
}
}
bool hasAnyItineraryData() {
List<String> keys = [
"train", "bus", "taxi", "miscellaneous", "flight",
"accomodation", "insurance", "visa", "forex"
"train",
"bus",
"taxi",
"miscellaneous",
"flight",
"accomodation",
"insurance",
"visa",
"forex"
];
for (String key in keys) {
if (widget.selectedPlanData.containsKey(key) &&
widget.selectedPlanData[key] is List &&
(widget.selectedPlanData[key] as List).isNotEmpty) {
return true; // At least one list has data
return true; // At least one list has data
}
}
return false; // No itinerary data available
}
void handleClose(bool value) {
setState(() {
selectedOption = "";
@ -131,23 +164,27 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
selectedIndex = null;
selectedItem = null;
});
}
void handleEdit(bool value,selectedItem, title) {
void handleEdit(bool value, selectedItem, title) {
setState(() {
selectedOption = title;
isSelected = value;
this.selectedItem = selectedItem;
});
print(selectedItem);
}
Widget build(BuildContext context) {
void handlecreateNewPlan(name, bool value) {
setState(() {
selectedOption = name;
isSelected = value;
this.selectedItem = null;
});
}
Widget build(BuildContext context) {
Widget selectedWidget;
Widget selectedListWidget;
@ -157,17 +194,15 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
// });
// }
void updateFormData(String tab, String key, String value) {
setState(() {
if (!formData.containsKey(tab)) {
formData[tab] = {}; // Initialize if null
formData[tab] = {}; // Initialize if null
}
formData[tab]![key] = value; // Update the stored data
formData[tab]![key] = value; // Update the stored data
});
}
void handleItineraryUpdate(String type, Map<String, dynamic> newData) {
setState(() {
if (!itineraryData.containsKey(type)) {
@ -181,9 +216,8 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
// int? existingId = newData["id"];
// String? existingId = newData["id"];
String? idKey = "${type.toLowerCase()}_id";
String? existingId = newData[idKey];
String? idKey = "${type.toLowerCase()}_id";
String? existingId = newData[idKey];
print("Looking for ID using key: $idKey, Found ID: $existingId");
@ -193,7 +227,8 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
// CASE 2: Update using id if available
if (existingId != null && existingId != 0) {
// int itemId = itemList.indexWhere((item) => item["id"] == existingId);
int itemId = itemList.indexWhere((item) => item[idKey]?.toString() == existingId.toString());
int itemId = itemList.indexWhere(
(item) => item[idKey]?.toString() == existingId.toString());
if (itemId != -1) {
print(" Updating existing item with id: $existingId");
@ -205,7 +240,8 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
// CASE 1: Update if indx exists in list
if (existingIndex != null && existingIndex != 0) {
int itemIndex = itemList.indexWhere((item) => item["indx"] == existingIndex);
int itemIndex =
itemList.indexWhere((item) => item["indx"] == existingIndex);
if (itemIndex != -1) {
print("Updating existing item with indx: $existingIndex");
newData["is_active"] = "1";
@ -221,62 +257,57 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
print(" Updated $type List: ${itineraryData[type]}");
});
print( " onItineraryUpdate - $type - ${itineraryData[type]!} ");
print(" onItineraryUpdate - $type - ${itineraryData[type]!} ");
widget.onItineraryUpdate(type, itineraryData[type]!); // Notify parent
}
void handleItinerarydelete(String type, Map<String, dynamic> data){
setState(() {
// if(!itineraryData.containsKey(type)){
// return;
// }
void handleItinerarydelete(String type, Map<String, dynamic> data) {
setState(() {
// if(!itineraryData.containsKey(type)){
// return;
// }
if (!itineraryData.containsKey(type)) {
itineraryData[type] = []; // Initialize if null
}
if (!itineraryData.containsKey(type)) {
itineraryData[type] = []; // Initialize if null
}
List<Map<String, dynamic>> itemList = itineraryData[type]!;
List<Map<String, dynamic>> itemList = itineraryData[type]!;
String? idKey = "${type.toLowerCase()}_id";
String? existingId = data[idKey];
// int? existingId = data["id"];
// String? existingId = data["id"];
int? existingIndex = data["indx"];
print("🗑️ Deleting item -> ID: $existingId, Index: $existingIndex");
// Delete by ID
if (existingId != null && existingId != 0) {
// itemList.removeWhere((item) => item[idKey]?.toString() == existingId.toString());
String? idKey = "${type.toLowerCase()}_id";
String? existingId = data[idKey];
// int? existingId = data["id"];
// String? existingId = data["id"];
int? existingIndex = data["indx"];
for (var item in itemList) {
if (item[idKey]?.toString() == existingId.toString()) {
item["is_active"] = 0; // Soft delete
print("Updated is_active to 0 for ID: $existingId");
}
}
itineraryData[type] = List.from(itemList);
print("Deleted by ID: $existingId");
}
//Delete by Index
else if (existingIndex != null && existingId != 0) {
itemList.removeWhere((item) => item["indx"] == existingIndex);
print("Deleted by ID: $existingIndex");
} else {
print("No Valid Deletion");
}
print("🗑️ Deleting item -> ID: $existingId, Index: $existingIndex");
// Delete by ID
if(existingId != null && existingId != 0){
// itemList.removeWhere((item) => item[idKey]?.toString() == existingId.toString());
for (var item in itemList) {
if (item[idKey]?.toString() == existingId.toString()) {
item["is_active"] = 0; // Soft delete
print("Updated is_active to 0 for ID: $existingId");
}
}
itineraryData[type] = List.from(itemList);
print("Deleted by ID: $existingId");
}
//Delete by Index
else if(existingIndex != null && existingId !=0){
itemList.removeWhere((item) => item["indx"] == existingIndex);
print("Deleted by ID: $existingIndex");
}
else{
print("No Valid Deletion");
}
itineraryData[type]= List.from(itemList);
});
print( " onItineraryUpdate - $type - ${itineraryData[type]!} ");
widget.onItineraryUpdate(type, itineraryData[type]!); // Notify parent
itineraryData[type] = List.from(itemList);
});
print(" onItineraryUpdate - $type - ${itineraryData[type]!} ");
widget.onItineraryUpdate(type, itineraryData[type]!); // Notify parent
}
// void handleItineraryUpdate(String type, Map<String, dynamic> newData) {
// setState(() {
// if (!itineraryData.containsKey(type)) {
@ -294,265 +325,416 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
// print("Updated $type List: ${itineraryData[type]}");
// }
switch (selectedListOption) {
case "Train":
selectedListWidget = TrainListWidget( trainList : itineraryData["Train"]!,
selectedListWidget = TrainListWidget(
trainList: itineraryData["Train"]!,
onOpen: handleEdit,
onDeleteTrain: (data)=> handleItinerarydelete("Train", data),
onAddNew: handlecreateNewPlan,
isViewMode: widget.isViewMode,
onDeleteTrain: (data) => handleItinerarydelete("Train", data),
);
break;
case "Taxi":
selectedListWidget = TaxiListWidget( taxiList : itineraryData["Taxi"]! ,
onOpen: handleEdit,
onDeleteTaxi: (data)=> handleItinerarydelete("Taxi", data),);
break;
case "Bus":
selectedListWidget = BusListWidget(busList : itineraryData["Bus"]!,
onOpen: handleEdit,
onDeleteBus: (data) => handleItinerarydelete("Bus", data),);
break;
case "Insurance":
selectedListWidget = InsuranceListWidget(insuranceList : itineraryData["Insurance"]!,
onOpen: handleEdit, apiData: widget.apiData,
onDeleteInsurance:(data) => handleItinerarydelete("Insurance", data),);
break;
case "Visa":
selectedListWidget = VisaListWidget(visaList : itineraryData["Visa"]!,
apiData: widget.apiData, apiCountryData : widget.apiCountryData,
selectedListWidget = TaxiListWidget(
taxiList: itineraryData["Taxi"]!,
onOpen: handleEdit,
onDeleteMiscellaneous: (data) => handleItinerarydelete("Visa", data),);
break;
case "Forex":
selectedListWidget = ForexListWidget(forexList: itineraryData["Forex"]!,
apiCountryData : widget.apiCountryData,
onOpen: handleEdit,
onDeleteForex: (data) => handleItinerarydelete("Forex", data),);
break;
case "Accomodation":
selectedListWidget = AccomodationListWidget(accommodationList: itineraryData["Accomodation"]!,
onOpen: handleEdit,
onDeleteAccommodation:(data) => handleItinerarydelete("Accomodation", data));
break;
case "Miscellaneous":
selectedListWidget = MiscellaneousListWidget(miscellaneousList: itineraryData["Miscellaneous"]!,
onOpen: handleEdit,apiData: widget.apiData,
onDeleteMiscellaneous: (data) => handleItinerarydelete("Miscellaneous", data),
onAddNew: handlecreateNewPlan,
isViewMode: widget.isViewMode,
onDeleteTaxi: (data) => handleItinerarydelete("Taxi", data),
);
break;
case "Flight":
case "Bus":
selectedListWidget = BusListWidget(
busList: itineraryData["Bus"]!,
onOpen: handleEdit,
onAddNew: handlecreateNewPlan,
isViewMode: widget.isViewMode,
onDeleteBus: (data) => handleItinerarydelete("Bus", data),
);
break;
case "Insurance":
selectedListWidget = InsuranceListWidget(
insuranceList: itineraryData["Insurance"]!,
onOpen: handleEdit,
onAddNew: handlecreateNewPlan,
isViewMode: widget.isViewMode,
apiData: widget.apiData,
onDeleteInsurance: (data) => handleItinerarydelete("Insurance", data),
);
break;
case "Visa":
selectedListWidget = VisaListWidget(
visaList: itineraryData["Visa"]!,
apiData: widget.apiData,
apiCountryData: widget.apiCountryData,
onOpen: handleEdit,
onAddNew: handlecreateNewPlan,
isViewMode: widget.isViewMode,
onDeleteMiscellaneous: (data) => handleItinerarydelete("Visa", data),
);
break;
case "Forex":
selectedListWidget = ForexListWidget(
forexList: itineraryData["Forex"]!,
apiCountryData: widget.apiCountryData,
onOpen: handleEdit,
onAddNew: handlecreateNewPlan,
isViewMode: widget.isViewMode,
onDeleteForex: (data) => handleItinerarydelete("Forex", data),
);
break;
case "Accomodation":
selectedListWidget = AccomodationListWidget(
accommodationList: itineraryData["Accomodation"]!,
onOpen: handleEdit,
onAddNew: handlecreateNewPlan,
isViewMode: widget.isViewMode,
onDeleteAccommodation: (data) =>
handleItinerarydelete("Accomodation", data));
break;
case "Miscellaneous":
selectedListWidget = MiscellaneousListWidget(
miscellaneousList: itineraryData["Miscellaneous"]!,
onOpen: handleEdit,
isViewMode: widget.isViewMode,
onAddNew: handlecreateNewPlan,
apiData: widget.apiData,
onDeleteMiscellaneous: (data) =>
handleItinerarydelete("Miscellaneous", data),
);
break;
case "Flight":
default:
selectedListWidget = FlightListWidget(flightList : itineraryData["Flight"]!,
onOpen: handleEdit,
onDeleteFlight: (data) => handleItinerarydelete("Flight", data)
);
selectedListWidget = FlightListWidget(
flightList: itineraryData["Flight"]!,
onOpen: handleEdit,
onAddNew: handlecreateNewPlan,
isViewMode: widget.isViewMode,
onDeleteFlight: (data) => handleItinerarydelete("Flight", data));
break;
}
switch (selectedOption) {
case "Train":
selectedWidget = TrainScreen(onClose: handleClose, apiData: widget.apiData, loginUser : widget.loginUser,
onSavetrain :(data) => handleItineraryUpdate("Train", data),
selectedWidget = TrainScreen(
onClose: handleClose,
apiData: widget.apiData,
loginUser: widget.loginUser,
onSavetrain: (data) => handleItineraryUpdate("Train", data),
selectedItem: selectedItem);
break;
case "Taxi":
selectedWidget = TaxiScreen(onClose: handleClose,apiData: widget.apiData, loginUser : widget.loginUser,
onSavetaxi: (data)=> handleItineraryUpdate("Taxi", data),
selectedWidget = TaxiScreen(
onClose: handleClose,
apiData: widget.apiData,
loginUser: widget.loginUser,
onSavetaxi: (data) => handleItineraryUpdate("Taxi", data),
selectedItem: selectedItem);
break;
case "Bus":
selectedWidget = BusScreen(onClose: handleClose, apiData: widget.apiData, loginUser : widget.loginUser,
onSaveBus: (data)=> handleItineraryUpdate("Bus", data),
selectedWidget = BusScreen(
onClose: handleClose,
apiData: widget.apiData,
loginUser: widget.loginUser,
onSaveBus: (data) => handleItineraryUpdate("Bus", data),
selectedItem: selectedItem);
break;
case "Insurance":
selectedWidget = InsuranceScreen(onClose: handleClose, apiData: widget.apiData, loginUser : widget.loginUser,
onSaveInsurance:(data) => handleItineraryUpdate("Insurance", data),
selectedItem: selectedItem);
selectedWidget = InsuranceScreen(
onClose: handleClose,
apiData: widget.apiData,
loginUser: widget.loginUser,
onSaveInsurance: (data) => handleItineraryUpdate("Insurance", data),
selectedItem: selectedItem);
break;
case "Visa":
selectedWidget = VisaScreen(onClose: handleClose,apiData: widget.apiData, apiCountryData : widget.apiCountryData, loginUser : widget.loginUser,
onSaveVisa: (data) => handleItineraryUpdate("Visa", data),
selectedItem: selectedItem,);
break;
case "Visa":
selectedWidget = VisaScreen(
onClose: handleClose,
apiData: widget.apiData,
apiCountryData: widget.apiCountryData,
loginUser: widget.loginUser,
onSaveVisa: (data) => handleItineraryUpdate("Visa", data),
selectedItem: selectedItem,
);
break;
case "Miscellaneous":
selectedWidget = MiscellaneousScreen(onClose: handleClose, apiData: widget.apiData, loginUser : widget.loginUser,
onSaveMiscellaneous: (data) => handleItineraryUpdate("Miscellaneous", data),
selectedItem: selectedItem, selectedIndex: selectedIndex, );
break;
case "Accomodation":
selectedWidget = AccomodationScreen( onClose: handleClose, loginUser : widget.loginUser,
onSaveAccomadation: (data)=>handleItineraryUpdate("Accomodation", data),
selectedItem: selectedItem, );
selectedWidget = MiscellaneousScreen(
onClose: handleClose,
apiData: widget.apiData,
loginUser: widget.loginUser,
onSaveMiscellaneous: (data) =>
handleItineraryUpdate("Miscellaneous", data),
selectedItem: selectedItem,
selectedIndex: selectedIndex,
);
break;
case "Accomodation":
selectedWidget = AccomodationScreen(
onClose: handleClose,
loginUser: widget.loginUser,
onSaveAccomadation: (data) =>
handleItineraryUpdate("Accomodation", data),
selectedItem: selectedItem,
);
break;
case "Forex":
selectedWidget = ForexScreen( onClose: handleClose,apiData: widget.apiData, loginUser : widget.loginUser,
apiCountryData : widget.apiCountryData,
onSaveForex: (data)=>handleItineraryUpdate("Forex", data),
selectedWidget = ForexScreen(
onClose: handleClose,
apiData: widget.apiData,
loginUser: widget.loginUser,
apiCountryData: widget.apiCountryData,
onSaveForex: (data) => handleItineraryUpdate("Forex", data),
selectedItem: selectedItem);
break;
case "Flight":
default:
selectedWidget = FlightScreen(onClose: handleClose,loginUser : widget.loginUser,
onSaveFlight: (data)=>handleItineraryUpdate("Flight", data), apiData: widget.apiData,
selectedItem: selectedItem
);
selectedWidget = FlightScreen(
onClose: handleClose,
loginUser: widget.loginUser,
onSaveFlight: (data) => handleItineraryUpdate("Flight", data),
apiData: widget.apiData,
selectedItem: selectedItem);
break;
}
return ResponsiveBuilder(builder: (context, sizingInfo) {
bool isMobile = sizingInfo.isMobile;
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Column(
// mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Itinerary",
style: TextStyle(
fontSize: 18,
),),
],
),
SizedBox(height: 8),
Container(
decoration: BoxDecoration(
border: Border.all(color: Color(0xFFF4F4FB)),
border: Border(
bottom: BorderSide(color: Color(0xFFF4F4FB), width: 2)),
borderRadius: BorderRadius.circular(1),
color: Color(0xFFF4F4FB),
// color: Color(0xFFF4F4FB),
),
padding: EdgeInsets.all(5),
child: isMobile ?
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: _buildOptions(),
),
),
)
padding: EdgeInsets.all(10),
child: isMobile
? Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: _buildOptions(),
),
),
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// mainAxisSize: MainAxisSize.min,
children: _buildOptions(),
),
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// mainAxisSize: MainAxisSize.min,
children: _buildOptions(),
),
),
Column(
// mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 2),
isSelected ? selectedWidget : selectedListWidget,
// TrainScreen()
],
),
Column(
// mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 2),
isSelected ? selectedWidget : selectedListWidget,
// TrainScreen()
],
),
],
);
});
);
});
}
List<Widget> _buildOptions() {
return [
_buildOption("Flight", itineraryData["Flight"]?.isNotEmpty ?? false),
SizedBox(width: 20),
_buildOption("Taxi", itineraryData["Taxi"]?.isNotEmpty ?? false),
SizedBox(width: 20),
_buildOption("Train", itineraryData["Train"]?.isNotEmpty ?? false),
SizedBox(width: 20),
_buildOption("Bus", itineraryData["Bus"]?.isNotEmpty ?? false),
SizedBox(width: 20),
_buildOption("Accomodation", itineraryData["Accomodation"]?.isNotEmpty ?? false),
SizedBox(width: 20),
_buildOption("Forex", itineraryData["Forex"]?.isNotEmpty ?? false),
SizedBox(width: 20),
_buildOption("Insurance", itineraryData["Insurance"]?.isNotEmpty ?? false),
SizedBox(width: 20),
_buildOption("Visa", itineraryData["Visa"]?.isNotEmpty ?? false),
SizedBox(width: 20),
_buildOption("Miscellaneous", itineraryData["Miscellaneous"]?.isNotEmpty ?? false),
];
if (apiAllServices == null) return [];
return apiAllServices!.map((service) {
return Padding(
padding: const EdgeInsets.only(right: 20.0),
child: _buildOption(
service, itineraryData[service['name']]?.isNotEmpty ?? false),
);
}).toList();
}
Widget _buildOption(
Map<String, dynamic> service,
bool hasData,
) {
String name = service['name'];
String iconUrl = service['icon']; // Can be empty string
// Optional: define local icon fallback if iconUrl is empty
IconData fallbackIcon = _getLocalIconForService(name);
// final idMap = {"service_id": service['service_id'].toString()};
// final isSelected = selectedServiceIds.contains(idMap);
// List<Widget> _buildOptions() {
// return [
// _buildOption("Flight", itineraryData["Flight"]?.isNotEmpty ?? false),
// _buildOption("Flight"),
// SizedBox(width: 20),
// _buildOption("Taxi"),
// SizedBox(width: 20),
// _buildOption("Train"),
// SizedBox(width: 20),
// _buildOption("Bus"),
// SizedBox(width: 20),
// _buildOption("Accomodation"),
// SizedBox(width: 20),
// _buildOption("Forex"),
// SizedBox(width: 20),
// _buildOption("Insurance"),
// SizedBox(width: 20),
// _buildOption("Visa"),
// SizedBox(width: 20),
// _buildOption("Miscellaneous"),
// ];
// }
String serviceId = service['service_id'].toString();
// bool isSelected = selectedServiceIds.contains(serviceId);
// bool isSelected =
// selectedServiceIds.any((item) => item["service_id"] == serviceId);
Widget _buildOption(String title, bool hasData) {
return GestureDetector(
onTap: () {
setState(() {
selectedListOption = title;
selectedListOption = name;
isSelected = false;
});
},
child: Row(
children: [
if(hasData)
Icon(Icons.circle_notifications,color: Colors.green, size: 10,),
SizedBox(width: 5),
Text(title,
style: TextStyle(
fontSize: 13,
color: selectedListOption == title ? Colors.blueAccent : Color(0xFF575A74),
fontWeight:
selectedListOption == title ? FontWeight.bold : FontWeight.normal,)),
SizedBox(width: 5),
SizedBox(width: 5),
if (selectedListOption == title && widget.isViewMode == false)
GestureDetector(
onTap: () {
setState(() {
selectedOption = title;
isSelected = true;
selectedItem = null;
});
},
child: Icon(
Icons.add_circle,
color: Colors.blueAccent,
child: Row(children: [
iconUrl.isNotEmpty
? Image.network(
iconUrl,
width: 18,
height: 18,
errorBuilder: (context, error, stackTrace) {
return Icon(
fallbackIcon,
size: 18,
color: selectedListOption == name
? Color(0xFF114D8B)
: Color(0xFF475569),
);
},
)
: Icon(
fallbackIcon,
size: 18,
color: selectedListOption == name
? Color(0xFF114D8B)
: Color(0xFF475569),
),
),
]
),
SizedBox(width: 5),
SizedBox(width: 5),
Text(
name,
style: TextStyle(
fontSize: 14,
// color: selectedListOption == title ? Colors.blueAccent : Color(0xFF575A74),
color: selectedListOption == name
? Color(0xFF114D8B)
: Color(0xFF475569),
fontFamily: "Archivo",
fontWeight: selectedListOption == name
? FontWeight.bold
: FontWeight.w500),
// fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)),
),
SizedBox(width: 5),
// if (selectedListOption == title && widget.isViewMode == false)
if (hasData)
Container(
height: 13,
width: 13,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.green, width: 1.5),
),
child:
Icon(Icons.notifications_rounded, size: 8, color: Colors.green
// 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;
}
}
//
// List<Widget> _buildOptions1() {
// return [
// _buildOption("Flight", itineraryData["Flight"]?.isNotEmpty ?? false),
// SizedBox(width: 20),
// _buildOption("Taxi", itineraryData["Taxi"]?.isNotEmpty ?? false),
// SizedBox(width: 20),
// _buildOption("Train", itineraryData["Train"]?.isNotEmpty ?? false),
// SizedBox(width: 20),
// _buildOption("Bus", itineraryData["Bus"]?.isNotEmpty ?? false),
// SizedBox(width: 20),
// _buildOption(
// "Accomodation", itineraryData["Accomodation"]?.isNotEmpty ?? false),
// SizedBox(width: 20),
// _buildOption("Forex", itineraryData["Forex"]?.isNotEmpty ?? false),
// SizedBox(width: 20),
// _buildOption(
// "Insurance", itineraryData["Insurance"]?.isNotEmpty ?? false),
// SizedBox(width: 20),
// _buildOption("Visa", itineraryData["Visa"]?.isNotEmpty ?? false),
// SizedBox(width: 20),
// _buildOption(
// "Miscellaneous", itineraryData["Miscellaneous"]?.isNotEmpty ?? false),
// ];
// }
//
//
// Widget _buildOption1(String title, bool hasData) {
// return GestureDetector(
// onTap: () {
// setState(() {
// selectedListOption = title;
// isSelected = false;
// });
// },
// child: Row(children: [
// if (hasData)
// Icon(
// Icons.circle_notifications,
// color: Colors.green,
// size: 10,
// ),
// SizedBox(width: 5),
// Text(title,
// style: TextStyle(
// fontSize: 13,
// color: selectedListOption == title
// ? Colors.blueAccent
// : Color(0xFF575A74),
// fontWeight: selectedListOption == title
// ? FontWeight.bold
// : FontWeight.normal,
// )),
// SizedBox(width: 5),
// SizedBox(width: 5),
// if (selectedListOption == title && widget.isViewMode == false)
// GestureDetector(
// onTap: () {
// setState(() {
// selectedOption = title;
// isSelected = true;
// selectedItem = null;
// });
// },
// child: Icon(
// Icons.add_circle,
// color: Colors.blueAccent,
// size: 18,
// ),
// ),
// ]),
// );
// }
}

View File

@ -10,6 +10,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart';
import '../../utils/auth_utils.dart';
class ListPlans extends StatefulWidget {
const ListPlans({super.key});
@ -24,15 +25,37 @@ class _ListPlansState extends State<ListPlans> {
String? orgId;
String? token;
Color? layoutColor;
Color? bodyColor;
@override
void initState() {
super.initState();
getToken();
initializeData();
WidgetsBinding.instance.addPostFrameCallback((_) {
initializeData();
loadInitialData();
});
// futurePlans = fetchPlans();
}
void loadInitialData() async {
String? layoutString = await getLayoutColor();
String? bodyStringColor = await getBodyColor();
setState(() {
layoutColor = layoutString != null
? Color(int.parse(layoutString))
: Colors.redAccent;
bodyColor = bodyStringColor != null
? Color(int.parse(bodyStringColor))
: Colors.white;
});
}
Future<void> initializeData() async {
token = await getToken();
userId = await getUserId();
@ -170,336 +193,331 @@ class _ListPlansState extends State<ListPlans> {
}
Widget buildTableLayout(isDesktop) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Container(
color: bodyColor,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Container(
padding: const EdgeInsets.all(10.0),
color: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 2),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Plans List',
style:
TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
IconButton(
icon: const Icon(Icons.keyboard_arrow_down),
onPressed: () {},
Row(
children: [
const Text('Plans List',
style: TextStyle(
fontFamily: "Archivo",
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF212121))),
],
),
],
),
ElevatedButton(
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.blueAccent),
onPressed: () {
context.go('/createPlan', extra: {
// 'apiCountryData': apiCountryData,
'orgId': orgId,
});
if (!isDesktop) Navigator.pop(context);
},
child: Row(
children: [
Icon(
Icons.add_circle,
color: Colors.white,
),
SizedBox(
width: 5,
),
Text('NewPlan'),
],
),
SizedBox(height: 2),
Divider(
thickness: 0.2, // how "thick" the line is
color: Colors.grey, // optional
),
],
),
const SizedBox(height: 10),
FutureBuilder<List<Plan>>(
future: futurePlans, // Use the futurePlans variable
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.error_outline,
color: Colors.redAccent,
size: 60,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
width: MediaQuery.of(context).size.width * 0.2,
// or use Flexible
child: TextField(
onChanged: (query) {},
decoration: InputDecoration(
hintText: "Search for a plan",
hintStyle:
TextStyle(fontSize: 14, color: Color(0xFF9E9DBD)),
prefixIcon:
Icon(Icons.search, color: Color(0xFF9E9DBD)),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
SizedBox(height: 16),
Text(
"Oops!",
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.redAccent,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey.shade300, width: 0.5),
),
SizedBox(height: 8),
Text(
"No Plans Available For This User",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.grey,
),
),
SizedBox(height: 20),
Text(
" Please Create Plan",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
color: Colors.grey[700],
),
),
SizedBox(height: 20),
// ElevatedButton.icon(
// onPressed: () {
// // Optional: retry logic or navigation
// },
// icon: Icon(Icons.refresh),
// label: Text("Try Again"),
// style: ElevatedButton.styleFrom(
// backgroundColor: Colors.blueAccent,
// ),
// ),
],
),
),
);
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return const Center(child: Text("No plans available"));
}
List<Plan> plans = snapshot.data!; // Extract the list of plans
// Ensure planId is sorted in descending order
plans.sort((a, b) => int.parse(b.planId.toString())
.compareTo(int.parse(a.planId.toString())));
// return ResponsiveBuilder(
// builder: (context, sizingInfo) {
// bool isTabletOrDesktop = sizingInfo.isTablet || sizingInfo.isDesktop;
//
// return SingleChildScrollView(
// scrollDirection: Axis.horizontal,
// child: Container(
// color: Colors.grey,
// child: SizedBox(
// width: MediaQuery.of(context).size.width ,
// child: SingleChildScrollView(
// scrollDirection: Axis.vertical,
// // scrollDirection: isTabletOrDesktop ? Axis.vertical : Axis.horizontal,
//
// // constraints: isTabletOrDesktop
// // ? const BoxConstraints(maxWidth: double.infinity)
// // : BoxConstraints.tightFor(width: 600),
//
//
// child: DataTable(
// // columnSpacing: 50.0,
// dividerThickness: 0.5, // Reduce the thickness of row dividers
// border: TableBorder(
// horizontalInside: BorderSide(width: 0.5, color: Colors.grey.shade200), // Reduce horizontal line thickness
// ),
// columns: const [
// DataColumn(label: Text('Plan ID', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// DataColumn(label: Text('Trip Title', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// DataColumn(label: Text('Trip Type', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// DataColumn(label: Text('Cost Center', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// // DataColumn(label: Text('Functional Department', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// // DataColumn(label: Text('Purpose Of Travel', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// // DataColumn(label: Text('Description', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// //
// DataColumn(label: Text('Is Billable', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// DataColumn(label: Text('Status', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// DataColumn(label: Text('Actions', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// ],
// rows: plans.map((plan) {
// return DataRow(cells: [
// DataCell(Text(plan.planId)),
// // DataCell(Text(plan.tripTitle)),
// DataCell(Row(
// children: [
// Flexible(
// child: Text(
// plan.tripTitle,
// softWrap: true,
// overflow: TextOverflow.ellipsis, // Adds "..." if text is too long
// ),
// ),
// ],
// )),
//
//
// DataCell(Text(plan.tripType)),
// DataCell(Text(plan.costCenter)),
// // DataCell(Text(plan.functionalDepartment)),
// // DataCell(Text(plan.purposeOfTravel)),
// // DataCell(Text(plan.description)),
// //
// DataCell(Text(plan.isBillable)),
// DataCell(Text(plan.status)),
// DataCell(
// TextButton(
// onPressed: () {
// viewPlan(plan.planId);
// print("View button clicked for ${plan.planId}");
// },
// child: const Text('View',
// style: TextStyle(color: Colors.blueAccent)),
// ),
// ),
// ]);
// }).toList(),
// ),
//
//
// ),
// ),
// ),
// );
//
// },
// );
return Expanded(
child: SingleChildScrollView(
// scrollDirection: Axis.horizontal, // Outer wrapper for horizontal scrolling
scrollDirection: Axis.vertical,
child: SizedBox(
width: MediaQuery.of(context).size.width * 1.5,
// width: MediaQuery.of(context).size.width , // Ensure table is wider than screen
// width: double.infinity , // Ensure table is wider than screen
child: SingleChildScrollView(
// scrollDirection: Axis.vertical, // Inner wrapper for vertical scrolling
scrollDirection: Axis
.horizontal, // Inner wrapper for vertical scrolling
child: ConstrainedBox(
constraints: BoxConstraints(minWidth: 1300),
// width: MediaQuery.of(context).size.width ,
child: Container(
// color: Colors.amber,
child: DataTable(
columnSpacing:
50.0, // Adjust spacing between columns
dividerThickness: 0.5,
border: TableBorder(
horizontalInside: BorderSide(
width: 0.5, color: Colors.grey.shade200),
),
columns: const [
DataColumn(
label: Text('Plan ID',
style: TextStyle(
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('Trip Title',
style: TextStyle(
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('Trip Type',
style: TextStyle(
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('Cost Center',
style: TextStyle(
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('Is Billable',
style: TextStyle(
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('Status',
style: TextStyle(
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('Actions',
style: TextStyle(
fontWeight: FontWeight.bold))),
],
rows: plans.map((plan) {
return DataRow(cells: [
DataCell(Text(plan.planId)),
DataCell(Text(plan.tripTitle,
softWrap: true,
overflow: TextOverflow.ellipsis)),
DataCell(Text(plan.tripType)),
DataCell(Text(plan.costCenter)),
DataCell(Text(plan.isBillable)),
DataCell(
Container(
padding: const EdgeInsets.symmetric(
vertical: 5,
horizontal:
10), // Padding for better look
decoration: BoxDecoration(
color: plan.status == "Active"
? Colors.green.shade50
: Colors
.grey.shade50, // Background color
borderRadius: BorderRadius.circular(
10), // Rounded corners
),
child: Text(
plan.status,
style: TextStyle(
color: plan.status == "Active"
? Colors.green
: Colors.grey, // Text color
fontWeight: FontWeight
.bold, // Optional: Make text bold
),
),
),
),
DataCell(Row(children: [
IconButton(
icon: Icon(Icons.remove_red_eye,
color: Colors.blue),
onPressed: () {
viewPlan(plan.planId, isViewMode: true);
},
),
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
onPressed: () {
viewPlan(plan.planId, isViewMode: false);
},
),
// IconButton(
// icon: Icon(Icons.delete, color: Colors.red),
// onPressed: () {
// deletePlan(plan.planId);
// },
// ),
])),
]);
}).toList(),
),
focusedBorder: OutlineInputBorder(
// borderRadius: BorderRadius.circular(8),
borderSide:
BorderSide(color: Colors.blueAccent, width: 1),
),
),
),
),
),
);
},
// SizedBox(width: 16),
Spacer(),
// ElevatedButton(
// style: ElevatedButton.styleFrom(
// foregroundColor: Colors.white,
// backgroundColor: Colors.blueAccent,
// ),
// onPressed: () {
// context.go('/createPlan', extra: {
// 'orgId': orgId,
// });
// if (!isDesktop) Navigator.pop(context);
// },
// child: Row(
// children: [
// Icon(Icons.add_circle, color: Colors.white),
// SizedBox(width: 5),
// Text('NewPlan'),
//
// ],
// ),
// ),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding:
EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
context.go('/createPlan', extra: {
'orgId': orgId,
});
if (!isDesktop) Navigator.pop(context);
},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add New Plan",
style: TextStyle(fontSize: 13),
),
SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_outline_rounded,
size: 15,
color: Colors.white,
),
],
),
),
],
),
const SizedBox(height: 10),
FutureBuilder<List<Plan>>(
future: futurePlans,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError ||
!snapshot.hasData ||
snapshot.data!.isEmpty) {
return Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: const [
Icon(Icons.error_outline,
color: Colors.redAccent, size: 60),
SizedBox(height: 16),
Text("Oops!",
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.redAccent)),
SizedBox(height: 8),
Text("No Plans Available For This User",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.grey)),
SizedBox(height: 20),
Text("Please Create Plan",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16, color: Colors.grey)),
SizedBox(height: 20),
],
),
),
);
}
List<Plan> plans = snapshot.data!;
plans.sort((a, b) =>
int.parse(b.planId).compareTo(int.parse(a.planId)));
Widget table = LayoutBuilder(
builder: (context, constraints) {
double minWidth = isDesktop ? constraints.maxWidth : 1300;
return ConstrainedBox(
constraints: BoxConstraints(minWidth: minWidth),
child: DataTable(
dividerThickness: 0.5,
columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder(
horizontalInside: BorderSide(
width: 0.5, color: Colors.grey.shade200),
),
columns: const [
DataColumn(
label: Text('Plan Id',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontSize: 14,
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('UserName',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('Trip Title',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('Trip Type',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('Created On',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('Status',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('Actions',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
],
rows: plans.map((plan) {
return DataRow(cells: [
DataCell(Text(plan.planId,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
))),
DataCell(Text(
plan.userName.isNotEmpty
? plan.userName
: plan.travellerName,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
))),
DataCell(Text(plan.tripTitle,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
),
softWrap: true,
overflow: TextOverflow.ellipsis)),
DataCell(Text(plan.tripType,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
))),
DataCell(Text(plan.createdOn,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
))),
DataCell(Container(
padding: const EdgeInsets.symmetric(
vertical: 4, horizontal: 10),
decoration: BoxDecoration(
color: plan.status == "Active"
? layoutColor
: Colors.grey.shade50,
borderRadius: BorderRadius.circular(10),
),
child: Text(
plan.statusValue,
style: TextStyle(
color: plan.status == "Active"
? Colors.white
: Colors.grey,
fontSize: 13,
fontWeight: FontWeight.bold,
),
),
)),
DataCell(Row(children: [
IconButton(
icon: const Icon(
Icons.remove_red_eye,
color: Color(0xFF475569),
size: 18,
),
onPressed: () => viewPlan(plan.planId,
isViewMode: true)),
GestureDetector(
onTap: () =>
viewPlan(plan.planId, isViewMode: false),
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15),
),
// IconButton(
// icon: const Icon(Icons.edit,
// color: Colors.green),
// onPressed: () => viewPlan(plan.planId,
// isViewMode: false) ),
])),
]);
}).toList(),
),
);
},
);
return Expanded(
child: isDesktop
? table
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table,
),
),
);
},
)
],
),
],
),
),
);
}

View File

@ -2,10 +2,14 @@ import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:frontend/Screens/policy/policyCriteria.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:responsive_builder/responsive_builder.dart';
import '../../config/apiUrl.dart';
import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart';
import '../../utils/auth_utils.dart';
import '../../widgets/custom_text_field.dart';
import '../../widgets/custom_user_form.dart';
@ -17,14 +21,156 @@ class Policy extends StatefulWidget {
}
class _PolicyState extends State<Policy> {
final GlobalKey<PolicyCriteriaState> policyCriteriaKey =
GlobalKey<PolicyCriteriaState>();
Color? layoutColor;
Color? bodyColor;
late String policyType = "domestic";
int? selectedServiceIndex = 1;
// int? selectedServiceIndex = 1;
ValueNotifier<String> selectedServiceIndex = ValueNotifier("1");
late String selectedService = "Train";
// ValueNotifier<String> selectedService = ValueNotifier("Train");
bool isViewMode = false;
String? _selectedTripType;
String? PolicyName;
String? SelectedDomestic = "1";
String? SelectedInternational = "0";
String? orgId;
String? userId;
bool showClass = true;
bool showCost = true;
TextEditingController _policyController = TextEditingController();
List<Map<String, dynamic>>? policy_details = [];
Map<String, dynamic> get policyData {
List<Map<String, dynamic>> policyDetails = policy_details!.where((service) {
// Only check these specific fields for emptiness
final fieldsToCheck = [
'cost',
'class',
'a1_action',
'a2_action',
'a3_action'
];
// If any of the important fields has a value, keep it
return fieldsToCheck.any((field) {
final value = service[field];
return value != null && value.toString().trim().isNotEmpty;
});
}).toList();
Map<String, dynamic> data = {
"name": _policyController.text,
"domestic": SelectedDomestic,
"international": SelectedInternational,
"is_active": "1",
"org_id": orgId,
"created_by": userId,
"policy_details": policyDetails,
};
return data;
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
loadinitializeData();
loadInitialData();
});
// updateData();
}
void loadInitialData() async {
String? layoutString = await getLayoutColor();
String? bodyStringColor = await getBodyColor();
setState(() {
layoutColor = layoutString != null
? Color(int.parse(layoutString))
: Colors.redAccent;
bodyColor = bodyStringColor != null
? Color(int.parse(bodyStringColor))
: Colors.white;
});
}
void loadinitializeData() async {
orgId = await getOrgId();
userId = await getUserId();
}
// void updateData(){}
void handleSubmit() async {
print("USR Detail Submit - $policyData");
policyCriteriaKey.currentState?.saveCurrentPolicy();
// Now the full data is ready in policyDataFromChild
print("Submitting full policyData: $policyData");
Map<String, dynamic> data = policyData;
createPolicyData(data);
// if (!isValidData(data)) {
// print("USERDETAILS : $policyData");
// print("Validation Failed: Required fields are missing.");
// setState(() {});
// return; // Stop execution if validation fails
// } else {
// // print("USERDETAILS : $policyData");
// // orgId = await getOrgId();
//
// createPolicyData(policyData);
// }
}
Future<void> createPolicyData(Map<String, dynamic> policyData) async {
final String apiUrldata = '$apiUrl/api/policy/createOrUpdate';
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(policyData), // Convert map to JSON
);
if (response.statusCode == 200) {
print("policyData submitted successfully!");
print("Response: ${response.body}");
context.go('/PolicyList');
} else {
print("Failed to submit policyData. Status: ${response.statusCode}");
print("Error: ${response.body}");
}
} catch (e) {
print(" Error submitting policyData: $e");
}
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
@ -32,14 +178,70 @@ class _PolicyState extends State<Policy> {
return Scaffold(
backgroundColor: Colors.white,
appBar: isDesktop ? null : const CustomAppBar(title: 'Home'),
appBar: isDesktop ? null : const CustomAppBar(title: 'Policy'),
drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
body: Row(
body: Column(
children: [
if (isDesktop) CustomDrawer(isDesktop: true),
Expanded(child: buildPolicyLayout(isDesktop))
Expanded(
child: Row(
children: [
if (isDesktop) CustomDrawer(isDesktop: true),
Expanded(
child: Container(
color: bodyColor,
child: buildPolicyLayout(isDesktop),
),
),
],
),
),
Container(
color: Colors.white,
padding: const EdgeInsets.all(8.0),
child: isDesktop
? Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _buildSubmit(isDesktop),
)
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _buildSubmit(isDesktop),
),
),
],
),
// Row(
// children: [
// if (isDesktop) CustomDrawer(isDesktop: true),
// Expanded(
// child: Column(
// children: [
// Expanded(
// child: Container(
// // height: MediaQuery.of(context).size.height * 0.8,
// color: bodyColor,
// child: buildPolicyLayout(isDesktop)),
// ),
// ],
// ),
// ),
// Container(
// color: Colors.white,
// child: Padding(
// padding: const EdgeInsets.all(8.0),
// child: isDesktop
// ? Row(
// mainAxisAlignment: MainAxisAlignment.end,
// children: _buildSubmit(isDesktop),
// )
// : Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: _buildSubmit(isDesktop),
// )),
// )
// ],
// ),
);
});
}
@ -49,9 +251,11 @@ class _PolicyState extends State<Policy> {
scrollDirection: Axis.vertical,
child: Container(
margin: isDesktop
? EdgeInsets.all(20.0)
? EdgeInsets.all(10.0)
: EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
height: MediaQuery.of(context).size.height,
height: isDesktop
? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
border: isDesktop
? Border.all(
@ -59,7 +263,10 @@ class _PolicyState extends State<Policy> {
color: Color(0xFFF7F7FB),
)
: null,
color: Color(0xFFF7F7FB),
color: Colors.white,
// color: Color(0xFFF7F7FB),
// color: Colors.amber,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
@ -72,7 +279,8 @@ class _PolicyState extends State<Policy> {
Container(
padding: isDesktop ? EdgeInsets.all(6) : EdgeInsets.all(3),
// color: Colors.white, // Background to avoid overlapping
color: isDesktop ? Color(0xFFF7F7FB) : Colors.white,
color: Colors.white,
// color: isDesktop ? Color(0xFFF7F7FB) : Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
@ -87,73 +295,6 @@ class _PolicyState extends State<Policy> {
],
),
),
// Container(
// child: Row(
// children: [
// Expanded(
// child: GestureDetector(
// onTap: () {
// setState(() {
// policyType = "domestic";
// });
// },
// child: Container(
// padding: EdgeInsets.all(10),
// color: policyType == "domestic"
// ? Colors.blueAccent.shade100
// : Color(0xFFEBEBF7),
// // color: Colors.blue.shade300,
// child: Column(
// children: [
// Text(
// "Domestic",
// style: TextStyle(
// color: policyType == "domestic"
// ? Colors.white
// : Colors.black87,
// fontSize: 15,
// fontWeight: FontWeight.bold),
// ),
// ],
// ),
// ),
// ),
// ),
// Expanded(
// child: GestureDetector(
// onTap: () {
// setState(() {
// policyType = "international";
// });
// },
// child: Container(
// padding: EdgeInsets.all(10),
// // color: Color(0xFFEBEBF7),
// color: policyType == "international"
// ? Colors.blueAccent.shade100
// : Color(0xFFEBEBF7),
// // color: Color(0xFFE3F2FD),
//
// child: Column(
// children: [
// Text(
// "International",
// style: TextStyle(
// color: policyType == "international"
// ? Colors.white
// : Colors.black87,
// fontSize: 15,
// fontWeight: FontWeight.bold),
// ),
// ],
// ),
// ),
// )),
// ],
// ),
// ),
//
],
),
),
@ -182,7 +323,7 @@ class _PolicyState extends State<Policy> {
height: 40,
child: TextField(
style: TextStyle(fontSize: 12),
// controller: controllers["Fname"],
controller: _policyController,
// enabled: !isViewMode,
onChanged: (value) {},
decoration: InputDecoration(
@ -202,6 +343,9 @@ class _PolicyState extends State<Policy> {
),
],
),
SizedBox(
height: 5,
),
Row(
children: [
Column(
@ -226,6 +370,10 @@ class _PolicyState extends State<Policy> {
SizedBox(
height: 10,
),
Divider(
thickness: 0.2,
color: Colors.grey,
),
isDesktop
? Expanded(
child: Row(
@ -242,7 +390,7 @@ class _PolicyState extends State<Policy> {
_buildPolicyCategory(isDesktop),
],
),
)
),
],
),
),
@ -290,9 +438,9 @@ class _PolicyState extends State<Policy> {
child: Flex(
direction: isDesktop ? Axis.vertical : Axis.horizontal,
children: services.asMap().entries.map((entry) {
int index = entry.key;
int index = entry.key + 1;
String service = entry.value;
bool isSelected = selectedServiceIndex == index;
bool isSelected = selectedServiceIndex.value == index.toString();
return SizedBox(
width: isDesktop ? 180 : null,
@ -305,7 +453,7 @@ class _PolicyState extends State<Policy> {
onTap: () {
print("Selected Services - $service - $index");
setState(() {
selectedServiceIndex = index;
selectedServiceIndex.value = index.toString();
selectedService = service;
if (selectedService == "Flight" ||
@ -322,15 +470,14 @@ class _PolicyState extends State<Policy> {
});
},
child: Container(
margin: EdgeInsets.all(8),
margin: EdgeInsets.all(5),
padding: isDesktop
? EdgeInsets.all(8)
: EdgeInsets.only(top: 3, bottom: 3, left: 8, right: 8),
decoration: BoxDecoration(
// color: Colors.blue,
color: isSelected
? Colors.blueAccent.shade100
: Colors.grey.shade100,
color:
isSelected ? Color(0xFF114D8B) : Colors.grey.shade100,
// : Color(0xFFEBEBF7),
borderRadius: BorderRadius.circular(8),
),
@ -354,17 +501,220 @@ class _PolicyState extends State<Policy> {
Widget _buildPolicyCategory(bool isDesktop) {
return Expanded(
child: Container(
margin: EdgeInsets.all(10),
// color: Colors.brown.shade100,
color: Colors.white60,
// color: Colors.white60,
child: PolicyCriteria(
isDesktop: isDesktop,
isClass: showClass,
isCost: showCost,
selectedTab: selectedService)),
key: policyCriteriaKey,
isDesktop: isDesktop,
isClass: showClass,
isCost: showCost,
selectedTabNotifier: selectedServiceIndex,
selectedService: selectedService,
userId: userId,
onPolicyDataChanged: (List<Map<String, dynamic>>? policyData) {
print("🟢 policyData received from child: $policyData");
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
policy_details = policyData;
});
});
},
)),
);
}
List<Widget> _buildTripType(bool isDesktop) {
return [
CustomTextFieldWrapper(
color: Color(0xFFF4F4FB),
layoutColor: layoutColor,
borderRadius: BorderRadius.circular(25),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
width: 130,
isFocused: _selectedTripType == "1",
isDesktop: isDesktop,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Domestic",
style: TextStyle(
color: _selectedTripType == "1" ? Colors.white : Colors.black,
fontWeight: _selectedTripType == "1" ? FontWeight.w600 : null,
fontSize: 13),
),
// Radio<String>(
// activeColor: Colors.blueAccent,
// // contentPadding: EdgeInsets.zero,
// visualDensity: VisualDensity.compact,
// // dense: true,
// value: "1",
// groupValue: _selectedTripType,
// onChanged: widget.isViewMode
// ? null
// : (value) {
// setState(() {
// _selectedTripType = value!;
// });
// },
// ),
GestureDetector(
onTap: () {
setState(() {
_selectedTripType = "1";
PolicyName = "Domestic Policy";
SelectedDomestic = "1";
SelectedInternational = "0";
});
},
child: Container(
width: 15,
height: 15,
decoration: BoxDecoration(
shape: BoxShape.rectangle,
// color: _selectedOption == option["value"]
// ? Colors.blueAccent
// : Colors.transparent,
borderRadius: BorderRadius.circular(4), // Rounded rectangle
border: Border.all(
color:
_selectedTripType == "1" ? Colors.white : Colors.black,
width: _selectedTripType == "1" ? 2 : 1,
),
),
child: _selectedTripType == "1"
? Icon(Icons.rectangle, size: 8, color: Colors.white)
: null, // Add checkmark if selected
),
)
],
),
),
SizedBox(width: 20),
CustomTextFieldWrapper(
color: Color(0xFFF4F4FB),
layoutColor: layoutColor,
borderRadius: BorderRadius.circular(25),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
width: 150,
// padding: EdgeInsets.symmetric(horizontal: 5, vertical: 2),
isFocused: _selectedTripType == "2",
isDesktop: isDesktop,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"International",
style: TextStyle(
fontSize: 13,
color: _selectedTripType == "2" ? Colors.white : Colors.black,
fontWeight: _selectedTripType == "2" ? FontWeight.w600 : null,
),
),
GestureDetector(
onTap: () {
setState(() {
_selectedTripType = "2";
PolicyName = "International Policy";
SelectedDomestic = "0";
SelectedInternational = "1";
});
},
child: Container(
width: 15,
height: 15,
decoration: BoxDecoration(
shape: BoxShape.rectangle,
// color: _selectedOption == option["value"]
// ? Colors.blueAccent
// : Colors.transparent,
borderRadius: BorderRadius.circular(4), // Rounded rectangle
border: Border.all(
color:
_selectedTripType == "2" ? Colors.white : Colors.black,
width: _selectedTripType == "2" ? 2 : 1,
),
),
child: _selectedTripType == "2"
? Icon(Icons.rectangle, size: 8, color: Colors.white)
: null, // Add checkmark if selected
),
)
],
),
// RadioListTile<String>(
// activeColor: Colors.blueAccent,
// contentPadding: EdgeInsets.zero,
// dense: true,
// title: Text("International"),
// value: "2",
// groupValue: _selectedTripType,
// onChanged: widget.isViewMode
// ? null
// : (value) {
// setState(() {
// _selectedTripType = value!;
// });
// },
// ),
),
];
}
List<Widget> _buildSubmit(isDesktop) {
return [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: layoutColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: layoutColor ?? Colors.grey, width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
context.go('/PolicyList');
},
child: Text("Cancel")),
SizedBox(
width: 20,
),
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
isViewMode ? layoutColor : layoutColor, // Keep original color
foregroundColor:
isViewMode ? Colors.white : Colors.white, // Keep original color
disabledBackgroundColor:
layoutColor, // Ensure color remains when disabled
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: layoutColor ?? Colors.grey, width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed:
isViewMode ? null : handleSubmit, // Disable when in view mode
child: Text("Submit"),
),
)
];
}
List<Widget> _buildTripType1(bool isDesktop) {
return [
CustomTextFieldWrapper(
color: Color(0xFFF4F4FB),
@ -387,6 +737,9 @@ class _PolicyState extends State<Policy> {
onChanged: (value) {
setState(() {
_selectedTripType = value!;
PolicyName = "Domestic Policy";
SelectedDomestic = "1";
SelectedInternational = "0";
});
},
),
@ -415,6 +768,9 @@ class _PolicyState extends State<Policy> {
onChanged: (value) {
setState(() {
_selectedTripType = value!;
PolicyName = "International Policy";
SelectedDomestic = "0";
SelectedInternational = "1";
});
},
),

View File

@ -6,37 +6,154 @@ import '../../widgets/custom_user_form.dart';
class PolicyCriteria extends StatefulWidget {
bool isDesktop;
String selectedTab;
String selectedService;
final ValueNotifier<String> selectedTabNotifier;
String? userId;
bool isClass = true;
bool isCost = true;
final Function(List<Map<String, dynamic>>?) onPolicyDataChanged;
PolicyCriteria(
{super.key,
required this.isDesktop,
required this.selectedTab,
required this.selectedService,
required this.selectedTabNotifier,
required this.userId,
required this.isClass,
required this.isCost});
required this.isCost,
required this.onPolicyDataChanged});
@override
_PolicyCriteriaState createState() => _PolicyCriteriaState();
PolicyCriteriaState createState() => PolicyCriteriaState();
}
class _PolicyCriteriaState extends State<PolicyCriteria> {
String? selectedFirstApprovarType;
String? selectedService;
String? _selectedTripType;
class PolicyCriteriaState extends State<PolicyCriteria> {
String? ServiceId = "1";
// final TextEditingController _costController = TextEditingController();
// final TextEditingController _classController = TextEditingController();
// String? FirstApproverAction;
// String? SecondApproverAction;
// String? ThirdApproverAction;
// String? SelectedParallelProcess = "3";
Map<String, TextEditingController> costController = {};
Map<String, TextEditingController> classController = {};
Map<String, String?> FirstApproverAction = {};
Map<String, String?> SecondApproverAction = {};
Map<String, String?> ThirdApproverAction = {};
Map<String, String?> SelectedParallelProcess = {};
Map<String, String> validationErrors = {};
// bool isClass = true;
// bool isCost = true;
List<Map<String, dynamic>>? policyData = [];
// Map<String, dynamic> get policyServices {
// Map<String, dynamic> data = {
// "service_id": ServiceId,
// "cost": costController[ServiceId]?.text,
// "class": classController[ServiceId]?.text,
// "a1_action": FirstApproverAction[ServiceId],
// "a2_action": SecondApproverAction[ServiceId],
// "a3_action": ThirdApproverAction[ServiceId],
// "parallel_process_from": SelectedParallelProcess[ServiceId],
// "created_by": widget.userId
// };
// return data;
// }
@override
void initState() {
super.initState();
fieldForPolicy();
// selectedService = widget.selectedTab;
widget.selectedTabNotifier.addListener(() {
print("selectedTab changed: ${widget.selectedTabNotifier.value}");
fieldForPolicy();
});
}
void saveCurrentPolicy() {
if (ServiceId != null) {
addOrUpdatePolicy(ServiceId!);
}
}
void addOrUpdatePolicy(String serviceId) {
Map<String, dynamic> data = {
"service_id": serviceId,
"cost": costController[serviceId]?.text,
"class": classController[serviceId]?.text,
"a1_action": FirstApproverAction[serviceId],
"a2_action": SecondApproverAction[serviceId],
"a3_action": ThirdApproverAction[serviceId],
"parallel_process_from": SelectedParallelProcess[serviceId],
"created_by": widget.userId
};
final cost = costController[serviceId]?.text ?? "";
final travelClass = classController[serviceId]?.text ?? "";
final a1 = FirstApproverAction[serviceId];
final a2 = SecondApproverAction[serviceId];
final a3 = ThirdApproverAction[serviceId];
bool hasValue = cost.trim().isNotEmpty || travelClass.trim().isNotEmpty;
bool allActionsNull = a1 == null && a2 == null && a3 == null;
bool someActionsMissing = [a1, a2, a3].where((a) => a != null).length > 0 &&
[a1, a2, a3].where((a) => a == null).length > 0;
if (someActionsMissing) {
validationErrors[serviceId] = "All 3 approver actions must be selected.";
return;
}
if (hasValue && allActionsNull) {
validationErrors[serviceId] = "Please select all actions.";
return;
}
validationErrors.remove(serviceId);
int index =
policyData!.indexWhere((item) => item["service_id"] == serviceId);
if (index != -1) {
policyData![index] = data; // Replace existing entry
print("🔁 Updated policy for ServiceId: $serviceId");
} else {
policyData!.add(data); // Add new entry
print(" Added policy for ServiceId: $serviceId");
}
print("📋 policyData - $policyData");
}
void fieldForPolicy() {
print("fieldForPolicy - ${widget.selectedTab}");
print("fieldForPolicy - ${widget.selectedTabNotifier.value}");
// Save current input to policyData before switching
if (ServiceId != null) {
addOrUpdatePolicy(
ServiceId!); // 👈 Save current values for existing service
}
setState(() {
ServiceId = widget.selectedTabNotifier.value ?? "1";
// Initialize controllers and variables if not present
costController.putIfAbsent(ServiceId!, () => TextEditingController());
classController.putIfAbsent(ServiceId!, () => TextEditingController());
FirstApproverAction.putIfAbsent(ServiceId!, () => null);
SecondApproverAction.putIfAbsent(ServiceId!, () => null);
ThirdApproverAction.putIfAbsent(ServiceId!, () => null);
SelectedParallelProcess.putIfAbsent(ServiceId!, () => "3");
});
widget.onPolicyDataChanged(policyData);
}
void printData() {
// print("policyServices - $policyServices");
}
Widget build(BuildContext context) {
@ -49,7 +166,7 @@ class _PolicyCriteriaState extends State<PolicyCriteria> {
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
" Policy Criteria For ${widget.selectedTab} ",
" Policy Criteria For ${widget.selectedService} ",
style: TextStyle(
fontSize: 13,
color: Color(0xFF9E9DBD),
@ -59,102 +176,53 @@ class _PolicyCriteriaState extends State<PolicyCriteria> {
),
if (widget.isClass!)
widget.isDesktop ? SizedBox(height: 15) : SizedBox(height: 5),
Row(
children: [
Expanded(
child: Container(
// color: Colors.grey,
padding:
const EdgeInsets.only(top: 5, bottom: 5, left: 5, right: 5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.isClass!)
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Class",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w200,
color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12),
// controller: controllers["Fname"],
// enabled: !isViewMode,
onChanged: (value) {},
decoration: InputDecoration(
labelText: "Class",
labelStyle: TextStyle(
fontSize: 12, color: Colors.grey),
floatingLabelBehavior:
FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
),
],
),
SizedBox(height: 10),
if (widget.isCost!)
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Cost",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w200,
color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12),
// controller: controllers["Fname"],
// enabled: !isViewMode,
onChanged: (value) {},
decoration: InputDecoration(
labelText: "Cost",
labelStyle: TextStyle(
fontSize: 12, color: Colors.grey),
floatingLabelBehavior:
FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
),
],
),
],
Padding(
padding: const EdgeInsets.only(right: 18.0),
child: Row(
children: [
Expanded(
child: Container(
// color: Colors.grey,
padding: const EdgeInsets.only(
top: 5, bottom: 5, left: 5, right: 5),
child: widget.isDesktop
? Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (widget.isClass!)
buildClassWidget(widget.isDesktop),
SizedBox(height: 10),
if (widget.isCost!)
buildCostWidget(widget.isDesktop),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.isClass!)
buildClassWidget(widget.isDesktop),
SizedBox(height: 10),
if (widget.isCost!)
buildCostWidget(widget.isDesktop),
],
),
),
),
),
],
],
),
),
if (widget.isCost!) SizedBox(height: 15),
if (validationErrors[ServiceId] != null)
Row(
children: [
Text(validationErrors[ServiceId]!,
style: TextStyle(
color: Colors.red,
fontSize: 10,
fontWeight: FontWeight.bold))
],
),
if (validationErrors[ServiceId] != null) SizedBox(height: 15),
Expanded(
child: Container(
decoration: BoxDecoration(
@ -163,7 +231,7 @@ class _PolicyCriteriaState extends State<PolicyCriteria> {
// color: Color(0xFFEBEBF7),
// ),
borderRadius: BorderRadius.circular(8),
// color: Colors.brown.shade200,
color: Colors.brown.shade200,
),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
@ -241,7 +309,8 @@ class _PolicyCriteriaState extends State<PolicyCriteria> {
height: 40,
child: DropdownSearch<String>(
selectedItem:
selectedFirstApprovarType,
FirstApproverAction[
ServiceId],
// enabled: !isViewMode,
popupProps: PopupProps.menu(
// showSearchBox: true,
@ -251,7 +320,7 @@ class _PolicyCriteriaState extends State<PolicyCriteria> {
maxHeight: 250),
),
items: [
"Approve",
"Approval",
"Notification",
],
dropdownDecoratorProps:
@ -275,15 +344,16 @@ class _PolicyCriteriaState extends State<PolicyCriteria> {
selectedItem ?? "Select",
style: TextStyle(
fontSize: 12,
color: Colors
.blueAccent),
color: Color(
0xFF114D8B)),
),
),
onChanged:
(String? newValue) {
setState(() {
// Find the country_code based on selected country_name
selectedFirstApprovarType =
FirstApproverAction[
ServiceId!] =
newValue;
// print("selectedUserType - $selectedUserType");
@ -296,21 +366,41 @@ class _PolicyCriteriaState extends State<PolicyCriteria> {
),
),
),
Container(
height: 25,
width: 25,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: Colors.grey,
width: 2),
),
child: Icon(
Icons.check_circle,
size: 20,
// color: Colors.green,
color: Colors.grey,
)),
GestureDetector(
onTap: () {
setState(() {
SelectedParallelProcess[
ServiceId!] = "1";
});
print(
"SelectedParallelProcess - $SelectedParallelProcess");
},
child: Container(
height: 25,
width: 25,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: (SelectedParallelProcess[
ServiceId] ==
"1")
? Colors.green
: Colors.grey,
width: 2),
),
child: Icon(
Icons.check_circle,
size: 20,
color:
(SelectedParallelProcess[
ServiceId] ==
"1")
? Colors.green
: Colors.grey,
// color: Colors.grey,
)),
),
],
),
),
@ -328,7 +418,8 @@ class _PolicyCriteriaState extends State<PolicyCriteria> {
height: 40,
child: DropdownSearch<String>(
selectedItem:
selectedFirstApprovarType,
SecondApproverAction[
ServiceId],
// enabled: !isViewMode,
popupProps: PopupProps.menu(
// showSearchBox: true,
@ -338,7 +429,7 @@ class _PolicyCriteriaState extends State<PolicyCriteria> {
maxHeight: 250),
),
items: [
"Approve",
"Approval",
"Notification",
],
dropdownDecoratorProps:
@ -362,15 +453,16 @@ class _PolicyCriteriaState extends State<PolicyCriteria> {
selectedItem ?? "Select",
style: TextStyle(
fontSize: 12,
color: Colors
.blueAccent),
color: Color(
0xFF114D8B)),
),
),
onChanged:
(String? newValue) {
setState(() {
// Find the country_code based on selected country_name
selectedFirstApprovarType =
SecondApproverAction[
ServiceId!] =
newValue;
// print("selectedUserType - $selectedUserType");
@ -383,21 +475,46 @@ class _PolicyCriteriaState extends State<PolicyCriteria> {
),
),
),
Container(
height: 25,
width: 25,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: Colors.green,
width: 2),
),
child: Icon(
Icons.check_circle,
size: 20,
color: Colors.green,
// color: Colors.grey,
)),
GestureDetector(
onTap: () {
setState(() {
SelectedParallelProcess[
ServiceId!] = "2";
});
print(
"SelectedParallelProcess - $SelectedParallelProcess");
},
child: Container(
height: 25,
width: 25,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ((SelectedParallelProcess[
ServiceId] ==
"1") ||
(SelectedParallelProcess[
ServiceId] ==
"2"))
? Colors.green
: Colors.grey,
width: 2),
),
child: Icon(
Icons.check_circle,
size: 20,
color: ((SelectedParallelProcess[
ServiceId] ==
"1") ||
(SelectedParallelProcess[
ServiceId] ==
"2"))
? Colors.green
: Colors.grey,
// color: Colors.grey,
)),
),
],
),
),
@ -415,7 +532,8 @@ class _PolicyCriteriaState extends State<PolicyCriteria> {
height: 40,
child: DropdownSearch<String>(
selectedItem:
selectedFirstApprovarType,
ThirdApproverAction[
ServiceId],
// enabled: !isViewMode,
popupProps: PopupProps.menu(
// showSearchBox: true,
@ -425,7 +543,7 @@ class _PolicyCriteriaState extends State<PolicyCriteria> {
maxHeight: 250),
),
items: [
"Approve",
"Approval",
"Notification",
],
dropdownDecoratorProps:
@ -449,15 +567,16 @@ class _PolicyCriteriaState extends State<PolicyCriteria> {
selectedItem ?? "Select",
style: TextStyle(
fontSize: 12,
color: Colors
.blueAccent),
color: Color(
0xFF114D8B)),
),
),
onChanged:
(String? newValue) {
setState(() {
// Find the country_code based on selected country_name
selectedFirstApprovarType =
ThirdApproverAction[
ServiceId!] =
newValue;
// print("selectedUserType - $selectedUserType");
@ -470,21 +589,47 @@ class _PolicyCriteriaState extends State<PolicyCriteria> {
),
),
),
Container(
height: 25,
width: 25,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: Colors.green,
width: 2),
),
child: Icon(
Icons.check_circle,
size: 20,
color: Colors.green,
// color: Colors.grey,
)),
GestureDetector(
onTap: () {
setState(() {
SelectedParallelProcess[
ServiceId!] = "3";
});
print(
"SelectedParallelProcess - $SelectedParallelProcess[ServiceId]");
},
child: Container(
height: 25,
width: 25,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ((SelectedParallelProcess[ServiceId] == "1") ||
(SelectedParallelProcess[
ServiceId] ==
"2") ||
(SelectedParallelProcess[
ServiceId] ==
"3"))
? Colors.green
: Colors.grey,
width: 2),
),
child: Icon(
Icons.check_circle,
size: 20,
color: ((SelectedParallelProcess[ServiceId] == "1") ||
(SelectedParallelProcess[
ServiceId] ==
"2") ||
(SelectedParallelProcess[
ServiceId] ==
"3"))
? Colors.green
: Colors.grey,
// color: Colors.grey,
)),
),
],
),
),
@ -504,4 +649,82 @@ class _PolicyCriteriaState extends State<PolicyCriteria> {
],
);
}
Widget buildClassWidget(isDesktop) {
return Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Class",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w200,
color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12),
controller: classController[ServiceId],
// enabled: !isViewMode,
onChanged: (value) {},
decoration: InputDecoration(
labelText: "Class",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
),
],
);
}
Widget buildCostWidget(isDesktop) {
return Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Cost",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w200,
color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12),
controller: costController[ServiceId],
// enabled: !isViewMode,
onChanged: (value) {
printData();
},
decoration: InputDecoration(
labelText: "Cost",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
),
],
);
}
}

View File

@ -0,0 +1,235 @@
import 'package:flutter/material.dart';
import 'package:frontend/Screens/group/group.dart';
import 'package:go_router/go_router.dart';
import 'package:responsive_builder/responsive_builder.dart';
import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
class PolicyList extends StatefulWidget {
@override
_PolicyListState createState() => _PolicyListState();
}
class _PolicyListState extends State<PolicyList> {
final ApiService apiService = ApiService();
List<dynamic>? apiAllGroups;
Color? layoutColor;
Color? bodyColor;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
loadAllGroups();
loadInitialData();
});
}
void loadInitialData() async {
String? layoutString = await getLayoutColor();
String? bodyStringColor = await getBodyColor();
setState(() {
layoutColor = layoutString != null
? Color(int.parse(layoutString))
: Colors.redAccent;
bodyColor = bodyStringColor != null
? Color(int.parse(bodyStringColor))
: Colors.white;
});
}
Future<void> loadAllGroups() async {
try {
final result = await apiService.fetchAllPolicy();
setState(() {
apiAllGroups = result;
});
print("Fetched services: $apiAllGroups");
} catch (e) {
print('Error fetching role list: $e');
}
}
void deleteGroup(int groupId) {
setState(() {
apiAllGroups?.removeWhere((group) => group['group_id'] == groupId);
});
}
// Future<void> deleteGroupFromApi(int groupId) async {
// try {
// await apiService.deleteGroup(groupId); // your delete API call
// deleteGroup(groupId); // remove from UI list
// } catch (e) {
// print('Error deleting group: $e');
// }
// }'
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
backgroundColor: Colors.white,
appBar: isDesktop ? null : const CustomAppBar(title: 'Home'),
drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
body: Row(
children: [
if (isDesktop) CustomDrawer(isDesktop: true),
Expanded(child: buildGroupListLayout(isDesktop))
],
),
);
});
}
Widget buildGroupListLayout(bool isDesktop) {
return Container(
color: bodyColor,
// color: Colors.white,
width: double.infinity,
height: MediaQuery.of(context).size.height,
// margin: const EdgeInsets.all(8),
padding: const EdgeInsets.all(8),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const Text('Policy List',
style:
TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
IconButton(
icon: const Icon(Icons.keyboard_arrow_down),
onPressed: () {},
),
],
),
ElevatedButton(
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: layoutColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
// side: BorderSide(color: , width: 1),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () async {
// List<dynamic> users = await futureUsers;
context.go('/Policy');
},
child: Row(
children: [
Text('New Policy'),
SizedBox(
width: 5,
),
Icon(
Icons.add_circle_outline_rounded,
color: Colors.white,
),
],
),
),
],
),
SizedBox(
height: 5,
),
Row(
children: [
Expanded(
child: Container(
height: MediaQuery.of(context).size.height * 0.899,
padding: const EdgeInsets.all(10),
// margin: const EdgeInsets.only(bottom: 10),
color: Colors.white,
// color: Colors.red.shade100,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
children: [
buildGroupListView(isDesktop),
],
),
),
),
),
],
)
],
),
);
}
// Widget buildGroupListView(bool isDesktop) {
// return Container(
// child: Text("DAta"),
// );
// }
Widget buildGroupListView(bool isDesktop) {
if (apiAllGroups == null || apiAllGroups!.isEmpty) {
return Center(child: Text("No groups found."));
}
return ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: apiAllGroups!.length,
itemBuilder: (context, index) {
final group = apiAllGroups![index];
return Card(
// color: bodyColor,
color: Color(0xFFF5F5F5),
margin: EdgeInsets.symmetric(vertical: 6, horizontal: 10),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
flex: 2,
child: Text("Group Name: ${group['name']}",
style: TextStyle(
fontSize: 13, fontWeight: FontWeight.bold)),
),
Expanded(flex: 1, child: Text(" ${group['created_on']}")),
Expanded(flex: 1, child: Text("${group['created_by']}")),
],
),
SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
context.go("/CreateGroup", extra: group);
print("Edit ${group['group_id']} $group");
},
child: Text("Edit"),
),
],
),
],
),
),
);
},
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,11 @@ class Plan {
final String description;
final String isBillable;
final String userName;
final String travellerName;
final String statusValue;
final String createdOn;
Plan({
required this.planId,
required this.tripTitle,
@ -21,6 +26,10 @@ class Plan {
required this.soNumber,
required this.description,
required this.isBillable,
required this.userName,
required this.travellerName,
required this.statusValue,
required this.createdOn,
});
factory Plan.fromJson(Map<String, dynamic> json) {
@ -35,6 +44,10 @@ class Plan {
soNumber: json['so_number'] ?? '',
description: json['description'] ?? '',
isBillable: json['is_billable_value'] ?? '',
userName: json["user_name"] ?? '',
travellerName: json["traveller_name"] ?? '',
statusValue: json["status_value"] ?? '',
createdOn: json["created_on"] ?? '',
);
}
}

View File

@ -5,6 +5,8 @@ import 'package:go_router/go_router.dart';
import 'package:responsive_builder/responsive_builder.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../services/apiService.dart';
class CustomDrawer extends StatefulWidget {
final bool isDesktop;
const CustomDrawer({super.key, required this.isDesktop});
@ -14,15 +16,25 @@ class CustomDrawer extends StatefulWidget {
}
class _CustomDrawerState extends State<CustomDrawer> {
final ApiService apiService = ApiService();
String? token;
Map<String, dynamic>? userData;
Map<String, dynamic>? fetchedUserData;
Map<String, dynamic> userDetails = {};
Map<String, dynamic>? selectedOrg;
Color? layoutColor;
Color? bodyColor;
@override
void initState() {
super.initState();
initializeData();
// initializeData();
WidgetsBinding.instance.addPostFrameCallback((_) {
initializeData();
getOrganizationData();
});
}
Future<void> initializeData() async {
@ -66,92 +78,259 @@ class _CustomDrawerState extends State<CustomDrawer> {
return null;
}
Future<void> getOrganizationData() async {
try {
print("getUpdatedServices");
final result = await apiService.fetchOrganization();
final prefs = await SharedPreferences.getInstance();
print("UUPdatedServices - $result");
setState(() {
selectedOrg = result;
layoutColor = selectedOrg?['layout_color'] != null
? Color(int.parse(selectedOrg!['layout_color']))
: Colors.white;
bodyColor = selectedOrg?['color'] != null
? Color(int.parse(
selectedOrg!['color'].toString().replaceFirst('0x', ''),
radix: 16))
: Colors.blue;
});
// Save to SharedPreferences
await prefs.setString('layout_color', selectedOrg?['layout_color']);
await prefs.setString('body_color', selectedOrg?['color']);
print(
"Layout Color- ${selectedOrg?['layout_color']} - $layoutColor ---------- bodyColor - $bodyColor");
} catch (e) {
print("Error : $e");
}
}
@override
Widget build(BuildContext context) {
Widget drawerContent = Container(
color: Color(0xFFF3F3FA),
child: Column(
children: [
GestureDetector(
onTap: () {
print("ONTAP Custom");
print("ONTAP Custom- $userDetails ");
context.go(
"/CreateUserDetails",
extra: {
"selectedUser": userDetails,
"isEditProfile": true,
"isViewMode": true
},
);
},
child: SizedBox(
height: 80,
child: Container(
color: Color(0xFFF3F3FA),
padding: EdgeInsets.all(16),
width: double.infinity,
child: Row(
color: Colors.white,
// color: Color(0xFFF3F3FA),
child: Container(
margin: const EdgeInsets.all(18),
child: Column(
children: [
Container(
margin: const EdgeInsets.only(left: 20),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(2.0),
child: Container(
height: 50,
width: 50,
decoration: BoxDecoration(
color: Colors.blueAccent, shape: BoxShape.circle),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
userData?["name"]?.isNotEmpty == true
? userData!["name"]![0].toUpperCase()
: "N/A",
style: TextStyle(
color: Colors.white, fontSize: 25),
),
],
),
),
Image.asset(
'assets/images/login/travelSpend_Logo.png',
width: 160,
height: 40,
fit: BoxFit.contain,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
],
),
SizedBox(
height: 20,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
children: [
Text(
userData?["name"] ?? "N/A",
style:
TextStyle(color: Colors.black87, fontSize: 11),
),
Text(
userData?["email"] ?? "N/A",
style:
TextStyle(color: Colors.black45, fontSize: 10),
"Welcome,",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
fontFamily: "Archivo",
color: Colors.black,
),
),
],
)
),
Row(
children: [
GestureDetector(
onTap: () {
print("ONTAP Custom");
print("ONTAP Custom- $userDetails ");
context.go(
"/CreateUserDetails",
extra: {
"selectedUser": userDetails,
"isEditProfile": true,
"isViewMode": true
},
);
},
child: Text(
userData?["name"] ?? "N/A",
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
fontFamily: "Archivo",
// color: Color(0xFF12B24B),
color: layoutColor,
),
),
),
],
),
],
)),
),
// GestureDetector(
// onTap: () {
// print("ONTAP Custom");
// print("ONTAP Custom- $userDetails ");
// context.go(
// "/CreateUserDetails",
// extra: {
// "selectedUser": userDetails,
// "isEditProfile": true,
// "isViewMode": true
// },
// );
// },
// child: SizedBox(
// height: 80,
// child: Container(
// color: Color(0xFFF3F3FA),
// padding: EdgeInsets.all(16),
// // width: double.infinity,
// child: Row(
// // mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.start,
// children: [
// Column(
// children: [
// Text(
// "Welcome",
// style: TextStyle(
// color: Colors.black,
// fontSize: 13,
// fontWeight: FontWeight.w400,
// fontFamily: "Archivo"),
// ),
// ],
// ),
// Column(
// children: [
// Text(
// userData?["name"] ?? "N/A",
// style: TextStyle(
// color: Color(0xFF12B24B),
// fontSize: 13,
// fontWeight: FontWeight.w600,
// fontFamily: "Archivo"),
// ),
// ],
// )
// ],
// ),
// ],
// )),
// ),
// ),
SizedBox(
height: 15,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Components",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
fontFamily: "Archivo",
color: Colors.black87,
),
),
],
),
SizedBox(
height: 10,
),
],
),
),
),
_buildDrawerItem(context, Icons.home, 'Home', '/home'),
_buildExpandableItem(context, Icons.assessment, 'Plans', [
_buildSubDrawerItem(context, 'My Travel Request', '/listPlan'),
// _buildSubDrawerItem(context,'PlanB','/PlanB')
]),
_buildExpandableItem(
context, Icons.account_circle_outlined, 'User ', [
_buildSubDrawerItem(context, 'User List', '/listUser'),
// _buildSubDrawerItem(context,'PlanB','/PlanB')
]),
_buildExpandableItem(context, Icons.policy, 'Settings ', [
_buildSubDrawerItem(context, 'Organization', '/OrganizationSetup'),
_buildSubDrawerItem(context, 'Group', '/group'),
_buildSubDrawerItem(context, 'Policy', '/Policy'),
// _buildSubDrawerItem(context,'PlanB','/PlanB')
]),
_buildDrawerItem(context, Icons.logout, 'Logout', '/')
],
_buildDrawerItem(context, Icons.home_outlined, 'Home', '/home'),
_buildExpandableItem(
context,
Icons.assessment_outlined,
'Plans',
[
_buildSubDrawerItem(
context, 'My Travel Request', '/listPlan'),
_buildSubDrawerItem(context, 'My Approvals', '/ApprovalList')
],
'/listPlan'),
_buildExpandableItem(
context,
Icons.account_circle_outlined,
'User ',
[
_buildSubDrawerItem(context, 'User List', '/listUser'),
// _buildSubDrawerItem(context,'PlanB','/PlanB')
],
'/listUser'),
_buildExpandableItem(
context,
Icons.settings_outlined,
'Settings ',
[
_buildSubDrawerItem(
context, 'Organization', '/OrganizationSetup'),
_buildSubDrawerItem(context, 'Group', '/group'),
_buildSubDrawerItem(context, 'Policy', '/PolicyList'),
// _buildSubDrawerItem(context,'PlanB','/PlanB')
],
'/OrganizationSetup',
),
_buildDrawerItem(context, Icons.login_outlined, 'Logout', '/'),
if (widget.isDesktop) Spacer(),
Container(
margin: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Powered by",
style:
TextStyle(fontSize: 11, color: Color(0xFF212121)),
),
Image.asset(
'assets/images/login/travelSpend_Logo.png',
width: 90,
height: 25,
fit: BoxFit.contain,
),
],
),
],
),
),
],
),
),
);
@ -172,35 +351,193 @@ class _CustomDrawerState extends State<CustomDrawer> {
/// **Reusable Drawer Item**
Widget _buildDrawerItem(
BuildContext context, IconData icon, String title, String route) {
return ListTile(
leading: Icon(icon),
title: Text(title),
onTap: () async {
if (route == '/') {
// Handle logout separately
final pref = await SharedPreferences.getInstance();
await pref.clear(); // Clear stored token or session data
context.go("/"); // Redirect to login instead of home
} else {
context.go(route);
}
});
String selectedRoute = GoRouterState.of(context).uri.toString();
// return Container(
// color: selectedRoute == route ? Colors.blue.shade50 : null,
// child: InkWell(
// onTap: () async {
// if (route == '/') {
// final pref = await SharedPreferences.getInstance();
// await pref.clear();
// context.go("/");
// } else {
// context.go(route);
// }
// },
// child: Padding(
// padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12),
// child: Row(
// children: [
// Icon(
// icon,
// size: 20,
// color: Color(0xFF475569),
// ),
// // SizedBox(width: 9), // Reduce or increase this for spacing
// Text(
// title,
// style: TextStyle(
// fontSize: 14,
// fontWeight: FontWeight.w600,
// color: Color(0xFF475569),
// fontFamily: "Archivo",
// ),
// ),
// ],
// ),
// ),
// ),
// );
return Material(
color: selectedRoute == route ? bodyColor : Colors.transparent,
child: ListTile(
leading: Icon(
icon,
size: 20,
),
title: Text(
title,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF475569),
fontFamily: "Archivo"),
),
// tileColor: selectedRoute == route ? Colors.blue.shade50 : null,
onTap: () async {
if (route == '/') {
// Handle logout separately
final pref = await SharedPreferences.getInstance();
await pref.clear(); // Clear stored token or session data
context.go("/"); // Redirect to login instead of home
} else {
context.go(route);
}
}),
);
}
Widget _buildExpandableItem(BuildContext context, IconData icon, String title,
List<Widget> children) {
return ExpansionTile(
leading: Icon(icon),
title: Text(title),
shape: const Border(), // Removes top and bottom dividers
childrenPadding: const EdgeInsets.only(left: 40), // Indent sub-items
children: children,
Widget _buildExpandableItem(
BuildContext context,
IconData icon,
String title,
List<Widget> children,
String routeToMatch,
) {
String selectedRoute = GoRouterState.of(context).uri.toString();
return Theme(
data: Theme.of(context).copyWith(
dividerColor:
Colors.transparent, // Removes default ExpansionTile divider
),
child: Material(
color: selectedRoute == routeToMatch ? bodyColor : Colors.transparent,
child: ExpansionTile(
tilePadding: EdgeInsets.symmetric(horizontal: 16),
// childrenPadding: EdgeInsets.only(left: 36),
leading: Icon(
icon,
size: 20,
color: Color(0xFF475569),
),
title: Row(
children: [
// You could manually build this instead of using `leading`, but it's simpler here
// SizedBox(width: 8),
Text(
title,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF475569),
fontFamily: "Archivo",
),
),
],
),
collapsedBackgroundColor: Colors.transparent,
shape: const Border(),
children: children,
),
),
);
}
Widget _buildSubDrawerItem(BuildContext context, String title, String route) {
String selectedRoute = GoRouterState.of(context).uri.toString();
return InkWell(
onTap: () {
context.go(route);
if (!widget.isDesktop) Navigator.pop(context);
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 44.0, vertical: 8.0),
child: Row(
children: [
Icon(
Icons.circle_rounded,
color: Color(0xFF475569),
size: 6,
),
SizedBox(width: 8),
Text(
title,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: selectedRoute == route ? Colors.blue : Color(0xFF475569),
fontFamily: "Archivo",
),
),
],
),
),
);
}
Widget _buildExpandableItem1(BuildContext context, IconData icon,
String title, List<Widget> children) {
return ExpansionTile(
leading: Icon(
icon,
size: 20,
),
title: Text(
title,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF475569),
fontFamily: "Archivo"),
),
collapsedBackgroundColor: Colors.transparent,
shape: const Border(), // Removes top and bottom dividers
// childrenPadding: const EdgeInsets.only(left: 40), // Indent sub-items
childrenPadding: EdgeInsets.only(left: 24),
children: children,
);
}
Widget _buildSubDrawerItem1(
BuildContext context, String title, String route) {
return ListTile(
title: Text(title),
leading: Icon(
Icons.circle_rounded,
color: Color(0xFF475569),
size: 8,
),
title: Text(
title,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Color(0xFF475569),
fontFamily: "Archivo"),
),
onTap: () {
context.go(route);
if (!widget.isDesktop) Navigator.pop(context);

View File

@ -5,13 +5,16 @@ import 'package:frontend/Screens/authentication/login/login_page.dart';
import 'package:frontend/Screens/authentication/loginPage1.dart';
import 'package:frontend/Screens/dashboard/home_page.dart';
import 'package:frontend/Screens/organization/orgSetup.dart';
import 'package:frontend/Screens/organization/org_List.dart';
import 'package:frontend/Screens/plans/create_plans.dart';
import 'package:frontend/Screens/plans/list_plans.dart';
import 'package:frontend/Screens/policy/policy.dart';
import 'package:frontend/Screens/policy/policy_list.dart';
import 'package:frontend/Screens/userManagement/create_user/create_user.dart';
import 'package:frontend/Screens/userManagement/user_List.dart';
import 'package:go_router/go_router.dart';
import '../Screens/approvals/approval_list.dart';
import '../Screens/group/group.dart';
import '../Screens/group/groupList.dart';
@ -62,6 +65,10 @@ final GoRouter router = GoRouter(
path: '/Policy',
builder: (context, state) => Policy(),
),
GoRoute(
path: '/PolicyList',
builder: (context, state) => PolicyList(),
),
GoRoute(
path: '/OrganizationSetup',
builder: (context, state) => OrgSetUp(),
@ -70,6 +77,10 @@ final GoRouter router = GoRouter(
path: '/group',
builder: (context, state) => GroupList(),
),
GoRoute(
path: '/approvallist',
builder: (context, state) => ApprovalList(),
),
GoRoute(
path: '/CreateGroup',
pageBuilder: (context, state) => MaterialPage(

View File

@ -265,9 +265,10 @@ class ApiService {
}
}
Future<List<dynamic>> fetchUpdatedOrganization() async {
Future<Map<String, dynamic>> fetchOrganization() async {
String? orgId = await getOrgId();
// final String apiUrldata = '$apiUrl/api/organizations';
final String apiUrldata = '$apiUrl/api/organizations/find/$orgId';
final token = await getToken();
@ -288,16 +289,87 @@ class ApiService {
try {
final data = json.decode(response.body);
print(data);
if (!data.containsKey('data') || data['data'] is! List) {
if (!data.containsKey('data') || data['data'] is! Map) {
throw Exception(
"Invalid response format: 'data' field is missing or not a List");
"Invalid response format: 'data' field is missing or not a Map");
}
return data['data'];
// Make sure each item is a Map<String, dynamic>
// final List<Map<String, dynamic>> orgList =
// List<Map<String, dynamic>>.from(data['data']);
// return orgList;
return Map<String, dynamic>.from(data['data']);
} catch (e) {
throw Exception('Error parsing response: $e');
}
} else {
throw Exception('Failed to load plans');
throw Exception('Failed to load organizations');
}
}
Future<Map<String, dynamic>> getViewPlan(
String planId, List plansJson) async {
try {
final plan = plansJson.firstWhere(
(item) => item["plan_id"].toString() == planId,
orElse: () => null,
);
if (plan == null) {
throw Exception("Plan with ID $planId not found.");
}
return Map<String, dynamic>.from(plan);
} catch (e) {
throw Exception("Error finding plan: $e");
}
}
Future<Map<String, dynamic>> fetchUserApprovalList() async {
String? orgId = await getOrgId();
String? userId = await getUserId();
// final String apiUrldata = '$apiUrl/api/organizations';
final String apiUrldata =
'$apiUrl/api/plans/findApprovalList?user_id=$userId&org_id=$orgId';
// '$apiUrl/api/findApprovalList?user_id=$userId&org_id=$orgId';
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");
}
// Make sure each item is a Map<String, dynamic>
// final List<Map<String, dynamic>> orgList =
// List<Map<String, dynamic>>.from(data['data']);
// return orgList;
return Map<String, dynamic>.from(data['data']);
} catch (e) {
throw Exception('Error parsing response: $e');
}
} else {
throw Exception('Failed to load organizations');
}
}
}

View File

@ -7,6 +7,16 @@ Future<String?> getToken() async {
return prefs.getString("auth_token");
}
Future<String?> getLayoutColor() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString("layout_color");
}
Future<String?> getBodyColor() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString("body_color");
}
Future<String?> getUserId() async {
final prefs = await SharedPreferences.getInstance();
final String? userDataString = prefs.getString('user_data');

View File

@ -0,0 +1,13 @@
import 'package:flutter/material.dart';
extension ColorOpacityExtension on Color {
Color withOpacitySafe(double opacity) {
final int alpha = (opacity * 255).round().clamp(0, 255);
return Color.fromARGB(
alpha, // alpha value should be an int
this.r.toInt(), // red channel
this.g.toInt(), // green channel
this.b.toInt(), // blue channel
);
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:frontend/utils/colorOpcity.dart';
class CustomTextFieldWrapper extends StatefulWidget {
final Widget child;
@ -8,6 +9,8 @@ class CustomTextFieldWrapper extends StatefulWidget {
final Color? color;
final VoidCallback? onFocusChange; // Callback for focus handling
final EdgeInsetsGeometry padding;
final BorderRadius? borderRadius;
final Color? layoutColor;
const CustomTextFieldWrapper({
super.key,
@ -18,6 +21,8 @@ class CustomTextFieldWrapper extends StatefulWidget {
this.color = Colors.white,
this.onFocusChange,
this.padding = const EdgeInsets.symmetric(horizontal: 12),
this.borderRadius,
this.layoutColor,
});
@override
@ -25,6 +30,16 @@ class CustomTextFieldWrapper extends StatefulWidget {
}
class _CustomTextFieldWrapperState extends State<CustomTextFieldWrapper> {
Color getColorWithOpacity(Color color, double opacity) {
final int alpha = (opacity * 255).round().clamp(0, 255);
return Color.fromARGB(
alpha,
color.r.toInt(),
color.g.toInt(),
color.b.toInt(),
);
}
@override
Widget build(BuildContext context) {
return Container(
@ -34,22 +49,30 @@ class _CustomTextFieldWrapperState extends State<CustomTextFieldWrapper> {
: MediaQuery.of(context).size.width * 0.85),
padding: widget.padding,
decoration: BoxDecoration(
color: widget.color,
borderRadius: BorderRadius.circular(10),
color: widget.isFocused
? (widget.layoutColor ?? widget.color)
: widget.color,
borderRadius: widget.borderRadius ?? BorderRadius.circular(8),
border: Border.all(
color: widget.isFocused ? Color(0xFF78B4FC) : Color(0xFFD6D5E6),
width: widget.isFocused ? 2.0 : 0.5,
color: widget.isFocused
? (widget.layoutColor ?? Colors.blueAccent)
: Color(0xFFF5F5F5),
width: widget.isFocused ? 1.5 : 1.5,
),
boxShadow: widget.isFocused
? [
BoxShadow(
color: Color.fromRGBO(120, 180, 252, 0.3),
blurRadius: 10,
spreadRadius: 2,
offset: Offset(0, 4),
),
]
: [],
// boxShadow: widget.isFocused
// ? [
// BoxShadow(
// // color: Color.fromRGBO(120, 180, 252, 0.3),
// // color: widget.isFocused
// // ? (widget.layoutColor ?? Colors.red).withAlpha(204)
// // : Color(0xFFF5F5F5),
// blurRadius: 10,
// spreadRadius: 1,
// offset: Offset(0, 4),
// ),
// ]
// : [],
),
child: widget.child,
);

View File

@ -21,10 +21,12 @@ class CustomTextFieldUserWrapper extends StatefulWidget {
});
@override
_CustomTextFieldUserWrapperState createState() => _CustomTextFieldUserWrapperState();
_CustomTextFieldUserWrapperState createState() =>
_CustomTextFieldUserWrapperState();
}
class _CustomTextFieldUserWrapperState extends State<CustomTextFieldUserWrapper> {
class _CustomTextFieldUserWrapperState
extends State<CustomTextFieldUserWrapper> {
@override
Widget build(BuildContext context) {
return Container(
@ -35,22 +37,22 @@ class _CustomTextFieldUserWrapperState extends State<CustomTextFieldUserWrapper>
padding: widget.padding,
decoration: BoxDecoration(
// color: widget.color,
color: Color(0xFFF7F7FB),
// 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),
),
]
BoxShadow(
color: Color.fromRGBO(120, 180, 252, 0.3),
blurRadius: 10,
spreadRadius: 2,
offset: Offset(0, 4),
),
]
: [],
),
child: widget.child,

View File

@ -47,6 +47,7 @@ dependencies:
image_picker: ^1.1.2
dev_dependencies:
flutter_test:
sdk: flutter
@ -68,6 +69,14 @@ flutter:
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
assets:
- assets/images/login/login_img1.png
- assets/images/login/travelSpend_Logo.png
- assets/images/login/TravelSpendsLogo1.png
- assets/images/login/VectorG.png
- assets/images/IconsImg/delete.png
- assets/images/IconsImg/edit.png
# To add assets to your application, add an assets section, like this:
# assets: