// Updated AssignTaskBottomSheet with bottom sheet height fix // Only modified layout for employee selection area to prevent overflow. import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:marco/controller/task_Planning/daily_task_Planning_controller.dart'; import 'package:marco/controller/project_controller.dart'; import 'package:marco/controller/tenant/organization_selection_controller.dart'; import 'package:marco/controller/tenant/service_controller.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_snackbar.dart'; import 'package:marco/helpers/utils/base_bottom_sheet.dart'; import 'package:marco/helpers/widgets/tenant/organization_selector.dart'; import 'package:marco/helpers/widgets/tenant/service_selector.dart'; import 'package:marco/model/attendance/organization_per_project_list_model.dart'; import 'package:marco/model/tenant/tenant_services_model.dart'; import 'package:marco/model/employees/employee_model.dart'; import 'package:marco/model/employees/multiple_select_role_bottomsheet.dart'; class AssignTaskBottomSheet extends StatefulWidget { final String workLocation; final String activityName; final int pendingTask; final String workItemId; final DateTime assignmentDate; final String buildingName; final String floorName; final String workAreaName; const AssignTaskBottomSheet({ super.key, required this.buildingName, required this.workLocation, required this.floorName, required this.workAreaName, required this.activityName, required this.pendingTask, required this.workItemId, required this.assignmentDate, }); @override State createState() => _AssignTaskBottomSheetState(); } class _AssignTaskBottomSheetState extends State { final DailyTaskPlanningController controller = Get.find(); final ProjectController projectController = Get.find(); final OrganizationController orgController = Get.put(OrganizationController()); final OrganizationController orgController = Get.put(OrganizationController()); final ServiceController serviceController = Get.put(ServiceController()); final TextEditingController targetController = TextEditingController(); final TextEditingController descriptionController = TextEditingController(); String? selectedProjectId; String? selectedRoleId; Organization? selectedOrganization; Service? selectedService; @override void initState() { super.initState(); selectedProjectId = projectController.selectedProjectId.value; WidgetsBinding.instance.addPostFrameCallback((_) async { if (selectedProjectId != null) { await orgController.fetchOrganizations(selectedProjectId!); _resetSelections(); await _fetchEmployeesAndTasks(); } }); } void _resetSelections() { controller.selectedEmployees.clear(); controller.uploadingStates.forEach((key, value) => value.value = false); } Future _fetchEmployeesAndTasks() async { await controller.fetchEmployeesByProjectService( projectId: selectedProjectId!, serviceId: selectedService?.id, organizationId: selectedOrganization?.id, ); await controller.fetchTaskData( selectedProjectId, serviceId: selectedService?.id, ); } @override void dispose() { targetController.dispose(); descriptionController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Obx( () => BaseBottomSheet( title: "Assign Task", child: _buildAssignTaskForm(), onCancel: () => Get.back(), onSubmit: _onAssignTaskPressed, isSubmitting: controller.isAssigningTask.value, ), ); } Widget _buildAssignTaskForm() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( height: 50, child: OrganizationSelector( controller: orgController, onSelectionChanged: (org) async { setState(() => selectedOrganization = org); _resetSelections(); if (selectedProjectId != null) await _fetchEmployeesAndTasks(); }, ), ), MySpacing.height(12), SizedBox( height: 50, child: ServiceSelector( controller: serviceController, onSelectionChanged: (service) async { setState(() => selectedService = service); _resetSelections(); if (selectedProjectId != null) await _fetchEmployeesAndTasks(); }, ), ), MySpacing.height(16), _infoRow(Icons.location_on, "Work Location", "${widget.buildingName} > ${widget.floorName} > ${widget.workAreaName} > ${widget.activityName}"), const Divider(), _infoRow( Icons.pending_actions, "Pending Task", "${widget.pendingTask}"), const Divider(), GestureDetector( onTap: _onRoleMenuPressed, child: Row(children: [ MyText.titleMedium("Select Team :", fontWeight: 600), const SizedBox(width: 4), const Icon(Icons.tune, color: Color.fromARGB(255, 95, 132, 255)), ]), ), MySpacing.height(8), /// TEAM SELECT BOX GestureDetector( onTap: _openEmployeeSelectionSheet, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(6), ), child: Row( children: [ const Icon(Icons.group, color: Colors.black54), const SizedBox(width: 10), // Expanded name area Expanded( child: Obx(() { final count = controller.selectedEmployees.length; if (count == 0) { return MyText( "Select team members", color: Colors.grey.shade700, maxLines: 1, overflow: TextOverflow.ellipsis, ); } final names = controller.selectedEmployees .map((e) => e.name) .join(", "); return Text( names, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 14), ); }), ), const Icon(Icons.arrow_drop_down, color: Colors.grey), ], ) ), ), MySpacing.height(8), _buildSelectedEmployees(), MySpacing.height(8), _buildTextField( icon: Icons.track_changes, label: "Target for Today :", controller: targetController, hintText: "Enter target", keyboardType: const TextInputType.numberWithOptions(decimal: true), validatorType: "target", ), MySpacing.height(16), _buildTextField( icon: Icons.description, label: "Description :", controller: descriptionController, hintText: "Enter task description", maxLines: 3, validatorType: "description", ), ], ); } void _onRoleMenuPressed() { final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox; final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox; final Size screenSize = overlay.size; showMenu( context: context, position: RelativeRect.fromLTRB( screenSize.width / 2 - 100, screenSize.height / 2 - 20, screenSize.width / 2 - 100, screenSize.height / 2 - 20, ), items: [ const PopupMenuItem(value: 'all', child: Text("All Roles")), ...controller.roles.map( (role) => PopupMenuItem( value: role['id'].toString(), child: Text(role['name'] ?? 'Unknown Role'), ), ), ], ).then((value) { if (value != null) { selectedRoleId = value == 'all' ? null : value; controller.onRoleSelected(selectedRoleId); } }); } Widget _buildSelectedEmployees() { return Obx(() { if (controller.selectedEmployees.isEmpty) return Container(); return Wrap( spacing: 4, runSpacing: 4, children: controller.selectedEmployees.map((e) { return Chip( label: Text(e.name, style: const TextStyle(color: Colors.white)), backgroundColor: const Color.fromARGB(255, 95, 132, 255), deleteIcon: const Icon(Icons.close, color: Colors.white), onDeleted: () { controller.selectedEmployees.remove(e); }, ); }).toList(), ); }); } Widget _buildTextField({ required IconData icon, required String label, required TextEditingController controller, required String hintText, TextInputType keyboardType = TextInputType.text, int maxLines = 1, required String validatorType, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Icon(icon, size: 18, color: Colors.black54), const SizedBox(width: 6), MyText.titleMedium(label, fontWeight: 600), ]), MySpacing.height(6), TextFormField( controller: controller, keyboardType: keyboardType, maxLines: maxLines, decoration: InputDecoration( hintText: hintText, border: OutlineInputBorder( borderRadius: BorderRadius.circular(6), ), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), validator: (value) => this .controller .formFieldValidator(value, fieldType: validatorType), validator: (value) => this .controller .formFieldValidator(value, fieldType: validatorType), ), ], ); } Widget _infoRow(IconData icon, String title, String value) { return Padding( padding: MySpacing.y(6), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(icon, size: 20, color: Colors.grey[700]), const SizedBox(width: 8), Expanded( child: RichText( text: TextSpan( children: [ WidgetSpan( child: MyText.titleMedium( "$title: ", fontWeight: 600, color: Colors.black, ), ), TextSpan( text: value, style: const TextStyle(color: Colors.black), ), ], ), ), ) ], ), ); } Future _openEmployeeSelectionSheet() async { final result = await showModalBottomSheet>( context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (context) { return DraggableScrollableSheet( expand: false, initialChildSize: 0.85, minChildSize: 0.6, maxChildSize: 1.0, builder: (_, scrollController) { return Container( decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: MultipleSelectRoleBottomSheet( projectId: selectedProjectId!, organizationId: selectedOrganization?.id, serviceId: selectedService?.id, roleId: selectedRoleId, initiallySelected: controller.selectedEmployees.toList(), scrollController: scrollController, ), ); }, ); }, ); if (result != null) { controller.selectedEmployees.assignAll(result); controller.updateSelectedEmployees(); } } void _onAssignTaskPressed() { final selectedTeam = controller.selectedEmployees; if (selectedTeam.isEmpty) { showAppSnackbar( title: "Team Required", message: "Please select at least one team member", type: SnackbarType.error, ); return; } final target = double.tryParse(targetController.text.trim()); if (target == null || target <= 0) { showAppSnackbar( title: "Invalid Input", message: "Please enter a valid target number", type: SnackbarType.error, ); return; } if (target > widget.pendingTask) { showAppSnackbar( title: "Target Too High", message: "Target cannot exceed pending task (${widget.pendingTask})", type: SnackbarType.error, ); return; } final description = descriptionController.text.trim(); if (description.isEmpty) { showAppSnackbar( title: "Description Required", message: "Please enter a description", type: SnackbarType.error, ); return; } controller.assignDailyTask( workItemId: widget.workItemId, plannedTask: target.toInt(), description: description, taskTeam: selectedTeam.map((e) => e.id).toList(), // pass IDs assignmentDate: widget.assignmentDate, organizationId: selectedOrganization?.id, serviceId: selectedService?.id, ); } }