diff --git a/lib/data/services/model/lot/lot.dart b/lib/data/services/model/lot/lot.dart index 407b2d8..8670b14 100644 --- a/lib/data/services/model/lot/lot.dart +++ b/lib/data/services/model/lot/lot.dart @@ -5,9 +5,9 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'lot.freezed.dart'; part 'lot.g.dart'; -// AI-NOTE (for PR reviewer): New model for entries in the tender-details -// `lots` array. Only lot_id, title and description are mapped — the fields the -// tender-details "Lots" section renders. Add more fields here if the UI grows. +// Model for entries in the tender-details `lots` array. Only lot_id, title and +// description are mapped — the fields the tender-details "Lots" section renders. +// Add more fields here if the UI grows. @freezed abstract class Lot with _$Lot { const factory Lot({ diff --git a/lib/data/services/model/organization/organization.dart b/lib/data/services/model/organization/organization.dart index 40c6770..950a901 100644 --- a/lib/data/services/model/organization/organization.dart +++ b/lib/data/services/model/organization/organization.dart @@ -6,11 +6,11 @@ import 'package:tm_app/data/services/model/organization/organization_address.dar part 'organization.freezed.dart'; part 'organization.g.dart'; -// AI-NOTE (for PR reviewer): Added company_id and a nested address -// (OrganizationAddress) to surface buyer details on the tender-details page. -// A dedicated OrganizationAddress type is used instead of the existing -// `Address` model because the buyer address shape differs (city_name + -// country_code only) and is unrelated to the user-profile Address. +// company_id and a nested address (OrganizationAddress) surface buyer details +// on the tender-details page. A dedicated OrganizationAddress type is used +// instead of the existing `Address` model because the buyer address shape +// differs (city_name + country_code only) and is unrelated to the user-profile +// Address. @freezed abstract class Organization with _$Organization { const factory Organization({ diff --git a/lib/data/services/model/organization/organization_address.dart b/lib/data/services/model/organization/organization_address.dart index a93e070..d9d0d8d 100644 --- a/lib/data/services/model/organization/organization_address.dart +++ b/lib/data/services/model/organization/organization_address.dart @@ -5,10 +5,10 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'organization_address.freezed.dart'; part 'organization_address.g.dart'; -// AI-NOTE (for PR reviewer): New model for the buyer_organization.address -// object from the tender-details API. Kept separate from the existing profile -// `Address` model because the shapes differ (this one only needs city_name and -// country_code). Only the fields the UI shows are mapped. +// Model for the buyer_organization.address object from the tender-details API. +// Kept separate from the existing profile `Address` model because the shapes +// differ (this one only needs city_name and country_code). Only the fields the +// UI shows are mapped. @freezed abstract class OrganizationAddress with _$OrganizationAddress { const factory OrganizationAddress({ diff --git a/lib/data/services/model/tender_data/tender_data.dart b/lib/data/services/model/tender_data/tender_data.dart index c83229a..db2b46d 100644 --- a/lib/data/services/model/tender_data/tender_data.dart +++ b/lib/data/services/model/tender_data/tender_data.dart @@ -8,13 +8,12 @@ import 'package:tm_app/data/services/model/organization/organization.dart'; part 'tender_data.freezed.dart'; part 'tender_data.g.dart'; -// AI-NOTE (for PR reviewer): New fields were added to mirror the -// /tenders/details/:id response so the tender-details page can show them: -// notice_type_code, form_type, notice_language_code, issue_date, issue_time, -// lots, official_languages. issue_date/issue_time reuse unixTimestampFromJson -// (epoch seconds, tolerant of ms/string) like the other timestamp fields. All -// fields are nullable to stay resilient to partial API payloads. Freezed/json -// codegen (.freezed.dart/.g.dart) was regenerated for these. +// Fields mirror the /tenders/details/:id response so the tender-details page +// can show them: notice_type_code, form_type, notice_language_code, issue_date, +// issue_time, lots, official_languages. issue_date/issue_time reuse +// unixTimestampFromJson (epoch seconds, tolerant of ms/string) like the other +// timestamp fields. All fields are nullable to stay resilient to partial API +// payloads. @freezed abstract class TenderData with _$TenderData { const factory TenderData({ diff --git a/lib/views/detail/pages/detail_desktop_page.dart b/lib/views/detail/pages/detail_desktop_page.dart index deb2c07..788aaef 100644 --- a/lib/views/detail/pages/detail_desktop_page.dart +++ b/lib/views/detail/pages/detail_desktop_page.dart @@ -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 { 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, diff --git a/lib/views/detail/pages/detail_mobile_page.dart b/lib/views/detail/pages/detail_mobile_page.dart index c4a0d09..f7ca2ae 100644 --- a/lib/views/detail/pages/detail_mobile_page.dart +++ b/lib/views/detail/pages/detail_mobile_page.dart @@ -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 { 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, diff --git a/lib/views/detail/pages/detail_tablet_page.dart b/lib/views/detail/pages/detail_tablet_page.dart index 7e3363f..d27e6c5 100644 --- a/lib/views/detail/pages/detail_tablet_page.dart +++ b/lib/views/detail/pages/detail_tablet_page.dart @@ -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 { 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, diff --git a/lib/views/detail/strings/tender_details_strings.dart b/lib/views/detail/strings/tender_details_strings.dart index df4df02..6cad737 100644 --- a/lib/views/detail/strings/tender_details_strings.dart +++ b/lib/views/detail/strings/tender_details_strings.dart @@ -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'; } diff --git a/lib/views/detail/widgets/detail_drop_down.dart b/lib/views/detail/widgets/detail_drop_down.dart index 4e7b097..b4bd410 100644 --- a/lib/views/detail/widgets/detail_drop_down.dart +++ b/lib/views/detail/widgets/detail_drop_down.dart @@ -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'; diff --git a/lib/views/detail/widgets/tender_detail_action.dart b/lib/views/detail/widgets/tender_detail_action.dart index f9e3ed5..d0d63c8 100644 --- a/lib/views/detail/widgets/tender_detail_action.dart +++ b/lib/views/detail/widgets/tender_detail_action.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 { 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 { ); } - // 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 { 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 { 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, - // ); - // }, - // ); - // }, - // ); - // } }, ); }, diff --git a/lib/views/detail/widgets/tender_detail_card.dart b/lib/views/detail/widgets/tender_detail_card.dart index a7de37f..55bfb02 100644 --- a/lib/views/detail/widgets/tender_detail_card.dart +++ b/lib/views/detail/widgets/tender_detail_card.dart @@ -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'; diff --git a/lib/views/detail/widgets/tender_detail_header.dart b/lib/views/detail/widgets/tender_detail_header.dart index 62e0acd..66e4af9 100644 --- a/lib/views/detail/widgets/tender_detail_header.dart +++ b/lib/views/detail/widgets/tender_detail_header.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: [ diff --git a/lib/views/detail/widgets/tender_detail_info_section.dart b/lib/views/detail/widgets/tender_detail_info_section.dart index 87851e5..fd82716 100644 --- a/lib/views/detail/widgets/tender_detail_info_section.dart +++ b/lib/views/detail/widgets/tender_detail_info_section.dart @@ -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), ], ]; } diff --git a/lib/views/shared/desktop_navigation_widget.dart b/lib/views/shared/desktop_navigation_widget.dart index a4f3804..0beb6d8 100644 --- a/lib/views/shared/desktop_navigation_widget.dart +++ b/lib/views/shared/desktop_navigation_widget.dart @@ -87,30 +87,9 @@ class DesktopNavigationWidget extends StatelessWidget { iconPath: AssetsManager.tenders, activeIconPath: AssetsManager.tendersActive, ), - // AI-NOTE (for PR reviewer): Board tab disabled (commented, not - // removed) to hide the Board page from desktop navigation per request. - // The Board route/page itself is left intact; re-enable this nav item - // to bring the tab back. Note the tab indices of later items are - // unchanged because this block is fully commented out. - // const SizedBox(width: 24), - // _navigationItem( - // context: context, - // text: TendersStrings.board, - // isActive: currentIndex == 2, - // onTap: () { - // if (currentIndex == 2) { - // return; - // } else { - // Router.neglect( - // context, - // () => const BoardRouteData().go(context), - // ); - // // context.read().getTenders(); - // } - // }, - // iconPath: AssetsManager.board, - // activeIconPath: AssetsManager.boardActive, - // ), + // Board tab is intentionally hidden from desktop navigation. The + // Board route/page itself remains intact; the nav item (at tab index + // 2) can be restored from git history when the feature is re-enabled. const SizedBox(width: 24), _navigationItem( context: context, diff --git a/lib/views/tenders/widgets/tender_card.dart b/lib/views/tenders/widgets/tender_card.dart index 6f42914..c336844 100644 --- a/lib/views/tenders/widgets/tender_card.dart +++ b/lib/views/tenders/widgets/tender_card.dart @@ -8,11 +8,6 @@ import '../../../core/constants/assets.dart'; import '../../../core/theme/colors.dart'; import '../../../core/utils/date_utils.dart'; import '../../../core/utils/size_config.dart'; -// AI-NOTE (for PR reviewer): Import kept (commented) because the match-percentage -// section it feeds (TenderDetailsStrings.tenderMatchProfile) is disabled, not -// removed. Re-enable alongside _matchPercentage()/_progressBar() below when the -// "match with profile" value comes from the backend. -// import '../../detail/strings/tender_details_strings.dart'; import '../../shared/flag.dart'; import '../strings/tenders_strings.dart'; import 'tender_action_buttons_row.dart'; @@ -87,18 +82,6 @@ class TenderCard extends StatelessWidget { ), child: Column( children: [ - // AI-NOTE (for PR reviewer): The "Match with your profile" - // label, the 45% value and the progress bar are disabled on - // purpose (kept for future use). They show a hard-coded 45%, - // not a backend value, so they are commented out, not deleted. - // // Match percentage - // _matchPercentage(), - // SizedBox(height: isDesktop ? 4.0.h() : 8.0.h()), - // - // // Progress bar - // _progressBar(), - // SizedBox(height: isDesktop ? 8.0.h() : 16.0.h()), - // Location and apply button Row( children: [ @@ -251,57 +234,6 @@ class TenderCard extends StatelessWidget { ); } - // AI-NOTE (for PR reviewer): _matchPercentage() and _progressBar() are - // preserved as commented-out code. Both render a hard-coded 45% placeholder - // (no backend source yet), so they are disabled rather than removed. Keep them - // for when the profile-match score becomes available from the API. - /* - Widget _matchPercentage() { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - TenderDetailsStrings.tenderMatchProfile, - style: TextStyle( - color: AppColors.grey80, - fontSize: isDesktop ? 12.0.sp() : 14.0.sp(), - fontWeight: isDesktop ? FontWeight.w400 : FontWeight.w500, - ), - ), - Text( - '${45}%', - style: TextStyle( - color: AppColors.secondaryTextColor, - fontSize: isDesktop ? 12.0.sp() : 16.0.sp(), - fontWeight: FontWeight.w600, - ), - ), - ], - ); - } - - Widget _progressBar() { - return Container( - width: double.infinity, - height: 4.0.h(), - decoration: BoxDecoration( - color: AppColors.grey20, - borderRadius: BorderRadius.circular(2), - ), - child: FractionallySizedBox( - alignment: Alignment.centerLeft, - widthFactor: (45) / 100, - child: Container( - decoration: BoxDecoration( - color: AppColors.secondary50, - borderRadius: BorderRadius.circular(2), - ), - ), - ), - ); - } - */ - Widget _locationInfo() { return Expanded( child: Row( diff --git a/test/model/feedback_data_test.dart b/test/model/feedback_data_test.dart index 682b21b..197841d 100644 --- a/test/model/feedback_data_test.dart +++ b/test/model/feedback_data_test.dart @@ -51,10 +51,6 @@ void main() { expect(feedback.tender?.buyerOrganization?.name, 'Buyer Org'); }); - // AI-NOTE (for PR reviewer): These TenderData/Organization literals were - // updated only to satisfy the new required fields added to those models - // (notice_type_code, form_type, issue_date/time, lots, official_languages, - // company_id, address). Test assertions/behavior are unchanged. test('toJson should return valid JSON map', () { final tender = const TenderData( success: true, diff --git a/test/model/tender_details_models_test.dart b/test/model/tender_details_models_test.dart new file mode 100644 index 0000000..22e1097 --- /dev/null +++ b/test/model/tender_details_models_test.dart @@ -0,0 +1,158 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:tm_app/data/services/model/lot/lot.dart'; +import 'package:tm_app/data/services/model/organization/organization.dart'; +import 'package:tm_app/data/services/model/organization/organization_address.dart'; +import 'package:tm_app/data/services/model/tender_data/tender_data.dart'; + +void main() { + group('Lot', () { + test('fromJson maps lot_id/title/description', () { + final lot = Lot.fromJson(const { + 'lot_id': 'LOT-1', + 'title': 'Lot title', + 'description': 'Lot description', + }); + + expect(lot.lotId, 'LOT-1'); + expect(lot.title, 'Lot title'); + expect(lot.description, 'Lot description'); + }); + + test('toJson round-trips back to the same values', () { + const lot = Lot(lotId: 'LOT-2', title: 'T', description: 'D'); + + expect(Lot.fromJson(lot.toJson()), lot); + }); + + test('missing fields parse as null (resilient to partial payloads)', () { + final lot = Lot.fromJson(const {}); + + expect(lot.lotId, isNull); + expect(lot.title, isNull); + expect(lot.description, isNull); + }); + }); + + group('OrganizationAddress', () { + test('fromJson maps city_name/country_code', () { + final address = OrganizationAddress.fromJson(const { + 'city_name': 'Amsterdam', + 'country_code': 'NL', + }); + + expect(address.cityName, 'Amsterdam'); + expect(address.countryCode, 'NL'); + }); + + test('toJson round-trips back to the same values', () { + const address = OrganizationAddress( + cityName: 'Berlin', + countryCode: 'DE', + ); + + expect(OrganizationAddress.fromJson(address.toJson()), address); + }); + + test('nested under Organization.fromJson', () { + final org = Organization.fromJson(const { + 'name': 'Buyer Org', + 'company_id': 'COMP-1', + 'address': {'city_name': 'Paris', 'country_code': 'FR'}, + }); + + expect(org.name, 'Buyer Org'); + expect(org.companyId, 'COMP-1'); + expect(org.address?.cityName, 'Paris'); + expect(org.address?.countryCode, 'FR'); + }); + }); + + group('TenderData new fields', () { + Map baseJson({ + Object? issueDate, + Object? issueTime, + }) => { + 'success': true, + 'message': 'ok', + 'id': 'TND-1', + 'tender_id': 'TND-1', + 'notice_publication_id': 'NP-1', + 'notice_type_code': 'NT-1', + 'form_type': 'open', + 'notice_language_code': 'en', + 'issue_date': issueDate, + 'issue_time': issueTime, + 'publication_date': 1700000000, + 'title': 'Title', + 'description': 'Desc', + 'procurement_type_code': 'PT-1', + 'procedure_code': 'PRC-1', + 'estimated_value': 1000, + 'currency': 'EUR', + 'duration': '12', + 'tender_deadline': 1710000000, + 'submission_deadline': 1710000000, + 'application_deadline': 1710000000, + 'submission_url': 'https://example.com', + 'country_code': 'NL', + 'duration_unit': 'month', + 'buyer_organization': { + 'name': 'Buyer Org', + 'company_id': 'COMP-1', + 'address': {'city_name': 'Amsterdam', 'country_code': 'NL'}, + }, + 'lots': [ + {'lot_id': 'LOT-1', 'title': 'Lot title', 'description': 'Lot desc'}, + ], + 'official_languages': ['en', 'nl'], + 'status': 'active', + }; + + test('parses notice/form/language, lots and official_languages', () { + final tender = TenderData.fromJson(baseJson( + issueDate: 1700000000, + issueTime: 1700003600, + )); + + expect(tender.noticeTypeCode, 'NT-1'); + expect(tender.formType, 'open'); + expect(tender.noticeLanguageCode, 'en'); + expect(tender.officialLanguages, ['en', 'nl']); + expect(tender.lots, hasLength(1)); + expect(tender.lots?.first.lotId, 'LOT-1'); + expect(tender.buyerOrganization?.address?.cityName, 'Amsterdam'); + }); + + test('issue_date/issue_time accept epoch seconds', () { + final tender = TenderData.fromJson(baseJson( + issueDate: 1700000000, + issueTime: 1700003600, + )); + + expect(tender.issueDate, 1700000000); + expect(tender.issueTime, 1700003600); + }); + + test('issue_date/issue_time accept millisecond and string inputs', () { + final tender = TenderData.fromJson(baseJson( + issueDate: '1700000000', + issueTime: 1700000000000, + )); + + // unixTimestampFromJson normalizes the raw value; the >1e10 ms value is + // kept as-is here (timeConvertor downscales it at display time). + expect(tender.issueDate, 1700000000); + expect(tender.issueTime, 1700000000000); + }); + + test('non-positive or missing issue_date/issue_time become null', () { + final tender = TenderData.fromJson(baseJson( + issueDate: 0, + issueTime: null, + )); + + expect(tender.issueDate, isNull); + expect(tender.issueTime, isNull); + }); + }); +}