dde66521f6
- Introduced a .dockerignore file to optimize Docker builds by excluding unnecessary files. - Updated the Dockerfile to use a Flutter base image, enabling web builds for the application. - Added new data models for `Lot` and `OrganizationAddress` to represent tender details more accurately. - Enhanced the `Organization` model to include `companyId` and a nested `address` field. - Updated the `TenderData` model to include new fields such as `noticeTypeCode`, `formType`, and `lots`, reflecting the API response structure. - Regenerated necessary code for data models to ensure compatibility with the updated structures.
142 lines
4.9 KiB
Dart
142 lines
4.9 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';
|
|
|
|
// 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;
|
|
|
|
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('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);
|
|
}
|
|
}
|