From 60bc53afefb3f6b6147391f4f87b91c167123ddf Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Tue, 25 Nov 2025 18:13:22 +0530 Subject: [PATCH] refactor: remove project selection from layout and update dashboard layout --- lib/view/dashboard/dashboard_screen.dart | 204 ++++++++++- lib/view/layouts/layout.dart | 444 ++++++----------------- 2 files changed, 308 insertions(+), 340 deletions(-) diff --git a/lib/view/dashboard/dashboard_screen.dart b/lib/view/dashboard/dashboard_screen.dart index 59c8e95..f475dc4 100644 --- a/lib/view/dashboard/dashboard_screen.dart +++ b/lib/view/dashboard/dashboard_screen.dart @@ -48,13 +48,16 @@ class _DashboardScreenState extends State with UIMixin { Widget build(BuildContext context) { return Layout( child: SingleChildScrollView( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildDashboardCards(), MySpacing.height(24), + _buildProjectSelector(), + MySpacing.height(24), _buildAttendanceChartSection(), + MySpacing.height(12), MySpacing.height(24), _buildProjectProgressChartSection(), MySpacing.height(24), @@ -286,6 +289,205 @@ class _DashboardScreenState extends State with UIMixin { ); }); } + + /// ---------------- Project Selector (Inserted between Attendance & Project Progress) + Widget _buildProjectSelector() { + return Obx(() { + final isLoading = projectController.isLoading.value; + final isExpanded = projectController.isProjectSelectionExpanded.value; + final projects = projectController.projects; + final selectedProjectId = projectController.selectedProjectId.value; + final hasProjects = projects.isNotEmpty; + + if (isLoading) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 12), + child: Center(child: CircularProgressIndicator(strokeWidth: 2)), + ); + } + + if (!hasProjects) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Row( + children: const [ + Icon(Icons.warning_amber_outlined, color: Colors.redAccent), + SizedBox(width: 8), + Text( + "No Project Assigned", + style: TextStyle( + color: Colors.redAccent, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ); + } + + final selectedProject = + projects.firstWhereOrNull((p) => p.id == selectedProjectId); + + final searchNotifier = ValueNotifier(""); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + onTap: () => projectController.isProjectSelectionExpanded.toggle(), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + border: Border.all(color: Colors.grey.withOpacity(0.15)), + color: Colors.white, + boxShadow: const [ + BoxShadow( + color: Colors.black12, + blurRadius: 1, + offset: Offset(0, 1)) + ], + ), + child: Row( + children: [ + const Icon(Icons.work_outline, + size: 18, color: Colors.blueAccent), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + selectedProject?.name ?? "Select Project", + style: const TextStyle( + fontSize: 14, fontWeight: FontWeight.w700), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 2), + Text( + "Tap to switch project (${projects.length})", + style: const TextStyle( + fontSize: 12, color: Colors.black54), + ), + ], + ), + ), + Icon( + isExpanded + ? Icons.arrow_drop_up_outlined + : Icons.arrow_drop_down_outlined, + color: Colors.black, + ), + ], + ), + ), + ), + if (isExpanded) + ValueListenableBuilder( + valueListenable: searchNotifier, + builder: (context, query, _) { + final lowerQuery = query.toLowerCase(); + final filteredProjects = lowerQuery.isEmpty + ? projects + : projects + .where((p) => p.name.toLowerCase().contains(lowerQuery)) + .toList(); + + return Container( + margin: const EdgeInsets.only(top: 8), + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + border: Border.all(color: Colors.grey.withOpacity(0.12)), + boxShadow: const [ + BoxShadow( + color: Colors.black12, + blurRadius: 4, + offset: Offset(0, 2)) + ], + ), + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.35), + child: Column( + children: [ + TextField( + decoration: InputDecoration( + isDense: true, + prefixIcon: const Icon(Icons.search, size: 18), + hintText: "Search project", + hintStyle: const TextStyle(fontSize: 13), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5)), + contentPadding: const EdgeInsets.symmetric( + horizontal: 10, vertical: 2), + ), + onChanged: (value) => searchNotifier.value = value, + ), + const SizedBox(height: 8), + if (filteredProjects.isEmpty) + const Expanded( + child: Center( + child: Text("No projects found", + style: TextStyle( + fontSize: 13, color: Colors.black54)), + ), + ) + else + Expanded( + child: ListView.builder( + shrinkWrap: true, + itemCount: filteredProjects.length, + itemBuilder: (context, index) { + final project = filteredProjects[index]; + final isSelected = + project.id == selectedProjectId; + return RadioListTile( + value: project.id, + groupValue: selectedProjectId, + onChanged: (value) { + if (value != null) { + projectController + .updateSelectedProject(value); + projectController.isProjectSelectionExpanded + .value = false; + } + }, + title: Text( + project.name, + style: TextStyle( + fontWeight: isSelected + ? FontWeight.bold + : FontWeight.normal, + color: isSelected + ? Colors.blueAccent + : Colors.black87, + ), + ), + contentPadding: + const EdgeInsets.symmetric(horizontal: 0), + activeColor: Colors.blueAccent, + tileColor: isSelected + ? Colors.blueAccent.withOpacity(0.06) + : Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5)), + visualDensity: + const VisualDensity(vertical: -4), + ); + }, + ), + ), + ], + ), + ); + }, + ), + ], + ); + }); + } } /// ---------------- Dashboard Card Models ---------------- diff --git a/lib/view/layouts/layout.dart b/lib/view/layouts/layout.dart index 2aaf940..d266ef3 100644 --- a/lib/view/layouts/layout.dart +++ b/lib/view/layouts/layout.dart @@ -7,8 +7,8 @@ import 'package:on_field_work/helpers/services/storage/local_storage.dart'; import 'package:on_field_work/model/employees/employee_info.dart'; import 'package:on_field_work/helpers/services/api_endpoints.dart'; import 'package:on_field_work/images.dart'; -import 'package:on_field_work/controller/project_controller.dart'; import 'package:on_field_work/view/layouts/user_profile_right_bar.dart'; +import 'package:on_field_work/helpers/services/tenant_service.dart'; class Layout extends StatefulWidget { final Widget? child; @@ -24,7 +24,6 @@ class _LayoutState extends State { final LayoutController controller = LayoutController(); final EmployeeInfo? employeeInfo = LocalStorage.getEmployeeInfo(); final bool isBetaEnvironment = ApiEndpoints.baseUrl.contains("stage"); - final projectController = Get.find(); bool hasMpin = true; @@ -65,27 +64,18 @@ class _LayoutState extends State { body: SafeArea( child: GestureDetector( behavior: HitTestBehavior.translucent, - onTap: () { - if (projectController.isProjectSelectionExpanded.value) { - projectController.isProjectSelectionExpanded.value = false; - } - }, - child: Stack( + onTap: () {}, // project selection removed → nothing to close + child: Column( children: [ - Column( - children: [ - _buildHeader(context, isMobile), - Expanded( - child: SingleChildScrollView( - key: controller.scrollKey, - padding: EdgeInsets.symmetric( - horizontal: 0, vertical: isMobile ? 16 : 32), - child: widget.child, - ), - ), - ], + _buildHeader(context, isMobile), + Expanded( + child: SingleChildScrollView( + key: controller.scrollKey, + padding: EdgeInsets.symmetric( + horizontal: 0, vertical: isMobile ? 16 : 32), + child: widget.child, + ), ), - _buildProjectDropdown(context, isMobile), ], ), ), @@ -93,339 +83,115 @@ class _LayoutState extends State { ); } - /// Header Section + /// Header Section (Project selection removed) Widget _buildHeader(BuildContext context, bool isMobile) { + final selectedTenant = TenantService.currentTenant; + return Padding( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), - child: Obx(() { - final isLoading = projectController.isLoading.value; - - if (isLoading) { - return _buildLoadingSkeleton(); - } - - final isExpanded = projectController.isProjectSelectionExpanded.value; - final selectedProjectId = projectController.selectedProjectId.value; - final selectedProject = projectController.projects.firstWhereOrNull( - (p) => p.id == selectedProjectId, - ); - - final hasProjects = projectController.projects.isNotEmpty; - - if (!hasProjects) { - projectController.selectedProjectId.value = ''; - } else if (selectedProject == null) { - projectController - .updateSelectedProject(projectController.projects.first.id); - } - - return Card( - elevation: 4, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5), - ), - margin: EdgeInsets.zero, - clipBehavior: Clip.antiAlias, - child: Stack( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 0), + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + margin: EdgeInsets.zero, + clipBehavior: Clip.antiAlias, + child: Padding( + padding: const EdgeInsets.all(10), + child: Row( children: [ - Padding( - padding: const EdgeInsets.all(10), - child: Row( + ClipRRect( + child: Stack( + clipBehavior: Clip.none, children: [ - ClipRRect( - borderRadius: BorderRadius.circular(5), - child: Stack( - clipBehavior: Clip.none, - children: [ - Image.asset( - Images.logoDark, - height: 50, - width: 50, - fit: BoxFit.contain, + Image.asset( + Images.logoDark, + height: 50, + width: 50, + fit: BoxFit.contain, + ), + if (isBetaEnvironment) + Positioned( + bottom: 0, + left: 0, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 4, vertical: 2), + decoration: BoxDecoration( + color: Colors.deepPurple, + borderRadius: BorderRadius.circular(6), + border: Border.all(color: Colors.white, width: 1.2), ), - if (isBetaEnvironment) - Positioned( - bottom: 0, - left: 0, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 4, vertical: 2), - decoration: BoxDecoration( - color: Colors.deepPurple, - borderRadius: - BorderRadius.circular(6), // capsule shape - border: Border.all( - color: Colors.white, width: 1.2), - boxShadow: [ - BoxShadow( - color: Colors.black12, - blurRadius: 2, - offset: Offset(0, 1), - ) - ], - ), - child: const Text( - 'B', - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ), + child: const Text( + 'B', + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: Colors.white, ), - ], - ), - ), - const SizedBox(width: 12), - Expanded( - child: hasProjects - ? (projectController.projects.length > 1 - ? GestureDetector( - onTap: () => projectController - .isProjectSelectionExpanded - .toggle(), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: Row( - children: [ - Expanded( - child: MyText.bodyLarge( - selectedProject?.name ?? - "Select Project", - fontWeight: 700, - maxLines: 1, - overflow: - TextOverflow.ellipsis, - ), - ), - Icon( - isExpanded - ? Icons - .arrow_drop_up_outlined - : Icons - .arrow_drop_down_outlined, - color: Colors.black, - ), - ], - ), - ), - ], - ), - MyText.bodyMedium( - "Hi, ${employeeInfo?.firstName ?? ''}", - color: Colors.black54, - ), - ], - ), - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MyText.bodyLarge( - selectedProject?.name ?? "No Project", - fontWeight: 700, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - MyText.bodyMedium( - "Hi, ${employeeInfo?.firstName ?? ''}", - color: Colors.black54, - ), - ], - )) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MyText.bodyLarge( - "No Project Assigned", - fontWeight: 700, - color: Colors.redAccent, - ), - MyText.bodyMedium( - "Hi, ${employeeInfo?.firstName ?? ''}", - color: Colors.black54, - ), - ], - ), - ), - Stack( - clipBehavior: Clip.none, - alignment: Alignment.center, - children: [ - IconButton( - icon: const Icon(Icons.menu), - onPressed: () => controller.scaffoldKey.currentState - ?.openEndDrawer(), + ), ), - if (!hasMpin) - Positioned( - right: 10, - top: 10, - child: Container( - width: 14, - height: 14, - decoration: BoxDecoration( - color: Colors.redAccent, - shape: BoxShape.circle, - border: - Border.all(color: Colors.white, width: 2), - ), - ), - ), - ], - ) + ), ], ), ), - if (isExpanded && hasProjects) - Positioned( - top: 70, - left: 0, - right: 0, - child: Container( - padding: const EdgeInsets.all(5), - color: Colors.white, - child: _buildProjectList(context, isMobile), - ), + const SizedBox(width: 12), + + /// Dashboard title + current organization + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MyText.bodyLarge( + "Dashboard", + fontWeight: 700, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + // MyText.bodyMedium( + // "Hi, ${employeeInfo?.firstName ?? ''}", + // color: Colors.black54, + // ), + if (selectedTenant != null) + MyText.bodySmall( + "Organization: ${selectedTenant.name}", + color: Colors.black54, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], ), + ), + + /// Menu Button + Stack( + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + IconButton( + icon: const Icon(Icons.menu), + onPressed: () => + controller.scaffoldKey.currentState?.openEndDrawer(), + ), + if (!hasMpin) + Positioned( + right: 10, + top: 10, + child: Container( + width: 14, + height: 14, + decoration: BoxDecoration( + color: Colors.redAccent, + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2), + ), + ), + ), + ], + ) ], ), - ); - }), - ); - } - - /// Loading Skeleton for Header - Widget _buildLoadingSkeleton() { - return Card( - elevation: 4, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5), - ), - margin: EdgeInsets.zero, - child: Padding( - padding: const EdgeInsets.all(10), - child: Row( - children: [ - Container( - height: 50, - width: 50, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(5), - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - height: 18, - width: 140, - color: Colors.grey.shade300, - ), - const SizedBox(height: 6), - Container( - height: 14, - width: 100, - color: Colors.grey.shade200, - ), - ], - ), - ), - const SizedBox(width: 10), - Container( - height: 30, - width: 30, - color: Colors.grey.shade300, - ), - ], ), ), ); } - - /// Project List Popup - Widget _buildProjectDropdown(BuildContext context, bool isMobile) { - return Obx(() { - if (!projectController.isProjectSelectionExpanded.value) { - return const SizedBox.shrink(); - } - return Positioned( - top: 95, - left: 16, - right: 16, - child: Material( - elevation: 4, - borderRadius: BorderRadius.circular(5), - child: Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(5), - ), - padding: const EdgeInsets.all(10), - child: _buildProjectList(context, isMobile), - ), - ), - ); - }); - } - - Widget _buildProjectList(BuildContext context, bool isMobile) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MyText.titleSmall("Switch Project", fontWeight: 600), - const SizedBox(height: 4), - ConstrainedBox( - constraints: BoxConstraints( - maxHeight: - isMobile ? MediaQuery.of(context).size.height * 0.4 : 400, - ), - child: ListView.builder( - shrinkWrap: true, - itemCount: projectController.projects.length, - itemBuilder: (context, index) { - final project = projectController.projects[index]; - final selectedId = projectController.selectedProjectId.value; - final isSelected = project.id == selectedId; - - return RadioListTile( - value: project.id, - groupValue: selectedId, - onChanged: (value) { - projectController.updateSelectedProject(value!); - projectController.isProjectSelectionExpanded.value = false; - }, - title: Text( - project.name, - style: TextStyle( - fontWeight: - isSelected ? FontWeight.bold : FontWeight.normal, - color: isSelected ? Colors.blueAccent : Colors.black87, - ), - ), - contentPadding: const EdgeInsets.symmetric(horizontal: 0), - activeColor: Colors.blueAccent, - tileColor: isSelected - ? Colors.blueAccent.withOpacity(0.1) - : Colors.transparent, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5), - ), - visualDensity: const VisualDensity(vertical: -4), - ); - }, - ), - ), - ], - ); - } }