Vaibhav_Feature-#768 #59

Closed
vaibhav.surve wants to merge 74 commits from Vaibhav_Feature-#768 into Feature_Expense
Showing only changes of commit 7dd47ce460 - Show all commits

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:marco/controller/task_planing/report_task_action_controller.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/utils/mixins/ui_mixin.dart';
import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_spacing.dart';
import 'package:marco/helpers/widgets/my_text.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/helpers/widgets/image_viewer_dialog.dart';
import 'package:marco/model/dailyTaskPlaning/create_task_botom_sheet.dart'; import 'package:marco/model/dailyTaskPlaning/create_task_botom_sheet.dart';
import 'package:marco/model/dailyTaskPlaning/report_action_widgets.dart'; import 'package:marco/model/dailyTaskPlaning/report_action_widgets.dart';
import 'package:marco/helpers/utils/base_bottom_sheet.dart';
class ReportActionBottomSheet extends StatefulWidget { class ReportActionBottomSheet extends StatefulWidget {
final Map<String, dynamic> taskData; final Map<String, dynamic> taskData;
@ -44,8 +44,6 @@ class _ReportActionBottomSheetState extends State<ReportActionBottomSheet>
with UIMixin { with UIMixin {
late ReportTaskActionController controller; late ReportTaskActionController controller;
final ScrollController _scrollController = ScrollController();
String selectedAction = 'Select Action';
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -54,9 +52,9 @@ class _ReportActionBottomSheetState extends State<ReportActionBottomSheet>
tag: widget.taskData['taskId'] ?? '', tag: widget.taskData['taskId'] ?? '',
); );
controller.fetchWorkStatuses(); controller.fetchWorkStatuses();
controller.basicValidator.getController('approved_task')?.text =
widget.taskData['approvedTask']?.toString() ?? '';
final data = widget.taskData; final data = widget.taskData;
controller.basicValidator.getController('approved_task')?.text =
data['approvedTask']?.toString() ?? '';
controller.basicValidator.getController('assigned_date')?.text = controller.basicValidator.getController('assigned_date')?.text =
data['assignedOn'] ?? ''; data['assignedOn'] ?? '';
controller.basicValidator.getController('assigned_by')?.text = controller.basicValidator.getController('assigned_by')?.text =
@ -73,150 +71,85 @@ class _ReportActionBottomSheetState extends State<ReportActionBottomSheet>
(data['teamMembers'] as List<dynamic>).join(', '); (data['teamMembers'] as List<dynamic>).join(', ');
controller.basicValidator.getController('assigned')?.text = controller.basicValidator.getController('assigned')?.text =
data['assigned'] ?? ''; data['assigned'] ?? '';
controller.basicValidator.getController('task_id')?.text =
data['taskId'] ?? '';
controller.basicValidator.getController('comment')?.clear();
controller.basicValidator.getController('task_id')?.text = controller.basicValidator.getController('task_id')?.text =
widget.taskDataId; widget.taskDataId;
controller.basicValidator.getController('comment')?.clear();
controller.selectedImages.clear(); controller.selectedImages.clear();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_scrollController.hasClients) {
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
}
});
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return GetBuilder<ReportTaskActionController>(
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),
),
),
GetBuilder<ReportTaskActionController>(
tag: widget.taskData['taskId'] ?? '', tag: widget.taskData['taskId'] ?? '',
builder: (controller) { 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( return Form(
key: controller.basicValidator.formKey, key: controller.basicValidator.formKey,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 4.0, vertical: 8),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( // 📋 Task Details
mainAxisAlignment: MainAxisAlignment.center, buildRow("Assigned By",
children: [ controller.basicValidator.getController('assigned_by')?.text,
MyText.titleMedium( icon: Icons.person_outline),
"Take Report Action", buildRow("Work Area",
fontWeight: 600, controller.basicValidator.getController('work_area')?.text,
fontSize: 18, icon: Icons.place_outlined),
), buildRow("Activity",
], controller.basicValidator.getController('activity')?.text,
), icon: Icons.assignment_outlined),
MySpacing.height(24), buildRow("Planned Work",
buildRow( controller.basicValidator.getController('planned_work')?.text,
"Assigned By", icon: Icons.schedule_outlined),
controller.basicValidator buildRow("Completed Work",
.getController('assigned_by') controller.basicValidator.getController('completed_work')?.text,
?.text icon: Icons.done_all_outlined),
.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(), buildTeamMembers(),
MySpacing.height(8), MySpacing.height(8),
// Approved Task Field
Row( Row(
children: [ children: [
Icon(Icons.check_circle_outline, Icon(Icons.check_circle_outline,
size: 18, color: Colors.grey[700]), size: 18, color: Colors.grey[700]),
MySpacing.width(8), MySpacing.width(8),
MyText.titleSmall( MyText.titleSmall("Approved Task:", fontWeight: 600),
"Approved Task:",
fontWeight: 600,
),
], ],
), ),
MySpacing.height(10), MySpacing.height(10),
TextFormField( TextFormField(
controller: controller.basicValidator controller:
.getController('approved_task'), controller.basicValidator.getController('approved_task'),
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) if (value == null || value.isEmpty) return 'Required';
return 'Required'; if (int.tryParse(value) == null) return 'Must be a number';
if (int.tryParse(value) == null)
return 'Must be a number';
return null; return null;
}, },
decoration: InputDecoration( decoration: InputDecoration(
hintText: "eg: 5", hintText: "eg: 5",
hintStyle: MyTextStyle.bodySmall(xMuted: true), hintStyle: MyTextStyle.bodySmall(xMuted: true),
border: outlineInputBorder, border: outlineInputBorder,
enabledBorder: outlineInputBorder,
focusedBorder: focusedInputBorder,
contentPadding: MySpacing.all(16), contentPadding: MySpacing.all(16),
isCollapsed: true,
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
), ),
), ),
MySpacing.height(10),
if ((widget.taskData['reportedPreSignedUrls'] MySpacing.height(10),
as List<dynamic>?) if ((widget.taskData['reportedPreSignedUrls'] as List<dynamic>?)
?.isNotEmpty == ?.isNotEmpty ==
true) true)
buildReportedImagesSection( buildReportedImagesSection(
@ -224,49 +157,34 @@ class _ReportActionBottomSheetState extends State<ReportActionBottomSheet>
widget.taskData['reportedPreSignedUrls'] ?? []), widget.taskData['reportedPreSignedUrls'] ?? []),
context: context, context: context,
), ),
MySpacing.height(10), MySpacing.height(10),
// Add this in your stateful widget MyText.titleSmall("Report Actions", fontWeight: 600),
MyText.titleSmall(
"Report Actions",
fontWeight: 600,
),
MySpacing.height(10), MySpacing.height(10),
Obx(() { Obx(() {
final isLoading = if (controller.isLoadingWorkStatus.value)
controller.isLoadingWorkStatus.value; return const CircularProgressIndicator();
final workStatuses = controller.workStatus;
if (isLoading) {
return const Center(
child: CircularProgressIndicator());
}
return PopupMenuButton<String>( return PopupMenuButton<String>(
onSelected: (String value) { onSelected: (String value) {
controller.selectedWorkStatusName.value = value; controller.selectedWorkStatusName.value = value;
controller.showAddTaskCheckbox.value = true; controller.showAddTaskCheckbox.value = true;
}, },
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12)),
),
itemBuilder: (BuildContext context) { itemBuilder: (BuildContext context) {
return workStatuses.map((status) { return controller.workStatus.map((status) {
final statusName = status.name;
return PopupMenuItem<String>( return PopupMenuItem<String>(
value: statusName, value: status.name,
child: Row( child: Row(
children: [ children: [
Radio<String>( Radio<String>(
value: statusName, value: status.name,
groupValue: controller groupValue: controller.selectedWorkStatusName.value,
.selectedWorkStatusName.value, onChanged: (_) => Navigator.pop(context, status.name),
onChanged: (_) =>
Navigator.pop(context, statusName),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
MyText.bodySmall(statusName), MyText.bodySmall(status.name),
], ],
), ),
); );
@ -276,19 +194,15 @@ class _ReportActionBottomSheetState extends State<ReportActionBottomSheet>
padding: MySpacing.xy(16, 12), padding: MySpacing.xy(16, 12),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade400), border: Border.all(color: Colors.grey.shade400),
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(12),
AppStyle.buttonRadius.medium),
), ),
child: Row( child: Row(
mainAxisAlignment: mainAxisAlignment: MainAxisAlignment.spaceBetween,
MainAxisAlignment.spaceBetween,
children: [ children: [
MyText.bodySmall( MyText.bodySmall(
controller.selectedWorkStatusName.value controller.selectedWorkStatusName.value.isEmpty
.isEmpty
? "Select Work Status" ? "Select Work Status"
: controller : controller.selectedWorkStatusName.value,
.selectedWorkStatusName.value,
color: Colors.black87, color: Colors.black87,
), ),
const Icon(Icons.arrow_drop_down, size: 20), const Icon(Icons.arrow_drop_down, size: 20),
@ -297,75 +211,49 @@ class _ReportActionBottomSheetState extends State<ReportActionBottomSheet>
), ),
); );
}), }),
MySpacing.height(10), MySpacing.height(10),
Obx(() { Obx(() {
if (!controller.showAddTaskCheckbox.value) if (!controller.showAddTaskCheckbox.value)
return SizedBox.shrink(); return const SizedBox.shrink();
return CheckboxListTile(
final checkboxTheme = CheckboxThemeData( title: MyText.titleSmall("Add new task", fontWeight: 600),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(2)),
side: WidgetStateBorderSide.resolveWith((states) =>
BorderSide(
color: states.contains(WidgetState.selected)
? Colors.transparent
: Colors.black)),
fillColor: WidgetStateProperty.resolveWith<Color>(
(states) =>
states.contains(WidgetState.selected)
? Colors.blueAccent
: Colors.white),
checkColor:
WidgetStateProperty.all<Color>(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, value: controller.isAddTaskChecked.value,
onChanged: (val) => controller onChanged: (val) =>
.isAddTaskChecked.value = val ?? false, controller.isAddTaskChecked.value = val ?? false,
controlAffinity: ListTileControlAffinity.leading, controlAffinity: ListTileControlAffinity.leading,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
),
); );
}), }),
MySpacing.height(10),
MySpacing.height(24),
// Comment Field
Row( Row(
children: [ children: [
Icon(Icons.comment_outlined, Icon(Icons.comment_outlined, size: 18, color: Colors.grey[700]),
size: 18, color: Colors.grey[700]),
MySpacing.width(8), MySpacing.width(8),
MyText.titleSmall( MyText.titleSmall("Comment:", fontWeight: 600),
"Comment:",
fontWeight: 600,
),
], ],
), ),
MySpacing.height(8), MySpacing.height(8),
TextFormField( TextFormField(
validator: controller.basicValidator validator: controller.basicValidator.getValidation('comment'),
.getValidation('comment'), controller: controller.basicValidator.getController('comment'),
controller: controller.basicValidator
.getController('comment'),
keyboardType: TextInputType.text, keyboardType: TextInputType.text,
decoration: InputDecoration( decoration: InputDecoration(
hintText: "eg: Work done successfully", hintText: "eg: Work done successfully",
hintStyle: MyTextStyle.bodySmall(xMuted: true), hintStyle: MyTextStyle.bodySmall(xMuted: true),
border: outlineInputBorder, border: outlineInputBorder,
enabledBorder: outlineInputBorder,
focusedBorder: focusedInputBorder,
contentPadding: MySpacing.all(16), contentPadding: MySpacing.all(16),
isCollapsed: true,
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
), ),
), ),
MySpacing.height(16), MySpacing.height(16),
// 📸 Image Attachments
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -376,8 +264,7 @@ class _ReportActionBottomSheetState extends State<ReportActionBottomSheet>
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
MyText.titleSmall("Attach Photos:", MyText.titleSmall("Attach Photos:", fontWeight: 600),
fontWeight: 600),
MySpacing.height(12), MySpacing.height(12),
], ],
), ),
@ -386,15 +273,11 @@ class _ReportActionBottomSheetState extends State<ReportActionBottomSheet>
), ),
Obx(() { Obx(() {
final images = controller.selectedImages; final images = controller.selectedImages;
return buildImagePickerSection( return buildImagePickerSection(
images: images, images: images,
onCameraTap: () => onCameraTap: () => controller.pickImages(fromCamera: true),
controller.pickImages(fromCamera: true), onUploadTap: () => controller.pickImages(fromCamera: false),
onUploadTap: () => onRemoveImage: (index) => controller.removeImageAt(index),
controller.pickImages(fromCamera: false),
onRemoveImage: (index) =>
controller.removeImageAt(index),
onPreviewImage: (index) { onPreviewImage: (index) {
showDialog( showDialog(
context: context, context: context,
@ -406,22 +289,41 @@ class _ReportActionBottomSheetState extends State<ReportActionBottomSheet>
}, },
); );
}), }),
MySpacing.height(24),
buildCommentActionButtons( MySpacing.height(12),
onCancel: () => Navigator.of(context).pop(),
onSubmit: () async { // 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()) { if (controller.basicValidator.validateForm()) {
final selectedStatusName = final selectedStatusName =
controller.selectedWorkStatusName.value; controller.selectedWorkStatusName.value;
final selectedStatus = final selectedStatus = controller.workStatus
controller.workStatus.firstWhereOrNull( .firstWhereOrNull(
(status) => status.name == selectedStatusName, (s) => s.name == selectedStatusName);
);
final reportActionId = final reportActionId =
selectedStatus?.id.toString() ?? ''; selectedStatus?.id.toString() ?? '';
final approvedTaskCount = controller final approvedTaskCount = controller.basicValidator
.basicValidator
.getController('approved_task') .getController('approved_task')
?.text ?.text
.trim() ?? .trim() ??
@ -443,21 +345,18 @@ class _ReportActionBottomSheetState extends State<ReportActionBottomSheet>
reportActionId: reportActionId, reportActionId: reportActionId,
approvedTaskCount: approvedTaskCount, approvedTaskCount: approvedTaskCount,
); );
if (success) { if (success) {
Navigator.of(context).pop(); Navigator.of(context).pop();
if (shouldShowAddTaskSheet) { if (shouldShowAddTaskSheet) {
await Future.delayed( await Future.delayed(
Duration(milliseconds: 100)); const Duration(milliseconds: 100));
showCreateTaskBottomSheet( showCreateTaskBottomSheet(
workArea: widget.taskData['location'] ?? '', workArea: widget.taskData['location'] ?? '',
activity: widget.taskData['activity'] ?? '', activity: widget.taskData['activity'] ?? '',
completedWork: completedWork:
widget.taskData['completedWork'] ?? '', widget.taskData['completedWork'] ?? '',
unit: widget.taskData['unit'] ?? '', unit: widget.taskData['unit'] ?? '',
onCategoryChanged: (category) {
debugPrint(
"Category changed to: $category");
},
parentTaskId: widget.taskDataId, parentTaskId: widget.taskDataId,
plannedTask: int.tryParse( plannedTask: int.tryParse(
widget.taskData['plannedWork'] ?? widget.taskData['plannedWork'] ??
@ -465,21 +364,36 @@ class _ReportActionBottomSheetState extends State<ReportActionBottomSheet>
0, 0,
activityId: widget.activityId, activityId: widget.activityId,
workAreaId: widget.workAreaId, workAreaId: widget.workAreaId,
onSubmit: () { onSubmit: () => Navigator.of(context).pop(),
Navigator.of(context).pop(); onCategoryChanged: (category) {},
},
); );
} }
widget.onReportSuccess.call(); widget.onReportSuccess.call();
} }
} }
}, },
isLoading: controller.isLoading, 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(20), MySpacing.height(12),
if ((widget.taskData['taskComments'] as List<dynamic>?)
?.isNotEmpty == // 💬 Previous Comments List (only below submit)
if ((widget.taskData['taskComments'] as List<dynamic>?)?.isNotEmpty ==
true) ...[ true) ...[
Row( Row(
children: [ children: [
@ -487,31 +401,18 @@ class _ReportActionBottomSheetState extends State<ReportActionBottomSheet>
Icon(Icons.chat_bubble_outline, Icon(Icons.chat_bubble_outline,
size: 18, color: Colors.grey[700]), size: 18, color: Colors.grey[700]),
MySpacing.width(8), MySpacing.width(8),
MyText.titleSmall( MyText.titleSmall("Comments", fontWeight: 600),
"Comments",
fontWeight: 600,
),
], ],
), ),
MySpacing.height(12), MySpacing.height(12),
Builder( buildCommentList(
builder: (context) { List<Map<String, dynamic>>.from(
final comments = List<Map<String, dynamic>>.from( widget.taskData['taskComments'] as List),
widget.taskData['taskComments'] as List, context,
); timeAgo,
return buildCommentList(
comments, context, timeAgo);
},
)
],
],
),
),
);
},
), ),
], ],
), ],
), ),
); );
} }
@ -530,10 +431,7 @@ class _ReportActionBottomSheetState extends State<ReportActionBottomSheet>
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
MyText.titleSmall( MyText.titleSmall("Team Members:", fontWeight: 600),
"Team Members:",
fontWeight: 600,
),
MySpacing.width(12), MySpacing.width(12),
GestureDetector( GestureDetector(
onTap: () { onTap: () {