From 7dd47ce460c59bfcd4401347cdf97cf77d8a3f21 Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Sat, 2 Aug 2025 17:29:29 +0530 Subject: [PATCH] refactor: Simplify ReportActionBottomSheet by removing unused imports, optimizing widget structure, and enhancing form handling for improved readability and maintainability. --- .../report_action_bottom_sheet.dart | 760 ++++++++---------- 1 file changed, 329 insertions(+), 431 deletions(-) diff --git a/lib/model/dailyTaskPlaning/report_action_bottom_sheet.dart b/lib/model/dailyTaskPlaning/report_action_bottom_sheet.dart index 6621335..84974ce 100644 --- a/lib/model/dailyTaskPlaning/report_action_bottom_sheet.dart +++ b/lib/model/dailyTaskPlaning/report_action_bottom_sheet.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:marco/controller/task_planing/report_task_action_controller.dart'; -import 'package:marco/helpers/theme/app_theme.dart'; import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_text.dart'; @@ -11,6 +10,7 @@ import 'package:marco/helpers/widgets/my_team_model_sheet.dart'; import 'package:marco/helpers/widgets/image_viewer_dialog.dart'; import 'package:marco/model/dailyTaskPlaning/create_task_botom_sheet.dart'; import 'package:marco/model/dailyTaskPlaning/report_action_widgets.dart'; +import 'package:marco/helpers/utils/base_bottom_sheet.dart'; class ReportActionBottomSheet extends StatefulWidget { final Map taskData; @@ -44,8 +44,6 @@ class _ReportActionBottomSheetState extends State with UIMixin { late ReportTaskActionController controller; - final ScrollController _scrollController = ScrollController(); - String selectedAction = 'Select Action'; @override void initState() { super.initState(); @@ -54,9 +52,9 @@ class _ReportActionBottomSheetState extends State tag: widget.taskData['taskId'] ?? '', ); controller.fetchWorkStatuses(); - controller.basicValidator.getController('approved_task')?.text = - widget.taskData['approvedTask']?.toString() ?? ''; final data = widget.taskData; + controller.basicValidator.getController('approved_task')?.text = + data['approvedTask']?.toString() ?? ''; controller.basicValidator.getController('assigned_date')?.text = data['assignedOn'] ?? ''; controller.basicValidator.getController('assigned_by')?.text = @@ -73,445 +71,348 @@ class _ReportActionBottomSheetState extends State (data['teamMembers'] as List).join(', '); controller.basicValidator.getController('assigned')?.text = data['assigned'] ?? ''; - controller.basicValidator.getController('task_id')?.text = - data['taskId'] ?? ''; - controller.basicValidator.getController('comment')?.clear(); controller.basicValidator.getController('task_id')?.text = widget.taskDataId; - + controller.basicValidator.getController('comment')?.clear(); controller.selectedImages.clear(); - - WidgetsBinding.instance.addPostFrameCallback((_) { - if (_scrollController.hasClients) { - _scrollController.jumpTo(_scrollController.position.maxScrollExtent); - } - }); } @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), - ), - child: SingleChildScrollView( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom + 24, - left: 24, - right: 24, - top: 12, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Drag handle - Container( - width: 40, - height: 4, - margin: const EdgeInsets.only(bottom: 12), - decoration: BoxDecoration( - color: Colors.grey.shade400, - borderRadius: BorderRadius.circular(2), - ), + return GetBuilder( + tag: widget.taskData['taskId'] ?? '', + builder: (controller) { + return BaseBottomSheet( + title: "Take Report Action", + isSubmitting: controller.isLoading.value, + onCancel: () => Navigator.of(context).pop(), + onSubmit: () async {}, // not used since buttons moved + showButtons: false, // disable internal buttons + child: _buildForm(context, controller), + ); + }, + ); + } + + Widget _buildForm( + BuildContext context, ReportTaskActionController controller) { + return Form( + key: controller.basicValidator.formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 📋 Task Details + buildRow("Assigned By", + controller.basicValidator.getController('assigned_by')?.text, + icon: Icons.person_outline), + buildRow("Work Area", + controller.basicValidator.getController('work_area')?.text, + icon: Icons.place_outlined), + buildRow("Activity", + controller.basicValidator.getController('activity')?.text, + icon: Icons.assignment_outlined), + buildRow("Planned Work", + controller.basicValidator.getController('planned_work')?.text, + icon: Icons.schedule_outlined), + buildRow("Completed Work", + controller.basicValidator.getController('completed_work')?.text, + icon: Icons.done_all_outlined), + buildTeamMembers(), + MySpacing.height(8), + + // ✅ Approved Task Field + Row( + children: [ + Icon(Icons.check_circle_outline, + size: 18, color: Colors.grey[700]), + MySpacing.width(8), + MyText.titleSmall("Approved Task:", fontWeight: 600), + ], + ), + MySpacing.height(10), + TextFormField( + controller: + controller.basicValidator.getController('approved_task'), + keyboardType: TextInputType.number, + validator: (value) { + if (value == null || value.isEmpty) return 'Required'; + if (int.tryParse(value) == null) return 'Must be a number'; + return null; + }, + decoration: InputDecoration( + hintText: "eg: 5", + hintStyle: MyTextStyle.bodySmall(xMuted: true), + border: outlineInputBorder, + contentPadding: MySpacing.all(16), + floatingLabelBehavior: FloatingLabelBehavior.never, ), - GetBuilder( - tag: widget.taskData['taskId'] ?? '', - builder: (controller) { - return Form( - key: controller.basicValidator.formKey, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 4.0, vertical: 8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + ), + + MySpacing.height(10), + if ((widget.taskData['reportedPreSignedUrls'] as List?) + ?.isNotEmpty == + true) + buildReportedImagesSection( + imageUrls: List.from( + widget.taskData['reportedPreSignedUrls'] ?? []), + context: context, + ), + + MySpacing.height(10), + MyText.titleSmall("Report Actions", fontWeight: 600), + MySpacing.height(10), + + Obx(() { + if (controller.isLoadingWorkStatus.value) + return const CircularProgressIndicator(); + return PopupMenuButton( + onSelected: (String value) { + controller.selectedWorkStatusName.value = value; + controller.showAddTaskCheckbox.value = true; + }, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12)), + itemBuilder: (BuildContext context) { + return controller.workStatus.map((status) { + return PopupMenuItem( + value: status.name, + child: Row( children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MyText.titleMedium( - "Take Report Action", - fontWeight: 600, - fontSize: 18, - ), - ], + Radio( + value: status.name, + groupValue: controller.selectedWorkStatusName.value, + onChanged: (_) => Navigator.pop(context, status.name), ), - MySpacing.height(24), - buildRow( - "Assigned By", - controller.basicValidator - .getController('assigned_by') - ?.text - .trim(), - icon: Icons.person_outline, - ), - buildRow( - "Work Area", - controller.basicValidator - .getController('work_area') - ?.text - .trim(), - icon: Icons.place_outlined, - ), - buildRow( - "Activity", - controller.basicValidator - .getController('activity') - ?.text - .trim(), - icon: Icons.assignment_outlined, - ), - buildRow( - "Planned Work", - controller.basicValidator - .getController('planned_work') - ?.text - .trim(), - icon: Icons.schedule_outlined, - ), - buildRow( - "Completed Work", - controller.basicValidator - .getController('completed_work') - ?.text - .trim(), - icon: Icons.done_all_outlined, - ), - buildTeamMembers(), - MySpacing.height(8), - Row( - children: [ - Icon(Icons.check_circle_outline, - size: 18, color: Colors.grey[700]), - MySpacing.width(8), - MyText.titleSmall( - "Approved Task:", - fontWeight: 600, - ), - ], - ), - MySpacing.height(10), - TextFormField( - controller: controller.basicValidator - .getController('approved_task'), - keyboardType: TextInputType.number, - validator: (value) { - if (value == null || value.isEmpty) - return 'Required'; - if (int.tryParse(value) == null) - return 'Must be a number'; - return null; - }, - decoration: InputDecoration( - hintText: "eg: 5", - hintStyle: MyTextStyle.bodySmall(xMuted: true), - border: outlineInputBorder, - enabledBorder: outlineInputBorder, - focusedBorder: focusedInputBorder, - contentPadding: MySpacing.all(16), - isCollapsed: true, - floatingLabelBehavior: FloatingLabelBehavior.never, - ), - ), - MySpacing.height(10), - - if ((widget.taskData['reportedPreSignedUrls'] - as List?) - ?.isNotEmpty == - true) - buildReportedImagesSection( - imageUrls: List.from( - widget.taskData['reportedPreSignedUrls'] ?? []), - context: context, - ), - MySpacing.height(10), - // Add this in your stateful widget - MyText.titleSmall( - "Report Actions", - fontWeight: 600, - ), - MySpacing.height(10), - - Obx(() { - final isLoading = - controller.isLoadingWorkStatus.value; - final workStatuses = controller.workStatus; - - if (isLoading) { - return const Center( - child: CircularProgressIndicator()); - } - - return PopupMenuButton( - onSelected: (String value) { - controller.selectedWorkStatusName.value = value; - controller.showAddTaskCheckbox.value = true; - }, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - itemBuilder: (BuildContext context) { - return workStatuses.map((status) { - final statusName = status.name; - - return PopupMenuItem( - value: statusName, - child: Row( - children: [ - Radio( - value: statusName, - groupValue: controller - .selectedWorkStatusName.value, - onChanged: (_) => - Navigator.pop(context, statusName), - ), - const SizedBox(width: 8), - MyText.bodySmall(statusName), - ], - ), - ); - }).toList(); - }, - child: Container( - padding: MySpacing.xy(16, 12), - decoration: BoxDecoration( - border: Border.all(color: Colors.grey.shade400), - borderRadius: BorderRadius.circular( - AppStyle.buttonRadius.medium), - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - MyText.bodySmall( - controller.selectedWorkStatusName.value - .isEmpty - ? "Select Work Status" - : controller - .selectedWorkStatusName.value, - color: Colors.black87, - ), - const Icon(Icons.arrow_drop_down, size: 20), - ], - ), - ), - ); - }), - MySpacing.height(10), - Obx(() { - if (!controller.showAddTaskCheckbox.value) - return SizedBox.shrink(); - - final checkboxTheme = CheckboxThemeData( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(2)), - side: WidgetStateBorderSide.resolveWith((states) => - BorderSide( - color: states.contains(WidgetState.selected) - ? Colors.transparent - : Colors.black)), - fillColor: WidgetStateProperty.resolveWith( - (states) => - states.contains(WidgetState.selected) - ? Colors.blueAccent - : Colors.white), - checkColor: - WidgetStateProperty.all(Colors.white), - ); - - return Theme( - data: Theme.of(context) - .copyWith(checkboxTheme: checkboxTheme), - child: CheckboxListTile( - title: MyText.titleSmall( - "Add new task", - fontWeight: 600, - ), - value: controller.isAddTaskChecked.value, - onChanged: (val) => controller - .isAddTaskChecked.value = val ?? false, - controlAffinity: ListTileControlAffinity.leading, - contentPadding: EdgeInsets.zero, - ), - ); - }), - MySpacing.height(10), - Row( - children: [ - Icon(Icons.comment_outlined, - size: 18, color: Colors.grey[700]), - MySpacing.width(8), - MyText.titleSmall( - "Comment:", - fontWeight: 600, - ), - ], - ), - MySpacing.height(8), - TextFormField( - validator: controller.basicValidator - .getValidation('comment'), - controller: controller.basicValidator - .getController('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(16), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Icon(Icons.camera_alt_outlined, - size: 18, color: Colors.grey[700]), - MySpacing.width(8), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MyText.titleSmall("Attach Photos:", - fontWeight: 600), - MySpacing.height(12), - ], - ), - ), - ], - ), - Obx(() { - final images = controller.selectedImages; - - return buildImagePickerSection( - images: images, - onCameraTap: () => - controller.pickImages(fromCamera: true), - onUploadTap: () => - controller.pickImages(fromCamera: false), - onRemoveImage: (index) => - controller.removeImageAt(index), - onPreviewImage: (index) { - showDialog( - context: context, - builder: (_) => ImageViewerDialog( - imageSources: images, - initialIndex: index, - ), - ); - }, - ); - }), - MySpacing.height(24), - buildCommentActionButtons( - onCancel: () => Navigator.of(context).pop(), - onSubmit: () async { - if (controller.basicValidator.validateForm()) { - final selectedStatusName = - controller.selectedWorkStatusName.value; - final selectedStatus = - controller.workStatus.firstWhereOrNull( - (status) => status.name == selectedStatusName, - ); - - final reportActionId = - selectedStatus?.id.toString() ?? ''; - final approvedTaskCount = controller - .basicValidator - .getController('approved_task') - ?.text - .trim() ?? - ''; - - final shouldShowAddTaskSheet = - controller.isAddTaskChecked.value; - - final success = await controller.approveTask( - projectId: controller.basicValidator - .getController('task_id') - ?.text ?? - '', - comment: controller.basicValidator - .getController('comment') - ?.text ?? - '', - images: controller.selectedImages, - reportActionId: reportActionId, - approvedTaskCount: approvedTaskCount, - ); - if (success) { - Navigator.of(context).pop(); - if (shouldShowAddTaskSheet) { - await Future.delayed( - Duration(milliseconds: 100)); - showCreateTaskBottomSheet( - workArea: widget.taskData['location'] ?? '', - activity: widget.taskData['activity'] ?? '', - completedWork: - widget.taskData['completedWork'] ?? '', - unit: widget.taskData['unit'] ?? '', - onCategoryChanged: (category) { - debugPrint( - "Category changed to: $category"); - }, - parentTaskId: widget.taskDataId, - plannedTask: int.tryParse( - widget.taskData['plannedWork'] ?? - '0') ?? - 0, - activityId: widget.activityId, - workAreaId: widget.workAreaId, - onSubmit: () { - Navigator.of(context).pop(); - }, - ); - } - widget.onReportSuccess.call(); - } - } - }, - isLoading: controller.isLoading, - ), - - MySpacing.height(20), - if ((widget.taskData['taskComments'] as List?) - ?.isNotEmpty == - true) ...[ - Row( - children: [ - MySpacing.width(10), - Icon(Icons.chat_bubble_outline, - size: 18, color: Colors.grey[700]), - MySpacing.width(8), - MyText.titleSmall( - "Comments", - fontWeight: 600, - ), - ], - ), - MySpacing.height(12), - Builder( - builder: (context) { - final comments = List>.from( - widget.taskData['taskComments'] as List, - ); - return buildCommentList( - comments, context, timeAgo); - }, - ) - ], + const SizedBox(width: 8), + MyText.bodySmall(status.name), ], ), + ); + }).toList(); + }, + child: Container( + padding: MySpacing.xy(16, 12), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade400), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + MyText.bodySmall( + controller.selectedWorkStatusName.value.isEmpty + ? "Select Work Status" + : controller.selectedWorkStatusName.value, + color: Colors.black87, + ), + const Icon(Icons.arrow_drop_down, size: 20), + ], + ), + ), + ); + }), + + MySpacing.height(10), + + Obx(() { + if (!controller.showAddTaskCheckbox.value) + return const SizedBox.shrink(); + return CheckboxListTile( + title: MyText.titleSmall("Add new task", fontWeight: 600), + value: controller.isAddTaskChecked.value, + onChanged: (val) => + controller.isAddTaskChecked.value = val ?? false, + controlAffinity: ListTileControlAffinity.leading, + contentPadding: EdgeInsets.zero, + ); + }), + + MySpacing.height(24), + + // ✏️ Comment Field + Row( + children: [ + Icon(Icons.comment_outlined, size: 18, color: Colors.grey[700]), + MySpacing.width(8), + MyText.titleSmall("Comment:", fontWeight: 600), + ], + ), + MySpacing.height(8), + TextFormField( + validator: controller.basicValidator.getValidation('comment'), + controller: controller.basicValidator.getController('comment'), + keyboardType: TextInputType.text, + decoration: InputDecoration( + hintText: "eg: Work done successfully", + hintStyle: MyTextStyle.bodySmall(xMuted: true), + border: outlineInputBorder, + contentPadding: MySpacing.all(16), + floatingLabelBehavior: FloatingLabelBehavior.never, + ), + ), + + MySpacing.height(16), + + // 📸 Image Attachments + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(Icons.camera_alt_outlined, + size: 18, color: Colors.grey[700]), + MySpacing.width(8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MyText.titleSmall("Attach Photos:", fontWeight: 600), + MySpacing.height(12), + ], + ), + ), + ], + ), + Obx(() { + final images = controller.selectedImages; + return buildImagePickerSection( + images: images, + onCameraTap: () => controller.pickImages(fromCamera: true), + onUploadTap: () => controller.pickImages(fromCamera: false), + onRemoveImage: (index) => controller.removeImageAt(index), + onPreviewImage: (index) { + showDialog( + context: context, + builder: (_) => ImageViewerDialog( + imageSources: images, + initialIndex: index, ), ); }, + ); + }), + + MySpacing.height(12), + + // ✅ Submit/Cancel Buttons moved here + Row( + children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: () => Navigator.of(context).pop(), + icon: const Icon(Icons.close, color: Colors.white), + label: MyText.bodyMedium("Cancel", + color: Colors.white, fontWeight: 600), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.grey, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12)), + padding: const EdgeInsets.symmetric(vertical: 8), + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: ElevatedButton.icon( + onPressed: controller.isLoading.value + ? null + : () async { + if (controller.basicValidator.validateForm()) { + final selectedStatusName = + controller.selectedWorkStatusName.value; + final selectedStatus = controller.workStatus + .firstWhereOrNull( + (s) => s.name == selectedStatusName); + final reportActionId = + selectedStatus?.id.toString() ?? ''; + final approvedTaskCount = controller.basicValidator + .getController('approved_task') + ?.text + .trim() ?? + ''; + + final shouldShowAddTaskSheet = + controller.isAddTaskChecked.value; + + final success = await controller.approveTask( + projectId: controller.basicValidator + .getController('task_id') + ?.text ?? + '', + comment: controller.basicValidator + .getController('comment') + ?.text ?? + '', + images: controller.selectedImages, + reportActionId: reportActionId, + approvedTaskCount: approvedTaskCount, + ); + + if (success) { + Navigator.of(context).pop(); + if (shouldShowAddTaskSheet) { + await Future.delayed( + const Duration(milliseconds: 100)); + showCreateTaskBottomSheet( + workArea: widget.taskData['location'] ?? '', + activity: widget.taskData['activity'] ?? '', + completedWork: + widget.taskData['completedWork'] ?? '', + unit: widget.taskData['unit'] ?? '', + parentTaskId: widget.taskDataId, + plannedTask: int.tryParse( + widget.taskData['plannedWork'] ?? + '0') ?? + 0, + activityId: widget.activityId, + workAreaId: widget.workAreaId, + onSubmit: () => Navigator.of(context).pop(), + onCategoryChanged: (category) {}, + ); + } + widget.onReportSuccess.call(); + } + } + }, + icon: const Icon(Icons.check_circle_outline, + color: Colors.white), + label: MyText.bodyMedium( + controller.isLoading.value ? "Submitting..." : "Submit", + color: Colors.white, + fontWeight: 600, + ), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.indigo, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12)), + padding: const EdgeInsets.symmetric(vertical: 8), + ), + ), + ), + ], + ), + + MySpacing.height(12), + + // 💬 Previous Comments List (only below submit) + if ((widget.taskData['taskComments'] as List?)?.isNotEmpty == + true) ...[ + Row( + children: [ + MySpacing.width(10), + Icon(Icons.chat_bubble_outline, + size: 18, color: Colors.grey[700]), + MySpacing.width(8), + MyText.titleSmall("Comments", fontWeight: 600), + ], + ), + MySpacing.height(12), + buildCommentList( + List>.from( + widget.taskData['taskComments'] as List), + context, + timeAgo, ), ], - ), + ], ), ); } @@ -530,10 +431,7 @@ class _ReportActionBottomSheetState extends State child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - MyText.titleSmall( - "Team Members:", - fontWeight: 600, - ), + MyText.titleSmall("Team Members:", fontWeight: 600), MySpacing.width(12), GestureDetector( onTap: () {