diff --git a/assets/images/login/microsoft.png b/assets/images/login/microsoft.png new file mode 100644 index 0000000..a4a9f7a Binary files /dev/null and b/assets/images/login/microsoft.png differ diff --git a/lib/Screens/allTrips/list_all_plans.dart b/lib/Screens/allTrips/list_all_plans.dart index bfdc978..11ae9b5 100644 --- a/lib/Screens/allTrips/list_all_plans.dart +++ b/lib/Screens/allTrips/list_all_plans.dart @@ -289,7 +289,7 @@ class _ListAllPlansState extends State { String _formatDate(String rawDate) { try { final dateTime = DateTime.parse(rawDate); - return DateFormat('dd MMM yy hh:m a').format(dateTime); + return DateFormat('dd, MMM yyyy hh:m a').format(dateTime); } catch (e) { return rawDate; // fallback if parsing fails } @@ -309,7 +309,7 @@ class _ListAllPlansState extends State { // color: Color(0xFFF7F7FB), ) : null, - color: Colors.white, + color: isDesktop ? Colors.white : Color(0xFFFCFCFC), // color: Color(0xFFF7F7FB), // color: Colors.amber, @@ -318,7 +318,7 @@ class _ListAllPlansState extends State { padding: const EdgeInsets.all(1.0), child: Container( // padding: const EdgeInsets.all(10.0), - color: Colors.white, + color: isDesktop ? Colors.white : Color(0xFFFCFCFC), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -331,12 +331,12 @@ class _ListAllPlansState extends State { children: [ Row( children: [ - const Text('All Trips', + Text('All Trips', style: TextStyle( fontFamily: "Inter", - fontSize: 16, + fontSize: isDesktop ? 16 : 14, fontWeight: FontWeight.w600, - color: Color(0xFF212121))), + color: const Color(0xFF212121))), ], ), @@ -474,7 +474,7 @@ class _ListAllPlansState extends State { children: [ Container( width: MediaQuery.of(context).size.width * 0.85, - height: 40, + height: 35, child: TextField( controller: searchController, onChanged: filterPlans, @@ -781,99 +781,142 @@ class _ListAllPlansState extends State { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), - elevation: 3, + elevation: 3, // Control the strength of the shadow + // shadowColor: Colors.black54, // Softer shadow + // clipBehavior: Clip.antiAlias, child: Padding( padding: const EdgeInsets.all(12.0), + // padding: EdgeInsets.symmetric(vertical: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, children: [ // Status and Employee Code Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Container( - padding: EdgeInsets.symmetric( - horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: getStatusColor(plan.statusValue), - borderRadius: BorderRadius.circular(8), - ), - child: Text( - plan.statusValue, - style: TextStyle( - color: getStatusTextColor( - plan.statusValue), - fontSize: 12, - fontWeight: FontWeight.w600, + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: getStatusColor( + plan.statusValue), + borderRadius: + BorderRadius.circular(8), + ), + child: Text( + plan.statusValue, + style: TextStyle( + color: getStatusTextColor( + plan.statusValue), + fontSize: 10, + fontWeight: FontWeight.w600, + ), + ), ), - ), + ], ), - PlanPopupMenu( - plan: plan, - deletePlan: deletePlan, - apiService: apiService, + Column( + children: [ + PlanPopupMenu( + plan: plan, + deletePlan: deletePlan, + apiService: apiService, + ), + ], ), ], ), - SizedBox(height: 8), - // Name - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - plan.employeeCode ?? 'No Data', - style: TextStyle( - fontSize: 12, fontFamily: "Inter"), - ), - Text( - plan.userName.isNotEmpty - ? plan.userName - : plan.travellerName, - style: TextStyle( - fontSize: 12, fontFamily: "Inter"), - ), - ], - ), - SizedBox(height: 5), + + SizedBox(height: 2), // Trip Id and Trip Name Row( children: [ - Expanded( - child: Text('${plan.tripTitle}', - style: TextStyle( - fontSize: 13, - fontFamily: "Inter", - fontWeight: FontWeight.bold)), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text(' ${plan.tripTitle}', + style: TextStyle( + fontSize: 12, + fontFamily: "Inter", + fontWeight: FontWeight.bold)), + ], ), ], ), SizedBox(height: 2), // Type and Created On Row( - mainAxisSize: MainAxisSize.min, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ - Expanded( - child: Text('${plan.tripType}', - style: TextStyle( - fontSize: 10, - color: Colors.black87, - fontFamily: "Inter", - )), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text(' ${plan.tripType}', + style: TextStyle( + fontSize: 9, + color: Colors.black87, + fontFamily: "Inter", + )), + ], ), - Expanded( - child: - Text('${_formatDate(plan.createdOn)}', - style: TextStyle( - fontSize: 10, - color: Colors.black87, - fontFamily: "Inter", - )), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text('${_formatDate(plan.createdOn)}', + style: TextStyle( + fontSize: 9, + color: Colors.black87, + fontFamily: "Inter", + )), + ], + ), + ], + ), + SizedBox(height: 3), + // Name + Row( + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + plan.userName.isNotEmpty + ? '${plan.userName}' + : '${plan.travellerName}', + style: TextStyle( + fontSize: 9, + fontFamily: "Inter", + color: Colors.black87), + ), + ], + ), + Spacer(), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + plan.employeeCode ?? 'No Data', + style: TextStyle( + fontSize: 9, + fontFamily: "Inter", + color: Colors.black87), + ), + ], ), ], ), - // Actions ], ), diff --git a/lib/Screens/allTrips/travel_agent_list.dart b/lib/Screens/allTrips/travel_agent_list.dart index 3bf28e4..eb3e512 100644 --- a/lib/Screens/allTrips/travel_agent_list.dart +++ b/lib/Screens/allTrips/travel_agent_list.dart @@ -280,7 +280,7 @@ class _TravelAgentListPlansState extends State { String _formatDate(String rawDate) { try { final dateTime = DateTime.parse(rawDate); - return DateFormat('dd MMM yy hh:m a').format(dateTime); + return DateFormat('dd,MMM yyyy hh:m a').format(dateTime); } catch (e) { return rawDate; // fallback if parsing fails } @@ -300,7 +300,7 @@ class _TravelAgentListPlansState extends State { // color: Color(0xFFF7F7FB), ) : null, - color: Colors.white, + color: isDesktop ? Colors.white : Color(0xFFFCFCFC), // color: Color(0xFFF7F7FB), // color: Colors.amber, @@ -309,7 +309,7 @@ class _TravelAgentListPlansState extends State { padding: const EdgeInsets.all(1.0), child: Container( // padding: const EdgeInsets.all(10.0), - color: Colors.white, + color: isDesktop ? Colors.white : Color(0xFFFCFCFC), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -322,10 +322,10 @@ class _TravelAgentListPlansState extends State { children: [ Row( children: [ - const Text('My Trips', + Text('My Trips', style: TextStyle( fontFamily: "Inter", - fontSize: 16, + fontSize: isDesktop ? 16 : 14, fontWeight: FontWeight.w600, color: Color(0xFF212121))), ], @@ -400,7 +400,7 @@ class _TravelAgentListPlansState extends State { children: [ Container( width: MediaQuery.of(context).size.width * 0.85, - height: 40, + height: 35, child: TextField( controller: searchController, onChanged: filterPlans, @@ -607,16 +607,6 @@ class _TravelAgentListPlansState extends State { alignment: Alignment.center, decoration: BoxDecoration( color: getStatusColor(plan.statusValue), - // color: plan.statusValue == - // "Partially Approved" - // ? Colors.yellow.shade100 - // : plan.statusValue == "Approved" - // ? Colors.green.shade100 - // : plan.statusValue == "Completed" - // ? Colors.green.shade500 - // : plan.statusValue == "Rejected" - // ? Colors.red.shade100 - // : Colors.grey.shade100, borderRadius: BorderRadius.circular(10), ), child: Text( @@ -625,14 +615,6 @@ class _TravelAgentListPlansState extends State { style: TextStyle( color: getStatusTextColor(plan.statusValue), - // color: (plan.statusValue == - // "Partially Approved" || - // plan.statusValue == "Approved" || - // plan.statusValue == "Rejected") - // ? Colors.black - // : plan.statusValue == "Completed" - // ? Colors.white - // : Colors.grey, fontSize: 12, fontFamily: "Inter", fontWeight: FontWeight.w400, @@ -791,7 +773,7 @@ class _TravelAgentListPlansState extends State { style: TextStyle( color: getStatusTextColor( plan.statusValue), - fontSize: 12, + fontSize: 10, fontWeight: FontWeight.w600, ), ), @@ -803,63 +785,92 @@ class _TravelAgentListPlansState extends State { ), ], ), - SizedBox(height: 8), - // Name - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - plan.employeeCode ?? 'No Data', - style: TextStyle( - fontSize: 12, fontFamily: "Inter"), - ), - Text( - plan.userName.isNotEmpty - ? plan.userName - : plan.travellerName, - style: TextStyle( - fontSize: 12, fontFamily: "Inter"), - ), - ], - ), - SizedBox(height: 5), + + SizedBox(height: 2), // Trip Id and Trip Name Row( children: [ - Expanded( - child: Text('${plan.tripTitle}', - style: TextStyle( - fontSize: 13, - fontFamily: "Inter", - fontWeight: FontWeight.bold)), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text(' ${plan.tripTitle}', + style: TextStyle( + fontSize: 12, + fontFamily: "Inter", + fontWeight: FontWeight.bold)), + ], ), ], ), SizedBox(height: 2), // Type and Created On Row( - mainAxisSize: MainAxisSize.min, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ - Expanded( - child: Text('${plan.tripType}', - style: TextStyle( - fontSize: 10, - color: Colors.black87, - fontFamily: "Inter", - )), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text(' ${plan.tripType}', + style: TextStyle( + fontSize: 9, + color: Colors.black87, + fontFamily: "Inter", + )), + ], ), - Expanded( - child: - Text('${_formatDate(plan.createdOn)}', - style: TextStyle( - fontSize: 10, - color: Colors.black87, - fontFamily: "Inter", - )), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text('${_formatDate(plan.createdOn)}', + style: TextStyle( + fontSize: 9, + color: Colors.black87, + fontFamily: "Inter", + )), + ], ), ], ), + SizedBox(height: 3), + // Name + Row( + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + plan.userName.isNotEmpty + ? '${plan.userName}' + : '${plan.travellerName}', + style: TextStyle( + fontSize: 9, + fontFamily: "Inter", + color: Colors.black87), + ), + ], + ), + Spacer(), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + plan.employeeCode ?? 'No Data', + style: TextStyle( + fontSize: 9, + fontFamily: "Inter", + color: Colors.black87), + ), + ], + ), + ], + ), + // Actions // Actions ], diff --git a/lib/Screens/approvals/approval_dialogs.dart b/lib/Screens/approvals/approval_dialogs.dart index 785267e..14c145e 100644 --- a/lib/Screens/approvals/approval_dialogs.dart +++ b/lib/Screens/approvals/approval_dialogs.dart @@ -1,7 +1,190 @@ import 'package:flutter/material.dart'; +Future showApprovalDialog( + BuildContext context, Color layoutColor) async { + String selectedAction = ""; // "", "accept", "reject" + String remarks = ""; + + return showDialog( + context: context, + barrierDismissible: false, // force user to tap cancel + builder: (context) { + return StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + backgroundColor: Colors.white, + contentPadding: const EdgeInsets.all(24), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "To Approve or Reject Trip", + style: TextStyle( + fontFamily: "Inter", fontWeight: FontWeight.w500), + ), + IconButton( + icon: const Icon( + Icons.close, + size: 15, + ), + onPressed: () { + Navigator.pop(context, null); // Close the dialog + }, + ), + ], + ), + // Accept and Reject Buttons + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: selectedAction == "accept" + ? layoutColor + : Colors.grey.shade200, + foregroundColor: selectedAction == "accept" + ? Colors.white + : Colors.black, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + padding: EdgeInsets.symmetric(vertical: 10), + ), + onPressed: () { + setState(() { + selectedAction = "accept"; + }); + }, + child: const Text("Accept"), + ), + ), + const SizedBox(width: 20), + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: selectedAction == "reject" + ? Colors.redAccent + : Colors.grey.shade200, + foregroundColor: selectedAction == "reject" + ? Colors.white + : Colors.black, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + padding: EdgeInsets.symmetric(vertical: 10), + ), + onPressed: () { + setState(() { + selectedAction = "reject"; + }); + }, + child: const Text( + "Reject", + style: TextStyle(fontFamily: "Inter"), + ), + ), + ), + ], + ), + + const SizedBox(height: 20), + + // Dynamic Content Based on Selection + if (selectedAction == "accept") ...[ + const Text( + "Do you want to approve?", + style: TextStyle(fontFamily: "Inter", fontSize: 14), + ), + ] else if (selectedAction == "reject") ...[ + const Text( + "Please enter reason for rejection:", + style: TextStyle(fontFamily: "Inter", fontSize: 14), + ), + const SizedBox(height: 10), + TextField( + style: TextStyle(fontFamily: "Inter", fontSize: 14), + maxLines: 3, + onChanged: (value) => remarks = value, + decoration: InputDecoration( + hintText: "Remarks...", + hintStyle: const TextStyle( + fontSize: 10, // 👈 Set your desired font size here + color: Colors.grey, + fontFamily: + "Inter", // optional if you want consistent font + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide( + color: Colors.blueGrey, width: 0.5), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: + BorderSide(color: Colors.blueGrey, width: 0.5), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: layoutColor, width: 1), + ), + ), + ), + ] + ], + ), + actionsAlignment: MainAxisAlignment.center, + actions: [ + // Cancel Button + if (selectedAction.isNotEmpty) + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: layoutColor, + side: BorderSide(color: layoutColor, width: 1.5), + ), + onPressed: () { + Navigator.pop(context, null); // No result + }, + child: const Text( + "Cancel", + style: TextStyle(fontFamily: "Inter", fontSize: 12), + ), + ), + if (selectedAction.isNotEmpty) + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: layoutColor, + foregroundColor: Colors.white, + ), + onPressed: () { + if (selectedAction == "accept") { + Navigator.pop(context, "approved"); // Return approval + } else if (selectedAction == "reject" && + remarks.trim().isNotEmpty) { + Navigator.pop(context, remarks.trim()); // Return remarks + } + }, + child: const Text( + "OK", + style: TextStyle(fontFamily: "Inter", fontSize: 12), + ), + ), + ], + ); + }, + ); + }, + ); +} + /// Show confirm dialog for approval -Future showApproveDialog(BuildContext context, Color layoutColor) { +Future showApproveDialog1(BuildContext context, Color layoutColor) { return showDialog( context: context, builder: (context) => AlertDialog( @@ -47,7 +230,7 @@ Future showApproveDialog(BuildContext context, Color layoutColor) { } /// Show confirm dialog for rejection with remarks input -Future showRejectDialog( +Future showRejectDialog1( BuildContext context, Color layoutColor) async { String remarks = ""; @@ -75,10 +258,9 @@ Future showRejectDialog( decoration: const InputDecoration( hintText: "Remarks...", hintStyle: TextStyle( - fontSize: 12, // 👈 Set your desired font size here + fontSize: 10, // 👈 Set your desired font size here color: Colors.grey, - fontFamily: - "Archivo", // optional if you want consistent font + fontFamily: "Inter", // optional if you want consistent font ), border: OutlineInputBorder( borderSide: BorderSide(color: Colors.blueGrey, width: 0.5), diff --git a/lib/Screens/approvals/approval_list.dart b/lib/Screens/approvals/approval_list.dart index 3beaa29..7dc6d75 100644 --- a/lib/Screens/approvals/approval_list.dart +++ b/lib/Screens/approvals/approval_list.dart @@ -342,7 +342,7 @@ class _ApprovalListState extends State { String _formatDate(String rawDate) { try { final dateTime = DateTime.parse(rawDate); - return DateFormat('dd MMM yy hh:m a').format(dateTime); + return DateFormat('dd, MMM yyyy hh:m a').format(dateTime); } catch (e) { return rawDate; // fallback if parsing fails } @@ -362,7 +362,7 @@ class _ApprovalListState extends State { // color: Color(0xFFF7F7FB), ) : null, - color: Colors.white, + color: isDesktop ? Colors.white : Color(0xFFFCFCFC), // color: Color(0xFFF7F7FB), // color: Colors.amber, @@ -371,7 +371,7 @@ class _ApprovalListState extends State { padding: const EdgeInsets.all(1.0), child: Container( // padding: const EdgeInsets.all(10.0), - color: Colors.white, + color: isDesktop ? Colors.white : Color(0xFFFCFCFC), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -383,10 +383,10 @@ class _ApprovalListState extends State { children: [ Row( children: [ - const Text('My Approvals', + Text('My Approvals', style: TextStyle( fontFamily: "Inter", - fontSize: 16, + fontSize: isDesktop ? 16 : 14, fontWeight: FontWeight.w600, )), ], @@ -447,7 +447,7 @@ class _ApprovalListState extends State { children: [ Container( width: MediaQuery.of(context).size.width * 0.8, - height: 40, + height: 35, child: TextField( controller: searchController, onChanged: filterPlans, @@ -554,15 +554,15 @@ class _ApprovalListState extends State { fontFamily: "Inter", fontWeight: FontWeight.w600))), DataColumn( - label: Text('Employee Code', + label: Text('Trip Name', style: TextStyle( - // color: Color(0xFF9E9DBD), fontSize: 13, fontFamily: "Inter", fontWeight: FontWeight.w600))), DataColumn( - label: Text('Trip Name', + label: Text('Employee Code', style: TextStyle( + // color: Color(0xFF9E9DBD), fontSize: 13, fontFamily: "Inter", fontWeight: FontWeight.w600))), @@ -605,6 +605,14 @@ class _ApprovalListState extends State { fontSize: 13, fontFamily: "Archivo", ))), + + DataCell(Text(plan.tripTitle, + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ), + softWrap: true, + overflow: TextOverflow.ellipsis)), DataCell(Text(plan.employeeCode ?? "no data", style: TextStyle( fontSize: 13, @@ -623,13 +631,7 @@ class _ApprovalListState extends State { fontSize: 13, fontFamily: "Inter", ))), - DataCell(Text(plan.tripTitle, - style: TextStyle( - fontSize: 13, - fontFamily: "Inter", - ), - softWrap: true, - overflow: TextOverflow.ellipsis)), + DataCell(Text( // plan.createdOn, @@ -684,17 +686,17 @@ class _ApprovalListState extends State { MainAxisAlignment.center, children: [ IconButton( - icon: Icon(Icons.remove_red_eye, - color: Color(0xFF475569), - size: 18), - onPressed: () { - Navigator.pop( - context); // Close popup manually - ApiService.viewPlan( - context, plan.planId, - isViewMode: true); - }, - ), + icon: Icon(Icons.remove_red_eye, + color: Color(0xFF475569), + size: 18), + onPressed: () { + Navigator.pop( + context); // Close popup manually + viewPlanforApprover( + plan.planId, + isViewMode: true, + isApprover: true); + }), IconButton( icon: Image.asset( 'assets/images/IconsImg/edit.png', @@ -702,9 +704,10 @@ class _ApprovalListState extends State { height: 15), onPressed: () { Navigator.pop(context); - ApiService.viewPlan( + ApiService.viewPlanForApprover( context, plan.planId, - isViewMode: false); + isViewMode: false, + isApprover: true); }, ), IconButton( @@ -825,76 +828,173 @@ class _ApprovalListState extends State { style: TextStyle( color: getStatusTextColor( plan.statusValue), - fontSize: 12, + fontSize: 10, fontWeight: FontWeight.w600, ), ), ), - PlanPopupMenu( - plan: plan, - deletePlan: deletePlan, - apiService: apiService, + 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), + onPressed: () { + Navigator.pop( + context); // Close popup manually + viewPlanforApprover( + plan.planId, + isViewMode: true, + isApprover: true); + }), + IconButton( + icon: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15), + onPressed: () { + Navigator.pop(context); + ApiService + .viewPlanForApprover( + context, + plan.planId, + isViewMode: false, + isApprover: true); + }, + ), + IconButton( + icon: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15), + onPressed: () { + Navigator.pop(context); + deletePlan(plan.planId); + }, + ), + IconButton( + icon: Icon(Icons.download, + color: Color(0xFF114D8B), + size: 18), + onPressed: () { + Navigator.pop(context); + apiService.getPdfDownload( + plan.planId); + }, + ), + ], + ), + ), + ), + ], ), ], ), - SizedBox(height: 8), - // Name - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - plan.employeeCode ?? 'No Data', - style: TextStyle( - fontSize: 12, fontFamily: "Inter"), - ), - Text( - plan.userName.isNotEmpty - ? plan.userName - : plan.travellerName, - style: TextStyle( - fontSize: 12, fontFamily: "Inter"), - ), - ], - ), - SizedBox(height: 5), + + SizedBox(height: 2), // Trip Id and Trip Name Row( children: [ - Expanded( - child: Text('${plan.tripTitle}', - style: TextStyle( - fontSize: 13, - fontFamily: "Inter", - fontWeight: FontWeight.bold)), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text(' ${plan.tripTitle}', + style: TextStyle( + fontSize: 12, + fontFamily: "Inter", + fontWeight: FontWeight.bold)), + ], ), ], ), SizedBox(height: 2), // Type and Created On Row( - mainAxisSize: MainAxisSize.min, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ - Expanded( - child: Text('${plan.tripType}', - style: TextStyle( - fontSize: 10, - color: Colors.black87, - fontFamily: "Inter", - )), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text(' ${plan.tripType}', + style: TextStyle( + fontSize: 9, + color: Colors.black87, + fontFamily: "Inter", + )), + ], ), - Expanded( - child: - Text('${_formatDate(plan.createdOn)}', - style: TextStyle( - fontSize: 10, - color: Colors.black87, - fontFamily: "Inter", - )), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text('${_formatDate(plan.createdOn)}', + style: TextStyle( + fontSize: 9, + color: Colors.black87, + fontFamily: "Inter", + )), + ], ), ], ), - + SizedBox(height: 3), + // Name + Row( + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + plan.userName.isNotEmpty + ? '${plan.userName}' + : '${plan.travellerName}', + style: TextStyle( + fontSize: 9, + fontFamily: "Inter", + color: Colors.black87), + ), + ], + ), + Spacer(), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + plan.employeeCode ?? 'No Data', + style: TextStyle( + fontSize: 9, + fontFamily: "Inter", + color: Colors.black87), + ), + ], + ), + ], + ), + // Actions // Actions ], ), @@ -914,13 +1014,7 @@ class _ApprovalListState extends State { scrollDirection: Axis.vertical, child: table, // <-- your existing table ) - : SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: table, - ), - ), + : buildMobileCardView(paginatedPlans), ), PaginationControls( currentPage: currentPage, diff --git a/lib/Screens/approvals/approval_tooltip.dart b/lib/Screens/approvals/approval_tooltip.dart new file mode 100644 index 0000000..4576801 --- /dev/null +++ b/lib/Screens/approvals/approval_tooltip.dart @@ -0,0 +1,128 @@ +import 'package:flutter/material.dart'; +import 'package:super_tooltip/super_tooltip.dart'; + +class ApprovalTooltip extends StatefulWidget { + final List> planStatusList; + final bool hasApprovals; + final String statusText; + final bool isApproverApproved; + final bool isApproverRejected; + final Color Function(String) getStatusColor; + + const ApprovalTooltip({ + super.key, + required this.planStatusList, + required this.hasApprovals, + required this.statusText, + required this.isApproverApproved, + required this.isApproverRejected, + required this.getStatusColor, + }); + + @override + State createState() => _ApprovalTooltipState(); +} + +class _ApprovalTooltipState extends State { + late SuperTooltip tooltip; + + @override + void initState() { + super.initState(); + tooltip = SuperTooltip( + popupDirection: TooltipDirection.down, + content: _buildTooltipContent(), + borderColor: Colors.grey.shade300, + backgroundColor: Colors.white, + shadowColor: Colors.black38, + barrierColor: Colors.transparent, + ); + } + + Widget _buildTooltipContent() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (!widget.hasApprovals) + Center( + child: Text( + "--- No Approvals ---", + style: const TextStyle( + fontFamily: "Archivo", + fontSize: 11, + fontWeight: FontWeight.w500, + color: Colors.black87, + ), + ), + ), + for (int i = 0; i < widget.planStatusList.length; i++) ...[ + if (widget.planStatusList[i].entries.any((entry) => + entry.key.contains('status') && + entry.value != null && + entry.value.toString().isNotEmpty)) ...[ + _buildApprovalItem( + "Approver ${i + 1}", + widget.planStatusList[i].entries + .firstWhere( + (entry) => entry.key.contains('status'), + orElse: () => MapEntry('', ''), + ) + .value + .toString(), + ), + const SizedBox(height: 6), + ], + ], + ], + ); + } + + Widget _buildApprovalItem(String title, String value) { + return Row( + children: [ + Text( + title, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(width: 8), + Text( + value, + style: const TextStyle( + fontSize: 12, + ), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + return MouseRegion( + child: Container( + decoration: BoxDecoration( + border: Border.all(color: widget.getStatusColor(widget.statusText)), + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 6), + child: Text( + widget.statusText, + style: TextStyle( + fontFamily: "Roboto", + fontWeight: FontWeight.w400, + fontSize: 12, + color: widget.getStatusColor( + widget.isApproverApproved + ? "Approved" + : widget.isApproverRejected + ? "Rejected" + : (widget.statusText), + ), + ), + ), + ), + ); + } +} diff --git a/lib/Screens/authentication/login/login_widget.dart b/lib/Screens/authentication/login/login_widget.dart index 3807c5d..e70a33c 100644 --- a/lib/Screens/authentication/login/login_widget.dart +++ b/lib/Screens/authentication/login/login_widget.dart @@ -675,24 +675,36 @@ class _LoginWidgetState extends State { const SizedBox(height: 20), if (!_isForgotPassword && !_showOtpResetFields) - 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 - ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + 'assets/images/login/microsoft.png', + width: 30, // Optional: control size + height: 30, + fit: BoxFit.contain, + ), + const SizedBox(width: 10), + Container( + width: 30, // Adjust size + height: 30, + 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, - )), + child: Center( + child: Image.asset( + 'assets/images/login/VectorG.png', + width: 20, // Optional: control size + height: 20, + fit: BoxFit.contain, + )), + ), + ], ), ], ), diff --git a/lib/Screens/dialog/user_selection_dialog.dart b/lib/Screens/dialog/user_selection_dialog.dart index 6d4a882..a43e277 100644 --- a/lib/Screens/dialog/user_selection_dialog.dart +++ b/lib/Screens/dialog/user_selection_dialog.dart @@ -14,12 +14,14 @@ class UserSelectionDialog extends StatefulWidget { final String title; final Color layoutColorForUser; final void Function(String, String, bool) onSubmit; + final void Function() onClose; UserSelectionDialog({ Key? key, required this.title, required this.layoutColorForUser, required this.onSubmit, + required this.onClose, }) : super(key: key); @override @@ -427,7 +429,10 @@ class _UserSelectionDialogState extends State { ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), - onPressed: () => Navigator.pop(context), + onPressed: () { + widget.onClose(); + Navigator.pop(context); + }, child: Text( "Cancel", ), diff --git a/lib/Screens/plans/create_plans.dart b/lib/Screens/plans/create_plans.dart index 7cbd44f..0dbcb44 100644 --- a/lib/Screens/plans/create_plans.dart +++ b/lib/Screens/plans/create_plans.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'dart:typed_data'; import 'package:dropdown_search/dropdown_search.dart'; +import 'package:super_tooltip/super_tooltip.dart'; import 'package:web/web.dart' as web; import 'package:flutter/material.dart'; @@ -38,6 +39,9 @@ class _CreatePlansState extends State { Color layoutColor = Colors.redAccent; Color bodyColor = Colors.white; + final FocusNode focusNode = FocusNode(); + bool isFocused = false; + @override void initState() { super.initState(); @@ -78,7 +82,7 @@ class _CreatePlansState extends State { vertical: MediaQuery.of(context).size.height * 0, // 5% of screen height as vertical padding ) - : EdgeInsets.all(8), + : EdgeInsets.all(0), child: Row( children: [ // if (isDesktop) CustomDrawer(isDesktop: true), @@ -111,12 +115,12 @@ class _CreatePlansState extends State { // print("RECived palndata - ${planData}"); return Container( - margin: const EdgeInsets.only(top: 10.0, bottom: 10.0), + margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null, padding: const EdgeInsets.all(8), decoration: BoxDecoration( // color: Colors.amber, // color: bodyColor, - color: Colors.white, + color: isDesktop ? Colors.white : Color(0xFFFCFCFC), // border: Border.all( // color: Colors.white, // // color: Color(0xFFF7F7FB), @@ -299,8 +303,9 @@ class CreateNewPlansState extends State { final FocusNode _tripTitleFocusNode = FocusNode(); final FocusNode _descriptionFocusNode = FocusNode(); // Declare FocusNode - bool _isTripTitleFocused = false; bool _isdescriptionFocused = false; + bool _isTripTitleFocused = false; + late String _selectedOption = "Option 1"; // late String? _selectedIsBillable = "Billable"; @@ -333,6 +338,9 @@ class CreateNewPlansState extends State { late Map purposeMap; List purposeKeys = []; + // Declare tooltip controller + late SuperTooltip tooltip; + String? orgId; String? planUsrId; String? planTravlrId; @@ -911,6 +919,7 @@ class CreateNewPlansState extends State { final requiredFields = { if (TripPlanAction != "Plan Creation Not Allowed") // "trip_type": _selectedTripType, + "trip_title": _tripTitleController.text, "cost_center_id": selectedCostCenterId, "functional_department": selectedFuncDept, "purpose_of_travel": selectedPurpose, @@ -918,8 +927,8 @@ class CreateNewPlansState extends State { for (var entry in requiredFields.entries) { if (entry.value == null || entry.value!.isEmpty) { - validationErrors[entry.key] = - "${entry.key.replaceAll('_', ' ').toUpperCase()} is required"; + validationErrors[entry.key] = "Required"; + // "${entry.key.replaceAll('_', ' ').toUpperCase()} Required"; } } @@ -1097,42 +1106,51 @@ class CreateNewPlansState extends State { DeviceScreenType.desktop; return Container( - padding: const EdgeInsets.all(5), - child: isDesktop - ? Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ..._buildApproverControls(isDesktop), - Spacer(), - Text( - widget.isViewMode - ? "View Plan" - : (selectedPlanId != null && - selectedPlanId!.isNotEmpty - ? "Update Plan" - : "New Plan"), - style: TextStyle(fontSize: 18), - ), - Spacer(), - ..._buildPlanPdf() - ], - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ..._buildApproverControls(isDesktop), - Text( - widget.isViewMode - ? "View Plan" - : (planData.isNotEmpty - ? "Update Plan" - : "New Plan"), - style: TextStyle(fontSize: 18), - ), - ..._buildPlanPdf() - ], - ), - ); + padding: const EdgeInsets.all(5), + child: isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + // mainAxisAlignment: MainAxisAlignment.end, + children: [ + _buildTripName(isDesktop), + if (isDesktop) Spacer(), + ..._buildApproverControls(isDesktop), + + // Text( + // widget.isViewMode + // ? "View Plan" + // : (selectedPlanId != null && + // selectedPlanId!.isNotEmpty + // ? "Update Plan" + // : "New Plan"), + // style: TextStyle(fontSize: 18), + // ), + // Spacer(), + + if (statusValue != "") + ..._buildPlanPdf(isDesktop) + ], + ) + : Column( + children: [ + Row( + // crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ..._buildApproverControls(isDesktop), + if (statusValue != "") + ..._buildPlanPdf(isDesktop) + ], + ), + Row( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + _buildTripName(isDesktop), + ], + ) + ], + )); }), // if (isStatusExpanded) // Container( @@ -1242,6 +1260,15 @@ class CreateNewPlansState extends State { SizedBox( height: 5, ), + + Text( + "Planning This Trip *", // Your label + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 6), isMobile ? SingleChildScrollView( scrollDirection: Axis.horizontal, @@ -1252,33 +1279,40 @@ class CreateNewPlansState extends State { : Row( children: _buildPlanTrip(isMobile), ), - SizedBox(height: 13), - Text.rich( - TextSpan( - text: "Planning this Trip For : ", // Static text + SizedBox(height: 7), + Text(otherUserName ?? userName ?? " ", // Your label 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 - ), - ), - ], - ), - ), + fontSize: 12, + fontWeight: FontWeight.w400, + color: widget.layoutColor, + )), + + // Text.rich( + // TextSpan( + // text: "Trip Planned 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.w400, + // color: widget + // .layoutColor, // Change this to any color + // // color: Colors.blueAccent, // Change this to any color + // ), + // ), + // ], + // ), + // ), ], ), ), @@ -1327,16 +1361,16 @@ class CreateNewPlansState extends State { ? Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildNonDescriptionColumn(), - SizedBox(width: 25), + // _buildNonDescriptionColumn(), + // SizedBox(width: 25), _buildDescriptionColumn(isDesktop), ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildNonDescriptionColumn(), - SizedBox(height: 15), + // _buildNonDescriptionColumn(), + // SizedBox(height: 15), _buildDescriptionColumn(isDesktop), ], ), @@ -1416,6 +1450,12 @@ class CreateNewPlansState extends State { List _buildTripRow(bool isMobile) { List purposeList = apiData?['plan_is_billable'] ?? []; + + List> tripTypeList = [ + {"dropdown_key": "1", "dropdown_value": "Domestic"}, + {"dropdown_key": "2", "dropdown_value": "International"}, + ]; + return [ Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -1428,16 +1468,90 @@ class CreateNewPlansState extends State { color: Color(0xFF575A74)), ), SizedBox(height: 5), - isMobile - ? SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: _buildTripType(isMobile), + CustomTextFieldWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 35, + width: double.infinity, + child: DropdownSearch>( + popupProps: PopupProps.menu( + showSearchBox: false, // Optionally enable search + fit: FlexFit.loose, + menuProps: const MenuProps( + backgroundColor: Colors.white, ), - ) - : Row( - children: _buildTripType(isMobile), + itemBuilder: (context, item, isSelected) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, vertical: 8), + child: Text( + item['dropdown_value'] ?? '', + style: const TextStyle( + fontSize: 12, // 👈 Smaller font size + color: Colors.black, + ), + ), + ); + }, ), + dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, // No underline + contentPadding: + EdgeInsets.symmetric(horizontal: 1, vertical: 5), + ), + ), + dropdownBuilder: (context, selectedItem) { + if (selectedItem == null || selectedItem.isEmpty) { + return const Text( + "Select Trip Type", + style: TextStyle(color: Colors.grey, fontSize: 13), + ); + } + return Text( + selectedItem['dropdown_value'] ?? '', + style: const TextStyle(fontSize: 12, color: Colors.black), + ); + }, + selectedItem: tripTypeList.firstWhere( + (item) => item['dropdown_key'] == _selectedTripType, + orElse: () => {}, + ), + itemAsString: (item) => item['dropdown_value'] ?? '', + items: [ + {"dropdown_key": "1", "dropdown_value": "Domestic"}, + {"dropdown_key": "2", "dropdown_value": "International"}, + ].map>((item) { + return Map.from(item); + }).toList(), + onChanged: widget.isViewMode + ? null + : (Map? newItem) { + if (newItem != null) { + setState(() { + _selectedTripType = + newItem['dropdown_key'].toString(); + fetchTrainFlightClass( + int.parse(_selectedTripType!)); + dynamicItineraryKey.currentState + ?.updateSelectedServices(); + }); + } + }, + ), + ), + ), + // isMobile + // ? SingleChildScrollView( + // scrollDirection: Axis.horizontal, + // child: Row( + // children: _buildTripType(isMobile), + // ), + // ) + // : Row( + // children: _buildTripType(isMobile), + // ), if (validationErrors["trip_type"] != null) Padding( padding: EdgeInsets.only(top: 4), @@ -1449,7 +1563,7 @@ class CreateNewPlansState extends State { ], ), SizedBox( - width: 150, + width: 25, height: 5, ), @@ -1467,94 +1581,144 @@ class CreateNewPlansState extends State { ), SizedBox(height: 5), - Row( - children: purposeList.map((item) { - final isSelected = - _selectedIsBillable == item['dropdown_key'].toString(); - - 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 - : () { - setState(() { - _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), + CustomTextFieldWrapper( + width: + widget.isDesktop ? MediaQuery.of(context).size.width * 0.2 : null, + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 35, + width: double.infinity, + child: DropdownSearch>( + popupProps: PopupProps.menu( + showSearchBox: false, + fit: FlexFit.loose, + menuProps: const MenuProps( + backgroundColor: Colors.white, + ), + itemBuilder: (context, item, isSelected) { + return Padding( + padding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + child: Text( + item['dropdown_value'] ?? '', + style: const TextStyle( + fontSize: 12, // 👈 Smaller font size + color: Colors.black, ), - 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, - ), - ], - ), + ), + ); + }, + ), + dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 5), ), ), - ); - }).toList(), + selectedItem: purposeList.cast>().firstWhere( + (item) => item['dropdown_key'] == _selectedIsBillable, + orElse: () => {}, + ), + dropdownButtonProps: const DropdownButtonProps( + icon: Icon(Icons.arrow_drop_down), + ), + // itemAsString: (item) => item['dropdown_value'] ?? '', + // items: purposeList.cast>(), + dropdownBuilder: (context, selectedItem) { + if (selectedItem == null || selectedItem.isEmpty) { + return const Text( + "Select Billable", // fallback text + style: TextStyle(fontSize: 12, color: Colors.grey), + ); + } + return Text( + selectedItem['dropdown_value'] ?? '', + style: const TextStyle( + fontSize: 12, // 👈 Small font size for selected item + color: Colors.black, + ), + ); + }, + + items: purposeList.map>((item) { + return Map.from( + item); // Ensuring each item is properly cast + }).toList(), + onChanged: widget.isViewMode + ? null + : (Map? newItem) { + if (newItem != null) { + setState(() { + _selectedIsBillable = + newItem['dropdown_key'].toString(); + }); + } + }, + ), + ), ), // 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 - // // ], - // // ); + // final isSelected = + // _selectedIsBillable == item['dropdown_key'].toString(); + // + // 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 + // : () { + // setState(() { + // _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, + // ), + // ], + // ), + // ), + // ), + // ); // }).toList(), - // - // // ), ]) ]; @@ -1693,16 +1857,35 @@ class CreateNewPlansState extends State { dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 1), - ), - ), - dropdownBuilder: (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem ?? "Select", - style: TextStyle(fontSize: 12), + // contentPadding: EdgeInsets.symmetric(horizontal: 1), + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 5), ), ), + + dropdownBuilder: (context, selectedItem) { + if (selectedItem == null || selectedItem.isEmpty) { + return const Text( + "Select Purpose", // fallback text + style: TextStyle(fontSize: 12, color: Colors.grey), + ); + } + return Text( + selectedItem ?? '', + style: const TextStyle( + fontSize: + 12, // 👈 Small font size for selected item + color: Colors.black, + ), + ); + }, + // dropdownBuilder: (context, selectedItem) => Align( + // alignment: Alignment.centerLeft, + // child: Text( + // selectedItem ?? "Select", + // style: TextStyle(fontSize: 12), + // ), + // ), onChanged: (String? newValue) { setState(() { selectedCostCenterId = costCenterMap.entries @@ -1807,9 +1990,8 @@ class CreateNewPlansState extends State { ), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( - contentPadding: EdgeInsets.symmetric( - horizontal: 1, - ), + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 5), border: InputBorder.none, ), ), @@ -1822,13 +2004,30 @@ class CreateNewPlansState extends State { }); print("selectedPurpose - $selectedPurpose"); }, - dropdownBuilder: (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem?['dropdown_value'] ?? '', - style: TextStyle(fontSize: 12), - ), - ), + + dropdownBuilder: (context, selectedItem) { + if (selectedItem == null || selectedItem.isEmpty) { + return const Text( + "Select Purpose", // fallback text + style: TextStyle(fontSize: 12, color: Colors.grey), + ); + } + return Text( + selectedItem['dropdown_value'] ?? '', + style: const TextStyle( + fontSize: + 12, // 👈 Small font size for selected item + color: Colors.black, + ), + ); + }, + // dropdownBuilder: (context, selectedItem) => Align( + // alignment: Alignment.centerLeft, + // child: Text( + // selectedItem?['dropdown_value'] ?? '', + // style: TextStyle(fontSize: 12), + // ), + // ), ), ), ), @@ -1933,7 +2132,8 @@ class CreateNewPlansState extends State { ), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( - contentPadding: EdgeInsets.symmetric(horizontal: 1), + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 5), border: InputBorder.none, ), ), @@ -1946,13 +2146,30 @@ class CreateNewPlansState extends State { }); print("selectedFuncDept - $selectedFuncDept"); }, - dropdownBuilder: (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem?['dropdown_value'] ?? '', - style: TextStyle(fontSize: 12), - ), - ), + + dropdownBuilder: (context, selectedItem) { + if (selectedItem == null || selectedItem.isEmpty) { + return const Text( + "Select Purpose", // fallback text + style: TextStyle(fontSize: 12, color: Colors.grey), + ); + } + return Text( + selectedItem['dropdown_value'] ?? '', + style: const TextStyle( + fontSize: + 12, // 👈 Small font size for selected item + color: Colors.black, + ), + ); + }, + // dropdownBuilder: (context, selectedItem) => Align( + // alignment: Alignment.centerLeft, + // child: Text( + // selectedItem?['dropdown_value'] ?? '', + // style: TextStyle(fontSize: 12), + // ), + // ), ), ), ), @@ -2241,15 +2458,16 @@ class CreateNewPlansState extends State { SizedBox(height: 5), CustomTextFieldWrapper( - isFocused: _isdescriptionFocused, + isFocused: false, + // isFocused: _isdescriptionFocused, isDesktop: widget.isDesktop, width: isDesktop - ? MediaQuery.of(context).size.width * 0.42 + ? MediaQuery.of(context).size.width * 0.74 : MediaQuery.of(context).size.width * 0.85, child: SizedBox( height: 35, child: TextField( - focusNode: _descriptionFocusNode, + // focusNode: _descriptionFocusNode, controller: _descriptionController, style: TextStyle(fontSize: 12), enabled: !widget.isViewMode, @@ -2348,6 +2566,31 @@ class CreateNewPlansState extends State { ]; } + Widget _buildTripName(bool isDesktop) { + return SizedBox( + width: isDesktop ? MediaQuery.of(context).size.width * 0.2 : 180, + height: 35, + child: TextField( + focusNode: _tripTitleFocusNode, + controller: _tripTitleController, + style: TextStyle(fontSize: 12), + enabled: !widget.isViewMode, + decoration: InputDecoration( + labelText: "Trip Name", + labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + errorText: validationErrors["trip_title"], // <--- add this line + errorStyle: TextStyle( + fontSize: 10, + color: Colors.red, + ), + ), + ), + ); + } + Widget _buildApprovalItem(String title, String name) { return Row( crossAxisAlignment: CrossAxisAlignment.start, @@ -2401,6 +2644,186 @@ class CreateNewPlansState extends State { ); return [ + // Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // Text( + // isApproverApproved + // ? "Approved" + // : isApproverRejected + // ? "Rejected" + // : (statusValue ?? ""), + // style: TextStyle( + // fontFamily: "Archivo", + // fontWeight: FontWeight.bold, + // color: widget.layoutColor ?? Colors.grey, + // ), + // ), + // SizedBox(width: 5), + // MouseRegion( + // onEnter: (_) => setState(() => isStatusExpanded = true), + // onExit: (_) => setState(() => isStatusExpanded = false), + // child: Icon( + // Icons.approval_outlined, + // size: 18, + // color: isStatusExpanded ? Colors.green : Colors.grey, + // ), + // ), + // ], + // ), + const SizedBox(height: 10, width: 10), + if (widget.isApprover) + GestureDetector( + // onTap: () { + // setState(() { + // isShowApprovalAction = !isShowApprovalAction; + // + // }); + // }, + onTap: () async { + final result = await showApprovalDialog( + context, widget.layoutColor ?? Colors.grey); + + if (result == 'approved') { + // User approved + setState(() { + isApproverApproved = true; + isApproverRejected = false; + statusValue = "Approved"; + }); + callApproveAPI(selectedPlanId!, planData['user_id']); + } else if (result is String) { + // User rejected with remarks + _remarksController.text = result; + setState(() { + isApproverApproved = false; + isApproverRejected = true; + statusValue = "Rejected"; + }); + callRejectAPI(selectedPlanId!, planData['user_id'], result); + } + }, + + child: Icon( + Icons.edit, + size: 18, + color: isShowApprovalAction ? Colors.green : Colors.grey, + ), + ), + // const SizedBox(height: 10, width: 4), + // if (isShowApprovalAction) + // Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // MouseRegion( + // cursor: widget.isViewMode + // ? SystemMouseCursors.forbidden + // : SystemMouseCursors.click, + // child: ElevatedButton( + // style: ElevatedButton.styleFrom( + // disabledBackgroundColor: + // statusValue == "Approved" ? Colors.green.shade100 : null, + // disabledForegroundColor: + // statusValue == "Approved" ? Colors.white : Colors.black, + // backgroundColor: isApproverApproved || + // (!isApproverApproved && + // !isApproverRejected && + // statusValue == "Approved") + // ? Colors.green + // : Colors.grey.shade100, + // foregroundColor: isApproverApproved || + // (!isApproverApproved && + // !isApproverRejected && + // statusValue == "Approved") + // ? Colors.white + // : Colors.black, + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(8), + // ), + // padding: EdgeInsets.symmetric(horizontal: 18, vertical: 10), + // ), + // onPressed: widget.isViewMode + // ? null + // : () async { + // final confirmed = await showApproveDialog( + // context, widget.layoutColor ?? Colors.grey); + // if (confirmed == true) { + // setState(() { + // isApproverApproved = true; + // isApproverRejected = false; + // statusValue = "Approved"; + // }); + // callApproveAPI( + // selectedPlanId!, + // planData['user_id'], + // ); + // } + // }, + // child: Text( + // isApproverApproved || statusValue == "Approved" + // ? "Approved" + // : "Approve", + // style: TextStyle(fontSize: 12), + // ), + // ), + // ), + // const SizedBox(width: 10), + // MouseRegion( + // cursor: widget.isViewMode + // ? SystemMouseCursors.forbidden + // : SystemMouseCursors.click, + // child: ElevatedButton( + // style: ElevatedButton.styleFrom( + // disabledBackgroundColor: statusValue == "Rejected" + // ? Colors.redAccent.shade100 + // : null, + // disabledForegroundColor: + // statusValue == "Rejected" ? Colors.white : Colors.black, + // backgroundColor: isApproverRejected || + // (!isApproverApproved && + // !isApproverRejected && + // statusValue == "Rejected") + // ? Colors.redAccent + // : Colors.grey.shade100, + // foregroundColor: isApproverRejected || + // (!isApproverApproved && + // !isApproverRejected && + // statusValue == "Rejected") + // ? Colors.white + // : Colors.black, + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(8), + // ), + // padding: EdgeInsets.symmetric(horizontal: 18, vertical: 10), + // ), + // 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); + // } + // }, + // child: Text( + // isApproverRejected || statusValue == "Rejected" + // ? "Rejected" + // : "Reject", + // style: TextStyle(fontSize: 12), + // ), + // ), + // ), + // ], + // ), + const SizedBox(height: 10, width: 10), + Stack( clipBehavior: Clip.none, // allow tooltip to overflow children: [ @@ -2418,7 +2841,7 @@ class CreateNewPlansState extends State { children: [ Padding( padding: const EdgeInsets.only( - top: 8.0, bottom: 8.0, left: 15, right: 15), + top: 6.0, bottom: 6.0, left: 15, right: 15), child: Text( statusText, style: TextStyle( @@ -2443,9 +2866,9 @@ class CreateNewPlansState extends State { // Tooltip if (isStatusExpanded) Positioned( - top: -20, // Ensure the tooltip is above the container - left: - MediaQuery.of(context).size.width * 0.06, // align with label + top: 45, // Ensure the tooltip is above the container + // left: + // MediaQuery.of(context).size.width * 0.06, // align with label child: Material( // important: avoid clipping, give elevation elevation: 4, @@ -2453,7 +2876,7 @@ class CreateNewPlansState extends State { child: SizedBox( child: Container( width: isDesktop - ? MediaQuery.of(context).size.width * 0.25 + ? MediaQuery.of(context).size.width * 0.2 : MediaQuery.of(context).size.width, padding: const EdgeInsets.all(12), decoration: BoxDecoration( @@ -2559,189 +2982,72 @@ class CreateNewPlansState extends State { // ), ], ), - - // Row( - // mainAxisSize: MainAxisSize.min, - // children: [ - // Text( - // isApproverApproved - // ? "Approved" - // : isApproverRejected - // ? "Rejected" - // : (statusValue ?? ""), - // style: TextStyle( - // fontFamily: "Archivo", - // fontWeight: FontWeight.bold, - // color: widget.layoutColor ?? Colors.grey, - // ), - // ), - // SizedBox(width: 5), - // MouseRegion( - // onEnter: (_) => setState(() => isStatusExpanded = true), - // onExit: (_) => setState(() => isStatusExpanded = false), - // child: Icon( - // Icons.approval_outlined, - // size: 18, - // color: isStatusExpanded ? Colors.green : Colors.grey, - // ), - // ), - // ], - // ), const SizedBox(height: 10, width: 10), - if (widget.isApprover) - GestureDetector( - onTap: () { - setState(() { - isShowApprovalAction = !isShowApprovalAction; - }); - }, - child: Icon( - Icons.edit, - size: 18, - color: isShowApprovalAction ? Colors.green : Colors.grey, - ), - ), - const SizedBox(height: 10, width: 10), - if (isShowApprovalAction) - Row( - mainAxisSize: MainAxisSize.min, - children: [ - MouseRegion( - cursor: widget.isViewMode - ? SystemMouseCursors.forbidden - : SystemMouseCursors.click, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - disabledBackgroundColor: - statusValue == "Approved" ? Colors.green.shade100 : null, - disabledForegroundColor: - statusValue == "Approved" ? Colors.white : Colors.black, - backgroundColor: isApproverApproved || - (!isApproverApproved && - !isApproverRejected && - statusValue == "Approved") - ? Colors.green - : Colors.grey.shade100, - foregroundColor: isApproverApproved || - (!isApproverApproved && - !isApproverRejected && - statusValue == "Approved") - ? Colors.white - : Colors.black, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - padding: EdgeInsets.symmetric(horizontal: 18, vertical: 10), - ), - onPressed: widget.isViewMode - ? null - : () async { - final confirmed = await showApproveDialog( - context, widget.layoutColor ?? Colors.grey); - if (confirmed == true) { - setState(() { - isApproverApproved = true; - isApproverRejected = false; - statusValue = "Approved"; - }); - callApproveAPI( - selectedPlanId!, - planData['user_id'], - ); - } - }, - child: Text( - isApproverApproved || statusValue == "Approved" - ? "Approved" - : "Approve", - style: TextStyle(fontSize: 12), - ), - ), - ), - const SizedBox(width: 10), - MouseRegion( - cursor: widget.isViewMode - ? SystemMouseCursors.forbidden - : SystemMouseCursors.click, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - disabledBackgroundColor: statusValue == "Rejected" - ? Colors.redAccent.shade100 - : null, - disabledForegroundColor: - statusValue == "Rejected" ? Colors.white : Colors.black, - backgroundColor: isApproverRejected || - (!isApproverApproved && - !isApproverRejected && - statusValue == "Rejected") - ? Colors.redAccent - : Colors.grey.shade100, - foregroundColor: isApproverRejected || - (!isApproverApproved && - !isApproverRejected && - statusValue == "Rejected") - ? Colors.white - : Colors.black, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - padding: EdgeInsets.symmetric(horizontal: 18, vertical: 10), - ), - 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); - } - }, - child: Text( - isApproverRejected || statusValue == "Rejected" - ? "Rejected" - : "Reject", - style: TextStyle(fontSize: 12), - ), - ), - ), - ], - ), ]; } - List _buildPlanPdf() { + List _buildPlanPdf(isDesktop) { return [ Column( children: [ - InkWell( - hoverColor: Colors.white, - onTap: () { - print('📄 PDF icon clicked!'); - getPdfDownload(); - }, - child: Row( - children: [ - Text("Download PDF"), - SizedBox( - width: 10, + 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: isDesktop + ? EdgeInsets.symmetric(horizontal: 20, vertical: 12) + : EdgeInsets.symmetric(horizontal: 15, vertical: 10), + ), + onPressed: () { + getPdfDownload(); + }, + child: Row( + mainAxisSize: MainAxisSize.min, // Ensures content fits nicely + children: [ + if (isDesktop) + Text( + "Download PDF", + style: TextStyle(fontSize: isDesktop ? 13 : 11), ), - Transform.scale( - scale: 1.5, // 1.0 = normal, 1.5 = 50% bigger - child: Image.asset( - 'assets/images/IconsImg/planPdf_icon.png', - width: 25, - height: 25, // keep the real height small - ), - ), - ], - )), + if (isDesktop) + SizedBox(width: 8), // spacing between icon and text + Icon( + Icons.download_rounded, + size: 15, + color: Colors.white, + ), + ], + ), + ), + + // InkWell( + // hoverColor: Colors.white, + // onTap: () { + // print('📄 PDF icon clicked!'); + // getPdfDownload(); + // }, + // child: Row( + // children: [ + // Text("Download PDF"), + // SizedBox( + // width: 10, + // ), + // Transform.scale( + // scale: 1.5, // 1.0 = normal, 1.5 = 50% bigger + // child: Image.asset( + // 'assets/images/IconsImg/planPdf_icon.png', + // width: 25, + // height: 25, // keep the real height small + // ), + // ), + // ], + // )), // SizedBox(height: 10), // small spacing ], ), @@ -2763,6 +3069,9 @@ class CreateNewPlansState extends State { print("USer entered : $otherUserName $userId $isTraveller"); getSelectedPlanFor(); }, + onClose: () { + getSelectedPlanFor(); + }, layoutColorForUser: widget.layoutColor!, ); }); diff --git a/lib/Screens/plans/list_plans.dart b/lib/Screens/plans/list_plans.dart index 1cf67b0..91d1a9f 100644 --- a/lib/Screens/plans/list_plans.dart +++ b/lib/Screens/plans/list_plans.dart @@ -318,7 +318,7 @@ class _ListPlansState extends State { String _formatDate(String rawDate) { try { final dateTime = DateTime.parse(rawDate); - return DateFormat('dd MMM yy hh:m a').format(dateTime); + return DateFormat('dd, MMM yyyy hh:m a').format(dateTime); } catch (e) { return rawDate; // fallback if parsing fails } @@ -338,7 +338,7 @@ class _ListPlansState extends State { // color: Color(0xFFF7F7FB), ) : null, - color: Colors.white, + color: isDesktop ? Colors.white : Color(0xFFFCFCFC), // color: Color(0xFFF7F7FB), // color: Colors.amber, @@ -347,7 +347,7 @@ class _ListPlansState extends State { padding: const EdgeInsets.all(1.0), child: Container( // padding: const EdgeInsets.all(10.0), - color: Colors.white, + color: isDesktop ? Colors.white : Color(0xFFFCFCFC), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -360,10 +360,10 @@ class _ListPlansState extends State { children: [ Row( children: [ - const Text('My Trip Request', + Text('My Trips', style: TextStyle( fontFamily: "Inter", - fontSize: 16, + fontSize: isDesktop ? 16 : 14, fontWeight: FontWeight.w600, color: Color(0xFF212121))), ], @@ -440,8 +440,9 @@ class _ListPlansState extends State { borderRadius: BorderRadius.circular(8), side: BorderSide(color: Color(0xFF114D8B), width: 2), ), - padding: - EdgeInsets.symmetric(horizontal: 20, vertical: 12), + padding: isDesktop + ? EdgeInsets.symmetric(horizontal: 20, vertical: 12) + : EdgeInsets.symmetric(horizontal: 15, vertical: 10), ), onPressed: () { if (TripPlanAction == "Plan Creation Not Allowed") { @@ -479,7 +480,7 @@ class _ListPlansState extends State { children: [ Text( "Add New Trip", - style: TextStyle(fontSize: 13), + style: TextStyle(fontSize: isDesktop ? 13 : 11), ), SizedBox(width: 8), // spacing between icon and text Icon( @@ -498,7 +499,7 @@ class _ListPlansState extends State { children: [ Container( width: MediaQuery.of(context).size.width * 0.85, - height: 40, + height: 35, child: TextField( controller: searchController, onChanged: filterPlans, @@ -896,7 +897,7 @@ class _ListPlansState extends State { style: TextStyle( color: getStatusTextColor( plan.statusValue), - fontSize: 12, + fontSize: 10, fontWeight: FontWeight.w600, ), ), @@ -908,64 +909,92 @@ class _ListPlansState extends State { ), ], ), - SizedBox(height: 8), - // Name - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - plan.employeeCode ?? 'No Data', - style: TextStyle( - fontSize: 12, fontFamily: "Inter"), - ), - Text( - plan.userName.isNotEmpty - ? plan.userName - : plan.travellerName, - style: TextStyle( - fontSize: 12, fontFamily: "Inter"), - ), - ], - ), - SizedBox(height: 5), + + SizedBox(height: 2), // Trip Id and Trip Name Row( children: [ - Expanded( - child: Text('${plan.tripTitle}', - style: TextStyle( - fontSize: 13, - fontFamily: "Inter", - fontWeight: FontWeight.bold)), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text(' ${plan.tripTitle}', + style: TextStyle( + fontSize: 12, + fontFamily: "Inter", + fontWeight: FontWeight.bold)), + ], ), ], ), SizedBox(height: 2), // Type and Created On Row( - mainAxisSize: MainAxisSize.min, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ - Expanded( - child: Text('${plan.tripType}', - style: TextStyle( - fontSize: 10, - color: Colors.black87, - fontFamily: "Inter", - )), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text(' ${plan.tripType}', + style: TextStyle( + fontSize: 9, + color: Colors.black87, + fontFamily: "Inter", + )), + ], ), - Expanded( - child: - Text('${_formatDate(plan.createdOn)}', - style: TextStyle( - fontSize: 10, - color: Colors.black87, - fontFamily: "Inter", - )), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text('${_formatDate(plan.createdOn)}', + style: TextStyle( + fontSize: 9, + color: Colors.black87, + fontFamily: "Inter", + )), + ], ), ], ), - + SizedBox(height: 3), + // Name + Row( + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + plan.userName.isNotEmpty + ? '${plan.userName}' + : '${plan.travellerName}', + style: TextStyle( + fontSize: 9, + fontFamily: "Inter", + color: Colors.black87), + ), + ], + ), + Spacer(), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + plan.employeeCode ?? 'No Data', + style: TextStyle( + fontSize: 9, + fontFamily: "Inter", + color: Colors.black87), + ), + ], + ), + ], + ), + // Actions // Actions ], ), diff --git a/lib/routes/custom_appBar.dart b/lib/routes/custom_appBar.dart index 0ac1e5e..74adb01 100644 --- a/lib/routes/custom_appBar.dart +++ b/lib/routes/custom_appBar.dart @@ -4,6 +4,8 @@ import 'package:go_router/go_router.dart'; import 'package:shared_preferences/shared_preferences.dart'; // don't forget import '../services/apiService.dart'; +enum TabSelection { allTrips, myTrips, myApprovals } + class CustomAppBar extends StatefulWidget implements PreferredSizeWidget { final bool isDesktop; final String? logoUrl; @@ -30,6 +32,8 @@ class CustomAppBar extends StatefulWidget implements PreferredSizeWidget { class _CustomAppBarState extends State { final ApiService apiService = ApiService(); + TabSelection selectedTab = TabSelection.allTrips; + String? token; Map? userData; Map? fetchedUserData; @@ -68,6 +72,17 @@ class _CustomAppBarState extends State { }); } + String getTabLabel(TabSelection tab) { + switch (tab) { + case TabSelection.allTrips: + return "All Trips"; + case TabSelection.myTrips: + return "My Trips"; + case TabSelection.myApprovals: + return "My Approvals"; + } + } + Future getToken() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString("auth_token"); @@ -144,6 +159,10 @@ class _CustomAppBarState extends State { surfaceTintColor: Colors.white, // elevation: 3, automaticallyImplyLeading: !widget.isDesktop, + iconTheme: IconThemeData( + color: layoutColor, // 👈 Set your desired icon color here + ), + titleSpacing: 0, title: !widget.isDesktop @@ -186,183 +205,221 @@ class _CustomAppBarState extends State { SizedBox( width: MediaQuery.of(context).size.width * 0.2, ), + // Container( + // width: MediaQuery.of(context).size.width * 0.35, + // // color: Colors.blueGrey, + // child: Row( + // // crossAxisAlignment: CrossAxisAlignment.start, + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // if (userData?["role"] == "Org Admin" || + // userData?["role"] == "Travel Admin") + // MouseRegion( + // cursor: SystemMouseCursors.click, + // onEnter: (_) { + // setState(() { + // // _myTravelRequestPadding = + // // EdgeInsets.symmetric(horizontal: 12, vertical: 78); + // // _myTravelRequestColor = + // // Color(0xFF114D8B); // Change color on hover + // }); + // }, + // onExit: (_) { + // setState(() { + // // _myTravelRequestPadding = EdgeInsets.symmetric( + // // horizontal: 8, vertical: 4); // Normal padding + // // _myTravelRequestColor = + // // Color(0xFF475569); // Revert color when hover ends + // }); + // }, + // child: InkWell( + // onTap: () { + // context.go('/listAllPlan'); + // }, + // child: Text( + // "All Trips", + // style: TextStyle( + // fontSize: 12, + // fontWeight: FontWeight.w600, + // // color: Color(0xFF475569), + // color: _myTravelRequestColor, + // fontFamily: "Inter", + // ), + // )), + // ), + // const SizedBox(width: 20), + // if (userDetails["role"] == "Travel Agent") + // MouseRegion( + // cursor: SystemMouseCursors.click, + // onEnter: (_) { + // setState(() { + // // _myTravelRequestPadding = + // // EdgeInsets.symmetric(horizontal: 12, vertical: 78); + // // _myTravelRequestColor = + // // Color(0xFF114D8B); // Change color on hover + // }); + // }, + // onExit: (_) { + // setState(() { + // // _myTravelRequestPadding = EdgeInsets.symmetric( + // // horizontal: 8, vertical: 4); // Normal padding + // // _myTravelRequestColor = + // // Color(0xFF475569); // Revert color when hover ends + // }); + // }, + // child: InkWell( + // onTap: () { + // context.go('/listTravelAgentPlan'); + // }, + // child: Text( + // "My Trips", + // style: TextStyle( + // fontSize: 12, + // fontWeight: FontWeight.w600, + // // color: Color(0xFF475569), + // color: _myTravelRequestColor, + // fontFamily: "Inter", + // ), + // )), + // ), + // if (userDetails["role"] != "Travel Agent") + // const SizedBox(width: 20), + // if (userDetails["role"] != "Travel Agent") + // MouseRegion( + // cursor: SystemMouseCursors.click, + // onEnter: (_) { + // setState(() { + // // _myTravelRequestPadding = + // // EdgeInsets.symmetric(horizontal: 12, vertical: 78); + // // _myTravelRequestColor = + // // Color(0xFF114D8B); // Change color on hover + // }); + // }, + // onExit: (_) { + // setState(() { + // // _myTravelRequestPadding = EdgeInsets.symmetric( + // // horizontal: 8, vertical: 4); // Normal padding + // // _myTravelRequestColor = + // // Color(0xFF475569); // Revert color when hover ends + // }); + // }, + // child: InkWell( + // onTap: () { + // context.go('/listPlan'); + // }, + // child: Text( + // "My Trips", + // style: TextStyle( + // fontSize: 12, + // fontWeight: FontWeight.w600, + // // color: Color(0xFF475569), + // color: _myTravelRequestColor, + // fontFamily: "Inter", + // ), + // )), + // ), + // const SizedBox(width: 20), + // if (userData?["role"] != "Travel Agent") + // MouseRegion( + // cursor: SystemMouseCursors.click, + // onEnter: (_) { + // setState(() { + // _myApprovalsColor = + // Color(0xFF114D8B); // Change color on hover + // }); + // }, + // onExit: (_) { + // setState(() { + // _myApprovalsColor = Color( + // 0xFF475569); // Revert color when hover ends + // }); + // }, + // child: InkWell( + // onTap: () { + // context.go('/ApprovalList'); + // }, + // child: Text( + // "My Approvals", + // style: TextStyle( + // fontSize: 12, + // fontWeight: FontWeight.w600, + // color: _myApprovalsColor, + // // color: Color(0xFF475569), + // fontFamily: "Inter", + // ), + // )), + // ), + // if (userData?["role"] == "Travel Agent") + // MouseRegion( + // cursor: SystemMouseCursors.click, + // onEnter: (_) { + // setState(() { + // _myApprovalsColor = + // Color(0xFF114D8B); // Change color on hover + // }); + // }, + // onExit: (_) { + // setState(() { + // _myApprovalsColor = Color( + // 0xFF475569); // Revert color when hover ends + // }); + // }, + // child: InkWell( + // onTap: () { + // context.go('/listTravelAgentPlan'); + // }, + // child: Text( + // "My Approvals", + // style: TextStyle( + // fontSize: 12, + // fontWeight: FontWeight.w600, + // color: _myApprovalsColor, + // // color: Color(0xFF475569), + // fontFamily: "Inter", + // ), + // )), + // ), + // ], + // ), + // ), + Container( width: MediaQuery.of(context).size.width * 0.35, - // color: Colors.blueGrey, child: Row( - // crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ if (userData?["role"] == "Org Admin" || userData?["role"] == "Travel Admin") - MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (_) { - setState(() { - // _myTravelRequestPadding = - // EdgeInsets.symmetric(horizontal: 12, vertical: 78); - // _myTravelRequestColor = - // Color(0xFF114D8B); // Change color on hover - }); - }, - onExit: (_) { - setState(() { - // _myTravelRequestPadding = EdgeInsets.symmetric( - // horizontal: 8, vertical: 4); // Normal padding - // _myTravelRequestColor = - // Color(0xFF475569); // Revert color when hover ends - }); - }, - child: InkWell( - onTap: () { - context.go('/listAllPlan'); - }, - child: Text( - "All Trips", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - // color: Color(0xFF475569), - color: _myTravelRequestColor, - fontFamily: "Inter", - ), - )), - ), + buildNavItem("All Trips", _myTravelRequestColor, + () => context.go('/listAllPlan'), + isSelected: selectedTab == "All Trips"), const SizedBox(width: 20), if (userDetails["role"] == "Travel Agent") - MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (_) { - setState(() { - // _myTravelRequestPadding = - // EdgeInsets.symmetric(horizontal: 12, vertical: 78); - // _myTravelRequestColor = - // Color(0xFF114D8B); // Change color on hover - }); - }, - onExit: (_) { - setState(() { - // _myTravelRequestPadding = EdgeInsets.symmetric( - // horizontal: 8, vertical: 4); // Normal padding - // _myTravelRequestColor = - // Color(0xFF475569); // Revert color when hover ends - }); - }, - child: InkWell( - onTap: () { - context.go('/listTravelAgentPlan'); - }, - child: Text( - "My Trips", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - // color: Color(0xFF475569), - color: _myTravelRequestColor, - fontFamily: "Inter", - ), - )), - ), - if (userDetails["role"] != "Travel Agent") + buildNavItem("My Trips", _myTravelRequestColor, + () => context.go('/listTravelAgentPlan'), + isSelected: selectedTab == "My Trips"), + if (userDetails["role"] != "Travel Agent") ...[ const SizedBox(width: 20), - if (userDetails["role"] != "Travel Agent") - MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (_) { - setState(() { - // _myTravelRequestPadding = - // EdgeInsets.symmetric(horizontal: 12, vertical: 78); - // _myTravelRequestColor = - // Color(0xFF114D8B); // Change color on hover - }); - }, - onExit: (_) { - setState(() { - // _myTravelRequestPadding = EdgeInsets.symmetric( - // horizontal: 8, vertical: 4); // Normal padding - // _myTravelRequestColor = - // Color(0xFF475569); // Revert color when hover ends - }); - }, - child: InkWell( - onTap: () { - context.go('/listPlan'); - }, - child: Text( - "My Trip Request", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - // color: Color(0xFF475569), - color: _myTravelRequestColor, - fontFamily: "Inter", - ), - )), - ), + buildNavItem("My Trips", _myTravelRequestColor, + () => context.go('/listPlan'), + isSelected: selectedTab == "My Trips"), + ], const SizedBox(width: 20), - if (userData?["role"] != "Travel Agent") - MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (_) { - setState(() { - _myApprovalsColor = - Color(0xFF114D8B); // Change color on hover - }); - }, - onExit: (_) { - setState(() { - _myApprovalsColor = Color( - 0xFF475569); // Revert color when hover ends - }); - }, - child: InkWell( - onTap: () { - context.go('/ApprovalList'); - }, - child: Text( - "My Approvals", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: _myApprovalsColor, - // color: Color(0xFF475569), - fontFamily: "Inter", - ), - )), - ), - if (userData?["role"] == "Travel Agent") - MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (_) { - setState(() { - _myApprovalsColor = - Color(0xFF114D8B); // Change color on hover - }); - }, - onExit: (_) { - setState(() { - _myApprovalsColor = Color( - 0xFF475569); // Revert color when hover ends - }); - }, - child: InkWell( - onTap: () { - context.go('/listTravelAgentPlan'); - }, - child: Text( - "My Approvals", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: _myApprovalsColor, - // color: Color(0xFF475569), - fontFamily: "Inter", - ), - )), - ), + buildNavItem("My Approvals", _myApprovalsColor, () { + if (userData?["role"] == "Travel Agent") { + isSelected: + selectedTab == "My Approvals"; + context.go('/listTravelAgentPlan'); + } else { + isSelected: + selectedTab == "My Approvals"; + context.go('/ApprovalList'); + } + }), ], ), ), + Spacer(), ], ), @@ -499,7 +556,8 @@ class _CustomAppBarState extends State { preferredSize: Size.fromHeight(1), child: Container( height: 1, - color: Colors.grey.shade200, // Set the color of the bottom border + // color: Colors.grey.shade200, // Set the color of the bottom border + color: layoutColor, // Set the color of the bottom border ), ), ); @@ -554,3 +612,35 @@ PopupMenuItem buildMenuItem(Map item) { ), ); } + +Widget buildNavItem(String label, Color color, VoidCallback onTap, + {bool isSelected = false}) { + return MouseRegion( + cursor: SystemMouseCursors.click, + child: InkWell( + onTap: onTap, + child: Container( + padding: EdgeInsets.only(bottom: 2), + decoration: isSelected + ? BoxDecoration( + border: Border( + bottom: BorderSide( + color: color, // underline color + width: 2, // underline thickness + ), + ), + ) + : null, // no underline when not selected + child: Text( + label, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: color, + fontFamily: "Inter", + ), + ), + ), + ), + ); +} diff --git a/lib/routes/custom_drawer.dart b/lib/routes/custom_drawer.dart index a22bb0c..b0b5146 100644 --- a/lib/routes/custom_drawer.dart +++ b/lib/routes/custom_drawer.dart @@ -175,13 +175,13 @@ class _CustomDrawerState extends State { _buildDrawerItem(context, Icons.request_page_outlined, 'All Trips', '/listAllPlan'), - if (userDetails["role"] != "Travel Agent") + if (userDetails["role"] == "Travel Agent") _buildDrawerItem(context, Icons.request_page_outlined, - 'My Approvals1', '/listTravelAgentPlan'), + 'My Approvals', '/listTravelAgentPlan'), if (userDetails["role"] != "Travel Agent") - _buildDrawerItem(context, Icons.request_page_outlined, - 'My Trip Request', '/listPlan'), + _buildDrawerItem(context, Icons.request_page_outlined, 'My Trips', + '/listPlan'), if (userDetails["role"] != "Travel Agent") _buildDrawerItem(context, Icons.assessment_outlined, diff --git a/lib/utils/pagination.dart b/lib/utils/pagination.dart index d5b4abf..56644fd 100644 --- a/lib/utils/pagination.dart +++ b/lib/utils/pagination.dart @@ -66,28 +66,41 @@ class PaginationControls extends StatelessWidget { onPressed: currentPage > 0 ? () => onPageChanged(currentPage - 1) : null, ), - Row( - children: List.generate( - totalPages, - (index) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: ElevatedButton( - onPressed: () => onPageChanged(index), - style: ElevatedButton.styleFrom( - backgroundColor: - currentPage == index ? activeColor : Colors.grey[300]!, - minimumSize: Size(30, 30), - padding: EdgeInsets.zero, - ), - child: Text( - '${index + 1}', - style: TextStyle( - fontSize: 12, - fontFamily: "Inter", - color: currentPage == index ? Colors.white : Colors.black, - ), - ), - ), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: List.generate( + totalPages, + (index) { + // Only show 2 pages: currentPage and currentPage+1 + if (index == currentPage || index == currentPage + 1) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: ElevatedButton( + onPressed: () => onPageChanged(index), + style: ElevatedButton.styleFrom( + backgroundColor: currentPage == index + ? activeColor + : Colors.grey[300]!, + minimumSize: Size(30, 30), + padding: EdgeInsets.zero, + ), + child: Text( + '${index + 1}', + style: TextStyle( + fontSize: 12, + fontFamily: "Inter", + color: currentPage == index + ? Colors.white + : Colors.black, + ), + ), + ), + ); + } else { + return SizedBox.shrink(); // Don't show other pages + } + }, ), ), ), diff --git a/lib/widgets/popup_listPlan_action.dart b/lib/widgets/popup_listPlan_action.dart index 7fc5b04..ab8cc42 100644 --- a/lib/widgets/popup_listPlan_action.dart +++ b/lib/widgets/popup_listPlan_action.dart @@ -28,14 +28,16 @@ class PlanPopupMenu extends StatelessWidget { icon: Icon( Icons.more_vert, color: Color(0xFF475569), - size: 20, + size: 15, ), itemBuilder: (context) => [ CustomPopupMenuEntry( child: Container( - padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8), + padding: EdgeInsets.symmetric(horizontal: 2, vertical: 4), + // padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8), child: Row( mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( icon: Icon(Icons.remove_red_eye, size: 18), diff --git a/pubspec.lock b/pubspec.lock index 9d45aca..f0b2fc9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -565,6 +565,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + super_tooltip: + dependency: "direct main" + description: + name: super_tooltip + sha256: "55c0764554c961846d489503b76a1fc33ec2120bd7407266b77a34abbf3ed471" + url: "https://pub.dev" + source: hosted + version: "2.0.9" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b0d328f..2a4fbaf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,6 +47,7 @@ dependencies: image_picker: ^1.1.2 web: ^1.1.0 universal_html: ^2.2.4 + super_tooltip: ^2.0.9 dev_dependencies: @@ -75,6 +76,7 @@ flutter: - assets/images/login/travelSpend_Logo.png - assets/images/login/TravelSpendsLogo1.png - assets/images/login/VectorG.png + - assets/images/login/microsoft.png - assets/images/IconsImg/delete.png - assets/images/IconsImg/edit.png - assets/images/IconsImg/planPdf_icon.png