From 0ec324bf03e13fbe64ffafec9fc658ea99c43fe9 Mon Sep 17 00:00:00 2001 From: venba-Inspriron-3558 Date: Thu, 17 Jul 2025 12:02:27 +0530 Subject: [PATCH] dashboard mobile --- lib/Screens/dashboard/status_dashboard.dart | 440 +++++++++++------- .../create_user/create_user.dart | 47 +- .../create_user/office_details.dart | 215 ++++++--- .../create_user/personal_details.dart | 260 +++++++---- .../create_user/traveller_details.dart | 24 +- 5 files changed, 647 insertions(+), 339 deletions(-) diff --git a/lib/Screens/dashboard/status_dashboard.dart b/lib/Screens/dashboard/status_dashboard.dart index 93ea009..2650814 100644 --- a/lib/Screens/dashboard/status_dashboard.dart +++ b/lib/Screens/dashboard/status_dashboard.dart @@ -30,6 +30,7 @@ class StatusDashboardState extends State { Map? apiData; String? organizationId; late bool _dialogShown = false; + bool isLoading = false; late StreamSubscription _popStateListener; String selectedView = 'Today'; String AccessKeyFlag = "Both"; @@ -148,6 +149,10 @@ class StatusDashboardState extends State { WidgetsBinding.instance.addPostFrameCallback((_) { checkbackbutton(); }); + setState(() { + isLoading = true; + + }); // If token exists, load the dashboard data loadDashboardData(); } @@ -157,6 +162,7 @@ class StatusDashboardState extends State { final data = await fetchStatusDashboard(); setState(() { apiData = data; + isLoading = false; }); } catch (e) { print("Error loading dashboard: $e"); @@ -484,37 +490,35 @@ class StatusDashboardState extends State { ); } - 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), + Widget statusCard(String label, int count, bool isDesktop) { + return Container( + width: isDesktop ? 120 : double.infinity, + 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: isDesktop ? 18 : 16, + color: Colors.black87, + fontWeight: FontWeight.bold, + ), ), - 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, - ), - ), - ], + const SizedBox(height: 5), + Text( + label, + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: isDesktop ? 12 : 11, + color: Colors.black87, + ), ), - ), - ], + ], + ), ); } @@ -725,152 +729,256 @@ class StatusDashboardState extends State { 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: 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( + padding: isDesktop + ? const EdgeInsets.symmetric(vertical: 5) + : EdgeInsets.zero, + child: Center( + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: 1200, + ), + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 15.0), // ✅ Scroll content padding + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + isLoading ? Center(child: CircularProgressIndicator()) : + LayoutBuilder( + builder: (context, constraints) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, 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( + // First Row: Toggle Buttons Row + Row( + mainAxisAlignment: MainAxisAlignment.end, children: [ - ...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), + 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 + isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + 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), ), ], ), - ); - }).toList(), + padding: const EdgeInsets.all(15), + child: Row( + children: [ + ...typeData.map((item) { + return Expanded( + child: Column( + children: [ + buildTopCard( + 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), + 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: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ...statusData.map((item) => statusCard(item['value'], item['count'], isDesktop)).toList(), + ], + ), + ], + ), + ), + ), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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: [ + ...typeData.map((item) { + return Expanded( + child: Column( + children: [ + buildTopCard( + color: item['value'] == "Domestic" + ? const Color(0xFF0DB04B) + : const Color(0xFF004A8E), + label: item['value'], + count: item['count'], + bgColor: item['value'] == "Domestic" + ? const Color(0xFFD6FBE4) + : const Color(0xFFD9E8FF), + ), + ], + ), + ); + }).toList(), + ], + ), + ), + const SizedBox(height: 20), + 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), + Wrap( + spacing: 10, + runSpacing: 10, + children: [ + ...statusData.map((item) => statusCard(item['value'], item['count'], isDesktop)).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)), - ], - ), - ], + const SizedBox(height: 20), + + // Third Row: Flight, Accommodation, Forex + isDesktop + ? 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)), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded(child: buildCategoryCard("Flight", Icons.flight, statusLabels, flightData)), + ], + ), + const SizedBox(height: 20), + Row( + children: [ + Expanded(child: buildCategoryCard("Accomodation", Icons.hotel_outlined, statusLabels, accommodationData)), + ], + ), + const SizedBox(height: 20), + Row( + children: [ + Expanded(child: buildCategoryCard("Forex", Icons.attach_money, statusLabels, forexData)), + ], + ), + const SizedBox(height: 20), + ], + ), + ], + ); + }, + ) , + ], + ), ), - ); - }, + ), + ), ), ), ), diff --git a/lib/Screens/userManagement/create_user/create_user.dart b/lib/Screens/userManagement/create_user/create_user.dart index 9fbcdfc..d56b564 100644 --- a/lib/Screens/userManagement/create_user/create_user.dart +++ b/lib/Screens/userManagement/create_user/create_user.dart @@ -11,6 +11,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:frontend/Screens/userManagement/create_user/personal_details.dart'; import 'package:frontend/Screens/userManagement/create_user/traveller_details.dart'; +import 'package:frontend/Screens/userManagement/create_user/office_details.dart'; import 'package:frontend/utils/auth_utils.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -30,7 +31,6 @@ import '../../../widgets/custom_text_field.dart'; import '../../../widgets/custom_text_forex.dart'; import '../../../widgets/custom_user_form.dart'; import '../../../widgets/saving_loader.dart'; -import 'office_details.dart'; import 'package:universal_html/html.dart' as html; @@ -46,6 +46,8 @@ class _CreateUserFormDetialsState extends State { GlobalKey(); final GlobalKey travellerDetailsKey = GlobalKey(); + final GlobalKey officeDetailsKey = + GlobalKey(); // late List?> travelDetailsData; Map? travelDetailsData; @@ -60,6 +62,9 @@ class _CreateUserFormDetialsState extends State { bool isCheckingToken = false; bool isDisable = false; + final FocusNode submitButtonFocusNode = FocusNode(); + + String? token; late bool isapiselectedUser = false; @@ -1448,7 +1453,6 @@ class _CreateUserFormDetialsState extends State { selectedRole = role; }); }, - onServiceIdsChanged: (List> updatedList) { setState(() { selectedServiceIds = updatedList; @@ -1755,26 +1759,29 @@ class _CreateUserFormDetialsState extends State { isViewMode ? SystemMouseCursors.forbidden : SystemMouseCursors.click, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: - isViewMode ? layoutColor : layoutColor, // Keep original color - foregroundColor: - isViewMode - ? Colors.white - : Colors.white, // Keep original color - disabledBackgroundColor: - layoutColor, // Ensure color remains when disabled - disabledForegroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: BorderSide(color: layoutColor, width: 2), + child: Focus( // 👈 Wrap with Focus here + focusNode: submitButtonFocusNode, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: + isViewMode ? layoutColor : layoutColor, // Keep original color + foregroundColor: + isViewMode + ? Colors.white + : Colors.white, // Keep original color + disabledBackgroundColor: + layoutColor, // Ensure color remains when disabled + disabledForegroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide(color: layoutColor, width: 2), + ), + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), + onPressed: + isViewMode ? null : handleSubmit, // Disable when in view mode + child: Text("Submit"), ), - onPressed: - isViewMode ? null : handleSubmit, // Disable when in view mode - child: Text("Submit"), ), ), ]; diff --git a/lib/Screens/userManagement/create_user/office_details.dart b/lib/Screens/userManagement/create_user/office_details.dart index e0f9845..b9c53b4 100644 --- a/lib/Screens/userManagement/create_user/office_details.dart +++ b/lib/Screens/userManagement/create_user/office_details.dart @@ -58,10 +58,10 @@ class OfficeDetails extends StatefulWidget { }) : super(key: key); @override - _OfficeDetailsState createState() => _OfficeDetailsState(); + OfficeDetailsState createState() => OfficeDetailsState(); } -class _OfficeDetailsState extends State { +class OfficeDetailsState extends State { final ApiService apiService = ApiService(); late Map countryMap; // Mapping country_code -> country_name @@ -169,6 +169,7 @@ class _OfficeDetailsState extends State { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { loadInitialData(); + _employeeCodeFocusNode.requestFocus(); }); apiCountryData = null; apiUserData = null; @@ -702,8 +703,8 @@ class _OfficeDetailsState extends State { borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: - (_departmentFocus ?? false) - ? (layoutColor ?? Colors.white) + (_departmentFocus) + ? (layoutColor) : Colors.white, width: 0.5, ), @@ -711,8 +712,8 @@ class _OfficeDetailsState extends State { enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: - (_departmentFocus ?? false) - ? (layoutColor ?? Colors.white) + (_departmentFocus) + ? (layoutColor) : Colors.white, width: 0.5, // const Color(0xFFD6D5E6), @@ -1868,39 +1869,83 @@ class _OfficeDetailsState extends State { isDesktop: widget.isDesktop, child: SizedBox( height: 40, - child: GestureDetector( - // onTap: () async{ - // _selectCheckOutDate(context); - // - // }, - onTap: () async { - _startDateFocusNode?.requestFocus(); - await _selectCheckOutDate(context); + child:Focus( + focusNode: _startDateFocusNode, + onKeyEvent: (FocusNode node, KeyEvent event) { + // Only act on KeyDown and when Enter is pressed + if (event is KeyDownEvent && + (event.logicalKey == LogicalKeyboardKey.enter || + event.logicalKey == LogicalKeyboardKey.numpadEnter)) { + _startDateFocusNode?.requestFocus(); + _selectCheckOutDate(context); + return KeyEventResult.handled; // Stop default behavior + } + return KeyEventResult.ignored; // Allow normal handling }, - child: AbsorbPointer( - child: TextField( - focusNode: _startDateFocusNode, - controller: widget.controllers["delegationStartDate"], - readOnly: true, - style: const TextStyle(fontSize: 12), - decoration: InputDecoration( - labelText: "Select Date", - labelStyle: const TextStyle( - fontSize: 12, - color: Colors.grey, - ), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: const EdgeInsets.symmetric(vertical: 16), - suffixIcon: const Icon( - Icons.calendar_today, - size: 16, - color: Colors.grey, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTapDown: (details) { + _startDateFocusNode?.requestFocus(); + _selectCheckOutDate(context); + }, + child: AbsorbPointer( + child: TextField( + controller: widget.controllers["delegationStartDate"], + readOnly: true, + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + decoration: InputDecoration( + labelText: "Select Date", + labelStyle: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.grey, + ), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + suffixIcon: Icon( + Icons.calendar_today, + size: 16, + color: Color(0xFF8B8FB2), + ), ), ), ), ), ), + // child: GestureDetector( + // // onTap: () async{ + // // _selectCheckOutDate(context); + // // + // // }, + // onTap: () async { + // _startDateFocusNode?.requestFocus(); + // await _selectCheckOutDate(context); + // }, + // child: AbsorbPointer( + // child: TextField( + // focusNode: _startDateFocusNode, + // controller: widget.controllers["delegationStartDate"], + // readOnly: true, + // style: const TextStyle(fontSize: 12), + // decoration: InputDecoration( + // labelText: "Select Date", + // labelStyle: const TextStyle( + // fontSize: 12, + // color: Colors.grey, + // ), + // floatingLabelBehavior: FloatingLabelBehavior.never, + // border: InputBorder.none, + // contentPadding: const EdgeInsets.symmetric(vertical: 16), + // suffixIcon: const Icon( + // Icons.calendar_today, + // size: 16, + // color: Colors.grey, + // ), + // ), + // ), + // ), + // ), ), ), // if (errorMessages["start_date"] != null) ...[ @@ -1975,39 +2020,89 @@ class _OfficeDetailsState extends State { isDesktop: widget.isDesktop, child: SizedBox( height: 40, - child: GestureDetector( - // onTap: () async{ - // _selectCheckOutDate(context); - // - // }, - onTap: () async { - _endDateFocusNode?.requestFocus(); - await _selectForexEndDate(context); + child:Focus( + focusNode: _endDateFocusNode, + onKeyEvent: (FocusNode node, KeyEvent event) { + // Only act on KeyDown and when Enter is pressed + if (event is KeyDownEvent && + (event.logicalKey == LogicalKeyboardKey.enter || + event.logicalKey == LogicalKeyboardKey.numpadEnter)) { + if (!widget.isViewMode) { + _selectForexEndDate(context).then((_) { + _endDateFocusNode?.requestFocus(); + }); + } + return KeyEventResult.handled; // Stop default behavior + } + return KeyEventResult.ignored; // Allow normal handling }, - child: AbsorbPointer( - child: TextField( - focusNode: _endDateFocusNode, - controller: widget.controllers["delegationEndDate"], - readOnly: true, - style: const TextStyle(fontSize: 12), - decoration: InputDecoration( - labelText: "Select Date", - labelStyle: const TextStyle( - fontSize: 12, - color: Colors.grey, - ), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: const EdgeInsets.symmetric(vertical: 16), - suffixIcon: const Icon( - Icons.calendar_today, - size: 16, - color: Colors.grey, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTapDown: (details) { + if (!widget.isViewMode) { + _selectForexEndDate(context).then((_) { + _endDateFocusNode?.requestFocus(); + }); + } + }, + child: AbsorbPointer( + child: TextField( + controller: widget.controllers["delegationEndDate"], + readOnly: true, + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + decoration: InputDecoration( + labelText: "Select Date", + labelStyle: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.grey, + ), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + suffixIcon: Icon( + Icons.calendar_today, + size: 16, + color: Color(0xFF8B8FB2), + ), ), ), ), ), ), + // child: GestureDetector( + // // onTap: () async{ + // // _selectCheckOutDate(context); + // // + // // }, + // onTap: () async { + // _endDateFocusNode?.requestFocus(); + // await _selectForexEndDate(context); + // }, + // child: AbsorbPointer( + // child: TextField( + // focusNode: _endDateFocusNode, + // controller: widget.controllers["delegationEndDate"], + // readOnly: true, + // style: const TextStyle(fontSize: 12), + // decoration: InputDecoration( + // labelText: "Select Date", + // labelStyle: const TextStyle( + // fontSize: 12, + // color: Colors.grey, + // ), + // floatingLabelBehavior: FloatingLabelBehavior.never, + // border: InputBorder.none, + // contentPadding: const EdgeInsets.symmetric(vertical: 16), + // suffixIcon: const Icon( + // Icons.calendar_today, + // size: 16, + // color: Colors.grey, + // ), + // ), + // ), + // ), + // ), ), ), diff --git a/lib/Screens/userManagement/create_user/personal_details.dart b/lib/Screens/userManagement/create_user/personal_details.dart index 9873e62..7de3dbf 100644 --- a/lib/Screens/userManagement/create_user/personal_details.dart +++ b/lib/Screens/userManagement/create_user/personal_details.dart @@ -35,6 +35,7 @@ class PersonalDetails extends StatefulWidget { final String? selectedCountry; final String? selectedRole; final String? userIdApi; + final dynamic focusNodes; // const PersonalDetails(this.isDesktop, this.isViewMode, {super.key},); const PersonalDetails({ @@ -50,6 +51,7 @@ class PersonalDetails extends StatefulWidget { required this.isDesktop, required this.isViewMode, required this.apiselectedUser, + this.focusNodes, this.onGenderChanged, this.onCountryChanged, this.onRoleChanged, @@ -75,8 +77,32 @@ class PersonalDetailsState extends State { String? get selectedGenderValue => selectedGender; String? get selectedCountryValue => selectedCountry; String? get selectedRoleValue => selectedRole; - Map focusNodes = {}; - Map focusStates = {}; + + final FocusNode _fnameFocusNode = FocusNode(); + final FocusNode _lnameFocusNode = FocusNode(); + final FocusNode _emailFocusNode = FocusNode(); + final FocusNode _passwordFocusNode = FocusNode(); + final FocusNode _dobFocusNode = FocusNode(); + final FocusNode _genderFocusNode = FocusNode(); + final FocusNode _mobileNumberFocusNode = FocusNode(); + final FocusNode _alternateMobileFocusNode = FocusNode(); + final FocusNode _addressFocusNode = FocusNode(); + final FocusNode _countryFocusNode = FocusNode(); + final FocusNode _postalCodeFocusNode = FocusNode(); + final FocusNode _roleIdFocusNode = FocusNode(); + bool _fnameFocus = false; + bool _lnameFocus = false; + bool _emailFocus = false; + bool _passwordFocus = false; + bool _dobFocus = false; + bool _genderFocus = false; + bool _mobileNumberFocus = false; + bool _alternateMobileFocus = false; + bool _addressFocus = false; + bool _countryFocus = false; + bool _postalCodeFocus = false; + bool _roleIdFocus = false; + late bool isTravelAgent = false; @@ -154,23 +180,26 @@ class PersonalDetailsState extends State { print("selectedServiceIdsAPI - $selectedServiceIds"); - for (var field in dataHeader) { - focusNodes["${field}FocusNode"] = FocusNode(); - focusStates["${field}Focused"] = false; - } - for (var key in focusNodes.keys) { - _addFocusListener(focusNodes[key]!, (focus) { - setState(() { - focusStates[key.replaceFirst("FocusNode", "Focused")] = focus; - }); - }); - } + _addFocusListener(_fnameFocusNode, (focus) => _fnameFocus = focus); + _addFocusListener(_lnameFocusNode, (focus) => _lnameFocus = focus); + _addFocusListener(_emailFocusNode, (focus) => _emailFocus = focus); + _addFocusListener(_passwordFocusNode, (focus) => _passwordFocus = focus); + _addFocusListener(_dobFocusNode, (focus) => _dobFocus = focus); + _addFocusListener(_genderFocusNode, (focus) => _genderFocus = focus); + _addFocusListener(_mobileNumberFocusNode, (focus) => _mobileNumberFocus = focus); + _addFocusListener(_alternateMobileFocusNode, (focus) => _alternateMobileFocus = focus); + _addFocusListener(_addressFocusNode, (focus) => _addressFocus = focus); + _addFocusListener(_countryFocusNode, (focus) => _countryFocus = focus); + _addFocusListener(_postalCodeFocusNode, (focus) => _postalCodeFocus = focus); + _addFocusListener(_roleIdFocusNode, (focus) => _roleIdFocus = focus); + WidgetsBinding.instance.addPostFrameCallback((_) { loadAllServices(); getOrganizationData(); loadInitialData(); + _fnameFocusNode.requestFocus(); }); } @@ -802,13 +831,14 @@ class PersonalDetailsState extends State { ), SizedBox(height: 5), CustomTextFieldUserWrapper( - isFocused: focusStates["FnameFocused"] ?? false, + isFocused: _fnameFocus, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), - focusNode: focusNodes["FnameFocusNode"], + // focusNode: focusNodes["FnameFocusNode"], + focusNode: _fnameFocusNode, // 👈 Use it here controller: widget.controllers["Fname"], autofocus: true, enabled: !widget.isViewMode, @@ -854,13 +884,13 @@ class PersonalDetailsState extends State { ), SizedBox(height: 5), CustomTextFieldUserWrapper( - isFocused: focusStates["LnameFocused"] ?? false, + isFocused: _lnameFocus, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12, color: Colors.black), - focusNode: focusNodes["LnameFocusNode"], + focusNode: _lnameFocusNode, autofocus: true, controller: widget.controllers["Lname"], enabled: !widget.isViewMode, @@ -1006,21 +1036,21 @@ class PersonalDetailsState extends State { widget.isDesktop ? MediaQuery.of(context).size.width * 0.12 : null, - isFocused: focusStates["genderFocused"] ?? false, + isFocused: _genderFocus, padding: const EdgeInsets.symmetric(horizontal: 0), isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: Focus( - focusNode: focusNodes["genderFocusNode"], + focusNode: _genderFocusNode, onFocusChange: (hasFocus) { setState(() { - focusStates["genderFocused"] = hasFocus; + _genderFocus = hasFocus; }); }, child: GestureDetector( onTap: () { - focusNodes["genderFocusNode"]?.requestFocus(); + _genderFocusNode?.requestFocus(); }, child: DropdownSearch( selectedItem: selectedGender, @@ -1051,7 +1081,7 @@ class PersonalDetailsState extends State { borderRadius: BorderRadius.circular(5), borderSide: BorderSide( color: - (focusStates["genderFocused"] ?? false) + (_genderFocus) ? (layoutColor ?? Colors.blue) : Colors.white, width: 0.5, @@ -1060,7 +1090,7 @@ class PersonalDetailsState extends State { enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: - (focusStates["genderFocused"] ?? false) + (_genderFocus) ? (layoutColor ?? Colors.blue) : Colors.white, // : const Color(0xFFD6D5E6), @@ -1069,7 +1099,8 @@ class PersonalDetailsState extends State { ), ), focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color : (layoutColor ?? Colors.grey), width: 0.5), + borderSide: BorderSide( + color : (layoutColor ?? Colors.grey), width: 0.5), ), contentPadding: EdgeInsets.symmetric( horizontal: 10, @@ -1152,51 +1183,110 @@ class PersonalDetailsState extends State { widget.isDesktop ? MediaQuery.of(context).size.width * 0.12 : null, - isFocused: focusStates["dobFocused"] ?? false, + isFocused: _dobFocus, isDesktop: widget.isDesktop, child: SizedBox( height: 40, - child: GestureDetector( - onTap: - widget.isViewMode - ? null - : () async { - await _selectCheckDateOfBirth(context); - if (widget.controllers["dob"]!.text.isNotEmpty) { - setState(() { - // errorMessages.remove("start_date"); - }); - // Delay re-focusing the DOB field slightly - Future.delayed(Duration(milliseconds: 100), () { - focusNodes["dobFocusNode"]?.requestFocus(); - }); - } - }, - child: AbsorbPointer( - child: TextField( - controller: widget.controllers["dob"], - focusNode: focusNodes["dobFocusNode"], - readOnly: true, - style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), - decoration: InputDecoration( - labelText: "Select Date", - labelStyle: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.grey, - ), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), - suffixIcon: Icon( - Icons.calendar_today, - size: 16, - color: Color(0xFF8B8FB2), + child: + Focus( + focusNode: _dobFocusNode, + onKeyEvent: (FocusNode node, KeyEvent event) { + // Only act on KeyDown and when Enter is pressed + if (event is KeyDownEvent && + (event.logicalKey == LogicalKeyboardKey.enter || + event.logicalKey == LogicalKeyboardKey.numpadEnter)) { + if (!widget.isViewMode) { + _selectCheckDateOfBirth(context).then((_) { + if (widget.controllers["dob"]!.text.isNotEmpty) { + Future.delayed(Duration(milliseconds: 100), () { + _dobFocusNode?.requestFocus(); + }); + } + }); + } + return KeyEventResult.handled; // Stop default behavior + } + return KeyEventResult.ignored; // Allow normal handling + }, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTapDown: (details) { + if (!widget.isViewMode) { + _selectCheckDateOfBirth(context).then((_) { + if (widget.controllers["dob"]!.text.isNotEmpty) { + Future.delayed(Duration(milliseconds: 100), () { + _dobFocusNode?.requestFocus(); + }); + } + }); + } + }, + child: AbsorbPointer( + child: TextField( + controller: widget.controllers["dob"], + readOnly: true, + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + decoration: InputDecoration( + labelText: "Select Date", + labelStyle: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.grey, + ), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + suffixIcon: Icon( + Icons.calendar_today, + size: 16, + color: Color(0xFF8B8FB2), + ), ), ), ), ), ), + // GestureDetector( + // onTap: + // widget.isViewMode + // ? null + // : () async { + // await _selectCheckDateOfBirth(context); + // if (widget.controllers["dob"]!.text.isNotEmpty) { + // setState(() { + // // errorMessages.remove("start_date"); + // }); + // // Delay re-focusing the DOB field slightly + // Future.delayed(Duration(milliseconds: 100), () { + // _dobFocusNode?.requestFocus(); + // }); + // } + // }, + // child: AbsorbPointer( + // child: TextField( + // controller: widget.controllers["dob"], + // focusNode:_dobFocusNode, + // readOnly: true, + // style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + // decoration: InputDecoration( + // labelText: "Select Date", + // labelStyle: GoogleFonts.poppins( + // fontSize: 12, + // fontWeight: FontWeight.w400, + // color: Colors.grey, + // ), + // floatingLabelBehavior: FloatingLabelBehavior.never, + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric(vertical: 16), + // suffixIcon: Icon( + // Icons.calendar_today, + // size: 16, + // color: Color(0xFF8B8FB2), + // ), + // ), + // ), + // ), + // ), ), ), ], @@ -1217,13 +1307,13 @@ class PersonalDetailsState extends State { ), SizedBox(height: 5), CustomTextFieldUserWrapper( - isFocused: focusStates["emailFocused"] ?? false, + isFocused: _emailFocus, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), - focusNode: focusNodes["emailFocusNode"], + focusNode: _emailFocusNode, autofocus: true, controller: widget.controllers["email"], onChanged: (value) { @@ -1269,13 +1359,13 @@ class PersonalDetailsState extends State { ), SizedBox(height: 5), CustomTextFieldUserWrapper( - isFocused: focusStates["mobileNumberFocused"] ?? false, + isFocused: _mobileNumberFocus, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), - focusNode: focusNodes["mobileNumberFocusNode"], + focusNode: _mobileNumberFocusNode, autofocus: true, controller: widget.controllers["mobileNumber"], enabled: !widget.isViewMode, @@ -1332,13 +1422,13 @@ class PersonalDetailsState extends State { ), SizedBox(height: 5), CustomTextFieldUserWrapper( - isFocused: focusStates["alternateMobileFocused"] ?? false, + isFocused: _alternateMobileFocus, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), - focusNode: focusNodes["alternateMobileFocusNode"], + focusNode: _alternateMobileFocusNode, autofocus: true, controller: widget.controllers["alternateMobile"], enabled: !widget.isViewMode, @@ -1408,23 +1498,23 @@ class PersonalDetailsState extends State { ), SizedBox(height: 5), CustomTextFieldUserWrapper( - isFocused: focusStates["countryFocused"] ?? false, + isFocused: _countryFocus, padding: const EdgeInsets.symmetric(horizontal: 0), isDesktop: widget.isDesktop, child: SizedBox( height: 40, width: double.infinity, child: Focus( - focusNode: focusNodes["countryFocusNode"], + focusNode: _countryFocusNode, onFocusChange: (hasFocus) { setState(() { - focusStates["countryFocused"] = hasFocus; + _countryFocus = hasFocus; }); }, child: GestureDetector( onTap: () { // Request focus when user taps - focusNodes["countryFocusNode"]?.requestFocus(); + _countryFocusNode?.requestFocus(); }, child: DropdownSearch( selectedItem: countryMap[selectedCountry], @@ -1461,7 +1551,7 @@ class PersonalDetailsState extends State { borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: - (focusStates["countryFocused"] ?? false) + (_countryFocus) ? (layoutColor ?? Colors.blue) : Colors.white, width: 0.5, @@ -1470,7 +1560,7 @@ class PersonalDetailsState extends State { enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: - (focusStates["countryFocused"] ?? false) + (_countryFocus) ? (layoutColor ?? Colors.blue) : Colors.white, width: 0.5, @@ -1552,14 +1642,14 @@ class PersonalDetailsState extends State { ), SizedBox(height: 5), CustomTextFieldUserWrapper( - isFocused: focusStates["postalCodeFocused"] ?? false, + isFocused: _postalCodeFocus, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( autofocus: true, style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), - focusNode: focusNodes["postalCodeFocusNode"], + focusNode: _postalCodeFocusNode, controller: widget.controllers["postalCode"], enabled: !widget.isViewMode, keyboardType: TextInputType.numberWithOptions(decimal: true), @@ -1673,22 +1763,22 @@ class PersonalDetailsState extends State { SizedBox(height: 5), CustomTextFieldUserWrapper( // isFocused: false, - isFocused: focusStates["roleIdFocusNode"] ?? false, + isFocused: _roleIdFocus, padding: const EdgeInsets.symmetric(horizontal: 0), isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: Focus( - focusNode: focusNodes["roleIdFocusNode"], + focusNode: _roleIdFocusNode, onFocusChange: (hasFocus) { setState(() { - focusStates["roleIdFocused"] = hasFocus; + _roleIdFocus = hasFocus; }); }, child: GestureDetector( onTap: () { // Request focus when user taps - focusNodes["roleIdFocusNode"]?.requestFocus(); + _roleIdFocusNode?.requestFocus(); }, child: DropdownSearch( selectedItem: selectedRole != null ? roleMap[selectedRole] : null, @@ -1726,7 +1816,7 @@ class PersonalDetailsState extends State { borderRadius: BorderRadius.circular(5), borderSide: BorderSide( color: - (focusStates["roleIdFocused"] ?? false) + (_roleIdFocus) ? (layoutColor ?? Colors.blue) : Colors.white, width: 0.5, @@ -1735,7 +1825,7 @@ class PersonalDetailsState extends State { enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: - (focusStates["roleIdFocused"] ?? false) + (_roleIdFocus) ? (layoutColor ?? Colors.blue) : Colors.white, // : const Color(0xFFD6D5E6), @@ -1810,14 +1900,14 @@ class PersonalDetailsState extends State { ), SizedBox(height: 5), CustomTextFieldUserWrapper( - isFocused: focusStates["addressFocused"] ?? false, + isFocused: _addressFocus, isDesktop: widget.isDesktop, child: SizedBox( height: 80, child: TextField( autofocus: true, style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), - focusNode: focusNodes["addressFocusNode"], + focusNode: _addressFocusNode, controller: widget.controllers["address"], enabled: !widget.isViewMode, maxLines: 2, @@ -1854,13 +1944,13 @@ class PersonalDetailsState extends State { ), SizedBox(height: 5), CustomTextFieldUserWrapper( - isFocused: focusStates["passwordFocused"] ?? false, + isFocused: _passwordFocus, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), - focusNode: focusNodes["passwordFocusNode"], + focusNode: _passwordFocusNode, autofocus: true, controller: widget.controllers["password"], enabled: !widget.isViewMode, diff --git a/lib/Screens/userManagement/create_user/traveller_details.dart b/lib/Screens/userManagement/create_user/traveller_details.dart index 0199a20..9ac2b8d 100644 --- a/lib/Screens/userManagement/create_user/traveller_details.dart +++ b/lib/Screens/userManagement/create_user/traveller_details.dart @@ -169,6 +169,7 @@ class TravellerDetailsState extends State { fetchApiData(); // Delay adding the row until after the first frame WidgetsBinding.instance.addPostFrameCallback((_) { + // focusNodes["FnameFocusNode"]?.requestFocus(); setState(() { addHotelLoyaltyEntry(); addFrequentFlierEntry(); @@ -176,6 +177,13 @@ class TravellerDetailsState extends State { loadInitialData(); }); updateTravel(); + print("Trying to focus Fname..."); + + final node = focusNodes["FnameFocusNode"]; + if (node != null) { + print("Node exists, requesting focus"); + node.requestFocus(); + } }); _addFocusListener(_iSeatPrefFocusNode, (focus) => _iSeatPrefFocus = focus); @@ -1200,8 +1208,8 @@ class TravellerDetailsState extends State { "First Name", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -1244,8 +1252,8 @@ class TravellerDetailsState extends State { "Middle Name", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -1287,8 +1295,8 @@ class TravellerDetailsState extends State { "Last Name", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -1339,8 +1347,8 @@ class TravellerDetailsState extends State { "Nationality", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5),