Implement tender workflows and dashboard integration
continuous-integration/drone/push Build encountered an error

This commit is contained in:
AmirReza Jamali
2026-07-14 11:12:09 +03:30
parent ce170f9bc8
commit 72881df210
29 changed files with 1820 additions and 159 deletions
+60 -15
View File
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:tm_app/core/utils/device_utils.dart';
import 'package:tm_app/core/utils/logger.dart';
import 'package:tm_app/core/network/app_exceptions.dart';
import 'package:tm_app/data/repositories/tenders_repository.dart';
import 'package:tm_app/data/services/model/feedback_data/feedback_data.dart';
import 'package:tm_app/data/services/model/feedback_response/feedback_response.dart';
@@ -36,6 +37,12 @@ class TendersViewModel with ChangeNotifier {
String? get searchQuery => _searchQuery;
List<FeedbackData> feedbacks = [];
final Set<String> _hydratedFeedbackTenderIds = <String>{};
final Set<String> _feedbackHydrationsInFlight = <String>{};
final Set<String> _feedbackRequestsInFlight = <String>{};
bool isFeedbackLoading(String tenderId) =>
_feedbackRequestsInFlight.contains(tenderId);
static const int _pageSize = 10;
@@ -207,8 +214,7 @@ class TendersViewModel with ChangeNotifier {
_currentOffset = meta!.offset!;
}
final tenders = _tendersResponse?.data?.tenders ?? [];
_hasMoreData =
meta?.hasMore ?? (tenders.length >= _pageSize);
_hasMoreData = meta?.hasMore ?? (tenders.length >= _pageSize);
if (tenders.isNotEmpty) {
if (fetchAllFeedbacks) {
for (final tender in tenders) {
@@ -263,8 +269,13 @@ class TendersViewModel with ChangeNotifier {
data: result.value.data?.copyWith(tenders: combined),
);
final newMeta = result.value.meta;
_hasMoreData =
newMeta?.hasMore ?? (newTenders.length >= _pageSize);
_hasMoreData = newMeta?.hasMore ?? (newTenders.length >= _pageSize);
for (final tender in newTenders) {
final id = tender.id;
if (id != null && !_hydratedFeedbackTenderIds.contains(id)) {
getTenderFeedback(id);
}
}
} else {
_hasMoreData = false;
}
@@ -283,14 +294,18 @@ class TendersViewModel with ChangeNotifier {
}
Future<void> getTenderFeedback(String tenderId) async {
_errorMessage = null;
notifyListeners();
if (_hydratedFeedbackTenderIds.contains(tenderId) ||
_feedbackHydrationsInFlight.contains(tenderId)) {
return;
}
_feedbackHydrationsInFlight.add(tenderId);
final result = await _tendersRepository.getTenderFeedback(
tenderId: tenderId,
);
switch (result) {
case Ok<FeedbackResponse>():
_hydratedFeedbackTenderIds.add(tenderId);
_feedbackData = result.value.data;
if (_feedbackData != null) {
final index = feedbacks.indexWhere(
@@ -311,19 +326,33 @@ class TendersViewModel with ChangeNotifier {
}
break;
case Error<FeedbackResponse>():
_errorMessage = result.error.toString();
if (result.error is NotFoundException) {
// A 404 is the expected representation of neutral feedback state.
feedbacks.removeWhere((feedback) => feedback.tenderId == tenderId);
_hydratedFeedbackTenderIds.add(tenderId);
} else {
_feedbackErrorMessage = result.error.toString();
}
break;
}
_feedbackHydrationsInFlight.remove(tenderId);
notifyListeners();
_errorMessage = null;
notifyListeners();
if (_feedbackErrorMessage != null) {
_feedbackErrorMessage = null;
notifyListeners();
}
}
Future<void> toogleFeedback({
Future<void> toggleFeedback({
required String tenderId,
required String feedbackType,
}) async {
if (_feedbackRequestsInFlight.contains(tenderId)) {
return;
}
_feedbackErrorMessage = null;
_feedbackRequestsInFlight.add(tenderId);
final existingFeedback = getFeedbackForTender(tenderId);
final existingFeedbackIndex = feedbacks.indexWhere(
@@ -335,13 +364,13 @@ class TendersViewModel with ChangeNotifier {
if (existingFeedback == null) {
optimisticFeedback = FeedbackData(
companyId: null,
createdAt: DateTime.now().millisecondsSinceEpoch,
createdAt: DateTime.now().millisecondsSinceEpoch ~/ 1000,
customerId: null,
feedbackType: feedbackType,
id: null,
tenderId: tenderId,
tender: null,
updatedAt: DateTime.now().millisecondsSinceEpoch,
updatedAt: DateTime.now().millisecondsSinceEpoch ~/ 1000,
);
feedbacks.add(optimisticFeedback);
} else {
@@ -353,7 +382,7 @@ class TendersViewModel with ChangeNotifier {
} else {
optimisticFeedback = existingFeedback.copyWith(
feedbackType: feedbackType,
updatedAt: DateTime.now().millisecondsSinceEpoch,
updatedAt: DateTime.now().millisecondsSinceEpoch ~/ 1000,
);
if (existingFeedbackIndex != -1) {
feedbacks[existingFeedbackIndex] = optimisticFeedback;
@@ -371,9 +400,13 @@ class TendersViewModel with ChangeNotifier {
switch (result) {
case Ok<FeedbackResponse>():
_feedbackData = result.value.data;
if (_feedbackData != null) {
final responseType = _feedbackData?.feedbackType;
if (responseType == 'unlike' || responseType == 'undislike') {
feedbacks.removeWhere((feedback) => feedback.tenderId == tenderId);
_feedbackData = null;
} else if (_feedbackData != null) {
final index = feedbacks.indexWhere(
(f) => f.tender == _feedbackData!.tender,
(feedback) => feedback.tenderId == tenderId,
);
if (index != -1) {
feedbacks[index] = _feedbackData!;
@@ -383,6 +416,7 @@ class TendersViewModel with ChangeNotifier {
} else {
feedbacks.removeWhere((f) => f.tenderId == tenderId);
}
_hydratedFeedbackTenderIds.add(tenderId);
break;
case Error<FeedbackResponse>():
if (existingFeedback == null) {
@@ -401,13 +435,24 @@ class TendersViewModel with ChangeNotifier {
break;
}
_feedbackRequestsInFlight.remove(tenderId);
notifyListeners();
_feedbackErrorMessage = null;
notifyListeners();
}
/// Kept temporarily for callers compiled against the old misspelled API.
@Deprecated('Use toggleFeedback')
Future<void> toogleFeedback({
required String tenderId,
required String feedbackType,
}) => toggleFeedback(tenderId: tenderId, feedbackType: feedbackType);
void clearFeedbacks() {
feedbacks.clear();
_hydratedFeedbackTenderIds.clear();
_feedbackHydrationsInFlight.clear();
_feedbackRequestsInFlight.clear();
_feedbackData = null;
_feedbackErrorMessage = null;
notifyListeners();