Feat: fix bug after implementation rejected status
This commit is contained in:
parent
08331d69ca
commit
368788c85a
|
@ -638,7 +638,7 @@
|
|||
"languageVersion": "3.4"
|
||||
}
|
||||
],
|
||||
"generated": "2025-04-27T19:47:24.712851Z",
|
||||
"generated": "2025-04-29T13:17:45.754727Z",
|
||||
"generator": "pub",
|
||||
"generatorVersion": "3.5.0",
|
||||
"flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0",
|
||||
|
|
|
@ -155,67 +155,6 @@ class TransactionPorterRepositoryImpl implements TransactionPorterRepository {
|
|||
|
||||
return controller.stream;
|
||||
}
|
||||
// @override
|
||||
// Future<PorterTransactionModel?> getTransactionById(String transactionId) async {
|
||||
// try {
|
||||
// log('Fetching transaction by ID from Firestore: $transactionId');
|
||||
|
||||
// // Coba cari langsung dengan transactionId di Firestore
|
||||
// final docSnapshot = await _firestore.collection('porterTransactions').doc(transactionId).get();
|
||||
|
||||
// if (docSnapshot.exists) {
|
||||
// log('Transaction found in Firestore with data: ${docSnapshot.data()}');
|
||||
// final data = docSnapshot.data();
|
||||
// if (data != null) {
|
||||
// return PorterTransactionModel.fromJson(data, transactionId);
|
||||
// }
|
||||
// } else {
|
||||
// log('Transaction not found in Firestore with direct ID: $transactionId');
|
||||
// }
|
||||
|
||||
// // Jika tidak ada di Firestore, coba cari dengan format "ticketId-transactionId"
|
||||
// if (transactionId.contains('-')) {
|
||||
// log('Trying with direct ID format: $transactionId');
|
||||
// final combinedDocSnapshot = await _firestore.collection('porterTransactions').doc(transactionId).get();
|
||||
|
||||
// if (combinedDocSnapshot.exists) {
|
||||
// log('Transaction found in Firestore with combined ID');
|
||||
// final data = combinedDocSnapshot.data();
|
||||
// if (data != null) {
|
||||
// return PorterTransactionModel.fromJson(data, transactionId);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// log('Transaction not found in Firestore with any ID format. Final attempt with Realtime DB...');
|
||||
|
||||
// // Mencoba cari di semua porterTransactions nodes di Realtime DB (sebagai fallback)
|
||||
// // Ini inefficient, tapi bisa membantu menemukan data jika struktur tidak konsisten
|
||||
// final dbRef = _database.ref().child('porterTransactions');
|
||||
// final dataSnapshot = await dbRef.get();
|
||||
|
||||
// if (dataSnapshot.exists && dataSnapshot.value is Map) {
|
||||
// final allData = dataSnapshot.value as Map<dynamic, dynamic>;
|
||||
|
||||
// // Iterasi semua porter IDs
|
||||
// for (var porterId in allData.keys) {
|
||||
// final porterData = allData[porterId];
|
||||
|
||||
// if (porterData is Map<dynamic, dynamic> && porterData.containsKey(transactionId)) {
|
||||
// log('Transaction found in Realtime DB under porter: $porterId');
|
||||
// final transactionData = porterData[transactionId] as Map<dynamic, dynamic>;
|
||||
// return PorterTransactionModel.fromJson(transactionData, transactionId);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// log('Transaction not found with ID: $transactionId in any database');
|
||||
// return null;
|
||||
// } catch (e) {
|
||||
// log('Error fetching transaction by ID: $e');
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
@override
|
||||
Future<PorterTransactionModel?> getTransactionById(String transactionId) async {
|
||||
|
@ -282,28 +221,31 @@ class TransactionPorterRepositoryImpl implements TransactionPorterRepository {
|
|||
final snapshot = await _database.ref().child('porterTransactions/$transactionId').get();
|
||||
|
||||
if (snapshot.exists && snapshot.value != null) {
|
||||
final result = Map<String, dynamic>.from(snapshot.value as Map);
|
||||
// Safe cast: Konversi Map<Object?, Object?> ke Map<String, dynamic>
|
||||
final Map<dynamic, dynamic> rawData = snapshot.value as Map;
|
||||
final Map<String, dynamic> result = {};
|
||||
|
||||
// Proses setiap entry untuk memastikan keys sebagai string
|
||||
rawData.forEach((key, value) {
|
||||
String keyStr = key.toString();
|
||||
|
||||
// Perlakukan rejectionInfo secara khusus
|
||||
if (keyStr == 'rejectionInfo' && value is Map) {
|
||||
Map<String, dynamic> rejectionMap = {};
|
||||
(value as Map).forEach((rKey, rValue) {
|
||||
rejectionMap[rKey.toString()] = rValue;
|
||||
});
|
||||
result[keyStr] = rejectionMap;
|
||||
} else {
|
||||
result[keyStr] = value;
|
||||
}
|
||||
});
|
||||
|
||||
log('[TransactionPorterRepo] Data transaksi ditemukan untuk ID: $transactionId');
|
||||
return result;
|
||||
}
|
||||
|
||||
log('[TransactionPorterRepo] Data transaksi TIDAK ditemukan untuk ID: $transactionId');
|
||||
|
||||
// Jika tidak ditemukan di lokasi langsung, coba cari di semua transaksi
|
||||
final allTransactions = await _database.ref().child('porterTransactions').get();
|
||||
if (allTransactions.exists && allTransactions.value != null) {
|
||||
final Map<dynamic, dynamic> allData = allTransactions.value as Map<dynamic, dynamic>;
|
||||
|
||||
// Transactionid mungkin tersimpan sebagai child dari porter ID lain
|
||||
for (var key in allData.keys) {
|
||||
if (key.toString() == transactionId) {
|
||||
final result = Map<String, dynamic>.from(allData[key] as Map);
|
||||
log('[TransactionPorterRepo] Data transaksi ditemukan di level atas');
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (e) {
|
||||
log('[TransactionPorterRepo] Error mengambil data transaksi porter: $e');
|
||||
|
@ -338,6 +280,96 @@ class TransactionPorterRepositoryImpl implements TransactionPorterRepository {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> rejectTransaction({
|
||||
required String transactionId,
|
||||
required String reason,
|
||||
}) async {
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
try {
|
||||
log('[TransactionPorterRepo] Menolak transaksi: $transactionId dengan alasan: $reason');
|
||||
|
||||
// 1. Dapatkan data transaksi sekali dan simpan dalam variabel
|
||||
final transactionData = await getPorterTransactionById(transactionId);
|
||||
if (transactionData == null) {
|
||||
throw FirebaseException(plugin: 'TransactionPorterRepo', message: 'Transaksi tidak ditemukan');
|
||||
}
|
||||
|
||||
final String porterOnlineId = transactionData['porterOnlineId'] ?? '';
|
||||
if (porterOnlineId.isEmpty) {
|
||||
throw FirebaseException(plugin: 'TransactionPorterRepo', message: 'ID Porter tidak ditemukan pada transaksi');
|
||||
}
|
||||
|
||||
final rejectionId = DateTime.now().millisecondsSinceEpoch.toString();
|
||||
|
||||
// Data yang akan digunakan untuk menyimpan di kedua tempat (Realtime Database & Firestore)
|
||||
final rejectionData = {
|
||||
'idPassenger': transactionData['idPassenger'] ?? '',
|
||||
'kodePorter': transactionData['kodePorter'] ?? '',
|
||||
'locationPassenger': transactionData['locationPassenger'] ?? '',
|
||||
'locationPoter': transactionData['locationPorter'] ?? '',
|
||||
'porterId': porterOnlineId,
|
||||
'porterUserId': transactionData['porterUserId'] ?? '',
|
||||
'ticketId': transactionData['ticketId'] ?? '',
|
||||
'transactionId': transactionData['transactionId'] ?? '',
|
||||
'status': 'rejected',
|
||||
'reason': reason,
|
||||
'timestamp': now
|
||||
};
|
||||
|
||||
// 2. Mulai Batch untuk mengelola multiple writes dengan lebih efisien di Firestore
|
||||
final WriteBatch batch = _firestore.batch();
|
||||
final transactionRef = _firestore.collection('porterTransactions').doc(transactionId);
|
||||
final rejectionRef = _firestore.collection('porterRejections').doc(rejectionId);
|
||||
|
||||
// 3. Menyimpan riwayat penolakan di Firestore menggunakan Batch
|
||||
batch.set(rejectionRef, rejectionData);
|
||||
|
||||
// 4. Update transaksi untuk tampilan di tab "Ditolak"
|
||||
batch.update(transactionRef, {
|
||||
'updatedAt': now,
|
||||
'locationPorter': null,
|
||||
'porterOnlineId': null,
|
||||
'porterUserId': null,
|
||||
'status': 'rejected',
|
||||
'rejectionInfo': {'reason': reason, 'timestamp': now, 'status': 'rejected'},
|
||||
});
|
||||
|
||||
await _database.ref().child('porterTransactions/$transactionId').update({
|
||||
'updatedAt': now,
|
||||
'locationPorter': null,
|
||||
'porterOnlineId': null,
|
||||
'porterUserId': null,
|
||||
'status': 'rejected',
|
||||
'rejectionInfo': {'reason': reason, 'timestamp': now, 'status': 'rejected'},
|
||||
});
|
||||
|
||||
log('[Repository_impl] ID Transaction Porter: $transactionId');
|
||||
|
||||
// 5. Reset status porter supaya tersedia kembali
|
||||
final porterRef = _firestore.collection('porterOnline').doc(porterOnlineId);
|
||||
batch.update(porterRef, {
|
||||
'idTransaction': null,
|
||||
'idUser': null,
|
||||
'isAvailable': true,
|
||||
});
|
||||
|
||||
// 6. Commit batch ke Firestore
|
||||
await batch.commit();
|
||||
|
||||
log('[TransactionPorterRepo] Transaksi berhasil ditolak dan dikembalikan ke antrian di Firestore');
|
||||
|
||||
// 7. Simpan juga ke Realtime Database untuk pencatatan sementara
|
||||
await _database.ref().child('porterRejections/$rejectionId').set(rejectionData);
|
||||
|
||||
log('[TransactionPorterRepo] Data penolakan berhasil disimpan di Realtime Database');
|
||||
} catch (e) {
|
||||
log('[TransactionPorterRepo] Error menolak transaksi: ${e.toString()}', level: 4);
|
||||
throw FirebaseException(plugin: 'TransactionPorterRepo', message: 'Gagal menolak transaksi: ${e.toString()}');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> completePorterTransaction({
|
||||
required String transactionId,
|
||||
|
@ -367,126 +399,157 @@ class TransactionPorterRepositoryImpl implements TransactionPorterRepository {
|
|||
}
|
||||
}
|
||||
|
||||
// @override
|
||||
// Future<void> updateTransactionStatus({
|
||||
// required String ticketId,
|
||||
// required String transactionId,
|
||||
// required String status,
|
||||
// }) async {
|
||||
// try {
|
||||
// final now = DateTime.now();
|
||||
// log('Updating transaction status: $transactionId to $status');
|
||||
@override
|
||||
Future<String?> reassignRejectedTransaction({
|
||||
required String transactionId,
|
||||
String? newPorterId,
|
||||
}) async {
|
||||
try {
|
||||
log('[TransactionPorterRepo] Mencoba mengalihkan transaksi: $transactionId');
|
||||
|
||||
// // Update di Firestore, coba dengan transactionId langsung
|
||||
// try {
|
||||
// await _firestore.collection('porterTransactions').doc(transactionId).update({
|
||||
// 'status': status,
|
||||
// 'updatedAt': now,
|
||||
// });
|
||||
// log('Successfully updated transaction in Firestore with direct ID');
|
||||
// } catch (e) {
|
||||
// log('Failed to update Firestore with direct ID: $e');
|
||||
// 1. Dapatkan data transaksi yang ditolak
|
||||
final rejectedTransaction = await getPorterTransactionById(transactionId);
|
||||
if (rejectedTransaction == null) {
|
||||
throw FirebaseException(plugin: 'TransactionPorterRepo', message: 'Transaksi tidak ditemukan');
|
||||
}
|
||||
|
||||
// // Coba dengan format ticketId-transactionId jika direct ID gagal
|
||||
// if (ticketId.isNotEmpty) {
|
||||
// final combinedId = '$ticketId-$transactionId';
|
||||
// log('Trying update with combined ID: $combinedId');
|
||||
// Verifikasi bahwa transaksi ini memang ditolak
|
||||
final status = rejectedTransaction['status'] as String? ?? '';
|
||||
final hasRejectionInfo = rejectedTransaction.containsKey('rejectionInfo');
|
||||
|
||||
if (status != 'rejected' && !hasRejectionInfo) {
|
||||
throw FirebaseException(
|
||||
plugin: 'TransactionPorterRepo',
|
||||
message: 'Transaksi tidak dalam status ditolak'
|
||||
);
|
||||
}
|
||||
|
||||
// try {
|
||||
// await _firestore.collection('porterTransactions').doc(combinedId).update({
|
||||
// 'status': status,
|
||||
// 'updatedAt': now,
|
||||
// });
|
||||
// log('Successfully updated transaction in Firestore with combined ID');
|
||||
// } catch (e2) {
|
||||
// log('Failed to update Firestore with combined ID: $e2');
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// 2. Tandai transaksi lama sebagai sudah dialihkan
|
||||
await _database.ref().child('porterTransactions/$transactionId').update({
|
||||
'isReassigned': true,
|
||||
});
|
||||
|
||||
// // Mencari transaksi di Realtime DB
|
||||
// // Perlu memeriksa semua porter untuk menemukan transaksi yang tepat
|
||||
// final dbRef = _database.ref().child('porterTransactions');
|
||||
// final dataSnapshot = await dbRef.get();
|
||||
// 3. Cari porter yang tersedia (jika tidak ada yang ditentukan)
|
||||
String selectedPorterId = '';
|
||||
String porterUserId = '';
|
||||
String porterLocation = '';
|
||||
|
||||
if (newPorterId != null && newPorterId.isNotEmpty) {
|
||||
// Gunakan porter yang ditentukan
|
||||
final porterDoc = await _firestore.collection('porterOnline').doc(newPorterId).get();
|
||||
|
||||
if (!porterDoc.exists) {
|
||||
throw FirebaseException(plugin: 'TransactionPorterRepo', message: 'Porter yang ditentukan tidak ditemukan');
|
||||
}
|
||||
|
||||
final porterData = porterDoc.data();
|
||||
if (porterData == null || porterData['isAvailable'] != true) {
|
||||
throw FirebaseException(plugin: 'TransactionPorterRepo', message: 'Porter yang ditentukan tidak tersedia');
|
||||
}
|
||||
|
||||
selectedPorterId = newPorterId;
|
||||
porterUserId = porterData['userId'] ?? '';
|
||||
porterLocation = porterData['location'] ?? '';
|
||||
} else {
|
||||
// Cari porter yang tersedia
|
||||
final availablePortersSnapshot = await _firestore
|
||||
.collection('porterOnline')
|
||||
.where('isAvailable', isEqualTo: true)
|
||||
.limit(1)
|
||||
.get();
|
||||
|
||||
if (availablePortersSnapshot.docs.isEmpty) {
|
||||
throw FirebaseException(plugin: 'TransactionPorterRepo', message: 'Tidak ada porter tersedia saat ini');
|
||||
}
|
||||
|
||||
final porterDoc = availablePortersSnapshot.docs.first;
|
||||
final porterData = porterDoc.data();
|
||||
|
||||
selectedPorterId = porterDoc.id;
|
||||
porterUserId = porterData['userId'] ?? '';
|
||||
porterLocation = porterData['location'] ?? '';
|
||||
}
|
||||
|
||||
log('[TransactionPorterRepo] Porter baru dipilih: $selectedPorterId (userId: $porterUserId)');
|
||||
|
||||
// if (dataSnapshot.exists && dataSnapshot.value is Map) {
|
||||
// final allData = dataSnapshot.value as Map<dynamic, dynamic>;
|
||||
// 4. Buat transaksi baru dengan referensi ke transaksi lama
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
final newTransactionId = '${now}_${100000 + (now % 900000)}';
|
||||
|
||||
// Data untuk transaksi baru
|
||||
final newTransactionData = <String, dynamic>{
|
||||
'transactionId': rejectedTransaction['transactionId'] ?? '',
|
||||
'ticketId': rejectedTransaction['ticketId'] ?? '',
|
||||
'idPassenger': rejectedTransaction['idPassenger'] ?? '',
|
||||
'locationPassenger': rejectedTransaction['locationPassenger'] ?? '',
|
||||
'kodePorter': rejectedTransaction['kodePorter'] ?? '',
|
||||
'status': 'pending',
|
||||
'createdAt': now,
|
||||
'porterOnlineId': selectedPorterId,
|
||||
'porterUserId': porterUserId,
|
||||
'locationPorter': porterLocation,
|
||||
'previousTransactionId': transactionId, // Referensi ke transaksi yang ditolak
|
||||
};
|
||||
|
||||
// Jika ada rejectionInfo, tambahkan alasan pengalihan
|
||||
if (hasRejectionInfo && rejectedTransaction['rejectionInfo'] is Map) {
|
||||
final rejectionInfoMap = rejectedTransaction['rejectionInfo'] as Map<dynamic, dynamic>;
|
||||
final reason = rejectionInfoMap['reason']?.toString() ?? 'Ditolak oleh porter sebelumnya';
|
||||
|
||||
newTransactionData['reassignmentInfo'] = {
|
||||
'previousTransactionId': transactionId,
|
||||
'reason': reason,
|
||||
'timestamp': now,
|
||||
};
|
||||
}
|
||||
|
||||
// // Iterasi semua porter IDs
|
||||
// for (var porterId in allData.keys) {
|
||||
// final porterData = allData[porterId];
|
||||
// 5. Simpan transaksi baru ke Realtime Database dan Firestore
|
||||
final batch = _firestore.batch();
|
||||
|
||||
// Ke Realtime Database
|
||||
await _database.ref().child('porterTransactions/$newTransactionId').set(newTransactionData);
|
||||
|
||||
// Firestore - update porter
|
||||
final porterRef = _firestore.collection('porterOnline').doc(selectedPorterId);
|
||||
batch.update(porterRef, {
|
||||
'isAvailable': false,
|
||||
'idTransaction': newTransactionId,
|
||||
'idUser': rejectedTransaction['idPassenger'] ?? '',
|
||||
});
|
||||
|
||||
// Commit Firestore batch
|
||||
await batch.commit();
|
||||
|
||||
// if (porterData is Map<dynamic, dynamic> && porterData.containsKey(transactionId)) {
|
||||
// log('Found transaction in Realtime DB under porter: $porterId');
|
||||
// 6. Tambahkan ke porterHistory
|
||||
if (porterUserId.isNotEmpty) {
|
||||
await _database.ref().child('porterHistory/$porterUserId/$newTransactionId').set({
|
||||
'timestamp': now,
|
||||
'transactionId': newTransactionId,
|
||||
});
|
||||
}
|
||||
|
||||
// Tambahkan ke history porterId juga
|
||||
if (selectedPorterId.isNotEmpty) {
|
||||
await _database.ref().child('porterHistory/$selectedPorterId/$newTransactionId').set({
|
||||
'timestamp': now,
|
||||
'transactionId': newTransactionId,
|
||||
});
|
||||
}
|
||||
|
||||
// // Update status di Realtime DB
|
||||
// final transactionRef =
|
||||
// _database.ref().child('porterTransactions').child(porterId.toString()).child(transactionId);
|
||||
// 7. Tambahkan ke passengerHistory
|
||||
final passengerId = rejectedTransaction['idPassenger'] ?? '';
|
||||
if (passengerId.isNotEmpty) {
|
||||
await _database.ref().child('passengerHistory/$passengerId/$newTransactionId').set({
|
||||
'timestamp': now,
|
||||
'transactionId': newTransactionId,
|
||||
});
|
||||
}
|
||||
|
||||
// await transactionRef.update({
|
||||
// 'status': status,
|
||||
// 'updatedAt': now.millisecondsSinceEpoch,
|
||||
// });
|
||||
|
||||
// log('Successfully updated transaction status in Realtime DB');
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// log('Status update process completed for transaction: $transactionId');
|
||||
// } catch (e) {
|
||||
// log('Error updating transaction status: $e');
|
||||
// throw Exception('Failed to update transaction status: $e');
|
||||
// }
|
||||
// }
|
||||
|
||||
// @override
|
||||
// Future<void> completePorterTransaction({
|
||||
// required String ticketId,
|
||||
// required String transactionId,
|
||||
// required String porterOnlineId,
|
||||
// }) async {
|
||||
// try {
|
||||
// final now = DateTime.now();
|
||||
// log('Completing porter transaction: $transactionId and updating porterOnline: $porterOnlineId');
|
||||
|
||||
// await updateTransactionStatus(
|
||||
// ticketId: ticketId,
|
||||
// transactionId: transactionId,
|
||||
// status: 'selesai',
|
||||
// );
|
||||
|
||||
// try {
|
||||
// await _firestore.collection('porterOnline').doc(porterOnlineId).update({
|
||||
// 'idTransaction': null,
|
||||
// 'idUser': null,
|
||||
// 'isAvailable': true,
|
||||
// 'onlineAt': now,
|
||||
// });
|
||||
// log('Successfully updated porterOnline data in Firestore');
|
||||
// } catch (e) {
|
||||
// log('Error updating porterOnline data in Firestore: $e');
|
||||
// throw Exception('Gagal mengupdate data porterOnline di Firestore: $e');
|
||||
// }
|
||||
|
||||
// // try {
|
||||
// // final porterOnlineRef = _database.ref().child('porterOnline').child(porterOnlineId);
|
||||
// // await porterOnlineRef.update({
|
||||
// // 'idTransaction': null,
|
||||
// // 'idUser': null,
|
||||
// // 'isAvailable': true,
|
||||
// // 'onlineAt': now.millisecondsSinceEpoch,
|
||||
// // });
|
||||
// // log('Successfully updated porterOnline data in Realtime DB');
|
||||
// // } catch (e) {
|
||||
// // log('Error updating porterOnline data in Realtime DB: $e');
|
||||
// // }
|
||||
|
||||
// log('Porter transaction completion process finished successfully');
|
||||
// } catch (e) {
|
||||
// log('Error completing porter transaction: $e');
|
||||
// throw Exception('Gagal menyelesaikan transaksi porter: $e');
|
||||
// }
|
||||
// }
|
||||
log('[TransactionPorterRepo] Transaksi berhasil dialihkan ke porter baru. ID baru: $newTransactionId');
|
||||
return newTransactionId;
|
||||
} catch (e) {
|
||||
log('[TransactionPorterRepo] Error mengalihkan transaksi: $e', level: 4);
|
||||
throw FirebaseException(plugin: 'TransactionPorterRepo', message: 'Gagal mengalihkan transaksi: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// domain/models/transaction_porter_model.dart
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
|
||||
class PorterTransactionModel {
|
||||
|
@ -15,6 +16,7 @@ class PorterTransactionModel {
|
|||
final String transactionId;
|
||||
final DateTime createdAt;
|
||||
final DateTime? updatedAt;
|
||||
final RejectionInfo? rejectionInfo;
|
||||
|
||||
PorterTransactionModel({
|
||||
required this.id,
|
||||
|
@ -29,25 +31,60 @@ class PorterTransactionModel {
|
|||
required this.transactionId,
|
||||
required this.createdAt,
|
||||
this.updatedAt,
|
||||
}) : normalizedStatus = _normalizeStatus(status);
|
||||
this.rejectionInfo,
|
||||
}) : normalizedStatus = _normalizeStatus(status, rejectionInfo);
|
||||
|
||||
static String _normalizeStatus(String status, RejectionInfo? rejectionInfo) {
|
||||
if (rejectionInfo != null) {
|
||||
return "rejected";
|
||||
}
|
||||
|
||||
// Simplify status normalization since we only have 3 possible statuses
|
||||
static String _normalizeStatus(String status) {
|
||||
final lowercaseStatus = status.toLowerCase().trim();
|
||||
|
||||
// Status standar
|
||||
if (lowercaseStatus == "pending") return "pending";
|
||||
if (lowercaseStatus == "proses") return "proses";
|
||||
if (lowercaseStatus == "selesai") return "selesai";
|
||||
if (lowercaseStatus == "rejected" || lowercaseStatus == "ditolak") return "rejected";
|
||||
|
||||
// Default fallback logic for potentially inconsistent data
|
||||
// Logic fallback untuk menangani potensi status yang tidak konsisten
|
||||
if (lowercaseStatus.contains("pend")) return "pending";
|
||||
if (lowercaseStatus.contains("pros")) return "proses";
|
||||
if (lowercaseStatus.contains("sele") || lowercaseStatus.contains("done")) return "selesai";
|
||||
if (lowercaseStatus.contains("rej") || lowercaseStatus.contains("tol")) return "rejected";
|
||||
|
||||
return "pending"; // Default to pending if unknown status
|
||||
return "pending";
|
||||
}
|
||||
|
||||
factory PorterTransactionModel.fromJson(Map<dynamic, dynamic> json, String id) {
|
||||
RejectionInfo? rejectionInfo;
|
||||
if (json.containsKey('rejectionInfo')) {
|
||||
try {
|
||||
final rejectionData = json['rejectionInfo'];
|
||||
if (rejectionData is Map) {
|
||||
final Map<String, dynamic> safeRejectionData =
|
||||
Map<String, dynamic>.from(rejectionData.map((key, value) => MapEntry(key.toString(), value)));
|
||||
|
||||
rejectionInfo = RejectionInfo(
|
||||
reason: safeRejectionData['reason']?.toString() ?? 'Tidak ada alasan',
|
||||
timestamp: safeRejectionData.containsKey('timestamp')
|
||||
? (safeRejectionData['timestamp'] is int
|
||||
? DateTime.fromMillisecondsSinceEpoch(safeRejectionData['timestamp'])
|
||||
: DateTime.now())
|
||||
: DateTime.now(),
|
||||
status: safeRejectionData['status']?.toString() ?? 'rejected',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
log('[Transaction Porter Model] Error parsing rejectionInfo: $e');
|
||||
rejectionInfo = RejectionInfo(
|
||||
reason: 'Data tidak valid',
|
||||
timestamp: DateTime.now(),
|
||||
status: 'rejected',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return PorterTransactionModel(
|
||||
id: id,
|
||||
kodePorter: json['kodePorter'] ?? '',
|
||||
|
@ -56,7 +93,7 @@ class PorterTransactionModel {
|
|||
locationPassenger: json['locationPassenger'] as String? ?? '',
|
||||
locationPorter: json['locationPorter'] as String? ?? '',
|
||||
porterOnlineId: json['porterOnlineId'] as String? ?? '',
|
||||
status: json['status'] as String? ?? 'Data Not Fount',
|
||||
status: json['status'] as String? ?? 'Data Not Found',
|
||||
ticketId: json['ticketId'] as String? ?? '',
|
||||
transactionId: json['transactionId'] as String? ?? '',
|
||||
createdAt: json['createdAt'] is int
|
||||
|
@ -69,6 +106,92 @@ class PorterTransactionModel {
|
|||
? DateTime.fromMillisecondsSinceEpoch(json['updatedAt'])
|
||||
: (json['updatedAt'] is DateTime ? json['updatedAt'] : null))
|
||||
: null,
|
||||
rejectionInfo: rejectionInfo,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = {
|
||||
'id': id,
|
||||
'kodePorter': kodePorter,
|
||||
'porterUserId': porterUserId,
|
||||
'idPassenger': idPassenger,
|
||||
'locationPassenger': locationPassenger,
|
||||
'locationPorter': locationPorter,
|
||||
'porterOnlineId': porterOnlineId,
|
||||
'status': status,
|
||||
'ticketId': ticketId,
|
||||
'transactionId': transactionId,
|
||||
'createdAt': createdAt.millisecondsSinceEpoch,
|
||||
};
|
||||
|
||||
if (updatedAt != null) {
|
||||
data['updatedAt'] = updatedAt!.millisecondsSinceEpoch;
|
||||
}
|
||||
|
||||
if (rejectionInfo != null) {
|
||||
data['rejectionInfo'] = rejectionInfo!.toJson();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Membuat salinan model dengan nilai baru
|
||||
PorterTransactionModel copyWith({
|
||||
String? id,
|
||||
String? kodePorter,
|
||||
String? porterUserId,
|
||||
String? idPassenger,
|
||||
String? locationPassenger,
|
||||
String? locationPorter,
|
||||
String? porterOnlineId,
|
||||
String? status,
|
||||
String? ticketId,
|
||||
String? transactionId,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
RejectionInfo? rejectionInfo,
|
||||
}) {
|
||||
return PorterTransactionModel(
|
||||
id: id ?? this.id,
|
||||
kodePorter: kodePorter ?? this.kodePorter,
|
||||
porterUserId: porterUserId ?? this.porterUserId,
|
||||
idPassenger: idPassenger ?? this.idPassenger,
|
||||
locationPassenger: locationPassenger ?? this.locationPassenger,
|
||||
locationPorter: locationPorter ?? this.locationPorter,
|
||||
porterOnlineId: porterOnlineId ?? this.porterOnlineId,
|
||||
status: status ?? this.status,
|
||||
ticketId: ticketId ?? this.ticketId,
|
||||
transactionId: transactionId ?? this.transactionId,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
rejectionInfo: rejectionInfo ?? this.rejectionInfo,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RejectionInfo {
|
||||
final String reason;
|
||||
final DateTime timestamp;
|
||||
final String status;
|
||||
|
||||
RejectionInfo({required this.reason, required this.timestamp, this.status = 'rejected'});
|
||||
|
||||
factory RejectionInfo.fromJson(Map<String, dynamic> json) {
|
||||
return RejectionInfo(
|
||||
reason: json['reason'] ?? 'Tidak ada alasan',
|
||||
timestamp: json['timestamp'] is Timestamp
|
||||
? (json['timestamp'] as Timestamp).toDate()
|
||||
: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] ?? 0),
|
||||
status: json['status'] ?? 'rejected',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'reason': reason,
|
||||
'timestamp': timestamp.millisecondsSinceEpoch,
|
||||
'status': status,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,19 +5,29 @@ abstract class TransactionPorterRepository {
|
|||
|
||||
Stream<PorterTransactionModel?> watchTransactionById(String transactionId);
|
||||
|
||||
Future<Map<String, dynamic>?> getPorterTransactionById(String transactionId);
|
||||
|
||||
Future<PorterTransactionModel?> getTransactionById(String transactionId);
|
||||
|
||||
Future<List<String>> getPorterTransactionIds(String porterId);
|
||||
|
||||
Future<Map<String, dynamic>?> getPorterTransactionById(String transactionId);
|
||||
|
||||
Future<void> updateTransactionStatus({
|
||||
required String transactionId,
|
||||
required String status,
|
||||
});
|
||||
|
||||
Future<void> rejectTransaction({
|
||||
required String transactionId,
|
||||
required String reason,
|
||||
});
|
||||
|
||||
Future<void> completePorterTransaction({
|
||||
required String transactionId,
|
||||
required String porterOnlineId,
|
||||
});
|
||||
|
||||
Future<String?> reassignRejectedTransaction({
|
||||
required String transactionId,
|
||||
String? newPorterId,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -13,6 +13,10 @@ class TransactionPorterUsecase {
|
|||
return _repository.watchTransactionById(transactionId);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> getPorterTransactionById(String transactionId) {
|
||||
return _repository.getPorterTransactionById(transactionId);
|
||||
}
|
||||
|
||||
Future<PorterTransactionModel?> getTransactionById(String transactionId) {
|
||||
return _repository.getTransactionById(transactionId);
|
||||
}
|
||||
|
@ -21,31 +25,43 @@ class TransactionPorterUsecase {
|
|||
return _repository.getPorterTransactionIds(porterId);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> getPorterTransactionById(String transactionId) {
|
||||
return _repository.getPorterTransactionById(transactionId);
|
||||
}
|
||||
|
||||
Future<void> updateTransactionStatus({
|
||||
// required String ticketId,
|
||||
required String transactionId,
|
||||
required String status,
|
||||
}) {
|
||||
return _repository.updateTransactionStatus(
|
||||
// ticketId: ticketId,
|
||||
transactionId: transactionId,
|
||||
status: status,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> rejectTransaction({
|
||||
required String transactionId,
|
||||
required String reason,
|
||||
}) {
|
||||
return _repository.rejectTransaction(
|
||||
transactionId: transactionId,
|
||||
reason: reason,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> completePorterTransaction({
|
||||
// required String ticketId,
|
||||
required String transactionId,
|
||||
required String porterOnlineId,
|
||||
}) {
|
||||
return _repository.completePorterTransaction(
|
||||
// ticketId: ticketId,
|
||||
transactionId: transactionId,
|
||||
porterOnlineId: porterOnlineId,
|
||||
);
|
||||
}
|
||||
|
||||
Future<String?> reassignRejectedTransaction({
|
||||
required String transactionId,
|
||||
String? newPorterId,
|
||||
}) {
|
||||
return _repository.reassignRejectedTransaction(
|
||||
transactionId: transactionId,
|
||||
newPorterId: newPorterId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:e_porter/_core/service/preferences_service.dart';
|
|||
import 'package:e_porter/_core/utils/snackbar/snackbar_helper.dart';
|
||||
import 'package:e_porter/domain/models/porter_queue_model.dart';
|
||||
import 'package:e_porter/presentation/controllers/porter_queue_controller.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../domain/models/transaction_porter_model.dart';
|
||||
import '../../domain/usecases/transaction_porter_usecase.dart';
|
||||
|
@ -20,6 +21,8 @@ class TransactionPorterController extends GetxController {
|
|||
final RxBool isLoading = false.obs;
|
||||
final RxString error = ''.obs;
|
||||
|
||||
final TextEditingController rejectionReasonController = TextEditingController();
|
||||
|
||||
StreamSubscription<List<PorterTransactionModel>>? _subscription;
|
||||
StreamSubscription<PorterQueueModel?>? _porterSubscription;
|
||||
|
||||
|
@ -290,6 +293,68 @@ class TransactionPorterController extends GetxController {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> rejectTransaction({
|
||||
required String transactionId,
|
||||
required String reason,
|
||||
}) async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
error.value = '';
|
||||
|
||||
log('Menolak transaksi: $transactionId dengan alasan: $reason');
|
||||
|
||||
// Proses penolakan transaksi
|
||||
await _useCase.rejectTransaction(
|
||||
transactionId: transactionId,
|
||||
reason: reason.isEmpty ? 'Tidak ada alasan' : reason,
|
||||
);
|
||||
|
||||
// Segera coba reassign ke porter lain
|
||||
try {
|
||||
log('Mencoba mengalihkan transaksi yang ditolak ke porter baru...');
|
||||
final newTransactionId = await _useCase.reassignRejectedTransaction(
|
||||
transactionId: transactionId,
|
||||
);
|
||||
|
||||
if (newTransactionId != null) {
|
||||
log('Transaksi berhasil dialihkan ke ID baru: $newTransactionId');
|
||||
SnackbarHelper.showSuccess('Berhasil', 'Transaksi dialihkan ke porter lain');
|
||||
} else {
|
||||
// Jika tidak ada porter tersedia saat ini, service di background akan mencoba lagi nanti
|
||||
log('Tidak ada porter tersedia saat ini, akan dicoba lagi nanti oleh service');
|
||||
}
|
||||
} catch (reassignError) {
|
||||
log('Error saat mencoba mengalihkan transaksi: $reassignError');
|
||||
// Tidak perlu menampilkan error ke user, service akan mencoba lagi nanti
|
||||
}
|
||||
|
||||
// Dapatkan transaksi yang diperbarui
|
||||
final updatedTransaction = await getTransactionById(transactionId);
|
||||
|
||||
// Update list transaksi yang ada dengan yang baru
|
||||
if (updatedTransaction != null) {
|
||||
final index = transactions.indexWhere((tx) => tx.id == transactionId);
|
||||
if (index >= 0) {
|
||||
transactions[index] = updatedTransaction;
|
||||
log('Transaksi di daftar utama diperbarui menjadi ditolak: $transactionId');
|
||||
} else {
|
||||
refreshTransactions();
|
||||
}
|
||||
}
|
||||
|
||||
// Reset controller alasan
|
||||
rejectionReasonController.clear();
|
||||
|
||||
SnackbarHelper.showSuccess('Berhasil', 'Transaksi berhasil ditolak');
|
||||
} catch (e) {
|
||||
log('Error menolak transaksi: $e');
|
||||
error.value = 'Gagal menolak transaksi: $e';
|
||||
SnackbarHelper.showError('Terjadi Kesalahan', 'Gagal menolak transaksi');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Metode serupa untuk completePorterTransaction
|
||||
Future<void> completePorterTransaction({
|
||||
required String transactionId,
|
||||
|
@ -365,6 +430,7 @@ class TransactionPorterController extends GetxController {
|
|||
|
||||
@override
|
||||
void onClose() {
|
||||
rejectionReasonController.dispose();
|
||||
_porterSubscription?.cancel();
|
||||
_subscription?.cancel();
|
||||
for (var subscription in _transactionWatchers.values) {
|
||||
|
@ -373,4 +439,51 @@ class TransactionPorterController extends GetxController {
|
|||
_transactionWatchers.clear();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
void showRejectionDialog(String transactionId, BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Tolak Permintaan Porter'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('Masukkan alasan penolakan:'),
|
||||
const SizedBox(height: 10),
|
||||
TextField(
|
||||
controller: rejectionReasonController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Contoh: Sedang melayani penumpang lain',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
maxLines: 3,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
rejectionReasonController.clear();
|
||||
},
|
||||
child: const Text('Batal'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final reason = rejectionReasonController.text.trim();
|
||||
Navigator.of(context).pop();
|
||||
rejectTransaction(
|
||||
transactionId: transactionId,
|
||||
reason: reason,
|
||||
);
|
||||
},
|
||||
child: const Text('Tolak'),
|
||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,16 +27,14 @@ class DetailHistoryPorterScreen extends StatefulWidget {
|
|||
class _DetailHistoryPorterScreenState extends State<DetailHistoryPorterScreen> {
|
||||
final TransactionPorterController _porterController = Get.find<TransactionPorterController>();
|
||||
final HistoryController _historyController = Get.find<HistoryController>();
|
||||
final RxBool _isLoadingTicket = false.obs;
|
||||
|
||||
PorterTransactionModel? porterTransaction;
|
||||
|
||||
late final String porterTransactionId;
|
||||
|
||||
// Formatters
|
||||
final RxBool _isLoadingTicket = false.obs;
|
||||
final DateFormat _dateFormat = DateFormat('dd MMMM yyyy', 'en_US');
|
||||
final DateFormat _timeFormat = DateFormat.jm();
|
||||
// final NumberFormat _priceFormatter = NumberFormat.decimalPattern('id_ID');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -114,6 +112,10 @@ class _DetailHistoryPorterScreenState extends State<DetailHistoryPorterScreen> {
|
|||
_buildLocationPassenger(porterTransaction),
|
||||
SizedBox(height: 20.h),
|
||||
_buildDetailsOrder(ticketTransaction),
|
||||
if (porterTransaction.normalizedStatus == 'rejected') ...[
|
||||
SizedBox(height: 20.h),
|
||||
_buildRejectionInfo(porterTransaction),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -128,15 +130,32 @@ class _DetailHistoryPorterScreenState extends State<DetailHistoryPorterScreen> {
|
|||
switch (transaction.normalizedStatus) {
|
||||
case 'pending':
|
||||
return CustomeShadowCotainner(
|
||||
child: ButtonFill(
|
||||
text: 'Terima Orderan',
|
||||
textColor: Colors.white,
|
||||
onTap: () {
|
||||
_porterController.updateTransactionStatus(
|
||||
transactionId: porterTransactionId,
|
||||
status: 'proses',
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ButtonFill(
|
||||
text: 'Tolak',
|
||||
textColor: Colors.white,
|
||||
backgroundColor: RedColors.red500,
|
||||
onTap: () {
|
||||
_porterController.showRejectionDialog(porterTransactionId, context);
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10.w),
|
||||
Expanded(
|
||||
child: ButtonFill(
|
||||
text: 'Terima',
|
||||
textColor: Colors.white,
|
||||
onTap: () {
|
||||
_porterController.updateTransactionStatus(
|
||||
transactionId: porterTransactionId,
|
||||
status: 'proses',
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -154,6 +173,7 @@ class _DetailHistoryPorterScreenState extends State<DetailHistoryPorterScreen> {
|
|||
);
|
||||
|
||||
case 'selesai':
|
||||
case 'rejected':
|
||||
return const SizedBox.shrink();
|
||||
|
||||
default:
|
||||
|
@ -282,6 +302,59 @@ class _DetailHistoryPorterScreenState extends State<DetailHistoryPorterScreen> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildRejectionInfo(PorterTransactionModel transaction) {
|
||||
// Tampilkan info penolakan jika tersedia
|
||||
if (transaction.rejectionInfo == null) {
|
||||
return CustomeShadowCotainner(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_componentHeaderText(
|
||||
text: 'Informasi Penolakan',
|
||||
svgIcon: 'assets/icons/ic_info.svg',
|
||||
),
|
||||
TypographyStyles.body(
|
||||
'Tidak ada informasi penolakan',
|
||||
color: GrayColors.gray500,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final rejectionDate = _dateFormat.format(transaction.rejectionInfo!.timestamp);
|
||||
final rejectionTime = _timeFormat.format(transaction.rejectionInfo!.timestamp);
|
||||
|
||||
return CustomeShadowCotainner(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.info_outline, color: Colors.red, size: 24.sp),
|
||||
SizedBox(width: 10.w),
|
||||
TypographyStyles.body(
|
||||
'Informasi Penolakan',
|
||||
color: Colors.red,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 10.h),
|
||||
child: Divider(thickness: 1, color: GrayColors.gray200),
|
||||
),
|
||||
_componentRowText(label: 'Alasan', value: transaction.rejectionInfo!.reason),
|
||||
SizedBox(height: 6.h),
|
||||
_componentRowText(label: 'Tanggal Penolakan', value: rejectionDate),
|
||||
SizedBox(height: 6.h),
|
||||
_componentRowText(label: 'Waktu Penolakan', value: rejectionTime),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _componentHeaderText({required String text, required String svgIcon}) {
|
||||
return Column(
|
||||
children: [
|
||||
|
@ -328,7 +401,21 @@ class _DetailHistoryPorterScreenState extends State<DetailHistoryPorterScreen> {
|
|||
);
|
||||
}
|
||||
|
||||
// Helper untuk warna status
|
||||
String _getStatusText(PorterTransactionModel transaction) {
|
||||
switch (transaction.normalizedStatus) {
|
||||
case 'pending':
|
||||
return 'Menunggu';
|
||||
case 'proses':
|
||||
return 'Dalam Proses';
|
||||
case 'selesai':
|
||||
return 'Selesai';
|
||||
case 'rejected':
|
||||
return 'Ditolak';
|
||||
default:
|
||||
return transaction.status;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getStatusColor(PorterTransactionModel? transaction) {
|
||||
if (transaction == null) return GrayColors.gray400;
|
||||
|
||||
|
@ -339,12 +426,13 @@ class _DetailHistoryPorterScreenState extends State<DetailHistoryPorterScreen> {
|
|||
return PrimaryColors.primary800;
|
||||
case 'selesai':
|
||||
return Colors.green;
|
||||
case 'rejected':
|
||||
return Colors.red;
|
||||
default:
|
||||
return GrayColors.gray400;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper untuk icon status
|
||||
IconData _getStatusIcon(PorterTransactionModel? transaction) {
|
||||
if (transaction == null) return Icons.info_outline;
|
||||
|
||||
|
@ -355,6 +443,8 @@ class _DetailHistoryPorterScreenState extends State<DetailHistoryPorterScreen> {
|
|||
return Icons.directions_run;
|
||||
case 'selesai':
|
||||
return Icons.check_circle_outline;
|
||||
case 'rejected':
|
||||
return Icons.cancel_outlined;
|
||||
default:
|
||||
return Icons.info_outline;
|
||||
}
|
||||
|
|
|
@ -33,30 +33,6 @@ class _HistoryPorterScreenState extends State<HistoryPorterScreen> {
|
|||
decimalDigits: 0,
|
||||
);
|
||||
|
||||
// Future<List<PorterTransactionModel>> _loadPorterTransactionHistory() async {
|
||||
// try {
|
||||
// if (_userId.isEmpty) return [];
|
||||
|
||||
// // Dapatkan ID semua transaksi porter
|
||||
// final transactionIds = await _porterController.getPorterTransactionIds(_userId);
|
||||
|
||||
// // Muat data untuk setiap ID transaksi
|
||||
// final transactions = <PorterTransactionModel>[];
|
||||
|
||||
// for (final id in transactionIds) {
|
||||
// final txData = await _porterController.getPorterTransactionById(id);
|
||||
// if (txData != null) {
|
||||
// transactions.add(PorterTransactionModel.fromJson(txData, id));
|
||||
// }
|
||||
// }
|
||||
|
||||
// return transactions;
|
||||
// } catch (e) {
|
||||
// log('Error loading porter transaction history: $e');
|
||||
// return [];
|
||||
// }
|
||||
// }
|
||||
|
||||
Future<TransactionModel?> _loadTicketTransaction(String ticketId, String transactionId) async {
|
||||
final cacheKey = "$ticketId-$transactionId";
|
||||
if (_ticketTransactionCache.containsKey(cacheKey)) {
|
||||
|
@ -84,7 +60,7 @@ class _HistoryPorterScreenState extends State<HistoryPorterScreen> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DefaultTabController(
|
||||
length: 3,
|
||||
length: 4,
|
||||
child: Scaffold(
|
||||
backgroundColor: GrayColors.gray50,
|
||||
appBar: SimpleAppbarComponent(
|
||||
|
@ -102,6 +78,7 @@ class _HistoryPorterScreenState extends State<HistoryPorterScreen> {
|
|||
_buildTransactionList('pending'),
|
||||
_buildTransactionList('proses'),
|
||||
_buildTransactionList('selesai'),
|
||||
_buildTransactionList('rejected'),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -129,11 +106,12 @@ class _HistoryPorterScreenState extends State<HistoryPorterScreen> {
|
|||
labelColor: PrimaryColors.primary800,
|
||||
unselectedLabelColor: GrayColors.gray400,
|
||||
indicatorColor: PrimaryColors.primary800,
|
||||
indicatorWeight: 3,
|
||||
indicatorWeight: 4,
|
||||
tabs: const [
|
||||
Tab(text: 'Pending'),
|
||||
Tab(text: 'Proses'),
|
||||
Tab(text: 'Selesai'),
|
||||
Tab(text: 'Ditolak'),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -184,7 +162,7 @@ class _HistoryPorterScreenState extends State<HistoryPorterScreen> {
|
|||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.error_outline, color: Colors.red, size: 20.w),
|
||||
Icon(Icons.error_outline, color: RedColors.red500, size: 20.w),
|
||||
SizedBox(width: 8.w),
|
||||
Expanded(
|
||||
child: Text(
|
||||
|
@ -215,29 +193,55 @@ class _HistoryPorterScreenState extends State<HistoryPorterScreen> {
|
|||
// Log untuk debug
|
||||
log('Semua transaksi: ${allTransactions.length}');
|
||||
|
||||
// Filter berdasarkan status
|
||||
final filteredTransactions = allTransactions.where((tx) => tx.normalizedStatus == statusFilter).toList();
|
||||
// Filter berdasarkan status dengan logika yang diperbaiki
|
||||
final filteredTransactions = allTransactions.where((tx) {
|
||||
// Cek apakah transaksi memiliki rejectionInfo
|
||||
final hasRejectionInfo = tx.rejectionInfo != null;
|
||||
|
||||
// Transaksi yang memiliki rejectionInfo harus masuk ke tab "rejected" saja
|
||||
if (hasRejectionInfo && statusFilter != 'rejected') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Transaksi yang tidak memiliki rejectionInfo tapi statusnya "rejected"
|
||||
// juga harus masuk ke tab "rejected" saja
|
||||
if (!hasRejectionInfo && tx.normalizedStatus == 'rejected' && statusFilter != 'rejected') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Untuk tab "rejected", tampilkan semua transaksi yang ditolak
|
||||
if (statusFilter == 'rejected') {
|
||||
return hasRejectionInfo || tx.normalizedStatus == 'rejected';
|
||||
}
|
||||
|
||||
// Untuk tab lainnya, gunakan status normal
|
||||
return tx.normalizedStatus == statusFilter;
|
||||
}).toList();
|
||||
|
||||
log('Transaksi dengan status $statusFilter: ${filteredTransactions.length}');
|
||||
|
||||
// Jika tidak ada transaksi, tampilkan pesan kosong
|
||||
if (filteredTransactions.isEmpty) {
|
||||
// Jika ada error tapi tidak ada transaksi
|
||||
if (_porterController.error.value.contains('Porter tidak ditemukan') && statusFilter == 'selesai') {
|
||||
if (_porterController.error.value.contains('Porter tidak ditemukan') &&
|
||||
(statusFilter == 'selesai' || statusFilter == 'rejected')) {
|
||||
// Tampilkan pesan yang lebih positif
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.history, size: 48.h, color: Colors.grey[400]),
|
||||
Icon(statusFilter == 'rejected' ? Icons.block_outlined : Icons.history,
|
||||
size: 48.h, color: Colors.grey[400]),
|
||||
SizedBox(height: 16.h),
|
||||
TypographyStyles.body(
|
||||
'Tidak ada riwayat transaksi selesai',
|
||||
'Tidak ada riwayat transaksi ${statusFilter == 'rejected' ? 'ditolak' : 'selesai'}',
|
||||
color: GrayColors.gray600,
|
||||
),
|
||||
SizedBox(height: 8.h),
|
||||
TypographyStyles.caption(
|
||||
'Riwayat akan muncul setelah Anda menyelesaikan transaksi',
|
||||
statusFilter == 'rejected'
|
||||
? 'Riwayat akan muncul ketika Anda menolak permintaan porter'
|
||||
: 'Riwayat akan muncul setelah Anda menyelesaikan transaksi',
|
||||
color: GrayColors.gray500,
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
|
@ -273,17 +277,22 @@ class _HistoryPorterScreenState extends State<HistoryPorterScreen> {
|
|||
|
||||
// Pesan tidak ada transaksi
|
||||
Widget _buildEmptyTransactionMessage(String statusFilter) {
|
||||
// Ubah teks pesan sesuai dengan status filter
|
||||
String statusText = statusFilter;
|
||||
if (statusFilter == 'rejected') {
|
||||
statusText = 'ditolak';
|
||||
}
|
||||
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
TypographyStyles.body(
|
||||
'Tidak ada transaksi ${statusFilter.capitalizeFirst}',
|
||||
'Tidak ada transaksi ${statusText.capitalizeFirst}',
|
||||
color: GrayColors.gray600,
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
ElevatedButton.icon(
|
||||
// Perbaiki ini untuk memanggil refreshTransactions di controller
|
||||
onPressed: () => _porterController.refreshTransactions(),
|
||||
icon: Icon(Icons.refresh, size: 16.h),
|
||||
label: const Text('Refresh'),
|
||||
|
@ -342,18 +351,24 @@ class _HistoryPorterScreenState extends State<HistoryPorterScreen> {
|
|||
}
|
||||
}
|
||||
|
||||
// Modifikasi tampilan status untuk "rejected"
|
||||
String displayStatus = transaction.normalizedStatus.capitalizeFirst!;
|
||||
if (transaction.normalizedStatus == 'rejected' || transaction.rejectionInfo != null) {
|
||||
displayStatus = 'Ditolak';
|
||||
}
|
||||
|
||||
return CardHistoryPorter(
|
||||
namePassenger: passengerName,
|
||||
tlpnPassenger: passengerPhone,
|
||||
lokasiPassenger: transaction.locationPassenger,
|
||||
status: transaction.normalizedStatus.capitalizeFirst!,
|
||||
status: displayStatus,
|
||||
date: _dateFormat.format(transaction.createdAt),
|
||||
time: _timeFormat.format(transaction.createdAt),
|
||||
porter1: porter1,
|
||||
porter2: porter2,
|
||||
porter3: porter3,
|
||||
price: _priceFormatter.format(price),
|
||||
statusColor: _getStatusColor(transaction.normalizedStatus),
|
||||
statusColor: _getStatusColor(transaction.rejectionInfo != null ? 'rejected' : transaction.normalizedStatus),
|
||||
onTap: () {
|
||||
log('ID Transaction Porter: ${transaction.id}');
|
||||
Get.toNamed(Routes.DETAILHISTORYPORTER, arguments: {
|
||||
|
@ -376,6 +391,8 @@ class _HistoryPorterScreenState extends State<HistoryPorterScreen> {
|
|||
return PrimaryColors.primary800;
|
||||
case 'selesai':
|
||||
return Colors.green;
|
||||
case 'rejected':
|
||||
return RedColors.red500;
|
||||
default:
|
||||
return GrayColors.gray400;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue