diff --git a/assets/images/IconsImg/delete.png b/assets/images/IconsImg/delete.png new file mode 100644 index 0000000..b0eeb61 Binary files /dev/null and b/assets/images/IconsImg/delete.png differ diff --git a/assets/images/IconsImg/edit.png b/assets/images/IconsImg/edit.png new file mode 100644 index 0000000..c7304ae Binary files /dev/null and b/assets/images/IconsImg/edit.png differ diff --git a/assets/images/login/TravelSpendsLogo1.png b/assets/images/login/TravelSpendsLogo1.png new file mode 100644 index 0000000..a22863b Binary files /dev/null and b/assets/images/login/TravelSpendsLogo1.png differ diff --git a/assets/images/login/VectorG.png b/assets/images/login/VectorG.png new file mode 100644 index 0000000..35f44ca Binary files /dev/null and b/assets/images/login/VectorG.png differ diff --git a/assets/images/login/login_img1.png b/assets/images/login/login_img1.png new file mode 100644 index 0000000..e3eba35 Binary files /dev/null and b/assets/images/login/login_img1.png differ diff --git a/assets/images/login/travelSpend_Logo.png b/assets/images/login/travelSpend_Logo.png new file mode 100644 index 0000000..dd27882 Binary files /dev/null and b/assets/images/login/travelSpend_Logo.png differ diff --git a/lib/Screens/approvals/approval_dialogs.dart b/lib/Screens/approvals/approval_dialogs.dart new file mode 100644 index 0000000..d39a3fc --- /dev/null +++ b/lib/Screens/approvals/approval_dialogs.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; + +/// Show confirm dialog for approval +Future showApproveDialog(BuildContext context, Color layoutColor) { + return showDialog( + 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 showRejectDialog( + BuildContext context, Color layoutColor) async { + String remarks = ""; + + final confirmed = await showDialog( + 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; +} diff --git a/lib/Screens/approvals/approval_list.dart b/lib/Screens/approvals/approval_list.dart new file mode 100644 index 0000000..72cf230 --- /dev/null +++ b/lib/Screens/approvals/approval_list.dart @@ -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 { + final ApiService apiService = ApiService(); + late Future> futurePlans; + String? userId; + String? orgId; + String? token; + + Color? layoutColor; + Color? bodyColor; + + late List 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 loadAllGroups() async { + // try { + // final result = await apiService.fetchUserApprovalList(); + // // setState(() { + // // apiAllGroups = result; + // // }); + // print("Fetched services: $result"); + // } catch (e) { + // print('Error fetching role list: $e'); + // } + // } + + Future 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 getUserId() async { + final prefs = await SharedPreferences.getInstance(); + final String? userDataString = prefs.getString('user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + return userData["user_id"]?.toString(); + } catch (e) { + return null; + } + } + return null; + } + + Future getOrgId() async { + final prefs = await SharedPreferences.getInstance(); + final String? userDataString = prefs.getString('user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + return userData["org_id"]?.toString(); + } catch (e) { + return null; + } + } + return null; + } + + Future getToken() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString('auth_token'); + } + + // Fetch API Data + Future> 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> 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? resData = json.decode(response.body); + // + // return resData?["data"]; + // } else { + // throw Exception('Failed to load plans'); + // } + // } + + // Future> 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.from(plan); + // } catch (e) { + // throw Exception("Error finding plan: $e"); + // } + // } + + void viewPlanforApprover(String planId, + {bool isViewMode = false, bool isApprover = true}) async { + try { + Map 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>( + 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 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, + ), + ), + ); + }, + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/Screens/authentication/login/login_widget.dart b/lib/Screens/authentication/login/login_widget.dart index d273b85..4a381cc 100644 --- a/lib/Screens/authentication/login/login_widget.dart +++ b/lib/Screens/authentication/login/login_widget.dart @@ -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 { final TextEditingController _passwordController = TextEditingController(); bool _obscureText = true; - Future storeUserDetails(String token) async{ - try{ - final parts = token.split('.'); - if (parts.length != 3) throw Exception('Invalid token format'); + Future 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 { 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 { await storeUserDetails(token); - ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text("Login Successful"), @@ -83,7 +82,9 @@ class _LoginWidgetState extends State { 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 { } @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 { 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 { 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 { 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 + ), ), ); } diff --git a/lib/Screens/dialog/user_selection_dialog.dart b/lib/Screens/dialog/user_selection_dialog.dart index d7b556e..6d4a882 100644 --- a/lib/Screens/dialog/user_selection_dialog.dart +++ b/lib/Screens/dialog/user_selection_dialog.dart @@ -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 { 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 { ), ), + SizedBox(height: 10), + if (widget.title == "Others") ...[ - SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -324,8 +331,8 @@ class _UserSelectionDialogState extends State { }); }, 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 { 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 { 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), ), diff --git a/lib/Screens/group/group.dart b/lib/Screens/group/group.dart index b67cf30..a314944 100644 --- a/lib/Screens/group/group.dart +++ b/lib/Screens/group/group.dart @@ -36,6 +36,9 @@ class Group extends StatefulWidget { class _groupState extends State { final ApiService apiService = ApiService(); + Color? layoutColor; + Color? bodyColor; + String? orgId; String? userId; String? selectedGroupId; @@ -72,7 +75,11 @@ class _groupState extends State { @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 { 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 updateData() async { // Ensure apiselectedUser is not null before printing if (widget.group != null) { @@ -247,7 +269,8 @@ class _groupState extends State { 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 { 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 { 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 { ], ), ), - 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 { children: _buildSecondRow(isDesktop), ), - SizedBox(height: 15), + // SizedBox(height: 15), + + Spacer(), isDesktop ? Row( mainAxisAlignment: MainAxisAlignment.end, @@ -587,10 +617,10 @@ class _groupState extends State { 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 { // : 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), ), diff --git a/lib/Screens/group/groupList.dart b/lib/Screens/group/groupList.dart index 02d1696..5e2eff6 100644 --- a/lib/Screens/group/groupList.dart +++ b/lib/Screens/group/groupList.dart @@ -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 { final ApiService apiService = ApiService(); List? 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 loadAllGroups() async { @@ -71,10 +93,12 @@ class _GroupListState extends State { 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 { ), 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 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 { 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), diff --git a/lib/Screens/itnerary_list/accomodation_list.dart b/lib/Screens/itnerary_list/accomodation_list.dart index 4bcb8b0..30abbee 100644 --- a/lib/Screens/itnerary_list/accomodation_list.dart +++ b/lib/Screens/itnerary_list/accomodation_list.dart @@ -1,34 +1,87 @@ import 'package:flutter/material.dart'; class AccomodationListWidget extends StatelessWidget { - - final List> accommodationList; + final List> accommodationList; final Function(bool, Map, String) onOpen; - final Function(Map) onDeleteAccommodation; + final Function(Map) 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 _buildDataRows() { - - List> filteredList = accommodationList - .where((item) => item["is_active"] == "1") - .toList(); + List> 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 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(); } diff --git a/lib/Screens/itnerary_list/bus_list.dart b/lib/Screens/itnerary_list/bus_list.dart index 7fa6ed6..38a4da4 100644 --- a/lib/Screens/itnerary_list/bus_list.dart +++ b/lib/Screens/itnerary_list/bus_list.dart @@ -11,11 +11,17 @@ class BusListWidget extends StatelessWidget { final List> busList; final Function(bool, Map, String) onOpen; final Function(Map) 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(); } diff --git a/lib/Screens/itnerary_list/flight_list.dart b/lib/Screens/itnerary_list/flight_list.dart index f2e769d..adeb9f2 100644 --- a/lib/Screens/itnerary_list/flight_list.dart +++ b/lib/Screens/itnerary_list/flight_list.dart @@ -1,11 +1,20 @@ import 'package:flutter/material.dart'; class FlightListWidget extends StatelessWidget { - final List> flightList; - final Function( bool,Map, String) onOpen; - final Function(Map) onDeleteFlight; + final List> flightList; + final Function(bool, Map, String) onOpen; + final Function(Map) 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 _buildDataRows() { - - List> filteredList = flightList - .where((item) => item["is_active"] == "1") - .toList(); + List> filteredList = + flightList.where((item) => item["is_active"] == "1").toList(); print("filteredList- $filteredList"); - return filteredList.asMap().entries.map((entry) { - Map item = entry.value; + Map 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(); } diff --git a/lib/Screens/itnerary_list/forex_list.dart b/lib/Screens/itnerary_list/forex_list.dart index 5c908fb..56fd1df 100644 --- a/lib/Screens/itnerary_list/forex_list.dart +++ b/lib/Screens/itnerary_list/forex_list.dart @@ -8,12 +8,17 @@ class ForexListWidget extends StatelessWidget { final Function(Map) onDeleteForex; final List? 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(); } diff --git a/lib/Screens/itnerary_list/insurance_list.dart b/lib/Screens/itnerary_list/insurance_list.dart index e0b4dbf..51a8f7c 100644 --- a/lib/Screens/itnerary_list/insurance_list.dart +++ b/lib/Screens/itnerary_list/insurance_list.dart @@ -3,12 +3,22 @@ import 'dart:js_interop'; import 'package:flutter/material.dart'; class InsuranceListWidget extends StatelessWidget { - final List> insuranceList; - final Function(bool, Map, String)onOpen; - final Function(Map)onDeleteInsurance; + final List> insuranceList; + final Function(bool, Map, String) onOpen; + final Function(Map) onDeleteInsurance; final Map? 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 _buildDataRows() { - - - List> filteredList = insuranceList - .where((item) => item["is_active"] == "1") - .toList(); + List> filteredList = + insuranceList.where((item) => item["is_active"] == "1").toList(); print("filteredList- $filteredList"); - List insurancetypeList = apiData?['insurance_type_of_insurance'] ?? []; - + List 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 item = entry.value; + Map 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(); } diff --git a/lib/Screens/itnerary_list/miscellaneous_list.dart b/lib/Screens/itnerary_list/miscellaneous_list.dart index 5f64ac4..959b4d3 100644 --- a/lib/Screens/itnerary_list/miscellaneous_list.dart +++ b/lib/Screens/itnerary_list/miscellaneous_list.dart @@ -1,14 +1,21 @@ import 'package:flutter/material.dart'; class MiscellaneousListWidget extends StatelessWidget { - - final List> miscellaneousList; + final List> miscellaneousList; final Function(bool, Map, String) onOpen; - final Function(Map) onDeleteMiscellaneous; + final Function(Map) onDeleteMiscellaneous; final Map? 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> filteredList = miscellaneousList - .where((item) => item["is_active"] == "1") - .toList(); + List> 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 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(); } diff --git a/lib/Screens/itnerary_list/taxi_list.dart b/lib/Screens/itnerary_list/taxi_list.dart index d9e9bd0..3129b9e 100644 --- a/lib/Screens/itnerary_list/taxi_list.dart +++ b/lib/Screens/itnerary_list/taxi_list.dart @@ -1,11 +1,20 @@ import 'package:flutter/material.dart'; class TaxiListWidget extends StatelessWidget { - final List> taxiList; - final Function(bool, Map, String) onOpen; - final Function(Map) onDeleteTaxi; + final List> taxiList; + final Function(bool, Map, String) onOpen; + final Function(Map) 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 _buildDataRows() { - - List> filteredList = taxiList - .where((item) => item["is_active"] == "1") - .toList(); + List> filteredList = + taxiList.where((item) => item["is_active"] == "1").toList(); print("filteredList- $filteredList"); return filteredList.asMap().entries.map((entry) { - final Map 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(); } diff --git a/lib/Screens/itnerary_list/train_list.dart b/lib/Screens/itnerary_list/train_list.dart index 3c71d00..9da6e3e 100644 --- a/lib/Screens/itnerary_list/train_list.dart +++ b/lib/Screens/itnerary_list/train_list.dart @@ -1,10 +1,20 @@ import 'package:flutter/material.dart'; class TrainListWidget extends StatelessWidget { - final List> trainList; - final Function(bool, Map, String) onOpen; - final Function(Map) onDeleteTrain; - const TrainListWidget({super.key, required this.trainList, required this.onOpen, required this.onDeleteTrain}); + final List> trainList; + final Function(bool, Map, String) onOpen; + final Function(Map) 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 _buildDataRows() { - - - List> filteredList = trainList - .where((item) => item["is_active"] == "1") - .toList(); + List> filteredList = + trainList.where((item) => item["is_active"] == "1").toList(); print("filteredList- $filteredList"); - return filteredList.asMap().entries.map((entry) { - final Map item = entry.value; + final Map 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(); } diff --git a/lib/Screens/itnerary_list/visa_list.dart b/lib/Screens/itnerary_list/visa_list.dart index 176057f..facccf7 100644 --- a/lib/Screens/itnerary_list/visa_list.dart +++ b/lib/Screens/itnerary_list/visa_list.dart @@ -3,12 +3,22 @@ import 'package:flutter/material.dart'; class VisaListWidget extends StatelessWidget { final List> visaList; final Function(bool, Map, String) onOpen; - final Function(Map) onDeleteMiscellaneous; + final Function(Map) onDeleteMiscellaneous; final Map? apiData; final List? 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 _buildDataRows() { - List> filteredList = visaList - .where((item) => item["is_active"] == "1") - .toList(); + List> filteredList = + visaList.where((item) => item["is_active"] == "1").toList(); print("filteredList- $filteredList"); List visatypeList = apiData?['visa_type_of_visa'] ?? []; - List countryList = apiCountryData ?? []; + List 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 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(); } diff --git a/lib/Screens/organization/mailSettings.dart b/lib/Screens/organization/mailSettings.dart index fa86cef..48cbf97 100644 --- a/lib/Screens/organization/mailSettings.dart +++ b/lib/Screens/organization/mailSettings.dart @@ -9,10 +9,12 @@ import '../../widgets/custom_text_field.dart'; class MailSetting extends StatefulWidget { bool isDesktop; final Function(Map) onMailDataChanged; + final Map initialMailData; MailSetting({ super.key, required this.isDesktop, + required this.initialMailData, required this.onMailDataChanged, }); @@ -26,6 +28,7 @@ class _MailSettingState extends State { Map errorMessages = {}; final Map controllers = {}; + bool _obscurePassword = true; List dataHeader = [ "host", @@ -78,6 +81,27 @@ class _MailSettingState extends State { } _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 { 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 { 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 { onChanged: (value) { _clearError("mail_password"); }, + obscureText: _obscurePassword, style: TextStyle(fontSize: 12), decoration: InputDecoration( labelText: "password", @@ -377,6 +413,19 @@ class _MailSettingState extends State { 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 { 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 { 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(); }, diff --git a/lib/Screens/organization/orgSetup.dart b/lib/Screens/organization/orgSetup.dart index 2494498..229e8f2 100644 --- a/lib/Screens/organization/orgSetup.dart +++ b/lib/Screens/organization/orgSetup.dart @@ -5,9 +5,12 @@ import 'package:flutter/material.dart'; import 'package:frontend/Screens/organization/mailSettings.dart'; import 'package:frontend/Screens/organization/themeColor.dart'; import 'package:go_router/go_router.dart'; +import 'package:http/http.dart' as http; +import 'package:http_parser/http_parser.dart'; import 'package:image_picker/image_picker.dart'; import 'package:responsive_builder/responsive_builder.dart'; +import '../../config/apiUrl.dart'; import '../../routes/custom_appBar.dart'; import '../../routes/custom_drawer.dart'; import '../../services/apiService.dart'; @@ -20,19 +23,94 @@ class OrgSetUp extends StatefulWidget { class _OrgSetUpState extends State { final ApiService apiService = ApiService(); + String? userId; + String? orgId; + String? token; + + bool isViewMode = false; + bool showMail = false; + Map errorMessages = {}; final Map controllers = {}; List? apiAllServices; - List? selectedService; + Map? selectedOrg; + Color? layoutColor; + Color? bodyColor; bool isSelected = false; + Uint8List? _webImage; + Uint8List? _imageBytes; + final TextEditingController _orgNameController = TextEditingController(); + + // List selectedServiceIds = []; + + List> selectedServiceIds = []; + @override void initState() { super.initState(); - loadAllServices(); - getUpdatedServices(); + + WidgetsBinding.instance.addPostFrameCallback((_) { + loadAllServices(); + getOrganizationData(); + initializeData(); + 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; + }); + } + + Map mailConfig = {}; + + Map get orgData { + final data = { + "org_id": 1, + "name": _orgNameController.text, + "logo": null, + + "sender_email": mailConfig['sender_email'], + "mail_user_name": mailConfig['mail_user_name'], + "mail_password": mailConfig['mail_password'], + "mail_host": mailConfig['mail_host'], + "mail_port": mailConfig['mail_port'], + + "layout_color": + "0x${layoutColor?.toARGB32().toRadixString(16).padLeft(8, '0').toUpperCase()}", + "color": + "0x${bodyColor?.toARGB32().toRadixString(16).padLeft(8, '0').toUpperCase()}", + + "services_ids": jsonEncode(selectedServiceIds), + + "created_by": null, + "updated_by": null, + "is_active": 1 + + // "org_id": orgId, + // "created_by": userId, + // "updated_by": userId, + }; + + // Only add group_id if it's an edit operation + // if (widget.group != null && widget.group!.containsKey('group_id')) { + // data["group_id"] = selectedGroupId; + // } + + return data; } Future loadAllServices() async { @@ -47,17 +125,95 @@ class _OrgSetUpState extends State { } } - Future getUpdatedServices() async { - try { - final result = await apiService.fetchUpdatedOrganization(); + Future initializeData() async { + token = await getToken(); + userId = await getUserId(); + if (token == null || userId == null) { + print("Token or USerId missing"); + print("Retrieved Token: $token"); + print("Retrieved UserId: $userId"); + return; + } else { + setState(() {}); + } + } + + Future getOrganizationData() async { + try { + print("getUpdatedServices"); + + final result = await apiService.fetchOrganization(); + print("UUPdatedServices - $result"); setState(() { - selectedService = result; + selectedOrg = result; + + String? rawLogoPath = selectedOrg?['logo']; + if (rawLogoPath != null && rawLogoPath.contains('/assets')) { + const baseUrl = "https://apitest.tripapprovaltool.com"; + final assetPath = rawLogoPath.split('/assets').last; + selectedOrg!['logo'] = "$baseUrl/assets$assetPath"; + } + + _orgNameController.text = selectedOrg?['name']; + + layoutColor = selectedOrg?['layout_color'] != null + ? Color(int.parse( + selectedOrg!['layout_color'].toString().replaceFirst('0x', ''), + radix: 16)) + : Colors.white; + + bodyColor = selectedOrg?['color'] != null + ? Color(int.parse( + selectedOrg!['color'].toString().replaceFirst('0x', ''), + radix: 16)) + : Colors.blue; + + // Set mail config fields + mailConfig['sender_email'] = selectedOrg?['sender_email']; + mailConfig['mail_user_name'] = selectedOrg?['mail_user_name']; + mailConfig['mail_password'] = selectedOrg?['mail_password']; + mailConfig['mail_host'] = selectedOrg?['mail_host']; + mailConfig['mail_port'] = selectedOrg?['mail_port']; + + // Set selected service IDs + // final services = selectedOrg?['services_ids'] as List? ?? []; + // selectedServiceIds = + // services.map((item) => item['service_id'].toString()).toList(); + // + + final servicesRaw = selectedOrg?['services_ids']; + + List services; + + if (servicesRaw is String) { + try { + services = jsonDecode(servicesRaw); + } catch (e) { + print('❌ Failed to decode services_ids: $e'); + services = []; + } + } else if (servicesRaw is List) { + services = servicesRaw; + } else { + services = []; + } + + selectedServiceIds = services.map>((item) { + // force cast or copy to a regular map + final map = Map.from(item); + return { + "service_id": map['service_id'].toString(), + }; + }).toList(); }); - print("UUPdatedServices - $selectedService"); + orgId = await getOrgId(); + + print("selectedOrg - $selectedOrg"); + print("mailConfig - $mailConfig"); } catch (e) { - print('Error fetching role list: $e'); + print('Error fetching updatedServices list: $e'); } } @@ -88,19 +244,84 @@ class _OrgSetUpState extends State { } } - void handleSubmit() { - // print("HandleSubmiy - $groupData"); + Future createOrgData(Map userData) async { + final bool isUpdating = selectedOrg != null && selectedOrg!.isNotEmpty; + final uri = Uri.parse( + isUpdating + ? '$apiUrl/api/organizations/update/${selectedOrg?["org_id"]}' + : '$apiUrl/api/organizations/create', + ); - // setState(() { - // if (!isValidData(groupData)) { - // print("USERDETAILS : $groupData"); - // print("Validation Failed: Required fields are missing."); - // return; // Stop execution if validation fails - // } else { - // print("USERDETAILS : $groupData"); - // postGroupData(groupData); - // } - // }); + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + // Use MultipartRequest (POST only) + final request = http.MultipartRequest('POST', uri); + request.headers['Authorization'] = 'Bearer $token'; + + // If updating, spoof the method Laravel-style + if (isUpdating) { + request.fields['_method'] = 'PUT'; + request.fields['org_id'] = selectedOrg!["org_id"].toString(); + print("UpdatingLarvel..."); + } + + // Add all non-null and non-empty user data fields + userData.forEach((key, value) { + if (value != null && value.toString().trim().isNotEmpty) { + request.fields[key] = value.toString(); + } + }); + + if (_imageBytes != null) { + final multipartFile = http.MultipartFile.fromBytes( + 'logo', // 👈 this should match the key expected by your backend + _imageBytes!, + filename: 'logo.png', + contentType: MediaType('image', 'png'), + ); + request.files.add(multipartFile); + print("📎 Logo image attached."); + } else { + print("⚠️ No logo selected."); + } + + print("🚀 Sending request with fields: ${request.fields}"); + + try { + final streamedResponse = await request.send(); + final response = await http.Response.fromStream(streamedResponse); + print("Response status: ${response.statusCode}"); + print("Response body: ${response.body}"); + + if (response.statusCode == 200 || response.statusCode == 201) { + print("✅ User submitted successfully!"); + print("📨 Response: ${response.body}"); + context.go('/home'); + } else { + print("❌ Submission failed. Status: ${response.statusCode}"); + print("📨 Body: ${response.body}"); + } + } catch (e) { + print("🔥 Error submitting user: $e"); + } + } + + void handleSubmit() { + print("HandleSubmiy - $orgData"); + createOrgData(orgData); + + setState(() { + if (!isValidData(orgData)) { + print("USERDETAILS : $orgData"); + print("Validation Failed: Required fields are missing."); + return; // Stop execution if validation fails + } else { + print("USERDETAILS : $orgData"); + createOrgData(orgData); + } + }); } @override @@ -123,216 +344,338 @@ class _OrgSetUpState extends State { } Widget buildOrganizationLayout(isDesktop) { - File? _imageFile; - Uint8List? _webImage; - - Color? layoutColor; - Color? bodyColor; - Future _pickImage() async { - final pickedFile = - await ImagePicker().pickImage(source: ImageSource.gallery); + final picker = ImagePicker(); + final XFile? pickedFile = + await picker.pickImage(source: ImageSource.gallery); - if (pickedFile != null) { - if (kIsWeb) { - final bytes = await pickedFile.readAsBytes(); // <-- key part + if (pickedFile != null && kIsWeb) { + try { + final bytes = await pickedFile.readAsBytes(); + print('✅ Image loaded, size: ${bytes.length} bytes'); setState(() { - _webImage = bytes; - }); - } else { - setState(() { - _imageFile = File(pickedFile.path); + _imageBytes = bytes; }); + } catch (e) { + print('❌ Error reading image bytes: $e'); } + } else { + print('⚠️ Image picking canceled or not on web.'); } } return Container( - color: Colors.white, - width: double.infinity, - height: MediaQuery.of(context).size.height, - margin: const EdgeInsets.all(8), - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - color: Colors.white, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, + decoration: BoxDecoration( + // color: Colors.amber, + color: bodyColor, + border: Border.all(color: Color(0xFFF7F7FB), width: 3.5)), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Container( + // color: bodyColor, + // color: Colors.grey, + width: double.infinity, + // height: MediaQuery.of(context).size.height, + padding: const EdgeInsets.all(8), + child: Column( children: [ - Text( - "Create Organization", - style: TextStyle(fontSize: 18), - ), - ], - ), - ), - Container( - color: Colors.white, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Name:", - style: TextStyle(fontSize: 16, color: Colors.black), - ), - SizedBox(width: 8), - Expanded( - child: TextField( - // controller: _visaCommentsController, - style: TextStyle(fontSize: 16, color: Colors.blueAccent), - decoration: InputDecoration( - labelText: "Enter Organization Name", - labelStyle: TextStyle(fontSize: 16, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 4), - ), - ), - ), - Spacer(), - GestureDetector( - onTap: _pickImage, - child: CircleAvatar( - radius: 30, - backgroundColor: Colors.amber, - child: ClipOval( - child: _webImage != null - ? Image.memory(_webImage!, - width: 50, height: 50, fit: BoxFit.cover) - : _imageFile != null - ? Image.file(_imageFile!, - width: 50, height: 50, fit: BoxFit.cover) - : Icon(Icons.camera_alt, - size: 18, color: Colors.white), - ), - ), - ), - ], - ), - ), - SizedBox( - height: 15, - ), - Container( - color: Colors.white, - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Mail Settings", - style: TextStyle(color: Colors.blueAccent), - ), - Icon( - Icons.keyboard_arrow_down_outlined, - color: Colors.blueAccent, - size: 30, - ), - ], - ), - Container( - // width: double.infinity, - decoration: BoxDecoration( - border: Border.all( - color: Colors.blueGrey.shade100, - width: 1.0, - // color: Colors.blueAccent - )), - child: Row( - mainAxisAlignment: isDesktop - ? MainAxisAlignment.start - : MainAxisAlignment.center, - children: [ - MailSetting( - isDesktop: isDesktop, - onMailDataChanged: (updatedData) { - // You can setState here or do something else with updatedData - // print("Updated Mail Data: $updatedData"); - }, + SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Container( + padding: const EdgeInsets.all(20), + // height: MediaQuery.of(context).size.height * 0.8, + color: Colors.white, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + color: Colors.white, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Create Organization", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600), + ), + ], ), - ], - )) - ], - )), - SizedBox( - height: 10, - ), - Text( - "Services", - style: TextStyle( - fontWeight: FontWeight.bold, + ), + Container( + color: Colors.white, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.only(top: 1.0), + child: Text( + "Name:", + style: TextStyle( + fontFamily: "Archivo", + fontSize: 14, + fontWeight: FontWeight.w600, + color: Color(0xFF212121)), + ), + ), + SizedBox(width: 8), + Expanded( + child: TextFormField( + controller: _orgNameController, + style: TextStyle( + fontSize: 16, + color: Color(0xFF114D8B), + ), + decoration: InputDecoration( + hintText: "Enter Organization Name", + hintStyle: TextStyle( + fontSize: 14, color: Colors.grey), + floatingLabelBehavior: + FloatingLabelBehavior.never, + border: InputBorder.none, + isDense: true, + // contentPadding: + // EdgeInsets.symmetric(vertical: 14), + ), + // textAlignVertical: TextAlignVertical.center, + ), + ), + Spacer(), + GestureDetector( + onTap: _pickImage, + child: _imageBytes != null + ? ClipOval( + child: Image.memory( + _imageBytes!, + width: 50, + height: 50, + fit: BoxFit.cover, + ), + ) + : selectedOrg?['logo'] != null + ? ClipOval( + child: Image.network( + selectedOrg!['logo'], + width: 50, + height: 50, + fit: BoxFit.cover, + errorBuilder: (context, error, + stackTrace) { + return const CircleAvatar( + radius: 20, + backgroundColor: + Colors.redAccent, + child: Icon(Icons.error, + size: 10), + ); + }, + ), + ) + : const CircleAvatar( + radius: 20, + backgroundColor: Colors.amber, + child: Icon(Icons.add_a_photo, + size: 10), + ), + ), + ], + ), + ), + SizedBox( + height: 5, + ), + Container( + color: Colors.white, + child: Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Mail Settings", + style: TextStyle( + fontFamily: "Archivo", + fontSize: 14, + fontWeight: FontWeight.w600, + color: Color(0xFF212121)), + ), + + // GestureDetector( + // onTap: () { + // setState(() { + // showMail = !showMail; + // }); + // }, + // child: Icon( + // Icons.keyboard_arrow_down_outlined, + // color: Color(0xFF114D8B), + // size: 30, + // ), + // ), + ], + ), + // if (showMail) + + SizedBox( + height: 3, + ), + Container( + // width: double.infinity, + decoration: BoxDecoration( + border: Border.all( + color: Color(0xFFF5F5F5), + // color: bodyColor ?? Colors.grey, + width: 1.0, + ), + // color: bodyColor, + color: Color(0xFFF5F5F5), + ), + child: Row( + mainAxisAlignment: isDesktop + ? MainAxisAlignment.start + : MainAxisAlignment.center, + children: [ + mailConfig['sender_email'] != null + ? MailSetting( + isDesktop: isDesktop, + initialMailData: mailConfig, + onMailDataChanged: + (updatedData) { + // You can setState here or do something else with updatedData + print( + "Updated Mail Data: $updatedData"); + + mailConfig = updatedData; + }, + ) + : CircularProgressIndicator(), + ], + )) + ], + )), + SizedBox( + height: 5, + ), + + Text( + "Services", + style: TextStyle( + fontFamily: "Archivo", + fontSize: 14, + fontWeight: FontWeight.w600, + color: Color(0xFF212121)), + ), + SizedBox( + height: 5, + ), + Container( + decoration: BoxDecoration( + border: Border.all(color: Color(0xFFF4F4FB)), + borderRadius: BorderRadius.circular(1), + // color: bodyColor, + color: Color(0xFFF5F5F5), + ), + padding: EdgeInsets.only( + left: 5, right: 5, top: 15, bottom: 15), + child: isDesktop + ? Row( + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, + // mainAxisSize: MainAxisSize.min, + children: _buildOptions(), + ) + : Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: _buildOptions(), + ), + ), + ), + ), + SizedBox( + height: 5, + ), + + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Choose Theme", + style: TextStyle( + fontFamily: "Archivo", + fontSize: 14, + fontWeight: FontWeight.w600, + color: Color(0xFF212121)), + ), + Container( + decoration: BoxDecoration( + // border: Border.all(color: Color(0xFFF4F4FB)), + borderRadius: BorderRadius.circular(1), + // color: Color(0xFFF4F4FB), + ), + padding: EdgeInsets.only( + left: 5, right: 5, top: 15, bottom: 5), + child: layoutColor != null && bodyColor != null + ? ColorThemePickerWidget( + initialLayoutColor: layoutColor, + initialBodyColor: bodyColor, + onLayoutColorSelected: + (Color selectedLayoutColor) { + setState(() { + layoutColor = selectedLayoutColor; + }); + }, + onBodyColorSelected: + (Color selectedBodyColor) { + setState(() { + bodyColor = selectedBodyColor; + }); + }, + ) + : CircularProgressIndicator(), + ), + ], + ), + + // isDesktop + // ? Row( + // mainAxisAlignment: MainAxisAlignment.end, + // children: _buildSubmit(isDesktop), + // ) + // : Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: _buildSubmit(isDesktop), + // ) + ], + ), + ), + ), + ], ), ), - SizedBox( - height: 10, - ), - Container( - decoration: BoxDecoration( - border: Border.all(color: Color(0xFFF4F4FB)), - borderRadius: BorderRadius.circular(1), - color: Color(0xFFF4F4FB), - ), - padding: EdgeInsets.only(left: 5, right: 5, top: 15, bottom: 15), + ), + Container( + padding: const EdgeInsets.all(8), + color: Colors.white, child: isDesktop ? Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - // mainAxisSize: MainAxisSize.min, - children: _buildOptions(), + mainAxisAlignment: MainAxisAlignment.end, + // children: [Text("Button")], + children: + _buildSubmit(isDesktop, isViewMode, layoutColor), ) - : Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: _buildOptions(), - ), - ), - ), - ), - SizedBox( - height: 10, - ), - Text( - "Choose Theme", - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - Container( - decoration: BoxDecoration( - // border: Border.all(color: Color(0xFFF4F4FB)), - borderRadius: BorderRadius.circular(1), - // color: Color(0xFFF4F4FB), - ), - padding: EdgeInsets.only(left: 5, right: 5, top: 15, bottom: 15), - child: ColorThemePickerWidget( - onLayoutColorSelected: (Color selectedLayoutColor) { - setState(() { - layoutColor = selectedLayoutColor; - }); - }, - onBodyColorSelected: (Color selectedBodyColor) { - setState(() { - bodyColor = selectedBodyColor; - }); - }, - ), - ), - isDesktop - ? Row( - mainAxisAlignment: MainAxisAlignment.end, - children: _buildSubmit(isDesktop), - ) - : Row( - mainAxisAlignment: MainAxisAlignment.center, - children: _buildSubmit(isDesktop), - ) - ], - ), + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: + _buildSubmit(isDesktop, isViewMode, layoutColor), + )) + ], ), ); } @@ -353,12 +696,28 @@ class _OrgSetUpState extends State { 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); + + String serviceId = service['service_id'].toString(); + // bool isSelected = selectedServiceIds.contains(serviceId); + bool isSelected = + selectedServiceIds.any((item) => item["service_id"] == serviceId); return GestureDetector( onTap: () { setState(() { - // selectedListOption = title; - // isSelected = false; + String serviceId = service['service_id'].toString(); + + // Check if already selected + int existingIndex = selectedServiceIds + .indexWhere((item) => item["service_id"] == serviceId); + + if (existingIndex != -1) { + selectedServiceIds.removeAt(existingIndex); + } else { + selectedServiceIds.add({"service_id": serviceId}); + } }); }, child: Row(children: [ @@ -368,10 +727,17 @@ class _OrgSetUpState extends State { width: 18, height: 18, errorBuilder: (context, error, stackTrace) { - return Icon(fallbackIcon, size: 18, color: Colors.blueAccent); + return Icon(fallbackIcon, + size: 18, + color: isSelected == name + ? Color(0xFF114D8B) + : Color(0xFF475569)); }, ) - : Icon(fallbackIcon, size: 18, color: Colors.blueAccent), + : Icon(fallbackIcon, + size: 18, + color: + isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569)), SizedBox(width: 5), @@ -380,35 +746,29 @@ class _OrgSetUpState extends State { name, style: TextStyle( fontSize: 13, - // color: selectedListOption == title ? Colors.blueAccent : Color(0xFF575A74), - color: Colors.grey, - fontWeight: FontWeight.bold), + color: isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569), + fontFamily: "Archivo", + fontWeight: + isSelected == name ? FontWeight.bold : FontWeight.w500), // fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)), ), SizedBox(width: 5), // if (selectedListOption == title && widget.isViewMode == false) - GestureDetector( - onTap: () { - setState(() { - isSelected = !isSelected; - }); - }, - child: Container( - height: 15, - width: 15, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: isSelected ? Colors.green : Colors.grey, width: 1), - ), - child: Icon( - Icons.check_circle, - size: 10, - color: isSelected ? Colors.green : Colors.grey, - // color: Colors.grey, - )), - ), + Container( + height: 15, + width: 15, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: isSelected ? Colors.green : Colors.grey, width: 1), + ), + child: Icon( + Icons.check_circle, + size: 10, + color: isSelected ? Colors.green : Colors.grey, + // color: Colors.grey, + )), ]), ); } @@ -416,42 +776,42 @@ class _OrgSetUpState extends State { IconData _getLocalIconForService(String name) { switch (name.toLowerCase()) { case 'flight': - return Icons.flight; + return Icons.flight_takeoff_outlined; case 'train': - return Icons.train; + return Icons.train_outlined; case 'bus': - return Icons.directions_bus; + return Icons.bus_alert_outlined; case 'taxi': - return Icons.local_taxi; + return Icons.local_taxi_outlined; case 'accomodation': - return Icons.hotel; + return Icons.local_hotel_outlined; case 'forex': - return Icons.attach_money; + return Icons.attach_money_outlined; case 'insurance': - return Icons.verified_user; + return Icons.list_alt_outlined; case 'visa': - return Icons.badge; + return Icons.badge_outlined; case 'miscellaneous': - return Icons.widgets; + return Icons.card_giftcard_outlined; default: return Icons.circle_notifications; } } - List _buildSubmit(isDesktop) { + List _buildSubmit(isDesktop, bool isViewMode, Color? layoutColor) { return [ 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.grey, width: 2), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), onPressed: () { - context.go('/group'); + context.go('/home'); }, child: Text("Cancel")), SizedBox( @@ -463,14 +823,14 @@ class _OrgSetUpState extends State { // : 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 + layoutColor, // Ensure color remains when disabled disabledForegroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), - side: BorderSide(color: Colors.blueAccent, width: 2), + side: BorderSide(color: layoutColor ?? Colors.grey, width: 2), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), diff --git a/lib/Screens/organization/org_List.dart b/lib/Screens/organization/org_List.dart new file mode 100644 index 0000000..7a12ec2 --- /dev/null +++ b/lib/Screens/organization/org_List.dart @@ -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 { + final ApiService apiService = ApiService(); + + Map? apiAllOrganization; + + @override + void initState() { + super.initState(); + loadAllOrganization(); + } + + Future 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 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 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"), + ), + ], + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/Screens/organization/themeColor.dart b/lib/Screens/organization/themeColor.dart index 6e1454a..7c14f12 100644 --- a/lib/Screens/organization/themeColor.dart +++ b/lib/Screens/organization/themeColor.dart @@ -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 { - Color? selectedLayoutColor; - Color? selectedBodyColor; + late Color selectedLayoutColor; + late Color selectedBodyColor; // Layout colors final List layoutThemeColors = [ Color(0xFF448AFF), // BlueAccent Color(0xFFF44336), // Red - Color(0xFF4CAF50), // Green + Color(0xFF12B24B), // Green Color(0xFFFF9800), // Orange Color(0xFF9C27B0), // Purple ]; // Body colors final List 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 { colors: bodyThemeColors, onColorSelected: (color) { setState(() { - selectedBodyColor = color.withOpacity(0.3); // low opacity + selectedBodyColor = color; // low opacity }); widget.onBodyColorSelected(selectedBodyColor!); }, diff --git a/lib/Screens/plans/create_plans.dart b/lib/Screens/plans/create_plans.dart index fd4f2ed..fafb225 100644 --- a/lib/Screens/plans/create_plans.dart +++ b/lib/Screens/plans/create_plans.dart @@ -12,18 +12,48 @@ import '../../config/apiUrl.dart'; import '../../data/models/plan.dart'; import '../../routes/custom_appBar.dart'; import '../../routes/custom_drawer.dart'; +import '../../services/apiService.dart'; import '../../widgets/custom_radio_button.dart'; import '../../widgets/custom_text_field.dart'; +import '../approvals/approval_dialogs.dart'; import '../dialog/user_selection_dialog.dart'; class CreatePlan extends StatefulWidget { - const CreatePlan({super.key}); + CreatePlan({super.key}); @override _CreatePlansState createState() => _CreatePlansState(); } class _CreatePlansState extends State { + final GlobalKey _createPlanKey = + GlobalKey(); + + Color layoutColor = Colors.redAccent; + Color bodyColor = Colors.white; + + @override + void initState() { + super.initState(); + + 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; + }); + } + @override Widget build(BuildContext context) { return ResponsiveBuilder(builder: (context, sizingInfo) { @@ -34,107 +64,192 @@ class _CreatePlansState extends State { body: Row( children: [ if (isDesktop) CustomDrawer(isDesktop: true), - Expanded(child: buildUserTable(isDesktop, context)), + Expanded( + child: + buildUserTable(isDesktop, context, bodyColor, layoutColor)), ], ), ); }); } -} -Widget buildUserTable(bool isDesktop, context) { - final args = GoRouterState.of(context).extra as Map? ?? {}; - // final planData = args?['planData']; - final bool isViewMode = args?['isViewMode'] ?? false; + Widget buildUserTable( + bool isDesktop, context, Color? bodyColor, Color layoutColor) { + final args = GoRouterState.of(context).extra as Map? ?? {}; + // final planData = args?['planData']; + final bool isViewMode = args?['isViewMode'] ?? false; + final bool isApprover = args?['isApprover'] ?? false; - final Map planData = - args['planData'] as Map? ?? {}; + final Map planData = + args['planData'] as Map? ?? {}; - // print("isViewMode: $isViewMode"); + // print("isViewMode: $isViewMode"); - // final bool isViewMode = true; - // final planData = GoRouterState.of(context).extra as Map? ?? {}; + // final bool isViewMode = true; + // final planData = GoRouterState.of(context).extra as Map? ?? {}; - print("RECived palndata"); - // print("RECived palndata - ${planData}"); + print("RECived palndata"); + // print("RECived palndata - ${planData}"); - return Container( - margin: - const EdgeInsets.only(left: 10.0, right: 15.0, top: 10.0, bottom: 10.0), - decoration: - BoxDecoration(border: Border.all(color: Color(0xFFF7F7FB), width: 3.5)), - child: Column( - children: [ - Container( - color: Color(0xFFF4F4FB), - padding: EdgeInsets.symmetric(vertical: 10, horizontal: 16), - child: Row(children: [ - Row( - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Icon( - Icons.create_new_folder_outlined, - color: Color(0xFF84869A), - size: 23, - ), - ), - Text( - isViewMode - ? "View Plan" - : (planData.isNotEmpty ? "Update Plan" : "New Plan"), - style: TextStyle(fontSize: 18), - ), - ], - ), - Spacer(), - // Container( - // color: Color(0xFFE9EBF6), - // child: IconButton( - // icon: Icon(Icons.close), - // onPressed: () { - // context.go('/listPlan'); - // }, - // ), - // ) - ]), - ), - Expanded( - child: Container( - color: Colors.white, - child: SingleChildScrollView( - child: Padding( - padding: EdgeInsets.all(26.0), - child: CreateNewPlan( + return Container( + // margin: const EdgeInsets.only(left: 10.0, right: 15.0, top: 10.0, bottom: 10.0), + decoration: BoxDecoration( + // color: Colors.amber, + color: bodyColor, + border: Border.all(color: Color(0xFFF7F7FB), width: 3.5)), + child: Column( + children: [ + // Container( + // color: Color(0xFFF4F4FB), + // padding: EdgeInsets.symmetric(vertical: 10, horizontal: 16), + // child: Row(children: [ + // Row( + // children: [ + // Padding( + // padding: const EdgeInsets.all(8.0), + // child: Icon( + // Icons.create_new_folder_outlined, + // color: Color(0xFF84869A), + // size: 23, + // ), + // ), + // Text( + // isViewMode + // ? "View Plan" + // : (planData.isNotEmpty ? "Update Plan" : "New Plan"), + // style: TextStyle(fontSize: 18), + // ), + // ], + // ), + // Spacer(), + // ]), + // ), + Expanded( + child: Container( + margin: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), // rounds all corners + ), + + // color: bodyColor, + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.all(20.0), + child: CreateNewPlan( + key: _createPlanKey, + bodyColor: bodyColor, + layoutColor: layoutColor, isDesktop: isDesktop, selectedPlanData: planData, - isViewMode: isViewMode), + isViewMode: isViewMode, + isApprover: isApprover, + ), + ), ), ), ), - ) - ], - ), - ); + + Container( + padding: const EdgeInsets.all(10), + color: Colors.white, + child: isDesktop + ? Row( + mainAxisAlignment: MainAxisAlignment.end, + // children: [Text("Button")], + children: _buildSubmit( + isDesktop, isViewMode, layoutColor, isApprover), + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: _buildSubmit( + isDesktop, isViewMode, layoutColor, isApprover), + )) + ], + ), + ); + } + + List _buildSubmit( + isDesktop, bool isViewMode, Color layoutColor, bool isApprover) { + return [ + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: layoutColor ?? Colors.blueAccent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide(color: layoutColor, width: 2), + ), + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), + ), + onPressed: () { + isApprover ? context.go('/approvallist') : context.go('/listPlan'); + }, + 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, width: 2), + ), + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), + ), + onPressed: isViewMode + ? null + : () { + _createPlanKey.currentState?.handleSubmit(); + }, // Disable when in view mode + + child: Text("Submit"), + ), + ) + ]; + } } class CreateNewPlan extends StatefulWidget { final bool isDesktop; final bool isViewMode; + final bool isApprover; + final Color? bodyColor; + final Color? layoutColor; + final Map selectedPlanData; - const CreateNewPlan( - {super.key, - required this.isDesktop, - required this.selectedPlanData, - required this.isViewMode}); + const CreateNewPlan({ + super.key, + required this.isDesktop, + required this.bodyColor, + required this.layoutColor, + required this.selectedPlanData, + required this.isViewMode, + required this.isApprover, + }); @override - _CreateNewPlansState createState() => _CreateNewPlansState(); + CreateNewPlansState createState() => CreateNewPlansState(); } -class _CreateNewPlansState extends State { +class CreateNewPlansState extends State { + final ApiService apiService = ApiService(); + final TextEditingController _tripTitleController = TextEditingController(); final TextEditingController _descriptionController = TextEditingController(); + final TextEditingController _remarksController = TextEditingController(); final FocusNode _tripTitleFocusNode = FocusNode(); final FocusNode _descriptionFocusNode = FocusNode(); // Declare FocusNode @@ -152,6 +267,7 @@ class _CreateNewPlansState extends State { String? otherUserName; String? selectedplanUserId; bool? selectedIstravelUser; + late Color layoutColorForUser; Map? apiData; // Store API response here List? apiCountryData; @@ -161,6 +277,8 @@ class _CreateNewPlansState extends State { String? orgId; String? planUsrId; String? planTravlrId; + String? statusValue; + String? _selectedTripType; String? selectedCostCenterId; String? _selectedIsBillable; @@ -179,6 +297,9 @@ class _CreateNewPlansState extends State { List> forexList = []; List> flightList = []; + late bool isApproverApproved = false; + late bool isApproverRejected = false; + //Getter Method Map get planData => { "org_id": orgId, @@ -192,7 +313,6 @@ class _CreateNewPlansState extends State { "description": _descriptionController.text, "functional_department": selectedFuncDept, "so_number": "12345", - "status": "0", "created_by": selfId, "updated_by": selfId, "is_active": "1", @@ -205,16 +325,9 @@ class _CreateNewPlansState extends State { "forex": forexList, "insurance": insuranceList, "miscellaneous": miscellaneousList, + // "status_value": statusValue, }; - // // Function to update miscellaneous list - // void updateMiscellaneousData(List> newMiscellaneousList) { - // setState(() { - // miscellaneousList = newMiscellaneousList; // Update miscellaneous data - // }); - // print("Updated Miscellaneous Data in CreateNewPlan: $miscellaneousList"); - // } - void handleItineraryUpdate(String type, List> newList) { setState(() { switch (type) { @@ -290,6 +403,10 @@ class _CreateNewPlansState extends State { void handleUpdateData() { if (widget.selectedPlanData != null) { setState(() { + statusValue = widget.selectedPlanData['status_value'] ?? ''; + + print("STATUS____ : $statusValue"); + planUsrId = widget.selectedPlanData['user_id'] ?? ''; _tripTitleController.text = widget.selectedPlanData['trip_title'] ?? ''; _descriptionController.text = @@ -588,21 +705,115 @@ class _CreateNewPlansState extends State { } } + // Validate at least one service is selected + final serviceLists = [ + flightList, + accommodationList, + busList, + taxiList, + trainList, + visaList, + forexList, + insuranceList, + miscellaneousList, + ]; + + bool anyServiceSelected = + serviceLists.any((list) => list != null && list.isNotEmpty); + if (!anyServiceSelected) { + validationErrors["services"] = "Please select at least one service"; + } + return validationErrors.isEmpty; // Returns true if no errors } + Future callApproveAPI(String planId, String userId) async { + await postToAPI( + endpoint: '/api/plans/approvePlan', + data: { + "plan_id": planId, + "user_id": userId, + }, + methodName: 'Plan Approval', + ); + } + + Future callRejectAPI( + String planId, String userId, String remarks) async { + await postToAPI( + endpoint: '/api/plans/rejectPlan', + data: { + "plan_id": planId, + "user_id": userId, + "reason": remarks, + }, + methodName: 'Plan Rejection', + ); + } + + Future postToAPI({ + required String endpoint, + required Map data, + String methodName = '', + }) async { + final token = await getToken(); + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + try { + final response = await http.post( + Uri.parse('$apiUrl$endpoint'), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + body: jsonEncode(data), + ); + + if (response.statusCode == 200) { + print("$methodName successful!"); + print("Response: ${response.body}"); + + // await apiService.getViewPlan( + // data['planId'],planData + // ); + print("ViewAAA - $planData"); + } else { + print("$methodName failed. Status: ${response.statusCode}"); + print("Error: ${response.body}"); + } + } catch (e) { + print("Error in $methodName: $e"); + } + } + void handleSubmit() { setState(() { if (validateForm()) { - print("Form submitted successfully: $planData"); + // if (selectedPlanId != null && selectedPlanId!.isNotEmpty) { + // planData['plan_id'] = selectedPlanId; // Add plan_id for update + // } + // + // print("Form submitted successfully:" + // " ${_remarksController.text}, ${planData['user_id']}, ${planData['traveller_id']}, " + // "${planData['traveller_id']}," + // " ${selectedPlanId}, " + // " "); + postPlanData(planData); - context.go('/listPlan'); + + widget.isApprover + ? context.go('/approvallist') + : context.go('/listPlan'); } }); } - Future postPlanData(Map planData) async { + Future postPlanData(planData) async { final String apiUrldata = '$apiUrl/api/plans/createOrEditPlan'; + final token = await getToken(); // Fetch token if (token == null) { @@ -613,6 +824,8 @@ class _CreateNewPlansState extends State { planData['plan_id'] = selectedPlanId; // Add plan_id for update } + print("POSTPlanTesting------- $planData}"); + try { final response = await http.post( Uri.parse(apiUrldata), @@ -649,39 +862,237 @@ class _CreateNewPlansState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Text( - // "Plan This Trip For : ${userName} ", // Your label - // style: TextStyle( - // fontSize: 12, - // fontWeight: FontWeight.w600, - // color: Color(0xFF575A74)), - // ), - - Text.rich( - TextSpan( - text: "Plan This Trip For: ", // Static text - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74), // Default color - ), + if (widget.selectedPlanData != null && + widget.selectedPlanData['status_value'] != null) + Row( children: [ - TextSpan( - text: otherUserName ?? - userName ?? - " ", // Dynamic username - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: - Colors.blueAccent, // Change this to any color + Text("Status : "), + if (isApproverApproved == false && + isApproverRejected == false) + Text(statusValue ?? "", + style: TextStyle( + fontFamily: "Archivo", + // fontSize: 13, + fontWeight: FontWeight.bold, + color: widget.layoutColor ?? Colors.grey, + )), + if (isApproverApproved == true) + Text( + "Approved", + style: TextStyle( + fontFamily: "Archivo", + // fontSize: 13, + fontWeight: FontWeight.bold, + color: widget.layoutColor ?? Colors.grey, + ), + ), + if (isApproverRejected == true) + Text("Rejected", + style: TextStyle( + fontFamily: "Archivo", + // fontSize: 13, + fontWeight: FontWeight.bold, + color: widget.layoutColor ?? Colors.grey, + )), + ], + ), + if (widget.isApprover) + Container( + padding: const EdgeInsets.all(10), + // color: Colors.yellow.shade50, + child: Row( + children: [ + Text( + "To Approve or Reject Plan", + style: TextStyle( + fontFamily: "Archivo", + // fontSize: 13, + fontWeight: FontWeight.w600, + color: Color(0xFF212121), + // color: Color(0xFF575A74), // Default color + ), + ), + Spacer(), + MouseRegion( + cursor: widget.isViewMode + ? SystemMouseCursors.forbidden + : SystemMouseCursors.click, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + // backgroundColor: widget.isViewMode + // ? widget.layoutColor + // : Colors + // .grey.shade100, // Keep original color + // foregroundColor: widget.isViewMode + // ? Colors.white + // : Colors.white, // Keep original color + disabledBackgroundColor: statusValue == + "Approved" + ? Colors.green.shade100 + : null, // Ensure color remains when disabled + disabledForegroundColor: + statusValue == "Approved" + ? Colors.white + : Colors.black, + + backgroundColor: isApproverApproved || + (isApproverApproved == false && + isApproverRejected == false && + statusValue == "Approved") + ? Colors.green + : Colors.grey.shade100, + foregroundColor: isApproverApproved || + (isApproverApproved == false && + isApproverRejected == false && + statusValue == "Approved") + ? Colors.white + : Colors.black, + + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + // side: BorderSide( + // color: Colors.green.shade100, width: 2) , + ), + padding: EdgeInsets.symmetric( + horizontal: 20, vertical: 12), + ), + onPressed: widget.isViewMode + ? null + : () async { + final confirmed = + await showApproveDialog( + context, + widget.layoutColor ?? + Colors.grey); + if (confirmed == true) { + setState(() { + isApproverApproved = true; + isApproverRejected = false; + statusValue = "Approved"; + }); + // " ${_remarksController.text}, ${planData['user_id']}, ${planData['traveller_id']}, " + // // "${planData['traveller_id']}," + // // " ${selectedPlanId}, " + callApproveAPI( + selectedPlanId!, + planData['user_id'], + ); // Your API call + } + }, + // Disable when in view mode + + child: Text("Approve"), + ), + ), + SizedBox( + width: 10, + ), + MouseRegion( + cursor: widget.isViewMode + ? SystemMouseCursors.forbidden + : SystemMouseCursors.click, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + // backgroundColor: widget.isViewMode + // ? Colors.redAccent + // : Colors.redAccent + // .shade100, // Keep original color + // foregroundColor: widget.isViewMode + // ? Colors.white + // : Colors.white, // Keep original color + disabledBackgroundColor: statusValue == + "Rejected" + ? Colors.redAccent.shade100 + : null, // Ensure color remains when disabled + disabledForegroundColor: + statusValue == "Rejected" + ? Colors.white + : Colors.black, + + backgroundColor: isApproverRejected || + (isApproverApproved == false && + isApproverRejected == false && + statusValue == "Rejected") + ? Colors.redAccent + : Colors.grey.shade100, + foregroundColor: isApproverRejected || + (isApproverApproved == false && + isApproverRejected == false && + statusValue == "Rejected") + ? Colors.white + : Colors.black, + + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + // side: BorderSide( + // // color: Colors.redAccent.shade100, + // width: 2), + ), + padding: EdgeInsets.symmetric( + horizontal: 20, vertical: 12), + ), + onPressed: widget.isViewMode + ? null + : () async { + final remarks = await showRejectDialog( + context, + widget.layoutColor ?? Colors.grey); + if (remarks != null) { + _remarksController.text = remarks; + setState(() { + isApproverApproved = false; + isApproverRejected = true; + statusValue = "Rejected"; + }); + + callRejectAPI( + selectedPlanId!, + planData['user_id'], + remarks); // Your API call with remarks + } + }, + child: Text("Reject"), + ), + ) + ], + ), + ), + if (isApproverRejected) + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.only(top: 1.0), + child: Text("Remarks : "), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + top: 0), // tweak if needed + child: TextFormField( + controller: _remarksController, + decoration: const InputDecoration( + hintText: "Please enter remarks...", + hintStyle: TextStyle( + color: Colors.grey, fontSize: 15), + border: InputBorder.none, + isDense: true, + ), + maxLines: null, + ), ), ), ], ), - ), - - SizedBox(height: 7), + if (widget.isApprover) + Divider( + thickness: 0.5, + color: Colors.grey, + ), + if (widget.isApprover) + SizedBox( + height: 5, + ), isMobile ? SingleChildScrollView( scrollDirection: Axis.horizontal, @@ -692,18 +1103,48 @@ class _CreateNewPlansState extends State { : Row( children: _buildPlanTrip(isMobile), ), + SizedBox(height: 13), + Text.rich( + TextSpan( + text: "Planning this Trip For : ", // Static text + style: TextStyle( + fontFamily: "Archivo", + fontSize: 13, + fontWeight: FontWeight.w600, + color: Color(0xFF212121), + // color: Color(0xFF575A74), // Default color + ), + children: [ + TextSpan( + text: otherUserName ?? + userName ?? + " ", // Dynamic username + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: widget + .layoutColor, // Change this to any color + // color: Colors.blueAccent, // Change this to any color + ), + ), + ], + ), + ), ], ), ), ], ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Divider( - color: Color(0xFFE6E7F5), // Change color - thickness: 0.5, - ), - ), + // Padding( + // padding: const EdgeInsets.all(8.0), + // child: Divider( + // color: Color(0xFFE6E7F5), // Change color + // thickness: 0.5, + // ), + // ), + + SizedBox(height: 10), + Row( children: [ Column( @@ -819,6 +1260,21 @@ class _CreateNewPlansState extends State { thickness: 0.5, ), ), + + if (validationErrors["services"] != null) + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + validationErrors["services"]!, + style: TextStyle( + color: Colors.red, + fontSize: 10, + fontWeight: FontWeight.bold), + ), + ], + ), + Row( children: [ Expanded( @@ -833,17 +1289,22 @@ class _CreateNewPlansState extends State { ], ), SizedBox(height: 15), - isDesktop - ? Row( - mainAxisAlignment: MainAxisAlignment.end, - children: _buildSubmit(isDesktop), - ) - : Row( - mainAxisAlignment: MainAxisAlignment.center, - children: _buildSubmit(isDesktop), - ) ], ); + + // Row( + // children: [ + // isDesktop + // ? Row( + // mainAxisAlignment: MainAxisAlignment.end, + // children: _buildSubmit(isDesktop), + // ) + // : Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: _buildSubmit(isDesktop), + // ) + // ], + // ) }); } @@ -909,32 +1370,96 @@ class _CreateNewPlansState extends State { color: Color(0xFF575A74)), ), SizedBox(height: 5), + Row( children: purposeList.map((item) { - return Row( - children: [ - Radio( - // value: item['dropdown_key'], // Use dropdown_value as value + final isSelected = + _selectedIsBillable == item['dropdown_key'].toString(); - value: item['dropdown_key'].toString(), // Convert to String - groupValue: _selectedIsBillable, - activeColor: Colors.blueAccent, - onChanged: widget.isViewMode + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 5), + child: CustomTextFieldWrapper( + color: Color(0xFFF5F5F5), + padding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + layoutColor: widget.layoutColor, + width: item['dropdown_key'] == 'some_key' ? 185 : 125, + borderRadius: BorderRadius.circular(25), + isFocused: isSelected, + isDesktop: widget.isDesktop, + child: GestureDetector( + onTap: widget.isViewMode ? null - : (value) { + : () { setState(() { - _selectedIsBillable = value; + _selectedIsBillable = + item['dropdown_key'].toString(); }); print("SELECBILL - $_selectedIsBillable"); }, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + item['dropdown_value'] ?? '', + style: TextStyle( + color: isSelected ? Colors.white : Colors.black, + fontWeight: isSelected ? FontWeight.w500 : null, + fontSize: 13), + ), + const SizedBox(width: 8), + Container( + width: 16, + height: 16, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: isSelected ? Colors.white : Colors.black, + width: isSelected ? 2 : 1, + ), + ), + child: isSelected + ? Icon(Icons.rectangle, + size: 8, color: Colors.white) + : null, + ), + ], + ), ), - - Text(item['dropdown_value'] ?? ''), // Display dropdown_value - SizedBox(width: 20), // Spacing - ], + ), ); }).toList(), ), + + // Row( + // children: purposeList.map((item) { + // // return Row( + // // children: [ + // // Radio( + // // // value: item['dropdown_key'], // Use dropdown_value as value + // // + // // value: item['dropdown_key'].toString(), // Convert to String + // // groupValue: _selectedIsBillable, + // // activeColor: Colors.blueAccent, + // // onChanged: widget.isViewMode + // // ? null + // // : (value) { + // // setState(() { + // // _selectedIsBillable = value; + // // }); + // // print("SELECBILL - $_selectedIsBillable"); + // // }, + // // ), + // // + // // Text(item['dropdown_value'] ?? ''), // Display dropdown_value + // // SizedBox(width: 20), // Spacing + // // ], + // // ); + // }).toList(), + // + // + // ), ]) // Column( // crossAxisAlignment: CrossAxisAlignment.start, @@ -994,33 +1519,88 @@ class _CreateNewPlansState extends State { {"title": "Others", "value": "Option 3"}, ]; + print(" layoutColor: ${widget.layoutColor}"); return options.map((option) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 5), + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 0), child: CustomTextFieldWrapper( - color: Color(0xFFF4F4FB), + // color: Color(0xFFF4F4FB), + color: Color(0xFFF5F5F5), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + layoutColor: widget.layoutColor, width: option["value"] == "Option 2" ? 185 : 125, // Adjust width conditionally + borderRadius: BorderRadius.circular(25), isFocused: _selectedOption == option["value"], isDesktop: isDesktop, - child: RadioListTile( - activeColor: Colors.blueAccent, - contentPadding: EdgeInsets.zero, - dense: true, - title: Text(option["title"]!), - value: option["value"]!, - groupValue: _selectedOption, - onChanged: widget.isViewMode - ? null - : (value) { - setState(() { - _selectedOption = value!; - if (value == 'Option 2' || value == 'Option 3') { - _showInputDialog(option["title"]!); - } - }); - }, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + option["title"]!, + style: TextStyle( + fontSize: 13, + color: _selectedOption == option["value"] + ? Colors.white + : Colors.black, + fontWeight: _selectedOption == option["value"] + ? FontWeight.w600 + : null, + ), + ), + + GestureDetector( + onTap: widget.isViewMode + ? null + : () { + setState(() { + _selectedOption = option["value"]!; + if (option["value"] == 'Option 2' || + option["value"] == 'Option 3') { + _showInputDialog(option["title"]!); + } + }); + }, + 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: _selectedOption == option["value"] + ? Colors.white + : Colors.black, + width: _selectedOption == option["value"] ? 2 : 1, + ), + ), + child: _selectedOption == option["value"] + ? Icon(Icons.rectangle, size: 8, color: Colors.white) + : null, // Add checkmark if selected + ), + ) + + // Radio( + // value: option["value"]!, + // groupValue: _selectedOption, + // activeColor: Colors.white, + // onChanged: widget.isViewMode + // ? null + // : (value) { + // setState(() { + // _selectedOption = value!; + // if (value == 'Option 2' || value == 'Option 3') { + // _showInputDialog(option["title"]!); + // } + // }); + // }, + // ), + ], ), ), ); @@ -1031,55 +1611,139 @@ class _CreateNewPlansState extends State { return [ CustomTextFieldWrapper( color: Color(0xFFF4F4FB), - padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2), + layoutColor: widget.layoutColor, + borderRadius: BorderRadius.circular(25), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), width: 130, isFocused: _selectedTripType == "1", isDesktop: widget.isDesktop, - child: SizedBox( - height: 45, - child: Material( - color: Colors.transparent, - child: RadioListTile( - activeColor: Colors.blueAccent, - contentPadding: EdgeInsets.zero, - visualDensity: VisualDensity.compact, - dense: true, - title: Text("Domestic"), - value: "1", - groupValue: _selectedTripType, - onChanged: widget.isViewMode + 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( + // 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: widget.isViewMode ? null - : (value) { + : () { setState(() { - _selectedTripType = value!; + _selectedTripType = "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 == "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: widget.layoutColor, + borderRadius: BorderRadius.circular(25), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), width: 150, - padding: EdgeInsets.symmetric(horizontal: 5, vertical: 2), + // padding: EdgeInsets.symmetric(horizontal: 5, vertical: 2), isFocused: _selectedTripType == "2", isDesktop: widget.isDesktop, - child: RadioListTile( - activeColor: Colors.blueAccent, - contentPadding: EdgeInsets.zero, - dense: true, - title: Text("International"), - value: "2", - groupValue: _selectedTripType, - onChanged: widget.isViewMode - ? null - : (value) { - setState(() { - _selectedTripType = value!; - }); - }, + 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: widget.isViewMode + ? null + : () { + setState(() { + _selectedTripType = "2"; + }); + }, + 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( + // activeColor: Colors.blueAccent, + // contentPadding: EdgeInsets.zero, + // dense: true, + // title: Text("International"), + // value: "2", + // groupValue: _selectedTripType, + // onChanged: widget.isViewMode + // ? null + // : (value) { + // setState(() { + // _selectedTripType = value!; + // }); + // }, + // ), ), ]; } @@ -1309,7 +1973,7 @@ class _CreateNewPlansState extends State { ); } - List _buildSubmit(isDesktop) { + List _buildSubmit1(isDesktop) { return [ ElevatedButton( style: ElevatedButton.styleFrom( @@ -1322,7 +1986,9 @@ class _CreateNewPlansState extends State { padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), onPressed: () { - context.go('/listPlan'); + widget.isApprover + ? context.go('/approvallist') + : context.go('/listPlan'); }, child: Text("Cancel")), SizedBox( @@ -1363,16 +2029,18 @@ class _CreateNewPlansState extends State { context: context, builder: (BuildContext context) { return UserSelectionDialog( - title: title, - onSubmit: (input, userId, isTraveller) { - setState(() { - otherUserName = input; - selectedplanUserId = userId; - selectedIstravelUser = isTraveller; - }); - print("USer entered : $otherUserName $userId $isTraveller"); - getSelectedPlanFor(); + title: title, + onSubmit: (input, userId, isTraveller) { + setState(() { + otherUserName = input; + selectedplanUserId = userId; + selectedIstravelUser = isTraveller; }); + print("USer entered : $otherUserName $userId $isTraveller"); + getSelectedPlanFor(); + }, + layoutColorForUser: widget.layoutColor!, + ); }); } } diff --git a/lib/Screens/plans/dynamic_itinerary_stepper.dart b/lib/Screens/plans/dynamic_itinerary_stepper.dart index 5cc39bc..8733948 100644 --- a/lib/Screens/plans/dynamic_itinerary_stepper.dart +++ b/lib/Screens/plans/dynamic_itinerary_stepper.dart @@ -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? apiData; final List? apiCountryData; final String? loginUser; - final Function(String, List>) onItineraryUpdate; // Updated Signature - final Map 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>) + onItineraryUpdate; // Updated Signature + final Map 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 { + final ApiService apiService = ApiService(); String selectedOption = ""; String selectedListOption = ""; @@ -46,13 +54,15 @@ class _DynamicItineraryState extends State { Map? selectedItem; int? selectedIndex; + List? apiAllServices; + // List> miscellaneousList = []; Map>> itineraryData = { "Train": [], "Bus": [], "Taxi": [], - "Miscellaneous": [], + "Miscellaneous": [], "Flight": [], "Accomodation": [], "Insurance": [], @@ -60,70 +70,93 @@ class _DynamicItineraryState extends State { "Forex": [], }; - // Store form values for each tab final Map> 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 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>.from(widget.selectedPlanData['train'] ?? []), - "Bus": List>.from(widget.selectedPlanData['bus'] ?? []), - "Taxi": List>.from(widget.selectedPlanData['taxi'] ?? []), - "Miscellaneous": List>.from(widget.selectedPlanData['miscellaneous'] ?? []), - "Flight": List>.from(widget.selectedPlanData['flight'] ?? []), - "Accomodation": List>.from(widget.selectedPlanData['accomodation'] ?? []), - "Insurance": List>.from(widget.selectedPlanData['insurance'] ?? []), - "Visa": List>.from(widget.selectedPlanData['visa'] ?? []), - "Forex": List>.from(widget.selectedPlanData['forex'] ?? []), + "Train": List>.from( + widget.selectedPlanData['train'] ?? []), + "Bus": List>.from( + widget.selectedPlanData['bus'] ?? []), + "Taxi": List>.from( + widget.selectedPlanData['taxi'] ?? []), + "Miscellaneous": List>.from( + widget.selectedPlanData['miscellaneous'] ?? []), + "Flight": List>.from( + widget.selectedPlanData['flight'] ?? []), + "Accomodation": List>.from( + widget.selectedPlanData['accomodation'] ?? []), + "Insurance": List>.from( + widget.selectedPlanData['insurance'] ?? []), + "Visa": List>.from( + widget.selectedPlanData['visa'] ?? []), + "Forex": List>.from( + widget.selectedPlanData['forex'] ?? []), }; }); - } - else { + } else { print("No itinerary data available"); } } bool hasAnyItineraryData() { List 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 { 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 { // }); // } - 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 newData) { setState(() { if (!itineraryData.containsKey(type)) { @@ -181,9 +216,8 @@ class _DynamicItineraryState extends State { // 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 { // 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 { // 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 { 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 data){ - setState(() { - // if(!itineraryData.containsKey(type)){ - // return; - // } + void handleItinerarydelete(String type, Map 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> itemList = itineraryData[type]!; - List> 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 newData) { // setState(() { // if (!itineraryData.containsKey(type)) { @@ -294,265 +325,416 @@ class _DynamicItineraryState extends State { // 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 _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 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 _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 _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, + // ), + // ), + // ]), + // ); + // } } - - diff --git a/lib/Screens/plans/list_plans.dart b/lib/Screens/plans/list_plans.dart index ed8f40b..58e675b 100644 --- a/lib/Screens/plans/list_plans.dart +++ b/lib/Screens/plans/list_plans.dart @@ -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 { 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 initializeData() async { token = await getToken(); userId = await getUserId(); @@ -170,336 +193,331 @@ class _ListPlansState extends State { } 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>( - 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 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>( + 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 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, + ), + ), + ); + }, + ) + ], ), - ], + ), ), ); } diff --git a/lib/Screens/policy/policy.dart b/lib/Screens/policy/policy.dart index bad4648..ce04905 100644 --- a/lib/Screens/policy/policy.dart +++ b/lib/Screens/policy/policy.dart @@ -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 { + final GlobalKey policyCriteriaKey = + GlobalKey(); + + Color? layoutColor; + Color? bodyColor; late String policyType = "domestic"; - int? selectedServiceIndex = 1; + // int? selectedServiceIndex = 1; + ValueNotifier selectedServiceIndex = ValueNotifier("1"); late String selectedService = "Train"; + // ValueNotifier 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>? policy_details = []; + + Map get policyData { + List> 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 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 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 createPolicyData(Map 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 { 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 { 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 { 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 { 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 { ], ), ), - - // 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 { 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 { ), ], ), + SizedBox( + height: 5, + ), Row( children: [ Column( @@ -226,6 +370,10 @@ class _PolicyState extends State { SizedBox( height: 10, ), + Divider( + thickness: 0.2, + color: Colors.grey, + ), isDesktop ? Expanded( child: Row( @@ -242,7 +390,7 @@ class _PolicyState extends State { _buildPolicyCategory(isDesktop), ], ), - ) + ), ], ), ), @@ -290,9 +438,9 @@ class _PolicyState extends State { 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 { 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 { }); }, 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 { 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>? policyData) { + print("🟢 policyData received from child: $policyData"); + + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + policy_details = policyData; + }); + }); + }, + )), ); } List _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( + // 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( + // activeColor: Colors.blueAccent, + // contentPadding: EdgeInsets.zero, + // dense: true, + // title: Text("International"), + // value: "2", + // groupValue: _selectedTripType, + // onChanged: widget.isViewMode + // ? null + // : (value) { + // setState(() { + // _selectedTripType = value!; + // }); + // }, + // ), + ), + ]; + } + + List _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 _buildTripType1(bool isDesktop) { return [ CustomTextFieldWrapper( color: Color(0xFFF4F4FB), @@ -387,6 +737,9 @@ class _PolicyState extends State { onChanged: (value) { setState(() { _selectedTripType = value!; + PolicyName = "Domestic Policy"; + SelectedDomestic = "1"; + SelectedInternational = "0"; }); }, ), @@ -415,6 +768,9 @@ class _PolicyState extends State { onChanged: (value) { setState(() { _selectedTripType = value!; + PolicyName = "International Policy"; + SelectedDomestic = "0"; + SelectedInternational = "1"; }); }, ), diff --git a/lib/Screens/policy/policyCriteria.dart b/lib/Screens/policy/policyCriteria.dart index d045a1f..5d740d7 100644 --- a/lib/Screens/policy/policyCriteria.dart +++ b/lib/Screens/policy/policyCriteria.dart @@ -6,37 +6,154 @@ import '../../widgets/custom_user_form.dart'; class PolicyCriteria extends StatefulWidget { bool isDesktop; - String selectedTab; + String selectedService; + final ValueNotifier selectedTabNotifier; + String? userId; bool isClass = true; bool isCost = true; + final Function(List>?) 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 { - String? selectedFirstApprovarType; - String? selectedService; - String? _selectedTripType; +class PolicyCriteriaState extends State { + String? ServiceId = "1"; + + // final TextEditingController _costController = TextEditingController(); + // final TextEditingController _classController = TextEditingController(); + // String? FirstApproverAction; + // String? SecondApproverAction; + // String? ThirdApproverAction; + // String? SelectedParallelProcess = "3"; + + Map costController = {}; + Map classController = {}; + Map FirstApproverAction = {}; + Map SecondApproverAction = {}; + Map ThirdApproverAction = {}; + Map SelectedParallelProcess = {}; + + Map validationErrors = {}; // bool isClass = true; // bool isCost = true; + List>? policyData = []; + + // Map get policyServices { + // Map 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 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 { 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 { ), 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 { // 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 { height: 40, child: DropdownSearch( selectedItem: - selectedFirstApprovarType, + FirstApproverAction[ + ServiceId], // enabled: !isViewMode, popupProps: PopupProps.menu( // showSearchBox: true, @@ -251,7 +320,7 @@ class _PolicyCriteriaState extends State { maxHeight: 250), ), items: [ - "Approve", + "Approval", "Notification", ], dropdownDecoratorProps: @@ -275,15 +344,16 @@ class _PolicyCriteriaState extends State { 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 { ), ), ), - 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 { height: 40, child: DropdownSearch( selectedItem: - selectedFirstApprovarType, + SecondApproverAction[ + ServiceId], // enabled: !isViewMode, popupProps: PopupProps.menu( // showSearchBox: true, @@ -338,7 +429,7 @@ class _PolicyCriteriaState extends State { maxHeight: 250), ), items: [ - "Approve", + "Approval", "Notification", ], dropdownDecoratorProps: @@ -362,15 +453,16 @@ class _PolicyCriteriaState extends State { 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 { ), ), ), - 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 { height: 40, child: DropdownSearch( selectedItem: - selectedFirstApprovarType, + ThirdApproverAction[ + ServiceId], // enabled: !isViewMode, popupProps: PopupProps.menu( // showSearchBox: true, @@ -425,7 +543,7 @@ class _PolicyCriteriaState extends State { maxHeight: 250), ), items: [ - "Approve", + "Approval", "Notification", ], dropdownDecoratorProps: @@ -449,15 +567,16 @@ class _PolicyCriteriaState extends State { 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 { ), ), ), - 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 { ], ); } + + 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), + ), + ), + ), + ), + ], + ), + ], + ); + } } diff --git a/lib/Screens/policy/policy_list.dart b/lib/Screens/policy/policy_list.dart new file mode 100644 index 0000000..9b5c2d4 --- /dev/null +++ b/lib/Screens/policy/policy_list.dart @@ -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 { + final ApiService apiService = ApiService(); + + List? 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 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 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 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"), + ), + ], + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/Screens/userManagement/user_List.dart b/lib/Screens/userManagement/user_List.dart index 11299d7..4632a99 100644 --- a/lib/Screens/userManagement/user_List.dart +++ b/lib/Screens/userManagement/user_List.dart @@ -21,6 +21,37 @@ class _UserListScreenState extends State { String? selectedUserId; String? orgId; + Color? layoutColor; + Color? bodyColor; + + @override + void initState() { + super.initState(); + futureUsers = fetchUsers(); + + WidgetsBinding.instance.addPostFrameCallback((_) { + fetchCountryList(); + 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 getToken() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString('auth_token'); @@ -102,13 +133,6 @@ class _UserListScreenState extends State { } } - @override - void initState() { - super.initState(); - futureUsers = fetchUsers(); - fetchCountryList(); - } - void handleDelete(userId) { print("handDel - $userId"); } @@ -238,432 +262,562 @@ class _UserListScreenState extends State { } Widget buildUserTable(bool isDesktop) { - return Padding( - padding: const EdgeInsets.all(16.0), - child: - Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( + return Container( + color: bodyColor, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Container( + color: Colors.white, + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - const Text('User List', - style: - TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), - IconButton( - icon: const Icon(Icons.keyboard_arrow_down), - onPressed: () {}, + SizedBox(height: 2), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + const Text('User List', + style: TextStyle( + fontFamily: "Archivo", + fontSize: 16, + fontWeight: FontWeight.w600, + color: Color(0xFF212121))), + ], + ), + ], ), - ], - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - foregroundColor: Colors.white, - backgroundColor: Colors.blueAccent), - onPressed: () async { - List users = await futureUsers; - - // Print the resolved value - print("CREATELIAS - $users"); - - context.go("/CreateUserDetails" - // extra: { - // // 'apiCountryData': apiCountryData, - // 'apiUserData': users, - // } - ); - if (!isDesktop) Navigator.pop(context); - }, - child: Row( - children: [ - Icon( - Icons.add_circle, - color: Colors.white, - ), - SizedBox( - width: 5, - ), - Text('New User'), - ], - ), - ), - ], - ), - const SizedBox(height: 10), - FutureBuilder>( - future: futureUsers, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return 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, - ), - SizedBox(height: 16), - Text( - "Oops!", - style: TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - color: Colors.redAccent, - ), - ), - SizedBox(height: 8), - Text( - "No User Available", - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.grey, - ), - ), - SizedBox(height: 20), - Text( - " Please Create NewUser", - 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, - // ), - // ), - ], - ), + SizedBox(height: 2), + Divider( + thickness: 0.2, // how "thick" the line is + color: Colors.grey, // optional ), - ); - } else if (!snapshot.hasData || snapshot.data!.isEmpty) { - return Center(child: Text("No users found")); - } - - List users = snapshot.data!; - Color borderColor = Color(0xFF9E9DBD); - - return Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: SizedBox( - width: MediaQuery.of(context).size.width * 1.5, - child: SingleChildScrollView( - scrollDirection: Axis - .horizontal, // Inner wrapper for vertical scrolling - - child: ConstrainedBox( - constraints: BoxConstraints( - minWidth: MediaQuery.of(context).size.width * 0.8), - // constraints: BoxConstraints(minWidth: 1300), - // width: MediaQuery.of(context).size.width , - - child: Container( - // color: Colors.grey, - // color: Colors.amber, - child: DataTable( - columnSpacing: - 20.0, // Adjust spacing between columns - dividerThickness: 0.5, - dataRowMinHeight: 60.0, // Minimum row height - dataRowMaxHeight: 100.0, - border: TableBorder( - horizontalInside: BorderSide( - width: 0.5, color: Colors.grey.shade200), + 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), ), - columns: const [ - DataColumn( - label: Text( - 'User Details', - style: TextStyle( - color: Color(0xFF9E9DBD), - fontSize: 15, - fontWeight: FontWeight.bold), - )), - DataColumn( - label: Text( - 'Role', - style: TextStyle( - color: Color(0xFF9E9DBD), - fontSize: 15, - fontWeight: FontWeight.bold), - )), - DataColumn( - label: Text( - 'Level', - style: TextStyle( - color: Color(0xFF9E9DBD), - fontSize: 15, - fontWeight: FontWeight.bold), - )), - DataColumn( - label: Text( - 'Status', - style: TextStyle( - color: Color(0xFF9E9DBD), - fontSize: 15, - fontWeight: FontWeight.bold), - )), - DataColumn( - label: Text( - 'Actions', - style: TextStyle( - color: Color(0xFF9E9DBD), - fontSize: 15, - fontWeight: FontWeight.bold), - )), - ], - - rows: users.map((user) { - String userId = - user['user_id'].toString(); // Get user ID - bool isSelected = selectedUserId == userId; - - return DataRow(cells: [ - // DataCell(Text(user['user_id'].toString())), - DataCell(Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Align( - alignment: Alignment.center, - child: GestureDetector( - onTap: () { - setState(() { - selectedUserId = - userId; // Store clicked user ID - }); - }, - child: Container( - width: 15, // Adjust size - height: 15, - decoration: BoxDecoration( - // Background color - shape: BoxShape.rectangle, - border: Border.all( - color: isSelected - ? Colors.blueAccent - : Color(0xFF9E9DBD), - // color: Color(0xFF9E9DBD), - // color: Color.fromRGBO(128, 128, 128, 0.6), - width: isSelected - ? 2 - : 1), // Grey outline - ), - ), - ), - ), - SizedBox( - width: 50, - ), - Align( - alignment: Alignment.center, - child: Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: Color(0xFF9E9DBD), - width: 1), // Grey outline - ), - child: Padding( - padding: const EdgeInsets.all(2.0), - child: Container( - width: 40, // Adjust size - height: 40, - decoration: BoxDecoration( - color: Colors - .amber, // Inner circle background - shape: BoxShape.circle, - ), - child: Column( - mainAxisAlignment: - MainAxisAlignment.center, - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - Text( - (user['first_name'] != - null && - user['first_name']! - .isNotEmpty) - ? user['first_name']![0] - .toUpperCase() - : "?", - style: TextStyle( - fontSize: 18, - fontWeight: - FontWeight.bold, - color: Colors.white, - ), - ), - ], - ), - ), - )), - ), - SizedBox( - width: 20, - ), - Column( - mainAxisAlignment: - MainAxisAlignment.center, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - "${user['first_name'] ?? ''} ${user['last_name'] ?? ''}", - style: TextStyle( - color: Colors.blueAccent, - fontSize: 16), - ), - ], - ), - SizedBox(height: 5), - Row( - children: [ - Icon(Icons.mail_outline, - size: 15, - color: Color(0xFF9E9EBE)), - SizedBox( - width: 10, - ), - Text(user['email'] ?? '') - ], - ), - SizedBox(height: 5), - Row( - children: [ - Icon(Icons.account_tree_outlined, - size: 15, - color: Color(0xFF9E9EBE)), - SizedBox( - width: 10, - ), - Text(user['user_type'] ?? '') - ], - ), - ], - ), - ], - )), - - DataCell(Text( - user['role_id'] ?? 'N/A', - style: TextStyle( - color: user['is_active'] == "1" - ? Colors.black - : Colors.grey, - fontWeight: FontWeight.bold), - )), - DataCell(Text( - user['level_id'] ?? 'N/A', - style: TextStyle( - color: user['is_active'] == "1" - ? Colors.black - : Colors.grey, - fontWeight: FontWeight.bold), - )), - DataCell(GestureDetector( - onTap: () { - handleToggleUserStatus(user['user_id'], - user['is_active'], user); - }, - child: Text( - user['is_active'] == "1" - ? "Active" - : "Inactive", - style: TextStyle( - color: user['is_active'] == "1" - ? Colors.lightGreen - : Colors.grey, - fontWeight: FontWeight.bold), - ))), - - DataCell( - Row( - children: [ - MouseRegion( - cursor: user['is_active'] == "0" - ? SystemMouseCursors.forbidden - : SystemMouseCursors.click, - child: IconButton( - icon: Icon(Icons.remove_red_eye, - color: user['is_active'] == "0" - ? Colors.grey - : Colors.blueAccent), - onPressed: user['is_active'] == "0" - ? null - : () { - context.go( - "/CreateUserDetails", - extra: { - "selectedUser": user, - "isViewMode": true - }, - ); - }, - ), - ), - MouseRegion( - cursor: user['is_active'] == "0" - ? SystemMouseCursors.forbidden - : SystemMouseCursors.click, - child: IconButton( - icon: Icon(Icons.edit, - color: user['is_active'] == "0" - ? Colors.grey - : Colors.green), - onPressed: user['is_active'] == "0" - ? null - : () { - print("USER: $user"); - - // final userJson = jsonEncode( - // user); // Convert user map to string - // final encodedUser = - // Uri.encodeComponent( - // userJson); - - context.go( - "/CreateUserDetails", - extra: { - "selectedUser": user, - "isViewMode": false - }, - ); - }, - ), - ), - ], - ), - ), - ]); - }).toList(), ), ), ), - ), + // SizedBox(width: 16), + Spacer(), + + 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: () async { + List users = await futureUsers; + + // Print the resolved value + print("CREATELIAS - $users"); + + context.go("/CreateUserDetails" + // extra: { + // // 'apiCountryData': apiCountryData, + // 'apiUserData': users, + // } + ); + if (!isDesktop) Navigator.pop(context); + }, + child: Row( + mainAxisSize: + MainAxisSize.min, // Ensures content fits nicely + children: [ + Text( + "Add New User", + 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>( + future: futureUsers, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return 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, + ), + SizedBox(height: 16), + Text( + "Oops!", + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: Colors.redAccent, + ), + ), + SizedBox(height: 8), + Text( + "No User Available", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.grey, + ), + ), + SizedBox(height: 20), + Text( + " Please Create NewUser", + 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 Center(child: Text("No users found")); + } + + List users = snapshot.data!; + Color borderColor = Color(0xFF9E9DBD); + + return Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: SizedBox( + width: MediaQuery.of(context).size.width * 1.5, + child: SingleChildScrollView( + scrollDirection: Axis + .horizontal, // Inner wrapper for vertical scrolling + + child: ConstrainedBox( + constraints: BoxConstraints( + minWidth: + MediaQuery.of(context).size.width * + 0.8), + // constraints: BoxConstraints(minWidth: 1300), + // width: MediaQuery.of(context).size.width , + + child: Container( + // color: Colors.grey, + // color: Colors.amber, + child: DataTable( + columnSpacing: + 20.0, // Adjust spacing between columns + dividerThickness: 0.5, + dataRowMinHeight: + 60.0, // Minimum row height + dataRowMaxHeight: 100.0, + border: TableBorder( + horizontalInside: BorderSide( + width: 0.5, + color: Colors.grey.shade200), + ), + columns: const [ + DataColumn( + label: Text( + 'User Details', + style: TextStyle( + color: Color(0xFF9E9DBD), + fontSize: 15, + fontWeight: FontWeight.bold), + )), + DataColumn( + label: Text( + 'Role', + style: TextStyle( + color: Color(0xFF9E9DBD), + fontSize: 15, + fontWeight: FontWeight.bold), + )), + DataColumn( + label: Text( + 'Level', + style: TextStyle( + color: Color(0xFF9E9DBD), + fontSize: 15, + fontWeight: FontWeight.bold), + )), + DataColumn( + label: Text( + 'Status', + style: TextStyle( + color: Color(0xFF9E9DBD), + fontSize: 15, + fontWeight: FontWeight.bold), + )), + DataColumn( + label: Text( + 'Actions', + style: TextStyle( + color: Color(0xFF9E9DBD), + fontSize: 15, + fontWeight: FontWeight.bold), + )), + ], + + rows: users.map((user) { + String userId = user['user_id'] + .toString(); // Get user ID + bool isSelected = + selectedUserId == userId; + + return DataRow(cells: [ + // DataCell(Text(user['user_id'].toString())), + DataCell(Row( + mainAxisAlignment: + MainAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Align( + alignment: Alignment.center, + child: GestureDetector( + onTap: () { + setState(() { + selectedUserId = + userId; // Store clicked user ID + }); + }, + child: Container( + width: 15, // Adjust size + height: 15, + decoration: BoxDecoration( + // Background color + shape: BoxShape.rectangle, + border: Border.all( + color: isSelected + ? Colors.blueAccent + : Color(0xFF9E9DBD), + // color: Color(0xFF9E9DBD), + // color: Color.fromRGBO(128, 128, 128, 0.6), + width: isSelected + ? 2 + : 1), // Grey outline + ), + ), + ), + ), + SizedBox( + width: 50, + ), + Align( + alignment: Alignment.center, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: + Color(0xFF9E9DBD), + width: + 1), // Grey outline + ), + child: Padding( + padding: + const EdgeInsets.all( + 2.0), + child: Container( + width: 40, // Adjust size + height: 40, + decoration: BoxDecoration( + color: Colors + .amber, // Inner circle background + shape: BoxShape.circle, + ), + child: Column( + mainAxisAlignment: + MainAxisAlignment + .center, + crossAxisAlignment: + CrossAxisAlignment + .center, + children: [ + Text( + (user['first_name'] != + null && + user['first_name']! + .isNotEmpty) + ? user['first_name']![ + 0] + .toUpperCase() + : "?", + style: TextStyle( + fontSize: 18, + fontWeight: + FontWeight + .bold, + color: + Colors.white, + ), + ), + ], + ), + ), + )), + ), + SizedBox( + width: 20, + ), + Column( + mainAxisAlignment: + MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + "${user['first_name'] ?? ''} ${user['last_name'] ?? ''}", + style: TextStyle( + color: + Colors.blueAccent, + fontSize: 16), + ), + ], + ), + SizedBox(height: 5), + Row( + children: [ + Icon(Icons.mail_outline, + size: 15, + color: + Color(0xFF9E9EBE)), + SizedBox( + width: 10, + ), + Text(user['email'] ?? '') + ], + ), + SizedBox(height: 5), + Row( + children: [ + Icon( + Icons + .account_tree_outlined, + size: 15, + color: + Color(0xFF9E9EBE)), + SizedBox( + width: 10, + ), + Text( + user['user_type'] ?? '') + ], + ), + ], + ), + ], + )), + + DataCell(Text( + user['role_id'] ?? 'N/A', + style: TextStyle( + color: user['is_active'] == "1" + ? Colors.black + : Colors.grey, + fontWeight: FontWeight.bold), + )), + DataCell(Text( + user['level_id'] ?? 'N/A', + style: TextStyle( + color: user['is_active'] == "1" + ? Colors.black + : Colors.grey, + fontWeight: FontWeight.bold), + )), + DataCell(GestureDetector( + onTap: () { + handleToggleUserStatus( + user['user_id'], + user['is_active'], + user); + }, + child: Text( + user['is_active'] == "1" + ? "Active" + : "Inactive", + style: TextStyle( + color: + user['is_active'] == "1" + ? Colors.lightGreen + : Colors.grey, + fontWeight: FontWeight.bold), + ))), + + DataCell( + Row( + children: [ + MouseRegion( + cursor: user['is_active'] == "0" + ? SystemMouseCursors + .forbidden + : SystemMouseCursors.click, + child: IconButton( + icon: Icon( + Icons.remove_red_eye, + size: 18, + color: + user['is_active'] == + "0" + ? Colors.grey + : Color( + 0xFF475569)), + onPressed: + user['is_active'] == "0" + ? null + : () { + context.go( + "/CreateUserDetails", + extra: { + "selectedUser": + user, + "isViewMode": + true + }, + ); + }, + ), + ), + + MouseRegion( + cursor: user['is_active'] == "0" + ? SystemMouseCursors + .forbidden + : SystemMouseCursors.click, + child: GestureDetector( + onTap: + user['is_active'] == "0" + ? null + : () { + context.go( + "/CreateUserDetails", + extra: { + "selectedUser": + user, + "isViewMode": + false + }, + ); + }, + child: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15), + ), + ), + + // MouseRegion( + // cursor: user['is_active'] == "0" + // ? SystemMouseCursors + // .forbidden + // : SystemMouseCursors.click, + // child: IconButton( + // icon: Icon(Icons.edit, + // color: + // user['is_active'] == + // "0" + // ? Colors.grey + // : Colors.green), + // onPressed: + // user['is_active'] == "0" + // ? null + // : () { + // print( + // "USER: $user"); + // + // // final userJson = jsonEncode( + // // user); // Convert user map to string + // // final encodedUser = + // // Uri.encodeComponent( + // // userJson); + // + // context.go( + // "/CreateUserDetails", + // extra: { + // "selectedUser": + // user, + // "isViewMode": + // false + // }, + // ); + // }, + // ), + // ), + ], + ), + ), + ]); + }).toList(), + ), + ), + ), + ), + ), + ), + ); + }, + ), + ]), + )), + ); } } diff --git a/lib/data/models/plan.dart b/lib/data/models/plan.dart index de1934a..f182e12 100644 --- a/lib/data/models/plan.dart +++ b/lib/data/models/plan.dart @@ -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 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"] ?? '', ); } } diff --git a/lib/routes/custom_drawer.dart b/lib/routes/custom_drawer.dart index 0480d79..261d751 100644 --- a/lib/routes/custom_drawer.dart +++ b/lib/routes/custom_drawer.dart @@ -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 { + final ApiService apiService = ApiService(); + String? token; Map? userData; Map? fetchedUserData; Map userDetails = {}; + Map? selectedOrg; + Color? layoutColor; + Color? bodyColor; + @override void initState() { super.initState(); - initializeData(); + // initializeData(); + WidgetsBinding.instance.addPostFrameCallback((_) { + initializeData(); + getOrganizationData(); + }); } Future initializeData() async { @@ -66,92 +78,259 @@ class _CustomDrawerState extends State { return null; } + Future 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 { /// **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 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 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 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); diff --git a/lib/routes/custom_router.dart b/lib/routes/custom_router.dart index 9631930..5729da7 100644 --- a/lib/routes/custom_router.dart +++ b/lib/routes/custom_router.dart @@ -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( diff --git a/lib/services/apiService.dart b/lib/services/apiService.dart index b990787..d95925f 100644 --- a/lib/services/apiService.dart +++ b/lib/services/apiService.dart @@ -265,9 +265,10 @@ class ApiService { } } - Future> fetchUpdatedOrganization() async { + Future> 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 + // final List> orgList = + // List>.from(data['data']); + // return orgList; + + return Map.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> 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.from(plan); + } catch (e) { + throw Exception("Error finding plan: $e"); + } + } + + Future> 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 + // final List> orgList = + // List>.from(data['data']); + // return orgList; + + return Map.from(data['data']); + } catch (e) { + throw Exception('Error parsing response: $e'); + } + } else { + throw Exception('Failed to load organizations'); } } } diff --git a/lib/utils/auth_utils.dart b/lib/utils/auth_utils.dart index 545a682..675a840 100644 --- a/lib/utils/auth_utils.dart +++ b/lib/utils/auth_utils.dart @@ -7,6 +7,16 @@ Future getToken() async { return prefs.getString("auth_token"); } +Future getLayoutColor() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString("layout_color"); +} + +Future getBodyColor() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString("body_color"); +} + Future getUserId() async { final prefs = await SharedPreferences.getInstance(); final String? userDataString = prefs.getString('user_data'); diff --git a/lib/utils/colorOpcity.dart b/lib/utils/colorOpcity.dart new file mode 100644 index 0000000..0a63bc3 --- /dev/null +++ b/lib/utils/colorOpcity.dart @@ -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 + ); + } +} diff --git a/lib/widgets/custom_text_field.dart b/lib/widgets/custom_text_field.dart index 1397b85..8254c07 100644 --- a/lib/widgets/custom_text_field.dart +++ b/lib/widgets/custom_text_field.dart @@ -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 { + 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 { : 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, ); diff --git a/lib/widgets/custom_user_form.dart b/lib/widgets/custom_user_form.dart index a1977af..f4dd57b 100644 --- a/lib/widgets/custom_user_form.dart +++ b/lib/widgets/custom_user_form.dart @@ -21,10 +21,12 @@ class CustomTextFieldUserWrapper extends StatefulWidget { }); @override - _CustomTextFieldUserWrapperState createState() => _CustomTextFieldUserWrapperState(); + _CustomTextFieldUserWrapperState createState() => + _CustomTextFieldUserWrapperState(); } -class _CustomTextFieldUserWrapperState extends State { +class _CustomTextFieldUserWrapperState + extends State { @override Widget build(BuildContext context) { return Container( @@ -35,22 +37,22 @@ class _CustomTextFieldUserWrapperState extends State 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, diff --git a/pubspec.yaml b/pubspec.yaml index 50f6d6e..931740b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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: