import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:on_field_work/controller/tenant/organization_selection_controller.dart'; import 'package:on_field_work/helpers/widgets/my_spacing.dart'; import 'package:on_field_work/helpers/widgets/my_text.dart'; import 'package:on_field_work/helpers/widgets/tenant/organization_selector.dart'; import 'package:on_field_work/model/attendance/organization_per_project_list_model.dart'; import 'package:on_field_work/model/employees/employee_model.dart'; import 'package:on_field_work/model/employees/multiple_select_bottomsheet.dart'; import 'package:on_field_work/controller/tenant/service_controller.dart'; import 'package:on_field_work/helpers/widgets/tenant/service_selector.dart'; import 'package:on_field_work/model/tenant/tenant_services_model.dart'; import 'package:on_field_work/helpers/services/api_service.dart'; import 'package:on_field_work/helpers/utils/base_bottom_sheet.dart'; import 'package:on_field_work/model/infra_project/assign_project_allocation_request.dart'; class JobRole { final String id; final String name; JobRole({required this.id, required this.name}); factory JobRole.fromJson(Map json) { return JobRole( id: json['id'].toString(), name: json['name'] ?? '', ); } } class AssignEmployeeBottomSheet extends StatefulWidget { final String projectId; const AssignEmployeeBottomSheet({ super.key, required this.projectId, }); @override State createState() => _AssignEmployeeBottomSheetState(); } class _AssignEmployeeBottomSheetState extends State { late final OrganizationController _organizationController; late final ServiceController _serviceController; final RxList _selectedEmployees = [].obs; Organization? _selectedOrganization; JobRole? _selectedRole; final RxBool _isLoadingRoles = false.obs; final RxList _roles = [].obs; @override void initState() { super.initState(); _organizationController = Get.put( OrganizationController(), tag: 'assign_employee_org', ); _serviceController = Get.put( ServiceController(), tag: 'assign_employee_service', ); _organizationController.fetchOrganizations(widget.projectId); _serviceController.fetchServices(widget.projectId); _fetchRoles(); } Future _fetchRoles() async { try { _isLoadingRoles.value = true; final res = await ApiService.getRoles(); if (res != null) { _roles.assignAll( res.map((e) => JobRole.fromJson(e)).toList(), ); } } finally { _isLoadingRoles.value = false; } } @override void dispose() { Get.delete(tag: 'assign_employee_org'); Get.delete(tag: 'assign_employee_service'); super.dispose(); } Future _openEmployeeSelector() async { final result = await showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (_) => EmployeeSelectionBottomSheet( title: 'Select Employee(s)', multipleSelection: true, initiallySelected: _selectedEmployees.toList(), ), ); if (result != null && result is List) { _selectedEmployees.assignAll(result); } } void _handleAssign() async { if (_selectedEmployees.isEmpty || _selectedRole == null || _serviceController.selectedService == null) { Get.snackbar('Error', 'Please complete all selections'); return; } final allocations = _selectedEmployees .map( (e) => AssignProjectAllocationRequest( employeeId: e.id, projectId: widget.projectId, jobRoleId: _selectedRole!.id, serviceId: _serviceController.selectedService!.id, status: true, ), ) .toList(); final res = await ApiService.assignEmployeesToProject( allocations: allocations, ); if (res?.success == true) { Navigator.of(context).pop(true); // 🔥 triggers refresh } else { Get.snackbar('Error', res?.message ?? 'Assignment failed'); } } @override Widget build(BuildContext context) { return BaseBottomSheet( title: 'Assign Employee', submitText: 'Assign', isSubmitting: false, onCancel: () => Navigator.of(context).pop(), onSubmit: _handleAssign, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ //ORGANIZATION MyText.bodySmall( 'Organization', fontWeight: 600, color: Colors.grey.shade700, ), MySpacing.height(6), OrganizationSelector( controller: _organizationController, height: 44, onSelectionChanged: (Organization? org) async { _selectedOrganization = org; _selectedEmployees.clear(); _selectedRole = null; _serviceController.clearSelection(); }, ), MySpacing.height(20), ///EMPLOYEES (SEARCH) MyText.bodySmall( 'Employees', fontWeight: 600, color: Colors.grey.shade700, ), MySpacing.height(6), Obx( () => InkWell( onTap: _openEmployeeSelector, child: _dropdownBox( _selectedEmployees.isEmpty ? 'Select employee(s)' : '${_selectedEmployees.length} employee(s) selected', icon: Icons.search, ), ), ), MySpacing.height(20), ///SERVICE MyText.bodySmall( 'Service', fontWeight: 600, color: Colors.grey.shade700, ), MySpacing.height(6), ServiceSelector( controller: _serviceController, height: 44, onSelectionChanged: (Service? service) async { _selectedRole = null; }, ), MySpacing.height(20), /// JOB ROLE MyText.bodySmall( 'Job Role', fontWeight: 600, color: Colors.grey.shade700, ), MySpacing.height(6), Obx(() { if (_isLoadingRoles.value) { return _skeleton(); } return PopupMenuButton( onSelected: (role) { _selectedRole = role; setState(() {}); }, itemBuilder: (context) { if (_roles.isEmpty) { return const [ PopupMenuItem( enabled: false, child: Text('No roles found'), ), ]; } return _roles .map( (r) => PopupMenuItem( value: r, child: Text(r.name), ), ) .toList(); }, child: _dropdownBox( _selectedRole?.name ?? 'Select role', ), ); }), ], ), ); } Widget _dropdownBox(String text, {IconData icon = Icons.arrow_drop_down}) { return Container( height: 44, padding: const EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey.shade300), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( text, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 13), ), ), Icon(icon, color: Colors.grey), ], ), ); } Widget _skeleton() { return Container( height: 44, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(12), ), ); } }