import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:marco/controller/task_planning/report_task_controller.dart'; import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; import 'package:marco/helpers/widgets/my_button.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_text_style.dart'; import 'package:marco/helpers/utils/base_bottom_sheet.dart'; class ReportTaskBottomSheet extends StatefulWidget { final Map taskData; final VoidCallback? onReportSuccess; const ReportTaskBottomSheet({ super.key, required this.taskData, this.onReportSuccess, }); @override State createState() => _ReportTaskBottomSheetState(); } class _ReportTaskBottomSheetState extends State with UIMixin { late final ReportTaskController controller; @override void initState() { super.initState(); controller = Get.put( ReportTaskController(), tag: widget.taskData['taskId'] ?? UniqueKey().toString(), ); _preFillFormFields(); } void _preFillFormFields() { final data = widget.taskData; final v = controller.basicValidator; v.getController('assigned_date')?.text = data['assignedOn'] ?? ''; v.getController('assigned_by')?.text = data['assignedBy'] ?? ''; v.getController('work_area')?.text = data['location'] ?? ''; v.getController('activity')?.text = data['activity'] ?? ''; v.getController('team_size')?.text = data['teamSize']?.toString() ?? ''; v.getController('assigned')?.text = data['assigned'] ?? ''; v.getController('task_id')?.text = data['taskId'] ?? ''; v.getController('completed_work')?.clear(); v.getController('comment')?.clear(); } @override Widget build(BuildContext context) { return Obx(() { return BaseBottomSheet( title: "Report Task", isSubmitting: controller.reportStatus.value == ApiStatus.loading, onCancel: () => Navigator.of(context).pop(), onSubmit: _handleSubmit, child: Form( key: controller.basicValidator.formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildRow("Assigned Date", controller.basicValidator.getController('assigned_date')?.text), _buildRow("Assigned By", controller.basicValidator.getController('assigned_by')?.text), _buildRow("Work Area", controller.basicValidator.getController('work_area')?.text), _buildRow("Activity", controller.basicValidator.getController('activity')?.text), _buildRow("Team Size", controller.basicValidator.getController('team_size')?.text), _buildRow( "Assigned", "${controller.basicValidator.getController('assigned')?.text ?? '-'} " "of ${widget.taskData['pendingWork'] ?? '-'} Pending", ), _buildCompletedWorkField(), _buildCommentField(), Obx(() => _buildImageSection()), ], ), ), ); }); } Future _handleSubmit() async { final v = controller.basicValidator; if (v.validateForm()) { final success = await controller.reportTask( projectId: v.getController('task_id')?.text ?? '', comment: v.getController('comment')?.text ?? '', completedTask: int.tryParse(v.getController('completed_work')?.text ?? '') ?? 0, checklist: [], reportedDate: DateTime.now(), images: controller.selectedImages, ); if (success) { widget.onReportSuccess?.call(); } } } Widget _buildRow(String label, String? value) { final icons = { "Assigned Date": Icons.calendar_today_outlined, "Assigned By": Icons.person_outline, "Work Area": Icons.place_outlined, "Activity": Icons.run_circle_outlined, "Team Size": Icons.group_outlined, "Assigned": Icons.assignment_turned_in_outlined, }; return Padding( padding: const EdgeInsets.only(bottom: 16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(icons[label] ?? Icons.info_outline, size: 18, color: Colors.grey[700]), MySpacing.width(8), MyText.titleSmall("$label:", fontWeight: 600), MySpacing.width(12), Expanded( child: MyText.bodyMedium(value?.trim().isNotEmpty == true ? value!.trim() : "-"), ), ], ), ); } Widget _buildCompletedWorkField() { final pending = widget.taskData['pendingWork'] ?? 0; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.work_outline, size: 18, color: Colors.grey[700]), MySpacing.width(8), MyText.titleSmall("Completed Work:", fontWeight: 600), ], ), MySpacing.height(8), TextFormField( controller: controller.basicValidator.getController('completed_work'), keyboardType: TextInputType.number, validator: (value) { if (value == null || value.trim().isEmpty) return 'Please enter completed work'; final completed = int.tryParse(value.trim()); if (completed == null) return 'Enter a valid number'; if (completed > pending) return 'Completed work cannot exceed pending work $pending'; return null; }, decoration: InputDecoration( hintText: "eg: 10", hintStyle: MyTextStyle.bodySmall(xMuted: true), border: outlineInputBorder, enabledBorder: outlineInputBorder, focusedBorder: focusedInputBorder, contentPadding: MySpacing.all(16), isCollapsed: true, floatingLabelBehavior: FloatingLabelBehavior.never, ), ), MySpacing.height(24), ], ); } Widget _buildCommentField() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.comment_outlined, size: 18, color: Colors.grey[700]), MySpacing.width(8), MyText.titleSmall("Comment:", fontWeight: 600), ], ), MySpacing.height(8), TextFormField( controller: controller.basicValidator.getController('comment'), validator: controller.basicValidator.getValidation('comment'), keyboardType: TextInputType.text, decoration: InputDecoration( hintText: "eg: Work done successfully", hintStyle: MyTextStyle.bodySmall(xMuted: true), border: outlineInputBorder, enabledBorder: outlineInputBorder, focusedBorder: focusedInputBorder, contentPadding: MySpacing.all(16), isCollapsed: true, floatingLabelBehavior: FloatingLabelBehavior.never, ), ), MySpacing.height(24), ], ); } Widget _buildImageSection() { final images = controller.selectedImages; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.camera_alt_outlined, size: 18, color: Colors.grey[700]), MySpacing.width(8), MyText.titleSmall("Attach Photos:", fontWeight: 600), ], ), MySpacing.height(12), if (images.isEmpty) Container( height: 70, width: double.infinity, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey.shade300, width: 2), color: Colors.grey.shade100, ), child: Center( child: Icon(Icons.photo_camera_outlined, size: 48, color: Colors.grey.shade400), ), ) else SizedBox( height: 70, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: images.length, separatorBuilder: (_, __) => MySpacing.width(12), itemBuilder: (context, index) { final file = images[index]; return Stack( children: [ GestureDetector( onTap: () { showDialog( context: context, builder: (_) => Dialog( child: InteractiveViewer(child: Image.file(file)), ), ); }, child: ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.file(file, height: 70, width: 70, fit: BoxFit.cover), ), ), Positioned( top: 4, right: 4, child: GestureDetector( onTap: () => controller.removeImageAt(index), child: Container( decoration: BoxDecoration(color: Colors.black54, shape: BoxShape.circle), child: const Icon(Icons.close, size: 20, color: Colors.white), ), ), ), ], ); }, ), ), MySpacing.height(16), Row( children: [ Expanded( child: MyButton.outlined( onPressed: () => controller.pickImages(fromCamera: true), padding: MySpacing.xy(12, 10), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.camera_alt, size: 16, color: Colors.blueAccent), MySpacing.width(6), MyText.bodySmall('Capture', color: Colors.blueAccent), ], ), ), ), MySpacing.width(12), Expanded( child: MyButton.outlined( onPressed: () => controller.pickImages(fromCamera: false), padding: MySpacing.xy(12, 10), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.upload_file, size: 16, color: Colors.blueAccent), MySpacing.width(6), MyText.bodySmall('Upload', color: Colors.blueAccent), ], ), ), ), ], ), ], ); } }