Refactor date handling in tender data model and UI components. Added unixTimestampFromJson for parsing timestamps and updated timeConvertor to handle null values. Updated relevant model fields and UI to use the new parsing logic. Added unit tests for date utilities.
This commit is contained in:
@@ -1,9 +1,32 @@
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
String timeConvertor(int timestamp) {
|
||||
// Convert seconds → milliseconds
|
||||
final date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
||||
/// Parses API date fields that may arrive as int, double, or string.
|
||||
int? unixTimestampFromJson(dynamic value) {
|
||||
if (value == null) return null;
|
||||
if (value is int) return value;
|
||||
if (value is num) return value.toInt();
|
||||
if (value is String) {
|
||||
final trimmed = value.trim();
|
||||
if (trimmed.isEmpty) return null;
|
||||
return int.tryParse(trimmed);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Format to yyyy-MM-dd
|
||||
/// Converts a Unix timestamp (seconds) from the API to a display string.
|
||||
/// Mirrors Next.js `unixToDate` using `moment.unix(unix)`.
|
||||
String timeConvertor(int? unix, {bool hasTime = false}) {
|
||||
if (unix == null || unix == 0) return '-';
|
||||
|
||||
// Values above ~year 2286 in seconds are treated as milliseconds.
|
||||
final seconds = unix > 9999999999 ? unix ~/ 1000 : unix;
|
||||
final date = DateTime.fromMillisecondsSinceEpoch(
|
||||
seconds * 1000,
|
||||
isUtc: true,
|
||||
).toLocal();
|
||||
|
||||
if (hasTime) {
|
||||
return DateFormat('yyyy-MM-dd HH:mm').format(date);
|
||||
}
|
||||
return DateFormat('yyyy-MM-dd').format(date);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// ignore_for_file: invalid_annotation_target
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:tm_app/core/utils/date_utils.dart';
|
||||
import 'package:tm_app/data/services/model/organization/organization.dart';
|
||||
|
||||
part 'tender_data.freezed.dart';
|
||||
@@ -15,7 +16,8 @@ abstract class TenderData with _$TenderData {
|
||||
@JsonKey(name: 'tender_id') required String? tenderId,
|
||||
@JsonKey(name: 'notice_publication_id')
|
||||
required String? noticePublicationId,
|
||||
@JsonKey(name: 'publication_date') required int? publicationDate,
|
||||
@JsonKey(name: 'publication_date', fromJson: unixTimestampFromJson)
|
||||
required int? publicationDate,
|
||||
required String? title,
|
||||
required String? description,
|
||||
@JsonKey(name: 'procurement_type_code')
|
||||
@@ -24,9 +26,12 @@ abstract class TenderData with _$TenderData {
|
||||
@JsonKey(name: 'estimated_value') required int? estimatedValue,
|
||||
required String? currency,
|
||||
required String? duration,
|
||||
@JsonKey(name: 'tender_deadline') required int? tenderDeadline,
|
||||
@JsonKey(name: 'submission_deadline') required int? submissionDeadline,
|
||||
@JsonKey(name: 'application_deadline') required int? applicationDeadline,
|
||||
@JsonKey(name: 'tender_deadline', fromJson: unixTimestampFromJson)
|
||||
required int? tenderDeadline,
|
||||
@JsonKey(name: 'submission_deadline', fromJson: unixTimestampFromJson)
|
||||
required int? submissionDeadline,
|
||||
@JsonKey(name: 'application_deadline', fromJson: unixTimestampFromJson)
|
||||
required int? applicationDeadline,
|
||||
@JsonKey(name: 'submission_url') required String? submissionUrl,
|
||||
@JsonKey(name: 'country_code') required String? countryCode,
|
||||
@JsonKey(name: 'duration_unit') required String? durationUnit,
|
||||
|
||||
@@ -12,7 +12,7 @@ _TenderData _$TenderDataFromJson(Map<String, dynamic> json) => _TenderData(
|
||||
id: json['id'] as String?,
|
||||
tenderId: json['tender_id'] as String?,
|
||||
noticePublicationId: json['notice_publication_id'] as String?,
|
||||
publicationDate: (json['publication_date'] as num?)?.toInt(),
|
||||
publicationDate: unixTimestampFromJson(json['publication_date']),
|
||||
title: json['title'] as String?,
|
||||
description: json['description'] as String?,
|
||||
procurementTypeCode: json['procurement_type_code'] as String?,
|
||||
@@ -20,9 +20,9 @@ _TenderData _$TenderDataFromJson(Map<String, dynamic> json) => _TenderData(
|
||||
estimatedValue: (json['estimated_value'] as num?)?.toInt(),
|
||||
currency: json['currency'] as String?,
|
||||
duration: json['duration'] as String?,
|
||||
tenderDeadline: (json['tender_deadline'] as num?)?.toInt(),
|
||||
submissionDeadline: (json['submission_deadline'] as num?)?.toInt(),
|
||||
applicationDeadline: (json['application_deadline'] as num?)?.toInt(),
|
||||
tenderDeadline: unixTimestampFromJson(json['tender_deadline']),
|
||||
submissionDeadline: unixTimestampFromJson(json['submission_deadline']),
|
||||
applicationDeadline: unixTimestampFromJson(json['application_deadline']),
|
||||
submissionUrl: json['submission_url'] as String?,
|
||||
countryCode: json['country_code'] as String?,
|
||||
durationUnit: json['duration_unit'] as String?,
|
||||
|
||||
@@ -51,7 +51,7 @@ class DeadlineItem extends StatelessWidget {
|
||||
),
|
||||
SizedBox(height: 4.0.h()),
|
||||
Text(
|
||||
timeConvertor(detail.submissionDeadline!),
|
||||
timeConvertor(detail.submissionDeadline),
|
||||
style: TextStyle(
|
||||
color: AppColors.grey80,
|
||||
fontWeight: FontWeight.w400,
|
||||
@@ -80,7 +80,7 @@ class DeadlineItem extends StatelessWidget {
|
||||
),
|
||||
SizedBox(height: 4.0.h()),
|
||||
Text(
|
||||
timeConvertor(detail.applicationDeadline!),
|
||||
timeConvertor(detail.applicationDeadline),
|
||||
style: TextStyle(
|
||||
color: AppColors.grey80,
|
||||
fontWeight: FontWeight.w400,
|
||||
@@ -124,7 +124,7 @@ class DeadlineItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
timeConvertor(detail.submissionDeadline!),
|
||||
timeConvertor(detail.submissionDeadline),
|
||||
style: TextStyle(
|
||||
color: AppColors.grey70,
|
||||
fontWeight: FontWeight.w400,
|
||||
@@ -146,7 +146,7 @@ class DeadlineItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
timeConvertor(detail.applicationDeadline!),
|
||||
timeConvertor(detail.applicationDeadline),
|
||||
style: TextStyle(
|
||||
color: AppColors.grey70,
|
||||
fontWeight: FontWeight.w400,
|
||||
|
||||
@@ -34,7 +34,7 @@ class TendersListItem extends StatelessWidget {
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: Text(
|
||||
timeConvertor(tender.publicationDate ?? 0),
|
||||
timeConvertor(tender.publicationDate),
|
||||
style: TextStyle(
|
||||
fontSize: 12.0.sp(),
|
||||
fontWeight: FontWeight.w400,
|
||||
@@ -64,7 +64,7 @@ class TendersListItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
timeConvertor(tender.tenderDeadline ?? 0),
|
||||
timeConvertor(tender.tenderDeadline),
|
||||
style: TextStyle(
|
||||
fontSize: 14.0.sp(),
|
||||
fontWeight: FontWeight.w500,
|
||||
|
||||
@@ -70,7 +70,7 @@ class LikedListItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
timeConvertor(tender.tenderDeadline!),
|
||||
timeConvertor(tender.tenderDeadline),
|
||||
style: TextStyle(
|
||||
fontSize: 14.0.sp(),
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -117,7 +117,7 @@ class LikedListItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
timeConvertor(tender.tenderDeadline!),
|
||||
timeConvertor(tender.tenderDeadline),
|
||||
style: TextStyle(
|
||||
fontSize: 14.0.sp(),
|
||||
fontWeight: FontWeight.w500,
|
||||
|
||||
@@ -115,7 +115,7 @@ class TenderCard extends StatelessWidget {
|
||||
|
||||
Widget _dateText() {
|
||||
return Text(
|
||||
timeConvertor(tender.publicationDate ?? 0),
|
||||
timeConvertor(tender.publicationDate),
|
||||
style: TextStyle(
|
||||
color: AppColors.grey60,
|
||||
fontSize: 12.0.sp(),
|
||||
@@ -150,7 +150,7 @@ class TenderCard extends StatelessWidget {
|
||||
),
|
||||
SizedBox(width: 4.0.w()),
|
||||
Text(
|
||||
timeConvertor(tender.submissionDeadline ?? 0),
|
||||
timeConvertor(tender.submissionDeadline),
|
||||
style: TextStyle(
|
||||
color: AppColors.grey80,
|
||||
fontSize: 14.0.sp(),
|
||||
|
||||
@@ -53,7 +53,7 @@ class TenderCard extends StatelessWidget {
|
||||
children: [
|
||||
// Date
|
||||
Text(
|
||||
timeConvertor(int.parse(tender.publicationDate!.toString())),
|
||||
timeConvertor(tender.publicationDate),
|
||||
style: TextStyle(
|
||||
color: AppColors.grey60,
|
||||
fontSize: 12.0.sp(),
|
||||
|
||||
Reference in New Issue
Block a user