diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5a20c02 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,16 @@ +build +.dart_tool +.git +.gitignore +.drone.yml +.vscode +.idea +.flutter-plugins-dependencies +README.md +*.md +Dockerfile +Dockerfile-old +.dockerignore +artifacts +ios +android diff --git a/Dockerfile b/Dockerfile index 69217da..80ab9c2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,23 @@ -FROM docker-mirror.ravanertebat.ir/hub/nginx:1.25.2-alpine3.18-slim +FROM docker-mirror.ravanertebat.ir/ghcr/cirruslabs/flutter:3.38.10 AS base -COPY ./artifacts/flutter/tm_pwa /usr/share/nginx/html \ No newline at end of file +WORKDIR /app +RUN flutter config --enable-web + +FROM base AS deps +COPY pubspec.yaml pubspec.lock ./ +RUN flutter pub get + +FROM base AS builder +# Pull in the resolved package config from deps so `flutter pub get` is not +# re-run when only source files change. +COPY --from=deps /app/.dart_tool ./.dart_tool +COPY . . +RUN flutter build web --release --output=build/tm_pwa + +FROM docker-mirror.ravanertebat.ir/hub/nginx:1.25.2-alpine3.18-slim AS runner + +COPY --from=builder /app/build/tm_pwa /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/lib/core/routes/app_routes.g.dart b/lib/core/routes/app_routes.g.dart index d4e96d6..32e0a69 100644 --- a/lib/core/routes/app_routes.g.dart +++ b/lib/core/routes/app_routes.g.dart @@ -47,7 +47,6 @@ mixin _$LoginScreenRoute on GoRouteData { RouteBase get $splashScreenRoute => GoRouteData.$route( path: '/splash', - factory: _$SplashScreenRoute._fromState, ); @@ -111,40 +110,33 @@ RouteBase get $appShellRouteData => StatefulShellRouteData.$route( branches: [ StatefulShellBranchData.$branch( navigatorKey: HomeBranch.$navigatorKey, - routes: [ GoRouteData.$route(path: '/home', factory: _$HomeRouteData._fromState), ], ), StatefulShellBranchData.$branch( navigatorKey: TendersBranch.$navigatorKey, - routes: [ GoRouteData.$route( path: '/tenders', - factory: _$TendersRouteData._fromState, ), ], ), StatefulShellBranchData.$branch( navigatorKey: NotificationBranch.$navigatorKey, - routes: [ GoRouteData.$route( path: '/notification', - factory: _$NotificationRouteData._fromState, ), ], ), StatefulShellBranchData.$branch( navigatorKey: ProfileBranch.$navigatorKey, - routes: [ GoRouteData.$route( path: '/profile', - factory: _$ProfileRouteData._fromState, ), ], @@ -242,7 +234,6 @@ mixin _$ProfileRouteData on GoRouteData { RouteBase get $yourTendersRouteData => GoRouteData.$route( path: '/your-tenders', - factory: _$YourTendersRouteData._fromState, ); @@ -274,7 +265,6 @@ mixin _$YourTendersRouteData on GoRouteData { RouteBase get $finalCompletionOfDocumentsRouteData => GoRouteData.$route( path: '/final_completion_of_documents', - factory: _$FinalCompletionOfDocumentsRouteData._fromState, ); @@ -302,7 +292,6 @@ mixin _$FinalCompletionOfDocumentsRouteData on GoRouteData { RouteBase get $completionOfDocumentsRouteData => GoRouteData.$route( path: '/completion_of_documents', - factory: _$CompletionOfDocumentsRouteData._fromState, ); @@ -318,9 +307,7 @@ mixin _$CompletionOfDocumentsRouteData on GoRouteData { @override String get location => GoRouteData.$location( '/completion_of_documents', - queryParams: { - if (_self.tenderId != null) 'tender-id': _self.tenderId!, - }, + queryParams: {if (_self.tenderId != null) 'tender-id': _self.tenderId}, ); @override @@ -339,7 +326,6 @@ mixin _$CompletionOfDocumentsRouteData on GoRouteData { RouteBase get $likedTendersRouteData => GoRouteData.$route( path: '/like-tenders', - factory: _$LikedTendersRouteData._fromState, ); @@ -390,7 +376,6 @@ mixin _$BoardRouteData on GoRouteData { RouteBase get $tenderDetailRouteData => GoRouteData.$route( path: '/tender-detail', - factory: _$TenderDetailRouteData._fromState, ); @@ -422,7 +407,6 @@ mixin _$TenderDetailRouteData on GoRouteData { RouteBase get $forgotPasswordRouteData => GoRouteData.$route( path: '/forgot-password', - factory: _$ForgotPasswordRouteData._fromState, ); @@ -449,7 +433,6 @@ mixin _$ForgotPasswordRouteData on GoRouteData { RouteBase get $forgotPasswordOtpRouteData => GoRouteData.$route( path: '/forgot-password-otp', - factory: _$ForgotPasswordOtpRouteData._fromState, ); @@ -481,7 +464,6 @@ mixin _$ForgotPasswordOtpRouteData on GoRouteData { RouteBase get $forgotPasswordCreateRouteData => GoRouteData.$route( path: '/forgot-password-create', - factory: _$ForgotPasswordCreateRouteData._fromState, ); diff --git a/lib/data/services/model/lot/lot.dart b/lib/data/services/model/lot/lot.dart new file mode 100644 index 0000000..407b2d8 --- /dev/null +++ b/lib/data/services/model/lot/lot.dart @@ -0,0 +1,20 @@ +// ignore_for_file: invalid_annotation_target + +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. +@freezed +abstract class Lot with _$Lot { + const factory Lot({ + @JsonKey(name: 'lot_id') required String? lotId, + required String? title, + required String? description, + }) = _Lot; + + factory Lot.fromJson(Map json) => _$LotFromJson(json); +} diff --git a/lib/data/services/model/lot/lot.freezed.dart b/lib/data/services/model/lot/lot.freezed.dart new file mode 100644 index 0000000..2ec799b --- /dev/null +++ b/lib/data/services/model/lot/lot.freezed.dart @@ -0,0 +1,283 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'lot.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$Lot { + +@JsonKey(name: 'lot_id') String? get lotId; String? get title; String? get description; +/// Create a copy of Lot +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$LotCopyWith get copyWith => _$LotCopyWithImpl(this as Lot, _$identity); + + /// Serializes this Lot to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Lot&&(identical(other.lotId, lotId) || other.lotId == lotId)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,lotId,title,description); + +@override +String toString() { + return 'Lot(lotId: $lotId, title: $title, description: $description)'; +} + + +} + +/// @nodoc +abstract mixin class $LotCopyWith<$Res> { + factory $LotCopyWith(Lot value, $Res Function(Lot) _then) = _$LotCopyWithImpl; +@useResult +$Res call({ +@JsonKey(name: 'lot_id') String? lotId, String? title, String? description +}); + + + + +} +/// @nodoc +class _$LotCopyWithImpl<$Res> + implements $LotCopyWith<$Res> { + _$LotCopyWithImpl(this._self, this._then); + + final Lot _self; + final $Res Function(Lot) _then; + +/// Create a copy of Lot +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? lotId = freezed,Object? title = freezed,Object? description = freezed,}) { + return _then(_self.copyWith( +lotId: freezed == lotId ? _self.lotId : lotId // ignore: cast_nullable_to_non_nullable +as String?,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Lot]. +extension LotPatterns on Lot { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Lot value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Lot() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Lot value) $default,){ +final _that = this; +switch (_that) { +case _Lot(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Lot value)? $default,){ +final _that = this; +switch (_that) { +case _Lot() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'lot_id') String? lotId, String? title, String? description)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Lot() when $default != null: +return $default(_that.lotId,_that.title,_that.description);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'lot_id') String? lotId, String? title, String? description) $default,) {final _that = this; +switch (_that) { +case _Lot(): +return $default(_that.lotId,_that.title,_that.description);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'lot_id') String? lotId, String? title, String? description)? $default,) {final _that = this; +switch (_that) { +case _Lot() when $default != null: +return $default(_that.lotId,_that.title,_that.description);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Lot implements Lot { + const _Lot({@JsonKey(name: 'lot_id') required this.lotId, required this.title, required this.description}); + factory _Lot.fromJson(Map json) => _$LotFromJson(json); + +@override@JsonKey(name: 'lot_id') final String? lotId; +@override final String? title; +@override final String? description; + +/// Create a copy of Lot +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$LotCopyWith<_Lot> get copyWith => __$LotCopyWithImpl<_Lot>(this, _$identity); + +@override +Map toJson() { + return _$LotToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Lot&&(identical(other.lotId, lotId) || other.lotId == lotId)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,lotId,title,description); + +@override +String toString() { + return 'Lot(lotId: $lotId, title: $title, description: $description)'; +} + + +} + +/// @nodoc +abstract mixin class _$LotCopyWith<$Res> implements $LotCopyWith<$Res> { + factory _$LotCopyWith(_Lot value, $Res Function(_Lot) _then) = __$LotCopyWithImpl; +@override @useResult +$Res call({ +@JsonKey(name: 'lot_id') String? lotId, String? title, String? description +}); + + + + +} +/// @nodoc +class __$LotCopyWithImpl<$Res> + implements _$LotCopyWith<$Res> { + __$LotCopyWithImpl(this._self, this._then); + + final _Lot _self; + final $Res Function(_Lot) _then; + +/// Create a copy of Lot +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? lotId = freezed,Object? title = freezed,Object? description = freezed,}) { + return _then(_Lot( +lotId: freezed == lotId ? _self.lotId : lotId // ignore: cast_nullable_to_non_nullable +as String?,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + + +} + +// dart format on diff --git a/lib/data/services/model/lot/lot.g.dart b/lib/data/services/model/lot/lot.g.dart new file mode 100644 index 0000000..68f5287 --- /dev/null +++ b/lib/data/services/model/lot/lot.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'lot.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_Lot _$LotFromJson(Map json) => _Lot( + lotId: json['lot_id'] as String?, + title: json['title'] as String?, + description: json['description'] as String?, +); + +Map _$LotToJson(_Lot instance) => { + 'lot_id': instance.lotId, + 'title': instance.title, + 'description': instance.description, +}; diff --git a/lib/data/services/model/organization/organization.dart b/lib/data/services/model/organization/organization.dart index 4bda620..40c6770 100644 --- a/lib/data/services/model/organization/organization.dart +++ b/lib/data/services/model/organization/organization.dart @@ -1,15 +1,22 @@ // ignore_for_file: invalid_annotation_target import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:tm_app/data/services/model/organization/organization_address.dart'; 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. @freezed abstract class Organization with _$Organization { const factory Organization({ required String? name, - + @JsonKey(name: 'company_id') required String? companyId, + required OrganizationAddress? address, }) = _Organization; factory Organization.fromJson(Map json) => diff --git a/lib/data/services/model/organization/organization.freezed.dart b/lib/data/services/model/organization/organization.freezed.dart index d10198f..7006efd 100644 --- a/lib/data/services/model/organization/organization.freezed.dart +++ b/lib/data/services/model/organization/organization.freezed.dart @@ -15,7 +15,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$Organization { - String? get name; + String? get name;@JsonKey(name: 'company_id') String? get companyId; OrganizationAddress? get address; /// Create a copy of Organization /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -28,16 +28,16 @@ $OrganizationCopyWith get copyWith => _$OrganizationCopyWithImpl Object.hash(runtimeType,name); +int get hashCode => Object.hash(runtimeType,name,companyId,address); @override String toString() { - return 'Organization(name: $name)'; + return 'Organization(name: $name, companyId: $companyId, address: $address)'; } @@ -48,11 +48,11 @@ abstract mixin class $OrganizationCopyWith<$Res> { factory $OrganizationCopyWith(Organization value, $Res Function(Organization) _then) = _$OrganizationCopyWithImpl; @useResult $Res call({ - String? name + String? name,@JsonKey(name: 'company_id') String? companyId, OrganizationAddress? address }); - +$OrganizationAddressCopyWith<$Res>? get address; } /// @nodoc @@ -65,13 +65,27 @@ class _$OrganizationCopyWithImpl<$Res> /// Create a copy of Organization /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? name = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? name = freezed,Object? companyId = freezed,Object? address = freezed,}) { return _then(_self.copyWith( name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable -as String?, +as String?,companyId: freezed == companyId ? _self.companyId : companyId // ignore: cast_nullable_to_non_nullable +as String?,address: freezed == address ? _self.address : address // ignore: cast_nullable_to_non_nullable +as OrganizationAddress?, )); } +/// Create a copy of Organization +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$OrganizationAddressCopyWith<$Res>? get address { + if (_self.address == null) { + return null; + } + return $OrganizationAddressCopyWith<$Res>(_self.address!, (value) { + return _then(_self.copyWith(address: value)); + }); +} } @@ -153,10 +167,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String? name)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String? name, @JsonKey(name: 'company_id') String? companyId, OrganizationAddress? address)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _Organization() when $default != null: -return $default(_that.name);case _: +return $default(_that.name,_that.companyId,_that.address);case _: return orElse(); } @@ -174,10 +188,10 @@ return $default(_that.name);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String? name) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String? name, @JsonKey(name: 'company_id') String? companyId, OrganizationAddress? address) $default,) {final _that = this; switch (_that) { case _Organization(): -return $default(_that.name);case _: +return $default(_that.name,_that.companyId,_that.address);case _: throw StateError('Unexpected subclass'); } @@ -194,10 +208,10 @@ return $default(_that.name);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? name)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? name, @JsonKey(name: 'company_id') String? companyId, OrganizationAddress? address)? $default,) {final _that = this; switch (_that) { case _Organization() when $default != null: -return $default(_that.name);case _: +return $default(_that.name,_that.companyId,_that.address);case _: return null; } @@ -209,10 +223,12 @@ return $default(_that.name);case _: @JsonSerializable() class _Organization implements Organization { - const _Organization({required this.name}); + const _Organization({required this.name, @JsonKey(name: 'company_id') required this.companyId, required this.address}); factory _Organization.fromJson(Map json) => _$OrganizationFromJson(json); @override final String? name; +@override@JsonKey(name: 'company_id') final String? companyId; +@override final OrganizationAddress? address; /// Create a copy of Organization /// with the given fields replaced by the non-null parameter values. @@ -227,16 +243,16 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _Organization&&(identical(other.name, name) || other.name == name)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Organization&&(identical(other.name, name) || other.name == name)&&(identical(other.companyId, companyId) || other.companyId == companyId)&&(identical(other.address, address) || other.address == address)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,name); +int get hashCode => Object.hash(runtimeType,name,companyId,address); @override String toString() { - return 'Organization(name: $name)'; + return 'Organization(name: $name, companyId: $companyId, address: $address)'; } @@ -247,11 +263,11 @@ abstract mixin class _$OrganizationCopyWith<$Res> implements $OrganizationCopyWi factory _$OrganizationCopyWith(_Organization value, $Res Function(_Organization) _then) = __$OrganizationCopyWithImpl; @override @useResult $Res call({ - String? name + String? name,@JsonKey(name: 'company_id') String? companyId, OrganizationAddress? address }); - +@override $OrganizationAddressCopyWith<$Res>? get address; } /// @nodoc @@ -264,14 +280,28 @@ class __$OrganizationCopyWithImpl<$Res> /// Create a copy of Organization /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? name = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? name = freezed,Object? companyId = freezed,Object? address = freezed,}) { return _then(_Organization( name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable -as String?, +as String?,companyId: freezed == companyId ? _self.companyId : companyId // ignore: cast_nullable_to_non_nullable +as String?,address: freezed == address ? _self.address : address // ignore: cast_nullable_to_non_nullable +as OrganizationAddress?, )); } +/// Create a copy of Organization +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$OrganizationAddressCopyWith<$Res>? get address { + if (_self.address == null) { + return null; + } + return $OrganizationAddressCopyWith<$Res>(_self.address!, (value) { + return _then(_self.copyWith(address: value)); + }); +} } // dart format on diff --git a/lib/data/services/model/organization/organization.g.dart b/lib/data/services/model/organization/organization.g.dart index 5464cf0..feefc45 100644 --- a/lib/data/services/model/organization/organization.g.dart +++ b/lib/data/services/model/organization/organization.g.dart @@ -7,7 +7,20 @@ part of 'organization.dart'; // ************************************************************************** _Organization _$OrganizationFromJson(Map json) => - _Organization(name: json['name'] as String?); + _Organization( + name: json['name'] as String?, + companyId: json['company_id'] as String?, + address: + json['address'] == null + ? null + : OrganizationAddress.fromJson( + json['address'] as Map, + ), + ); Map _$OrganizationToJson(_Organization instance) => - {'name': instance.name}; + { + 'name': instance.name, + 'company_id': instance.companyId, + 'address': instance.address, + }; diff --git a/lib/data/services/model/organization/organization_address.dart b/lib/data/services/model/organization/organization_address.dart new file mode 100644 index 0000000..a93e070 --- /dev/null +++ b/lib/data/services/model/organization/organization_address.dart @@ -0,0 +1,21 @@ +// ignore_for_file: invalid_annotation_target + +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. +@freezed +abstract class OrganizationAddress with _$OrganizationAddress { + const factory OrganizationAddress({ + @JsonKey(name: 'city_name') required String? cityName, + @JsonKey(name: 'country_code') required String? countryCode, + }) = _OrganizationAddress; + + factory OrganizationAddress.fromJson(Map json) => + _$OrganizationAddressFromJson(json); +} diff --git a/lib/data/services/model/organization/organization_address.freezed.dart b/lib/data/services/model/organization/organization_address.freezed.dart new file mode 100644 index 0000000..9bb79ae --- /dev/null +++ b/lib/data/services/model/organization/organization_address.freezed.dart @@ -0,0 +1,280 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'organization_address.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$OrganizationAddress { + +@JsonKey(name: 'city_name') String? get cityName;@JsonKey(name: 'country_code') String? get countryCode; +/// Create a copy of OrganizationAddress +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$OrganizationAddressCopyWith get copyWith => _$OrganizationAddressCopyWithImpl(this as OrganizationAddress, _$identity); + + /// Serializes this OrganizationAddress to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is OrganizationAddress&&(identical(other.cityName, cityName) || other.cityName == cityName)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,cityName,countryCode); + +@override +String toString() { + return 'OrganizationAddress(cityName: $cityName, countryCode: $countryCode)'; +} + + +} + +/// @nodoc +abstract mixin class $OrganizationAddressCopyWith<$Res> { + factory $OrganizationAddressCopyWith(OrganizationAddress value, $Res Function(OrganizationAddress) _then) = _$OrganizationAddressCopyWithImpl; +@useResult +$Res call({ +@JsonKey(name: 'city_name') String? cityName,@JsonKey(name: 'country_code') String? countryCode +}); + + + + +} +/// @nodoc +class _$OrganizationAddressCopyWithImpl<$Res> + implements $OrganizationAddressCopyWith<$Res> { + _$OrganizationAddressCopyWithImpl(this._self, this._then); + + final OrganizationAddress _self; + final $Res Function(OrganizationAddress) _then; + +/// Create a copy of OrganizationAddress +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? cityName = freezed,Object? countryCode = freezed,}) { + return _then(_self.copyWith( +cityName: freezed == cityName ? _self.cityName : cityName // ignore: cast_nullable_to_non_nullable +as String?,countryCode: freezed == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [OrganizationAddress]. +extension OrganizationAddressPatterns on OrganizationAddress { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _OrganizationAddress value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _OrganizationAddress() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _OrganizationAddress value) $default,){ +final _that = this; +switch (_that) { +case _OrganizationAddress(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _OrganizationAddress value)? $default,){ +final _that = this; +switch (_that) { +case _OrganizationAddress() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'city_name') String? cityName, @JsonKey(name: 'country_code') String? countryCode)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _OrganizationAddress() when $default != null: +return $default(_that.cityName,_that.countryCode);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'city_name') String? cityName, @JsonKey(name: 'country_code') String? countryCode) $default,) {final _that = this; +switch (_that) { +case _OrganizationAddress(): +return $default(_that.cityName,_that.countryCode);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'city_name') String? cityName, @JsonKey(name: 'country_code') String? countryCode)? $default,) {final _that = this; +switch (_that) { +case _OrganizationAddress() when $default != null: +return $default(_that.cityName,_that.countryCode);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _OrganizationAddress implements OrganizationAddress { + const _OrganizationAddress({@JsonKey(name: 'city_name') required this.cityName, @JsonKey(name: 'country_code') required this.countryCode}); + factory _OrganizationAddress.fromJson(Map json) => _$OrganizationAddressFromJson(json); + +@override@JsonKey(name: 'city_name') final String? cityName; +@override@JsonKey(name: 'country_code') final String? countryCode; + +/// Create a copy of OrganizationAddress +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$OrganizationAddressCopyWith<_OrganizationAddress> get copyWith => __$OrganizationAddressCopyWithImpl<_OrganizationAddress>(this, _$identity); + +@override +Map toJson() { + return _$OrganizationAddressToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _OrganizationAddress&&(identical(other.cityName, cityName) || other.cityName == cityName)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,cityName,countryCode); + +@override +String toString() { + return 'OrganizationAddress(cityName: $cityName, countryCode: $countryCode)'; +} + + +} + +/// @nodoc +abstract mixin class _$OrganizationAddressCopyWith<$Res> implements $OrganizationAddressCopyWith<$Res> { + factory _$OrganizationAddressCopyWith(_OrganizationAddress value, $Res Function(_OrganizationAddress) _then) = __$OrganizationAddressCopyWithImpl; +@override @useResult +$Res call({ +@JsonKey(name: 'city_name') String? cityName,@JsonKey(name: 'country_code') String? countryCode +}); + + + + +} +/// @nodoc +class __$OrganizationAddressCopyWithImpl<$Res> + implements _$OrganizationAddressCopyWith<$Res> { + __$OrganizationAddressCopyWithImpl(this._self, this._then); + + final _OrganizationAddress _self; + final $Res Function(_OrganizationAddress) _then; + +/// Create a copy of OrganizationAddress +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? cityName = freezed,Object? countryCode = freezed,}) { + return _then(_OrganizationAddress( +cityName: freezed == cityName ? _self.cityName : cityName // ignore: cast_nullable_to_non_nullable +as String?,countryCode: freezed == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + + +} + +// dart format on diff --git a/lib/data/services/model/organization/organization_address.g.dart b/lib/data/services/model/organization/organization_address.g.dart new file mode 100644 index 0000000..63cc39c --- /dev/null +++ b/lib/data/services/model/organization/organization_address.g.dart @@ -0,0 +1,20 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'organization_address.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_OrganizationAddress _$OrganizationAddressFromJson(Map json) => + _OrganizationAddress( + cityName: json['city_name'] as String?, + countryCode: json['country_code'] as String?, + ); + +Map _$OrganizationAddressToJson( + _OrganizationAddress instance, +) => { + 'city_name': instance.cityName, + 'country_code': instance.countryCode, +}; diff --git a/lib/data/services/model/tender_data/tender_data.dart b/lib/data/services/model/tender_data/tender_data.dart index 9754b48..c83229a 100644 --- a/lib/data/services/model/tender_data/tender_data.dart +++ b/lib/data/services/model/tender_data/tender_data.dart @@ -2,11 +2,19 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:tm_app/core/utils/date_utils.dart'; +import 'package:tm_app/data/services/model/lot/lot.dart'; 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. @freezed abstract class TenderData with _$TenderData { const factory TenderData({ @@ -16,6 +24,13 @@ abstract class TenderData with _$TenderData { @JsonKey(name: 'tender_id') required String? tenderId, @JsonKey(name: 'notice_publication_id') required String? noticePublicationId, + @JsonKey(name: 'notice_type_code') required String? noticeTypeCode, + @JsonKey(name: 'form_type') required String? formType, + @JsonKey(name: 'notice_language_code') required String? noticeLanguageCode, + @JsonKey(name: 'issue_date', fromJson: unixTimestampFromJson) + required int? issueDate, + @JsonKey(name: 'issue_time', fromJson: unixTimestampFromJson) + required int? issueTime, @JsonKey(name: 'publication_date', fromJson: unixTimestampFromJson) required int? publicationDate, required String? title, @@ -37,6 +52,9 @@ abstract class TenderData with _$TenderData { @JsonKey(name: 'duration_unit') required String? durationUnit, @JsonKey(name: 'buyer_organization') required Organization? buyerOrganization, + required List? lots, + @JsonKey(name: 'official_languages') + required List? officialLanguages, required String? status, }) = _TenderData; diff --git a/lib/data/services/model/tender_data/tender_data.freezed.dart b/lib/data/services/model/tender_data/tender_data.freezed.dart index e46222a..06df383 100644 --- a/lib/data/services/model/tender_data/tender_data.freezed.dart +++ b/lib/data/services/model/tender_data/tender_data.freezed.dart @@ -15,7 +15,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$TenderData { - bool? get success; String? get message; String? get id;@JsonKey(name: 'tender_id') String? get tenderId;@JsonKey(name: 'notice_publication_id') String? get noticePublicationId;@JsonKey(name: 'publication_date') int? get publicationDate; String? get title; String? get description;@JsonKey(name: 'procurement_type_code') String? get procurementTypeCode;@JsonKey(name: 'procedure_code') String? get procedureCode;@JsonKey(name: 'estimated_value') int? get estimatedValue; String? get currency; String? get duration;@JsonKey(name: 'tender_deadline') int? get tenderDeadline;@JsonKey(name: 'submission_deadline') int? get submissionDeadline;@JsonKey(name: 'application_deadline') int? get applicationDeadline;@JsonKey(name: 'submission_url') String? get submissionUrl;@JsonKey(name: 'country_code') String? get countryCode;@JsonKey(name: 'duration_unit') String? get durationUnit;@JsonKey(name: 'buyer_organization') Organization? get buyerOrganization; String? get status; + bool? get success; String? get message; String? get id;@JsonKey(name: 'tender_id') String? get tenderId;@JsonKey(name: 'notice_publication_id') String? get noticePublicationId;@JsonKey(name: 'notice_type_code') String? get noticeTypeCode;@JsonKey(name: 'form_type') String? get formType;@JsonKey(name: 'notice_language_code') String? get noticeLanguageCode;@JsonKey(name: 'issue_date', fromJson: unixTimestampFromJson) int? get issueDate;@JsonKey(name: 'issue_time', fromJson: unixTimestampFromJson) int? get issueTime;@JsonKey(name: 'publication_date', fromJson: unixTimestampFromJson) int? get publicationDate; String? get title; String? get description;@JsonKey(name: 'procurement_type_code') String? get procurementTypeCode;@JsonKey(name: 'procedure_code') String? get procedureCode;@JsonKey(name: 'estimated_value') int? get estimatedValue; String? get currency; String? get duration;@JsonKey(name: 'tender_deadline', fromJson: unixTimestampFromJson) int? get tenderDeadline;@JsonKey(name: 'submission_deadline', fromJson: unixTimestampFromJson) int? get submissionDeadline;@JsonKey(name: 'application_deadline', fromJson: unixTimestampFromJson) int? get applicationDeadline;@JsonKey(name: 'submission_url') String? get submissionUrl;@JsonKey(name: 'country_code') String? get countryCode;@JsonKey(name: 'duration_unit') String? get durationUnit;@JsonKey(name: 'buyer_organization') Organization? get buyerOrganization; List? get lots;@JsonKey(name: 'official_languages') List? get officialLanguages; String? get status; /// Create a copy of TenderData /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -28,16 +28,16 @@ $TenderDataCopyWith get copyWith => _$TenderDataCopyWithImpl Object.hashAll([runtimeType,success,message,id,tenderId,noticePublicationId,publicationDate,title,description,procurementTypeCode,procedureCode,estimatedValue,currency,duration,tenderDeadline,submissionDeadline,applicationDeadline,submissionUrl,countryCode,durationUnit,buyerOrganization,status]); +int get hashCode => Object.hashAll([runtimeType,success,message,id,tenderId,noticePublicationId,noticeTypeCode,formType,noticeLanguageCode,issueDate,issueTime,publicationDate,title,description,procurementTypeCode,procedureCode,estimatedValue,currency,duration,tenderDeadline,submissionDeadline,applicationDeadline,submissionUrl,countryCode,durationUnit,buyerOrganization,const DeepCollectionEquality().hash(lots),const DeepCollectionEquality().hash(officialLanguages),status]); @override String toString() { - return 'TenderData(success: $success, message: $message, id: $id, tenderId: $tenderId, noticePublicationId: $noticePublicationId, publicationDate: $publicationDate, title: $title, description: $description, procurementTypeCode: $procurementTypeCode, procedureCode: $procedureCode, estimatedValue: $estimatedValue, currency: $currency, duration: $duration, tenderDeadline: $tenderDeadline, submissionDeadline: $submissionDeadline, applicationDeadline: $applicationDeadline, submissionUrl: $submissionUrl, countryCode: $countryCode, durationUnit: $durationUnit, buyerOrganization: $buyerOrganization, status: $status)'; + return 'TenderData(success: $success, message: $message, id: $id, tenderId: $tenderId, noticePublicationId: $noticePublicationId, noticeTypeCode: $noticeTypeCode, formType: $formType, noticeLanguageCode: $noticeLanguageCode, issueDate: $issueDate, issueTime: $issueTime, publicationDate: $publicationDate, title: $title, description: $description, procurementTypeCode: $procurementTypeCode, procedureCode: $procedureCode, estimatedValue: $estimatedValue, currency: $currency, duration: $duration, tenderDeadline: $tenderDeadline, submissionDeadline: $submissionDeadline, applicationDeadline: $applicationDeadline, submissionUrl: $submissionUrl, countryCode: $countryCode, durationUnit: $durationUnit, buyerOrganization: $buyerOrganization, lots: $lots, officialLanguages: $officialLanguages, status: $status)'; } @@ -48,7 +48,7 @@ abstract mixin class $TenderDataCopyWith<$Res> { factory $TenderDataCopyWith(TenderData value, $Res Function(TenderData) _then) = _$TenderDataCopyWithImpl; @useResult $Res call({ - bool? success, String? message, String? id,@JsonKey(name: 'tender_id') String? tenderId,@JsonKey(name: 'notice_publication_id') String? noticePublicationId,@JsonKey(name: 'publication_date') int? publicationDate, String? title, String? description,@JsonKey(name: 'procurement_type_code') String? procurementTypeCode,@JsonKey(name: 'procedure_code') String? procedureCode,@JsonKey(name: 'estimated_value') int? estimatedValue, String? currency, String? duration,@JsonKey(name: 'tender_deadline') int? tenderDeadline,@JsonKey(name: 'submission_deadline') int? submissionDeadline,@JsonKey(name: 'application_deadline') int? applicationDeadline,@JsonKey(name: 'submission_url') String? submissionUrl,@JsonKey(name: 'country_code') String? countryCode,@JsonKey(name: 'duration_unit') String? durationUnit,@JsonKey(name: 'buyer_organization') Organization? buyerOrganization, String? status + bool? success, String? message, String? id,@JsonKey(name: 'tender_id') String? tenderId,@JsonKey(name: 'notice_publication_id') String? noticePublicationId,@JsonKey(name: 'notice_type_code') String? noticeTypeCode,@JsonKey(name: 'form_type') String? formType,@JsonKey(name: 'notice_language_code') String? noticeLanguageCode,@JsonKey(name: 'issue_date', fromJson: unixTimestampFromJson) int? issueDate,@JsonKey(name: 'issue_time', fromJson: unixTimestampFromJson) int? issueTime,@JsonKey(name: 'publication_date', fromJson: unixTimestampFromJson) int? publicationDate, String? title, String? description,@JsonKey(name: 'procurement_type_code') String? procurementTypeCode,@JsonKey(name: 'procedure_code') String? procedureCode,@JsonKey(name: 'estimated_value') int? estimatedValue, String? currency, String? duration,@JsonKey(name: 'tender_deadline', fromJson: unixTimestampFromJson) int? tenderDeadline,@JsonKey(name: 'submission_deadline', fromJson: unixTimestampFromJson) int? submissionDeadline,@JsonKey(name: 'application_deadline', fromJson: unixTimestampFromJson) int? applicationDeadline,@JsonKey(name: 'submission_url') String? submissionUrl,@JsonKey(name: 'country_code') String? countryCode,@JsonKey(name: 'duration_unit') String? durationUnit,@JsonKey(name: 'buyer_organization') Organization? buyerOrganization, List? lots,@JsonKey(name: 'official_languages') List? officialLanguages, String? status }); @@ -65,14 +65,19 @@ class _$TenderDataCopyWithImpl<$Res> /// Create a copy of TenderData /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? success = freezed,Object? message = freezed,Object? id = freezed,Object? tenderId = freezed,Object? noticePublicationId = freezed,Object? publicationDate = freezed,Object? title = freezed,Object? description = freezed,Object? procurementTypeCode = freezed,Object? procedureCode = freezed,Object? estimatedValue = freezed,Object? currency = freezed,Object? duration = freezed,Object? tenderDeadline = freezed,Object? submissionDeadline = freezed,Object? applicationDeadline = freezed,Object? submissionUrl = freezed,Object? countryCode = freezed,Object? durationUnit = freezed,Object? buyerOrganization = freezed,Object? status = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? success = freezed,Object? message = freezed,Object? id = freezed,Object? tenderId = freezed,Object? noticePublicationId = freezed,Object? noticeTypeCode = freezed,Object? formType = freezed,Object? noticeLanguageCode = freezed,Object? issueDate = freezed,Object? issueTime = freezed,Object? publicationDate = freezed,Object? title = freezed,Object? description = freezed,Object? procurementTypeCode = freezed,Object? procedureCode = freezed,Object? estimatedValue = freezed,Object? currency = freezed,Object? duration = freezed,Object? tenderDeadline = freezed,Object? submissionDeadline = freezed,Object? applicationDeadline = freezed,Object? submissionUrl = freezed,Object? countryCode = freezed,Object? durationUnit = freezed,Object? buyerOrganization = freezed,Object? lots = freezed,Object? officialLanguages = freezed,Object? status = freezed,}) { return _then(_self.copyWith( success: freezed == success ? _self.success : success // ignore: cast_nullable_to_non_nullable as bool?,message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable as String?,id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String?,tenderId: freezed == tenderId ? _self.tenderId : tenderId // ignore: cast_nullable_to_non_nullable as String?,noticePublicationId: freezed == noticePublicationId ? _self.noticePublicationId : noticePublicationId // ignore: cast_nullable_to_non_nullable -as String?,publicationDate: freezed == publicationDate ? _self.publicationDate : publicationDate // ignore: cast_nullable_to_non_nullable +as String?,noticeTypeCode: freezed == noticeTypeCode ? _self.noticeTypeCode : noticeTypeCode // ignore: cast_nullable_to_non_nullable +as String?,formType: freezed == formType ? _self.formType : formType // ignore: cast_nullable_to_non_nullable +as String?,noticeLanguageCode: freezed == noticeLanguageCode ? _self.noticeLanguageCode : noticeLanguageCode // ignore: cast_nullable_to_non_nullable +as String?,issueDate: freezed == issueDate ? _self.issueDate : issueDate // ignore: cast_nullable_to_non_nullable +as int?,issueTime: freezed == issueTime ? _self.issueTime : issueTime // ignore: cast_nullable_to_non_nullable +as int?,publicationDate: freezed == publicationDate ? _self.publicationDate : publicationDate // ignore: cast_nullable_to_non_nullable as int?,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable as String?,procurementTypeCode: freezed == procurementTypeCode ? _self.procurementTypeCode : procurementTypeCode // ignore: cast_nullable_to_non_nullable @@ -87,7 +92,9 @@ as int?,submissionUrl: freezed == submissionUrl ? _self.submissionUrl : submissi as String?,countryCode: freezed == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable as String?,durationUnit: freezed == durationUnit ? _self.durationUnit : durationUnit // ignore: cast_nullable_to_non_nullable as String?,buyerOrganization: freezed == buyerOrganization ? _self.buyerOrganization : buyerOrganization // ignore: cast_nullable_to_non_nullable -as Organization?,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable +as Organization?,lots: freezed == lots ? _self.lots : lots // ignore: cast_nullable_to_non_nullable +as List?,officialLanguages: freezed == officialLanguages ? _self.officialLanguages : officialLanguages // ignore: cast_nullable_to_non_nullable +as List?,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable as String?, )); } @@ -185,10 +192,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( bool? success, String? message, String? id, @JsonKey(name: 'tender_id') String? tenderId, @JsonKey(name: 'notice_publication_id') String? noticePublicationId, @JsonKey(name: 'publication_date') int? publicationDate, String? title, String? description, @JsonKey(name: 'procurement_type_code') String? procurementTypeCode, @JsonKey(name: 'procedure_code') String? procedureCode, @JsonKey(name: 'estimated_value') int? estimatedValue, String? currency, String? duration, @JsonKey(name: 'tender_deadline') int? tenderDeadline, @JsonKey(name: 'submission_deadline') int? submissionDeadline, @JsonKey(name: 'application_deadline') int? applicationDeadline, @JsonKey(name: 'submission_url') String? submissionUrl, @JsonKey(name: 'country_code') String? countryCode, @JsonKey(name: 'duration_unit') String? durationUnit, @JsonKey(name: 'buyer_organization') Organization? buyerOrganization, String? status)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( bool? success, String? message, String? id, @JsonKey(name: 'tender_id') String? tenderId, @JsonKey(name: 'notice_publication_id') String? noticePublicationId, @JsonKey(name: 'notice_type_code') String? noticeTypeCode, @JsonKey(name: 'form_type') String? formType, @JsonKey(name: 'notice_language_code') String? noticeLanguageCode, @JsonKey(name: 'issue_date', fromJson: unixTimestampFromJson) int? issueDate, @JsonKey(name: 'issue_time', fromJson: unixTimestampFromJson) int? issueTime, @JsonKey(name: 'publication_date', fromJson: unixTimestampFromJson) int? publicationDate, String? title, String? description, @JsonKey(name: 'procurement_type_code') String? procurementTypeCode, @JsonKey(name: 'procedure_code') String? procedureCode, @JsonKey(name: 'estimated_value') int? estimatedValue, String? currency, String? duration, @JsonKey(name: 'tender_deadline', fromJson: unixTimestampFromJson) int? tenderDeadline, @JsonKey(name: 'submission_deadline', fromJson: unixTimestampFromJson) int? submissionDeadline, @JsonKey(name: 'application_deadline', fromJson: unixTimestampFromJson) int? applicationDeadline, @JsonKey(name: 'submission_url') String? submissionUrl, @JsonKey(name: 'country_code') String? countryCode, @JsonKey(name: 'duration_unit') String? durationUnit, @JsonKey(name: 'buyer_organization') Organization? buyerOrganization, List? lots, @JsonKey(name: 'official_languages') List? officialLanguages, String? status)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _TenderData() when $default != null: -return $default(_that.success,_that.message,_that.id,_that.tenderId,_that.noticePublicationId,_that.publicationDate,_that.title,_that.description,_that.procurementTypeCode,_that.procedureCode,_that.estimatedValue,_that.currency,_that.duration,_that.tenderDeadline,_that.submissionDeadline,_that.applicationDeadline,_that.submissionUrl,_that.countryCode,_that.durationUnit,_that.buyerOrganization,_that.status);case _: +return $default(_that.success,_that.message,_that.id,_that.tenderId,_that.noticePublicationId,_that.noticeTypeCode,_that.formType,_that.noticeLanguageCode,_that.issueDate,_that.issueTime,_that.publicationDate,_that.title,_that.description,_that.procurementTypeCode,_that.procedureCode,_that.estimatedValue,_that.currency,_that.duration,_that.tenderDeadline,_that.submissionDeadline,_that.applicationDeadline,_that.submissionUrl,_that.countryCode,_that.durationUnit,_that.buyerOrganization,_that.lots,_that.officialLanguages,_that.status);case _: return orElse(); } @@ -206,10 +213,10 @@ return $default(_that.success,_that.message,_that.id,_that.tenderId,_that.notice /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( bool? success, String? message, String? id, @JsonKey(name: 'tender_id') String? tenderId, @JsonKey(name: 'notice_publication_id') String? noticePublicationId, @JsonKey(name: 'publication_date') int? publicationDate, String? title, String? description, @JsonKey(name: 'procurement_type_code') String? procurementTypeCode, @JsonKey(name: 'procedure_code') String? procedureCode, @JsonKey(name: 'estimated_value') int? estimatedValue, String? currency, String? duration, @JsonKey(name: 'tender_deadline') int? tenderDeadline, @JsonKey(name: 'submission_deadline') int? submissionDeadline, @JsonKey(name: 'application_deadline') int? applicationDeadline, @JsonKey(name: 'submission_url') String? submissionUrl, @JsonKey(name: 'country_code') String? countryCode, @JsonKey(name: 'duration_unit') String? durationUnit, @JsonKey(name: 'buyer_organization') Organization? buyerOrganization, String? status) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( bool? success, String? message, String? id, @JsonKey(name: 'tender_id') String? tenderId, @JsonKey(name: 'notice_publication_id') String? noticePublicationId, @JsonKey(name: 'notice_type_code') String? noticeTypeCode, @JsonKey(name: 'form_type') String? formType, @JsonKey(name: 'notice_language_code') String? noticeLanguageCode, @JsonKey(name: 'issue_date', fromJson: unixTimestampFromJson) int? issueDate, @JsonKey(name: 'issue_time', fromJson: unixTimestampFromJson) int? issueTime, @JsonKey(name: 'publication_date', fromJson: unixTimestampFromJson) int? publicationDate, String? title, String? description, @JsonKey(name: 'procurement_type_code') String? procurementTypeCode, @JsonKey(name: 'procedure_code') String? procedureCode, @JsonKey(name: 'estimated_value') int? estimatedValue, String? currency, String? duration, @JsonKey(name: 'tender_deadline', fromJson: unixTimestampFromJson) int? tenderDeadline, @JsonKey(name: 'submission_deadline', fromJson: unixTimestampFromJson) int? submissionDeadline, @JsonKey(name: 'application_deadline', fromJson: unixTimestampFromJson) int? applicationDeadline, @JsonKey(name: 'submission_url') String? submissionUrl, @JsonKey(name: 'country_code') String? countryCode, @JsonKey(name: 'duration_unit') String? durationUnit, @JsonKey(name: 'buyer_organization') Organization? buyerOrganization, List? lots, @JsonKey(name: 'official_languages') List? officialLanguages, String? status) $default,) {final _that = this; switch (_that) { case _TenderData(): -return $default(_that.success,_that.message,_that.id,_that.tenderId,_that.noticePublicationId,_that.publicationDate,_that.title,_that.description,_that.procurementTypeCode,_that.procedureCode,_that.estimatedValue,_that.currency,_that.duration,_that.tenderDeadline,_that.submissionDeadline,_that.applicationDeadline,_that.submissionUrl,_that.countryCode,_that.durationUnit,_that.buyerOrganization,_that.status);case _: +return $default(_that.success,_that.message,_that.id,_that.tenderId,_that.noticePublicationId,_that.noticeTypeCode,_that.formType,_that.noticeLanguageCode,_that.issueDate,_that.issueTime,_that.publicationDate,_that.title,_that.description,_that.procurementTypeCode,_that.procedureCode,_that.estimatedValue,_that.currency,_that.duration,_that.tenderDeadline,_that.submissionDeadline,_that.applicationDeadline,_that.submissionUrl,_that.countryCode,_that.durationUnit,_that.buyerOrganization,_that.lots,_that.officialLanguages,_that.status);case _: throw StateError('Unexpected subclass'); } @@ -226,10 +233,10 @@ return $default(_that.success,_that.message,_that.id,_that.tenderId,_that.notice /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( bool? success, String? message, String? id, @JsonKey(name: 'tender_id') String? tenderId, @JsonKey(name: 'notice_publication_id') String? noticePublicationId, @JsonKey(name: 'publication_date') int? publicationDate, String? title, String? description, @JsonKey(name: 'procurement_type_code') String? procurementTypeCode, @JsonKey(name: 'procedure_code') String? procedureCode, @JsonKey(name: 'estimated_value') int? estimatedValue, String? currency, String? duration, @JsonKey(name: 'tender_deadline') int? tenderDeadline, @JsonKey(name: 'submission_deadline') int? submissionDeadline, @JsonKey(name: 'application_deadline') int? applicationDeadline, @JsonKey(name: 'submission_url') String? submissionUrl, @JsonKey(name: 'country_code') String? countryCode, @JsonKey(name: 'duration_unit') String? durationUnit, @JsonKey(name: 'buyer_organization') Organization? buyerOrganization, String? status)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( bool? success, String? message, String? id, @JsonKey(name: 'tender_id') String? tenderId, @JsonKey(name: 'notice_publication_id') String? noticePublicationId, @JsonKey(name: 'notice_type_code') String? noticeTypeCode, @JsonKey(name: 'form_type') String? formType, @JsonKey(name: 'notice_language_code') String? noticeLanguageCode, @JsonKey(name: 'issue_date', fromJson: unixTimestampFromJson) int? issueDate, @JsonKey(name: 'issue_time', fromJson: unixTimestampFromJson) int? issueTime, @JsonKey(name: 'publication_date', fromJson: unixTimestampFromJson) int? publicationDate, String? title, String? description, @JsonKey(name: 'procurement_type_code') String? procurementTypeCode, @JsonKey(name: 'procedure_code') String? procedureCode, @JsonKey(name: 'estimated_value') int? estimatedValue, String? currency, String? duration, @JsonKey(name: 'tender_deadline', fromJson: unixTimestampFromJson) int? tenderDeadline, @JsonKey(name: 'submission_deadline', fromJson: unixTimestampFromJson) int? submissionDeadline, @JsonKey(name: 'application_deadline', fromJson: unixTimestampFromJson) int? applicationDeadline, @JsonKey(name: 'submission_url') String? submissionUrl, @JsonKey(name: 'country_code') String? countryCode, @JsonKey(name: 'duration_unit') String? durationUnit, @JsonKey(name: 'buyer_organization') Organization? buyerOrganization, List? lots, @JsonKey(name: 'official_languages') List? officialLanguages, String? status)? $default,) {final _that = this; switch (_that) { case _TenderData() when $default != null: -return $default(_that.success,_that.message,_that.id,_that.tenderId,_that.noticePublicationId,_that.publicationDate,_that.title,_that.description,_that.procurementTypeCode,_that.procedureCode,_that.estimatedValue,_that.currency,_that.duration,_that.tenderDeadline,_that.submissionDeadline,_that.applicationDeadline,_that.submissionUrl,_that.countryCode,_that.durationUnit,_that.buyerOrganization,_that.status);case _: +return $default(_that.success,_that.message,_that.id,_that.tenderId,_that.noticePublicationId,_that.noticeTypeCode,_that.formType,_that.noticeLanguageCode,_that.issueDate,_that.issueTime,_that.publicationDate,_that.title,_that.description,_that.procurementTypeCode,_that.procedureCode,_that.estimatedValue,_that.currency,_that.duration,_that.tenderDeadline,_that.submissionDeadline,_that.applicationDeadline,_that.submissionUrl,_that.countryCode,_that.durationUnit,_that.buyerOrganization,_that.lots,_that.officialLanguages,_that.status);case _: return null; } @@ -241,7 +248,7 @@ return $default(_that.success,_that.message,_that.id,_that.tenderId,_that.notice @JsonSerializable() class _TenderData implements TenderData { - const _TenderData({required this.success, required this.message, required this.id, @JsonKey(name: 'tender_id') required this.tenderId, @JsonKey(name: 'notice_publication_id') required this.noticePublicationId, @JsonKey(name: 'publication_date') required this.publicationDate, required this.title, required this.description, @JsonKey(name: 'procurement_type_code') required this.procurementTypeCode, @JsonKey(name: 'procedure_code') required this.procedureCode, @JsonKey(name: 'estimated_value') required this.estimatedValue, required this.currency, required this.duration, @JsonKey(name: 'tender_deadline') required this.tenderDeadline, @JsonKey(name: 'submission_deadline') required this.submissionDeadline, @JsonKey(name: 'application_deadline') required this.applicationDeadline, @JsonKey(name: 'submission_url') required this.submissionUrl, @JsonKey(name: 'country_code') required this.countryCode, @JsonKey(name: 'duration_unit') required this.durationUnit, @JsonKey(name: 'buyer_organization') required this.buyerOrganization, required this.status}); + const _TenderData({required this.success, required this.message, required this.id, @JsonKey(name: 'tender_id') required this.tenderId, @JsonKey(name: 'notice_publication_id') required this.noticePublicationId, @JsonKey(name: 'notice_type_code') required this.noticeTypeCode, @JsonKey(name: 'form_type') required this.formType, @JsonKey(name: 'notice_language_code') required this.noticeLanguageCode, @JsonKey(name: 'issue_date', fromJson: unixTimestampFromJson) required this.issueDate, @JsonKey(name: 'issue_time', fromJson: unixTimestampFromJson) required this.issueTime, @JsonKey(name: 'publication_date', fromJson: unixTimestampFromJson) required this.publicationDate, required this.title, required this.description, @JsonKey(name: 'procurement_type_code') required this.procurementTypeCode, @JsonKey(name: 'procedure_code') required this.procedureCode, @JsonKey(name: 'estimated_value') required this.estimatedValue, required this.currency, required this.duration, @JsonKey(name: 'tender_deadline', fromJson: unixTimestampFromJson) required this.tenderDeadline, @JsonKey(name: 'submission_deadline', fromJson: unixTimestampFromJson) required this.submissionDeadline, @JsonKey(name: 'application_deadline', fromJson: unixTimestampFromJson) required this.applicationDeadline, @JsonKey(name: 'submission_url') required this.submissionUrl, @JsonKey(name: 'country_code') required this.countryCode, @JsonKey(name: 'duration_unit') required this.durationUnit, @JsonKey(name: 'buyer_organization') required this.buyerOrganization, required final List? lots, @JsonKey(name: 'official_languages') required final List? officialLanguages, required this.status}): _lots = lots,_officialLanguages = officialLanguages; factory _TenderData.fromJson(Map json) => _$TenderDataFromJson(json); @override final bool? success; @@ -249,7 +256,12 @@ class _TenderData implements TenderData { @override final String? id; @override@JsonKey(name: 'tender_id') final String? tenderId; @override@JsonKey(name: 'notice_publication_id') final String? noticePublicationId; -@override@JsonKey(name: 'publication_date') final int? publicationDate; +@override@JsonKey(name: 'notice_type_code') final String? noticeTypeCode; +@override@JsonKey(name: 'form_type') final String? formType; +@override@JsonKey(name: 'notice_language_code') final String? noticeLanguageCode; +@override@JsonKey(name: 'issue_date', fromJson: unixTimestampFromJson) final int? issueDate; +@override@JsonKey(name: 'issue_time', fromJson: unixTimestampFromJson) final int? issueTime; +@override@JsonKey(name: 'publication_date', fromJson: unixTimestampFromJson) final int? publicationDate; @override final String? title; @override final String? description; @override@JsonKey(name: 'procurement_type_code') final String? procurementTypeCode; @@ -257,13 +269,31 @@ class _TenderData implements TenderData { @override@JsonKey(name: 'estimated_value') final int? estimatedValue; @override final String? currency; @override final String? duration; -@override@JsonKey(name: 'tender_deadline') final int? tenderDeadline; -@override@JsonKey(name: 'submission_deadline') final int? submissionDeadline; -@override@JsonKey(name: 'application_deadline') final int? applicationDeadline; +@override@JsonKey(name: 'tender_deadline', fromJson: unixTimestampFromJson) final int? tenderDeadline; +@override@JsonKey(name: 'submission_deadline', fromJson: unixTimestampFromJson) final int? submissionDeadline; +@override@JsonKey(name: 'application_deadline', fromJson: unixTimestampFromJson) final int? applicationDeadline; @override@JsonKey(name: 'submission_url') final String? submissionUrl; @override@JsonKey(name: 'country_code') final String? countryCode; @override@JsonKey(name: 'duration_unit') final String? durationUnit; @override@JsonKey(name: 'buyer_organization') final Organization? buyerOrganization; + final List? _lots; +@override List? get lots { + final value = _lots; + if (value == null) return null; + if (_lots is EqualUnmodifiableListView) return _lots; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); +} + + final List? _officialLanguages; +@override@JsonKey(name: 'official_languages') List? get officialLanguages { + final value = _officialLanguages; + if (value == null) return null; + if (_officialLanguages is EqualUnmodifiableListView) return _officialLanguages; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); +} + @override final String? status; /// Create a copy of TenderData @@ -279,16 +309,16 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _TenderData&&(identical(other.success, success) || other.success == success)&&(identical(other.message, message) || other.message == message)&&(identical(other.id, id) || other.id == id)&&(identical(other.tenderId, tenderId) || other.tenderId == tenderId)&&(identical(other.noticePublicationId, noticePublicationId) || other.noticePublicationId == noticePublicationId)&&(identical(other.publicationDate, publicationDate) || other.publicationDate == publicationDate)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.procurementTypeCode, procurementTypeCode) || other.procurementTypeCode == procurementTypeCode)&&(identical(other.procedureCode, procedureCode) || other.procedureCode == procedureCode)&&(identical(other.estimatedValue, estimatedValue) || other.estimatedValue == estimatedValue)&&(identical(other.currency, currency) || other.currency == currency)&&(identical(other.duration, duration) || other.duration == duration)&&(identical(other.tenderDeadline, tenderDeadline) || other.tenderDeadline == tenderDeadline)&&(identical(other.submissionDeadline, submissionDeadline) || other.submissionDeadline == submissionDeadline)&&(identical(other.applicationDeadline, applicationDeadline) || other.applicationDeadline == applicationDeadline)&&(identical(other.submissionUrl, submissionUrl) || other.submissionUrl == submissionUrl)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)&&(identical(other.durationUnit, durationUnit) || other.durationUnit == durationUnit)&&(identical(other.buyerOrganization, buyerOrganization) || other.buyerOrganization == buyerOrganization)&&(identical(other.status, status) || other.status == status)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _TenderData&&(identical(other.success, success) || other.success == success)&&(identical(other.message, message) || other.message == message)&&(identical(other.id, id) || other.id == id)&&(identical(other.tenderId, tenderId) || other.tenderId == tenderId)&&(identical(other.noticePublicationId, noticePublicationId) || other.noticePublicationId == noticePublicationId)&&(identical(other.noticeTypeCode, noticeTypeCode) || other.noticeTypeCode == noticeTypeCode)&&(identical(other.formType, formType) || other.formType == formType)&&(identical(other.noticeLanguageCode, noticeLanguageCode) || other.noticeLanguageCode == noticeLanguageCode)&&(identical(other.issueDate, issueDate) || other.issueDate == issueDate)&&(identical(other.issueTime, issueTime) || other.issueTime == issueTime)&&(identical(other.publicationDate, publicationDate) || other.publicationDate == publicationDate)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.procurementTypeCode, procurementTypeCode) || other.procurementTypeCode == procurementTypeCode)&&(identical(other.procedureCode, procedureCode) || other.procedureCode == procedureCode)&&(identical(other.estimatedValue, estimatedValue) || other.estimatedValue == estimatedValue)&&(identical(other.currency, currency) || other.currency == currency)&&(identical(other.duration, duration) || other.duration == duration)&&(identical(other.tenderDeadline, tenderDeadline) || other.tenderDeadline == tenderDeadline)&&(identical(other.submissionDeadline, submissionDeadline) || other.submissionDeadline == submissionDeadline)&&(identical(other.applicationDeadline, applicationDeadline) || other.applicationDeadline == applicationDeadline)&&(identical(other.submissionUrl, submissionUrl) || other.submissionUrl == submissionUrl)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)&&(identical(other.durationUnit, durationUnit) || other.durationUnit == durationUnit)&&(identical(other.buyerOrganization, buyerOrganization) || other.buyerOrganization == buyerOrganization)&&const DeepCollectionEquality().equals(other._lots, _lots)&&const DeepCollectionEquality().equals(other._officialLanguages, _officialLanguages)&&(identical(other.status, status) || other.status == status)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hashAll([runtimeType,success,message,id,tenderId,noticePublicationId,publicationDate,title,description,procurementTypeCode,procedureCode,estimatedValue,currency,duration,tenderDeadline,submissionDeadline,applicationDeadline,submissionUrl,countryCode,durationUnit,buyerOrganization,status]); +int get hashCode => Object.hashAll([runtimeType,success,message,id,tenderId,noticePublicationId,noticeTypeCode,formType,noticeLanguageCode,issueDate,issueTime,publicationDate,title,description,procurementTypeCode,procedureCode,estimatedValue,currency,duration,tenderDeadline,submissionDeadline,applicationDeadline,submissionUrl,countryCode,durationUnit,buyerOrganization,const DeepCollectionEquality().hash(_lots),const DeepCollectionEquality().hash(_officialLanguages),status]); @override String toString() { - return 'TenderData(success: $success, message: $message, id: $id, tenderId: $tenderId, noticePublicationId: $noticePublicationId, publicationDate: $publicationDate, title: $title, description: $description, procurementTypeCode: $procurementTypeCode, procedureCode: $procedureCode, estimatedValue: $estimatedValue, currency: $currency, duration: $duration, tenderDeadline: $tenderDeadline, submissionDeadline: $submissionDeadline, applicationDeadline: $applicationDeadline, submissionUrl: $submissionUrl, countryCode: $countryCode, durationUnit: $durationUnit, buyerOrganization: $buyerOrganization, status: $status)'; + return 'TenderData(success: $success, message: $message, id: $id, tenderId: $tenderId, noticePublicationId: $noticePublicationId, noticeTypeCode: $noticeTypeCode, formType: $formType, noticeLanguageCode: $noticeLanguageCode, issueDate: $issueDate, issueTime: $issueTime, publicationDate: $publicationDate, title: $title, description: $description, procurementTypeCode: $procurementTypeCode, procedureCode: $procedureCode, estimatedValue: $estimatedValue, currency: $currency, duration: $duration, tenderDeadline: $tenderDeadline, submissionDeadline: $submissionDeadline, applicationDeadline: $applicationDeadline, submissionUrl: $submissionUrl, countryCode: $countryCode, durationUnit: $durationUnit, buyerOrganization: $buyerOrganization, lots: $lots, officialLanguages: $officialLanguages, status: $status)'; } @@ -299,7 +329,7 @@ abstract mixin class _$TenderDataCopyWith<$Res> implements $TenderDataCopyWith<$ factory _$TenderDataCopyWith(_TenderData value, $Res Function(_TenderData) _then) = __$TenderDataCopyWithImpl; @override @useResult $Res call({ - bool? success, String? message, String? id,@JsonKey(name: 'tender_id') String? tenderId,@JsonKey(name: 'notice_publication_id') String? noticePublicationId,@JsonKey(name: 'publication_date') int? publicationDate, String? title, String? description,@JsonKey(name: 'procurement_type_code') String? procurementTypeCode,@JsonKey(name: 'procedure_code') String? procedureCode,@JsonKey(name: 'estimated_value') int? estimatedValue, String? currency, String? duration,@JsonKey(name: 'tender_deadline') int? tenderDeadline,@JsonKey(name: 'submission_deadline') int? submissionDeadline,@JsonKey(name: 'application_deadline') int? applicationDeadline,@JsonKey(name: 'submission_url') String? submissionUrl,@JsonKey(name: 'country_code') String? countryCode,@JsonKey(name: 'duration_unit') String? durationUnit,@JsonKey(name: 'buyer_organization') Organization? buyerOrganization, String? status + bool? success, String? message, String? id,@JsonKey(name: 'tender_id') String? tenderId,@JsonKey(name: 'notice_publication_id') String? noticePublicationId,@JsonKey(name: 'notice_type_code') String? noticeTypeCode,@JsonKey(name: 'form_type') String? formType,@JsonKey(name: 'notice_language_code') String? noticeLanguageCode,@JsonKey(name: 'issue_date', fromJson: unixTimestampFromJson) int? issueDate,@JsonKey(name: 'issue_time', fromJson: unixTimestampFromJson) int? issueTime,@JsonKey(name: 'publication_date', fromJson: unixTimestampFromJson) int? publicationDate, String? title, String? description,@JsonKey(name: 'procurement_type_code') String? procurementTypeCode,@JsonKey(name: 'procedure_code') String? procedureCode,@JsonKey(name: 'estimated_value') int? estimatedValue, String? currency, String? duration,@JsonKey(name: 'tender_deadline', fromJson: unixTimestampFromJson) int? tenderDeadline,@JsonKey(name: 'submission_deadline', fromJson: unixTimestampFromJson) int? submissionDeadline,@JsonKey(name: 'application_deadline', fromJson: unixTimestampFromJson) int? applicationDeadline,@JsonKey(name: 'submission_url') String? submissionUrl,@JsonKey(name: 'country_code') String? countryCode,@JsonKey(name: 'duration_unit') String? durationUnit,@JsonKey(name: 'buyer_organization') Organization? buyerOrganization, List? lots,@JsonKey(name: 'official_languages') List? officialLanguages, String? status }); @@ -316,14 +346,19 @@ class __$TenderDataCopyWithImpl<$Res> /// Create a copy of TenderData /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? success = freezed,Object? message = freezed,Object? id = freezed,Object? tenderId = freezed,Object? noticePublicationId = freezed,Object? publicationDate = freezed,Object? title = freezed,Object? description = freezed,Object? procurementTypeCode = freezed,Object? procedureCode = freezed,Object? estimatedValue = freezed,Object? currency = freezed,Object? duration = freezed,Object? tenderDeadline = freezed,Object? submissionDeadline = freezed,Object? applicationDeadline = freezed,Object? submissionUrl = freezed,Object? countryCode = freezed,Object? durationUnit = freezed,Object? buyerOrganization = freezed,Object? status = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? success = freezed,Object? message = freezed,Object? id = freezed,Object? tenderId = freezed,Object? noticePublicationId = freezed,Object? noticeTypeCode = freezed,Object? formType = freezed,Object? noticeLanguageCode = freezed,Object? issueDate = freezed,Object? issueTime = freezed,Object? publicationDate = freezed,Object? title = freezed,Object? description = freezed,Object? procurementTypeCode = freezed,Object? procedureCode = freezed,Object? estimatedValue = freezed,Object? currency = freezed,Object? duration = freezed,Object? tenderDeadline = freezed,Object? submissionDeadline = freezed,Object? applicationDeadline = freezed,Object? submissionUrl = freezed,Object? countryCode = freezed,Object? durationUnit = freezed,Object? buyerOrganization = freezed,Object? lots = freezed,Object? officialLanguages = freezed,Object? status = freezed,}) { return _then(_TenderData( success: freezed == success ? _self.success : success // ignore: cast_nullable_to_non_nullable as bool?,message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable as String?,id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String?,tenderId: freezed == tenderId ? _self.tenderId : tenderId // ignore: cast_nullable_to_non_nullable as String?,noticePublicationId: freezed == noticePublicationId ? _self.noticePublicationId : noticePublicationId // ignore: cast_nullable_to_non_nullable -as String?,publicationDate: freezed == publicationDate ? _self.publicationDate : publicationDate // ignore: cast_nullable_to_non_nullable +as String?,noticeTypeCode: freezed == noticeTypeCode ? _self.noticeTypeCode : noticeTypeCode // ignore: cast_nullable_to_non_nullable +as String?,formType: freezed == formType ? _self.formType : formType // ignore: cast_nullable_to_non_nullable +as String?,noticeLanguageCode: freezed == noticeLanguageCode ? _self.noticeLanguageCode : noticeLanguageCode // ignore: cast_nullable_to_non_nullable +as String?,issueDate: freezed == issueDate ? _self.issueDate : issueDate // ignore: cast_nullable_to_non_nullable +as int?,issueTime: freezed == issueTime ? _self.issueTime : issueTime // ignore: cast_nullable_to_non_nullable +as int?,publicationDate: freezed == publicationDate ? _self.publicationDate : publicationDate // ignore: cast_nullable_to_non_nullable as int?,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable as String?,procurementTypeCode: freezed == procurementTypeCode ? _self.procurementTypeCode : procurementTypeCode // ignore: cast_nullable_to_non_nullable @@ -338,7 +373,9 @@ as int?,submissionUrl: freezed == submissionUrl ? _self.submissionUrl : submissi as String?,countryCode: freezed == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable as String?,durationUnit: freezed == durationUnit ? _self.durationUnit : durationUnit // ignore: cast_nullable_to_non_nullable as String?,buyerOrganization: freezed == buyerOrganization ? _self.buyerOrganization : buyerOrganization // ignore: cast_nullable_to_non_nullable -as Organization?,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable +as Organization?,lots: freezed == lots ? _self._lots : lots // ignore: cast_nullable_to_non_nullable +as List?,officialLanguages: freezed == officialLanguages ? _self._officialLanguages : officialLanguages // ignore: cast_nullable_to_non_nullable +as List?,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable as String?, )); } diff --git a/lib/data/services/model/tender_data/tender_data.g.dart b/lib/data/services/model/tender_data/tender_data.g.dart index 1c934b5..91d5a63 100644 --- a/lib/data/services/model/tender_data/tender_data.g.dart +++ b/lib/data/services/model/tender_data/tender_data.g.dart @@ -12,6 +12,11 @@ _TenderData _$TenderDataFromJson(Map json) => _TenderData( id: json['id'] as String?, tenderId: json['tender_id'] as String?, noticePublicationId: json['notice_publication_id'] as String?, + noticeTypeCode: json['notice_type_code'] as String?, + formType: json['form_type'] as String?, + noticeLanguageCode: json['notice_language_code'] as String?, + issueDate: unixTimestampFromJson(json['issue_date']), + issueTime: unixTimestampFromJson(json['issue_time']), publicationDate: unixTimestampFromJson(json['publication_date']), title: json['title'] as String?, description: json['description'] as String?, @@ -32,6 +37,14 @@ _TenderData _$TenderDataFromJson(Map json) => _TenderData( : Organization.fromJson( json['buyer_organization'] as Map, ), + lots: + (json['lots'] as List?) + ?.map((e) => Lot.fromJson(e as Map)) + .toList(), + officialLanguages: + (json['official_languages'] as List?) + ?.map((e) => e as String) + .toList(), status: json['status'] as String?, ); @@ -42,6 +55,11 @@ Map _$TenderDataToJson(_TenderData instance) => 'id': instance.id, 'tender_id': instance.tenderId, 'notice_publication_id': instance.noticePublicationId, + 'notice_type_code': instance.noticeTypeCode, + 'form_type': instance.formType, + 'notice_language_code': instance.noticeLanguageCode, + 'issue_date': instance.issueDate, + 'issue_time': instance.issueTime, 'publication_date': instance.publicationDate, 'title': instance.title, 'description': instance.description, @@ -57,5 +75,7 @@ Map _$TenderDataToJson(_TenderData instance) => 'country_code': instance.countryCode, 'duration_unit': instance.durationUnit, 'buyer_organization': instance.buyerOrganization, + 'lots': instance.lots, + 'official_languages': instance.officialLanguages, 'status': instance.status, }; diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart index 695fe8f..09267f6 100644 --- a/lib/gen/assets.gen.dart +++ b/lib/gen/assets.gen.dart @@ -11,6 +11,38 @@ import 'package:flutter/widgets.dart'; +class $AssetsFontsGen { + const $AssetsFontsGen(); + + /// File path: assets/fonts/Roboto-Black.ttf + String get robotoBlack => 'assets/fonts/Roboto-Black.ttf'; + + /// File path: assets/fonts/Roboto-Bold.ttf + String get robotoBold => 'assets/fonts/Roboto-Bold.ttf'; + + /// File path: assets/fonts/Roboto-Light.ttf + String get robotoLight => 'assets/fonts/Roboto-Light.ttf'; + + /// File path: assets/fonts/Roboto-Medium.ttf + String get robotoMedium => 'assets/fonts/Roboto-Medium.ttf'; + + /// File path: assets/fonts/Roboto-Regular.ttf + String get robotoRegular => 'assets/fonts/Roboto-Regular.ttf'; + + /// File path: assets/fonts/Roboto-Thin.ttf + String get robotoThin => 'assets/fonts/Roboto-Thin.ttf'; + + /// List of all assets + List get values => [ + robotoBlack, + robotoBold, + robotoLight, + robotoMedium, + robotoRegular, + robotoThin, + ]; +} + class $AssetsIconsGen { const $AssetsIconsGen(); @@ -301,6 +333,7 @@ class $AssetsSvgsGen { class Assets { const Assets._(); + static const $AssetsFontsGen fonts = $AssetsFontsGen(); static const $AssetsIconsGen icons = $AssetsIconsGen(); static const $AssetsPngsGen pngs = $AssetsPngsGen(); static const $AssetsSvgsGen svgs = $AssetsSvgsGen(); diff --git a/lib/gen/fonts.gen.dart b/lib/gen/fonts.gen.dart new file mode 100644 index 0000000..9962f14 --- /dev/null +++ b/lib/gen/fonts.gen.dart @@ -0,0 +1,16 @@ +// dart format width=80 +/// GENERATED CODE - DO NOT MODIFY BY HAND +/// ***************************************************** +/// FlutterGen +/// ***************************************************** + +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: deprecated_member_use,directives_ordering,implicit_dynamic_list_literal,unnecessary_import + +class FontFamily { + FontFamily._(); + + /// Font family: Roboto + static const String roboto = 'Roboto'; +} diff --git a/lib/view_models/tenders_view_model.dart b/lib/view_models/tenders_view_model.dart index 47ff830..7dd0057 100644 --- a/lib/view_models/tenders_view_model.dart +++ b/lib/view_models/tenders_view_model.dart @@ -39,42 +39,12 @@ class TendersViewModel with ChangeNotifier { static const int _pageSize = 10; - // --- Offset pagination state --- + // --- Infinite-scroll offset state --- int _currentOffset = 0; - int _lastValidPages = 1; - int _lastValidLimit = _pageSize; bool _hasMoreData = true; bool get hasMoreData => _hasMoreData; - /// 0-based current page index. - int get currentPageIndex { - final meta = _tendersResponse?.meta; - final limit = meta?.limit ?? _lastValidLimit; - - if (limit > 0) { - return _currentOffset ~/ limit; - } - if (meta?.offset != null && limit > 0) { - return meta!.offset! ~/ limit; - } - if (meta?.page != null) { - return meta!.page! - 1; - } - return 0; - } - - /// 1-based current page (for display). - int get currentPage => currentPageIndex + 1; - - int get totalPages { - final pages = _tendersResponse?.meta?.pages; - if (pages != null && pages > 0) { - return pages; - } - return _lastValidPages; - } - // Filter States DateTimeRange? selectedDateRange; String selectedStatus = 'All'; @@ -233,12 +203,6 @@ class TendersViewModel with ChangeNotifier { case Ok(): _tendersResponse = result.value; final meta = _tendersResponse?.meta; - if (meta?.pages != null && meta!.pages! > 0) { - _lastValidPages = meta.pages!; - } - if (meta?.limit != null && meta!.limit! > 0) { - _lastValidLimit = meta.limit!; - } if (meta?.offset != null) { _currentOffset = meta!.offset!; } @@ -299,12 +263,6 @@ class TendersViewModel with ChangeNotifier { data: result.value.data?.copyWith(tenders: combined), ); final newMeta = result.value.meta; - if (newMeta?.pages != null && newMeta!.pages! > 0) { - _lastValidPages = newMeta.pages!; - } - if (newMeta?.limit != null && newMeta!.limit! > 0) { - _lastValidLimit = newMeta.limit!; - } _hasMoreData = newMeta?.hasMore ?? (newTenders.length >= _pageSize); } else { @@ -492,54 +450,4 @@ class TendersViewModel with ChangeNotifier { sortOrder = ''; notifyListeners(); } - - /// [selected] is the absolute 0-based target page index. - Future handlePaginationChange(int selected) async { - if (selected < 0) { - return; - } - final maxPage = totalPages - 1; - if (maxPage >= 0 && selected > maxPage) { - return; - } - - if (selected == currentPageIndex) { - return; - } - - await _goToOffsetPage(selected); - } - - Future _goToOffsetPage(int pageIndex) async { - final limit = _tendersResponse?.meta?.limit ?? _pageSize; - _currentOffset = pageIndex * limit; - await _fetchPage(); - } - - Future _fetchPage() async { - _isLoading = true; - notifyListeners(); - - final result = await _tendersRepository.getTenders( - limit: _pageSize, - offset: _currentOffset, - query: _searchQuery, - sortBy: sortBy, - sortOrder: sortOrder, - publicationDateFrom: startDateUnix, - publicationDateTo: endDateUnix, - ); - - _handleListResult(result, fetchAllFeedbacks: true); - _isLoading = false; - notifyListeners(); - _errorMessage = null; - notifyListeners(); - } - - /// Kept for backwards compatibility with existing callers. - /// Accepts a 1-based page number. - void jumpToPage(int page) { - handlePaginationChange(page - 1); - } } diff --git a/lib/views/detail/pages/detail_desktop_page.dart b/lib/views/detail/pages/detail_desktop_page.dart index 6cf75d1..deb2c07 100644 --- a/lib/views/detail/pages/detail_desktop_page.dart +++ b/lib/views/detail/pages/detail_desktop_page.dart @@ -4,7 +4,9 @@ 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'; -import 'package:tm_app/views/detail/widgets/tender_detail_card.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'; @@ -121,8 +123,10 @@ class _TenderDetailDesktopPageState extends State { detail: detail, ), SizedBox(height: 32.0.h()), - TenderDetailCard(detail: detail), - SizedBox(height: 24.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 0d76ffa..c4a0d09 100644 --- a/lib/views/detail/pages/detail_mobile_page.dart +++ b/lib/views/detail/pages/detail_mobile_page.dart @@ -5,7 +5,9 @@ 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'; -import 'package:tm_app/views/detail/widgets/tender_detail_card.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'; @@ -112,8 +114,10 @@ class _TenderDetailMobilePageState extends State { children: [ TenderDetailHeader(isScreenBig: false, detail: detail), SizedBox(height: 24.0.h()), - TenderDetailCard(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 a953d03..7e3363f 100644 --- a/lib/views/detail/pages/detail_tablet_page.dart +++ b/lib/views/detail/pages/detail_tablet_page.dart @@ -4,7 +4,9 @@ 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'; -import 'package:tm_app/views/detail/widgets/tender_detail_card.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'; @@ -123,8 +125,10 @@ class _TenderDetailTabletPageState extends State { SizedBox(height: 28.0.h()), TenderDetailHeader(isScreenBig: true, detail: detail), SizedBox(height: 28.0.h()), - TenderDetailCard(detail: detail), - SizedBox(height: 24.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 5a32bd6..df4df02 100644 --- a/lib/views/detail/strings/tender_details_strings.dart +++ b/lib/views/detail/strings/tender_details_strings.dart @@ -42,4 +42,33 @@ 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). + 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). + // Tender detail field labels + static const String noticeTypeCode = 'Notice Type'; + static const String formType = 'Form Type'; + static const String noticeLanguageCode = 'Notice Language'; + static const String issueDate = 'Issue Date'; + static const String issueTime = 'Issue Time'; + static const String procedureCode = 'Procedure'; + static const String durationLabel = 'Duration'; + static const String publicationDate = 'Publication Date'; + static const String tenderDeadlineDate = 'Tender Deadline'; + static const String submissionDeadline = 'Submission Deadline'; + static const String applicationDeadline = 'Application Deadline'; + static const String countryCode = 'Country'; + static const String buyerName = 'Buyer'; + static const String companyId = 'Company ID'; + static const String buyerCity = 'City'; + static const String buyerCountry = 'Buyer Country'; + static const String officialLanguages = 'Official Languages'; + static const String status = 'Status'; + static const String tenderId = 'Tender ID'; + static const String lots = 'Lots'; + static const String lotId = 'Lot ID'; } diff --git a/lib/views/detail/widgets/detail_drop_down.dart b/lib/views/detail/widgets/detail_drop_down.dart index 99a02fd..4e7b097 100644 --- a/lib/views/detail/widgets/detail_drop_down.dart +++ b/lib/views/detail/widgets/detail_drop_down.dart @@ -1,3 +1,7 @@ +// 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. 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 4dac05c..f9e3ed5 100644 --- a/lib/views/detail/widgets/tender_detail_action.dart +++ b/lib/views/detail/widgets/tender_detail_action.dart @@ -4,14 +4,19 @@ 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'; -import 'package:tm_app/views/detail/widgets/meeting_time_bottom_sheet.dart'; -import 'package:tm_app/views/detail/widgets/meeting_time_dialog.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/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'; @@ -68,38 +73,37 @@ class _TenderDetailActionsState extends State { builder: (context) { return SelectSubmissionBottomSheet( onConfirm: (String value) { - 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) { - // AppToast.success( - // context, - // TenderDetailsStrings - // .meetingTimeSuccessfullySet, - // ); - }, - ); - }, - ); - } - }); - } + // 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) {}, + // ); + // }, + // ); + // } + // }); + // } }, ); }, @@ -143,6 +147,17 @@ 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. + void _showSubmissionReceivedToast() { + if (!mounted) { + return; + } + AppToast.success(context, TenderDetailsStrings.submissionReceived); + } + Row _webActions(Role userRole) { return Row( children: [ @@ -206,33 +221,37 @@ class _TenderDetailActionsState extends State { builder: (context) { return SelectSubmissionDialog( onConfirm: (String value) { - 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) {}, - ); - }, - ); - } - }); - } + // 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) {}, + // ); + // }, + // ); + // } + // }); + // } }, ); }, @@ -242,41 +261,34 @@ class _TenderDetailActionsState extends State { builder: (context) { return SelectSubmissionBottomSheet( onConfirm: (String value) { - if (value == - TenderSubmissionMode.selfApply.value) { - // viewModel.submitTenderApproval( - // tenderId: widget.detail.id!, - // submissionMode: 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, - ); - }, - ); - // return MeetingTimeDialog( - // onConfirm: (String value) { - // if (context.mounted) { - // const CompletionOfDocumentsRouteData() - // .push(context); - // } - // }, - // ); - }, - ); - } + // 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 b94b65b..a7de37f 100644 --- a/lib/views/detail/widgets/tender_detail_card.dart +++ b/lib/views/detail/widgets/tender_detail_card.dart @@ -1,3 +1,7 @@ +// 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. 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 236d1fe..62e0acd 100644 --- a/lib/views/detail/widgets/tender_detail_header.dart +++ b/lib/views/detail/widgets/tender_detail_header.dart @@ -9,7 +9,12 @@ 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'; -import 'detail_drop_down.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; @@ -41,44 +46,32 @@ class TenderDetailHeader extends StatelessWidget { SizedBox(height: 16.0.h()), _locationBudgetRow(), SizedBox(height: 24.0.h()), - _projectId(), - SizedBox(height: 12.0.h()), - _winningChanceAndDificulty(), - SizedBox(height: 24.0.h()), - _keyRisksDropDown(), - 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(), - SizedBox(height: 24.0.h()), - _serviceDropDown(), - SizedBox(height: 24.0.h()), - _requirementsDropDown(), - SizedBox(height: 24.0.h()), - _backgroundDropDown(), + // AI-NOTE (for PR reviewer): Mock dropdowns disabled (kept for future). + // _serviceDropDown(), + // SizedBox(height: 24.0.h()), + // _requirementsDropDown(), + // SizedBox(height: 24.0.h()), + // _backgroundDropDown(), ], ), ); } - 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 _descriptionText() { return Text( detail.description ?? '', @@ -101,6 +94,26 @@ 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', @@ -146,7 +159,7 @@ class TenderDetailHeader extends StatelessWidget { borderRadius: BorderRadius.circular(8), ), child: Text( - 'Difficulty 2/5', + 'Difficulty 2/5', style: TextStyle( color: AppColors.blue10, fontSize: 12.0.sp(), @@ -167,7 +180,7 @@ class TenderDetailHeader extends StatelessWidget { borderRadius: BorderRadius.circular(8), ), child: Text( - 'Project ID 6243372025NLD', + 'Project ID 6243372025NLD', style: TextStyle( color: AppColors.blue10, fontSize: 12.0.sp(), @@ -176,6 +189,7 @@ class TenderDetailHeader extends StatelessWidget { ), ); } + */ Widget _locationBudgetRow() { return Row( diff --git a/lib/views/detail/widgets/tender_detail_info_section.dart b/lib/views/detail/widgets/tender_detail_info_section.dart index 56ec426..87851e5 100644 --- a/lib/views/detail/widgets/tender_detail_info_section.dart +++ b/lib/views/detail/widgets/tender_detail_info_section.dart @@ -1,10 +1,21 @@ import 'package:flutter/material.dart'; +import 'package:tm_app/core/theme/colors.dart'; +import 'package:tm_app/core/utils/date_utils.dart'; import 'package:tm_app/core/utils/price_extension.dart'; +import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/data/services/model/tender_data/tender_data.dart'; 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. class TenderDetailInfoSection extends StatelessWidget { final bool isScreenBig; final TenderData detail; @@ -17,24 +28,114 @@ class TenderDetailInfoSection extends StatelessWidget { @override Widget build(BuildContext context) { + final buyer = detail.buyerOrganization; + final address = buyer?.address; + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ DeadlineItem(detail: detail, isScreenBig: isScreenBig), - InfoItem( - title: TenderDetailsStrings.tenderClientLabel, - value: detail.buyerOrganization!.name ?? '', + ..._infoItem(TenderDetailsStrings.noticeTypeCode, detail.noticeTypeCode), + ..._infoItem(TenderDetailsStrings.formType, detail.formType), + ..._infoItem( + TenderDetailsStrings.noticeLanguageCode, + detail.noticeLanguageCode, ), - InfoItem( - title: TenderDetailsStrings.estimatedValue, - value: - '${detail.estimatedValue?.formattedPrice} ${detail.currency ?? ''}', + ..._infoItem( + TenderDetailsStrings.issueDate, + _date(detail.issueDate), ), - InfoItem( - title: TenderDetailsStrings.tenderReferenceNumberLabel, - value: detail.noticePublicationId ?? '', + ..._infoItem( + TenderDetailsStrings.issueTime, + _dateTime(detail.issueTime), ), + ..._infoItem(TenderDetailsStrings.procedureCode, detail.procedureCode), + ..._infoItem( + TenderDetailsStrings.estimatedValue, + detail.estimatedValue == null + ? null + : '${detail.estimatedValue?.formattedPrice} ${detail.currency ?? ''}', + ), + ..._infoItem(TenderDetailsStrings.durationLabel, _duration()), + ..._infoItem( + TenderDetailsStrings.publicationDate, + _date(detail.publicationDate), + ), + ..._infoItem( + TenderDetailsStrings.tenderDeadlineDate, + _date(detail.tenderDeadline), + ), + ..._infoItem(TenderDetailsStrings.countryCode, detail.countryCode), + ..._infoItem(TenderDetailsStrings.buyerName, buyer?.name), + ..._infoItem(TenderDetailsStrings.companyId, buyer?.companyId), + ..._infoItem(TenderDetailsStrings.buyerCity, address?.cityName), + ..._infoItem(TenderDetailsStrings.buyerCountry, address?.countryCode), + ..._infoItem( + TenderDetailsStrings.officialLanguages, + (detail.officialLanguages == null || + detail.officialLanguages!.isEmpty) + ? null + : detail.officialLanguages!.join(', '), + ), + ..._infoItem(TenderDetailsStrings.status, detail.status), + ..._infoItem(TenderDetailsStrings.tenderId, detail.tenderId), + ..._lotsSection(), ], ); } + + /// Returns an [InfoItem] only when [value] has content, so empty fields are + /// not rendered as blank rows. + List _infoItem(String title, String? value) { + if (value == null || value.trim().isEmpty) { + return const []; + } + return [InfoItem(title: title, value: value)]; + } + + List _lotsSection() { + final lots = detail.lots; + if (lots == null || lots.isEmpty) { + return const []; + } + + return [ + SizedBox(height: 8.0.h()), + Text( + TenderDetailsStrings.lots, + style: TextStyle( + color: AppColors.grey80, + fontWeight: FontWeight.w600, + fontSize: 16.0.sp(), + ), + ), + SizedBox(height: 8.0.h()), + for (final lot in lots) ...[ + ..._infoItem(TenderDetailsStrings.lotId, lot.lotId), + ..._infoItem('Title', lot.title), + ..._infoItem('Description', lot.description), + ], + ]; + } + + String? _duration() { + final duration = detail.duration?.trim() ?? ''; + final unit = detail.durationUnit?.trim() ?? ''; + final combined = '$duration $unit'.trim(); + return combined.isEmpty ? null : combined; + } + + String? _date(int? unix) { + if (unix == null) { + return null; + } + return timeConvertor(unix); + } + + String? _dateTime(int? unix) { + if (unix == null) { + return null; + } + return timeConvertor(unix, hasTime: true); + } } diff --git a/lib/views/shared/desktop_navigation_widget.dart b/lib/views/shared/desktop_navigation_widget.dart index b252910..a4f3804 100644 --- a/lib/views/shared/desktop_navigation_widget.dart +++ b/lib/views/shared/desktop_navigation_widget.dart @@ -87,25 +87,30 @@ class DesktopNavigationWidget extends StatelessWidget { iconPath: AssetsManager.tenders, activeIconPath: AssetsManager.tendersActive, ), - 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, - ), + // 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, + // ), const SizedBox(width: 24), _navigationItem( context: context, diff --git a/lib/views/tenders/pages/d_tenders_page.dart b/lib/views/tenders/pages/d_tenders_page.dart index cdfce5b..98e88da 100644 --- a/lib/views/tenders/pages/d_tenders_page.dart +++ b/lib/views/tenders/pages/d_tenders_page.dart @@ -13,7 +13,6 @@ import 'package:tm_app/views/tenders/strings/tenders_strings.dart'; import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart'; import 'package:tm_app/views/tenders/widgets/tenders_filter_dialog.dart'; import 'package:tm_app/views/tenders/widgets/tenders_sort_dialog.dart'; -import 'package:tm_app/views/tenders/widgets/windowed_pagination.dart'; import '../../../core/constants/common_strings.dart'; import '../../../core/utils/app_toast.dart'; @@ -78,32 +77,17 @@ class _DesktopTendersPageState extends State { return const Center(child: Text(CommonStrings.noData)); } - return Column( - children: [ - Expanded( - child: SingleChildScrollView( - child: SizedBox( - width: 740, - height: 800, - child: Padding( - padding: EdgeInsets.symmetric(vertical: 4.0.h()), - child: MainTendersSlider( - tenders: tenders, - isDesktop: true, - ), - ), - ), + return Center( + child: SizedBox( + width: 740, + child: Padding( + padding: EdgeInsets.symmetric(vertical: 4.0.h()), + child: MainTendersSlider( + tenders: tenders, + isDesktop: true, ), ), - SizedBox( - width: 740, - child: WindowedPagination( - currentPage: vm.currentPageIndex, - totalPages: vm.totalPages, - onPageChanged: vm.handlePaginationChange, - ), - ), - ], + ), ); }, ), diff --git a/lib/views/tenders/pages/m_tenders_page.dart b/lib/views/tenders/pages/m_tenders_page.dart index f29d6a1..5325e16 100644 --- a/lib/views/tenders/pages/m_tenders_page.dart +++ b/lib/views/tenders/pages/m_tenders_page.dart @@ -9,7 +9,6 @@ import '../../../core/utils/app_toast.dart'; import '../../shared/tender_app_bar.dart'; import '../strings/tenders_strings.dart'; import '../widgets/main_tenders_slider.dart'; -import '../widgets/windowed_pagination.dart'; class MobileTendersPage extends StatefulWidget { const MobileTendersPage({super.key}); @@ -62,29 +61,13 @@ class _MobileTendersPageState extends State { return const Center(child: Text(CommonStrings.noData)); } - return Column( - children: [ - Expanded( - child: SingleChildScrollView( - child: Padding( - padding: EdgeInsets.symmetric(vertical: 18.0.h()), - child: MainTendersSlider( - key: ValueKey( - 'tenders-slider-${viewModel.currentPageIndex}', - ), - tenders: - viewModel.tendersResponse?.data?.tenders ?? [], - ), - ), - ), + return SingleChildScrollView( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 18.0.h()), + child: MainTendersSlider( + tenders: viewModel.tendersResponse?.data?.tenders ?? [], ), - WindowedPagination( - currentPage: viewModel.currentPageIndex, - totalPages: viewModel.totalPages, - onPageChanged: viewModel.handlePaginationChange, - compact: true, - ), - ], + ), ); }, ), diff --git a/lib/views/tenders/pages/t_tenders_page.dart b/lib/views/tenders/pages/t_tenders_page.dart index 4129f0a..e3b38de 100644 --- a/lib/views/tenders/pages/t_tenders_page.dart +++ b/lib/views/tenders/pages/t_tenders_page.dart @@ -5,7 +5,6 @@ import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/view_models/tenders_view_model.dart'; import 'package:tm_app/views/shared/tablet_navigation_widget.dart'; import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart'; -import 'package:tm_app/views/tenders/widgets/windowed_pagination.dart'; import '../../../core/constants/common_strings.dart'; import '../../shared/tender_app_bar.dart'; @@ -54,30 +53,16 @@ class _TabletTendersPageState extends State { viewModel.isLoading == false) { return const Center(child: Text(CommonStrings.noData)); } - return Column( - children: [ - Expanded( - child: SingleChildScrollView( - child: Padding( - padding: EdgeInsetsDirectional.only( - end: 24.0.w(), - top: 128.0.h(), - ), - child: MainTendersSlider( - key: ValueKey( - 'tenders-slider-${viewModel.currentPageIndex}', - ), - tenders: viewModel.tendersResponse?.data?.tenders ?? [], - ), - ), - ), + return SingleChildScrollView( + child: Padding( + padding: EdgeInsetsDirectional.only( + end: 24.0.w(), + top: 128.0.h(), ), - WindowedPagination( - currentPage: viewModel.currentPageIndex, - totalPages: viewModel.totalPages, - onPageChanged: viewModel.handlePaginationChange, + child: MainTendersSlider( + tenders: viewModel.tendersResponse?.data?.tenders ?? [], ), - ], + ), ); }, ), diff --git a/lib/views/tenders/widgets/main_tenders_slider.dart b/lib/views/tenders/widgets/main_tenders_slider.dart index 66cde15..214c7d5 100644 --- a/lib/views/tenders/widgets/main_tenders_slider.dart +++ b/lib/views/tenders/widgets/main_tenders_slider.dart @@ -25,8 +25,41 @@ class MainTendersSlider extends StatefulWidget { class _MainTendersSliderState extends State { final PageController pageController = PageController(initialPage: 0); + final ScrollController _scrollController = ScrollController(); int currentPage = 1; + /// Start prefetching the next page once this fraction of the currently + /// loaded items has been scrolled/swiped past. + static const double _prefetchFraction = 0.5; + + @override + void initState() { + super.initState(); + _scrollController.addListener(_onScroll); + } + + void _onScroll() { + if (!_scrollController.hasClients) { + return; + } + final position = _scrollController.position; + if (position.maxScrollExtent <= 0) { + return; + } + if (position.pixels >= position.maxScrollExtent * _prefetchFraction) { + _maybeLoadMore(); + } + } + + void _maybeLoadMore() { + final viewModel = context.read(); + if (viewModel.hasMoreData && + !viewModel.isLoadingMore && + !viewModel.isLoading) { + viewModel.loadMoreTenders(); + } + } + void _goToPreviousPage() { if (currentPage == 1) { return; @@ -82,6 +115,7 @@ class _MainTendersSliderState extends State { @override void dispose() { pageController.dispose(); + _scrollController.dispose(); super.dispose(); } @@ -115,6 +149,12 @@ class _MainTendersSliderState extends State { if (!viewModel.hasFeedbackForTender(id)) { viewModel.getTenderFeedback(id); } + + // Infinite scroll: once we pass the halfway point of the loaded + // tenders, prefetch the next page in the background. + if (index >= (widget.tenders.length * _prefetchFraction).floor()) { + _maybeLoadMore(); + } }, itemBuilder: (context, index) { final tender = widget.tenders[index]; @@ -230,6 +270,7 @@ class _MainTendersSliderState extends State { children: [ Expanded( child: ListView.separated( + controller: _scrollController, padding: EdgeInsets.all(16.0.h()), itemCount: widget.tenders.length + diff --git a/lib/views/tenders/widgets/tender_card.dart b/lib/views/tenders/widgets/tender_card.dart index 15d9295..6f42914 100644 --- a/lib/views/tenders/widgets/tender_card.dart +++ b/lib/views/tenders/widgets/tender_card.dart @@ -8,7 +8,11 @@ import '../../../core/constants/assets.dart'; import '../../../core/theme/colors.dart'; import '../../../core/utils/date_utils.dart'; import '../../../core/utils/size_config.dart'; -import '../../detail/strings/tender_details_strings.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'; @@ -83,13 +87,17 @@ class TenderCard extends StatelessWidget { ), child: Column( children: [ - // Match percentage - _matchPercentage(), - SizedBox(height: isDesktop ? 4.0.h() : 8.0.h()), - - // Progress bar - _progressBar(), - SizedBox(height: isDesktop ? 8.0.h() : 16.0.h()), + // 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( @@ -243,6 +251,11 @@ 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, @@ -287,6 +300,7 @@ class TenderCard extends StatelessWidget { ), ); } + */ Widget _locationInfo() { return Expanded( diff --git a/lib/views/tenders/widgets/windowed_pagination.dart b/lib/views/tenders/widgets/windowed_pagination.dart deleted file mode 100644 index 3514267..0000000 --- a/lib/views/tenders/widgets/windowed_pagination.dart +++ /dev/null @@ -1,250 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:tm_app/core/theme/colors.dart'; - -/// A windowed numbered pagination widget that mirrors the Next.js -/// implementation: ±10 page-button window around the current page, -/// 1-based labels, prev/next chevrons. -class WindowedPagination extends StatelessWidget { - const WindowedPagination({ - super.key, - required this.currentPage, - required this.totalPages, - required this.onPageChanged, - this.maxPageJump = 10, - this.compact = false, - }); - - /// 0-based absolute current page index. - final int currentPage; - - /// Total number of pages (1-based count). - final int totalPages; - - /// Called with the 0-based target page index. - final ValueChanged onPageChanged; - - /// Half-width of the page-button window (web uses 10). - final int maxPageJump; - - /// If true, renders smaller buttons (for mobile). - final bool compact; - - @override - Widget build(BuildContext context) { - if (totalPages <= 1) { - return const SizedBox.shrink(); - } - - final clampedCurrent = currentPage.clamp(0, totalPages - 1); - final windowStart = (clampedCurrent - maxPageJump).clamp(0, totalPages - 1); - final windowEnd = (clampedCurrent + maxPageJump).clamp(0, totalPages - 1); - - final buttons = []; - - buttons.add( - _NavButton( - icon: Icons.chevron_left, - enabled: clampedCurrent > 0, - compact: compact, - onTap: () => onPageChanged(clampedCurrent - 1), - ), - ); - - if (windowStart > 0) { - buttons.add(_buildPageButton(0, clampedCurrent)); - if (windowStart > 1) { - buttons.add(const _Ellipsis()); - } - } - - for (var i = windowStart; i <= windowEnd; i++) { - buttons.add(_buildPageButton(i, clampedCurrent)); - } - - if (windowEnd < totalPages - 1) { - if (windowEnd < totalPages - 2) { - buttons.add(const _Ellipsis()); - } - buttons.add(_buildPageButton(totalPages - 1, clampedCurrent)); - } - - buttons.add( - _NavButton( - icon: Icons.chevron_right, - enabled: clampedCurrent < totalPages - 1, - compact: compact, - onTap: () => onPageChanged(clampedCurrent + 1), - ), - ); - - final gap = compact ? 4.0 : 6.0; - return Padding( - padding: EdgeInsets.symmetric(vertical: compact ? 8 : 12), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: ConstrainedBox( - constraints: BoxConstraints(minWidth: constraints.maxWidth), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: _withSpacing(buttons, gap), - ), - ), - ); - }, - ), - ); - } - - Widget _buildPageButton(int pageIndex, int currentPageIndex) { - final isActive = pageIndex == currentPageIndex; - return _PageButton( - label: '${pageIndex + 1}', - isActive: isActive, - compact: compact, - onTap: isActive ? null : () => onPageChanged(pageIndex), - ); - } - - static List _withSpacing(List items, double gap) { - final result = []; - for (var i = 0; i < items.length; i++) { - result.add(items[i]); - if (i != items.length - 1) { - result.add(SizedBox(width: gap)); - } - } - return result; - } -} - -class _PageButton extends StatelessWidget { - const _PageButton({ - required this.label, - required this.isActive, - required this.compact, - this.onTap, - }); - - final String label; - final bool isActive; - final bool compact; - final VoidCallback? onTap; - - @override - Widget build(BuildContext context) { - final size = compact ? 32.0 : 38.0; - return SizedBox( - width: size, - height: size, - child: Material( - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.circular(12), - onTap: onTap, - child: Container( - alignment: Alignment.center, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - gradient: isActive - ? const LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [AppColors.mainBlue, AppColors.primaryColor], - ) - : null, - color: isActive ? null : AppColors.grey0, - border: Border.all( - color: isActive ? Colors.transparent : AppColors.grey30, - width: 1, - ), - boxShadow: isActive - ? [ - BoxShadow( - color: AppColors.mainBlue.withValues(alpha: 0.25), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ] - : null, - ), - child: Text( - label, - style: TextStyle( - fontSize: compact ? 12 : 13, - fontWeight: isActive ? FontWeight.w600 : FontWeight.w500, - color: isActive ? Colors.white : AppColors.grey70, - ), - ), - ), - ), - ), - ); - } -} - -class _NavButton extends StatelessWidget { - const _NavButton({ - required this.icon, - required this.enabled, - required this.compact, - required this.onTap, - }); - - final IconData icon; - final bool enabled; - final bool compact; - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - final size = compact ? 32.0 : 38.0; - return SizedBox( - width: size, - height: size, - child: Material( - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.circular(12), - onTap: enabled ? onTap : null, - child: Container( - alignment: Alignment.center, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: AppColors.grey0, - border: Border.all( - color: enabled ? AppColors.grey30 : AppColors.grey20, - width: 1, - ), - ), - child: Icon( - icon, - size: compact ? 18 : 20, - color: enabled ? AppColors.grey70 : AppColors.grey40, - ), - ), - ), - ), - ); - } -} - -class _Ellipsis extends StatelessWidget { - const _Ellipsis(); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 2), - child: Text( - '…', - style: TextStyle( - fontSize: 14, - color: AppColors.grey60, - fontWeight: FontWeight.w500, - ), - ), - ); - } -} diff --git a/test/model/feedback_data_test.dart b/test/model/feedback_data_test.dart index 121a141..682b21b 100644 --- a/test/model/feedback_data_test.dart +++ b/test/model/feedback_data_test.dart @@ -51,6 +51,10 @@ 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, @@ -58,6 +62,11 @@ void main() { id: 'TND123', tenderId: 'TND123', noticePublicationId: 'NP123', + noticeTypeCode: 'cn-standard', + formType: 'competition', + noticeLanguageCode: 'ENG', + issueDate: 1700000000, + issueTime: 1700000000, publicationDate: 1700000000, title: 'Tender Title', description: 'Some description', @@ -74,7 +83,11 @@ void main() { durationUnit: 'month', buyerOrganization: Organization( name: 'Test Organization', + companyId: 'ORG001', + address: null, ), + lots: null, + officialLanguages: null, status: 'active', ); @@ -114,6 +127,11 @@ void main() { id: 'TND001', tenderId: 'TND001', noticePublicationId: 'NP1', + noticeTypeCode: 'cn-standard', + formType: 'competition', + noticeLanguageCode: 'ENG', + issueDate: 1700000000, + issueTime: 1700000000, publicationDate: 1700000000, title: 'Old Tender', description: 'Old desc', @@ -130,7 +148,11 @@ void main() { durationUnit: 'month', buyerOrganization: Organization( name: 'Old Org', + companyId: 'ORG001', + address: null, ), + lots: null, + officialLanguages: null, status: 'inactive', ), updatedAt: 1750000000,