Merge pull request 'refactor' (#74) from refactor into main

Reviewed-on: https://repo.ravanertebat.com/TM/tm_app/pulls/74
This commit is contained in:
a.ghabeli
2025-08-27 14:07:32 +03:30
46 changed files with 515 additions and 623 deletions
+18 -14
View File
@@ -1,5 +1,8 @@
class AppStrings { class AppStrings {
AppStrings._(); AppStrings._();
static const String noData = 'No data';
// App Titles // App Titles
static const String appName = 'My Awesome App'; static const String appName = 'My Awesome App';
static const String loginTitle = 'Welcome Back'; static const String loginTitle = 'Welcome Back';
@@ -50,6 +53,12 @@ class AppStrings {
static const String profile = 'Profile'; static const String profile = 'Profile';
static const String settings = 'Settings'; static const String settings = 'Settings';
static const String appTheme = 'App Theme'; static const String appTheme = 'App Theme';
static const String companyName = 'Company Name';
static const String nationalId = 'National ID';
static const String registrationNumber = 'Registration No.';
static const String businessType = 'Business Type';
static const String founded = 'Founded';
static const String logout = 'Logout';
//your tenders //your tenders
static const String yourTendersTitle = 'Your tenders'; static const String yourTendersTitle = 'Your tenders';
@@ -65,7 +74,6 @@ class AppStrings {
static const String tenderDeliveryLocationsLabel = 'Delivery Locations'; static const String tenderDeliveryLocationsLabel = 'Delivery Locations';
static const String tenderReferenceNumberLabel = 'Reference Number'; static const String tenderReferenceNumberLabel = 'Reference Number';
static const String tenderLocationLabel = 'Locations'; static const String tenderLocationLabel = 'Locations';
static const String tenderLocationCountry = 'UK';
static const String tenderPdfDocument = 'PDF , Document'; static const String tenderPdfDocument = 'PDF , Document';
static const String tenderMatchProfile = 'Match with your profile'; static const String tenderMatchProfile = 'Match with your profile';
static const String tenderIncompleteResume = 'Incomplete Resume Information'; static const String tenderIncompleteResume = 'Incomplete Resume Information';
@@ -73,21 +81,17 @@ class AppStrings {
'No experience in e-platform development'; 'No experience in e-platform development';
static const String tenderSubmitButton = 'Submit'; static const String tenderSubmitButton = 'Submit';
static const String tenderRejectButton = 'Reject'; static const String tenderRejectButton = 'Reject';
static const String tenderDateExample = '2025-05-21';
static const String tenderTitleExample =
'Operation, support and further development of open e-platform Operation, support and further development of open e-platform';
static const String tenderIdExample = 'JNDFKMDV-100-JF';
static const String tenderApprovalDateExample = '2025-05-21';
static const String tenderSubmissionDateExample = '2025-05-21';
static const String tenderClientExample = static const String tenderClientExample =
'Procurement Notice Procurement Notice'; 'Procurement Notice Procurement Notice';
static const String tenderDeliveryLocationExample = 'Norrbotten County';
static const String tenderReferenceNumberExample = 'KLF 2025/120';
static const String tenderDescriptionExample =
'Luleå Municipality is procuring on behalf of Norrbottens e-board (all municipalities in Norrbotten)...';
static const String tenderMatchPercentageExample = '75%';
static const String locationDescription =
'Luleå Municipality is procuring on behalf of Norrbottens e-board (all municipalities in Norrbotten).

The procurement includes operation, support, and further development of the existing open e-platform.

No additional information, amendments, or answers to questions will be provided after June 16.o additional information, amendments, or answers to questions will be provided after June 16.o additional information, amendments, or answers to questions will be provided after June 16.website: www.tenders.com Lorem ipsum is amet Luleå Municipality is procuring on behalf of Norrbottens e-board (all municipalities in Norrbotten).

The procurement includes operation, support, and further development of the existing open e-platform.

No additional information, amendments, or answers to questions will be provided after June 16. o additional information, amendments, or answers to questions will be provided after June 16. o additional information, amendments, or answers to questions will be provided after June 16.website: www.tenders.com';
static const String estimatedValue = 'Estimated Value '; static const String estimatedValue = 'Estimated Value ';
static const String duration = 'Duration'; 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';
} }
+3 -1
View File
@@ -32,7 +32,9 @@ class NetworkManager {
} }
void _addInterceptors() { void _addInterceptors() {
if (_interceptorsAdded) return; if (_interceptorsAdded) {
return;
}
final requestInterceptor = InterceptorsWrapper( final requestInterceptor = InterceptorsWrapper(
onRequest: (options, handler) { onRequest: (options, handler) {
@@ -13,6 +13,9 @@ class AppShellScreen extends StatelessWidget {
final StatefulNavigationShell navigationShell; final StatefulNavigationShell navigationShell;
void _gotoBranch(int index, BuildContext context) { void _gotoBranch(int index, BuildContext context) {
if (index == navigationShell.currentIndex) {
return;
}
navigationShell.goBranch( navigationShell.goBranch(
index, index,
initialLocation: index == navigationShell.currentIndex, initialLocation: index == navigationShell.currentIndex,
+1
View File
@@ -153,6 +153,7 @@ class AppColors {
// static const Color red0 = Color(0xFF312F2F); // static const Color red0 = Color(0xFF312F2F);
// static const Color red10 = Color(0xFF524545); // static const Color red10 = Color(0xFF524545);
static const Color red = Color(0xFFFF3B30); static const Color red = Color(0xFFFF3B30);
static const Color orange = Color(0xFFE5821E);
// Other colors // Other colors
static const Color lightBlue = Color(0xFFe8ecfc); static const Color lightBlue = Color(0xFFe8ecfc);
+98 -25
View File
@@ -1,13 +1,15 @@
import 'package:intl/intl.dart'; 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 /// Converts Unix timestamp (seconds since epoch) to a formatted date string
static String unixToDate(int? unixTimestamp, {String format = 'yyyy-MM-dd'}) { String toDate({String format = 'yyyy-MM-dd'}) {
if (unixTimestamp == null) return ''; if (this == null) {
return '';
}
// Convert seconds to milliseconds if needed // Convert seconds to milliseconds if needed
int timestampInMs = int timestampInMs = this! > 1000000000000 ? this! : this! * 1000;
unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000;
final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(
timestampInMs, timestampInMs,
@@ -16,11 +18,12 @@ class DateUtils {
} }
/// Converts Unix timestamp to a readable date string /// Converts Unix timestamp to a readable date string
static String unixToReadableDate(int? unixTimestamp) { String toReadableDate() {
if (unixTimestamp == null) return ''; if (this == null) {
return '';
}
int timestampInMs = int timestampInMs = this! > 1000000000000 ? this! : this! * 1000;
unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000;
final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(
timestampInMs, timestampInMs,
@@ -40,11 +43,12 @@ class DateUtils {
} }
/// Converts Unix timestamp to a short date format /// Converts Unix timestamp to a short date format
static String unixToShortDate(int? unixTimestamp) { String toShortDate() {
if (unixTimestamp == null) return ''; if (this == null) {
return '';
}
int timestampInMs = int timestampInMs = this! > 1000000000000 ? this! : this! * 1000;
unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000;
final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(
timestampInMs, timestampInMs,
@@ -53,11 +57,12 @@ class DateUtils {
} }
/// Converts Unix timestamp to a full date and time format /// Converts Unix timestamp to a full date and time format
static String unixToDateTime(int? unixTimestamp) { String toDateTime() {
if (unixTimestamp == null) return ''; if (this == null) {
return '';
}
int timestampInMs = int timestampInMs = this! > 1000000000000 ? this! : this! * 1000;
unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000;
final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(
timestampInMs, timestampInMs,
@@ -66,11 +71,12 @@ class DateUtils {
} }
/// Checks if a Unix timestamp is in the past /// Checks if a Unix timestamp is in the past
static bool isPast(int? unixTimestamp) { bool get isPast {
if (unixTimestamp == null) return false; if (this == null) {
return false;
}
int timestampInMs = int timestampInMs = this! > 1000000000000 ? this! : this! * 1000;
unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000;
final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(
timestampInMs, timestampInMs,
@@ -79,11 +85,12 @@ class DateUtils {
} }
/// Gets the number of days remaining until a Unix timestamp /// Gets the number of days remaining until a Unix timestamp
static int daysRemaining(int? unixTimestamp) { int get daysRemaining {
if (unixTimestamp == null) return 0; if (this == null) {
return 0;
}
int timestampInMs = int timestampInMs = this! > 1000000000000 ? this! : this! * 1000;
unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000;
final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(
timestampInMs, timestampInMs,
@@ -94,3 +101,69 @@ class DateUtils {
return difference.inDays; 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);
}
+1 -1
View File
@@ -1,8 +1,8 @@
import 'package:tm_app/data/services/model/login_response/login_response_model.dart'; import 'package:tm_app/data/services/model/login_response/login_response_model.dart';
import 'package:tm_app/data/services/model/logout_response/logout_response.dart';
import '../../core/utils/result.dart'; import '../../core/utils/result.dart';
import '../services/auth_service.dart'; import '../services/auth_service.dart';
import '../services/model/logout_response/logout_response.dart';
class AuthRepository { class AuthRepository {
final AuthService _authService; final AuthService _authService;
@@ -1,7 +1,5 @@
import 'package:tm_app/core/utils/result.dart'; import 'package:tm_app/core/utils/result.dart';
import 'package:tm_app/data/services/home_service.dart'; import 'package:tm_app/data/services/home_service.dart';
import 'package:tm_app/data/services/model/home/home_request/home_request_model.dart';
import 'package:tm_app/data/services/model/home/home_response/home_response_model.dart';
import 'package:tm_app/data/services/model/recommended_tenders_response/recommended_tenders_response.dart'; import 'package:tm_app/data/services/model/recommended_tenders_response/recommended_tenders_response.dart';
import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart'; import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart';
import 'package:tm_app/data/services/model/tender_approvals_state_response/tender_approvals_state_response.dart'; import 'package:tm_app/data/services/model/tender_approvals_state_response/tender_approvals_state_response.dart';
@@ -13,12 +11,6 @@ class HomeRepository {
: _homeService = homeService; : _homeService = homeService;
final HomeService _homeService; final HomeService _homeService;
Future<Result<HomeResponseModel>> getHome({
HomeRequestModel? homeRequestModel,
}) async {
return _homeService.getHome(homeRequestModel: homeRequestModel);
}
Future<Result<RecommendedTendersResponse>> getHomeRecommendedTenders({ Future<Result<RecommendedTendersResponse>> getHomeRecommendedTenders({
required int limit, required int limit,
required int offset, required int offset,
@@ -24,11 +24,11 @@ class TendersRepository {
return _tendersService.getTenderFeedback(tenderId: tenderId); return _tendersService.getTenderFeedback(tenderId: tenderId);
} }
Future<Result<FeedbackResponse>> feedback({ Future<Result<FeedbackResponse>> toggleFeedback({
required String tenderId, required String tenderId,
required String feedbackType, required String feedbackType,
}) async { }) async {
return _tendersService.feedback( return _tendersService.toggleFeedback(
tenderId: tenderId, tenderId: tenderId,
feedbackType: feedbackType, feedbackType: feedbackType,
); );
+4
View File
@@ -0,0 +1,4 @@
class AuthApi {
static const String login = '/api/v1/profile/login';
static const String logout = '/api/v1/profile/logout';
}
+13
View File
@@ -0,0 +1,13 @@
class HomeApi {
static const String recommendedTenders = '/api/v1/tenders/recommend';
static String getRecommendedTenders({
required int limit,
required int offset,
}) {
return '$recommendedTenders?limit=$limit&offset=$offset';
}
static const String tenderApprovals = '/api/v1/tender-approvals';
static const String tenderApprovalStats = '/api/v1/tender-approvals/stats';
static const String statsCompany = '/api/v1/feedback/stats/company';
}
+4
View File
@@ -0,0 +1,4 @@
class ProfileApi {
static const String profile = '/api/v1/profile';
static const String companyProfile = '/api/v1/companies';
}
@@ -0,0 +1,6 @@
class TenderDetailApi {
static const String tenderDetails = '/api/v1/tenders/details';
static String getTenderDetails({required String tenderId}) {
return '$tenderDetails/$tenderId';
}
}
+20
View File
@@ -0,0 +1,20 @@
class TendersApi {
static const String tenders = '/api/v1/tenders';
static String getTenders({required int limit, required int offset}) {
return '$tenders?limit=$limit&offset=$offset';
}
static const String feedbackTender = '/api/v1/feedback/tender';
static String getFeedbackForTender({required String tenderId}) {
return '$feedbackTender/$tenderId';
}
static const String feedback = '/api/v1/feedback';
static const String tenderApprovals = '/api/v1/tender-approvals';
static const String tenderApprovalsTender = '/api/v1/tender-approvals/tender';
static String getTenderApprovalById({required String tenderId}) {
return '$tenderApprovalsTender/$tenderId';
}
}
@@ -0,0 +1,4 @@
class YourTendersApi {
static const String tenderApprovalsStatus =
'/api/v1/tender-approvals?status=submitted';
}
+5 -2
View File
@@ -3,6 +3,8 @@
import 'dart:convert'; import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:tm_app/core/utils/logger.dart';
import 'package:tm_app/data/services/api/auth_api.dart';
import 'package:tm_app/data/services/model/login_response/login_response_model.dart'; import 'package:tm_app/data/services/model/login_response/login_response_model.dart';
import 'package:tm_app/data/services/model/logout_response/logout_response.dart'; import 'package:tm_app/data/services/model/logout_response/logout_response.dart';
@@ -22,7 +24,7 @@ class AuthService {
var data = jsonEncode({"password": password, "username": username}); var data = jsonEncode({"password": password, "username": username});
final result = await _networkManager.makeRequest( final result = await _networkManager.makeRequest(
'/api/v1/profile/login', AuthApi.login,
method: 'POST', method: 'POST',
(json) => LoginResponseModel.fromJson(json), (json) => LoginResponseModel.fromJson(json),
data: data, data: data,
@@ -41,7 +43,7 @@ class AuthService {
Future<Result<LogoutResponse>> logout() async { Future<Result<LogoutResponse>> logout() async {
final result = await _networkManager.makeRequest( final result = await _networkManager.makeRequest(
'/api/v1/profile/logout', AuthApi.logout,
method: 'DELETE', method: 'DELETE',
(json) => LogoutResponse.fromJson(json), (json) => LogoutResponse.fromJson(json),
); );
@@ -52,5 +54,6 @@ class AuthService {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
await prefs.remove('bearer'); await prefs.remove('bearer');
await prefs.remove('refresh_token'); await prefs.remove('refresh_token');
appLogger.info('tokens cleared!');
} }
} }
+5 -35
View File
@@ -1,12 +1,6 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:tm_app/core/utils/logger.dart';
import 'package:tm_app/core/utils/result.dart'; import 'package:tm_app/core/utils/result.dart';
import 'package:tm_app/data/services/mock_data/home_mock_data.dart'; import 'package:tm_app/data/services/api/home_api.dart';
import 'package:tm_app/data/services/model/feedback_stats_response/feedback_stat_response.dart'; import 'package:tm_app/data/services/model/feedback_stats_response/feedback_stat_response.dart';
import 'package:tm_app/data/services/model/home/home_request/home_request_model.dart';
import 'package:tm_app/data/services/model/home/home_response/home_response_model.dart';
import 'package:tm_app/data/services/model/recommended_tenders_response/recommended_tenders_response.dart'; import 'package:tm_app/data/services/model/recommended_tenders_response/recommended_tenders_response.dart';
import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart'; import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart';
import 'package:tm_app/data/services/model/tender_approvals_state_response/tender_approvals_state_response.dart'; import 'package:tm_app/data/services/model/tender_approvals_state_response/tender_approvals_state_response.dart';
@@ -18,36 +12,12 @@ class HomeService {
: _networkManager = networkManager; : _networkManager = networkManager;
final NetworkManager _networkManager; final NetworkManager _networkManager;
Future<Result<HomeResponseModel>> getHome({
HomeRequestModel? homeRequestModel,
}) async {
try {
// final result = await _networkManager.makeRequest(
// '/api/v1/home',
// (json) => HomeResponseModel.fromJson(json),
// method: 'GET',
// );
await Future.delayed(Duration(seconds: 2));
appLogger.info('get home success');
// TODO: will change when api is ready
return Result.ok(
HomeResponseModel.fromJson(jsonDecode(homeMockData)['data']),
);
} on DioException catch (e) {
appLogger.error('get home failed: $e');
return Result.error(e);
} on Exception catch (e, stackTrace) {
appLogger.error('get home failed: $e', stackTrace: stackTrace);
return Result.error(Exception(e));
}
}
Future<Result<RecommendedTendersResponse>> getRecommendedTenders({ Future<Result<RecommendedTendersResponse>> getRecommendedTenders({
required int limit, required int limit,
required int offset, required int offset,
}) async { }) async {
final result = await _networkManager.makeRequest( final result = await _networkManager.makeRequest(
'/api/v1/tenders/recommend?limit=$limit&offset=$offset', HomeApi.getRecommendedTenders(limit: limit, offset: offset),
method: 'GET', method: 'GET',
(json) => RecommendedTendersResponse.fromJson(json), (json) => RecommendedTendersResponse.fromJson(json),
); );
@@ -56,7 +26,7 @@ class HomeService {
Future<Result<TenderApprovalsResponse>> getYourTenders() async { Future<Result<TenderApprovalsResponse>> getYourTenders() async {
final result = await _networkManager.makeRequest( final result = await _networkManager.makeRequest(
'/api/v1/tender-approvals', HomeApi.tenderApprovals,
(json) => TenderApprovalsResponse.fromJson(json), (json) => TenderApprovalsResponse.fromJson(json),
method: 'GET', method: 'GET',
); );
@@ -67,7 +37,7 @@ class HomeService {
Future<Result<TenderApprovalsStateResponse>> Future<Result<TenderApprovalsStateResponse>>
getTendersApprovalsState() async { getTendersApprovalsState() async {
final result = await _networkManager.makeRequest( final result = await _networkManager.makeRequest(
'/api/v1/tender-approvals/stats', HomeApi.tenderApprovalStats,
(json) => TenderApprovalsStateResponse.fromJson(json), (json) => TenderApprovalsStateResponse.fromJson(json),
method: 'GET', method: 'GET',
); );
@@ -77,7 +47,7 @@ class HomeService {
Future<Result<FeedbackStatResponse>> getFeedbackStats() async { Future<Result<FeedbackStatResponse>> getFeedbackStats() async {
final result = await _networkManager.makeRequest( final result = await _networkManager.makeRequest(
'/api/v1/feedback/stats/company', HomeApi.statsCompany,
(json) => FeedbackStatResponse.fromJson(json), (json) => FeedbackStatResponse.fromJson(json),
method: 'GET', method: 'GET',
); );
+3 -2
View File
@@ -1,3 +1,4 @@
import 'package:tm_app/data/services/api/profile_api.dart';
import 'package:tm_app/data/services/model/company_profile_response/company_profile_response.dart'; import 'package:tm_app/data/services/model/company_profile_response/company_profile_response.dart';
import 'package:tm_app/data/services/model/profile_response/profile_response.dart'; import 'package:tm_app/data/services/model/profile_response/profile_response.dart';
@@ -12,7 +13,7 @@ class ProfileService {
Future<Result<ProfileResponse>> getProfile() async { Future<Result<ProfileResponse>> getProfile() async {
final result = await _networkManager.makeRequest( final result = await _networkManager.makeRequest(
'/api/v1/profile', ProfileApi.profile,
method: 'GET', method: 'GET',
(json) => ProfileResponse.fromJson(json), (json) => ProfileResponse.fromJson(json),
); );
@@ -21,7 +22,7 @@ class ProfileService {
Future<Result<CompanyProfileResponse>> getCompanyProfile() async { Future<Result<CompanyProfileResponse>> getCompanyProfile() async {
final result = await _networkManager.makeRequest( final result = await _networkManager.makeRequest(
'/api/v1/companies', ProfileApi.companyProfile,
method: 'GET', method: 'GET',
(json) => CompanyProfileResponse.fromJson(json), (json) => CompanyProfileResponse.fromJson(json),
); );
+2 -1
View File
@@ -1,4 +1,5 @@
import 'package:tm_app/core/utils/result.dart'; import 'package:tm_app/core/utils/result.dart';
import 'package:tm_app/data/services/api/tender_detail_api.dart';
import 'package:tm_app/data/services/model/tender_detail_response/tender_detail_response_model.dart'; import 'package:tm_app/data/services/model/tender_detail_response/tender_detail_response_model.dart';
import '../../core/network/network_manager.dart'; import '../../core/network/network_manager.dart';
@@ -13,7 +14,7 @@ class TenderDetailService {
required String tenderId, required String tenderId,
}) async { }) async {
final result = await _networkManager.makeRequest( final result = await _networkManager.makeRequest(
'/api/v1/tenders/details/$tenderId', TenderDetailApi.getTenderDetails(tenderId: tenderId),
method: 'GET', method: 'GET',
(json) => TenderDetailResponseModel.fromJson(json), (json) => TenderDetailResponseModel.fromJson(json),
); );
+7 -6
View File
@@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:tm_app/core/utils/result.dart'; import 'package:tm_app/core/utils/result.dart';
import 'package:tm_app/data/services/api/tenders_api.dart';
import 'package:tm_app/data/services/model/feedback_response/feedback_response.dart'; import 'package:tm_app/data/services/model/feedback_response/feedback_response.dart';
import 'package:tm_app/data/services/model/tender_approvals_by_id_response/tender_approvals_by_id_response.dart'; import 'package:tm_app/data/services/model/tender_approvals_by_id_response/tender_approvals_by_id_response.dart';
import 'package:tm_app/data/services/model/tender_approvals_toggle_response/tender_approvals_toggle_response.dart'; import 'package:tm_app/data/services/model/tender_approvals_toggle_response/tender_approvals_toggle_response.dart';
@@ -18,7 +19,7 @@ class TendersService {
required int offset, required int offset,
}) async { }) async {
final result = await _networkManager.makeRequest( final result = await _networkManager.makeRequest(
'/api/v1/tenders?limit=$limit&offset=$offset', TendersApi.getTenders(limit: limit, offset: offset),
method: 'GET', method: 'GET',
(json) => TendersResponse.fromJson(json), (json) => TendersResponse.fromJson(json),
); );
@@ -29,14 +30,14 @@ class TendersService {
required String tenderId, required String tenderId,
}) async { }) async {
final result = await _networkManager.makeRequest( final result = await _networkManager.makeRequest(
'/api/v1/feedback/tender/$tenderId', TendersApi.getFeedbackForTender(tenderId: tenderId),
method: 'GET', method: 'GET',
(json) => FeedbackResponse.fromJson(json), (json) => FeedbackResponse.fromJson(json),
); );
return result; return result;
} }
Future<Result<FeedbackResponse>> feedback({ Future<Result<FeedbackResponse>> toggleFeedback({
required String tenderId, required String tenderId,
required String feedbackType, required String feedbackType,
}) async { }) async {
@@ -45,7 +46,7 @@ class TendersService {
'tender_id': tenderId, 'tender_id': tenderId,
}); });
final result = await _networkManager.makeRequest( final result = await _networkManager.makeRequest(
'/api/v1/feedback', TendersApi.feedback,
method: 'POST', method: 'POST',
(json) => FeedbackResponse.fromJson(json), (json) => FeedbackResponse.fromJson(json),
data: data, data: data,
@@ -64,7 +65,7 @@ class TendersService {
'status': status, 'status': status,
}); });
final result = await _networkManager.makeRequest( final result = await _networkManager.makeRequest(
'/api/v1/tender-approvals', TendersApi.tenderApprovals,
method: 'POST', method: 'POST',
(json) => TenderApprovalsToggleResponse.fromJson(json), (json) => TenderApprovalsToggleResponse.fromJson(json),
data: data, data: data,
@@ -76,7 +77,7 @@ class TendersService {
required String tenderId, required String tenderId,
}) async { }) async {
final result = await _networkManager.makeRequest( final result = await _networkManager.makeRequest(
'/api/v1/tender-approvals/tender/$tenderId', TendersApi.getTenderApprovalById(tenderId: tenderId),
method: 'GET', method: 'GET',
(json) => TenderApprovalsByIdResponse.fromJson(json), (json) => TenderApprovalsByIdResponse.fromJson(json),
); );
+7 -19
View File
@@ -1,6 +1,5 @@
import 'package:dio/dio.dart';
import 'package:tm_app/core/utils/logger.dart';
import 'package:tm_app/core/utils/result.dart'; import 'package:tm_app/core/utils/result.dart';
import 'package:tm_app/data/services/api/your_tenders_api.dart';
import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart'; import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart';
import '../../../core/network/network_manager.dart'; import '../../../core/network/network_manager.dart';
@@ -12,23 +11,12 @@ class YourTendersService {
final NetworkManager _networkManager; final NetworkManager _networkManager;
Future<Result<TenderApprovalsResponse>> getApprovedTenders() async { Future<Result<TenderApprovalsResponse>> getApprovedTenders() async {
try { final result = await _networkManager.makeRequest(
final result = await _networkManager.makeRequest( YourTendersApi.tenderApprovalsStatus,
'/api/v1/tender-approvals?status=submitted', (json) => TenderApprovalsResponse.fromJson(json),
(json) => TenderApprovalsResponse.fromJson(json), method: 'GET',
method: 'GET', );
);
return result; return result;
} on DioException catch (e) {
appLogger.error('get submitted tenders failed: $e');
return Result.error(e);
} on Exception catch (e, stackTrace) {
appLogger.error(
'get submitted tenders failed: $e',
stackTrace: stackTrace,
);
return Result.error(Exception(e));
}
} }
} }
+26 -24
View File
@@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tm_app/core/utils/result.dart'; import 'package:tm_app/core/utils/result.dart';
import 'package:tm_app/data/services/model/feedback_stats/feedback_stats.dart'; import 'package:tm_app/data/services/model/feedback_stats/feedback_stats.dart';
import 'package:tm_app/data/services/model/home/home_request/home_request_model.dart';
import 'package:tm_app/data/services/model/recommended_tenders_response/recommended_tenders_response.dart'; import 'package:tm_app/data/services/model/recommended_tenders_response/recommended_tenders_response.dart';
import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart'; import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart';
import 'package:tm_app/data/services/model/tender_approvals_state_response/tender_approvals_state_response.dart'; import 'package:tm_app/data/services/model/tender_approvals_state_response/tender_approvals_state_response.dart';
@@ -51,30 +50,10 @@ class HomeViewModel with ChangeNotifier {
await getFeedbackStats(); await getFeedbackStats();
} }
Future<void> getHome({HomeRequestModel? homeRequestModel}) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
final result = await _homeRepository.getHome(
homeRequestModel: homeRequestModel,
);
switch (result) {
case Ok<HomeResponseModel>():
_homeResponse = result.value;
break;
case Error<HomeResponseModel>():
_errorMessage = result.error.toString();
break;
}
_isLoading = false;
notifyListeners();
}
Future<void> getHomeRecommandTenders({bool reset = false}) async { Future<void> getHomeRecommandTenders({bool reset = false}) async {
if (_isLoadingMore || (!_hasMore && !reset)) return; if (_isLoadingMore || (!_hasMore && !reset)) {
return;
}
if (reset) { if (reset) {
_currentPage = 0; _currentPage = 0;
@@ -186,4 +165,27 @@ class HomeViewModel with ChangeNotifier {
_isLoading = false; _isLoading = false;
notifyListeners(); notifyListeners();
} }
// Calculation methods for progress bars
double get partnershipCount {
return tenderApprovalsStateResponse?.data?.partnershipCount?.toDouble() ??
0.0;
}
double get selfApplyCount {
return tenderApprovalsStateResponse?.data?.selfApplyCount?.toDouble() ??
0.0;
}
double get totalCount {
return partnershipCount + selfApplyCount;
}
double get partnershipPercent {
return totalCount > 0 ? (partnershipCount / totalCount) * 100 : 0;
}
double get selfApplyPercent {
return totalCount > 0 ? (selfApplyCount / totalCount) * 100 : 0;
}
} }
@@ -121,6 +121,7 @@ class TenderDetailViewModel with ChangeNotifier {
final result = await _tendersRepository.getTenderApprovals( final result = await _tendersRepository.getTenderApprovals(
tenderId: tenderId, tenderId: tenderId,
); );
approvalStatus = false;
switch (result) { switch (result) {
case Ok<TenderApprovalsByIdResponse>(): case Ok<TenderApprovalsByIdResponse>():
_tenderApprovalData = result.value.data; _tenderApprovalData = result.value.data;
+5 -3
View File
@@ -122,7 +122,9 @@ class TendersViewModel with ChangeNotifier {
} }
Future<void> loadMoreTenders() async { Future<void> loadMoreTenders() async {
if (_isLoadingMore || !_hasMoreData) return; if (_isLoadingMore || !_hasMoreData) {
return;
}
_isLoadingMore = true; _isLoadingMore = true;
_errorMessage = null; _errorMessage = null;
@@ -246,14 +248,14 @@ class TendersViewModel with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
Future<void> feedback({ Future<void> toogleFeedback({
required String tenderId, required String tenderId,
required String feedbackType, required String feedbackType,
}) async { }) async {
_feedbackErrorMessage = null; _feedbackErrorMessage = null;
notifyListeners(); notifyListeners();
final result = await _tendersRepository.feedback( final result = await _tendersRepository.toggleFeedback(
tenderId: tenderId, tenderId: tenderId,
feedbackType: feedbackType, feedbackType: feedbackType,
); );
+18 -17
View File
@@ -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_card.dart';
import 'package:tm_app/views/detail/widgets/tender_detail_header.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/home_view_model.dart';
import '../../../view_models/tenders_view_model.dart'; import '../../../view_models/tenders_view_model.dart';
import '../../shared/desktop_navigation_widget.dart'; import '../../shared/desktop_navigation_widget.dart';
@@ -34,39 +35,39 @@ class _TenderDetailDesktopPageState extends State<TenderDetailDesktopPage> {
if (viewModel.status.isNotEmpty && if (viewModel.status.isNotEmpty &&
viewModel.status == 'submitted' && viewModel.status == 'submitted' &&
viewModel.approvalStatus == true) { viewModel.approvalStatus == true) {
ScaffoldMessenger.of( ScaffoldMessenger.of(context).showSnackBar(
context, SnackBar(content: Text(AppStrings.tenderSubmittedSuccessfully)),
).showSnackBar(SnackBar(content: Text('tender submitted successfully!'))); );
} }
if (viewModel.status.isNotEmpty && if (viewModel.status.isNotEmpty &&
viewModel.status == 'unsubmitted' && viewModel.status == 'unsubmitted' &&
viewModel.approvalStatus == true) { viewModel.approvalStatus == true) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('tender submitted removed sucessfully!')), SnackBar(content: Text(AppStrings.tenderUnsubmittedSuccessfully)),
); );
} }
if (viewModel.status.isNotEmpty && if (viewModel.status.isNotEmpty &&
viewModel.status == 'rejected' && viewModel.status == 'rejected' &&
viewModel.approvalStatus == true) { viewModel.approvalStatus == true) {
ScaffoldMessenger.of( ScaffoldMessenger.of(context).showSnackBar(
context, SnackBar(content: Text(AppStrings.tenderRejectedSuccessfully)),
).showSnackBar(SnackBar(content: Text('tender rejected sucessfully!'))); );
} }
if (viewModel.status.isNotEmpty && if (viewModel.status.isNotEmpty &&
viewModel.status == 'unrejected' && viewModel.status == 'unrejected' &&
viewModel.approvalStatus == true) { viewModel.approvalStatus == true) {
ScaffoldMessenger.of(context).showSnackBar( 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 @override
@@ -99,7 +100,7 @@ class _TenderDetailDesktopPageState extends State<TenderDetailDesktopPage> {
final detail = tenderViewModel.tenderDetail; final detail = tenderViewModel.tenderDetail;
if (detail == null) { if (detail == null) {
return const Center(child: Text('No tender details available')); return const Center(child: Text(AppStrings.tenderNoData));
} }
return Column( return Column(
+17 -17
View File
@@ -30,39 +30,39 @@ class _TenderDetailMobilePageState extends State<TenderDetailMobilePage> {
if (viewModel.status.isNotEmpty && if (viewModel.status.isNotEmpty &&
viewModel.status == 'submitted' && viewModel.status == 'submitted' &&
viewModel.approvalStatus == true) { viewModel.approvalStatus == true) {
ScaffoldMessenger.of( ScaffoldMessenger.of(context).showSnackBar(
context, SnackBar(content: Text(AppStrings.tenderSubmittedSuccessfully)),
).showSnackBar(SnackBar(content: Text('tender submitted successfully!'))); );
} }
if (viewModel.status.isNotEmpty && if (viewModel.status.isNotEmpty &&
viewModel.status == 'unsubmitted' && viewModel.status == 'unsubmitted' &&
viewModel.approvalStatus == true) { viewModel.approvalStatus == true) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('tender submitted removed sucessfully!')), SnackBar(content: Text(AppStrings.tenderUnsubmittedSuccessfully)),
); );
} }
if (viewModel.status.isNotEmpty && if (viewModel.status.isNotEmpty &&
viewModel.status == 'rejected' && viewModel.status == 'rejected' &&
viewModel.approvalStatus == true) { viewModel.approvalStatus == true) {
ScaffoldMessenger.of( ScaffoldMessenger.of(context).showSnackBar(
context, SnackBar(content: Text(AppStrings.tenderRejectedSuccessfully)),
).showSnackBar(SnackBar(content: Text('tender rejected sucessfully!'))); );
} }
if (viewModel.status.isNotEmpty && if (viewModel.status.isNotEmpty &&
viewModel.status == 'unrejected' && viewModel.status == 'unrejected' &&
viewModel.approvalStatus == true) { viewModel.approvalStatus == true) {
ScaffoldMessenger.of(context).showSnackBar( 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 @override
@@ -90,7 +90,7 @@ class _TenderDetailMobilePageState extends State<TenderDetailMobilePage> {
final detail = tenderViewModel.tenderDetail; final detail = tenderViewModel.tenderDetail;
if (detail == null) { if (detail == null) {
return const Center(child: Text('No tender details available')); return const Center(child: Text(AppStrings.tenderNoData));
} }
return SingleChildScrollView( return SingleChildScrollView(
+19 -17
View File
@@ -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/detail/widgets/tender_detail_header.dart';
import 'package:tm_app/views/shared/tablet_navigation_widget.dart'; import 'package:tm_app/views/shared/tablet_navigation_widget.dart';
import '../../../core/constants/strings.dart';
class TenderDetailTabletPage extends StatefulWidget { class TenderDetailTabletPage extends StatefulWidget {
final String tenderId; final String tenderId;
const TenderDetailTabletPage({required this.tenderId, super.key}); const TenderDetailTabletPage({required this.tenderId, super.key});
@@ -33,39 +35,39 @@ class _TenderDetailTabletPageState extends State<TenderDetailTabletPage> {
if (viewModel.status.isNotEmpty && if (viewModel.status.isNotEmpty &&
viewModel.status == 'submitted' && viewModel.status == 'submitted' &&
viewModel.approvalStatus == true) { viewModel.approvalStatus == true) {
ScaffoldMessenger.of( ScaffoldMessenger.of(context).showSnackBar(
context, SnackBar(content: Text(AppStrings.tenderSubmittedSuccessfully)),
).showSnackBar(SnackBar(content: Text('tender submitted successfully!'))); );
} }
if (viewModel.status.isNotEmpty && if (viewModel.status.isNotEmpty &&
viewModel.status == 'unsubmitted' && viewModel.status == 'unsubmitted' &&
viewModel.approvalStatus == true) { viewModel.approvalStatus == true) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('tender submitted removed sucessfully!')), SnackBar(content: Text(AppStrings.tenderUnsubmittedSuccessfully)),
); );
} }
if (viewModel.status.isNotEmpty && if (viewModel.status.isNotEmpty &&
viewModel.status == 'rejected' && viewModel.status == 'rejected' &&
viewModel.approvalStatus == true) { viewModel.approvalStatus == true) {
ScaffoldMessenger.of( ScaffoldMessenger.of(context).showSnackBar(
context, SnackBar(content: Text(AppStrings.tenderRejectedSuccessfully)),
).showSnackBar(SnackBar(content: Text('tender rejected sucessfully!'))); );
} }
if (viewModel.status.isNotEmpty && if (viewModel.status.isNotEmpty &&
viewModel.status == 'unrejected' && viewModel.status == 'unrejected' &&
viewModel.approvalStatus == true) { viewModel.approvalStatus == true) {
ScaffoldMessenger.of(context).showSnackBar( 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 @override
@@ -138,7 +140,7 @@ class _TenderDetailTabletPageState extends State<TenderDetailTabletPage> {
final detail = tenderViewModel.tenderDetail; final detail = tenderViewModel.tenderDetail;
if (detail == null) { if (detail == null) {
return const Center(child: Text('No tender details available')); return const Center(child: Text(AppStrings.tenderNoData));
} }
return SingleChildScrollView( return SingleChildScrollView(
child: Center( child: Center(
+19 -22
View File
@@ -1,21 +1,18 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tm_app/core/theme/colors.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/core/utils/size_config.dart';
import '../../../core/constants/strings.dart';
import '../../../data/services/model/tender_data/tender_data.dart';
class DeadlineItem extends StatelessWidget { class DeadlineItem extends StatelessWidget {
final String title; final TenderData detail;
final String approvalText;
final String approvalDate;
final String submissionText;
final String submissionDate;
final bool isScreenBig; final bool isScreenBig;
const DeadlineItem({ const DeadlineItem({
required this.title, required this.detail,
required this.approvalText,
required this.approvalDate,
required this.submissionText,
required this.submissionDate,
required this.isScreenBig, required this.isScreenBig,
super.key, super.key,
}); });
@@ -31,7 +28,7 @@ class DeadlineItem extends StatelessWidget {
SizedBox( SizedBox(
height: 45.0.h(), height: 45.0.h(),
child: Text( child: Text(
title, detail.title ?? '',
style: TextStyle( style: TextStyle(
color: AppColors.grey80, color: AppColors.grey80,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -47,7 +44,7 @@ class DeadlineItem extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
approvalText, AppStrings.tenderApprovalText,
style: TextStyle( style: TextStyle(
color: AppColors.grey70, color: AppColors.grey70,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@@ -56,7 +53,7 @@ class DeadlineItem extends StatelessWidget {
), ),
SizedBox(height: 4.0.h()), SizedBox(height: 4.0.h()),
Text( Text(
approvalDate, unixToDate(detail.submissionDeadline),
style: TextStyle( style: TextStyle(
color: AppColors.grey80, color: AppColors.grey80,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@@ -76,7 +73,7 @@ class DeadlineItem extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
submissionText, AppStrings.tenderSubmissionText,
style: TextStyle( style: TextStyle(
color: AppColors.grey70, color: AppColors.grey70,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@@ -85,7 +82,7 @@ class DeadlineItem extends StatelessWidget {
), ),
SizedBox(height: 4.0.h()), SizedBox(height: 4.0.h()),
Text( Text(
submissionDate, unixToDate(detail.applicationDeadline),
style: TextStyle( style: TextStyle(
color: AppColors.grey80, color: AppColors.grey80,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@@ -99,7 +96,7 @@ class DeadlineItem extends StatelessWidget {
), ),
), ),
SizedBox(height: 8.0.h()), SizedBox(height: 8.0.h()),
Divider(color: AppColors.grey20,), Divider(color: AppColors.grey20),
], ],
), ),
) )
@@ -111,7 +108,7 @@ class DeadlineItem extends StatelessWidget {
SizedBox( SizedBox(
height: 45.0.h(), height: 45.0.h(),
child: Text( child: Text(
title, detail.title ?? '',
style: TextStyle( style: TextStyle(
color: AppColors.grey80, color: AppColors.grey80,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -123,7 +120,7 @@ class DeadlineItem extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
approvalText, AppStrings.tenderApprovalText,
style: TextStyle( style: TextStyle(
color: AppColors.grey70, color: AppColors.grey70,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@@ -131,7 +128,7 @@ class DeadlineItem extends StatelessWidget {
), ),
), ),
Text( Text(
approvalDate, unixToDate(detail.submissionDeadline),
style: TextStyle( style: TextStyle(
color: AppColors.grey70, color: AppColors.grey70,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@@ -145,7 +142,7 @@ class DeadlineItem extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
submissionText, AppStrings.tenderSubmissionText,
style: TextStyle( style: TextStyle(
color: AppColors.grey70, color: AppColors.grey70,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@@ -153,7 +150,7 @@ class DeadlineItem extends StatelessWidget {
), ),
), ),
Text( Text(
submissionDate, unixToDate(detail.applicationDeadline),
style: TextStyle( style: TextStyle(
color: AppColors.grey70, color: AppColors.grey70,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@@ -163,7 +160,7 @@ class DeadlineItem extends StatelessWidget {
], ],
), ),
SizedBox(height: 8.0.h()), SizedBox(height: 8.0.h()),
Divider(color: AppColors.grey20,), Divider(color: AppColors.grey20),
], ],
), ),
); );
@@ -33,7 +33,7 @@ class TenderDetailCard extends StatelessWidget {
), ),
), ),
Text( Text(
AppStrings.tenderMatchPercentageExample, '75%',
style: TextStyle( style: TextStyle(
fontSize: 12.0.sp(), fontSize: 12.0.sp(),
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:tm_app/core/theme/colors.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/core/utils/size_config.dart';
import 'package:tm_app/data/services/model/tender_data/tender_data.dart'; import 'package:tm_app/data/services/model/tender_data/tender_data.dart';
import 'package:tm_app/views/detail/widgets/status_tag.dart'; import 'package:tm_app/views/detail/widgets/status_tag.dart';
@@ -73,14 +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);
}
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:tm_app/core/constants/strings.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/data/services/model/tender_data/tender_data.dart';
import 'package:tm_app/views/detail/widgets/deadline_item.dart'; import 'package:tm_app/views/detail/widgets/deadline_item.dart';
@@ -21,14 +20,7 @@ class TenderDetailInfoSection extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
InfoItem(title: AppStrings.tenderIdLabel, value: detail.tenderId ?? ''), InfoItem(title: AppStrings.tenderIdLabel, value: detail.tenderId ?? ''),
DeadlineItem( DeadlineItem(detail: detail, isScreenBig: isScreenBig),
title: AppStrings.tenderDeadlineLabel,
approvalText: AppStrings.tenderApprovalText,
approvalDate: unixToDate(detail.submissionDeadline),
submissionText: AppStrings.tenderSubmissionText,
submissionDate: unixToDate(detail.applicationDeadline),
isScreenBig: isScreenBig,
),
InfoItem( InfoItem(
title: AppStrings.tenderClientLabel, title: AppStrings.tenderClientLabel,
value: detail.buyerOrganization!.name ?? '', 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);
}
+73 -86
View File
@@ -6,7 +6,6 @@ import 'package:tm_app/core/constants/strings.dart';
import 'package:tm_app/core/routes/app_routes.dart'; import 'package:tm_app/core/routes/app_routes.dart';
import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/data/services/model/tender_approvals_state_response/tender_approvals_state_response.dart';
import 'package:tm_app/view_models/home_view_model.dart'; import 'package:tm_app/view_models/home_view_model.dart';
import 'package:tm_app/views/shared/desktop_navigation_widget.dart'; import 'package:tm_app/views/shared/desktop_navigation_widget.dart';
@@ -30,111 +29,99 @@ class DesktopHomePage extends StatelessWidget {
return Center(child: Text(homeViewModel.errorMessage!)); return Center(child: Text(homeViewModel.errorMessage!));
} }
return Column( if (homeViewModel.tenderApprovalsStateResponse != null &&
children: [ homeViewModel.data != null &&
DesktopNavigationWidget( homeViewModel.feedbackStats != null) {
currentIndex: 0, // Home index return Column(
onTabChanged: (index) { children: [
// Navigate to different screens based on index DesktopNavigationWidget(
switch (index) { currentIndex: 0, // Home index
case 0: onTabChanged: (index) {
// Already on home // Navigate to different screens based on index
break; switch (index) {
case 1: case 0:
// Navigate to tenders // Already on home
Router.neglect(context, () => context.go('/tenders')); break;
break; case 1:
case 2: // Navigate to tenders
// Navigate to profile Router.neglect(context, () => context.go('/tenders'));
Router.neglect(context, () => context.go('/profile')); break;
break; case 2:
} // Navigate to profile
}, Router.neglect(context, () => context.go('/profile'));
), break;
Expanded( }
child: Center( },
child: SizedBox( ),
width: 780, Expanded(
child: SingleChildScrollView( child: Center(
child: Column( child: SizedBox(
crossAxisAlignment: CrossAxisAlignment.center, width: 780,
children: [ child: SingleChildScrollView(
SizedBox(height: 55), child: Column(
// SizedBox(width: 780, child: NotificationCard()), crossAxisAlignment: CrossAxisAlignment.center,
SizedBox(height: 40.0), children: [
_progressBarsRow( SizedBox(height: 55),
homeViewModel.tenderApprovalsStateResponse!, // SizedBox(width: 780, child: NotificationCard()),
), SizedBox(height: 40.0),
SizedBox(height: 32.0.h()), _progressBarsRow(homeViewModel),
Padding( SizedBox(height: 32.0.h()),
padding: EdgeInsets.symmetric(horizontal: 15.0.w()), Padding(
child: _firstTenderCardsRow( padding: EdgeInsets.symmetric(
context, horizontal: 15.0.w(),
homeViewModel.tenderApprovalsStateResponse!, ),
homeViewModel, child: _firstTenderCardsRow(
context,
homeViewModel,
),
), ),
), SizedBox(height: 32.0.h()),
SizedBox(height: 32.0.h()), _yourTenderText(homeViewModel),
_yourTenderText(homeViewModel), SizedBox(height: 8.0.h()),
SizedBox(height: 8.0.h()), _bottomListView(homeViewModel),
_bottomListView(homeViewModel), ],
], ),
), ),
), ),
), ),
), ),
), ],
], );
}
return const Center(
child: CircularProgressIndicator(color: AppColors.secondary50),
); );
}, },
), ),
); );
} }
Widget _progressBarsRow( Widget _progressBarsRow(HomeViewModel homeViewModel) {
TenderApprovalsStateResponse tenderApprovalsStateResponse,
) {
final partnership =
tenderApprovalsStateResponse.data!.partnershipCount ?? 0;
final selfApply = tenderApprovalsStateResponse.data!.selfApplyCount ?? 0;
final total = (partnership + selfApply);
final partnershipPercent = total > 0 ? (partnership / total) * 100 : 0;
final selfApplyPercent = total > 0 ? (selfApply / total) * 100 : 0;
return Row( return Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
ProgressBarColumn( ProgressBarColumn(
text: AppStrings.partnership, text: AppStrings.partnership,
value: double.parse('$partnershipPercent'), value: homeViewModel.partnershipPercent,
amount: double.parse('$partnershipPercent').toString(), amount: homeViewModel.partnershipPercent.toString(),
circularProgressIndicatorWidth: 176, circularProgressIndicatorWidth: 176,
circularProgressIndicatorHeight: 176, circularProgressIndicatorHeight: 176,
), ),
SizedBox(width: 106), SizedBox(width: 106),
ProgressBarColumn( ProgressBarColumn(
text: AppStrings.selfApply, text: AppStrings.selfApply,
value: double.parse(selfApplyPercent.toString()), value: homeViewModel.selfApplyPercent,
amount: double.parse(selfApplyPercent.toString()).toString(), amount: homeViewModel.selfApplyPercent.toString(),
circularProgressIndicatorWidth: 176, circularProgressIndicatorWidth: 176,
circularProgressIndicatorHeight: 176, circularProgressIndicatorHeight: 176,
), ),
//SizedBox(width: 106),
// ProgressBarColumn(
// text: AppStrings.contracting,
// value: double.parse(homeResponse.contracting ?? '0'),
// amount: homeResponse.contracting ?? '0',
// circularProgressIndicatorWidth: 176,
// circularProgressIndicatorHeight: 176,
// ),
], ],
); );
} }
Widget _firstTenderCardsRow( Widget _firstTenderCardsRow(
BuildContext context, BuildContext context,
TenderApprovalsStateResponse tenderApprovalsStateResponse,
HomeViewModel homeViewModel, HomeViewModel homeViewModel,
) { ) {
return Row( return Row(
@@ -143,16 +130,16 @@ class DesktopHomePage extends StatelessWidget {
Expanded( Expanded(
child: TenderCard( child: TenderCard(
backgroundColor: AppColors.primary20, backgroundColor: AppColors.primary20,
iconPath: 'assets/icons/arrows.svg', iconPath: AssetsManager.arrows,
title: AppStrings.tenderSubmitted, title: AppStrings.tenderSubmitted,
amount: amount:
tenderApprovalsStateResponse.data!.submittedTenders.toString(), homeViewModel
// tenderApprovalsStateResponse .tenderApprovalsStateResponse!
// .data! .data!
// .approvalsBySubmission! .submittedTenders
// .selfApply .toString(),
// .toString(),
textColor: Color(0xFF0164FF), textColor: AppColors.mainBlue,
enableTap: true, enableTap: true,
width: 178, width: 178,
height: 148, height: 148,
@@ -168,7 +155,7 @@ class DesktopHomePage extends StatelessWidget {
iconPath: AssetsManager.approve, iconPath: AssetsManager.approve,
title: AppStrings.approvedTenders, title: AppStrings.approvedTenders,
amount: '0', amount: '0',
textColor: Color(0xFF24848E), textColor: AppColors.jellyBean,
enableTap: true, enableTap: true,
width: 178, width: 178,
height: 148, height: 148,
@@ -180,10 +167,10 @@ class DesktopHomePage extends StatelessWidget {
SizedBox(width: 10), SizedBox(width: 10),
TenderCard( TenderCard(
backgroundColor: AppColors.orange10, backgroundColor: AppColors.orange10,
iconPath: 'assets/icons/shield.svg', iconPath: AssetsManager.shield,
title: AppStrings.tenderValue, title: AppStrings.tenderValue,
amount: '3', amount: '3',
textColor: Color(0xFFE5821E), textColor: AppColors.orange,
enableTap: true, enableTap: true,
width: 178, width: 178,
height: 148, height: 148,
@@ -195,7 +182,7 @@ class DesktopHomePage extends StatelessWidget {
iconPath: AssetsManager.thumbLike, iconPath: AssetsManager.thumbLike,
title: AppStrings.likedTenders, title: AppStrings.likedTenders,
amount: homeViewModel.feedbackStats?.totalLikes.toString() ?? '0', amount: homeViewModel.feedbackStats?.totalLikes.toString() ?? '0',
textColor: Color(0xFF9E9E9E), textColor: AppColors.grey80,
enableTap: false, enableTap: false,
width: 178, width: 178,
height: 148, height: 148,
+12 -31
View File
@@ -31,7 +31,8 @@ class MobileHomePage extends StatelessWidget {
} }
if (homeViewModel.tenderApprovalsStateResponse != null && if (homeViewModel.tenderApprovalsStateResponse != null &&
homeViewModel.data != null) { homeViewModel.data != null &&
homeViewModel.feedbackStats != null) {
return SingleChildScrollView( return SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -39,7 +40,7 @@ class MobileHomePage extends StatelessWidget {
// SizedBox(height: 18.0.h()), // SizedBox(height: 18.0.h()),
// NotificationCard(), // NotificationCard(),
SizedBox(height: 32.0.h()), SizedBox(height: 32.0.h()),
_progressBarsRow(homeViewModel.tenderApprovalsStateResponse!), _progressBarsRow(homeViewModel),
SizedBox(height: 32.0.h()), SizedBox(height: 32.0.h()),
_firstTenderCardsRow( _firstTenderCardsRow(
context, context,
@@ -62,17 +63,7 @@ class MobileHomePage extends StatelessWidget {
); );
} }
Widget _progressBarsRow( Widget _progressBarsRow(HomeViewModel homeViewModel) {
TenderApprovalsStateResponse tenderApprovalsStateResponse,
) {
final partnership =
tenderApprovalsStateResponse.data!.partnershipCount ?? 0;
final selfApply = tenderApprovalsStateResponse.data!.selfApplyCount ?? 0;
final total = (partnership + selfApply);
final partnershipPercent = total > 0 ? (partnership / total) * 100 : 0;
final selfApplyPercent = total > 0 ? (selfApply / total) * 100 : 0;
return Padding( return Padding(
padding: EdgeInsets.symmetric(horizontal: 24.0.w()), padding: EdgeInsets.symmetric(horizontal: 24.0.w()),
child: Row( child: Row(
@@ -81,22 +72,17 @@ class MobileHomePage extends StatelessWidget {
SizedBox(width: 20.0.w()), SizedBox(width: 20.0.w()),
ProgressBarColumn( ProgressBarColumn(
text: AppStrings.partnership, text: AppStrings.partnership,
value: double.parse('$partnershipPercent'), value: homeViewModel.partnershipPercent,
amount: double.parse('$partnershipPercent').toString(), amount: homeViewModel.partnershipPercent.toString(),
), ),
Spacer(), Spacer(),
ProgressBarColumn( ProgressBarColumn(
text: AppStrings.selfApply, text: AppStrings.selfApply,
value: double.parse(selfApplyPercent.toString()), value: homeViewModel.selfApplyPercent,
amount: double.parse(selfApplyPercent.toString()).toString(), amount: homeViewModel.selfApplyPercent.toString(),
), ),
SizedBox(width: 20.0.w()), SizedBox(width: 20.0.w()),
Spacer(), Spacer(),
// ProgressBarColumn(
// text: AppStrings.contracting,
// value: double.parse(tenderApprovalsStateResponse.data.contracting ?? '0'),
// amount: tenderApprovalsStateResponse.data.contracting ?? '0',
// ),
], ],
), ),
); );
@@ -117,12 +103,7 @@ class MobileHomePage extends StatelessWidget {
title: AppStrings.tenderSubmitted, title: AppStrings.tenderSubmitted,
amount: amount:
tenderApprovalsStateResponse.data!.submittedTenders.toString(), tenderApprovalsStateResponse.data!.submittedTenders.toString(),
// tenderApprovalsStateResponse textColor: AppColors.mainBlue,
// .data!
// .approvalsBySubmission!
// .selfApply
// .toString(),
textColor: Color(0xFF0164FF),
enableTap: true, enableTap: true,
onTap: () { onTap: () {
YourTendersRouteData().push(context); YourTendersRouteData().push(context);
@@ -134,7 +115,7 @@ class MobileHomePage extends StatelessWidget {
title: AppStrings.approvedTenders, title: AppStrings.approvedTenders,
amount: '0', amount: '0',
textColor: Color(0xFF24848E), textColor: AppColors.jellyBean,
enableTap: true, enableTap: true,
onTap: () { onTap: () {
YourTendersRouteData().push(context); YourTendersRouteData().push(context);
@@ -157,7 +138,7 @@ class MobileHomePage extends StatelessWidget {
iconPath: AssetsManager.shield, iconPath: AssetsManager.shield,
title: AppStrings.tenderValue, title: AppStrings.tenderValue,
amount: '3', amount: '3',
textColor: Color(0xFFE5821E), textColor: AppColors.orange,
enableTap: true, enableTap: true,
onTap: () {}, onTap: () {},
), ),
@@ -166,7 +147,7 @@ class MobileHomePage extends StatelessWidget {
iconPath: AssetsManager.thumbLike, iconPath: AssetsManager.thumbLike,
title: AppStrings.likedTenders, title: AppStrings.likedTenders,
amount: homeViewModel.feedbackStats?.totalLikes.toString() ?? '0', amount: homeViewModel.feedbackStats?.totalLikes.toString() ?? '0',
textColor: Color(0xFF9E9E9E), textColor: AppColors.grey80,
enableTap: false, enableTap: false,
onTap: () {}, onTap: () {},
), ),
+46 -60
View File
@@ -81,58 +81,56 @@ class TabletHomePage extends StatelessWidget {
return Center(child: Text(homeViewModel.errorMessage!)); return Center(child: Text(homeViewModel.errorMessage!));
} }
return SingleChildScrollView( if (homeViewModel.tenderApprovalsStateResponse != null &&
child: Padding( homeViewModel.data != null &&
padding: EdgeInsetsDirectional.fromSTEB( homeViewModel.feedbackStats != null) {
24.0.w(), return SingleChildScrollView(
24.0.h(), child: Padding(
24.0.w(), padding: EdgeInsetsDirectional.fromSTEB(
24.0.h(), 24.0.w(),
24.0.h(),
24.0.w(),
24.0.h(),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// NotificationCard(),
SizedBox(height: 40.0.h()),
_progressBarsRow(homeViewModel),
SizedBox(height: 32.0.h()),
_firstTenderCardsRow(
context,
homeViewModel.tenderApprovalsStateResponse!,
),
SizedBox(height: 8.0.h()),
_secondTenderCardsRow(homeViewModel),
SizedBox(height: 32.0.h()),
_yourTenderText(homeViewModel),
SizedBox(height: 8.0.h()),
_bottomListView(homeViewModel),
],
),
), ),
child: Column( );
crossAxisAlignment: CrossAxisAlignment.start, }
children: [ return const Center(
// NotificationCard(), child: CircularProgressIndicator(color: AppColors.secondary50),
SizedBox(height: 40.0.h()),
_progressBarsRow(homeViewModel.tenderApprovalsStateResponse!),
SizedBox(height: 32.0.h()),
_firstTenderCardsRow(
context,
homeViewModel.tenderApprovalsStateResponse!,
),
SizedBox(height: 8.0.h()),
_secondTenderCardsRow(homeViewModel),
SizedBox(height: 32.0.h()),
_yourTenderText(homeViewModel),
SizedBox(height: 8.0.h()),
_bottomListView(homeViewModel),
],
),
),
); );
}, },
), ),
); );
} }
Widget _progressBarsRow( Widget _progressBarsRow(HomeViewModel homeViewModel) {
TenderApprovalsStateResponse tenderApprovalsStateResponse,
) {
final partnership =
tenderApprovalsStateResponse.data!.partnershipCount ?? 0;
final selfApply = tenderApprovalsStateResponse.data!.selfApplyCount ?? 0;
final total = (partnership + selfApply);
final partnershipPercent = total > 0 ? (partnership / total) * 100 : 0;
final selfApplyPercent = total > 0 ? (selfApply / total) * 100 : 0;
return Row( return Row(
children: [ children: [
Spacer(), Spacer(),
SizedBox(width: 20.0.w()), SizedBox(width: 20.0.w()),
ProgressBarColumn( ProgressBarColumn(
text: AppStrings.partnership, text: AppStrings.partnership,
value: double.parse('$partnershipPercent'), value: homeViewModel.partnershipPercent,
amount: double.parse('$partnershipPercent').toString(), amount: homeViewModel.partnershipPercent.toString(),
circularProgressIndicatorWidth: 176.0, circularProgressIndicatorWidth: 176.0,
circularProgressIndicatorHeight: 176.0, circularProgressIndicatorHeight: 176.0,
strokeWidth: 8.0, strokeWidth: 8.0,
@@ -140,22 +138,14 @@ class TabletHomePage extends StatelessWidget {
Spacer(), Spacer(),
ProgressBarColumn( ProgressBarColumn(
text: AppStrings.selfApply, text: AppStrings.selfApply,
value: double.parse(selfApplyPercent.toString()), value: homeViewModel.selfApplyPercent,
amount: double.parse(selfApplyPercent.toString()).toString(), amount: homeViewModel.selfApplyPercent.toString(),
circularProgressIndicatorWidth: 176.0, circularProgressIndicatorWidth: 176.0,
circularProgressIndicatorHeight: 176.0, circularProgressIndicatorHeight: 176.0,
strokeWidth: 8.0, strokeWidth: 8.0,
), ),
SizedBox(width: 20.0.w()), SizedBox(width: 20.0.w()),
Spacer(), Spacer(),
// ProgressBarColumn(
// text: AppStrings.contracting,
// value: double.parse(homeResponse.contracting ?? '0'),
// amount: homeResponse.contracting ?? '0',
// circularProgressIndicatorWidth: 176.0,
// circularProgressIndicatorHeight: 176.0,
// strokeWidth: 8.0,
// ),
], ],
); );
} }
@@ -170,16 +160,12 @@ class TabletHomePage extends StatelessWidget {
Expanded( Expanded(
child: TenderCard( child: TenderCard(
backgroundColor: AppColors.primary20, backgroundColor: AppColors.primary20,
iconPath: 'assets/icons/arrows.svg', iconPath: AssetsManager.arrows,
title: AppStrings.tenderSubmitted, title: AppStrings.tenderSubmitted,
amount: amount:
tenderApprovalsStateResponse.data!.submittedTenders.toString(), tenderApprovalsStateResponse.data!.submittedTenders.toString(),
// tenderApprovalsStateResponse
// .data! textColor: AppColors.mainBlue,
// .approvalsBySubmission!
// .selfApply
// .toString(),
textColor: Color(0xFF0164FF),
enableTap: true, enableTap: true,
width: double.infinity, width: double.infinity,
height: 148, height: 148,
@@ -196,7 +182,7 @@ class TabletHomePage extends StatelessWidget {
title: AppStrings.approvedTenders, title: AppStrings.approvedTenders,
amount: '0', amount: '0',
textColor: Color(0xFF24848E), textColor: AppColors.jellyBean,
enableTap: true, enableTap: true,
width: double.infinity, width: double.infinity,
height: 148, height: 148,
@@ -217,10 +203,10 @@ class TabletHomePage extends StatelessWidget {
Expanded( Expanded(
child: TenderCard( child: TenderCard(
backgroundColor: AppColors.orange10, backgroundColor: AppColors.orange10,
iconPath: 'assets/icons/shield.svg', iconPath: AssetsManager.shield,
title: AppStrings.tenderValue, title: AppStrings.tenderValue,
amount: '3', amount: '3',
textColor: Color(0xFFE5821E), textColor: AppColors.orange,
enableTap: true, enableTap: true,
width: double.infinity, width: double.infinity,
height: 148, height: 148,
@@ -234,7 +220,7 @@ class TabletHomePage extends StatelessWidget {
iconPath: AssetsManager.thumbLike, iconPath: AssetsManager.thumbLike,
title: AppStrings.likedTenders, title: AppStrings.likedTenders,
amount: homeViewModel.feedbackStats?.totalLikes.toString() ?? '0', amount: homeViewModel.feedbackStats?.totalLikes.toString() ?? '0',
textColor: Color(0xFF9E9E9E), textColor: AppColors.grey80,
enableTap: false, enableTap: false,
width: double.infinity, width: double.infinity,
height: 148, height: 148,
+1 -1
View File
@@ -78,7 +78,7 @@ class _ProgressBarColumnState extends State<ProgressBarColumn>
animation: _animation, animation: _animation,
builder: builder:
(context, child) => CircularProgressIndicator( (context, child) => CircularProgressIndicator(
backgroundColor: Color(0xFFE1E1E1), backgroundColor: AppColors.grey10,
strokeCap: StrokeCap.round, strokeCap: StrokeCap.round,
strokeWidth: widget.strokeWidth ?? 6.0, strokeWidth: widget.strokeWidth ?? 6.0,
valueColor: AlwaysStoppedAnimation<Color>(AppColors.jellyBean), valueColor: AlwaysStoppedAnimation<Color>(AppColors.jellyBean),
+2 -1
View File
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import '../../core/constants/assets.dart'; import '../../core/constants/assets.dart';
import '../../core/theme/colors.dart';
import '../../core/utils/size_config.dart'; import '../../core/utils/size_config.dart';
class TenderCard extends StatelessWidget { class TenderCard extends StatelessWidget {
@@ -50,7 +51,7 @@ class TenderCard extends StatelessWidget {
style: TextStyle( style: TextStyle(
fontSize: 16.0.sp(), fontSize: 16.0.sp(),
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Color(0xFF777777), color: AppColors.grey80,
), ),
), ),
Spacer(), Spacer(),
-18
View File
@@ -97,7 +97,6 @@ class TendersListItem extends StatelessWidget {
), ),
), ),
Spacer(), Spacer(),
// SizedBox(height: 18.0.h()),
TenderCardProgressBar(), TenderCardProgressBar(),
SizedBox(height: 15.0.h()), SizedBox(height: 15.0.h()),
@@ -123,23 +122,6 @@ class TendersListItem extends StatelessWidget {
), ),
), ),
Spacer(), Spacer(),
// Container(
// width: 96.0.w(),
// height: 24.0.h(),
// decoration: BoxDecoration(
// color: AppColors.grey30,
// borderRadius: BorderRadius.circular(88),
// ),
// alignment: Alignment.center,
// child: Text(
// 'tender.type',
// style: TextStyle(
// fontSize: 12.0.sp(),
// fontWeight: FontWeight.w500,
// color: AppColors.grey60,
// ),
// ),
// ),
], ],
), ),
], ],
@@ -23,7 +23,7 @@ class TenderCardProgressBar extends StatelessWidget {
), ),
), ),
Text( Text(
AppStrings.tenderMatchPercentageExample, '75%',
style: TextStyle( style: TextStyle(
fontSize: 12.0.sp(), fontSize: 12.0.sp(),
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
+13 -9
View File
@@ -64,6 +64,10 @@ class _DesktopProfilePageState extends State<DesktopProfilePage> {
return Center(child: Text(viewModel.errorMessage!)); return Center(child: Text(viewModel.errorMessage!));
} }
if (viewModel.companyProfileData == null) {
return Center(child: Text(AppStrings.noData));
}
return Column( return Column(
children: [ children: [
DesktopNavigationWidget( DesktopNavigationWidget(
@@ -81,34 +85,34 @@ class _DesktopProfilePageState extends State<DesktopProfilePage> {
break; break;
case 2: case 2:
// Already on profile // Already on profile
break; break;
} }
}, },
), ),
SizedBox( SizedBox(
// color: Colors.amber.shade100,
width: 740, width: 740,
// height: 662,
child: Column( child: Column(
children: [ children: [
SizedBox(height: 36.0.h()), SizedBox(height: 36.0.h()),
TitleDescription( TitleDescription(
title: 'Company Name', title: AppStrings.companyName,
description: viewModel.companyProfileData!.name!, description: viewModel.companyProfileData!.name!,
), ),
TitleDescription( TitleDescription(
title: 'National ID', title: AppStrings.nationalId,
description: viewModel.companyProfileData!.id!, description: viewModel.companyProfileData!.id!,
), ),
TitleDescription( TitleDescription(
title: 'Registration No.', title: AppStrings.registrationNumber,
description: description:
viewModel.companyProfileData!.registrationNumber!, viewModel.companyProfileData!.registrationNumber!,
), ),
TitleDescription(title: 'Business Type', description: '-'),
TitleDescription( TitleDescription(
title: 'Founded', title: AppStrings.businessType,
description: '-',
),
TitleDescription(
title: AppStrings.founded,
description: description:
viewModel.companyProfileData!.foundedYear!.toString(), viewModel.companyProfileData!.foundedYear!.toString(),
), ),
@@ -168,7 +172,7 @@ class _DesktopProfilePageState extends State<DesktopProfilePage> {
), ),
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
'Logout', AppStrings.logout,
style: TextStyle( style: TextStyle(
fontSize: 14.0.sp(), fontSize: 14.0.sp(),
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
+13 -6
View File
@@ -65,6 +65,10 @@ class _MobileProfilePageState extends State<MobileProfilePage> {
return Center(child: Text(viewModel.errorMessage!)); return Center(child: Text(viewModel.errorMessage!));
} }
if (viewModel.companyProfileData == null) {
return Center(child: Text(AppStrings.noData));
}
return Padding( return Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: 24.0.w(), horizontal: 24.0.w(),
@@ -74,21 +78,24 @@ class _MobileProfilePageState extends State<MobileProfilePage> {
children: [ children: [
SizedBox(height: 36.0.h()), SizedBox(height: 36.0.h()),
TitleDescription( TitleDescription(
title: 'Company Name', title: AppStrings.companyName,
description: viewModel.companyProfileData!.name!, description: viewModel.companyProfileData!.name!,
), ),
TitleDescription( TitleDescription(
title: 'National ID', title: AppStrings.nationalId,
description: viewModel.companyProfileData!.id!, description: viewModel.companyProfileData!.id!,
), ),
TitleDescription( TitleDescription(
title: 'Registration No.', title: AppStrings.registrationNumber,
description: description:
viewModel.companyProfileData!.registrationNumber!, viewModel.companyProfileData!.registrationNumber!,
), ),
TitleDescription(title: 'Business Type', description: '-'),
TitleDescription( TitleDescription(
title: 'Founded', title: AppStrings.businessType,
description: '-',
),
TitleDescription(
title: AppStrings.founded,
description: description:
viewModel.companyProfileData!.foundedYear!.toString(), viewModel.companyProfileData!.foundedYear!.toString(),
), ),
@@ -145,7 +152,7 @@ class _MobileProfilePageState extends State<MobileProfilePage> {
), ),
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
'Logout', AppStrings.logout,
style: TextStyle( style: TextStyle(
fontSize: 14.0.sp(), fontSize: 14.0.sp(),
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
+19 -10
View File
@@ -64,6 +64,10 @@ class _TabletProfilePageState extends State<TabletProfilePage> {
return Center(child: Text(viewModel.errorMessage!)); return Center(child: Text(viewModel.errorMessage!));
} }
if (viewModel.companyProfileData == null) {
return Center(child: Text(AppStrings.noData));
}
return SafeArea( return SafeArea(
child: Scaffold( child: Scaffold(
key: key, key: key,
@@ -118,22 +122,27 @@ class _TabletProfilePageState extends State<TabletProfilePage> {
children: [ children: [
SizedBox(height: 128.0.h()), SizedBox(height: 128.0.h()),
TitleDescription( TitleDescription(
title: 'Company Name', title: AppStrings.companyName,
description: 'Telecom Solutions GmbH', description: viewModel.companyProfileData!.name!,
), ),
TitleDescription( TitleDescription(
title: 'National ID', title: AppStrings.nationalId,
description: '12345678901', description: viewModel.companyProfileData!.id!,
), ),
TitleDescription( TitleDescription(
title: 'Registration No.', title: AppStrings.registrationNumber,
description: 'DE-55667788', description:
viewModel.companyProfileData!.registrationNumber!,
), ),
TitleDescription( TitleDescription(
title: 'Business Type', title: AppStrings.businessType,
description: 'Telecommunications', description: '-',
),
TitleDescription(
title: AppStrings.founded,
description:
viewModel.companyProfileData!.foundedYear!.toString(),
), ),
TitleDescription(title: 'Founded', description: '2010'),
SizedBox(height: 24.0.h()), SizedBox(height: 24.0.h()),
// Theme Toggle // Theme Toggle
Align( Align(
@@ -190,7 +199,7 @@ class _TabletProfilePageState extends State<TabletProfilePage> {
), ),
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
'Logout', AppStrings.logout,
style: TextStyle( style: TextStyle(
fontSize: 14.0.sp(), fontSize: 14.0.sp(),
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@@ -1,94 +0,0 @@
class Tender {
final String date;
final String deadline;
final String title;
final String description;
final String tenderId;
final String location;
final String country;
final double matchPercentage;
Tender({
required this.date,
required this.deadline,
required this.title,
required this.description,
required this.tenderId,
required this.location,
required this.country,
required this.matchPercentage,
});
factory Tender.fromJson(Map<String, dynamic> json) {
return Tender(
date: json['date'] as String,
deadline: json['deadline'] as String,
title: json['title'] as String,
description: json['description'] as String,
tenderId: json['tenderId'] as String,
location: json['location'] as String,
country: json['country'] as String,
matchPercentage: (json['matchPercentage'] as num).toDouble(),
);
}
Map<String, dynamic> toJson() {
return {
'date': date,
'deadline': deadline,
'title': title,
'description': description,
'tenderId': tenderId,
'location': location,
'country': country,
'matchPercentage': matchPercentage,
};
}
}
final List<Tender> sampleTenders = [
Tender(
date: '2025-05-20',
deadline: '2025-06-10',
title: 'Digital Services Tender',
description:
'A tender for digital services and IT infrastructure development. The project involves modernizing existing systems and implementing new digital solutions for improved efficiency.',
tenderId: 'KLF 2025/119',
location: 'Stockholm',
country: 'SE',
matchPercentage: 85.0,
),
Tender(
date: '2025-04-15',
deadline: '2025-05-01',
title: 'Construction Project',
description:
'Seeking contractors for a large-scale construction project in the city center. Includes both residential and commercial buildings.',
tenderId: 'CST 2025/042',
location: 'Gothenburg',
country: 'SE',
matchPercentage: 72.0,
),
Tender(
date: '2025-03-10',
deadline: '2025-04-05',
title: 'Healthcare Equipment Supply',
description:
'Supplying medical and healthcare equipment to regional hospitals. Includes installation and maintenance services.',
tenderId: 'HLT 2025/301',
location: 'Malmö',
country: 'SE',
matchPercentage: 90.0,
),
Tender(
date: '2025-02-28',
deadline: '2025-03-20',
title: 'IT Consulting Services',
description:
'Looking for experienced IT consultants to support digital transformation initiatives for public sector organizations.',
tenderId: 'ITC 2025/210',
location: 'Uppsala',
country: 'SE',
matchPercentage: 78.0,
),
];
@@ -8,6 +8,8 @@ import 'package:tm_app/view_models/tenders_view_model.dart';
import 'package:tm_app/views/shared/desktop_navigation_widget.dart'; import 'package:tm_app/views/shared/desktop_navigation_widget.dart';
import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart'; import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart';
import '../../../core/constants/strings.dart';
class DesktopTendersPage extends StatefulWidget { class DesktopTendersPage extends StatefulWidget {
const DesktopTendersPage({super.key}); const DesktopTendersPage({super.key});
@@ -91,6 +93,10 @@ class _DesktopTendersPageState extends State<DesktopTendersPage> {
if (viewModel.errorMessage != null) { if (viewModel.errorMessage != null) {
return Center(child: Text(viewModel.errorMessage!)); return Center(child: Text(viewModel.errorMessage!));
} }
if (viewModel.tendersResponse?.data?.tenders == null ||
viewModel.tendersResponse!.data!.tenders!.isEmpty) {
return Center(child: Text(AppStrings.noData));
}
return SizedBox( return SizedBox(
width: 740, width: 740,
height: 800, height: 800,
@@ -65,6 +65,11 @@ class _MobileTendersPageState extends State<MobileTendersPage> {
return Center(child: Text(viewModel.errorMessage!)); return Center(child: Text(viewModel.errorMessage!));
} }
if (viewModel.tendersResponse?.data?.tenders == null ||
viewModel.tendersResponse!.data!.tenders!.isEmpty) {
return Center(child: Text(AppStrings.noData));
}
return Padding( return Padding(
padding: EdgeInsets.symmetric(vertical: 24.0.h()), padding: EdgeInsets.symmetric(vertical: 24.0.h()),
child: MainTendersSlider( child: MainTendersSlider(
@@ -10,6 +10,7 @@ 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/main_tenders_slider.dart';
import '../../../core/constants/assets.dart'; import '../../../core/constants/assets.dart';
import '../../../core/constants/strings.dart';
class TabletTendersPage extends StatelessWidget { class TabletTendersPage extends StatelessWidget {
const TabletTendersPage({super.key}); const TabletTendersPage({super.key});
@@ -73,6 +74,10 @@ class TabletTendersPage extends StatelessWidget {
if (viewModel.errorMessage != null) { if (viewModel.errorMessage != null) {
return Center(child: Text(viewModel.errorMessage!)); return Center(child: Text(viewModel.errorMessage!));
} }
if (viewModel.tendersResponse?.data?.tenders == null ||
viewModel.tendersResponse!.data!.tenders!.isEmpty) {
return Center(child: Text(AppStrings.noData));
}
return Padding( return Padding(
padding: EdgeInsets.only(right: 24.0.w(), top: 128.0.h()), padding: EdgeInsets.only(right: 24.0.w(), top: 128.0.h()),
child: MainTendersSlider( child: MainTendersSlider(
@@ -207,7 +207,10 @@ class TenderActionButtonsRow extends StatelessWidget {
child: InkWell( child: InkWell(
splashColor: Colors.transparent, splashColor: Colors.transparent,
onTap: () { onTap: () {
viewModel.feedback(tenderId: tender.id!, feedbackType: 'dislike'); viewModel.toogleFeedback(
tenderId: tender.id!,
feedbackType: 'dislike',
);
}, },
child: Column( child: Column(
children: [ children: [
@@ -251,7 +254,7 @@ class TenderActionButtonsRow extends StatelessWidget {
child: InkWell( child: InkWell(
splashColor: Colors.transparent, splashColor: Colors.transparent,
onTap: () { onTap: () {
viewModel.feedback(tenderId: tender.id!, feedbackType: 'like'); viewModel.toogleFeedback(tenderId: tender.id!, feedbackType: 'like');
}, },
child: Column( child: Column(
children: [ children: [
@@ -154,59 +154,6 @@ class TenderCard extends StatelessWidget {
); );
} }
// Widget _approvalStatusBadge() {
// return Consumer<TendersViewModel>(
// builder: (context, viewModel, child) {
// final approvalStatus = viewModel.getApprovalStatusForTender(tender.id!);
// final statusText = approvalStatus ?? 'Pending';
// Color statusColor;
// switch (approvalStatus) {
// case 'approved':
// statusColor = AppColors.successColor;
// break;
// case 'rejected':
// statusColor = AppColors.errorColor;
// break;
// default:
// statusColor = AppColors.grey60;
// }
// return Container(
// width: double.infinity,
// height: 24.0.h(),
// padding: EdgeInsets.symmetric(horizontal: 8.0.w()),
// decoration: BoxDecoration(
// color: AppColors.grey20,
// borderRadius: BorderRadius.circular(4),
// border: Border.all(color: statusColor),
// ),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text(
// 'Approval Status',
// style: TextStyle(
// color: AppColors.grey80,
// fontSize: 14.0.sp(),
// fontWeight: FontWeight.w500,
// ),
// ),
// Text(
// statusText.toUpperCase(),
// style: TextStyle(
// color: statusColor,
// fontSize: 14.0.sp(),
// fontWeight: FontWeight.w500,
// ),
// ),
// ],
// ),
// );
// },
// );
// }
Widget _tenderTitle() { Widget _tenderTitle() {
return Text( return Text(
tender.title ?? '', tender.title ?? '',