Merge pull request 'added submit and reject for liked tenders' (#99) from liked_tenders_approval into main

Reviewed-on: https://repo.ravanertebat.com/TM/tm_app/pulls/99
This commit is contained in:
a.ghabeli
2025-09-06 10:32:27 +03:30
4 changed files with 252 additions and 93 deletions
+2 -1
View File
@@ -16,7 +16,8 @@ class AppConfig {
// // Handle different platforms for local development // // Handle different platforms for local development
if (kIsWeb) { if (kIsWeb) {
// For web, use localhost // For web, use localhost
return 'https://app.opplens.com'; // return 'https://app.opplens.com';
return 'http://localhost:8081';
} else { } else {
// For Android emulator, use 10.0.2.2 (special IP for host machine) // For Android emulator, use 10.0.2.2 (special IP for host machine)
return 'http://10.0.2.2:8081'; return 'http://10.0.2.2:8081';
+2 -2
View File
@@ -17,7 +17,7 @@ void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
// Initialize the logger // Initialize the logger
appLogger.init(); appLogger.init();
@@ -55,7 +55,7 @@ class _MyAppState extends State<MyApp> {
final themeProvider = context.watch<ThemeProvider>(); final themeProvider = context.watch<ThemeProvider>();
return MaterialApp.router( return MaterialApp.router(
title: 'Flutter Demo', title: 'OppLens',
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
scrollBehavior: MaterialScrollBehavior().copyWith( scrollBehavior: MaterialScrollBehavior().copyWith(
dragDevices: { dragDevices: {
+164 -55
View File
@@ -4,6 +4,7 @@ import 'package:tm_app/data/repositories/liked_tenders_repository.dart';
import 'package:tm_app/data/repositories/tenders_repository.dart'; import 'package:tm_app/data/repositories/tenders_repository.dart';
import 'package:tm_app/data/services/model/liked_tender_feedback/liked_tender_feedback.dart'; import 'package:tm_app/data/services/model/liked_tender_feedback/liked_tender_feedback.dart';
import 'package:tm_app/data/services/model/liked_tenders_response/liked_tenders_response.dart'; import 'package:tm_app/data/services/model/liked_tenders_response/liked_tenders_response.dart';
import 'package:tm_app/data/services/model/tender_approvals_toggle_response/tender_approvals_toggle_response.dart';
import '../data/services/model/feedback_response/feedback_response.dart'; import '../data/services/model/feedback_response/feedback_response.dart';
import '../data/services/model/tender_approvals_by_id_response/tender_approvals_by_id_response.dart'; import '../data/services/model/tender_approvals_by_id_response/tender_approvals_by_id_response.dart';
@@ -34,6 +35,18 @@ class LikedTendersViewModel with ChangeNotifier {
int currentPage = 1; int currentPage = 1;
// Approval status properties
bool approvalStatus = false;
String _status = '';
String? _errorMessage;
bool _isSubmitApprovalLoading = false;
bool _isRejectApprovalLoading = false;
String get status => _status;
String? get approvalErrorMessage => _errorMessage;
bool get isSubmitApprovalLoading => _isSubmitApprovalLoading;
bool get isRejectApprovalLoading => _isRejectApprovalLoading;
Future<void> getLikedTenders({bool reset = false}) async { Future<void> getLikedTenders({bool reset = false}) async {
if (!reset && (isLoadingMore || !hasMore)) { if (!reset && (isLoadingMore || !hasMore)) {
return; return;
@@ -199,61 +212,157 @@ class LikedTendersViewModel with ChangeNotifier {
return tenderApprovals.any((a) => a.tenderId == tenderId); return tenderApprovals.any((a) => a.tenderId == tenderId);
} }
// Future<void> submitTenderApproval({ /// Get approval status for a tender safely
// required String tenderId, String? getApprovalStatusForTenderId(String tenderId) {
// required String submissionMode, try {
// }) async { final approval = tenderApprovals.firstWhere(
(a) => a.tenderId == tenderId,
);
return approval.status;
} catch (e) {
return null;
}
}
// errorMessage = null; /// Check if tender is submitted
// notifyListeners(); bool isTenderSubmitted(String tenderId) {
// final result = await _tendersRepository.toggleApprovals( return getApprovalStatusForTenderId(tenderId) == 'submitted';
// tenderId: tenderId, }
// status: 'submitted',
// submissionMode: submissionMode,
// );
// switch (result) {
// case Ok<TenderApprovalsToggleResponse>():
// approvalStatus = true;
// _status = result.value.data?.status ?? '';
// break;
// case Error<TenderApprovalsToggleResponse>():
// _errorMessage = result.error.toString();
// approvalStatus = false;
// break;
// }
// _isSubmitApprovalLoading = false;
// notifyListeners();
// _errorMessage = null;
// approvalStatus = false;
// notifyListeners();
// }
// Future<void> rejectTenderApproval({ /// Check if tender is rejected
// required String tenderId, bool isTenderRejected(String tenderId) {
// required String submissionMode, return getApprovalStatusForTenderId(tenderId) == 'rejected';
// }) async { }
// _isRejectApprovalLoading = true;
// _errorMessage = null; Future<void> submitTenderApproval({
// notifyListeners(); required String tenderId,
// final result = await _tendersRepository.toggleApprovals( required String submissionMode,
// tenderId: tenderId, }) async {
// status: 'rejected', _isSubmitApprovalLoading = true;
// submissionMode: submissionMode, _errorMessage = null;
// ); notifyListeners();
// switch (result) {
// case Ok<TenderApprovalsToggleResponse>(): // Store original state for rollback
// approvalStatus = true; final originalApprovals = List<TenderApprovalsData>.of(tenderApprovals);
// _status = result.value.data?.status ?? '';
// break; // Check current approval status and update optimistically
// case Error<TenderApprovalsToggleResponse>(): final existingApproval = getApprovalStatusForTender(tenderId);
// _errorMessage = result.error.toString();
// approvalStatus = false; if (existingApproval != null) {
// break; if (existingApproval.status == 'submitted') {
// } // Remove from list if already submitted
// _isRejectApprovalLoading = false; tenderApprovals.removeWhere((a) => a.tenderId == tenderId);
// notifyListeners(); } else if (existingApproval.status == 'rejected') {
// _errorMessage = null; // Change rejected to submitted
// approvalStatus = false; final updatedApproval = existingApproval.copyWith(status: 'submitted');
// notifyListeners(); final index = tenderApprovals.indexWhere((a) => a.tenderId == tenderId);
// } if (index != -1) {
tenderApprovals[index] = updatedApproval;
}
}
} else {
// Add new approval with submitted status
final newApproval = TenderApprovalsData(
id: null,
status: 'submitted',
tenderId: tenderId,
companyId: null,
submissionMode: submissionMode,
createdAt: null,
tender: null,
);
tenderApprovals.add(newApproval);
}
notifyListeners();
// Make server request
final result = await _tendersRepository.toggleApprovals(
tenderId: tenderId,
status: 'submitted',
submissionMode: submissionMode,
);
switch (result) {
case Ok<TenderApprovalsToggleResponse>():
approvalStatus = true;
_status = result.value.data?.status ?? '';
break;
case Error<TenderApprovalsToggleResponse>():
// Rollback changes on error
tenderApprovals = originalApprovals;
_errorMessage = result.error.toString();
approvalStatus = false;
break;
}
_isSubmitApprovalLoading = false;
notifyListeners();
}
Future<void> rejectTenderApproval({
required String tenderId,
required String submissionMode,
}) async {
_isRejectApprovalLoading = true;
_errorMessage = null;
notifyListeners();
// Store original state for rollback
final originalApprovals = List<TenderApprovalsData>.of(tenderApprovals);
// Check current approval status and update optimistically
final existingApproval = getApprovalStatusForTender(tenderId);
if (existingApproval != null) {
if (existingApproval.status == 'rejected') {
// Remove from list if already rejected
tenderApprovals.removeWhere((a) => a.tenderId == tenderId);
} else if (existingApproval.status == 'submitted') {
// Change submitted to rejected
final updatedApproval = existingApproval.copyWith(status: 'rejected');
final index = tenderApprovals.indexWhere((a) => a.tenderId == tenderId);
if (index != -1) {
tenderApprovals[index] = updatedApproval;
}
}
} else {
// Add new approval with rejected status
final newApproval = TenderApprovalsData(
id: null,
status: 'rejected',
tenderId: tenderId,
companyId: null,
submissionMode: submissionMode,
createdAt: null,
tender: null,
);
tenderApprovals.add(newApproval);
}
notifyListeners();
// Make server request
final result = await _tendersRepository.toggleApprovals(
tenderId: tenderId,
status: 'rejected',
submissionMode: submissionMode,
);
switch (result) {
case Ok<TenderApprovalsToggleResponse>():
approvalStatus = true;
_status = result.value.data?.status ?? '';
break;
case Error<TenderApprovalsToggleResponse>():
// Rollback changes on error
tenderApprovals = originalApprovals;
_errorMessage = result.error.toString();
approvalStatus = false;
break;
}
_isRejectApprovalLoading = false;
notifyListeners();
}
} }
@@ -1,4 +1,5 @@
import 'package:country_flags/country_flags.dart'; import 'package:country_flags/country_flags.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -10,6 +11,8 @@ import 'package:tm_app/view_models/liked_tenders_view_model.dart';
import 'package:tm_app/views/home/strings/home_strings.dart'; import 'package:tm_app/views/home/strings/home_strings.dart';
import '../../../data/services/model/tender_approvals_data/tender_approvals_data.dart'; import '../../../data/services/model/tender_approvals_data/tender_approvals_data.dart';
import '../../shared/select_submission_bottom_sheet.dart';
import '../../shared/select_submission_dialog.dart';
class LikedListItem extends StatelessWidget { class LikedListItem extends StatelessWidget {
const LikedListItem({ const LikedListItem({
@@ -32,7 +35,6 @@ class LikedListItem extends StatelessWidget {
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
border: Border.all(color: AppColors.grey30), border: Border.all(color: AppColors.grey30),
), ),
margin: EdgeInsetsDirectional.only(end: 16.0.w()),
width: double.infinity, width: double.infinity,
height: 250.0.h(), height: 250.0.h(),
child: Padding( child: Padding(
@@ -172,52 +174,99 @@ class LikedListItem extends StatelessWidget {
), ),
), ),
Spacer(), Spacer(),
Container( InkWell(
width: 32.0.w(), onTap: () {
height: 32.0.h(), viewModel.rejectTenderApproval(
decoration: BoxDecoration( tenderId: tender.id!,
shape: BoxShape.circle, submissionMode: 'self-apply',
border: Border.all( );
},
child: Container(
width: 32.0.w(),
height: 32.0.h(),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color:
viewModel.isTenderRejected(tender.id!)
? AppColors.errorColor
: AppColors.grey60,
width: 1.5.w(),
),
),
alignment: Alignment.center,
child: Icon(
Icons.close,
color: color:
approval?.status == 'rejected' viewModel.isTenderRejected(tender.id!)
? AppColors.errorColor ? AppColors.errorColor
: AppColors.grey60, : AppColors.grey60,
width: 1.5.w(), size: 16,
), ),
), ),
alignment: Alignment.center,
child: Icon(
Icons.close,
color:
approval?.status == 'rejected'
? AppColors.errorColor
: AppColors.grey60,
size: 16,
),
), ),
SizedBox(width: 30.0.w()), SizedBox(width: 30.0.w()),
Container( InkWell(
width: 32.0.w(), onTap: () {
height: 32.0.h(), if (viewModel.isTenderSubmitted(tender.id!)) {
decoration: BoxDecoration( viewModel.submitTenderApproval(
shape: BoxShape.circle, tenderId: tender.id!,
border: Border.all( submissionMode: 'self-apply',
);
} else {
kIsWeb
? showDialog(
context: context,
builder: (context) {
return SelectSubmissionDialog(
onConfirm: (value) {
viewModel.submitTenderApproval(
tenderId: tender.id!,
submissionMode: value,
);
},
);
},
)
: showModalBottomSheet(
isDismissible: true,
context: context,
builder: (context) {
return SelectSubmissionBottomSheet(
onConfirm: (value) {
viewModel.submitTenderApproval(
tenderId: tender.id!,
submissionMode: value,
);
},
);
},
);
}
},
child: Container(
width: 32.0.w(),
height: 32.0.h(),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color:
viewModel.isTenderSubmitted(tender.id!)
? AppColors.successColor
: AppColors.grey60,
width: 1.5.w(),
),
),
alignment: Alignment.center,
child: Icon(
Icons.check,
color: color:
approval?.status == 'submitted' viewModel.isTenderSubmitted(tender.id!)
? AppColors.successColor ? AppColors.successColor
: AppColors.grey60, : AppColors.grey60,
width: 1.5.w(), size: 16,
), ),
), ),
alignment: Alignment.center,
child: Icon(
Icons.check,
color:
approval?.status == 'submitted'
? AppColors.successColor
: AppColors.grey60,
size: 16,
),
), ),
], ],
), ),