diff --git a/assets/images/IconsImg/Domestic.png b/assets/images/IconsImg/Domestic.png new file mode 100644 index 0000000..06c4dd3 Binary files /dev/null and b/assets/images/IconsImg/Domestic.png differ diff --git a/assets/images/IconsImg/Domestic_new.png b/assets/images/IconsImg/Domestic_new.png new file mode 100644 index 0000000..c5abd40 Binary files /dev/null and b/assets/images/IconsImg/Domestic_new.png differ diff --git a/assets/images/IconsImg/Domestic_old.png b/assets/images/IconsImg/Domestic_old.png new file mode 100644 index 0000000..f8002ce Binary files /dev/null and b/assets/images/IconsImg/Domestic_old.png differ diff --git a/assets/images/IconsImg/International.png b/assets/images/IconsImg/International.png new file mode 100644 index 0000000..f93db18 Binary files /dev/null and b/assets/images/IconsImg/International.png differ diff --git a/assets/images/IconsImg/International_new.png b/assets/images/IconsImg/International_new.png new file mode 100644 index 0000000..c67a960 Binary files /dev/null and b/assets/images/IconsImg/International_new.png differ diff --git a/assets/images/IconsImg/International_old.png b/assets/images/IconsImg/International_old.png new file mode 100644 index 0000000..59ae90f Binary files /dev/null and b/assets/images/IconsImg/International_old.png differ diff --git a/lib/Screens/allTrips/list_all_plans.dart b/lib/Screens/allTrips/list_all_plans.dart index b5418a4..85008e4 100644 --- a/lib/Screens/allTrips/list_all_plans.dart +++ b/lib/Screens/allTrips/list_all_plans.dart @@ -723,12 +723,12 @@ class _ListAllPlansState extends State { // ), SizedBox(height: adjHgt / 4), Text( - " No Trips Found", + " No Data Found", textAlign: TextAlign.center, style: GoogleFonts.poppins( fontSize: 20, - fontWeight: FontWeight.w500, - color: Colors.black54, + fontWeight: FontWeight.bold, + color: Colors.grey, ), ), ], @@ -1187,11 +1187,156 @@ class _ListAllPlansState extends State { ), Column( children: [ - PlanPopupMenu( - plan: plan, - deletePlan: deletePlan, - apiService: apiService, - layoutColor: layoutColor, + // PlanPopupMenu( + // plan: plan, + // deletePlan: deletePlan, + // apiService: apiService, + // layoutColor: layoutColor, + // ), + PopupMenuButton( + color: Colors.white, + padding: EdgeInsets.zero, + offset: Offset(0, 30), + icon: Icon( + Icons.more_vert, + color: Color(0xFF475569), + size: 14, + ), + itemBuilder: + (context) => [ + CustomPopupMenuEntry( + child: Container( + padding: + EdgeInsets.symmetric( + horizontal: 8, + vertical: 8, + ), + child: Row( + mainAxisSize: + MainAxisSize.min, + mainAxisAlignment: + MainAxisAlignment + .center, + children: [ + IconButton( + icon: Icon( + Icons + .remove_red_eye, + color: Color( + 0xFF475569, + ), + size: 18, + ), + tooltip: + 'View The Trip Details', + onPressed: () { + Navigator.pop( + context, + ); // Close popup manually + ApiService.viewPlan( + context, + plan.planId, + isViewMode: + true, + ); + }, + ), + if (plan.statusValue == + "Pending Approval" || + plan.statusValue == + "Partially Approved") + IconButton( + icon: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15, + ), + tooltip: + 'Edit The Trip Details', + onPressed: () { + Navigator.pop( + context, + ); + ApiService.viewPlan( + context, + plan.planId, + isViewMode: + false, + ); + }, + ), + IconButton( + icon: Icon( + Icons + .cancel_rounded, + size: 18, + ), + tooltip: + 'Cancellation The Trip Details', + onPressed: () { + Navigator.pop( + context, + ); + deletePlan( + plan.planId, + ); + }, + ), + IconButton( + icon: Icon( + Icons.download, + color: Color( + 0xFF114D8B, + ), + size: 18, + ), + tooltip: + 'Download The PDF', + onPressed: () { + Navigator.pop( + context, + ); + apiService + .getPdfDownload( + plan.planId, + ); + }, + ), + IconButton( + icon: const Icon( + Icons.comment, + color: Color( + 0xFF475569, + ), + size: 11, + ), + tooltip: + 'Trip Comments', + onPressed: () { + showDialog( + context: + context, + builder: + ( + context, + ) => CommentModalList( + // planId: plan.planId, + planId: + plan.planId + .toString(), + layoutColorForUser: + layoutColor!, + role: + "Admin", + ), + ); + }, + ), + ], + ), + ), + ), + ], ), ], ), @@ -1311,7 +1456,7 @@ class _ListAllPlansState extends State { filteredPlans.isEmpty ? Center( child: Text( - "No matches found", + "No Matches Found", style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey, @@ -1326,7 +1471,7 @@ class _ListAllPlansState extends State { filteredPlans.isEmpty ? Center( child: Text( - "No matches found", + "No Matches Found", style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey, diff --git a/lib/Screens/approvals/approval_list.dart b/lib/Screens/approvals/approval_list.dart index f780837..05e1b14 100644 --- a/lib/Screens/approvals/approval_list.dart +++ b/lib/Screens/approvals/approval_list.dart @@ -858,15 +858,12 @@ class _ApprovalListState extends State { // fontWeight: FontWeight.bold, // color: Colors.redAccent)), SizedBox(height: adjHgt / 4), - Text( - " No Trips Pending For Your Approvals", - + Text("No Data Found", textAlign: TextAlign.center, style: GoogleFonts.poppins( fontSize: 20, - // fontFamily: "Inter", - fontWeight: FontWeight.w500, - color: Colors.black54, + fontWeight: FontWeight.bold, + color: Colors.grey, ), ), ], @@ -1688,7 +1685,7 @@ class _ApprovalListState extends State { filteredPlans.isEmpty ? Center( child: Text( - "No matches found", + "No Matches Found", style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey, @@ -1703,7 +1700,7 @@ class _ApprovalListState extends State { filteredPlans.isEmpty ? Center( child: Text( - "No matches found", + "No Matches Found", style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey, diff --git a/lib/Screens/costCenter/costCenterDetails.dart b/lib/Screens/costCenter/costCenterDetails.dart index 697e3c8..9286603 100644 --- a/lib/Screens/costCenter/costCenterDetails.dart +++ b/lib/Screens/costCenter/costCenterDetails.dart @@ -50,6 +50,7 @@ class CostCenterDataState extends State { String? userId; int? costcenterDataId; late String isActive = "1"; + bool isDisable = false; List dataHeader = ["name", "description"]; @@ -163,9 +164,12 @@ class CostCenterDataState extends State { userId = await getUserId(); setState(() { + isDisable = true; // This triggers UI rebuild with error messages if (validateData()) { postCostCenterData(); + }else{ + isDisable = false; } }); @@ -221,22 +225,38 @@ class CostCenterDataState extends State { print("Update - Response: ${response.body}"); _clearError(); widget.fetchGetCostCenter(); - Navigator.of(context).pop(); + if (context.mounted) { + Navigator.of(context).pop(); // Close modal only if mounted + } + setState(() { + isDisable = false; + }); break; case 201: print("Save - Response: ${response.body}"); _clearError(); await widget.fetchGetCostCenter(); - Navigator.of(context).pop(); + if (context.mounted) { + Navigator.of(context).pop(); // Close modal only if mounted + } + setState(() { + isDisable = false; + }); break; default: print("Failed to submit costcenter. Status: ${response.statusCode}"); print("Error: ${response.body}"); + setState(() { + isDisable = false; + }); } } catch (e) { print(" Error submitting plan: $e"); + setState(() { + isDisable = false; + }); } } @@ -406,11 +426,27 @@ class CostCenterDataState extends State { // ), SizedBox( child: ElevatedButton( - onPressed: () { - handleSubmit(); - // You can get text from commentController.text - // Navigator.of(context).pop(); // Close the modal - }, + onPressed: isDisable + ? null + : () async { + setState(() { + isDisable = true; + }); + + await handleSubmit(); + // You can get text from commentController.text + // Navigator.of(context).pop(); // Close the modal + + // Optional: re-enable only on error + // setState(() { + // isDisable = false; + // }); + }, + // onPressed: () { + // handleSubmit(); + // // You can get text from commentController.text + // // Navigator.of(context).pop(); // Close the modal + // }, style: ElevatedButton.styleFrom( backgroundColor: widget.layoutColor, shape: RoundedRectangleBorder( diff --git a/lib/Screens/costCenter/costCenter_list.dart b/lib/Screens/costCenter/costCenter_list.dart index a428070..6450262 100644 --- a/lib/Screens/costCenter/costCenter_list.dart +++ b/lib/Screens/costCenter/costCenter_list.dart @@ -132,6 +132,7 @@ class CostCenterListState extends State { } Future> fetchGetCostCenter() async { + // return []; final String apiUrlData = '$apiUrl/api/getCostCenterMaster?for=table_view'; final String? token = await getToken(); @@ -285,7 +286,7 @@ class CostCenterListState extends State { isDesktop: isDesktop, breadcrumbItems: [ BreadcrumbItem( - title: 'Organization Settings', + title: 'Org Settings', tooltip: 'Go To Organization Settings', onTap: (context) { context.go("/OrganizationSettings"); @@ -455,6 +456,7 @@ class CostCenterListState extends State { FutureBuilder>( future: futureCostCenter, builder: (context, snapshot) { + final adjHgt = MediaQuery.of(context).size.height; if (futureCostCenter == null) { return CircularProgressIndicator(); } @@ -480,9 +482,9 @@ class CostCenterListState extends State { // fontWeight: FontWeight.bold, // color: Colors.redAccent), // ), - const SizedBox(height: 8), + SizedBox(height: adjHgt / 4), Text( - "No CostCenter Available ", + "No Data Found ", textAlign: TextAlign.center, style: GoogleFonts.poppins( fontSize: 20, @@ -491,15 +493,15 @@ class CostCenterListState extends State { ), ), const SizedBox(height: 20), - Text( - "Please Create CostCenter Details", - textAlign: TextAlign.center, - style: GoogleFonts.poppins( - fontSize: 16, - color: Colors.grey, - ), - ), - const SizedBox(height: 20), + // Text( + // "Please Create CostCenter Details", + // textAlign: TextAlign.center, + // style: GoogleFonts.poppins( + // fontSize: 16, + // color: Colors.grey, + // ), + // ), + // const SizedBox(height: 20), ], ), ), @@ -906,7 +908,7 @@ class CostCenterListState extends State { filteredCostCenter.isEmpty ? Center( child: Text( - "No matches found", + "No Matches Found", style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey, @@ -921,7 +923,7 @@ class CostCenterListState extends State { filteredCostCenter.isEmpty ? Center( child: Text( - "No matches found", + "No Matches Found", style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey, diff --git a/lib/Screens/dashboard/status_dashboard (copy).dart b/lib/Screens/dashboard/status_dashboard (copy).dart new file mode 100644 index 0000000..9cbe615 --- /dev/null +++ b/lib/Screens/dashboard/status_dashboard (copy).dart @@ -0,0 +1,423 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:html' as html; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:frontend/routes/custom_router.dart'; +import 'package:go_router/go_router.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:http/http.dart' as http; +import 'package:responsive_builder/responsive_builder.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../config/apiUrl.dart'; +import '../../routes/custom_appBar.dart'; +import '../../routes/custom_drawer.dart'; +import '../../services/apiService.dart'; +import '../../utils/auth_utils.dart'; +import '../../utils/pagination.dart'; + +class StatusDashboard extends StatefulWidget { + const StatusDashboard({super.key}); + + @override + StatusDashboardState createState() => StatusDashboardState(); +} + +class StatusDashboardState extends State { + Map? apiData; + String? organizationId; + late bool _dialogShown = false; + late StreamSubscription _popStateListener; + + @override + void initState() { + super.initState(); + _checkAuthAndLoadData(); + + checkbackbutton(); + + // loadDashboardData(); + } + + void checkbackbutton() async { + // Push a dummy state so back button triggers popstate instead of navigating + html.window.history.pushState(null, 'home', html.window.location.href); + + _popStateListener = html.window.onPopState.listen((event) { + if (!_dialogShown && mounted) { + _showBackConfirmationDialog(); + } + + // Re-push to prevent leaving + html.window.history.pushState(null, 'home', html.window.location.href); + }); + } + + @override + void dispose() { + _popStateListener.cancel(); // ✅ Remove the browser popstate listener + super.dispose(); + } + + void _showBackConfirmationDialog() { + if (!mounted) return; + + _dialogShown = true; + + showDialog( + context: context, + builder: + (context) => AlertDialog( + title: Text("Confirm"), + content: Text("Do you want to logout?"), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); // Close dialog + _dialogShown = false; + }, + child: Text("Cancel"), + ), + TextButton( + onPressed: () async { + Navigator.pop(context); // Close dialog + _dialogShown = false; + await _logoutAndRedirect(context); + }, + child: Text("Logout"), + ), + ], + ), + ); + } + + Future _logoutAndRedirect(BuildContext context) async { + print("logue 0"); + + // Example: clear session or shared preferences + final prefs = await SharedPreferences.getInstance(); + await prefs.clear(); + + context.go("/"); + + print("logue 1"); + } + + void _checkAuthAndLoadData() async { + final String? token = await getToken(); // Your async function to get token + + if (token == null || token.isEmpty) { + // Token doesn't exist → redirect to login + context.go( + "/", + ); // or use: router.go("/") if you're using `GoRouter` directly + return; + } + + WidgetsBinding.instance.addPostFrameCallback((_) { + checkbackbutton(); + }); + // If token exists, load the dashboard data + loadDashboardData(); + } + + Future loadDashboardData() async { + try { + final data = await fetchStatusDashboard(); + setState(() { + apiData = data; + }); + } catch (e) { + print("Error loading dashboard: $e"); + } + } + + Future getToken() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString('auth_token'); + } + + 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> fetchStatusDashboard() async { + organizationId = await getOrgId(); + + final String apiUrlData = + '$apiUrl/api/plans/statusDashboard?org_id=$organizationId'; + final String? token = await getToken(); + + print("Fetch StatusDashboard -- 2KN Here : $token"); + + 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', + }, + ); + print("called api : $apiUrlData"); + if (response.statusCode == 200) { + final apiData = json.decode(response.body); + print("Fetch StatusDashboard -- Reponse Here : $apiData"); + return apiData; // Returning raw JSON list + } else { + throw Exception('Failed to load users'); + } + } + + Color getStatusColor(String status) { + if (status == "Domestic") return Colors.white; + if (status == "International") return Colors.white; + if (status == "Partially Approved") return Colors.yellow.shade100; + if (status == "Pending Approval") return Colors.greenAccent.shade100; + if (status == "Approved") return Colors.green.shade100; + if (status == "Completed") return Colors.green.shade500; + if (status == "Rejected") return Colors.red.shade100; + if (status == "Cancelled") return Colors.red.shade200; + return Colors.grey.shade100; + } + + IconData getStatusIcon(String title) { + switch (title) { + case "Domestic": + return Icons.directions_bus_filled_outlined; + case "International": + return Icons.airplanemode_active; + case "Partially Approved": + return Icons.pending; + case "Approved": + return Icons.check_circle_outline; + case "Completed": + return Icons.done_all; + case "Rejected": + return Icons.cancel_outlined; + case "Cancelled": + return Icons.cancel_schedule_send_outlined; + + default: + return Icons.info_outline; + } + } + + @override + Widget build(BuildContext context) { + final typeBasedCount = List>.from( + (apiData?['data']?['typeBasedCount']) ?? [], + ); + + print("apiData - => $apiData"); + print("typeBasedCount => $typeBasedCount"); + + final statusBasedCount = List>.from( + (apiData?['data']?['statusBasedCount']) ?? [], + ); + print("statusBasedCount - => $statusBasedCount"); + // 👇 Local function to create the card widget + Widget buildInfoCard(String title, int count, double width) { + print('title $title'); + final color = getStatusColor(title); + final icon = getStatusIcon(title); + return Card( + elevation: 3, + color: color, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Container( + width: width, + padding: const EdgeInsets.all(12), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + icon, + size: 32, + color: + (title == 'Domestic' || title == 'International') + ? Colors.green + : Colors.black54, + ), // 👈 Add Icon here + const SizedBox(height: 8), + Text( + title, + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: + (title == 'Domestic' || title == 'International') + ? 16 + : 14, + ), + ), + const SizedBox(height: 8), + Text( + count.toString(), + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + // color: Colors.blue, + ), + ), + ], + ), + ), + ); + } + + void _handleBackButton() { + final location = GoRouterState.of(context).uri.toString(); + + print("location - $location"); + if (location.contains('/StatusDashboard')) { + // Do nothing or show "Press again to exit" toast + print("Blocked back on dashboard"); + } else { + print("dashboard .."); + } + } + + return ResponsiveBuilder( + builder: (context, sizingInfo) { + bool isDesktop = + sizingInfo.deviceScreenType == DeviceScreenType.desktop; + + return PopScope( + canPop: false, // Allow back navigation only if not login screen + onPopInvokedWithResult: (didPop, result) { + if (didPop) return; + _handleBackButton(); // Show exit confirmation dialog + }, + child: Scaffold( + backgroundColor: const Color(0xFFf5f5f5), + appBar: CustomAppBar(isDesktop: isDesktop), + drawer: CustomDrawer(isDesktop: false), + body: Padding( + padding: + isDesktop + ? EdgeInsets.symmetric( + horizontal: + MediaQuery.of(context).size.width * + 0.1, // 30% of screen width as horizontal padding + vertical: 10, // 5% of screen height as vertical padding + ) + : EdgeInsets.all(0), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + // Only needed if child layout depends on height + child: Container( + decoration: BoxDecoration( + color: + isDesktop + ? Colors.white + : const Color(0xFFFCFCFC), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(30.0), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + Wrap( + spacing: 10, + runSpacing: 10, + children: + typeBasedCount.map((item) { + double cardWidth = + isDesktop + ? (MediaQuery.of( + context, + ).size.width * + 0.75 - + 10) / + 2 // 80% width padding adjusted + : MediaQuery.of( + context, + ).size.width - + 24; // full width with padding + + return SizedBox( + width: cardWidth, + child: buildInfoCard( + item['value'], + item['count'], + cardWidth, + ), + ); + }).toList(), + ), + const SizedBox(height: 20), + Wrap( + spacing: 10, + runSpacing: 10, + children: + statusBasedCount.map((item) { + double cardWidth = + isDesktop + ? (MediaQuery.of( + context, + ).size.width * + 0.90 - + 50) / + 6 // desktop layout: 6 cards per row + : MediaQuery.of( + context, + ).size.width - + 24; // mobile: full width + + return SizedBox( + width: cardWidth, + child: buildInfoCard( + item['value'], + item['count'], + cardWidth, + ), + ); + }).toList(), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ); + }, + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/Screens/dashboard/status_dashboard.dart b/lib/Screens/dashboard/status_dashboard.dart index 9cbe615..93ea009 100644 --- a/lib/Screens/dashboard/status_dashboard.dart +++ b/lib/Screens/dashboard/status_dashboard.dart @@ -31,6 +31,34 @@ class StatusDashboardState extends State { String? organizationId; late bool _dialogShown = false; late StreamSubscription _popStateListener; + String selectedView = 'Today'; + String AccessKeyFlag = "Both"; + Map? currentData; + List typeData = []; + List statusData = []; + List flightData = []; + List accommodationData = []; + List forexData = []; + + final categoryIcons = { + "flight": Icons.flight, + "accommodation": Icons.hotel_outlined, + "forex": Icons.attach_money, + }; + + List statusLabels = [ + "Pending Approval", + "Partially Approved", + "Approved", + "Rejected", + "Cancelled" + ]; + + // Extract counts for "Both" section + List getStatusValues(Map serviceData) { + final both = serviceData['Both'] ?? {}; + return statusLabels.map((label) => (both[label] ?? 0) as int).toList(); + } @override void initState() { @@ -231,7 +259,82 @@ class StatusDashboardState extends State { final statusBasedCount = List>.from( (apiData?['data']?['statusBasedCount']) ?? [], ); + + final statusBasedTodayCount = List>.from( + (apiData?['data']?['statusBasedTodayCount']) ?? [], + ); + + final statusBasedWeeklyCount = List>.from( + (apiData?['data']?['statusBasedWeeklyCount']) ?? [], + ); + + final typeBasedTodayCount = List>.from( + (apiData?['data']?['typeBasedTodayCount']) ?? [], + ); + + final typeBasedWeeklyCount = List>.from( + (apiData?['data']?['typeBasedWeeklyCount']) ?? [], + ); + + final typeTodayBasedCount = List>.from( + (apiData?['data']?['typeTodayBasedCount']) ?? [], + ); + + final typeWeeklyBasedCount = List>.from( + (apiData?['data']?['typeWeeklyBasedCount']) ?? [], + ); + + final statusTodayBasedCount = List>.from( + (apiData?['data']?['statusTodayBasedCount']) ?? [], + ); + + final statusWeeklyBasedCount = List>.from( + (apiData?['data']?['statusWeeklyBasedCount']) ?? [], + ); + + final List todayList = List>.from( + apiData?['data']?['todayTripCounts'] ?? [], + ); + + final List weekList = List>.from( + apiData?['data']?['weekTripCounts'] ?? [], + ); + + // Merge all maps in the list into one map + Map todayBasedCount = {}; + Map weekBasedCount = {}; + + for (final item in todayList) { + todayBasedCount.addAll(item); + } + + for (final item in weekList) { + weekBasedCount.addAll(item); + } + + setState(() { + // currentData = selectedView == "Today" ? todayBasedCount : weekBasedCount; + typeData = selectedView == "Today" + ? typeBasedTodayCount + : typeBasedWeeklyCount ; + statusData = selectedView == "Today" + ? statusBasedTodayCount + : statusBasedWeeklyCount ; + + flightData = selectedView == "Today" + ? getStatusValues(todayBasedCount['flight'] ?? {}) + : getStatusValues(weekBasedCount['flight'] ?? {}); + accommodationData = selectedView == "Today" + ? getStatusValues(todayBasedCount['acomodation'] ?? {}) + : getStatusValues(weekBasedCount['acomodation'] ?? {}); + forexData = selectedView == "Today" + ? getStatusValues(todayBasedCount['forex'] ?? {}) + : getStatusValues(weekBasedCount['forex'] ?? {}); + }); + + print("statusBasedCount - => $statusBasedCount"); + print("Current Data - => $currentData"); // 👇 Local function to create the card widget Widget buildInfoCard(String title, int count, double width) { print('title $title'); @@ -265,6 +368,7 @@ class StatusDashboardState extends State { (title == 'Domestic' || title == 'International') ? 16 : 14, + color: Colors.black87, ), ), const SizedBox(height: 8), @@ -293,6 +397,317 @@ class StatusDashboardState extends State { print("dashboard .."); } } + String capitalize(String s) => s[0].toUpperCase() + s.substring(1); + + Widget toggleChip(String label) { + bool isSelected = selectedView == label; + + return GestureDetector( + onTap: () { + setState(() { + selectedView = label; + }); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20), + decoration: BoxDecoration( + color: isSelected ? const Color(0xFF003A78) : Colors.transparent, + borderRadius: BorderRadius.circular(20), + ), + alignment: Alignment.center, + height: 36, + child: Text( + label, + style: GoogleFonts.poppins( + fontSize: 10, + color: isSelected ? Colors.white : Colors.black, + fontWeight: FontWeight.w700, + ), + ), + ), + ); + } + + Widget buildTopCard({ + // required IconData icon, + required Color color, + required String label, + required int count, + required Color bgColor, + }) { + return Container( + padding: const EdgeInsets.all(1), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(1), + ), + child: Column( + children: [ + Tooltip( + message: label , + child: Image.asset( + label == 'Domestic' + ? 'assets/images/IconsImg/Domestic_new.png' + : 'assets/images/IconsImg/International_new.png', + // width: 65, + // height: label == 'Domestic' ? 37 : 40, + ), + ), + // Icon(icon, color: color, size: 40), + const SizedBox(height: 10), + Text( + count.toString(), + style: GoogleFonts.poppins( + fontSize: 28, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 5), + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: bgColor, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + label, + style: GoogleFonts.poppins( + fontSize: 10, + color: color, + // color: const Color(0xFFEAF3FB), + fontWeight: FontWeight.w700, + ), + ), + ), + ], + ), + ); + } + + Widget statusCard(String label, int count) { + return Column( + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15), + decoration: BoxDecoration( + color: const Color(0xFFEAF3FB), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + Text( + count.toString(), + style: GoogleFonts.poppins( + fontSize: 18, + color: Colors.black87, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 5), + Text( + label, + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black87, + ), + ), + ], + ), + ), + ], + ); + } + + Widget buildCategoryCard(String title, IconData icon, List statusLabels,List values) { + + final statusIcons = [ + Icons.calendar_today, + Icons.assignment, + Icons.verified, + Icons.block, + Icons.cancel, + ]; + + final statusColors = [ + Color(0xFFFBFFCA), + Color(0xFFFFF1CD),// Colors.yellow.shade100, // Color(0xFFCAE77B), + Color(0xFFDAFFE8),// Colors.green.shade100, // Color(0xFF72D480), + Color(0xFFFFD6D3),// Colors.red.shade100, // Color(0xFFF88C8C), + Color(0xFFFFA8A8), // Colors.red.shade200, // Color(0xFFE94B4B), + ]; + + final statusIconColors = [ + Color(0xFFB1BC1A), + Color(0xFFCC9300), + Color(0xFF509269), + Color(0xFFD25E55), + Color(0xFFAE0303), + ]; + + return Container( + padding: const EdgeInsets.all(16), + // decoration: BoxDecoration( + // color: Colors.white, + // borderRadius: BorderRadius.circular(12), + // ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.blueGrey, + blurRadius: 5, + offset: Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: const BoxDecoration( + // color: Colors.blue, + color: Color(0xFF004A8E), + shape: BoxShape.circle, + ), + child: Icon( + icon, + color: Colors.white, + size: 20, + ), + ), + const SizedBox(width: 10), + Text(title, style: GoogleFonts.poppins( + color: Colors.black87, + fontWeight: FontWeight.bold + ),), + ], + ), + const SizedBox(height: 12), + ...List.generate(values.length, (index) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + // Icon(statusIcons[index], size: 15, color: statusIconColors[index]), + Container( + width: 28, + height: 28, + decoration: BoxDecoration( + color: statusColors[index], // background color + borderRadius: BorderRadius.circular(4), // square with slight rounding + ), + alignment: Alignment.center, + child: Icon( + statusIcons[index], + size: 15, + color: statusIconColors[index], // icon color + ), + ), + const SizedBox(width: 6), + Text(statusLabels[index], style: GoogleFonts.poppins( + color: Colors.black87, + ),), + ], + ), + Text(values[index].toString(), style: GoogleFonts.poppins( + fontSize: 10, + color: Colors.black87, + fontWeight: FontWeight.bold, + ),), + ], + ), + ); + }), + ], + ), + ); + } + + // Widget buildCategoryCard(String title, IconData icon,statusLabels,statusvalue) { + // + // + // return Container( + // padding: const EdgeInsets.all(16), + // // decoration: BoxDecoration( + // // color: Colors.white, + // // borderRadius: BorderRadius.circular(12), + // // ), + // + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Row( + // children: [ + // Container( + // padding: const EdgeInsets.all(8), + // decoration: const BoxDecoration( + // color: Colors.blue, + // shape: BoxShape.circle, + // ), + // child: Icon( + // icon, + // color: Colors.white, + // size: 20, + // ), + // ), + // const SizedBox(width: 10), + // Text(title, style: GoogleFonts.poppins( + // color: Colors.black87, + // fontWeight: FontWeight.bold + // ),), + // ], + // ), + // const SizedBox(height: 12), + // ...List.generate(statusvalue.length, (index) { + // return Padding( + // padding: const EdgeInsets.symmetric(vertical: 10), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Row( + // children: [ + // // Icon(statusIcons[index], size: 20, color: statusColors[index]), + // Container( + // padding: const EdgeInsets.all(8), + // decoration: BoxDecoration( + // color: statusColors[index], // Background color behind the icon + // borderRadius: BorderRadius.circular(10), // Rounded square + // boxShadow: [ + // BoxShadow( + // color: Colors.grey, // Shadow color + // spreadRadius: 1, + // blurRadius: 5, + // offset: const Offset(0, 2), // Shadow position + // ), + // ], + // ), + // child: Icon( + // statusIcons[index], + // color: statusIconColors[index], + // size: 20, + // // color: statusColors[index], + // ), + // ), + // const SizedBox(width: 6), + // Text(statusLabels[index]), + // ], + // ), + // + // ], + // ), + // ); + // }), + // ], + // ), + // ); + // } return ResponsiveBuilder( builder: (context, sizingInfo) { @@ -322,94 +737,137 @@ class StatusDashboardState extends State { child: LayoutBuilder( builder: (context, constraints) { return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - // Only needed if child layout depends on height - child: Container( - decoration: BoxDecoration( - color: - isDesktop - ? Colors.white - : const Color(0xFFFCFCFC), - borderRadius: BorderRadius.circular(12), - ), - child: Row( - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.all(30.0), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.center, + // child: ConstrainedBox( + // constraints: BoxConstraints( + // minHeight: constraints.maxHeight, + // ), + // ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // First Row: Toggle Buttons Row + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + height: 36, + decoration: BoxDecoration( + color: const Color(0xFFEAEAEA), + borderRadius: BorderRadius.circular(20), + ), + child: Row( + children: [ + toggleChip("Today"), + toggleChip("This Week"), + ], + ), + ), + ], + ), + const SizedBox(height: 20), + + // Second Row: Domestic & International and Status + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // LEFT SIDE + Expanded( + flex: 2, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.blueGrey, + blurRadius: 5, + offset: Offset(0, 2), + ), + ], + ), + padding: const EdgeInsets.all(15), + child: Row( children: [ - Wrap( - spacing: 10, - runSpacing: 10, - children: - typeBasedCount.map((item) { - double cardWidth = - isDesktop - ? (MediaQuery.of( - context, - ).size.width * - 0.75 - - 10) / - 2 // 80% width padding adjusted - : MediaQuery.of( - context, - ).size.width - - 24; // full width with padding - - return SizedBox( - width: cardWidth, - child: buildInfoCard( - item['value'], - item['count'], - cardWidth, - ), - ); - }).toList(), - ), - const SizedBox(height: 20), - Wrap( - spacing: 10, - runSpacing: 10, - children: - statusBasedCount.map((item) { - double cardWidth = - isDesktop - ? (MediaQuery.of( - context, - ).size.width * - 0.90 - - 50) / - 6 // desktop layout: 6 cards per row - : MediaQuery.of( - context, - ).size.width - - 24; // mobile: full width - - return SizedBox( - width: cardWidth, - child: buildInfoCard( - item['value'], - item['count'], - cardWidth, - ), - ); - }).toList(), - ), + ...typeData.map((item) { + return Expanded( + child: Column( + children: [ + buildTopCard( + // icon: item['value'] == "Domestic" + // ? Icons.home + // : Icons.travel_explore, + color: item['value'] == "Domestic" + ? Color(0xFF0DB04B) + : Color(0xFF004A8E), + label: item['value'], + count: item['count'], + bgColor: item['value'] == "Domestic" + ? const Color(0xFFD6FBE4) + : const Color(0xFFD9E8FF), + ), + ], + ), + ); + }).toList(), ], ), + ), + ), + const SizedBox(width: 20), + // RIGHT SIDE + Expanded( + flex: 3, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.blueGrey, + blurRadius: 5, + offset: Offset(0, 2), + ), + ], + ), + padding: const EdgeInsets.all(15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Status", + style: GoogleFonts.poppins( + fontSize: 19, + color: Colors.black87, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 23), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ...statusData.map((item) => statusCard(item['value'], item['count'])).toList(), + ], + ), + ], ), ), - ], - ), + ), + ], ), - ), + const SizedBox(height: 20), + + // Third Row: Flight, Accommodation, Forex + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: buildCategoryCard("Flight", Icons.flight,statusLabels,flightData)), + const SizedBox(width: 10), + Expanded(child: buildCategoryCard("Accomodation", Icons.hotel_outlined,statusLabels,accommodationData)), + const SizedBox(width: 10), + Expanded(child: buildCategoryCard("Forex", Icons.attach_money,statusLabels,forexData)), + ], + ), + ], ), ); }, diff --git a/lib/Screens/department/departmentDetails.dart b/lib/Screens/department/departmentDetails.dart index dbb9e7a..0f69ca3 100644 --- a/lib/Screens/department/departmentDetails.dart +++ b/lib/Screens/department/departmentDetails.dart @@ -50,6 +50,7 @@ class DepartmentDataState extends State { String? userId; int? departmentDataId; late String isActive = "1"; + bool isDisable = false; List dataHeader = ["dropdown_value", "description"]; @@ -163,9 +164,12 @@ class DepartmentDataState extends State { userId = await getUserId(); setState(() { + isDisable = true; // This triggers UI rebuild with error messages if (validateData()) { postDepartmentData(); + }else{ + isDisable = false; } }); @@ -222,11 +226,20 @@ class DepartmentDataState extends State { _clearError(); widget.fetchGetDepartment(); // dispose(); - Navigator.of(context).pop(); + if (context.mounted) { + Navigator.of(context).pop(); // Close modal only if mounted + } + setState(() { + isDisable = false; + }); } else if (response.statusCode == 404) { - Navigator.of(context).pop(); + if (context.mounted) { + Navigator.of(context).pop(); + } final message = jsonDecode(response.body)['message'] ?? 'Unknown error'; - + setState(() { + isDisable = false; + }); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), @@ -237,9 +250,15 @@ class DepartmentDataState extends State { } else { print("Failed to submit. Status: ${response.statusCode}"); print("Error: ${response.body}"); + setState(() { + isDisable = false; + }); } } catch (e) { print(" Error submitting plan: $e"); + setState(() { + isDisable = false; + }); } } @@ -409,10 +428,21 @@ class DepartmentDataState extends State { // ), SizedBox( child: ElevatedButton( - onPressed: () { - handleSubmit(); + onPressed: isDisable + ? null + : () async { + setState(() { + isDisable = true; + }); + + await handleSubmit(); // You can get text from commentController.text // Navigator.of(context).pop(); // Close the modal + + // Optional: re-enable only on error + // setState(() { + // isDisable = false; + // }); }, style: ElevatedButton.styleFrom( backgroundColor: widget.layoutColor, diff --git a/lib/Screens/department/department_list.dart b/lib/Screens/department/department_list.dart index bf97295..215defd 100644 --- a/lib/Screens/department/department_list.dart +++ b/lib/Screens/department/department_list.dart @@ -132,6 +132,7 @@ class DepartmentListState extends State { } Future> fetchGetDepartment() async { + // return []; final String apiUrlData = '$apiUrl/api/getDepartmentList?for=table_view'; final String? token = await getToken(); @@ -284,7 +285,7 @@ class DepartmentListState extends State { isDesktop: isDesktop, breadcrumbItems: [ BreadcrumbItem( - title: 'Organization Settings', + title: 'Org Settings', tooltip: 'Go To Organization Settings', onTap: (context) { context.go("/OrganizationSettings"); @@ -454,6 +455,7 @@ class DepartmentListState extends State { FutureBuilder>( future: futureDepartment, builder: (context, snapshot) { + final adjHgt = MediaQuery.of(context).size.height; if (futureDepartment == null) { CircularProgressIndicator(); } @@ -479,9 +481,9 @@ class DepartmentListState extends State { // fontWeight: FontWeight.bold, // color: Colors.redAccent), // ), - const SizedBox(height: 8), + SizedBox(height: adjHgt / 4), Text( - "No Department Available ", + "No Data Found ", textAlign: TextAlign.center, style: GoogleFonts.poppins( fontSize: 20, @@ -490,15 +492,15 @@ class DepartmentListState extends State { ), ), const SizedBox(height: 20), - Text( - "Please Create Department Details", - textAlign: TextAlign.center, - style: GoogleFonts.poppins( - fontSize: 16, - color: Colors.grey, - ), - ), - const SizedBox(height: 20), + // Text( + // "Please Create Department Details", + // textAlign: TextAlign.center, + // style: GoogleFonts.poppins( + // fontSize: 16, + // color: Colors.grey, + // ), + // ), + // const SizedBox(height: 20), ], ), ), @@ -905,7 +907,7 @@ class DepartmentListState extends State { filteredDepartment.isEmpty ? Center( child: Text( - "No matches found", + "No Matches Found", style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey, @@ -920,7 +922,7 @@ class DepartmentListState extends State { filteredDepartment.isEmpty ? Center( child: Text( - "No matches found", + "No Matches Found", style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey, diff --git a/lib/Screens/group/groupDetails.dart b/lib/Screens/group/groupDetails.dart index 215c08a..0107aaf 100644 --- a/lib/Screens/group/groupDetails.dart +++ b/lib/Screens/group/groupDetails.dart @@ -66,6 +66,7 @@ class GroupDataState extends State { int? groupDataId; late String isActive = "1"; + bool isDisable = false; List dataHeader = [ "name", @@ -227,9 +228,12 @@ class GroupDataState extends State { userId = await getUserId(); setState(() { + isDisable = true; // This triggers UI rebuild with error messages if (validateData()) { postGroupData(); + }else{ + isDisable = false; } }); @@ -280,11 +284,21 @@ class GroupDataState extends State { // _clearError(); _clearError(); await widget.fetchGetGroup(); + if (context.mounted) { + Navigator.of(context).pop(); // Close modal only if mounted + } + setState(() { + isDisable = false; + }); // dispose(); - Navigator.of(context).pop(); } else if (response.statusCode == 404) { - Navigator.of(context).pop(); + if (context.mounted) { + Navigator.of(context).pop(); + } + setState(() { + isDisable = false; + }); final message = jsonDecode(response.body)['message'] ?? 'Unknown error'; ScaffoldMessenger.of(context).showSnackBar( @@ -294,12 +308,33 @@ class GroupDataState extends State { behavior: SnackBarBehavior.floating, ), ); - } else { + } + else { print("Failed to submit plan. Status: ${response.statusCode}"); print("Error: ${response.body}"); + final message = jsonDecode(response.body); + final errorMessage = message['messages']?['error'] ?? 'Unknown error occurred'; + + if (errorMessage.contains("Duplicate entry")) { + _clearError(); + await widget.fetchGetGroup(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text("Group Already Exists!"), + backgroundColor: Colors.orange, + behavior: SnackBarBehavior.floating, + ), + ); + } + setState(() { + isDisable = false; + }); } } catch (e) { print(" Error submitting plan: $e"); + setState(() { + isDisable = false; + }); } } @@ -377,7 +412,6 @@ class GroupDataState extends State { CustomTextFieldForexWrapper( // isFocused: false, isFocused: focusStates["nameFocused"] ?? false, - isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( @@ -444,6 +478,7 @@ class GroupDataState extends State { SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["domestic_policy_nameFocused"] ?? false, + padding: const EdgeInsets.symmetric(horizontal: 0), isDesktop: widget.isDesktop, child: SizedBox( height: 40, @@ -498,7 +533,7 @@ class GroupDataState extends State { borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: - (focusStates["country_codeFocused"] ?? false) + (focusStates["domestic_policy_nameFocused"] ?? false) ? widget.layoutColor! : Colors.white, // width: 0.5, @@ -507,7 +542,7 @@ class GroupDataState extends State { enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: - (focusStates["country_codeFocused"] ?? false) + (focusStates["domestic_policy_nameFocused"] ?? false) ? widget.layoutColor! : Colors.white, // : const Color(0xFFD6D5E6), @@ -518,9 +553,8 @@ class GroupDataState extends State { focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: widget.layoutColor!, width: 1), ), - contentPadding: EdgeInsets.symmetric( - horizontal: 8, - ) + contentPadding: EdgeInsets.symmetric(horizontal: 10.0, + vertical: 8.0,), // contentPadding: EdgeInsets.symmetric(horizontal: 1), ), ), @@ -583,7 +617,7 @@ class GroupDataState extends State { CustomTextFieldForexWrapper( isFocused: focusStates["international_policy_nameFocused"] ?? false, - + padding: const EdgeInsets.symmetric(horizontal: 0), isDesktop: widget.isDesktop, child: SizedBox( height: 40, @@ -639,20 +673,20 @@ class GroupDataState extends State { borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: - (focusStates["country_codeFocused"] ?? false) + (focusStates["international_policy_nameFocused"] ?? false) ? widget.layoutColor! : Colors.white, - // width: 0.5, + width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: - (focusStates["country_codeFocused"] ?? false) + (focusStates["international_policy_nameFocused"] ?? false) ? widget.layoutColor! : Colors.white, // : const Color(0xFFD6D5E6), - // width: 0.5, + width: 0.5, // const Color(0xFFD6D5E6), ), ), @@ -794,10 +828,25 @@ class GroupDataState extends State { // ), SizedBox( child: ElevatedButton( - onPressed: () { - handleSubmit(); + // onPressed: () { + // handleSubmit(); + // // You can get text from commentController.text + // // Navigator.of(context).pop(); // Close the modal + // }, + onPressed: isDisable + ? null + : () async { + setState(() { + isDisable = true; + }); + await handleSubmit(); // You can get text from commentController.text // Navigator.of(context).pop(); // Close the modal + + // Optional: re-enable only on error + // setState(() { + // isDisable = false; + // }); }, style: ElevatedButton.styleFrom( backgroundColor: widget.layoutColor, diff --git a/lib/Screens/group/groupList.dart b/lib/Screens/group/groupList.dart index 35be928..f67f303 100644 --- a/lib/Screens/group/groupList.dart +++ b/lib/Screens/group/groupList.dart @@ -117,6 +117,7 @@ class _GroupListState extends State { } Future> fetchGroups() async { + // return []; final result = await apiService.fetchAllGroup(); return result; // Returning raw JSON list } @@ -303,7 +304,7 @@ class _GroupListState extends State { isDesktop: isDesktop, breadcrumbItems: [ BreadcrumbItem( - title: 'Organization Settings', + title: 'Org Settings', tooltip: 'Go To Organization Settings', onTap: (context) { context.go("/OrganizationSettings"); @@ -475,6 +476,7 @@ class _GroupListState extends State { FutureBuilder>( future: futureGroups, builder: (context, snapshot) { + final adjHgt = MediaQuery.of(context).size.height; if (futureGroups == null) { return const CircularProgressIndicator(); } @@ -490,9 +492,9 @@ class _GroupListState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const SizedBox(height: 8), + SizedBox(height: adjHgt / 4), Text( - "No Group Found", + "No Data Found", textAlign: TextAlign.center, style: GoogleFonts.poppins( fontSize: 20, @@ -501,15 +503,15 @@ class _GroupListState extends State { ), ), const SizedBox(height: 20), - Text( - "Please Create Group", - textAlign: TextAlign.center, - style: GoogleFonts.poppins( - fontSize: 16, - color: Colors.grey, - ), - ), - const SizedBox(height: 20), + // Text( + // "Please Create Group", + // textAlign: TextAlign.center, + // style: GoogleFonts.poppins( + // fontSize: 16, + // color: Colors.grey, + // ), + // ), + // const SizedBox(height: 20), ], ), ), @@ -612,12 +614,27 @@ class _GroupListState extends State { return DataRow( cells: [ + // DataCell( + // Text( + // "${group['name'] ?? ''}", + // style: TextStyle( + // fontSize: 13, + // fontFamily: "Inter", + // ), + // ), + // ), DataCell( - Text( - "${group['name'] ?? ''}", - style: TextStyle( - fontSize: 13, - fontFamily: "Inter", + ConstrainedBox( + constraints: BoxConstraints(maxWidth: 200), // limit description width + child: Text( + "${group['name'] ?? ''}", + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, // optional: show only 1 line + softWrap: false, ), ), ), @@ -639,12 +656,27 @@ class _GroupListState extends State { ), ), ), + // DataCell( + // Text( + // "${group['description'] ?? 'N/A'}", + // style: TextStyle( + // fontSize: 13, + // fontFamily: "Inter", + // ), + // ), + // ), DataCell( - Text( - "${group['description'] ?? 'N/A'}", - style: TextStyle( - fontSize: 13, - fontFamily: "Inter", + ConstrainedBox( + constraints: BoxConstraints(maxWidth: 200), // limit description width + child: Text( + "${group['description'] ?? 'N/A'}", + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, ), ), ), @@ -984,7 +1016,7 @@ class _GroupListState extends State { filteredGroups.isEmpty ? Center( child: Text( - "No matches found", + "No Matches Found", style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey, @@ -999,7 +1031,7 @@ class _GroupListState extends State { paginatedGroup.isEmpty ? Center( child: Text( - "No matches found", + "No Matches Found", style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey, diff --git a/lib/Screens/hotels/hotelsDetails.dart b/lib/Screens/hotels/hotelsDetails.dart index feb40b1..e876093 100644 --- a/lib/Screens/hotels/hotelsDetails.dart +++ b/lib/Screens/hotels/hotelsDetails.dart @@ -54,6 +54,7 @@ class HotelsDataState extends State { String? userId; int? hotelsDataId; late String isActive = "1"; + bool isDisable = false; List dataHeader = [ "hotel_name", @@ -196,9 +197,12 @@ class HotelsDataState extends State { userId = await getUserId(); setState(() { + isDisable = true; // This triggers UI rebuild with error messages if (validateData()) { postHotelsData(); + }else{ + isDisable = false; } }); @@ -250,20 +254,24 @@ class HotelsDataState extends State { hotelsDataId != null ? await http.put(uri, headers: headers, body: body) : await http.post(uri, headers: headers, body: body); - if (response.statusCode == 200 || response.statusCode == 201) { print("Hotels Details Created successfully!"); print("Response: ${response.body}"); - // _clearError(); _clearError(); await widget.fetchGetHotels(); - // dispose(); - Navigator.of(context).pop(); + if (context.mounted) { + Navigator.of(context).pop(); // Close modal only if mounted + } + setState(() { + isDisable = false; + }); + // Do NOT re-enable here if success } else if (response.statusCode == 404) { - Navigator.of(context).pop(); + if (context.mounted) { + Navigator.of(context).pop(); + } final message = jsonDecode(response.body)['message'] ?? 'Unknown error'; - ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), @@ -271,13 +279,26 @@ class HotelsDataState extends State { behavior: SnackBarBehavior.floating, ), ); + setState(() { + isDisable = false; + }); + } else { print("Failed to submit plan. Status: ${response.statusCode}"); print("Error: ${response.body}"); + + setState(() { + isDisable = false; + }); } - } catch (e) { - print(" Error submitting plan: $e"); } + catch (e) { + print("Error submitting plan: $e"); + setState(() { + isDisable = false; + }); + } + } @override @@ -691,10 +712,21 @@ class HotelsDataState extends State { // ), SizedBox( child: ElevatedButton( - onPressed: () { - handleSubmit(); + onPressed: isDisable + ? null + : () async { + setState(() { + isDisable = true; + }); + + await handleSubmit(); // You can get text from commentController.text // Navigator.of(context).pop(); // Close the modal + + // Optional: re-enable only on error + // setState(() { + // isDisable = false; + // }); }, style: ElevatedButton.styleFrom( backgroundColor: widget.layoutColor, diff --git a/lib/Screens/hotels/hotels_list.dart b/lib/Screens/hotels/hotels_list.dart index 0838d5f..658e62d 100644 --- a/lib/Screens/hotels/hotels_list.dart +++ b/lib/Screens/hotels/hotels_list.dart @@ -135,6 +135,7 @@ class HotelsDataListState extends State { } Future> fetchGetHotels() async { + // return []; orgId = await getOrgId(); final String apiUrlData = '$apiUrl/api/getHotels?for=table_view'; @@ -335,7 +336,7 @@ class HotelsDataListState extends State { isDesktop: isDesktop, breadcrumbItems: [ BreadcrumbItem( - title: 'Organization Settings', + title: 'Org Settings', tooltip: 'Go To Organization Settings', onTap: (context) { context.go("/OrganizationSettings"); @@ -503,6 +504,7 @@ class HotelsDataListState extends State { FutureBuilder>( future: futureHotels, builder: (context, snapshot) { + final adjHgt = MediaQuery.of(context).size.height; if (futureHotels == null) { return CircularProgressIndicator(); } @@ -528,9 +530,9 @@ class HotelsDataListState extends State { // fontWeight: FontWeight.bold, // color: Colors.redAccent), // ), - const SizedBox(height: 8), + SizedBox(height: adjHgt / 4), Text( - "No Hotels Available ", + "No Data Found ", textAlign: TextAlign.center, style: GoogleFonts.poppins( fontSize: 20, @@ -539,15 +541,15 @@ class HotelsDataListState extends State { ), ), const SizedBox(height: 20), - Text( - "Please Create Hotels", - textAlign: TextAlign.center, - style: GoogleFonts.poppins( - fontSize: 16, - color: Colors.grey, - ), - ), - const SizedBox(height: 20), + // Text( + // "Please Create Hotels", + // textAlign: TextAlign.center, + // style: GoogleFonts.poppins( + // fontSize: 16, + // color: Colors.grey, + // ), + // ), + // const SizedBox(height: 20), ], ), ), @@ -922,7 +924,7 @@ class HotelsDataListState extends State { filteredHotels.isEmpty ? Center( child: Text( - "No matches found", + "No Matches Found", style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey, @@ -937,7 +939,7 @@ class HotelsDataListState extends State { filteredHotels.isEmpty ? Center( child: Text( - "No matches found", + "No Matches Found", style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey, diff --git a/lib/Screens/itnerary/accomodations.dart b/lib/Screens/itnerary/accomodations.dart index b628dcf..e4560ca 100644 --- a/lib/Screens/itnerary/accomodations.dart +++ b/lib/Screens/itnerary/accomodations.dart @@ -5,6 +5,7 @@ import 'package:intl/intl.dart'; import 'package:responsive_builder/responsive_builder.dart'; import '../../services/apiService.dart'; +import '../../utils/auth_utils.dart'; import '../../widgets/custom_confirmation_dialog.dart'; import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_text_itnerary_sub.dart'; @@ -76,6 +77,8 @@ class _AccomodationScreenState extends State { bool _checkOutTimeFocus = false; bool _commentsFocus = false; + Color layoutColor = Colors.grey; + void _addFocusListener(FocusNode node, Function(bool) onFocusChange) { node.addListener(() { setState(() { @@ -100,6 +103,9 @@ class _AccomodationScreenState extends State { "created_by": widget.loginUser, "updated_by": widget.loginUser, }; + print(":("); + print(_checkInController.text); + print(_checkInTimeController.text); if (widget.selectedItem != null) { if (widget.selectedItem?["indx"] != null && @@ -120,7 +126,9 @@ class _AccomodationScreenState extends State { @override void initState() { super.initState(); - + WidgetsBinding.instance.addPostFrameCallback((_) { + loadInitialData(); + }); _addFocusListener( _destinationFocusNode, (focus) => _destinationFocused = focus, @@ -129,6 +137,7 @@ class _AccomodationScreenState extends State { _hotelNameFocusNode, (focus) => _isHotelNameFocused = focus, ); + _addFocusListener( _CategoryFocusNode, (focus) => _CategoryFocused = focus); _addFocusListener(_checkInFocusNode, (focus) => _checkInFocus = focus); _addFocusListener( _checkInTimeFocusNode, @@ -160,6 +169,14 @@ class _AccomodationScreenState extends State { flightLastTripDateNotifier = ValueNotifier(null); flightFirstToDestinationNotifier = ValueNotifier(null); + // // ✅ Check and set default times if empty + // if (_checkInTimeController.text.isEmpty) { + // _checkInTimeController.text = '14:00'; // 2 PM + // } + // if (_checkOutTimeController.text.isEmpty) { + // _checkOutTimeController.text = '12:00'; // 12 PM + // } + WidgetsBinding.instance.addPostFrameCallback((_) { loadCountryList(); @@ -176,12 +193,23 @@ class _AccomodationScreenState extends State { widget.selectedItem?["destination_city"] ?? result['firstToDestination']; + // if (checkinDate != null) { + // flightFirstTripDateNotifier.value = checkinDate; + // } + // + // if (checkoutDate != null) { + // flightLastTripDateNotifier.value = checkoutDate; + // } + + final tripType = result['tripType'] ; + if (checkinDate != null) { flightFirstTripDateNotifier.value = checkinDate; - } - - if (checkoutDate != null) { - flightLastTripDateNotifier.value = checkoutDate; + if (tripType == "Oneway") { + flightLastTripDateNotifier.value = checkinDate; + } else { + flightLastTripDateNotifier.value = checkoutDate ?? checkinDate; + } } if (destinationCity != null) { @@ -199,24 +227,25 @@ class _AccomodationScreenState extends State { // ✅ Only set controller after value is updated // final parsedDate = DateTime.tryParse(flightFirstTripDateNotifier.value ?? ''); - final parsedDate = DateFormat( - "dd-MM-yyyy", - ).parse(flightFirstTripDateNotifier.value ?? ''); - if (parsedDate != null) { - _checkInController.text = DateFormat('dd-MM-yyyy').format(parsedDate); + final checkInDateInput = flightFirstTripDateNotifier.value; + if (checkInDateInput != null && checkInDateInput.isNotEmpty) { + try { + final parsedDate = DateFormat("dd-MM-yyyy").parseStrict(checkInDateInput); + _checkInController.text = DateFormat("dd-MM-yyyy").format(parsedDate); + } catch (e) { + _checkInController.text = ''; + } } // final parsedEndDate = DateTime.tryParse(flightLastTripDateNotifier.value ?? ''); - final parsedEndDate = DateFormat( - "dd-MM-yyyy", - ).parse(flightLastTripDateNotifier.value ?? ''); - - if (parsedEndDate != null) { - // _checkOutController.text = DateFormat('dd-MM-yyyy').format(parsedEndDate); - - _checkOutController.text = DateFormat( - 'dd-MM-yyyy', - ).format(parsedEndDate); + final checkOutDateInput = flightLastTripDateNotifier.value; + if (checkOutDateInput != null && checkOutDateInput.isNotEmpty) { + try { + final parsedDate = DateFormat("dd-MM-yyyy").parseStrict(checkOutDateInput); + _checkOutController.text = DateFormat("dd-MM-yyyy").format(parsedDate); + } catch (e) { + _checkOutController.text = ''; + } } // ✅ Check and set default times if empty @@ -239,6 +268,24 @@ class _AccomodationScreenState extends State { } } + void loadInitialData() async { + String? layoutString = await getLayoutColor(); + + // setState(() { + // layoutColor = + // layoutString != null + // ? Color(int.parse(layoutString)) + // : Colors.redAccent; + // }); + + setState(() { + layoutColor = + layoutString != null + ? Color(int.parse(layoutString)) + : Colors.redAccent; + }); + } + @override void dispose() { _destinationFocusNode.dispose(); @@ -272,8 +319,13 @@ class _AccomodationScreenState extends State { Map getFlightTripDateRange( List> flightData, ) { - print("FlightDAta - ${widget.flightData}"); - + String tt = ""; + if (widget.flightData.isNotEmpty) { + print("Trip Type - ${widget.flightData[0]['trip_type']}"); + tt = widget.flightData[0]['trip_type']; + } else { + print("No flight data available"); + } final allTrips = flightData .expand((flight) => flight['trips'] ?? []) @@ -298,6 +350,7 @@ class _AccomodationScreenState extends State { print("res1 - ${firstTrip['date']},"); print("res11 - $firstTripDate"); + print("res22 - $tt"); // Use `List.where` instead of `firstWhere` so you can handle an empty result @@ -319,6 +372,7 @@ class _AccomodationScreenState extends State { // 'lastTripDate': nextTrip['date'], // 'lastTripDate': lastTrip['date'], 'firstToDestination': firstTripToDestination['to_place'], + 'tripType': tt, // trip_type: Oneway }; } @@ -503,6 +557,8 @@ class _AccomodationScreenState extends State { List _buildFirstRow(isDesktop) { List purposeList = widget.apiDataForClass?['hotel_class'] ?? []; + print("hotel_classhotel_class"); + print(purposeList); List> dropdownItems = purposeList @@ -655,6 +711,20 @@ class _AccomodationScreenState extends State { child: SizedBox( height: 40, width: double.infinity, + child: Focus( + focusNode: _CategoryFocusNode, + + onFocusChange: (hasFocus) { + setState(() { + _CategoryFocused = hasFocus; + }); + }, + child: GestureDetector( + // + onTap: () { + // Request focus when user taps + _CategoryFocusNode?.requestFocus(); + }, child: DropdownSearch>( key: selectedClass == null @@ -697,10 +767,34 @@ class _AccomodationScreenState extends State { ); }, ), - dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( + // border: InputBorder.none, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: BorderSide( + color: + (_CategoryFocused ?? false) + ? layoutColor! + : Colors.white, + width: 1, + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: + (_CategoryFocused ?? false) + ? layoutColor + : Colors.white, + // : const Color(0xFFD6D5E6), + width: 1, + // const Color(0xFFD6D5E6), + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: layoutColor, width: 1), + ), + contentPadding: const EdgeInsets.symmetric( horizontal: 10, vertical: 10, ), @@ -764,7 +858,7 @@ class _AccomodationScreenState extends State { } } : null, - ), + ),),), ), ), if (errorMessages["class"] != null) ...[ diff --git a/lib/Screens/itnerary/bus.dart b/lib/Screens/itnerary/bus.dart index faadf5e..9522e4f 100644 --- a/lib/Screens/itnerary/bus.dart +++ b/lib/Screens/itnerary/bus.dart @@ -113,7 +113,7 @@ class _BusScreenState extends State { }); _dateFocusNode.addListener(() { setState(() { - _dateFocus = _fromFocusNode.hasFocus; + _dateFocus = _dateFocusNode.hasFocus; }); }); @@ -144,6 +144,9 @@ class _BusScreenState extends State { @override void dispose() { _tripTypeFocusNode.dispose(); + _dateFocusNode.dispose(); + _timeFocusNode.dispose(); + _tripTypeController.dispose(); _hotelNameController.dispose(); _fromController.dispose(); @@ -536,7 +539,11 @@ class _BusScreenState extends State { child: SizedBox( height: 40, child: GestureDetector( - onTap: () => _selectCheckOutDate(context), + // onTap: () => _selectCheckOutDate(context), + onTap: () { + _dateFocusNode.requestFocus(); + _selectCheckOutDate(context); + }, child: AbsorbPointer( child: TextField( focusNode: _dateFocusNode, @@ -588,6 +595,7 @@ class _BusScreenState extends State { onTap: () { // _selectedCheckOutTime = null; // ✅ Reset time variable _timeController.text = ""; // ✅ Clear text field + _timeFocusNode.requestFocus(); _selectCheckOutTime(context); }, child: AbsorbPointer( diff --git a/lib/Screens/itnerary/forex.dart b/lib/Screens/itnerary/forex.dart index a27f3f6..b8f619e 100644 --- a/lib/Screens/itnerary/forex.dart +++ b/lib/Screens/itnerary/forex.dart @@ -279,7 +279,7 @@ class _ForexScreenState extends State { if (cardNumber.length < 15) { // Show error or return false print("Card number must be 15 digits"); - errorMessages["card_number"] = "Card number must be atleat 15 digits"; + errorMessages["card_number"] = "Card number must be atleast 15 digits"; } } @@ -1091,8 +1091,8 @@ class _ForexScreenState extends State { // // }, onTap: () async { + focusNodes["_forexStartDate"]?.requestFocus(); await _selectCheckOutDate(context); - if (textControllers["_forexEndDate"]!.text.isNotEmpty) { DateTime? startDate = _parseDate( textControllers["_forexStartDate"]!.text, @@ -1189,6 +1189,7 @@ class _ForexScreenState extends State { child: GestureDetector( // onTap: () => _selectForexEndDate(context), onTap: () async { + focusNodes["_forexEndDate"]?.requestFocus(); await _selectForexEndDate(context); if (textControllers["_forexEndDate"]!.text.isNotEmpty) { diff --git a/lib/Screens/itnerary/miscellaneous.dart b/lib/Screens/itnerary/miscellaneous.dart index 201fb0d..745383f 100644 --- a/lib/Screens/itnerary/miscellaneous.dart +++ b/lib/Screens/itnerary/miscellaneous.dart @@ -4,6 +4,7 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; import 'package:responsive_builder/responsive_builder.dart'; +import '../../utils/auth_utils.dart'; import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_text_itnerary_sub.dart'; @@ -44,10 +45,10 @@ class _MiscellaneousScreenState extends State { late TextEditingController _commentsController = TextEditingController(); - bool _isHotelNameFocused = false; + bool _hotelNameFocused = false; bool _commentsFocus = false; String? selectedSpecialType; - + Color layoutColor = Colors.grey; Map errorMessages = {}; Map get miscellaneousData { @@ -77,10 +78,12 @@ class _MiscellaneousScreenState extends State { if (widget.selectedItem == null) { _commentsController.clear(); } - + WidgetsBinding.instance.addPostFrameCallback((_) { + loadInitialData(); + }); _hotelNameFocusNode.addListener(() { setState(() { - _isHotelNameFocused = _hotelNameFocusNode.hasFocus; + _hotelNameFocused = _hotelNameFocusNode.hasFocus; }); }); @@ -104,11 +107,30 @@ class _MiscellaneousScreenState extends State { @override void dispose() { _tripTypeFocusNode.dispose(); + _hotelNameFocusNode.dispose(); _tripTypeController.dispose(); _commentsController.dispose(); super.dispose(); } + void loadInitialData() async { + String? layoutString = await getLayoutColor(); + + // setState(() { + // layoutColor = + // layoutString != null + // ? Color(int.parse(layoutString)) + // : Colors.redAccent; + // }); + + setState(() { + layoutColor = + layoutString != null + ? Color(int.parse(layoutString)) + : Colors.redAccent; + }); + } + bool isValidData(Map data) { errorMessages.clear(); // Reset errors @@ -281,8 +303,9 @@ class _MiscellaneousScreenState extends State { // ), // ), CustomTextFieldItnerarySubWrapper( - isFocused: _isHotelNameFocused, + isFocused: _hotelNameFocused, isDesktop: isDesktop, + padding: const EdgeInsets.symmetric(horizontal: 0), width: isDesktop ? MediaQuery.of(context).size.width * 0.34 @@ -290,6 +313,19 @@ class _MiscellaneousScreenState extends State { child: SizedBox( height: 40, width: double.infinity, + child: Focus( + focusNode: _hotelNameFocusNode, + onFocusChange: (hasFocus) { + setState(() { + _hotelNameFocused = hasFocus; + }); + }, + child: GestureDetector( + // + onTap: () { + // Request focus when user taps + _hotelNameFocusNode?.requestFocus(); + }, child: DropdownSearch>( // focusNode: _taxiReqFocusNode, items: purposeList.cast>(), @@ -318,9 +354,33 @@ class _MiscellaneousScreenState extends State { ); }, ), - dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( - border: InputBorder.none, + // border: InputBorder.none, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(4), + borderSide: BorderSide( + color: + (_hotelNameFocused ?? false) + ? layoutColor! + : Colors.white, + width: 0.5, + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: + (_hotelNameFocused ?? false) + ? layoutColor + : Colors.white, + // : const Color(0xFFD6D5E6), + width: 0.5, + // const Color(0xFFD6D5E6), + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: layoutColor, width: 0.5), + ), contentPadding: EdgeInsets.symmetric( horizontal: 10, vertical: 10, @@ -348,7 +408,7 @@ class _MiscellaneousScreenState extends State { print("Updating form data: -> ${newValue ?? ""}"); } : null, - ), + ),),) ), ), if (errorMessages["special_request"] != null) ...[ diff --git a/lib/Screens/itnerary/taxi.dart b/lib/Screens/itnerary/taxi.dart index c26516a..3a2b12f 100644 --- a/lib/Screens/itnerary/taxi.dart +++ b/lib/Screens/itnerary/taxi.dart @@ -5,6 +5,7 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; import 'package:responsive_builder/responsive_builder.dart'; +import '../../utils/auth_utils.dart'; import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_text_itnerary_sub.dart'; @@ -41,6 +42,7 @@ class _TaxiScreenState extends State { final FocusNode _timeFocusNode = FocusNode(); final FocusNode _numPassengerFocusNode = FocusNode(); final FocusNode _commentsFocusNode = FocusNode(); + final FocusNode _carTypeFocusNode = FocusNode(); late TextEditingController _destinationController = TextEditingController(); late TextEditingController _locationController = TextEditingController(); @@ -54,12 +56,14 @@ class _TaxiScreenState extends State { bool _toFocus = false; bool _dateFocus = false; bool _taxiReqFocused = false; + bool _carTypeFocused = false; bool _numPassengerFocus = false; bool _timeFocus = false; bool _commentsFocus = false; String? selectedReqTaxi; String? selectedCarType; + Color layoutColor = Colors.grey; Map get taxiData { Map data = { @@ -98,6 +102,10 @@ class _TaxiScreenState extends State { void initState() { super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + loadInitialData(); + }); + _addFocusListener( _destinationFocusNode, (focus) => _destinationFocus = focus, @@ -106,6 +114,7 @@ class _TaxiScreenState extends State { _addFocusListener(_dateFocusNode, (focus) => _dateFocus = focus); _addFocusListener(_timeFocusNode, (focus) => _timeFocus = focus); _addFocusListener(_taxiReqFocusNode, (focus) => _taxiReqFocused = focus); + _addFocusListener(_carTypeFocusNode, (focus) => _carTypeFocused = focus); _addFocusListener( _numPassengerFocusNode, (focus) => _numPassengerFocus = focus, @@ -146,10 +155,22 @@ class _TaxiScreenState extends State { }); } + void loadInitialData() async { + String? layoutString = await getLayoutColor(); + + setState(() { + layoutColor = + layoutString != null + ? Color(int.parse(layoutString)) + : Colors.redAccent; + }); + } + @override void dispose() { _destinationFocusNode.dispose(); _locationFocusNode.dispose(); + _carTypeFocusNode.dispose(); _destinationController.dispose(); _dateController.dispose(); _timeController.dispose(); @@ -397,9 +418,22 @@ class _TaxiScreenState extends State { CustomTextFieldItnerarySubWrapper( isFocused: _toFocus, isDesktop: isDesktop, + padding: const EdgeInsets.symmetric(horizontal: 0), child: SizedBox( height: 40, width: double.infinity, + child: Focus( + focusNode: _carTypeFocusNode, + onFocusChange: (hasFocus) { + setState(() { + _carTypeFocused = hasFocus; + }); + }, + child: GestureDetector( + onTap: () { + // Request focus when user taps + _carTypeFocusNode?.requestFocus(); + }, child: DropdownSearch>( // focusNode: _taxiReqFocusNode, items: purposeList.cast>(), @@ -428,9 +462,33 @@ class _TaxiScreenState extends State { ); }, ), - dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( - border: InputBorder.none, + // border: InputBorder.none, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: BorderSide( + color: + (_carTypeFocused ?? false) + ? layoutColor! + : Colors.white, + width: 1, + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: + (_carTypeFocused ?? false) + ? layoutColor + : Colors.white, + // : const Color(0xFFD6D5E6), + width: 1, + // const Color(0xFFD6D5E6), + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: layoutColor, width: 1), + ), contentPadding: EdgeInsets.symmetric( horizontal: 10, vertical: 10, @@ -464,7 +522,7 @@ class _TaxiScreenState extends State { print("Updating form data: -> ${newValue ?? ""}"); } : null, - ), + ),),), ), ), ], @@ -541,6 +599,7 @@ class _TaxiScreenState extends State { CustomTextFieldItnerarySubWrapper( isFocused: _taxiReqFocused, isDesktop: isDesktop, + padding: const EdgeInsets.symmetric(horizontal: 0), width: isDesktop ? MediaQuery.of(context).size.width * 0.34 @@ -548,6 +607,17 @@ class _TaxiScreenState extends State { child: SizedBox( height: 40, width: double.infinity, + child: Focus( + focusNode: _taxiReqFocusNode, + onFocusChange: (hasFocus) { + setState(() { + _taxiReqFocused = hasFocus; + }); + }, + child: GestureDetector( + onTap: () { + _taxiReqFocusNode?.requestFocus(); + }, child: DropdownSearch>( // focusNode: _taxiReqFocusNode, items: purposeList.cast>(), @@ -576,12 +646,36 @@ class _TaxiScreenState extends State { ); }, ), - dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( - border: InputBorder.none, + // border: InputBorder.none, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: BorderSide( + color: + (_taxiReqFocused ?? false) + ? layoutColor! + : Colors.white, + width: 1, + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: + (_taxiReqFocused ?? false) + ? layoutColor + : Colors.white, + // : const Color(0xFFD6D5E6), + width: 1, + // const Color(0xFFD6D5E6), + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: layoutColor, width: 1), + ), contentPadding: EdgeInsets.symmetric( horizontal: 10, - vertical: 10, + vertical: 1, ), ), ), @@ -606,7 +700,7 @@ class _TaxiScreenState extends State { print("Updating form data: -> ${newValue ?? ""}"); } : null, - ), + ),),), ), ), ]; @@ -817,7 +911,11 @@ class _TaxiScreenState extends State { child: SizedBox( height: 40, child: GestureDetector( - onTap: () => _selectCheckOutDate(context), + // onTap: () => _selectCheckOutDate(context), + onTap: () { + _dateFocusNode.requestFocus(); + _selectCheckOutDate(context); + }, child: AbsorbPointer( child: TextField( focusNode: _dateFocusNode, @@ -868,6 +966,7 @@ class _TaxiScreenState extends State { child: GestureDetector( onTap: () { _timeController.text = ""; + _timeFocusNode.requestFocus(); _selectCheckOutTime(context); }, child: AbsorbPointer( diff --git a/lib/Screens/itnerary/train.dart b/lib/Screens/itnerary/train.dart index 2b3e806..446eabf 100644 --- a/lib/Screens/itnerary/train.dart +++ b/lib/Screens/itnerary/train.dart @@ -5,6 +5,7 @@ import 'package:intl/intl.dart'; import 'package:responsive_builder/responsive_builder.dart'; import '../../services/apiService.dart'; +import '../../utils/auth_utils.dart'; import '../../widgets/custom_confirmation_dialog.dart'; import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_text_itnerary_sub.dart'; @@ -44,7 +45,7 @@ class _TrainScreenState extends State { Map selectedValues = {}; final FocusNode _trainNoFocusNode = FocusNode(); - final FocusNode _hotelNameFocusNode = FocusNode(); + final FocusNode _typeOfClassFocusNode = FocusNode(); final FocusNode _fromFocusNode = FocusNode(); final FocusNode _toFocusNode = FocusNode(); final FocusNode _dateFocusNode = FocusNode(); @@ -60,7 +61,7 @@ class _TrainScreenState extends State { late TextEditingController _trainCommentsController = TextEditingController(); bool _trainNoFocused = false; - bool _isHotelNameFocused = false; + bool _typeOfClassFocus = false; bool _fromFocus = false; bool _toFocus = false; bool _dateFocus = false; @@ -71,6 +72,7 @@ class _TrainScreenState extends State { String? exceptionalClass = "0"; String? selectedFrom; String? selectedTo; + Color layoutColor = Colors.grey; Map errorMessages = {}; @@ -110,15 +112,17 @@ class _TrainScreenState extends State { @override void initState() { super.initState(); - + WidgetsBinding.instance.addPostFrameCallback((_) { + loadInitialData(); + }); _trainNoFocusNode.addListener(() { setState(() { _trainNoFocused = _trainNoFocusNode.hasFocus; }); }); - _hotelNameFocusNode.addListener(() { + _typeOfClassFocusNode.addListener(() { setState(() { - _isHotelNameFocused = _hotelNameFocusNode.hasFocus; + _typeOfClassFocus = _typeOfClassFocusNode.hasFocus; }); }); _fromFocusNode.addListener(() { @@ -133,7 +137,7 @@ class _TrainScreenState extends State { }); _dateFocusNode.addListener(() { setState(() { - _dateFocus = _fromFocusNode.hasFocus; + _dateFocus = _dateFocusNode.hasFocus; }); }); _timeFocusNode.addListener(() { @@ -213,6 +217,13 @@ class _TrainScreenState extends State { @override void dispose() { _trainNoFocusNode.dispose(); + _dateFocusNode.dispose(); + _timeFocusNode.dispose(); + _typeOfClassFocusNode.dispose(); + _fromFocusNode.dispose(); + _toFocusNode.dispose(); + _commentsFocusNode.dispose(); + _trainNoController.dispose(); _hotelNameController.dispose(); _fromController.dispose(); @@ -223,6 +234,17 @@ class _TrainScreenState extends State { super.dispose(); } + void loadInitialData() async { + String? layoutString = await getLayoutColor(); + + setState(() { + layoutColor = + layoutString != null + ? Color(int.parse(layoutString)) + : Colors.redAccent; + }); + } + void _clearError(String field) { if (mounted && errorMessages.containsKey(field)) { setState(() { @@ -632,12 +654,24 @@ class _TrainScreenState extends State { // ), // ), CustomTextFieldWrapper( - isFocused: _isHotelNameFocused, + isFocused: _typeOfClassFocus, + padding: const EdgeInsets.symmetric(horizontal: 0), isDesktop: isDesktop, width: isDesktop ? MediaQuery.of(context).size.width * 0.15 : null, child: SizedBox( height: 40, width: double.infinity, + child: Focus( + focusNode: _typeOfClassFocusNode, + onFocusChange: (hasFocus) { + setState(() { + _typeOfClassFocus = hasFocus; + }); + }, + child: GestureDetector( + onTap: () { + _typeOfClassFocusNode?.requestFocus(); + }, child: DropdownSearch>( key: selectedClass == null @@ -678,9 +712,33 @@ class _TrainScreenState extends State { ); }, ), - dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( - border: InputBorder.none, + // border: InputBorder.none, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: BorderSide( + color: + (_typeOfClassFocus ?? false) + ? layoutColor! + : Colors.white, + width: 1, + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: + (_typeOfClassFocus ?? false) + ? layoutColor + : Colors.white, + // : const Color(0xFFD6D5E6), + width: 1, + // const Color(0xFFD6D5E6), + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: layoutColor, width: 1), + ), contentPadding: EdgeInsets.symmetric( horizontal: 10, vertical: 10, @@ -759,7 +817,7 @@ class _TrainScreenState extends State { }); } : null, - ), + ),),) ), ), if (errorMessages["class"] != null) ...[ @@ -874,15 +932,27 @@ class _TrainScreenState extends State { // ), CustomTextFieldItnerarySubWrapper( isFocused: _fromFocus ?? false, + padding: const EdgeInsets.symmetric(horizontal: 0), isDesktop: isDesktop, child: SizedBox( height: 40, + child: Focus( + focusNode: _fromFocusNode, + onFocusChange: (hasFocus) { + setState(() { + _fromFocus = hasFocus; + }); + }, + child: GestureDetector( + onTap: () { + _fromFocusNode?.requestFocus(); + }, child: DropdownSearch( selectedItem: countryMap[selectedFrom], popupProps: PopupProps.menu( showSearchBox: true, fit: FlexFit.loose, - constraints: BoxConstraints(maxHeight: 250), + constraints: BoxConstraints(maxHeight: 150),// maxHeight: 250 menuProps: const MenuProps(backgroundColor: Colors.white), itemBuilder: (context, item, isSelected) => Container( @@ -906,8 +976,32 @@ class _TrainScreenState extends State { items: countryMap.values.toList(), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 1), + // border: InputBorder.none, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: BorderSide( + color: + (_fromFocus ?? false) + ? layoutColor! + : Colors.white, + width: 1, + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: + (_fromFocus ?? false) + ? layoutColor + : Colors.white, + // : const Color(0xFFD6D5E6), + width: 1, + // const Color(0xFFD6D5E6), + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: layoutColor, width: 1), + ), + contentPadding: EdgeInsets.symmetric(horizontal: 10), ), ), dropdownBuilder: @@ -929,7 +1023,7 @@ class _TrainScreenState extends State { .key; }); }, - ), + ),),) ), ), if (errorMessages["from_station"] != null) ...[ @@ -1008,14 +1102,26 @@ class _TrainScreenState extends State { CustomTextFieldItnerarySubWrapper( isFocused: _toFocus ?? false, isDesktop: isDesktop, + padding: const EdgeInsets.symmetric(horizontal: 0), child: SizedBox( height: 40, + child: Focus( + focusNode: _toFocusNode, + onFocusChange: (hasFocus) { + setState(() { + _toFocus = hasFocus; + }); + }, + child: GestureDetector( + onTap: () { + _toFocusNode?.requestFocus(); + }, child: DropdownSearch( selectedItem: countryMap[selectedTo], popupProps: PopupProps.menu( showSearchBox: true, fit: FlexFit.loose, - constraints: BoxConstraints(maxHeight: 250), + constraints: BoxConstraints(maxHeight: 150), menuProps: const MenuProps(backgroundColor: Colors.white), itemBuilder: (context, item, isSelected) => Container( @@ -1039,8 +1145,32 @@ class _TrainScreenState extends State { items: countryMap.values.toList(), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 1), + // border: InputBorder.none, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: BorderSide( + color: + (_toFocus ?? false) + ? layoutColor! + : Colors.white, + width: 1, + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: + (_toFocus ?? false) + ? layoutColor + : Colors.white, + // : const Color(0xFFD6D5E6), + width: 1, + // const Color(0xFFD6D5E6), + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: layoutColor, width: 1), + ), + contentPadding: EdgeInsets.symmetric(horizontal: 10), ), ), dropdownBuilder: @@ -1062,7 +1192,7 @@ class _TrainScreenState extends State { .key; }); }, - ), + ),),), ), ), if (errorMessages["to_station"] != null) ...[ @@ -1094,7 +1224,11 @@ class _TrainScreenState extends State { child: SizedBox( height: 40, child: GestureDetector( - onTap: () => _selectCheckOutDate(context), + // onTap: () => _selectCheckOutDate(context), + onTap: () { + _dateFocusNode.requestFocus(); + _selectCheckOutDate(context); + }, child: AbsorbPointer( child: TextField( focusNode: _dateFocusNode, @@ -1146,6 +1280,7 @@ class _TrainScreenState extends State { child: GestureDetector( onTap: () { _timeController.text = ""; + _timeFocusNode.requestFocus(); _selectCheckOutTime(context); }, child: AbsorbPointer( diff --git a/lib/Screens/itnerary/visa.dart b/lib/Screens/itnerary/visa.dart index 2a660ec..6ca8052 100644 --- a/lib/Screens/itnerary/visa.dart +++ b/lib/Screens/itnerary/visa.dart @@ -5,6 +5,7 @@ import 'package:intl/intl.dart'; import 'package:responsive_builder/responsive_builder.dart'; import '../../services/apiService.dart'; +import '../../utils/auth_utils.dart'; import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_text_itnerary_sub.dart'; @@ -49,6 +50,7 @@ class _VisaScreenState extends State { List countryList = []; final FocusNode _tripTypeFocusNode = FocusNode(); + final FocusNode _countryFocusNode = FocusNode(); final FocusNode _hotelNameFocusNode = FocusNode(); final FocusNode _dateFocusNode = FocusNode(); final FocusNode _commentsFocusNode = FocusNode(); @@ -62,12 +64,14 @@ class _VisaScreenState extends State { late TextEditingController _visaCommentsController = TextEditingController(); bool _tripTypeFocused = false; + bool _countryFocused = false; bool _isHotelNameFocused = false; bool _dateFocus = false; bool _commentsFocus = false; String? selectedPurpose; String? selectedCountry; + Color layoutColor = Colors.grey; Map errorMessages = {}; @@ -97,8 +101,11 @@ class _VisaScreenState extends State { @override void initState() { super.initState(); - + WidgetsBinding.instance.addPostFrameCallback((_) { + loadInitialData(); + }); _addFocusListener(_tripTypeFocusNode, (focus) => _tripTypeFocused = focus); + _addFocusListener(_countryFocusNode, (focus) => _countryFocused = focus); _addFocusListener( _hotelNameFocusNode, (focus) => _isHotelNameFocused = focus, @@ -144,6 +151,24 @@ class _VisaScreenState extends State { }); } + void loadInitialData() async { + String? layoutString = await getLayoutColor(); + + // setState(() { + // layoutColor = + // layoutString != null + // ? Color(int.parse(layoutString)) + // : Colors.redAccent; + // }); + + setState(() { + layoutColor = + layoutString != null + ? Color(int.parse(layoutString)) + : Colors.redAccent; + }); + } + void _addFocusListener(FocusNode node, Function(bool) updateState) { node.addListener(() { setState(() { @@ -217,6 +242,7 @@ class _VisaScreenState extends State { @override void dispose() { _tripTypeFocusNode.dispose(); + _countryFocusNode.dispose(); _tripTypeController.dispose(); _hotelNameController.dispose(); _fromController.dispose(); @@ -519,12 +545,27 @@ class _VisaScreenState extends State { CustomTextFieldItnerarySubWrapper( isFocused: _isHotelNameFocused, isDesktop: isDesktop, + padding: const EdgeInsets.symmetric(horizontal: 0), width: isDesktop ? MediaQuery.of(context).size.width * 0.34 : MediaQuery.of(context).size.width * 0.66, child: SizedBox( height: 40, + child: Focus( + focusNode: _countryFocusNode, + + onFocusChange: (hasFocus) { + setState(() { + _countryFocused = hasFocus; + }); + }, + child: GestureDetector( + // + onTap: () { + // Request focus when user taps + _countryFocusNode?.requestFocus(); + }, child: DropdownSearch( selectedItem: countryMap[selectedCountry], popupProps: PopupProps.menu( @@ -575,8 +616,32 @@ class _VisaScreenState extends State { items: countryMap.values.toList(), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 1), + // border: InputBorder.none, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: BorderSide( + color: + (_countryFocused ?? false) + ? layoutColor! + : Colors.white, + width: 1, + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: + (_countryFocused ?? false) + ? layoutColor + : Colors.white, + // : const Color(0xFFD6D5E6), + width: 1, + // const Color(0xFFD6D5E6), + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: layoutColor, width: 1), + ), + contentPadding: EdgeInsets.symmetric(horizontal: 10), ), ), dropdownBuilder: @@ -598,7 +663,7 @@ class _VisaScreenState extends State { .key; }); }, - ), + ),),), ), ), if (errorMessages["country_code"] != null) ...[ @@ -659,10 +724,24 @@ class _VisaScreenState extends State { CustomTextFieldWrapper( isFocused: _tripTypeFocused, isDesktop: isDesktop, + padding: const EdgeInsets.symmetric(horizontal: 0), width: isDesktop ? MediaQuery.of(context).size.width * 0.15 : null, child: SizedBox( height: 40, width: double.infinity, + child: Focus( + focusNode: _tripTypeFocusNode, + onFocusChange: (hasFocus) { + setState(() { + _tripTypeFocused = hasFocus; + }); + }, + child: GestureDetector( + // + onTap: () { + // Request focus when user taps + _tripTypeFocusNode?.requestFocus(); + }, child: DropdownSearch>( items: purposeList.cast>(), selectedItem: purposeList.firstWhere( @@ -690,9 +769,33 @@ class _VisaScreenState extends State { ); }, ), - dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( - border: InputBorder.none, + // border: InputBorder.none, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: BorderSide( + color: + (_tripTypeFocused ?? false) + ? layoutColor! + : Colors.white, + width: 0.5, + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: + (_tripTypeFocused ?? false) + ? layoutColor + : Colors.white, + // : const Color(0xFFD6D5E6), + width: 0.5, + // const Color(0xFFD6D5E6), + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: layoutColor, width: 0.5), + ), contentPadding: EdgeInsets.symmetric( horizontal: 10, vertical: 10, @@ -734,7 +837,7 @@ class _VisaScreenState extends State { }); } : null, - ), + ),),), ), ), if (errorMessages["type_of_visa"] != null) ...[ @@ -764,6 +867,7 @@ class _VisaScreenState extends State { height: 40, child: GestureDetector( onTap: () async { + _dateFocusNode.requestFocus(); await _selectCheckOutDate(context); if (_dateController.text.isNotEmpty) { setState(() { diff --git a/lib/Screens/myTemplates/template.dart b/lib/Screens/myTemplates/template.dart index b1eb202..6a0a5ec 100644 --- a/lib/Screens/myTemplates/template.dart +++ b/lib/Screens/myTemplates/template.dart @@ -34,6 +34,7 @@ import '../../routes/custom_drawer.dart'; import '../../services/apiService.dart'; import '../../utils/auth_utils.dart'; import '../../widgets/custom_breadcrumb_navigation.dart'; +import '../../widgets/custom_user_form.dart'; import '../../widgets/custom_user_travel.dart'; import 'dialog_placeholders.dart'; @@ -63,6 +64,8 @@ class TemplateState extends State