import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:tm_app/core/utils/device_utils.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/tender_approvals_toggle_data/tender_approvals_toggle_data.dart'; import 'package:tm_app/data/services/model/tender_approvals_toggle_response/tender_approvals_toggle_response.dart'; import 'package:tm_app/data/services/model/tenders/tenders_response/tenders_response.dart'; import '../core/constants/tender_submision_mode.dart'; import '../core/utils/result.dart'; import '../data/services/model/tender_approvals_by_id_response/tender_approvals_by_id_response.dart'; import '../data/services/model/tender_approvals_data/tender_approvals_data.dart'; import 'home_view_model.dart'; class TendersViewModel with ChangeNotifier { final TendersRepository _tendersRepository; final HomeViewModel _homeViewModel; TendersViewModel({ required TendersRepository tendersRepository, required HomeViewModel homeViewModel, }) : _tendersRepository = tendersRepository, _homeViewModel = homeViewModel; bool _isLoading = false; bool _isLoadingMore = false; String? _errorMessage; String? _feedbackErrorMessage; String? _tenderApprovalErrorMessage; TendersResponse? _tendersResponse; FeedbackData? _feedbackData; TenderApprovalsData? _tenderApprovalData; TenderApprovalsToggleData? _tenderApprovalToggleData; String? _searchQuery; bool get isLoading => _isLoading; bool get isLoadingMore => _isLoadingMore; String? get errorMessage => _errorMessage; String? get feedbackErrorMessage => _feedbackErrorMessage; String? get tenderApprovalErrorMessage => _tenderApprovalErrorMessage; TendersResponse? get tendersResponse => _tendersResponse; FeedbackData? get feedbackData => _feedbackData; TenderApprovalsData? get tenderApprovalData => _tenderApprovalData; TenderApprovalsToggleData? get tenderApprovalToggleData => _tenderApprovalToggleData; String? get searchQuery => _searchQuery; List feedbacks = []; List tenderApprovals = []; 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; /// Get feedback for a specific tender FeedbackData? getFeedbackForTender(String tenderId) { try { return feedbacks.firstWhere((f) => f.tender == tenderId); } catch (e) { return null; } } /// Check if feedback exists for a specific tender bool hasFeedbackForTender(String tenderId) { return feedbacks.any((f) => f.tender == tenderId); } /// Get tender approval for a specific tender TenderApprovalsData? getTenderApprovalForTender(String tenderId) { try { return tenderApprovals.firstWhere((a) => a.tenderId == tenderId); } catch (e) { return null; } } /// Check if tender approval exists for a specific tender bool hasTenderApprovalForTender(String tenderId) { return tenderApprovals.any((a) => a.tenderId == tenderId); } /// Get approval status for a specific tender String? getApprovalStatusForTender(String tenderId) { final approval = getTenderApprovalForTender(tenderId); return approval?.status; } Future getTenders({String? query, BuildContext? context}) async { if (context != null && DeviceUtils.isDesktop(context)) { await getTendersDesktop(query: query); } else { await getTendersMobile(query: query); } } Future getTendersMobile({String? query}) async { _isLoading = true; notifyListeners(); clearAll(); // Clear all data before fetching new tenders _currentOffset = 0; _hasMoreData = true; _searchQuery = query; final result = await _tendersRepository.getTenders( limit: _pageSize, offset: _currentOffset, query: query, ); switch (result) { case Ok(): _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!); getTenderApprovals(_tendersResponse!.data!.tenders!.first.id!); } if (_tendersResponse?.meta?.pages != null) { _totalPages = _tendersResponse!.meta!.pages!; print('Total pages: $_totalPages'); } else { _totalPages = 1; print('Total pages not available, fallback to 1'); } notifyListeners(); break; case Error(): _errorMessage = result.error.toString(); break; } _isLoading = false; notifyListeners(); _errorMessage = null; notifyListeners(); } Future getTendersDesktop({String? query}) async { _isLoading = true; notifyListeners(); clearAll(); // Clear all data before fetching new tenders _currentOffset = 0; _hasMoreData = true; _searchQuery = query; final result = await _tendersRepository.getTenders( limit: _pageSize, offset: _currentOffset, query: query, ); switch (result) { case Ok(): _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!); // getTenderApprovals(_tendersResponse!.data!.tenders!.first.id!); 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(): _errorMessage = result.error.toString(); break; } _isLoading = false; notifyListeners(); _errorMessage = null; notifyListeners(); } Future loadMoreTenders() async { if (_isLoadingMore || !_hasMoreData) { return; } _isLoadingMore = true; _errorMessage = null; notifyListeners(); _currentOffset += _pageSize; final result = await _tendersRepository.getTenders( limit: _pageSize, offset: _currentOffset, query: _searchQuery, ); switch (result) { case Ok(): 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(): _errorMessage = result.error.toString(); _currentOffset -= _pageSize; // Revert offset on error break; } _isLoadingMore = false; notifyListeners(); _errorMessage = null; notifyListeners(); } Future getTenderFeedback(String tenderId) async { // _isLoading = true; _errorMessage = null; notifyListeners(); final result = await _tendersRepository.getTenderFeedback( tenderId: tenderId, ); switch (result) { case Ok(): _feedbackData = result.value.data; // Add or update _feedbackData in feedbacks list by tender id if (_feedbackData != null) { final index = feedbacks.indexWhere( (f) => f.tender == _feedbackData!.tender, ); if (index != -1) { feedbacks[index] = _feedbackData!; log('Updated feedback for tender: ${_feedbackData!.tender}'); } else { feedbacks.add(_feedbackData!); log('Added new feedback for tender: ${_feedbackData!.tender}'); } log('Total feedbacks: ${feedbacks.length}'); } break; case Error(): _errorMessage = result.error.toString(); break; } // _isLoading = false; notifyListeners(); _errorMessage = null; notifyListeners(); } Future getTenderApprovals(String tenderId) async { _errorMessage = null; notifyListeners(); final result = await _tendersRepository.getTenderApprovals( tenderId: tenderId, ); switch (result) { case Ok(): _tenderApprovalData = result.value.data; // Add or update _tenderApprovalData in tenderApprovals list by tender id if (_tenderApprovalData != null) { final index = tenderApprovals.indexWhere( (a) => a.tenderId == _tenderApprovalData!.tenderId, ); if (index != -1) { tenderApprovals[index] = _tenderApprovalData!; log( 'Updated approval for tender: ${_tenderApprovalData!.tenderId}', ); } else { tenderApprovals.add(_tenderApprovalData!); log( 'Added new approval for tender: ${_tenderApprovalData!.tenderId}', ); } log('Total approvals: ${tenderApprovals.length}'); } break; case Error(): // Remove the tender from tenderApprovals if an error occurs for this tenderId tenderApprovals.removeWhere((a) => a.tenderId == tenderId); _errorMessage = result.error.toString(); break; } notifyListeners(); _errorMessage = null; notifyListeners(); } Future 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.tender == 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 tender: tenderId, 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(): _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.tender == tenderId); } break; case Error(): // Revert optimistic update on error if (existingFeedback == null) { // Remove the optimistic feedback we added feedbacks.removeWhere((f) => f.tender == tenderId); } else { // Restore the original feedback - find current index and update, or add if not found final currentIndex = feedbacks.indexWhere( (f) => f.tender == tenderId, ); if (currentIndex != -1) { feedbacks[currentIndex] = existingFeedback; } else { feedbacks.add(existingFeedback); } } _feedbackErrorMessage = result.error.toString(); break; } notifyListeners(); _feedbackErrorMessage = null; notifyListeners(); } Future toggleApprovals({ required String tenderId, required String submissionMode, required String status, }) async { _tenderApprovalErrorMessage = null; // Get current approval for this tender final existingApproval = getTenderApprovalForTender(tenderId); final existingApprovalIndex = tenderApprovals.indexWhere( (a) => a.tenderId == tenderId, ); // Create optimistic approval data TenderApprovalsData? optimisticApproval; if (existingApproval == null) { // No existing approval - create new optimistic approval optimisticApproval = TenderApprovalsData( id: null, // Will be filled by server status: status, tenderId: tenderId, companyId: null, // Will be filled by server submissionMode: submissionMode, createdAt: DateTime.now().millisecondsSinceEpoch, tender: null, ); tenderApprovals.add(optimisticApproval); } else { // Existing approval - update optimistically if (existingApproval.status == status) { // Same status - remove approval (toggle off) if (existingApprovalIndex != -1) { tenderApprovals.removeAt(existingApprovalIndex); } optimisticApproval = null; } else { // Different status - update to new status optimisticApproval = existingApproval.copyWith( status: status, submissionMode: submissionMode, ); if (existingApprovalIndex != -1) { tenderApprovals[existingApprovalIndex] = optimisticApproval; } } } // Notify listeners immediately for optimistic update notifyListeners(); // Call API final result = await _tendersRepository.toggleApprovals( tenderId: tenderId, submissionMode: submissionMode, status: status, ); switch (result) { case Ok(): _tenderApprovalToggleData = result.value.data; if (_tenderApprovalToggleData != null) { // Convert TenderApprovalsToggleData to TenderApprovalsData final approvalData = TenderApprovalsData( id: _tenderApprovalToggleData!.id, status: _tenderApprovalToggleData!.status!, tenderId: _tenderApprovalToggleData!.tenderId, companyId: _tenderApprovalToggleData!.companyId, submissionMode: (_tenderApprovalToggleData!.submissionMode?.isNotEmpty == true) ? _tenderApprovalToggleData!.submissionMode : TenderSubmissionMode.selfApply.value, createdAt: _tenderApprovalToggleData!.createdAt, tender: null, // Tender data is not included in toggle response ); final index = tenderApprovals.indexWhere( (a) => a.tenderId == approvalData.tenderId, ); if (index != -1) { tenderApprovals[index] = approvalData; } else { tenderApprovals.add(approvalData); } } else { // Server returned null data - remove approval tenderApprovals.removeWhere((a) => a.tenderId == tenderId); } break; case Error(): // Revert optimistic update on error if (existingApproval == null) { // Remove the optimistic approval we added tenderApprovals.removeWhere((a) => a.tenderId == tenderId); } else { // Restore the original approval - find current index and update, or add if not found final currentIndex = tenderApprovals.indexWhere( (a) => a.tenderId == tenderId, ); if (currentIndex != -1) { tenderApprovals[currentIndex] = existingApproval; } else { tenderApprovals.add(existingApproval); } } _tenderApprovalErrorMessage = result.error.toString(); break; } notifyListeners(); _tenderApprovalErrorMessage = null; notifyListeners(); } void updateHome() { _homeViewModel.init(); } /// 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(); } /// Search tenders with query Future searchTenders(String query) async { if (query.trim().isEmpty) { await getTendersDesktop(); } else { await getTendersDesktop(query: query.trim()); } } /// Clear all data (tenders, feedbacks, and approvals) void clearAll() { clearTenders(); clearFeedbacks(); tenderApprovals.clear(); _tenderApprovalData = null; _tenderApprovalToggleData = null; _tenderApprovalErrorMessage = null; _searchQuery = null; 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, ); switch (result) { case Ok(): _tendersResponse = result.value; break; case Error(): _errorMessage = result.error.toString(); break; } notifyListeners(); } }