import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:marco/controller/task_planing/daily_task_planing_controller.dart'; import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_snackbar.dart'; import 'package:marco/controller/project_controller.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 DailyTaskPlaningController controller = Get.find(); final ProjectController projectController = Get.find(); final TextEditingController targetController = TextEditingController(); final TextEditingController descriptionController = TextEditingController(); String? selectedProjectId; final ScrollController _employeeListScrollController = ScrollController(); @override void dispose() { _employeeListScrollController.dispose(); targetController.dispose(); descriptionController.dispose(); super.dispose(); } @override void initState() { super.initState(); selectedProjectId = projectController.selectedProjectId.value; WidgetsBinding.instance.addPostFrameCallback((_) { if (selectedProjectId != null) { controller.fetchEmployeesByProject(selectedProjectId!); } }); } @override Widget build(BuildContext context) { return SafeArea( child: Container( padding: MediaQuery.of(context).viewInsets.add(MySpacing.all(16)), decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Icon(Icons.assignment, color: Colors.black54), SizedBox(width: 8), MyText.titleMedium("Assign Task", fontSize: 18, fontWeight: 600), ], ), IconButton( icon: const Icon(Icons.close), onPressed: () => Get.back(), ), ], ), Divider(), _infoRow(Icons.location_on, "Work Location", "${widget.buildingName} > ${widget.floorName} > ${widget.workAreaName} > ${widget.activityName}"), Divider(), _infoRow(Icons.pending_actions, "Pending Task of Activity", "${widget.pendingTask}"), Divider(), GestureDetector( onTap: () { 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) { return PopupMenuItem( value: role['id'].toString(), child: Text(role['name'] ?? 'Unknown Role'), ); }), ], ).then((value) { if (value != null) { controller.onRoleSelected(value == 'all' ? null : value); } }); }, child: Row( children: [ MyText.titleMedium("Select Team :", fontWeight: 600), const SizedBox(width: 4), Icon(Icons.filter_alt, color: const Color.fromARGB(255, 95, 132, 255)), ], ), ), MySpacing.height(8), Container( constraints: BoxConstraints(maxHeight: 150), child: _buildEmployeeList(), ), MySpacing.height(8), Obx(() { if (controller.selectedEmployees.isEmpty) return Container(); return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Wrap( spacing: 4, runSpacing: 4, children: controller.selectedEmployees.map((e) { return Obx(() { final isSelected = controller.uploadingStates[e.id]?.value ?? false; if (!isSelected) return Container(); 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.uploadingStates[e.id]?.value = false; controller.updateSelectedEmployees(); }, ); }); }).toList(), ), ); }), _buildTextField( icon: Icons.track_changes, label: "Target for Today :", controller: targetController, hintText: "Enter target", keyboardType: TextInputType.number, validatorType: "target", ), MySpacing.height(24), _buildTextField( icon: Icons.description, label: "Description :", controller: descriptionController, hintText: "Enter task description", maxLines: 3, validatorType: "description", ), MySpacing.height(24), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ OutlinedButton.icon( onPressed: () => Get.back(), icon: const Icon(Icons.close, color: Colors.red), label: MyText.bodyMedium("Cancel", color: Colors.red), style: OutlinedButton.styleFrom( side: const BorderSide(color: Colors.red), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 14), ), ), ElevatedButton.icon( onPressed: _onAssignTaskPressed, icon: const Icon(Icons.check_circle_outline, color: Colors.white), label: MyText.bodyMedium("Assign Task", color: Colors.white), style: ElevatedButton.styleFrom( backgroundColor: Colors.indigo, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), padding: const EdgeInsets.symmetric( horizontal: 28, vertical: 14), ), ), ], ), ], ), ), ), ); } Widget _buildEmployeeList() { return Obx(() { if (controller.isLoading.value) { return const Center(child: CircularProgressIndicator()); } final selectedRoleId = controller.selectedRoleId.value; final filteredEmployees = selectedRoleId == null ? controller.employees : controller.employees .where((e) => e.jobRoleID.toString() == selectedRoleId) .toList(); if (filteredEmployees.isEmpty) { return const Text("No employees found for selected role."); } return Scrollbar( controller: _employeeListScrollController, thumbVisibility: true, interactive: true, child: ListView.builder( controller: _employeeListScrollController, shrinkWrap: true, physics: const AlwaysScrollableScrollPhysics(), itemCount: filteredEmployees.length, itemBuilder: (context, index) { final employee = filteredEmployees[index]; final rxBool = controller.uploadingStates[employee.id]; return Obx(() => Padding( padding: const EdgeInsets.symmetric(vertical: 0), child: Row( children: [ Theme( data: Theme.of(context) .copyWith(unselectedWidgetColor: Colors.black), child: Checkbox( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(4), side: const BorderSide(color: Colors.black), ), value: rxBool?.value ?? false, onChanged: (bool? selected) { if (rxBool != null) { rxBool.value = selected ?? false; controller.updateSelectedEmployees(); } }, fillColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return const Color.fromARGB(255, 95, 132, 255); } return Colors.transparent; }), checkColor: Colors.white, side: const BorderSide(color: Colors.black), ), ), const SizedBox(width: 8), Expanded( child: Text(employee.name, style: TextStyle(fontSize: 14))), ], ), )); }, ), ); }); } 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: const OutlineInputBorder(), ), 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), ), ], ), ), ), ], ), ); } void _onAssignTaskPressed() { final selectedTeam = controller.uploadingStates.entries .where((e) => e.value.value) .map((e) => e.key) .toList(); if (selectedTeam.isEmpty) { showAppSnackbar( title: "Team Required", message: "Please select at least one team member", type: SnackbarType.error, ); return; } final target = int.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 be greater than 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, description: description, taskTeam: selectedTeam, assignmentDate: widget.assignmentDate, ); } }