diff --git a/lib/controller/employee/employees_screen_controller.dart b/lib/controller/employee/employees_screen_controller.dart index d8a4c9e..b9dd505 100644 --- a/lib/controller/employee/employees_screen_controller.dart +++ b/lib/controller/employee/employees_screen_controller.dart @@ -7,20 +7,18 @@ import 'package:on_field_work/model/employees/employee_details_model.dart'; class EmployeesScreenController extends GetxController { /// ✅ Data lists RxList employees = [].obs; + RxList filteredEmployees = [].obs; Rxn selectedEmployeeDetails = Rxn(); /// ✅ Loading states - RxBool isLoading = false.obs; + RxBool isLoading = true.obs; RxBool isLoadingEmployeeDetails = false.obs; /// ✅ Selection state RxBool isAllEmployeeSelected = false.obs; RxSet selectedEmployeeIds = {}.obs; - /// ✅ Upload state tracking (if needed later) - RxMap uploadingStates = {}.obs; - RxList selectedEmployeePrimaryManagers = [].obs; RxList selectedEmployeeSecondaryManagers = [].obs; @@ -31,26 +29,51 @@ class EmployeesScreenController extends GetxController { fetchAllEmployees(); } - /// 🔹 Fetch all employees (no project filter) + /// 🔹 Search/Filter Logic + void searchEmployees(String query) { + if (query.isEmpty) { + filteredEmployees.assignAll(employees); + } else { + final searchQuery = query.toLowerCase(); + final result = employees + .where((e) => + e.name.toLowerCase().contains(searchQuery) || + e.email.toLowerCase().contains(searchQuery) || + e.phoneNumber.toLowerCase().contains(searchQuery) || + e.jobRole.toLowerCase().contains(searchQuery)) + .toList(); + + // Sort alphabetically + result + .sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); + filteredEmployees.assignAll(result); + } + } + + /// 🔹 Fetch all employees Future fetchAllEmployees({String? organizationId}) async { isLoading.value = true; - update(['employee_screen_controller']); - await _handleApiCall( () => ApiService.getAllEmployees(organizationId: organizationId), onSuccess: (data) { - employees.assignAll(data.map((json) => EmployeeModel.fromJson(json))); + final loadedList = + data.map((json) => EmployeeModel.fromJson(json)).toList(); + + employees.assignAll(loadedList); + + filteredEmployees.assignAll(loadedList); + logSafe( "All Employees fetched: ${employees.length} employees loaded.", level: LogLevel.info, ); - // Reset selection states when new data arrives selectedEmployeeIds.clear(); isAllEmployeeSelected.value = false; }, onEmpty: () { employees.clear(); + filteredEmployees.clear(); selectedEmployeeIds.clear(); isAllEmployeeSelected.value = false; logSafe("No Employee data found or API call failed", @@ -90,16 +113,14 @@ class EmployeesScreenController extends GetxController { isLoadingEmployeeDetails.value = false; } - /// Fetch reporting managers for a specific employee from /organization/hierarchy/list/:employeeId + /// Fetch reporting managers Future fetchReportingManagers(String? employeeId) async { if (employeeId == null || employeeId.isEmpty) return; try { - // ✅ Always clear before new fetch (to avoid mixing old data) selectedEmployeePrimaryManagers.clear(); selectedEmployeeSecondaryManagers.clear(); - // Fetch from existing API helper final data = await ApiService.getOrganizationHierarchyList(employeeId); if (data == null || data.isEmpty) { @@ -124,11 +145,8 @@ class EmployeesScreenController extends GetxController { selectedEmployeeSecondaryManagers.add(emp); } } - } catch (_) { - // ignore malformed items - } + } catch (_) {} } - update(['employee_screen_controller']); } catch (e) { logSafe("Error fetching reporting managers for $employeeId", @@ -139,13 +157,13 @@ class EmployeesScreenController extends GetxController { /// 🔹 Clear all employee data void clearEmployees() { employees.clear(); + filteredEmployees.clear(); selectedEmployeeIds.clear(); isAllEmployeeSelected.value = false; logSafe("Employees cleared", level: LogLevel.info); update(['employee_screen_controller']); } - /// 🔹 Generic handler for list API responses Future _handleApiCall( Future?> Function() apiCall, { required Function(List) onSuccess, @@ -168,7 +186,6 @@ class EmployeesScreenController extends GetxController { } } - /// 🔹 Generic handler for single-object API responses Future _handleSingleApiCall( Future?> Function() apiCall, { required Function(Map) onSuccess, diff --git a/lib/helpers/widgets/my_custom_skeleton.dart b/lib/helpers/widgets/my_custom_skeleton.dart index b448d49..6c0c590 100644 --- a/lib/helpers/widgets/my_custom_skeleton.dart +++ b/lib/helpers/widgets/my_custom_skeleton.dart @@ -22,7 +22,7 @@ class SkeletonLoaders { height: 16, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + borderRadius: BorderRadius.circular(5), ), ); }), @@ -94,7 +94,7 @@ class SkeletonLoaders { width: 60, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + borderRadius: BorderRadius.circular(5), ), ), ], @@ -134,7 +134,7 @@ class SkeletonLoaders { decoration: BoxDecoration( color: Colors .grey.shade300, - borderRadius: BorderRadius.circular(6), + borderRadius: BorderRadius.circular(5), ), ), ), @@ -165,7 +165,7 @@ static Widget _buildDetailRowSkeleton({ width: width, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(5), ), ), ], @@ -239,7 +239,7 @@ static Widget _buildDetailRowSkeleton({ decoration: BoxDecoration( color: Colors.grey.shade400, borderRadius: - BorderRadius.circular(6))), // Larger button size + BorderRadius.circular(5))), // Larger button size MySpacing.width(8), // Log View Button (Icon Button, approx size 28-32) Container( @@ -302,7 +302,7 @@ static Widget _buildDetailRowSkeleton({ width: cardWidth, height: cardHeight, paddingAll: 4, - borderRadiusAll: 10, + borderRadiusAll: 5, border: Border.all(color: Colors.grey.withOpacity(0.15)), child: ShimmerEffect( child: Column( @@ -314,7 +314,7 @@ static Widget _buildDetailRowSkeleton({ height: 16, // Reduced from 20 decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(5), ), ), MySpacing.height(4), // Reduced spacing from 6 @@ -419,7 +419,7 @@ static Widget _buildDetailRowSkeleton({ width: 120, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(5), ), ), const SizedBox(height: 6), @@ -432,7 +432,7 @@ static Widget _buildDetailRowSkeleton({ width: 50, decoration: BoxDecoration( color: Colors.grey.shade200, - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(5), ), ), const SizedBox(width: 8), @@ -441,7 +441,7 @@ static Widget _buildDetailRowSkeleton({ height: 12, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(5), ), ), ), @@ -460,7 +460,7 @@ static Widget _buildDetailRowSkeleton({ width: 50, decoration: BoxDecoration( color: Colors.grey.shade200, - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(5), ), ), const SizedBox(width: 6), @@ -469,7 +469,7 @@ static Widget _buildDetailRowSkeleton({ width: 80, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(5), ), ), ], @@ -481,7 +481,7 @@ static Widget _buildDetailRowSkeleton({ width: 60, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + borderRadius: BorderRadius.circular(5), ), ), ], @@ -502,7 +502,7 @@ static Widget _buildDetailRowSkeleton({ constraints: const BoxConstraints(maxWidth: 520), child: MyCard.bordered( paddingAll: 16, - borderRadiusAll: 8, + borderRadiusAll: 5, shadow: MyShadow(elevation: 3), child: ShimmerEffect( child: Column( @@ -566,7 +566,7 @@ static Widget _buildDetailRowSkeleton({ height: 40, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(5), ), ), )), @@ -635,7 +635,7 @@ static Widget _buildDetailRowSkeleton({ children: [ // Header skeleton (avatar + name + role) MyCard( - borderRadiusAll: 8, + borderRadiusAll: 5, paddingAll: 16, margin: MySpacing.bottom(16), shadow: MyShadow(elevation: 2), @@ -686,7 +686,7 @@ static Widget _buildDetailRowSkeleton({ (_) => Column( children: [ MyCard( - borderRadiusAll: 8, + borderRadiusAll: 5, paddingAll: 16, margin: MySpacing.bottom(16), shadow: MyShadow(elevation: 2), @@ -773,7 +773,7 @@ static Widget _buildDetailRowSkeleton({ crossAxisAlignment: CrossAxisAlignment.start, children: List.generate(3, (floorIndex) { return MyCard( - borderRadiusAll: 8, + borderRadiusAll: 5, paddingAll: 5, margin: MySpacing.bottom(10), shadow: MyShadow(elevation: 1.5), @@ -787,7 +787,7 @@ static Widget _buildDetailRowSkeleton({ width: 160, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + borderRadius: BorderRadius.circular(5), ), ), MySpacing.height(10), @@ -809,7 +809,7 @@ static Widget _buildDetailRowSkeleton({ width: 120, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(5), ), ), MySpacing.height(8), @@ -838,7 +838,7 @@ static Widget _buildDetailRowSkeleton({ decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: - BorderRadius.circular(4), + BorderRadius.circular(5), ), ), ), @@ -863,7 +863,7 @@ static Widget _buildDetailRowSkeleton({ static Widget chartSkeletonLoader() { return MyCard.bordered( paddingAll: 16, - borderRadiusAll: 12, + borderRadiusAll: 5, shadow: MyShadow( elevation: 1.5, position: MyShadowPosition.bottom, @@ -878,7 +878,7 @@ static Widget _buildDetailRowSkeleton({ width: 180, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + borderRadius: BorderRadius.circular(5), ), ), const SizedBox(height: 16), @@ -907,7 +907,7 @@ static Widget _buildDetailRowSkeleton({ height: 14, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + borderRadius: BorderRadius.circular(5), ), ); }), @@ -925,7 +925,7 @@ static Widget _buildDetailRowSkeleton({ width: 90, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + borderRadius: BorderRadius.circular(5), ), ), ); @@ -956,7 +956,7 @@ static Widget _buildDetailRowSkeleton({ width: 160, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + borderRadius: BorderRadius.circular(5), ), ), const SizedBox(height: 16), @@ -988,7 +988,7 @@ static Widget _buildDetailRowSkeleton({ width: 100, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(5), ), ), const SizedBox(height: 6), @@ -997,7 +997,7 @@ static Widget _buildDetailRowSkeleton({ width: 60, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(5), ), ), ], @@ -1010,7 +1010,7 @@ static Widget _buildDetailRowSkeleton({ width: 30, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(5), ), ), const SizedBox(width: 6), @@ -1037,7 +1037,7 @@ static Widget _buildDetailRowSkeleton({ width: 120, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(5), ), ), const SizedBox(height: 4), @@ -1046,7 +1046,7 @@ static Widget _buildDetailRowSkeleton({ width: 140, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(5), ), ), ], @@ -1056,7 +1056,7 @@ static Widget _buildDetailRowSkeleton({ width: 80, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(5), ), ), ], @@ -1084,7 +1084,7 @@ static Widget _buildDetailRowSkeleton({ width: 80, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + borderRadius: BorderRadius.circular(5), ), ), ), @@ -1096,7 +1096,7 @@ static Widget _buildDetailRowSkeleton({ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(5), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), @@ -1114,7 +1114,7 @@ static Widget _buildDetailRowSkeleton({ padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(5), ), child: const Icon(Icons.description, color: Colors.transparent), // invisible icon @@ -1178,7 +1178,7 @@ static Widget _buildDetailRowSkeleton({ padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.circular(10), + borderRadius: BorderRadius.circular(5), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.06), @@ -1235,7 +1235,7 @@ static Widget _buildDetailRowSkeleton({ width: 60, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(5), ), ); }), @@ -1289,7 +1289,7 @@ static Widget _buildDetailRowSkeleton({ height: 40, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(5), ), ), const SizedBox(width: 12), @@ -1335,7 +1335,7 @@ static Widget _buildDetailRowSkeleton({ return Column( children: List.generate(4, (index) { return MyCard.bordered( - borderRadiusAll: 12, + borderRadiusAll: 5, paddingAll: 10, margin: MySpacing.bottom(12), shadow: MyShadow(elevation: 3), @@ -1407,7 +1407,7 @@ static Widget _buildDetailRowSkeleton({ static Widget employeeListCollapsedSkeletonLoader() { return MyCard.bordered( - borderRadiusAll: 4, + borderRadiusAll: 5, paddingAll: 8, child: ShimmerEffect( child: Column( @@ -1479,7 +1479,7 @@ static Widget _buildDetailRowSkeleton({ static Widget dailyProgressReportSkeletonLoader() { return MyCard.bordered( - borderRadiusAll: 4, + borderRadiusAll: 5, border: Border.all(color: Colors.grey.withOpacity(0.2)), shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom), paddingAll: 8, @@ -1514,7 +1514,7 @@ static Widget _buildDetailRowSkeleton({ crossAxisAlignment: CrossAxisAlignment.start, children: List.generate(3, (index) { return MyCard.bordered( - borderRadiusAll: 12, + borderRadiusAll: 5, paddingAll: 16, margin: MySpacing.bottom(12), shadow: MyShadow(elevation: 3), @@ -1573,7 +1573,7 @@ static Widget _buildDetailRowSkeleton({ width: 120, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + borderRadius: BorderRadius.circular(5), ), ), Container( @@ -1581,7 +1581,7 @@ static Widget _buildDetailRowSkeleton({ width: 80, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + borderRadius: BorderRadius.circular(5), ), ), ], @@ -1595,7 +1595,7 @@ static Widget _buildDetailRowSkeleton({ width: 100, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + borderRadius: BorderRadius.circular(5), ), ), const Spacer(), @@ -1604,7 +1604,7 @@ static Widget _buildDetailRowSkeleton({ width: 50, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + borderRadius: BorderRadius.circular(5), ), ), ], @@ -1620,7 +1620,7 @@ static Widget _buildDetailRowSkeleton({ return MyCard.bordered( margin: MySpacing.only(bottom: 12), paddingAll: 12, - borderRadiusAll: 12, + borderRadiusAll: 5, shadow: MyShadow( elevation: 1.5, position: MyShadowPosition.bottom, @@ -1703,9 +1703,8 @@ static Widget _buildDetailRowSkeleton({ return MyCard.bordered( margin: MySpacing.only(bottom: 12), paddingAll: 16, - borderRadiusAll: 16, + borderRadiusAll: 5, shadow: MyShadow( - elevation: 1.5, position: MyShadowPosition.bottom, ), child: ShimmerEffect( @@ -1859,7 +1858,7 @@ static Widget _buildDetailRowSkeleton({ // Aging Stacked Bar Placeholder ClipRRect( - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(5), child: Row( children: List.generate( 4, diff --git a/lib/helpers/widgets/pill_tab_bar.dart b/lib/helpers/widgets/pill_tab_bar.dart index 87950c8..35dd991 100644 --- a/lib/helpers/widgets/pill_tab_bar.dart +++ b/lib/helpers/widgets/pill_tab_bar.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; -class PillTabBar extends StatelessWidget { +class PillTabBar extends StatefulWidget { final TabController controller; final List tabs; + final List icons; final Color selectedColor; final Color unselectedColor; final Color indicatorColor; @@ -13,6 +14,7 @@ class PillTabBar extends StatelessWidget { Key? key, required this.controller, required this.tabs, + required this.icons, this.selectedColor = Colors.blue, this.unselectedColor = Colors.grey, this.indicatorColor = Colors.blueAccent, @@ -21,64 +23,80 @@ class PillTabBar extends StatelessWidget { }) : super(key: key); @override - Widget build(BuildContext context) { - // Dynamic horizontal padding between tabs - final screenWidth = MediaQuery.of(context).size.width; - final tabSpacing = (screenWidth / (tabs.length * 12)).clamp(8.0, 24.0); + State createState() => _PillTabBarState(); +} +class _PillTabBarState extends State { + @override + void initState() { + super.initState(); + widget.controller.addListener(_onTabChange); + } + + void _onTabChange() { + if (mounted) setState(() {}); + } + + @override + void dispose() { + widget.controller.removeListener(_onTabChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Container( - height: height, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(height / 2), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.15), - blurRadius: 4, - offset: const Offset(0, 2), + height: widget.height, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(widget.height / 2), + ), + child: TabBar( + controller: widget.controller, + isScrollable: true, // important for dynamic spacing + indicatorSize: TabBarIndicatorSize.tab, + indicator: BoxDecoration( + color: widget.indicatorColor.withOpacity(0.2), + borderRadius: BorderRadius.circular(widget.height / 2), ), - ], - ), - child: TabBar( - controller: controller, - indicator: BoxDecoration( - color: indicatorColor.withOpacity(0.2), - borderRadius: BorderRadius.circular(height / 2), - ), - indicatorSize: TabBarIndicatorSize.tab, - indicatorPadding: EdgeInsets.symmetric( - horizontal: tabSpacing / 2, - vertical: 4, - ), - labelColor: selectedColor, - unselectedLabelColor: unselectedColor, - labelStyle: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 13, - ), - unselectedLabelStyle: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 13, - ), - tabs: tabs - .map( - (text) => Tab( - child: Padding( - padding: EdgeInsets.symmetric(horizontal: tabSpacing), - child: Text( - text, - overflow: TextOverflow.ellipsis, - maxLines: 2, - ), - ), + onTap: widget.onTap, + tabs: List.generate(widget.tabs.length, (index) { + final isSelected = widget.controller.index == index; + + return AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: EdgeInsets.symmetric( + horizontal: + isSelected ? 12 : 6, // reduce padding for unselected tabs ), - ) - .toList(), - onTap: onTap, - ), - ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + widget.icons[index], + size: isSelected ? 18 : 16, + color: isSelected + ? widget.selectedColor + : widget.unselectedColor, + ), + if (isSelected) ...[ + const SizedBox(width: 4), + Text( + widget.tabs[index], + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: widget.selectedColor, + ), + ), + ], + ], + ), + ); + }), + )), ); } } diff --git a/lib/view/Attendence/attendance_screen.dart b/lib/view/Attendence/attendance_screen.dart index e287267..8b0d61a 100644 --- a/lib/view/Attendence/attendance_screen.dart +++ b/lib/view/Attendence/attendance_screen.dart @@ -31,7 +31,7 @@ class _AttendanceScreenState extends State final projectController = Get.put(ProjectController()); late TabController _tabController; - late List> _tabs; + late List> _tabs; bool _tabsInitialized = false; @override @@ -62,9 +62,13 @@ class _AttendanceScreenState extends State void _initializeTabs() async { final allTabs = [ - {'label': "Today's", 'value': 'todaysAttendance'}, - {'label': "Logs", 'value': 'attendanceLogs'}, - {'label': "Regularization", 'value': 'regularizationRequests'}, + {'label': "Today's", 'value': 'todaysAttendance', 'icon': Icons.today}, + {'label': "Logs", 'value': 'attendanceLogs', 'icon': Icons.list_alt}, + { + 'label': "Regularization", + 'value': 'regularizationRequests', + 'icon': Icons.edit + }, ]; final hasRegularizationPermission = @@ -306,7 +310,11 @@ class _AttendanceScreenState extends State padding: const EdgeInsets.symmetric(horizontal: 8), child: PillTabBar( controller: _tabController, - tabs: _tabs.map((e) => e['label']!).toList(), + tabs: + _tabs.map((e) => e['label'] as String).toList(), + icons: _tabs + .map((e) => e['icon'] as IconData) + .toList(), selectedColor: contentTheme.primary, unselectedColor: Colors.grey.shade600, indicatorColor: contentTheme.primary, diff --git a/lib/view/directory/directory_main_screen.dart b/lib/view/directory/directory_main_screen.dart index 55be534..b06ee1e 100644 --- a/lib/view/directory/directory_main_screen.dart +++ b/lib/view/directory/directory_main_screen.dart @@ -73,6 +73,10 @@ class _DirectoryMainScreenState extends State PillTabBar( controller: _tabController, tabs: const ["Directory", "Notes"], + icons: const [ + Icons.people, + Icons.notes_outlined, + ], selectedColor: contentTheme.primary, unselectedColor: Colors.grey.shade600, indicatorColor: contentTheme.primary, diff --git a/lib/view/directory/directory_view.dart b/lib/view/directory/directory_view.dart index cf91ca5..da38ff3 100644 --- a/lib/view/directory/directory_view.dart +++ b/lib/view/directory/directory_view.dart @@ -424,6 +424,8 @@ class _DirectoryViewState extends State with UIMixin { child: controller.isLoading.value ? ListView.separated( physics: const AlwaysScrollableScrollPhysics(), + padding: MySpacing.only( + left: 10, right: 10, top: 4, bottom: 80), itemCount: 10, separatorBuilder: (_, __) => MySpacing.height(12), itemBuilder: (_, __) => diff --git a/lib/view/employees/employees_screen.dart b/lib/view/employees/employees_screen.dart index c0bd785..bac73c1 100644 --- a/lib/view/employees/employees_screen.dart +++ b/lib/view/employees/employees_screen.dart @@ -9,7 +9,6 @@ import 'package:on_field_work/controller/employee/employees_screen_controller.da import 'package:on_field_work/helpers/widgets/avatar.dart'; import 'package:on_field_work/controller/project_controller.dart'; import 'package:on_field_work/helpers/widgets/my_custom_skeleton.dart'; -import 'package:on_field_work/model/employees/employee_model.dart'; import 'package:on_field_work/helpers/utils/launcher_utils.dart'; import 'package:on_field_work/view/employees/assign_employee_bottom_sheet.dart'; import 'package:on_field_work/controller/permission_controller.dart'; @@ -30,56 +29,27 @@ class _EmployeesScreenState extends State with UIMixin { late final EmployeesScreenController _employeeController; late final PermissionController _permissionController; final TextEditingController _searchController = TextEditingController(); - final RxList _filteredEmployees = [].obs; @override void initState() { super.initState(); _employeeController = Get.put(EmployeesScreenController()); _permissionController = Get.put(PermissionController()); - WidgetsBinding.instance.addPostFrameCallback((_) async { - await _initEmployees(); - _searchController.addListener(() { - _filterEmployees(_searchController.text); - }); + _searchController.addListener(() { + _employeeController.searchEmployees(_searchController.text); }); } - Future _initEmployees() async { - await _employeeController.fetchAllEmployees(); - _filterEmployees(_searchController.text); - } - Future _refreshEmployees() async { try { await _employeeController.fetchAllEmployees(); - _filterEmployees(_searchController.text); - _employeeController.update(['employee_screen_controller']); + _employeeController.searchEmployees(_searchController.text); } catch (e, stackTrace) { debugPrint('Error refreshing employee data: $e'); debugPrintStack(stackTrace: stackTrace); } } - void _filterEmployees(String query) { - final employees = _employeeController.employees; - final searchQuery = query.toLowerCase(); - final filtered = query.isEmpty - ? List.from(employees) - : employees - .where( - (e) => - e.name.toLowerCase().contains(searchQuery) || - e.email.toLowerCase().contains(searchQuery) || - e.phoneNumber.toLowerCase().contains(searchQuery) || - e.jobRole.toLowerCase().contains(searchQuery), - ) - .toList(); - filtered - .sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); - _filteredEmployees.assignAll(filtered); - } - Future _onAddNewEmployee() async { final result = await showModalBottomSheet>( context: context, @@ -144,35 +114,27 @@ class _EmployeesScreenState extends State with UIMixin { // Main content SafeArea( - child: GetBuilder( - init: _employeeController, - tag: 'employee_screen_controller', - builder: (_) { - _filterEmployees(_searchController.text); - return MyRefreshIndicator( - onRefresh: _refreshEmployees, - child: SingleChildScrollView( - physics: const AlwaysScrollableScrollPhysics(), - padding: const EdgeInsets.only(bottom: 40), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MySpacing.height(flexSpacing), - Padding( - padding: MySpacing.x(15), - child: _buildSearchField(), - ), - MySpacing.height(flexSpacing), - Padding( - padding: MySpacing.x(flexSpacing), - child: _buildEmployeeList(), - ), - ], - ), + child: Obx(() { + return MyRefreshIndicator( + onRefresh: _refreshEmployees, + child: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + padding: const EdgeInsets.only(bottom: 40), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MySpacing.height(flexSpacing), + _buildSearchField(), + MySpacing.height(flexSpacing), + Padding( + padding: MySpacing.x(flexSpacing), + child: _buildEmployeeList(), + ), + ], ), - ); - }, - ), + ), + ); + }), ), ], ), @@ -238,7 +200,6 @@ class _EmployeesScreenState extends State with UIMixin { size: 20, color: Colors.grey), onPressed: () { _searchController.clear(); - _filterEmployees(''); }, ); }, @@ -255,13 +216,11 @@ class _EmployeesScreenState extends State with UIMixin { borderSide: BorderSide(color: Colors.grey.shade300), ), ), - onChanged: (_) => _filterEmployees(_searchController.text), ), ), ), MySpacing.width(10), - // Three dots menu (Manage Reporting) Container( height: 35, width: 35, @@ -277,10 +236,7 @@ class _EmployeesScreenState extends State with UIMixin { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5)), itemBuilder: (context) { - List> menuItems = []; - - // Section: Actions - menuItems.add( + return [ const PopupMenuItem( enabled: false, height: 30, @@ -290,10 +246,6 @@ class _EmployeesScreenState extends State with UIMixin { fontWeight: FontWeight.bold, color: Colors.grey), ), ), - ); - - // Manage Reporting option - menuItems.add( PopupMenuItem( value: 1, child: Row( @@ -317,9 +269,7 @@ class _EmployeesScreenState extends State with UIMixin { }); }, ), - ); - - return menuItems; + ]; }, ), ), @@ -329,88 +279,85 @@ class _EmployeesScreenState extends State with UIMixin { } Widget _buildEmployeeList() { - return Obx(() { - if (_employeeController.isLoading.value) { - return ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: 8, - separatorBuilder: (_, __) => MySpacing.height(12), - itemBuilder: (_, __) => SkeletonLoaders.employeeSkeletonCard(), - ); - } - - 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]), - ), - ); - } - + if (_employeeController.isLoading.value) { return ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - padding: MySpacing.only(bottom: 80), - itemCount: employees.length, + itemCount: 8, separatorBuilder: (_, __) => MySpacing.height(12), - 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(() => EmployeeProfilePage(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), - ], - ), - ); - }, + itemBuilder: (_, __) => SkeletonLoaders.employeeSkeletonCard(), ); - }); + } + + final employees = _employeeController.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]), + ), + ); + } + + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: MySpacing.only(bottom: 80), + itemCount: employees.length, + separatorBuilder: (_, __) => MySpacing.height(12), + 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(() => EmployeeProfilePage(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), + ], + ), + ); + }, + ); } Widget _buildLinkRow({ diff --git a/lib/view/expense/expense_screen.dart b/lib/view/expense/expense_screen.dart index d1d4687..1d257e4 100644 --- a/lib/view/expense/expense_screen.dart +++ b/lib/view/expense/expense_screen.dart @@ -133,6 +133,10 @@ class _ExpenseMainScreenState extends State PillTabBar( controller: _tabController, tabs: const ["Current Month", "History"], + icons: const [ + Icons.calendar_today, + Icons.history, + ], selectedColor: contentTheme.primary, unselectedColor: Colors.grey.shade600, indicatorColor: contentTheme.primary, diff --git a/lib/view/finance/payment_request_screen.dart b/lib/view/finance/payment_request_screen.dart index 51da5c7..86bec30 100644 --- a/lib/view/finance/payment_request_screen.dart +++ b/lib/view/finance/payment_request_screen.dart @@ -142,6 +142,10 @@ class _PaymentRequestMainScreenState extends State PillTabBar( controller: _tabController, tabs: const ["Current Month", "History"], + icons: const [ + Icons.calendar_today, + Icons.history, + ], selectedColor: contentTheme.primary, unselectedColor: Colors.grey.shade600, indicatorColor: contentTheme.primary, diff --git a/lib/view/infraProject/infra_project_details_screen.dart b/lib/view/infraProject/infra_project_details_screen.dart index c4af071..a6a3c51 100644 --- a/lib/view/infraProject/infra_project_details_screen.dart +++ b/lib/view/infraProject/infra_project_details_screen.dart @@ -51,27 +51,33 @@ class _InfraProjectDetailsScreenState extends State } void _prepareTabs() { - _tabs.add(_InfraTab(name: "Profile", view: _buildProfileTab())); - _tabs.add(_InfraTab(name: "Team", view: _buildTeamTab())); + _tabs.add(_InfraTab( + name: "Profile", + icon: Icons.person, + view: _buildProfileTab(), + )); + _tabs.add(_InfraTab( + name: "Team", + icon: Icons.group, + view: _buildTeamTab(), + )); final allowedMenu = menuController.menuItems.where((m) => m.available); if (allowedMenu.any((m) => m.id == MenuItems.dailyTaskPlanning)) { - _tabs.add( - _InfraTab( - name: "Task Planning", - view: DailyTaskPlanningScreen(projectId: widget.projectId), - ), - ); + _tabs.add(_InfraTab( + name: "Task Planning", + icon: Icons.task, + view: DailyTaskPlanningScreen(projectId: widget.projectId), + )); } if (allowedMenu.any((m) => m.id == MenuItems.dailyProgressReport)) { - _tabs.add( - _InfraTab( - name: "Task Progress", - view: DailyProgressReportScreen(projectId: widget.projectId), - ), - ); + _tabs.add(_InfraTab( + name: "Task Progress", + icon: Icons.trending_up, + view: DailyProgressReportScreen(projectId: widget.projectId), + )); } _tabController = TabController(length: _tabs.length, vsync: this); @@ -507,6 +513,7 @@ class _InfraProjectDetailsScreenState extends State PillTabBar( controller: _tabController, tabs: _tabs.map((e) => e.name).toList(), + icons: _tabs.map((e) => e.icon).toList(), selectedColor: contentTheme.primary, unselectedColor: Colors.grey.shade600, indicatorColor: contentTheme.primary, @@ -529,7 +536,12 @@ class _InfraProjectDetailsScreenState extends State /// INTERNAL MODEL class _InfraTab { final String name; + final IconData icon; final Widget view; - _InfraTab({required this.name, required this.view}); + _InfraTab({ + required this.name, + required this.icon, + required this.view, + }); } diff --git a/lib/view/service_project/service_project_details_screen.dart b/lib/view/service_project/service_project_details_screen.dart index 52dfbd6..c9488dd 100644 --- a/lib/view/service_project/service_project_details_screen.dart +++ b/lib/view/service_project/service_project_details_screen.dart @@ -475,6 +475,11 @@ class _ServiceProjectDetailsScreenState PillTabBar( controller: _tabController, tabs: const ["Profile", "Jobs", "Teams"], + icons: const [ + Icons.person, + Icons.work, + Icons.group, + ], selectedColor: contentTheme.primary, unselectedColor: Colors.grey.shade600, indicatorColor: contentTheme.primary.withOpacity(0.1),