Files
tm_app/lib/views/detail/widgets/tender_detail_action.dart
T
AmirReza Jamali dde66521f6 Add Docker support and enhance data models for tender details
- Introduced a .dockerignore file to optimize Docker builds by excluding unnecessary files.
- Updated the Dockerfile to use a Flutter base image, enabling web builds for the application.
- Added new data models for `Lot` and `OrganizationAddress` to represent tender details more accurately.
- Enhanced the `Organization` model to include `companyId` and a nested `address` field.
- Updated the `TenderData` model to include new fields such as `noticeTypeCode`, `formType`, and `lots`, reflecting the API response structure.
- Regenerated necessary code for data models to ensure compatibility with the updated structures.
2026-06-03 13:32:42 +03:30

306 lines
14 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/view_models/auth_view_model.dart';
import 'package:tm_app/view_models/tender_detail_view_model.dart';
// AI-NOTE (for PR reviewer): These three imports are kept (commented) because
// the post-confirm navigation flow they supported (documents-completion routes
// + meeting-time modals) is preserved as commented-out code below, not deleted.
// The Accept flow now just shows a confirmation toast instead. Re-enable these
// imports if/when the navigation flow is reinstated.
// import 'package:tm_app/views/detail/widgets/meeting_time_bottom_sheet.dart';
// import 'package:tm_app/views/detail/widgets/meeting_time_dialog.dart';
import 'package:tm_app/views/shared/base_button.dart';
import 'package:tm_app/views/shared/select_submission_bottom_sheet.dart';
import '../../../core/constants/tender_approval_status.dart';
import '../../../core/constants/tender_submision_mode.dart';
// import '../../../core/routes/app_routes.dart';
import '../../../core/utils/app_toast.dart';
import '../../../data/services/model/tender_data/tender_data.dart';
import '../../shared/select_submission_dialog.dart';
import '../strings/tender_details_strings.dart';
class TenderDetailActions extends StatefulWidget {
final bool isScreenBig;
final TenderData detail;
final TenderDetailViewModel viewModel;
const TenderDetailActions({
required this.isScreenBig,
required this.detail,
required this.viewModel,
super.key,
});
@override
State<TenderDetailActions> createState() => _TenderDetailActionsState();
}
class _TenderDetailActionsState extends State<TenderDetailActions> {
@override
Widget build(BuildContext context) {
final userRole = context.watch<AuthViewModel>().userRole;
return widget.isScreenBig
? _webActions(userRole!)
: Column(
children: [
Consumer<TenderDetailViewModel>(
builder: (context, viewModel, child) {
return viewModel.isSubmitApprovalLoading
? Container(
height: 56.0.h(),
width: double.infinity,
decoration: BoxDecoration(
color: AppColors.primary30,
borderRadius: BorderRadius.circular(100.0),
),
child: const Center(
child: CircularProgressIndicator(
color: AppColors.mainBlue,
),
),
)
: BaseButton(
isEnabled: true,
text: TenderDetailsStrings.tenderAcceptButton,
backgroundColor: AppColors.primary30,
textColor: AppColors.mainBlue,
onPressed: () {
{
showModalBottomSheet(
context: context,
builder: (context) {
return SelectSubmissionBottomSheet(
onConfirm: (String value) {
// AI-NOTE (for PR reviewer): New behavior —
// confirming the submission modal now just
// shows a confirmation toast (no API call, no
// navigation), per product request. The prior
// navigation flow is preserved below for future
// use and is intentionally NOT deleted.
_showSubmissionReceivedToast();
// if (value ==
// TenderSubmissionMode.selfApply.value) {
// // viewModel.submitTenderApproval(
// // tenderId: widget.detail.id!,
// // submissionMode: value,
// // );
// CompletionOfDocumentsRouteData(
// tenderId: widget.detail.id!,
// ).push(context);
// } else if (value ==
// TenderSubmissionMode.partnership.value) {
// Future.delayed(Duration.zero, () {
// if (context.mounted) {
// showModalBottomSheet(
// context: context,
// builder: (_) {
// return MeetingTimeBottomSheet(
// onConfirm: (value) {},
// );
// },
// );
// }
// });
// }
},
);
},
);
}
},
);
},
),
SizedBox(height: 16.0.h()),
Consumer<TenderDetailViewModel>(
builder: (context, viewModel, child) {
return viewModel.isRejectApprovalLoading
? BaseButton(
isEnabled: true,
text: TenderDetailsStrings.tenderRejectButton,
borderColor: AppColors.error,
textColor: AppColors.error,
backgroundColor: AppColors.backgroundColor,
borderWidth: 1,
onPressed: () {},
)
: BaseButton(
isEnabled: true,
text: TenderDetailsStrings.tenderRejectButton,
borderColor: AppColors.error,
textColor: AppColors.error,
backgroundColor: AppColors.backgroundColor,
borderWidth: 1,
onPressed: () {
viewModel.rejectTenderApproval(
tenderId: widget.detail.id!,
submissionMode: TenderSubmissionMode.selfApply.value,
);
},
);
},
),
SizedBox(height: 16.0.h()),
],
);
}
// AI-NOTE (for PR reviewer): New helper added for the Accept flow. Shows a
// success toast confirming the submission was received. Guards on `mounted`
// because it runs from a modal callback after the sheet/dialog is popped.
// Uses the State's own context (stable page context), not the popped modal's.
void _showSubmissionReceivedToast() {
if (!mounted) {
return;
}
AppToast.success(context, TenderDetailsStrings.submissionReceived);
}
Row _webActions(Role userRole) {
return Row(
children: [
const Spacer(),
Consumer<TenderDetailViewModel>(
builder: (context, viewModel, child) {
return viewModel.isRejectApprovalLoading
? BaseButton(
isEnabled: true,
text: TenderDetailsStrings.tenderRejectButton,
textColor: AppColors.errorColor,
backgroundColor: AppColors.backgroundColor,
borderWidth: 1,
onPressed: () {},
width: 120.0.w(),
)
: BaseButton(
isEnabled: true,
text: TenderDetailsStrings.tenderRejectButton,
textColor: AppColors.errorColor,
backgroundColor: AppColors.backgroundColor,
borderWidth: 1,
onPressed: () {
viewModel.rejectTenderApproval(
tenderId: widget.detail.id!,
submissionMode: TenderSubmissionMode.selfApply.value,
);
},
width: 120.0.w(),
);
},
),
SizedBox(width: 5.0.w()),
Consumer<TenderDetailViewModel>(
builder: (context, viewModel, child) {
return viewModel.isSubmitApprovalLoading
? BaseButton(
isEnabled: true,
text: TenderDetailsStrings.tenderAcceptButton,
backgroundColor: AppColors.lightBlue,
textColor: AppColors.mainBlue,
onPressed: () {},
width: 120.0.w(),
)
: BaseButton(
isEnabled: true,
text: TenderDetailsStrings.tenderAcceptButton,
backgroundColor: AppColors.lightBlue,
textColor: AppColors.mainBlue,
onPressed: () {
if (viewModel.status ==
TenderApprovalStatus.submitted.value) {
viewModel.submitTenderApproval(
tenderId: widget.detail.id!,
submissionMode: TenderSubmissionMode.selfApply.value,
);
} else {
(MediaQuery.sizeOf(context).width > 600)
? showDialog(
context: context,
builder: (context) {
return SelectSubmissionDialog(
onConfirm: (String value) {
// AI-NOTE (for PR reviewer): New behavior —
// toast only (no API/navigation). Old role-aware
// navigation preserved below for future use.
_showSubmissionReceivedToast();
// if (value ==
// TenderSubmissionMode.selfApply.value) {
// if (context.mounted) {
// if (userRole == Role.analyst) {
// const FinalCompletionOfDocumentsRouteData()
// .push(context);
// } else {
// CompletionOfDocumentsRouteData(
// tenderId: widget.detail.id!,
// ).push(context);
// }
// }
// } else if (value ==
// TenderSubmissionMode.partnership.value) {
// Future.delayed(Duration.zero, () {
// if (context.mounted) {
// showDialog(
// context: context,
// builder: (context) {
// return MeetingTimeDialog(
// onConfirm: (value) {},
// );
// },
// );
// }
// });
// }
},
);
},
)
: showModalBottomSheet(
context: context,
builder: (context) {
return SelectSubmissionBottomSheet(
onConfirm: (String value) {
// AI-NOTE (for PR reviewer): New behavior —
// toast only (no API/navigation). Old flow
// (final-completion route + meeting-time
// bottom sheet) preserved below for future use.
_showSubmissionReceivedToast();
// if (value ==
// TenderSubmissionMode.selfApply.value) {
// if (context.mounted) {
// const FinalCompletionOfDocumentsRouteData()
// .push(context);
// }
// } else if (value ==
// TenderSubmissionMode.partnership.value) {
// showDialog(
// context: context,
// builder: (context) {
// return MeetingTimeBottomSheet(
// onConfirm: (value) {
// AppToast.success(
// context,
// TenderDetailsStrings
// .meetingTimeSuccessfullySet,
// );
// },
// );
// },
// );
// }
},
);
},
);
}
},
width: 120.0.w(),
);
},
),
],
);
}
}