diff --git a/lib/core/constants/strings.dart b/lib/core/constants/strings.dart index f1013a7..abbff66 100644 --- a/lib/core/constants/strings.dart +++ b/lib/core/constants/strings.dart @@ -74,7 +74,6 @@ class AppStrings { static const String tenderDeliveryLocationsLabel = 'Delivery Locations'; static const String tenderReferenceNumberLabel = 'Reference Number'; static const String tenderLocationLabel = 'Locations'; - static const String tenderLocationCountry = 'UK'; static const String tenderPdfDocument = 'PDF , Document'; static const String tenderMatchProfile = 'Match with your profile'; static const String tenderIncompleteResume = 'Incomplete Resume Information'; @@ -82,10 +81,17 @@ class AppStrings { 'No experience in e-platform development'; static const String tenderSubmitButton = 'Submit'; static const String tenderRejectButton = 'Reject'; - static const String tenderClientExample = 'Procurement Notice Procurement Notice'; - static const String estimatedValue = 'Estimated Value '; static const String duration = 'Duration'; + static const String tenderSubmittedSuccessfully = + 'tender submitted successfully!'; + static const String tenderRejectedSuccessfully = + 'tender rejected successfully!'; + static const String tenderUnrejectedSuccessfully = + 'tender unrejected successfully!'; + static const String tenderUnsubmittedSuccessfully = + 'tender unsubmitted successfully!'; + static const String tenderNoData = 'No tender details available'; } diff --git a/lib/core/utils/date_utils.dart b/lib/core/utils/date_utils.dart index aabdfa9..cc98580 100644 --- a/lib/core/utils/date_utils.dart +++ b/lib/core/utils/date_utils.dart @@ -1,15 +1,15 @@ import 'package:intl/intl.dart'; -class DateUtils { +/// Extension on int to provide date formatting utilities for Unix timestamps +extension UnixTimestampExtension on int? { /// Converts Unix timestamp (seconds since epoch) to a formatted date string - static String unixToDate(int? unixTimestamp, {String format = 'yyyy-MM-dd'}) { - if (unixTimestamp == null) { + String toDate({String format = 'yyyy-MM-dd'}) { + if (this == null) { return ''; } // Convert seconds to milliseconds if needed - int timestampInMs = - unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; + int timestampInMs = this! > 1000000000000 ? this! : this! * 1000; final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( timestampInMs, @@ -18,13 +18,12 @@ class DateUtils { } /// Converts Unix timestamp to a readable date string - static String unixToReadableDate(int? unixTimestamp) { - if (unixTimestamp == null) { + String toReadableDate() { + if (this == null) { return ''; } - int timestampInMs = - unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; + int timestampInMs = this! > 1000000000000 ? this! : this! * 1000; final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( timestampInMs, @@ -44,13 +43,12 @@ class DateUtils { } /// Converts Unix timestamp to a short date format - static String unixToShortDate(int? unixTimestamp) { - if (unixTimestamp == null) { + String toShortDate() { + if (this == null) { return ''; } - int timestampInMs = - unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; + int timestampInMs = this! > 1000000000000 ? this! : this! * 1000; final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( timestampInMs, @@ -59,13 +57,12 @@ class DateUtils { } /// Converts Unix timestamp to a full date and time format - static String unixToDateTime(int? unixTimestamp) { - if (unixTimestamp == null) { + String toDateTime() { + if (this == null) { return ''; } - int timestampInMs = - unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; + int timestampInMs = this! > 1000000000000 ? this! : this! * 1000; final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( timestampInMs, @@ -74,13 +71,12 @@ class DateUtils { } /// Checks if a Unix timestamp is in the past - static bool isPast(int? unixTimestamp) { - if (unixTimestamp == null) { + bool get isPast { + if (this == null) { return false; } - int timestampInMs = - unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; + int timestampInMs = this! > 1000000000000 ? this! : this! * 1000; final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( timestampInMs, @@ -89,13 +85,12 @@ class DateUtils { } /// Gets the number of days remaining until a Unix timestamp - static int daysRemaining(int? unixTimestamp) { - if (unixTimestamp == null) { + int get daysRemaining { + if (this == null) { return 0; } - int timestampInMs = - unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; + int timestampInMs = this! > 1000000000000 ? this! : this! * 1000; final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( timestampInMs, @@ -106,3 +101,69 @@ class DateUtils { return difference.inDays; } } + +/// Extension on DateTime to provide additional formatting utilities +extension DateTimeExtension on DateTime { + /// Formats the DateTime to a readable string + String toReadableString() { + final DateTime now = DateTime.now(); + final Duration difference = now.difference(this); + + if (difference.inDays == 0) { + return 'Today'; + } else if (difference.inDays == 1) { + return 'Yesterday'; + } else if (difference.inDays < 7) { + return '${difference.inDays} days ago'; + } else { + return DateFormat('MMM dd, yyyy').format(this); + } + } + + /// Formats the DateTime to a short date string + String toShortDate() { + return DateFormat('MMM dd').format(this); + } + + /// Formats the DateTime to a full date and time string + String toDateTimeString() { + return DateFormat('MMM dd, yyyy HH:mm').format(this); + } + + /// Checks if the DateTime is in the past + bool get isPast { + return isBefore(DateTime.now()); + } + + /// Gets the number of days remaining until this DateTime + int get daysRemaining { + final DateTime now = DateTime.now(); + final Duration difference = this.difference(now); + return difference.inDays; + } +} + +/// Utility class for date operations +class DateUtils { + /// Converts Unix timestamp to a formatted date string + + static String unixToDate(int? timestamp) { + if (timestamp == null) { + return ''; + } + + // Convert seconds to milliseconds if needed + int timestampInMs = + timestamp > 1000000000000 ? timestamp : timestamp * 1000; + + final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( + timestampInMs, + ); + return DateFormat('MMM dd, yyyy').format(dateTime); + } +} + +/// Global function for backward compatibility +String unixToDate(int? timestamp) { + return DateUtils.unixToDate(timestamp); +} diff --git a/lib/view_models/tender_detail_view_model.dart b/lib/view_models/tender_detail_view_model.dart index d923401..511afb5 100644 --- a/lib/view_models/tender_detail_view_model.dart +++ b/lib/view_models/tender_detail_view_model.dart @@ -121,6 +121,7 @@ class TenderDetailViewModel with ChangeNotifier { final result = await _tendersRepository.getTenderApprovals( tenderId: tenderId, ); + approvalStatus = false; switch (result) { case Ok(): _tenderApprovalData = result.value.data; diff --git a/lib/views/detail/pages/detail_desktop_page.dart b/lib/views/detail/pages/detail_desktop_page.dart index 7401d44..876b527 100644 --- a/lib/views/detail/pages/detail_desktop_page.dart +++ b/lib/views/detail/pages/detail_desktop_page.dart @@ -8,6 +8,7 @@ import 'package:tm_app/views/detail/widgets/tender_detail_action.dart'; 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/strings.dart'; import '../../../view_models/home_view_model.dart'; import '../../../view_models/tenders_view_model.dart'; import '../../shared/desktop_navigation_widget.dart'; @@ -34,39 +35,39 @@ class _TenderDetailDesktopPageState extends State { if (viewModel.status.isNotEmpty && viewModel.status == 'submitted' && viewModel.approvalStatus == true) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('tender submitted successfully!'))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppStrings.tenderSubmittedSuccessfully)), + ); } if (viewModel.status.isNotEmpty && viewModel.status == 'unsubmitted' && viewModel.approvalStatus == true) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('tender submitted removed sucessfully!')), + SnackBar(content: Text(AppStrings.tenderUnsubmittedSuccessfully)), ); } if (viewModel.status.isNotEmpty && viewModel.status == 'rejected' && viewModel.approvalStatus == true) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('tender rejected sucessfully!'))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppStrings.tenderRejectedSuccessfully)), + ); } if (viewModel.status.isNotEmpty && viewModel.status == 'unrejected' && viewModel.approvalStatus == true) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('tender reject removed sucessfully!')), + SnackBar(content: Text(AppStrings.tenderUnrejectedSuccessfully)), + ); + } + if (viewModel.approvalStatus != false && viewModel.errorMessage != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: AppColors.errorColor, + content: Text(viewModel.errorMessage!), + ), ); } - // if (viewModel.errorMessage != null) { - // ScaffoldMessenger.of(context).showSnackBar( - // SnackBar( - // backgroundColor: AppColors.errorColor, - // content: Text(viewModel.errorMessage!), - // ), - // ); - // } } @override @@ -99,7 +100,7 @@ class _TenderDetailDesktopPageState extends State { final detail = tenderViewModel.tenderDetail; if (detail == null) { - return const Center(child: Text('No tender details available')); + return const Center(child: Text(AppStrings.tenderNoData)); } return Column( diff --git a/lib/views/detail/pages/detail_mobile_page.dart b/lib/views/detail/pages/detail_mobile_page.dart index 09986ce..7d67fc3 100644 --- a/lib/views/detail/pages/detail_mobile_page.dart +++ b/lib/views/detail/pages/detail_mobile_page.dart @@ -30,39 +30,39 @@ class _TenderDetailMobilePageState extends State { if (viewModel.status.isNotEmpty && viewModel.status == 'submitted' && viewModel.approvalStatus == true) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('tender submitted successfully!'))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppStrings.tenderSubmittedSuccessfully)), + ); } if (viewModel.status.isNotEmpty && viewModel.status == 'unsubmitted' && viewModel.approvalStatus == true) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('tender submitted removed sucessfully!')), + SnackBar(content: Text(AppStrings.tenderUnsubmittedSuccessfully)), ); } if (viewModel.status.isNotEmpty && viewModel.status == 'rejected' && viewModel.approvalStatus == true) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('tender rejected sucessfully!'))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppStrings.tenderRejectedSuccessfully)), + ); } if (viewModel.status.isNotEmpty && viewModel.status == 'unrejected' && viewModel.approvalStatus == true) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('tender reject removed sucessfully!')), + SnackBar(content: Text(AppStrings.tenderUnrejectedSuccessfully)), + ); + } + if (viewModel.approvalStatus != false && viewModel.errorMessage != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: AppColors.errorColor, + content: Text(viewModel.errorMessage!), + ), ); } - // if (viewModel.errorMessage != null) { - // ScaffoldMessenger.of(context).showSnackBar( - // SnackBar( - // backgroundColor: AppColors.errorColor, - // content: Text(viewModel.errorMessage!), - // ), - // ); - // } } @override @@ -90,7 +90,7 @@ class _TenderDetailMobilePageState extends State { final detail = tenderViewModel.tenderDetail; if (detail == null) { - return const Center(child: Text('No tender details available')); + return const Center(child: Text(AppStrings.tenderNoData)); } return SingleChildScrollView( diff --git a/lib/views/detail/pages/detail_tablet_page.dart b/lib/views/detail/pages/detail_tablet_page.dart index fc5ed57..dfac552 100644 --- a/lib/views/detail/pages/detail_tablet_page.dart +++ b/lib/views/detail/pages/detail_tablet_page.dart @@ -12,6 +12,8 @@ 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'; +import '../../../core/constants/strings.dart'; + class TenderDetailTabletPage extends StatefulWidget { final String tenderId; const TenderDetailTabletPage({required this.tenderId, super.key}); @@ -33,39 +35,39 @@ class _TenderDetailTabletPageState extends State { if (viewModel.status.isNotEmpty && viewModel.status == 'submitted' && viewModel.approvalStatus == true) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('tender submitted successfully!'))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppStrings.tenderSubmittedSuccessfully)), + ); } if (viewModel.status.isNotEmpty && viewModel.status == 'unsubmitted' && viewModel.approvalStatus == true) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('tender submitted removed sucessfully!')), + SnackBar(content: Text(AppStrings.tenderUnsubmittedSuccessfully)), ); } if (viewModel.status.isNotEmpty && viewModel.status == 'rejected' && viewModel.approvalStatus == true) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('tender rejected sucessfully!'))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppStrings.tenderRejectedSuccessfully)), + ); } if (viewModel.status.isNotEmpty && viewModel.status == 'unrejected' && viewModel.approvalStatus == true) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('tender reject removed sucessfully!')), + SnackBar(content: Text(AppStrings.tenderUnrejectedSuccessfully)), + ); + } + if (viewModel.approvalStatus != false && viewModel.errorMessage != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: AppColors.errorColor, + content: Text(viewModel.errorMessage!), + ), ); } - // if (viewModel.errorMessage != null) { - // ScaffoldMessenger.of(context).showSnackBar( - // SnackBar( - // backgroundColor: AppColors.errorColor, - // content: Text(viewModel.errorMessage!), - // ), - // ); - // } } @override @@ -138,7 +140,7 @@ class _TenderDetailTabletPageState extends State { final detail = tenderViewModel.tenderDetail; if (detail == null) { - return const Center(child: Text('No tender details available')); + return const Center(child: Text(AppStrings.tenderNoData)); } return SingleChildScrollView( child: Center( diff --git a/lib/views/detail/widgets/deadline_item.dart b/lib/views/detail/widgets/deadline_item.dart index e77bbdb..e894948 100644 --- a/lib/views/detail/widgets/deadline_item.dart +++ b/lib/views/detail/widgets/deadline_item.dart @@ -1,21 +1,18 @@ 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/size_config.dart'; +import '../../../core/constants/strings.dart'; +import '../../../data/services/model/tender_data/tender_data.dart'; + class DeadlineItem extends StatelessWidget { - final String title; - final String approvalText; - final String approvalDate; - final String submissionText; - final String submissionDate; + final TenderData detail; final bool isScreenBig; const DeadlineItem({ - required this.title, - required this.approvalText, - required this.approvalDate, - required this.submissionText, - required this.submissionDate, + required this.detail, + required this.isScreenBig, super.key, }); @@ -31,7 +28,7 @@ class DeadlineItem extends StatelessWidget { SizedBox( height: 45.0.h(), child: Text( - title, + detail.title ?? '', style: TextStyle( color: AppColors.grey80, fontWeight: FontWeight.w600, @@ -47,7 +44,7 @@ class DeadlineItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - approvalText, + AppStrings.tenderApprovalText, style: TextStyle( color: AppColors.grey70, fontWeight: FontWeight.w400, @@ -56,7 +53,7 @@ class DeadlineItem extends StatelessWidget { ), SizedBox(height: 4.0.h()), Text( - approvalDate, + unixToDate(detail.submissionDeadline), style: TextStyle( color: AppColors.grey80, fontWeight: FontWeight.w400, @@ -76,7 +73,7 @@ class DeadlineItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - submissionText, + AppStrings.tenderSubmissionText, style: TextStyle( color: AppColors.grey70, fontWeight: FontWeight.w400, @@ -85,7 +82,7 @@ class DeadlineItem extends StatelessWidget { ), SizedBox(height: 4.0.h()), Text( - submissionDate, + unixToDate(detail.applicationDeadline), style: TextStyle( color: AppColors.grey80, fontWeight: FontWeight.w400, @@ -99,7 +96,7 @@ class DeadlineItem extends StatelessWidget { ), ), SizedBox(height: 8.0.h()), - Divider(color: AppColors.grey20,), + Divider(color: AppColors.grey20), ], ), ) @@ -111,7 +108,7 @@ class DeadlineItem extends StatelessWidget { SizedBox( height: 45.0.h(), child: Text( - title, + detail.title ?? '', style: TextStyle( color: AppColors.grey80, fontWeight: FontWeight.w600, @@ -123,7 +120,7 @@ class DeadlineItem extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - approvalText, + AppStrings.tenderApprovalText, style: TextStyle( color: AppColors.grey70, fontWeight: FontWeight.w400, @@ -131,7 +128,7 @@ class DeadlineItem extends StatelessWidget { ), ), Text( - approvalDate, + unixToDate(detail.submissionDeadline), style: TextStyle( color: AppColors.grey70, fontWeight: FontWeight.w400, @@ -145,7 +142,7 @@ class DeadlineItem extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - submissionText, + AppStrings.tenderSubmissionText, style: TextStyle( color: AppColors.grey70, fontWeight: FontWeight.w400, @@ -153,7 +150,7 @@ class DeadlineItem extends StatelessWidget { ), ), Text( - submissionDate, + unixToDate(detail.applicationDeadline), style: TextStyle( color: AppColors.grey70, fontWeight: FontWeight.w400, @@ -163,7 +160,7 @@ class DeadlineItem extends StatelessWidget { ], ), SizedBox(height: 8.0.h()), - Divider(color: AppColors.grey20,), + Divider(color: AppColors.grey20), ], ), ); diff --git a/lib/views/detail/widgets/tender_detail_header.dart b/lib/views/detail/widgets/tender_detail_header.dart index b38abab..4a9b24c 100644 --- a/lib/views/detail/widgets/tender_detail_header.dart +++ b/lib/views/detail/widgets/tender_detail_header.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; import 'package:tm_app/core/theme/colors.dart'; +import 'package:tm_app/core/utils/date_utils.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/widgets/status_tag.dart'; @@ -73,16 +73,3 @@ class TenderDetailHeader extends StatelessWidget { ), ); } - -String unixToDate(int? unixTimestamp, {String format = 'yyyy-MM-dd'}) { - if (unixTimestamp == null) { - return ''; - } - - // Convert seconds to milliseconds if needed - int timestampInMs = - unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; - - final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestampInMs); - return DateFormat(format).format(dateTime); -} diff --git a/lib/views/detail/widgets/tender_detail_info_section.dart b/lib/views/detail/widgets/tender_detail_info_section.dart index 7dee5e4..0486e93 100644 --- a/lib/views/detail/widgets/tender_detail_info_section.dart +++ b/lib/views/detail/widgets/tender_detail_info_section.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; import 'package:tm_app/core/constants/strings.dart'; import 'package:tm_app/data/services/model/tender_data/tender_data.dart'; import 'package:tm_app/views/detail/widgets/deadline_item.dart'; @@ -21,14 +20,7 @@ class TenderDetailInfoSection extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ InfoItem(title: AppStrings.tenderIdLabel, value: detail.tenderId ?? ''), - DeadlineItem( - title: AppStrings.tenderDeadlineLabel, - approvalText: AppStrings.tenderApprovalText, - approvalDate: unixToDate(detail.submissionDeadline), - submissionText: AppStrings.tenderSubmissionText, - submissionDate: unixToDate(detail.applicationDeadline), - isScreenBig: isScreenBig, - ), + DeadlineItem(detail: detail, isScreenBig: isScreenBig), InfoItem( title: AppStrings.tenderClientLabel, value: detail.buyerOrganization!.name ?? '', @@ -49,16 +41,3 @@ class TenderDetailInfoSection extends StatelessWidget { ); } } - -String unixToDate(int? unixTimestamp, {String format = 'yyyy-MM-dd'}) { - if (unixTimestamp == null) { - return ''; - } - - // Convert seconds to milliseconds if needed - int timestampInMs = - unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; - - final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestampInMs); - return DateFormat(format).format(dateTime); -}