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'; 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 ServiceController serviceController = Get.put(ServiceController()); final TextEditingController targetController = TextEditingController(); final TextEditingController descriptionController = TextEditingController(); final ScrollController _employeeListScrollController = ScrollController(); String? selectedProjectId; 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() { _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: [ // Organization Selector SizedBox( height: 50, child: OrganizationSelector( controller: orgController, onSelectionChanged: (org) async { setState(() => selectedOrganization = org); _resetSelections(); if (selectedProjectId != null) await _fetchEmployeesAndTasks(); }, ), ), MySpacing.height(12), // Service Selector SizedBox( height: 50, child: ServiceSelector( controller: serviceController, onSelectionChanged: (service) async { setState(() => selectedService = service); _resetSelections(); if (selectedProjectId != null) await _fetchEmployeesAndTasks(); }, ), ), MySpacing.height(16), // Work Location Info _infoRow( Icons.location_on, "Work Location", "${widget.buildingName} > ${widget.floorName} > ${widget.workAreaName} > ${widget.activityName}", ), const Divider(), // Pending Task Info _infoRow(Icons.pending_actions, "Pending Task", "${widget.pendingTask}"), const Divider(), // Role Selector 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), // Employee List Container( constraints: const BoxConstraints(maxHeight: 180), decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(6), ), child: _buildEmployeeList(), ), MySpacing.height(8), // Selected Employees Chips _buildSelectedEmployees(), MySpacing.height(8), // Target Input _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), // Description Input _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.isFetchingEmployees.value) { return Center(child: CircularProgressIndicator()); } final filteredEmployees = controller.selectedRoleId.value == null ? controller.employees : controller.employees .where((e) => e.jobRoleID.toString() == controller.selectedRoleId.value) .toList(); if (filteredEmployees.isEmpty) { return Center(child: Text("No employees available for selected role.")); } return Scrollbar( controller: _employeeListScrollController, thumbVisibility: true, child: ListView.builder( controller: _employeeListScrollController, itemCount: filteredEmployees.length, itemBuilder: (context, index) { final employee = filteredEmployees[index]; final rxBool = controller.uploadingStates[employee.id]; return Obx(() => ListTile( dense: true, contentPadding: const EdgeInsets.symmetric(horizontal: 8), leading: Checkbox( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), value: rxBool?.value ?? false, onChanged: (selected) { if (rxBool != null) { rxBool.value = selected ?? false; controller.updateSelectedEmployees(); } }, fillColor: MaterialStateProperty.resolveWith((states) => states.contains(MaterialState.selected) ? const Color.fromARGB(255, 95, 132, 255) : Colors.transparent), checkColor: Colors.white, side: const BorderSide(color: Colors.black), ), title: Text(employee.name, style: const TextStyle(fontSize: 14)), visualDensity: VisualDensity.compact, )); }, ), ); }); } Widget _buildSelectedEmployees() { return Obx(() { if (controller.selectedEmployees.isEmpty) return Container(); return 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: 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), ), ], ); } 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 = 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, assignmentDate: widget.assignmentDate, organizationId: selectedOrganization?.id, serviceId: selectedService?.id, ); } }