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/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'; 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 TextEditingController targetController = TextEditingController(); final TextEditingController descriptionController = TextEditingController(); final ScrollController _employeeListScrollController = ScrollController(); String? selectedProjectId; @override void initState() { super.initState(); selectedProjectId = projectController.selectedProjectId.value; WidgetsBinding.instance.addPostFrameCallback((_) { if (selectedProjectId != null) { controller.fetchEmployeesByProject(selectedProjectId!); } }); } @override void dispose() { _employeeListScrollController.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: [ _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: _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), Container( constraints: const BoxConstraints(maxHeight: 150), child: _buildEmployeeList(), ), MySpacing.height(8), _buildSelectedEmployees(), _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", ), ], ); } void _onRoleMenuPressed() { 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); } }); } Widget _buildEmployeeList() { return Obx(() { if (controller.isLoading.value) { // Skeleton loader instead of CircularProgressIndicator return ListView.separated( shrinkWrap: true, itemCount: 5, // show 5 skeleton rows separatorBuilder: (_, __) => const SizedBox(height: 4), itemBuilder: (context, index) { return Padding( padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 8), child: Row( children: [ Container( width: 24, height: 24, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(4), ), ), const SizedBox(width: 8), Expanded( child: Container( height: 14, color: Colors.grey.shade300, ), ), ], ), ); }, ); } 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, child: ListView.builder( controller: _employeeListScrollController, shrinkWrap: true, itemCount: filteredEmployees.length, itemBuilder: (context, index) { final employee = filteredEmployees[index]; final rxBool = controller.uploadingStates[employee.id]; return Obx(() => Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( children: [ Checkbox( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(4), ), 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: const TextStyle(fontSize: 14))), ], ), )); }, ), ); }); } Widget _buildSelectedEmployees() { return 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(), ), ); }); } 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: const InputDecoration( hintText: '', border: 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, ); } }