From 70443d8e2497089199d79b1df772e062132d4f64 Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Sat, 2 Aug 2025 15:34:38 +0530 Subject: [PATCH] feat: Refactor EmployeesScreen for improved readability and structure --- lib/view/employees/employees_screen.dart | 300 +++++++++-------------- 1 file changed, 112 insertions(+), 188 deletions(-) diff --git a/lib/view/employees/employees_screen.dart b/lib/view/employees/employees_screen.dart index 2c2c1ad..813e3a8 100644 --- a/lib/view/employees/employees_screen.dart +++ b/lib/view/employees/employees_screen.dart @@ -22,8 +22,7 @@ class EmployeesScreen extends StatefulWidget { } class _EmployeesScreenState extends State with UIMixin { - final EmployeesScreenController _employeeController = - Get.put(EmployeesScreenController()); + final EmployeesScreenController _employeeController = Get.put(EmployeesScreenController()); final TextEditingController _searchController = TextEditingController(); final RxList _filteredEmployees = [].obs; @@ -32,39 +31,37 @@ class _EmployeesScreenState extends State with UIMixin { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _initEmployees(); - _searchController.addListener(() { - _filterEmployees(_searchController.text); - }); + _searchController.addListener(() => _filterEmployees(_searchController.text)); }); } Future _initEmployees() async { - final selectedProjectId = Get.find().selectedProject?.id; + final projectId = Get.find().selectedProject?.id; - if (selectedProjectId != null) { - _employeeController.selectedProjectId = selectedProjectId; - await _employeeController.fetchEmployeesByProject(selectedProjectId); - } else if (_employeeController.isAllEmployeeSelected.value) { + if (_employeeController.isAllEmployeeSelected.value) { _employeeController.selectedProjectId = null; await _employeeController.fetchAllEmployees(); + } else if (projectId != null) { + _employeeController.selectedProjectId = projectId; + await _employeeController.fetchEmployeesByProject(projectId); } else { _employeeController.clearEmployees(); } + _filterEmployees(_searchController.text); } Future _refreshEmployees() async { try { - final selectedProjectId = - Get.find().selectedProject?.id; - final isAllSelected = _employeeController.isAllEmployeeSelected.value; + final projectId = Get.find().selectedProject?.id; + final allSelected = _employeeController.isAllEmployeeSelected.value; - if (isAllSelected) { - _employeeController.selectedProjectId = null; + _employeeController.selectedProjectId = allSelected ? null : projectId; + + if (allSelected) { await _employeeController.fetchAllEmployees(); - } else if (selectedProjectId != null) { - _employeeController.selectedProjectId = selectedProjectId; - await _employeeController.fetchEmployeesByProject(selectedProjectId); + } else if (projectId != null) { + await _employeeController.fetchEmployeesByProject(projectId); } else { _employeeController.clearEmployees(); } @@ -79,17 +76,20 @@ class _EmployeesScreenState extends State with UIMixin { void _filterEmployees(String query) { final employees = _employeeController.employees; + if (query.isEmpty) { _filteredEmployees.assignAll(employees); return; } - final lowerQuery = query.toLowerCase(); + + final q = query.toLowerCase(); _filteredEmployees.assignAll( employees.where((e) => - e.name.toLowerCase().contains(lowerQuery) || - e.email.toLowerCase().contains(lowerQuery) || - e.phoneNumber.toLowerCase().contains(lowerQuery) || - e.jobRole.toLowerCase().contains(lowerQuery)), + e.name.toLowerCase().contains(q) || + e.email.toLowerCase().contains(q) || + e.phoneNumber.toLowerCase().contains(q) || + e.jobRole.toLowerCase().contains(q), + ), ); } @@ -98,7 +98,8 @@ class _EmployeesScreenState extends State with UIMixin { context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(16))), + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), backgroundColor: Colors.transparent, builder: (context) => AddEmployeeBottomSheet(), ); @@ -113,7 +114,8 @@ class _EmployeesScreenState extends State with UIMixin { context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(24))), + borderRadius: BorderRadius.vertical(top: Radius.circular(24)), + ), backgroundColor: Colors.transparent, builder: (context) => AssignProjectBottomSheet( employeeId: employeeId, @@ -134,7 +136,7 @@ class _EmployeesScreenState extends State with UIMixin { child: GetBuilder( init: _employeeController, tag: 'employee_screen_controller', - builder: (controller) { + builder: (_) { _filterEmployees(_searchController.text); return SingleChildScrollView( padding: const EdgeInsets.only(bottom: 40), @@ -168,34 +170,24 @@ class _EmployeesScreenState extends State with UIMixin { title: Padding( padding: MySpacing.xy(16, 0), child: Row( - crossAxisAlignment: CrossAxisAlignment.center, children: [ IconButton( - icon: const Icon(Icons.arrow_back_ios_new, - color: Colors.black, size: 20), + icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black, size: 20), onPressed: () => Get.offNamed('/dashboard'), ), MySpacing.width(8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, children: [ - MyText.titleLarge( - 'Employees', - fontWeight: 700, - color: Colors.black, - ), + MyText.titleLarge('Employees', fontWeight: 700, color: Colors.black), MySpacing.height(2), GetBuilder( builder: (projectController) { - final projectName = - projectController.selectedProject?.name ?? - 'Select Project'; + final projectName = projectController.selectedProject?.name ?? 'Select Project'; return Row( children: [ - const Icon(Icons.work_outline, - size: 14, color: Colors.grey), + const Icon(Icons.work_outline, size: 14, color: Colors.grey), MySpacing.width(4), Expanded( child: MyText.bodySmall( @@ -228,13 +220,7 @@ class _EmployeesScreenState extends State with UIMixin { decoration: BoxDecoration( color: Colors.red, borderRadius: BorderRadius.circular(28), - boxShadow: const [ - BoxShadow( - color: Colors.black26, - blurRadius: 6, - offset: Offset(0, 3), - ), - ], + boxShadow: const [BoxShadow(color: Colors.black26, blurRadius: 6, offset: Offset(0, 3))], ), child: const Row( mainAxisSize: MainAxisSize.min, @@ -271,11 +257,9 @@ class _EmployeesScreenState extends State with UIMixin { style: const TextStyle(fontSize: 13, height: 1.2), decoration: InputDecoration( isDense: true, - contentPadding: - const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), prefixIcon: const Icon(Icons.search, size: 18, color: Colors.grey), - prefixIconConstraints: - const BoxConstraints(minWidth: 32, minHeight: 32), + prefixIconConstraints: const BoxConstraints(minWidth: 32, minHeight: 32), hintText: 'Search contacts...', hintStyle: const TextStyle(fontSize: 13, color: Colors.grey), filled: true, @@ -324,46 +308,27 @@ class _EmployeesScreenState extends State with UIMixin { clipBehavior: Clip.none, children: [ const Icon(Icons.tune, color: Colors.black), - Obx(() { - return _employeeController.isAllEmployeeSelected.value - ? Positioned( - right: -1, - top: -1, - child: Container( - width: 10, - height: 10, - decoration: const BoxDecoration( - color: Colors.red, shape: BoxShape.circle), - ), - ) - : const SizedBox.shrink(); - }), + Obx(() => _employeeController.isAllEmployeeSelected.value + ? Positioned( + right: -1, + top: -1, + child: Container( + width: 10, + height: 10, + decoration: const BoxDecoration(color: Colors.red, shape: BoxShape.circle), + ), + ) + : const SizedBox.shrink()), ], ), onSelected: (value) async { if (value == 'all_employees') { - _employeeController.isAllEmployeeSelected.value = - !_employeeController.isAllEmployeeSelected.value; - - if (_employeeController.isAllEmployeeSelected.value) { - _employeeController.selectedProjectId = null; - await _employeeController.fetchAllEmployees(); - } else { - final selectedProjectId = - Get.find().selectedProject?.id; - if (selectedProjectId != null) { - _employeeController.selectedProjectId = selectedProjectId; - await _employeeController - .fetchEmployeesByProject(selectedProjectId); - } else { - _employeeController.clearEmployees(); - } - } - _filterEmployees(_searchController.text); + _employeeController.isAllEmployeeSelected.toggle(); + await _initEmployees(); _employeeController.update(['employee_screen_controller']); } }, - itemBuilder: (context) => [ + itemBuilder: (_) => [ PopupMenuItem( value: 'all_employees', child: Obx( @@ -371,17 +336,12 @@ class _EmployeesScreenState extends State with UIMixin { children: [ Checkbox( value: _employeeController.isAllEmployeeSelected.value, - onChanged: (bool? value) => - Navigator.pop(context, 'all_employees'), + onChanged: (_) => Navigator.pop(context, 'all_employees'), checkColor: Colors.white, activeColor: Colors.red, side: const BorderSide(color: Colors.black, width: 1.5), - fillColor: MaterialStateProperty.resolveWith((states) { - if (states.contains(MaterialState.selected)) { - return Colors.red; - } - return Colors.white; - }), + fillColor: MaterialStateProperty.resolveWith( + (states) => states.contains(MaterialState.selected) ? Colors.red : Colors.white), ), const Text('All Employees'), ], @@ -394,131 +354,95 @@ class _EmployeesScreenState extends State with UIMixin { Widget _buildEmployeeList() { return Obx(() { - final isLoading = _employeeController.isLoading.value; - final employees = _filteredEmployees; - - // Show skeleton loader while data is being fetched - if (isLoading) { + if (_employeeController.isLoading.value) { return ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - itemCount: 8, // number of skeleton items + itemCount: 8, separatorBuilder: (_, __) => MySpacing.height(12), itemBuilder: (_, __) => SkeletonLoaders.employeeSkeletonCard(), ); } - // Show empty state when no employees are found + final employees = _filteredEmployees; + if (employees.isEmpty) { return Padding( padding: const EdgeInsets.only(top: 60), child: Center( - child: MyText.bodySmall( - "No Employees Found", - fontWeight: 600, - color: Colors.grey[700], - ), + child: MyText.bodySmall("No Employees Found", fontWeight: 600, color: Colors.grey[700]), ), ); } - // Show the actual employee list return ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: MySpacing.only(bottom: 80), itemCount: employees.length, separatorBuilder: (_, __) => MySpacing.height(12), - itemBuilder: (context, index) { - final employee = employees[index]; - final nameParts = employee.name.trim().split(' '); - final firstName = nameParts.first; - final lastName = nameParts.length > 1 ? nameParts.last : ''; + itemBuilder: (_, index) { + final e = employees[index]; + final names = e.name.trim().split(' '); + final firstName = names.first; + final lastName = names.length > 1 ? names.last : ''; return InkWell( - onTap: () => - Get.to(() => EmployeeDetailPage(employeeId: employee.id)), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Avatar(firstName: firstName, lastName: lastName, size: 35), - MySpacing.width(12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MyText.titleSmall( - employee.name, - fontWeight: 600, - overflow: TextOverflow.ellipsis, - ), - if (employee.jobRole.isNotEmpty) - MyText.bodySmall( - employee.jobRole, - color: Colors.grey[700], - overflow: TextOverflow.ellipsis, - ), - MySpacing.height(8), - if (employee.email.isNotEmpty && employee.email != '-') - GestureDetector( - onTap: () => - LauncherUtils.launchEmail(employee.email), - onLongPress: () => LauncherUtils.copyToClipboard( - employee.email, - typeLabel: 'Email'), - child: Row( - children: [ - const Icon(Icons.email_outlined, - size: 16, color: Colors.indigo), - MySpacing.width(4), - ConstrainedBox( - constraints: - const BoxConstraints(maxWidth: 180), - child: MyText.labelSmall( - employee.email, - overflow: TextOverflow.ellipsis, - color: Colors.indigo, - decoration: TextDecoration.underline, - ), - ), - ], - ), - ), - if (employee.email.isNotEmpty && employee.email != '-') - MySpacing.height(6), - if (employee.phoneNumber.isNotEmpty) - GestureDetector( - onTap: () => - LauncherUtils.launchPhone(employee.phoneNumber), - onLongPress: () => LauncherUtils.copyToClipboard( - employee.phoneNumber, - typeLabel: 'Phone'), - child: Row( - children: [ - const Icon(Icons.phone_outlined, - size: 16, color: Colors.indigo), - MySpacing.width(4), - MyText.labelSmall( - employee.phoneNumber, - color: Colors.indigo, - decoration: TextDecoration.underline, - ), - ], - ), - ), - ], - ), + onTap: () => Get.to(() => EmployeeDetailPage(employeeId: e.id)), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Avatar(firstName: firstName, lastName: lastName, size: 35), + MySpacing.width(12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MyText.titleSmall(e.name, fontWeight: 600, overflow: TextOverflow.ellipsis), + if (e.jobRole.isNotEmpty) + MyText.bodySmall(e.jobRole, color: Colors.grey[700], overflow: TextOverflow.ellipsis), + MySpacing.height(8), + if (e.email.isNotEmpty && e.email != '-') + _buildLinkRow(icon: Icons.email_outlined, text: e.email, onTap: () => LauncherUtils.launchEmail(e.email), onLongPress: () => LauncherUtils.copyToClipboard(e.email, typeLabel: 'Email')), + if (e.email.isNotEmpty && e.email != '-') MySpacing.height(6), + if (e.phoneNumber.isNotEmpty) + _buildLinkRow(icon: Icons.phone_outlined, text: e.phoneNumber, onTap: () => LauncherUtils.launchPhone(e.phoneNumber), onLongPress: () => LauncherUtils.copyToClipboard(e.phoneNumber, typeLabel: 'Phone')), + ], ), - const Icon(Icons.arrow_forward_ios, - color: Colors.grey, size: 16), - ], - ), + ), + const Icon(Icons.arrow_forward_ios, color: Colors.grey, size: 16), + ], ), ); }, ); }); } + + Widget _buildLinkRow({ + required IconData icon, + required String text, + required VoidCallback onTap, + required VoidCallback onLongPress, + }) { + return GestureDetector( + onTap: onTap, + onLongPress: onLongPress, + child: Row( + children: [ + Icon(icon, size: 16, color: Colors.indigo), + MySpacing.width(4), + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 180), + child: MyText.labelSmall( + text, + overflow: TextOverflow.ellipsis, + color: Colors.indigo, + decoration: TextDecoration.underline, + ), + ), + ], + ), + ); + } }