Refactor tender detail models and UI components for improved clarity and functionality
- Updated the `Lot`, `Organization`, and `OrganizationAddress` models to enhance data mapping for tender details. - Revised the `TenderData` model to include additional fields reflecting the API response structure. - Improved UI components across desktop, mobile, and tablet views by commenting out unused sections for future use. - Added new string constants for lot details in the tender information section. - Enhanced unit tests to cover new model fields and ensure resilience to partial API payloads.
This commit is contained in:
@@ -4,9 +4,6 @@ import 'package:tm_app/core/theme/colors.dart';
|
||||
import 'package:tm_app/core/utils/size_config.dart';
|
||||
import 'package:tm_app/view_models/tender_detail_view_model.dart';
|
||||
import 'package:tm_app/views/detail/widgets/tender_detail_action.dart';
|
||||
// AI-NOTE (for PR reviewer): Import kept (commented) — the TenderDetailCard
|
||||
// (static "profile match / incomplete resume" card) is disabled, not removed.
|
||||
// import 'package:tm_app/views/detail/widgets/tender_detail_card.dart';
|
||||
import 'package:tm_app/views/detail/widgets/tender_detail_header.dart';
|
||||
|
||||
import '../../../core/constants/tender_approval_status.dart';
|
||||
@@ -123,10 +120,6 @@ class _TenderDetailDesktopPageState extends State<TenderDetailDesktopPage> {
|
||||
detail: detail,
|
||||
),
|
||||
SizedBox(height: 32.0.h()),
|
||||
// AI-NOTE (for PR reviewer): Static mock card
|
||||
// disabled (kept for future) — hard-coded data.
|
||||
// TenderDetailCard(detail: detail),
|
||||
// SizedBox(height: 24.0.h()),
|
||||
TenderDetailActions(
|
||||
isScreenBig: true,
|
||||
detail: detail,
|
||||
|
||||
@@ -5,9 +5,6 @@ import 'package:tm_app/core/utils/size_config.dart';
|
||||
import 'package:tm_app/view_models/tender_detail_view_model.dart';
|
||||
import 'package:tm_app/views/detail/strings/tender_details_strings.dart';
|
||||
import 'package:tm_app/views/detail/widgets/tender_detail_action.dart';
|
||||
// AI-NOTE (for PR reviewer): Import kept (commented) — the TenderDetailCard
|
||||
// (static "profile match / incomplete resume" card) is disabled, not removed.
|
||||
// import 'package:tm_app/views/detail/widgets/tender_detail_card.dart';
|
||||
import 'package:tm_app/views/detail/widgets/tender_detail_header.dart';
|
||||
import 'package:tm_app/views/shared/tender_app_bar.dart';
|
||||
|
||||
@@ -114,10 +111,6 @@ class _TenderDetailMobilePageState extends State<TenderDetailMobilePage> {
|
||||
children: [
|
||||
TenderDetailHeader(isScreenBig: false, detail: detail),
|
||||
SizedBox(height: 24.0.h()),
|
||||
// AI-NOTE (for PR reviewer): Static mock card disabled (kept
|
||||
// for future use) — shows hard-coded profile-match data.
|
||||
// TenderDetailCard(detail: detail),
|
||||
// SizedBox(height: 24.0.h()),
|
||||
TenderDetailActions(
|
||||
isScreenBig: false,
|
||||
detail: detail,
|
||||
|
||||
@@ -4,9 +4,6 @@ import 'package:tm_app/core/theme/colors.dart';
|
||||
import 'package:tm_app/core/utils/size_config.dart';
|
||||
import 'package:tm_app/view_models/tender_detail_view_model.dart';
|
||||
import 'package:tm_app/views/detail/widgets/tender_detail_action.dart';
|
||||
// AI-NOTE (for PR reviewer): Import kept (commented) — the TenderDetailCard
|
||||
// (static "profile match / incomplete resume" card) is disabled, not removed.
|
||||
// import 'package:tm_app/views/detail/widgets/tender_detail_card.dart';
|
||||
import 'package:tm_app/views/detail/widgets/tender_detail_header.dart';
|
||||
import 'package:tm_app/views/shared/tablet_navigation_widget.dart';
|
||||
|
||||
@@ -125,10 +122,6 @@ class _TenderDetailTabletPageState extends State<TenderDetailTabletPage> {
|
||||
SizedBox(height: 28.0.h()),
|
||||
TenderDetailHeader(isScreenBig: true, detail: detail),
|
||||
SizedBox(height: 28.0.h()),
|
||||
// AI-NOTE (for PR reviewer): Static mock card disabled
|
||||
// (kept for future) — hard-coded profile-match data.
|
||||
// TenderDetailCard(detail: detail),
|
||||
// SizedBox(height: 24.0.h()),
|
||||
TenderDetailActions(
|
||||
isScreenBig: true,
|
||||
detail: detail,
|
||||
|
||||
@@ -42,13 +42,13 @@ class TenderDetailsStrings {
|
||||
static const String requestSuccessfullyRegistered =
|
||||
'Your request has been successfully registered.';
|
||||
static const String confirmAndClose = 'Confirm and close';
|
||||
// AI-NOTE (for PR reviewer): Formal toast copy shown after the user confirms
|
||||
// the Accept/submission modal (see tender_detail_action.dart).
|
||||
// Toast copy shown after the user confirms the Accept/submission modal
|
||||
// (see tender_detail_action.dart).
|
||||
static const String submissionReceived =
|
||||
'Thank you. We have received your submission and will be in touch with you shortly.';
|
||||
|
||||
// AI-NOTE (for PR reviewer): Field labels added for the rewritten
|
||||
// tender-details info section (one per backend field rendered there).
|
||||
// Field labels for the tender-details info section (one per backend field
|
||||
// rendered there).
|
||||
// Tender detail field labels
|
||||
static const String noticeTypeCode = 'Notice Type';
|
||||
static const String formType = 'Form Type';
|
||||
@@ -71,4 +71,6 @@ class TenderDetailsStrings {
|
||||
static const String tenderId = 'Tender ID';
|
||||
static const String lots = 'Lots';
|
||||
static const String lotId = 'Lot ID';
|
||||
static const String lotTitle = 'Title';
|
||||
static const String lotDescription = 'Description';
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// AI-NOTE (for PR reviewer): This widget is currently unused — it was only used
|
||||
// by the static mock sections in TenderDetailHeader, which are now commented
|
||||
// out. It is intentionally KEPT for future use, not deleted. Safe to ignore as
|
||||
// dead code for now.
|
||||
// Currently unused — previously used by the mock sections in TenderDetailHeader,
|
||||
// which are now disabled. Kept for when those sections are re-enabled.
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tm_app/core/utils/size_config.dart';
|
||||
|
||||
@@ -4,19 +4,11 @@ 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';
|
||||
@@ -73,37 +65,10 @@ class _TenderDetailActionsState extends State<TenderDetailActions> {
|
||||
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.
|
||||
// Confirming the submission modal shows a
|
||||
// confirmation toast (no API call, no
|
||||
// navigation), per product request.
|
||||
_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) {},
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -147,10 +112,9 @@ class _TenderDetailActionsState extends State<TenderDetailActions> {
|
||||
);
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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, and uses the State's own (stable page) context, not the modal's.
|
||||
void _showSubmissionReceivedToast() {
|
||||
if (!mounted) {
|
||||
return;
|
||||
@@ -221,37 +185,7 @@ class _TenderDetailActionsState extends State<TenderDetailActions> {
|
||||
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) {},
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -261,34 +195,7 @@ class _TenderDetailActionsState extends State<TenderDetailActions> {
|
||||
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,
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// AI-NOTE (for PR reviewer): This widget is currently unused — its usages on the
|
||||
// tender-details pages are commented out (it renders a static/mock "profile
|
||||
// match 75% + incomplete resume" card, not backend data). It is intentionally
|
||||
// KEPT for future use, not deleted. Safe to ignore as dead code for now.
|
||||
// Currently unused — its usages on the tender-details pages are disabled because
|
||||
// it renders a static/mock "profile match + incomplete resume" card rather than
|
||||
// backend data. Kept for when the profile-match score is available from the API.
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tm_app/core/theme/colors.dart';
|
||||
import 'package:tm_app/core/utils/size_config.dart';
|
||||
|
||||
@@ -9,12 +9,6 @@ import 'package:tm_app/views/detail/widgets/tender_detail_info_section.dart';
|
||||
import '../../../core/constants/assets.dart';
|
||||
import '../../shared/flag.dart';
|
||||
import '../strings/tender_details_strings.dart';
|
||||
// AI-NOTE (for PR reviewer): Import is intentionally kept but commented out.
|
||||
// The static/mock sections it supports (key risks, winning chance, project id,
|
||||
// service/requirements/background dropdowns) were disabled — NOT removed —
|
||||
// because they were hard-coded placeholders, not backend data. Re-enable this
|
||||
// import together with those sections once the API provides that data.
|
||||
// import 'detail_drop_down.dart';
|
||||
|
||||
class TenderDetailHeader extends StatelessWidget {
|
||||
final bool isScreenBig;
|
||||
@@ -46,27 +40,12 @@ class TenderDetailHeader extends StatelessWidget {
|
||||
SizedBox(height: 16.0.h()),
|
||||
_locationBudgetRow(),
|
||||
SizedBox(height: 24.0.h()),
|
||||
// AI-NOTE (for PR reviewer): The following mock sections are disabled
|
||||
// on purpose (kept for future use). They render hard-coded values, not
|
||||
// backend data, so they are commented out rather than deleted.
|
||||
// _projectId(),
|
||||
// SizedBox(height: 12.0.h()),
|
||||
// _winningChanceAndDificulty(),
|
||||
// SizedBox(height: 24.0.h()),
|
||||
// _keyRisksDropDown(),
|
||||
// SizedBox(height: 24.0.h()),
|
||||
Divider(color: AppColors.grey20),
|
||||
TenderDetailInfoSection(isScreenBig: isScreenBig, detail: detail),
|
||||
SizedBox(height: 24.0.h()),
|
||||
_overViewTitle(),
|
||||
SizedBox(height: 12.0.h()),
|
||||
_descriptionText(),
|
||||
// AI-NOTE (for PR reviewer): Mock dropdowns disabled (kept for future).
|
||||
// _serviceDropDown(),
|
||||
// SizedBox(height: 24.0.h()),
|
||||
// _requirementsDropDown(),
|
||||
// SizedBox(height: 24.0.h()),
|
||||
// _backgroundDropDown(),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -94,103 +73,6 @@ class TenderDetailHeader extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
// AI-NOTE (for PR reviewer): The mock widget builders below are intentionally
|
||||
// preserved as commented-out code. They produce static placeholder content
|
||||
// (not backend-driven) and will be reinstated once the API supplies the
|
||||
// corresponding fields. Do not flag them as dead code — keep as-is.
|
||||
/*
|
||||
Widget _serviceDropDown() =>
|
||||
const DetailDropDown(title: 'Servive/Product', items: []);
|
||||
|
||||
Widget _backgroundDropDown() =>
|
||||
const DetailDropDown(title: 'Background', items: []);
|
||||
|
||||
Widget _requirementsDropDown() {
|
||||
return const DetailDropDown(
|
||||
title: 'Requirements (Summary)',
|
||||
items: [
|
||||
'Market consultation for selecting and procuring a Customer Data Platform (CDP) to unify student data from multiple sources and improve personalization and marketing.',
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _keyRisksDropDown() {
|
||||
return const DetailDropDown(
|
||||
title: 'Key risks & dealbreakers',
|
||||
items: [
|
||||
'ISO 27001:2022 likely mandatory for future tender.',
|
||||
'Lack of past performance — public sector values references.',
|
||||
'Implementation & ongoing support required (1–5 person team expectation).',
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _winningChanceAndDificulty() {
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
height: 24.0.h(),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16.0.w(),
|
||||
vertical: 4.0.h(),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.blue0,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'Winning chance: <5%',
|
||||
style: TextStyle(
|
||||
color: AppColors.blue10,
|
||||
fontSize: 12.0.sp(),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12.0.w()),
|
||||
Container(
|
||||
height: 24.0.h(),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16.0.w(),
|
||||
vertical: 4.0.h(),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.blue0,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'Difficulty 2/5',
|
||||
style: TextStyle(
|
||||
color: AppColors.blue10,
|
||||
fontSize: 12.0.sp(),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _projectId() {
|
||||
return Container(
|
||||
height: 24.0.h(),
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.0.w(), vertical: 4.0.h()),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.blue0,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'Project ID 6243372025NLD',
|
||||
style: TextStyle(
|
||||
color: AppColors.blue10,
|
||||
fontSize: 12.0.sp(),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
Widget _locationBudgetRow() {
|
||||
return Row(
|
||||
children: [
|
||||
|
||||
@@ -8,14 +8,12 @@ import 'package:tm_app/views/detail/strings/tender_details_strings.dart';
|
||||
import 'package:tm_app/views/detail/widgets/deadline_item.dart';
|
||||
import 'package:tm_app/views/detail/widgets/info_item.dart';
|
||||
|
||||
// AI-NOTE (for PR reviewer): This widget was rewritten to render ONLY the
|
||||
// backend-driven fields requested for the tender-details page (notice type,
|
||||
// form type, languages, issue/publication dates, procedure, value, duration,
|
||||
// deadlines, buyer org/address, lots, status, ids). Design is unchanged — it
|
||||
// still uses InfoItem/DeadlineItem. The _infoItem() helper returns an empty
|
||||
// list when a value is null/blank so empty fields don't render as blank rows
|
||||
// (this is why each entry is spread with `...`). The old hard-coded
|
||||
// "Reference Number" row was dropped because it isn't in the requested set.
|
||||
// Renders only the backend-driven fields for the tender-details page (notice
|
||||
// type, form type, languages, issue/publication dates, procedure, value,
|
||||
// duration, deadlines, buyer org/address, lots, status, ids), using
|
||||
// InfoItem/DeadlineItem. The _infoItem() helper returns an empty list when a
|
||||
// value is null/blank so empty fields don't render as blank rows (this is why
|
||||
// each entry is spread with `...`).
|
||||
class TenderDetailInfoSection extends StatelessWidget {
|
||||
final bool isScreenBig;
|
||||
final TenderData detail;
|
||||
@@ -112,8 +110,8 @@ class TenderDetailInfoSection extends StatelessWidget {
|
||||
SizedBox(height: 8.0.h()),
|
||||
for (final lot in lots) ...[
|
||||
..._infoItem(TenderDetailsStrings.lotId, lot.lotId),
|
||||
..._infoItem('Title', lot.title),
|
||||
..._infoItem('Description', lot.description),
|
||||
..._infoItem(TenderDetailsStrings.lotTitle, lot.title),
|
||||
..._infoItem(TenderDetailsStrings.lotDescription, lot.description),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user