86ed7bc665
- Updated the `Lot`, `Organization`, and `OrganizationAddress` models to enhance data mapping for tender details. - Revised the `TenderData` model to include additional fields reflecting the API response structure. - Improved UI components across desktop, mobile, and tablet views by commenting out unused sections for future use. - Added new string constants for lot details in the tender information section. - Enhanced unit tests to cover new model fields and ensure resilience to partial API payloads.
140 lines
4.7 KiB
Dart
140 lines
4.7 KiB
Dart
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';
|
|
|
|
// Renders only the backend-driven fields for the tender-details page (notice
|
|
// type, form type, languages, issue/publication dates, procedure, value,
|
|
// duration, deadlines, buyer org/address, lots, status, ids), using
|
|
// InfoItem/DeadlineItem. The _infoItem() helper returns an empty list when a
|
|
// value is null/blank so empty fields don't render as blank rows (this is why
|
|
// each entry is spread with `...`).
|
|
class TenderDetailInfoSection extends StatelessWidget {
|
|
final bool isScreenBig;
|
|
final TenderData detail;
|
|
|
|
const TenderDetailInfoSection({
|
|
required this.isScreenBig,
|
|
required this.detail,
|
|
super.key,
|
|
});
|
|
|
|
@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(TenderDetailsStrings.noticeTypeCode, detail.noticeTypeCode),
|
|
..._infoItem(TenderDetailsStrings.formType, detail.formType),
|
|
..._infoItem(
|
|
TenderDetailsStrings.noticeLanguageCode,
|
|
detail.noticeLanguageCode,
|
|
),
|
|
..._infoItem(
|
|
TenderDetailsStrings.issueDate,
|
|
_date(detail.issueDate),
|
|
),
|
|
..._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<Widget> _infoItem(String title, String? value) {
|
|
if (value == null || value.trim().isEmpty) {
|
|
return const [];
|
|
}
|
|
return [InfoItem(title: title, value: value)];
|
|
}
|
|
|
|
List<Widget> _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(TenderDetailsStrings.lotTitle, lot.title),
|
|
..._infoItem(TenderDetailsStrings.lotDescription, 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);
|
|
}
|
|
}
|