import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:marco/helpers/theme/app_theme.dart'; import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/model/employees/add_employee_bottom_sheet.dart'; import 'package:marco/controller/employee/employees_screen_controller.dart'; import 'package:marco/helpers/widgets/avatar.dart'; import 'package:marco/controller/project_controller.dart'; import 'package:marco/helpers/widgets/my_custom_skeleton.dart'; import 'package:marco/model/employees/employee_model.dart'; import 'package:marco/helpers/utils/launcher_utils.dart'; import 'package:marco/view/employees/assign_employee_bottom_sheet.dart'; import 'package:marco/controller/permission_controller.dart'; import 'package:marco/helpers/utils/permission_constants.dart'; import 'package:marco/helpers/widgets/my_refresh_indicator.dart'; import 'package:marco/view/employees/employee_profile_screen.dart'; import 'package:marco/view/employees/manage_reporting_bottom_sheet.dart'; class EmployeesScreen extends StatefulWidget { const EmployeesScreen({super.key}); @override State createState() => _EmployeesScreenState(); } 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); }); }); } Future _initEmployees() async { await _employeeController.fetchAllEmployees(); _filterEmployees(_searchController.text); } Future _refreshEmployees() async { try { await _employeeController.fetchAllEmployees(); _filterEmployees(_searchController.text); _employeeController.update(['employee_screen_controller']); } 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, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), backgroundColor: Colors.transparent, builder: (context) => AddEmployeeBottomSheet(), ); if (result == null || result['success'] != true) return; final employeeData = result['data']; final employeeId = employeeData['id'] as String; await showModalBottomSheet( context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), backgroundColor: Colors.transparent, builder: (_) => AssignProjectBottomSheet( employeeId: employeeId, jobRoleId: employeeData['jobRoleId'] as String, ), ); await _refreshEmployees(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: _buildAppBar(), floatingActionButton: _buildFloatingActionButton(), body: 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(), ), ], ), ), ); }, ), ), ); } PreferredSizeWidget _buildAppBar() { return PreferredSize( preferredSize: const Size.fromHeight(72), child: AppBar( backgroundColor: const Color(0xFFF5F5F5), elevation: 0.5, automaticallyImplyLeading: false, titleSpacing: 0, title: Padding( padding: MySpacing.xy(16, 0), child: Row( children: [ IconButton( 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, children: [ MyText.titleLarge('Employees', fontWeight: 700, color: Colors.black), MySpacing.height(2), GetBuilder( builder: (projectController) { final projectName = projectController.selectedProject?.name ?? 'Select Project'; return Row( children: [ const Icon(Icons.work_outline, size: 14, color: Colors.grey), MySpacing.width(4), Expanded( child: MyText.bodySmall( projectName, fontWeight: 600, overflow: TextOverflow.ellipsis, color: Colors.grey[700], ), ), ], ); }, ), ], ), ), ], ), ), ), ); } Widget _buildFloatingActionButton() { return Obx(() { if (_permissionController.isLoading.value) return const SizedBox.shrink(); final hasPermission = _permissionController.hasPermission(Permissions.manageEmployees); if (!hasPermission) return const SizedBox.shrink(); return InkWell( onTap: _onAddNewEmployee, borderRadius: BorderRadius.circular(28), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: contentTheme.primary, borderRadius: BorderRadius.circular(28), boxShadow: const [ BoxShadow( color: Colors.black26, blurRadius: 6, offset: Offset(0, 3)), ], ), child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.add, color: Colors.white), SizedBox(width: 8), Text('Add New Employee', style: TextStyle(color: Colors.white)), ], ), ), ); }); } Widget _buildSearchField() { return Padding( padding: MySpacing.xy(8, 8), child: Row( children: [ // Search field Expanded( child: SizedBox( height: 35, child: TextField( controller: _searchController, style: const TextStyle(fontSize: 13, height: 1.2), decoration: InputDecoration( contentPadding: const EdgeInsets.symmetric(horizontal: 12), prefixIcon: const Icon(Icons.search, size: 20, color: Colors.grey), suffixIcon: ValueListenableBuilder( valueListenable: _searchController, builder: (context, value, _) { if (value.text.isEmpty) return const SizedBox.shrink(); return IconButton( icon: const Icon(Icons.clear, size: 20, color: Colors.grey), onPressed: () { _searchController.clear(); _filterEmployees(''); }, ); }, ), hintText: 'Search employees...', filled: true, fillColor: Colors.white, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: BorderSide(color: Colors.grey.shade300), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: BorderSide(color: Colors.grey.shade300), ), ), onChanged: (_) => _filterEmployees(_searchController.text), ), ), ), MySpacing.width(10), // Three dots menu (Manage Reporting) Container( height: 35, width: 35, decoration: BoxDecoration( color: Colors.white, border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(5), ), child: PopupMenuButton( padding: EdgeInsets.zero, icon: const Icon(Icons.more_vert, size: 20, color: Colors.black87), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5)), itemBuilder: (context) { List> menuItems = []; // Section: Actions menuItems.add( const PopupMenuItem( enabled: false, height: 30, child: Text( "Actions", style: TextStyle( fontWeight: FontWeight.bold, color: Colors.grey), ), ), ); // Manage Reporting option menuItems.add( PopupMenuItem( value: 1, child: Row( children: [ const Icon(Icons.manage_accounts_outlined, size: 20, color: Colors.black87), const SizedBox(width: 10), const Expanded(child: Text("Manage Reporting")), Icon(Icons.chevron_right, size: 20, color: contentTheme.primary), ], ), onTap: () { Future.delayed(Duration.zero, () { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (_) => const ManageReportingBottomSheet(), ); }); }, ), ); return menuItems; }, ), ), ], ), ); } 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]), ), ); } 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({ 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, ), ), ], ), ); } }