Files
tm_app/lib/view_models/tenders_view_model.dart
T

523 lines
15 KiB
Dart

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/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';
import 'package:tm_app/data/services/model/tenders/tenders_response/tenders_response.dart';
import 'package:tm_app/views/tenders/strings/tenders_strings.dart';
import '../core/utils/result.dart';
class TendersViewModel with ChangeNotifier {
final TendersRepository _tendersRepository;
TendersViewModel({required TendersRepository tendersRepository})
: _tendersRepository = tendersRepository;
bool _isLoading = false;
bool _isLoadingMore = false;
String? _errorMessage;
String? _feedbackErrorMessage;
TendersResponse? _tendersResponse;
FeedbackData? _feedbackData;
String? _searchQuery;
bool get isLoading => _isLoading;
bool get isLoadingMore => _isLoadingMore;
String? get errorMessage => _errorMessage;
String? get feedbackErrorMessage => _feedbackErrorMessage;
TendersResponse? get tendersResponse => _tendersResponse;
FeedbackData? get feedbackData => _feedbackData;
String? get searchQuery => _searchQuery;
List<FeedbackData> feedbacks = [];
int currentPage = 1;
int _currentOffset = 0;
int _totalPages = 0;
int get totalPages => _totalPages;
static const int _pageSize = 10;
bool _hasMoreData = true;
bool get hasMoreData => _hasMoreData;
// Filter States
DateTimeRange? selectedDateRange;
String selectedStatus = 'All';
String selectedSort = '';
String sortBy = '';
String sortOrder = '';
final _fmt = DateFormat('yyyy/MM/dd');
String get startDateFormatted =>
selectedDateRange?.start != null
? _fmt.format(selectedDateRange!.start)
: 'YYYY/MM/DD';
String get endDateFormatted =>
selectedDateRange?.end != null
? _fmt.format(selectedDateRange!.end)
: 'YYYY/MM/DD';
String get startDateUnix =>
selectedDateRange?.start != null
? (selectedDateRange!.start.millisecondsSinceEpoch ~/ 1000).toString()
: '';
String get endDateUnix =>
selectedDateRange?.end != null
? (selectedDateRange!.end.millisecondsSinceEpoch ~/ 1000).toString()
: '';
// --- Filter Actions ---
void setDateRange(DateTimeRange? range) {
selectedDateRange = range;
notifyListeners();
}
void setStatus(String status) {
// Clear data when status changes
_clearAllData();
selectedStatus = status;
currentPage = 1;
notifyListeners();
}
void setSort(String sort) {
// Clear data when status changes
_clearAllData();
selectedSort = sort;
if (sort == TendersStrings.minimumTime) {
sortBy = 'submission_deadline';
sortOrder = 'asc';
} else if (sort == TendersStrings.mostOfTheTime) {
sortBy = 'submission_deadline';
sortOrder = 'desc';
}
currentPage = 1;
notifyListeners();
}
void _clearAllData() {
_tendersResponse = null;
_errorMessage = null;
_currentOffset = 0;
_hasMoreData = true;
currentPage = 1;
sortBy = '';
sortOrder = '';
selectedSort = '';
selectedStatus = '';
notifyListeners();
}
void init(BuildContext context) async {
_isLoading = true;
notifyListeners();
clearAll();
await getTenders(context: context);
}
/// Get feedback for a specific tender
FeedbackData? getFeedbackForTender(String tenderId) {
try {
return feedbacks.firstWhere((f) => f.tenderId == tenderId);
} catch (e) {
return null;
}
}
/// Check if feedback exists for a specific tender
bool hasFeedbackForTender(String tenderId) {
return feedbacks.any((f) => f.tenderId == tenderId);
}
Future<void> getTenders({
required BuildContext context,
String? query,
}) async {
_isLoading = true;
notifyListeners();
if (DeviceUtils.isDesktop(context)) {
await getTendersDesktop(query: query);
} else {
await getTendersMobile(query: query);
}
}
Future<void> getTendersMobile({String? query}) async {
// Clear all data before fetching new tenders
_currentOffset = 0;
_hasMoreData = true;
_searchQuery = query;
final result = await _tendersRepository.getTenders(
limit: _pageSize,
offset: _currentOffset,
query: query,
sortBy: '',
sortOrder: '',
);
switch (result) {
case Ok<TendersResponse>():
_tendersResponse = result.value;
// Check if we have more data
if (_tendersResponse?.data?.tenders != null) {
_hasMoreData = _tendersResponse!.data!.tenders!.length >= _pageSize;
}
if (_tendersResponse?.data?.tenders?.isNotEmpty == true) {
// Fetch feedback and approvals for the first tender only
getTenderFeedback(_tendersResponse!.data!.tenders!.first.id!);
}
if (_tendersResponse?.meta?.pages != null) {
_totalPages = _tendersResponse!.meta!.pages!;
} else {
_totalPages = 1;
}
notifyListeners();
break;
case Error<TendersResponse>():
_errorMessage = result.error.toString();
break;
}
_isLoading = false;
notifyListeners();
_errorMessage = null;
notifyListeners();
}
Future<void> getTendersDesktop({
String? query,
String? sortBy,
String? sortOrder,
String? publicationDateFrom,
String? publicationDateTo,
}) async {
// Clear all data before fetching new tenders
_isLoading = true;
notifyListeners();
_currentOffset = 0;
_hasMoreData = true;
_searchQuery = query;
final result = await _tendersRepository.getTenders(
limit: _pageSize,
offset: _currentOffset,
query: query,
sortBy: sortBy ?? '',
sortOrder: sortOrder ?? '',
publicationDateFrom: publicationDateFrom ?? '',
publicationDateTo: publicationDateTo ?? '',
);
switch (result) {
case Ok<TendersResponse>():
_tendersResponse = result.value;
// Check if we have more data
if (_tendersResponse?.data?.tenders != null) {
_hasMoreData = _tendersResponse!.data!.tenders!.length >= _pageSize;
}
if (_tendersResponse?.data?.tenders?.isNotEmpty == true) {
for (var tender in _tendersResponse!.data!.tenders!) {
getTenderFeedback(tender.id!);
}
}
if (_tendersResponse?.meta?.pages != null) {
_totalPages = _tendersResponse!.meta!.pages!;
} else {
_totalPages = 1;
}
notifyListeners();
break;
case Error<TendersResponse>():
_errorMessage = result.error.toString();
break;
}
_isLoading = false;
notifyListeners();
_errorMessage = null;
notifyListeners();
}
Future<void> loadMoreTenders() async {
if (_isLoadingMore || !_hasMoreData) {
return;
}
_isLoadingMore = true;
_errorMessage = null;
notifyListeners();
_currentOffset += _pageSize;
final result = await _tendersRepository.getTenders(
limit: _pageSize,
offset: _currentOffset,
query: _searchQuery,
sortBy: sortBy,
sortOrder: sortOrder,
);
switch (result) {
case Ok<TendersResponse>():
if (result.value.data?.tenders != null &&
result.value.data!.tenders!.isNotEmpty) {
// Create a new modifiable list by combining existing and new tenders
if (_tendersResponse?.data?.tenders != null) {
final existingTenders = _tendersResponse!.data!.tenders!;
final newTenders = result.value.data!.tenders!;
final combinedTenders = [...existingTenders, ...newTenders];
// Create a new TendersData with the combined list
_tendersResponse = _tendersResponse!.copyWith(
data: _tendersResponse!.data!.copyWith(tenders: combinedTenders),
);
} else {
_tendersResponse = result.value;
}
// Check if we have more data
_hasMoreData = result.value.data!.tenders!.length >= _pageSize;
} else {
_hasMoreData = false;
}
break;
case Error<TendersResponse>():
_errorMessage = result.error.toString();
_currentOffset -= _pageSize; // Revert offset on error
break;
}
_isLoadingMore = false;
notifyListeners();
_errorMessage = null;
notifyListeners();
}
Future<void> getTenderFeedback(String tenderId) async {
// _isLoading = true;
_errorMessage = null;
notifyListeners();
final result = await _tendersRepository.getTenderFeedback(
tenderId: tenderId,
);
switch (result) {
case Ok<FeedbackResponse>():
_feedbackData = result.value.data;
// Add or update _feedbackData in feedbacks list by tender id
if (_feedbackData != null) {
final index = feedbacks.indexWhere(
(f) => f.tenderId == _feedbackData!.tenderId,
);
if (index != -1) {
feedbacks[index] = _feedbackData!;
AppLogger().info(
'Updated feedback for tender: ${_feedbackData!.tenderId}',
);
} else {
feedbacks.add(_feedbackData!);
AppLogger().info(
'Added new feedback for tender: ${_feedbackData!.tenderId}',
);
}
AppLogger().info('Total feedbacks: ${feedbacks.length}');
}
break;
case Error<FeedbackResponse>():
_errorMessage = result.error.toString();
break;
}
// _isLoading = false;
notifyListeners();
_errorMessage = null;
notifyListeners();
}
Future<void> toogleFeedback({
required String tenderId,
required String feedbackType,
}) async {
_feedbackErrorMessage = null;
// Get current feedback for this tender
final existingFeedback = getFeedbackForTender(tenderId);
final existingFeedbackIndex = feedbacks.indexWhere(
(f) => f.tenderId == tenderId,
);
// Create optimistic feedback data
FeedbackData? optimisticFeedback;
if (existingFeedback == null) {
// No existing feedback - create new optimistic feedback
optimisticFeedback = FeedbackData(
companyId: null, // Will be filled by server
createdAt: DateTime.now().millisecondsSinceEpoch,
customerId: null, // Will be filled by server
feedbackType: feedbackType,
id: null, // Will be filled by server
tenderId: tenderId,
tender: null,
updatedAt: DateTime.now().millisecondsSinceEpoch,
);
feedbacks.add(optimisticFeedback);
} else {
// Existing feedback - update optimistically
if (existingFeedback.feedbackType == feedbackType) {
// Same feedback type - remove feedback (toggle off)
if (existingFeedbackIndex != -1) {
feedbacks.removeAt(existingFeedbackIndex);
}
optimisticFeedback = null;
} else {
// Different feedback type - update to new type
optimisticFeedback = existingFeedback.copyWith(
feedbackType: feedbackType,
updatedAt: DateTime.now().millisecondsSinceEpoch,
);
if (existingFeedbackIndex != -1) {
feedbacks[existingFeedbackIndex] = optimisticFeedback;
}
}
}
// Notify listeners immediately for optimistic update
notifyListeners();
// Call API
final result = await _tendersRepository.toggleFeedback(
tenderId: tenderId,
feedbackType: feedbackType,
);
switch (result) {
case Ok<FeedbackResponse>():
_feedbackData = result.value.data;
if (_feedbackData != null) {
// Update with complete data from server
final index = feedbacks.indexWhere(
(f) => f.tender == _feedbackData!.tender,
);
if (index != -1) {
feedbacks[index] = _feedbackData!;
} else {
feedbacks.add(_feedbackData!);
}
} else {
// Server returned null data - remove feedback
feedbacks.removeWhere((f) => f.tenderId == tenderId);
}
break;
case Error<FeedbackResponse>():
// Revert optimistic update on error
if (existingFeedback == null) {
// Remove the optimistic feedback we added
feedbacks.removeWhere((f) => f.tenderId == tenderId);
} else {
// Restore the original feedback - find current index and update, or add if not found
final currentIndex = feedbacks.indexWhere(
(f) => f.tenderId == tenderId,
);
if (currentIndex != -1) {
feedbacks[currentIndex] = existingFeedback;
} else {
feedbacks.add(existingFeedback);
}
}
_feedbackErrorMessage = result.error.toString();
break;
}
notifyListeners();
_feedbackErrorMessage = null;
notifyListeners();
}
/// Clear all feedbacks
void clearFeedbacks() {
feedbacks.clear();
_feedbackData = null;
_feedbackErrorMessage = null;
notifyListeners();
}
/// Clear all tenders
void clearTenders() {
_tendersResponse = null;
_errorMessage = null;
_currentOffset = 0;
_hasMoreData = true;
currentPage = 1;
notifyListeners();
}
Future<void> callWithSortAndStatus() async {
await getTendersDesktop(
sortBy: sortBy,
sortOrder: sortOrder,
publicationDateFrom: startDateUnix,
publicationDateTo: endDateUnix,
);
}
/// Search tenders with query
Future<void> searchTenders(String query) async {
await getTendersDesktop(
query: query.trim(),
sortBy: sortBy,
sortOrder: sortOrder,
publicationDateFrom: startDateUnix,
publicationDateTo: endDateUnix,
);
}
/// Clear all data (tenders, feedbacks, and approvals)
void clearAll() {
clearTenders();
clearFeedbacks();
_searchQuery = null;
selectedSort = '';
selectedStatus = '';
selectedDateRange = null;
sortBy = '';
sortOrder = '';
notifyListeners();
}
void jumpToPage(int page) async {
final meta = tendersResponse?.meta;
if (meta?.pages == null) {
return;
}
if (page < 1 || page > meta!.pages!) {
return;
}
currentPage = page;
_currentOffset = (page - 1) * _pageSize;
final result = await _tendersRepository.getTenders(
limit: _pageSize,
offset: _currentOffset,
query: _searchQuery,
sortBy: sortBy,
sortOrder: sortOrder,
);
switch (result) {
case Ok<TendersResponse>():
_tendersResponse = result.value;
break;
case Error<TendersResponse>():
_errorMessage = result.error.toString();
break;
}
notifyListeners();
}
}