diff --git a/lib/data/repositories/transaction_repository_impl.dart b/lib/data/repositories/transaction_repository_impl.dart index 750fabb..b77a0bf 100644 --- a/lib/data/repositories/transaction_repository_impl.dart +++ b/lib/data/repositories/transaction_repository_impl.dart @@ -1,5 +1,6 @@ -import 'dart:developer'; import 'dart:io'; +import 'dart:math' hide log; +import 'dart:developer'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_database/firebase_database.dart'; import 'package:firebase_storage/firebase_storage.dart'; @@ -17,13 +18,18 @@ class TransactionRepositoryImpl implements TransactionRepository { }) : _firestore = firestore ?? FirebaseFirestore.instance, _storage = storage ?? FirebaseStorage.instance; - // Method untuk generate ID unik String _generateUniqueId() { return DateTime.now().millisecondsSinceEpoch.toString() + '_' + (100000 + (DateTime.now().microsecond % 900000)).toString(); } + String _generateBookingId() { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + final rand = Random(); + return List.generate(7, (index) => chars[rand.nextInt(chars.length)]).join(); + } + @override Future createTransaction({ required String ticketId, @@ -41,6 +47,7 @@ class TransactionRepositoryImpl implements TransactionRepository { }) async { try { final transactionId = _generateUniqueId(); + final idBooking = _generateBookingId(); final now = DateTime.now(); final transactionData = { @@ -61,6 +68,7 @@ class TransactionRepositoryImpl implements TransactionRepository { 'numberSeat': numberSeat, }; + // Simpan transaksi di Firestore await _firestore .collection('tickets') .doc(ticketId) @@ -68,104 +76,87 @@ class TransactionRepositoryImpl implements TransactionRepository { .doc(transactionId) .set(transactionData); - DocumentSnapshot seatData = - await _firestore.collection('tickets').doc(ticketId).collection('flights').doc(flightId).get(); + // Update ID booking pada tiket + await _firestore.collection('tickets').doc(ticketId).update({ + 'idBooking': idBooking, + }); - if (seatData.exists) { - Map? data = seatData.data() as Map?; - String getSeatClass(String seatNumber) { - if (seatNumber.isNotEmpty) { - return seatNumber[0].toLowerCase(); - } - return "a"; + // Kelompokkan kursi berdasarkan kelas + Map> seatsByClass = {}; + for (String seatNumber in numberSeat) { + String seatClass = getSeatClass(seatNumber); + int seatIndex = getSeatIndex(seatNumber); + + if (!seatsByClass.containsKey(seatClass)) { + seatsByClass[seatClass] = []; } - - int getSeatIndex(String seatNumber) { - if (seatNumber.length > 1) { - try { - return int.parse(seatNumber.substring(1)) - 1; - } catch (e) { - log("Error parsing seat index: $e"); - } - } - return 0; - } - - for (String seatNumber in numberSeat) { - String seatClass = getSeatClass(seatNumber); - int seatIndex = getSeatIndex(seatNumber); - log("Processing seat: $seatNumber, class: $seatClass, index: $seatIndex"); - - if (data != null && data.containsKey('seat')) { - Map seatData = Map.from(data['seat']); - - if (seatData.containsKey(seatClass)) { - Map classSeatData = Map.from(seatData[seatClass]); - - if (classSeatData.containsKey('isTaken')) { - List isTaken = List.from(classSeatData['isTaken'] ?? []); - - while (isTaken.length <= seatIndex) { - isTaken.add(false); - } - isTaken[seatIndex] = true; - - await _firestore - .collection('tickets') - .doc(ticketId) - .collection('flights') - .doc(flightId) - .update({'seat.$seatClass.isTaken': isTaken}); - - log("Successfully updated seat $seatNumber in class $seatClass at index $seatIndex"); - } else { - List isTaken = List.filled(seatIndex + 1, false); - isTaken[seatIndex] = true; - - await _firestore - .collection('tickets') - .doc(ticketId) - .collection('flights') - .doc(flightId) - .update({'seat.$seatClass.isTaken': isTaken}); - - log("Created new isTaken array for seat $seatNumber in class $seatClass"); - } - } else { - List isTaken = List.filled(seatIndex + 1, false); - isTaken[seatIndex] = true; - - await _firestore.collection('tickets').doc(ticketId).collection('flights').doc(flightId).update({ - 'seat.$seatClass': {'isTaken': isTaken} - }); - - log("Created new seat class $seatClass for seat $seatNumber"); - } - } else { - List isTaken = List.filled(seatIndex + 1, false); - isTaken[seatIndex] = true; - - Map newSeatData = { - 'seat': { - seatClass: {'isTaken': isTaken} - } - }; - - await _firestore - .collection('tickets') - .doc(ticketId) - .collection('flights') - .doc(flightId) - .set(newSeatData, SetOptions(merge: true)); - - log("Created complete new seat structure for seat $seatNumber"); - } - } - } else { - log("Flight document not found"); - throw Exception("Flight document not found"); + seatsByClass[seatClass]!.add(seatIndex); + log("Mengelompokkan kursi: $seatNumber, kelas: $seatClass, indeks: $seatIndex"); } + // Ambil data dokumen penerbangan + DocumentSnapshot flightDoc = + await _firestore.collection('tickets').doc(ticketId).collection('flights').doc(flightId).get(); + + if (!flightDoc.exists) { + throw Exception("Dokumen penerbangan tidak ditemukan"); + } + + Map flightData = flightDoc.data() as Map; + + // Proses setiap kelas kursi + for (var entry in seatsByClass.entries) { + String seatClass = entry.key; + List indices = entry.value; + + // Tentukan total kursi + int totalSeat = 10; // Default jika tidak ditemukan + + // Cek apakah data kursi untuk kelas ini sudah ada + List isTaken = List.filled(totalSeat, false); + + if (flightData.containsKey('seat') && flightData['seat'] is Map && flightData['seat'].containsKey(seatClass)) { + var seatClassData = flightData['seat'][seatClass]; + + // Ambil totalSeat jika ada + if (seatClassData is Map && seatClassData.containsKey('totalSeat')) { + totalSeat = seatClassData['totalSeat']; + } + + // Ambil array isTaken yang sudah ada (jika ada) + if (seatClassData is Map && seatClassData.containsKey('isTaken')) { + List existingIsTaken = List.from(seatClassData['isTaken'] ?? []); + + // Salin nilai yang sudah ada ke array baru + for (int i = 0; i < existingIsTaken.length && i < totalSeat; i++) { + isTaken[i] = existingIsTaken[i] == true; + } + } + } + + // Tandai kursi yang dipilih sebagai terisi (true) + for (int index in indices) { + if (index < totalSeat) { + isTaken[index] = true; + log("Menandai kursi di kelas $seatClass indeks $index sebagai terisi"); + } else { + log("Warning: Indeks kursi $index melebihi total kursi $totalSeat pada kelas $seatClass"); + } + } + + // Konversi ke List untuk Firebase + List isTakenDynamic = isTaken.map((e) => e).toList(); + + // Update dalam satu operasi + await _firestore.collection('tickets').doc(ticketId).collection('flights').doc(flightId).update({ + 'seat.$seatClass.isTaken': isTakenDynamic, + 'seat.$seatClass.totalSeat': totalSeat, + }); + + log("Berhasil memperbarui status kursi untuk kelas $seatClass"); + } + + // Simpan ke Realtime Database final databaseRef = FirebaseDatabase.instance.ref(); final userId = userDetails['uid']; await databaseRef.child('transactions/$userId/$ticketId/$transactionId').set({ @@ -184,11 +175,13 @@ class TransactionRepositoryImpl implements TransactionRepository { 'passenger': passenger, 'passengerDetails': passengerDetails, 'numberSeat': numberSeat, + 'idBooking': idBooking, }); return transactionId; } catch (e) { - throw Exception('Failed to create transaction: $e'); + log('Error dalam createTransaction: $e'); + throw Exception('Gagal membuat transaksi: $e'); } } @@ -411,39 +404,60 @@ class TransactionRepositoryImpl implements TransactionRepository { // Metode helper untuk mereset status kursi Future _resetSeatStatus(String ticketId, String flightId, List numberSeat) async { try { + // Dapatkan data dokumen DocumentSnapshot seatData = await _firestore.collection('tickets').doc(ticketId).collection('flights').doc(flightId).get(); if (seatData.exists) { Map? data = seatData.data() as Map?; + // Kelompokkan kursi berdasarkan kelas untuk memproses dalam satu operasi per kelas + Map> seatsByClass = {}; + for (String seatNumber in numberSeat) { String seatClass = getSeatClass(seatNumber); int seatIndex = getSeatIndex(seatNumber); - if (data != null && data.containsKey('seat')) { - Map seatData = Map.from(data['seat']); + if (!seatsByClass.containsKey(seatClass)) { + seatsByClass[seatClass] = []; + } + seatsByClass[seatClass]!.add(seatIndex); + } - if (seatData.containsKey(seatClass)) { - Map classSeatData = Map.from(seatData[seatClass]); + // Proses setiap kelas kursi + for (var entry in seatsByClass.entries) { + String seatClass = entry.key; + List indices = entry.value; - if (classSeatData.containsKey('isTaken')) { - List isTaken = List.from(classSeatData['isTaken'] ?? []); + if (data != null && + data.containsKey('seat') && + data['seat'].containsKey(seatClass) && + data['seat'][seatClass].containsKey('isTaken') && + data['seat'][seatClass].containsKey('totalSeat')) { + int totalSeat = data['seat'][seatClass]['totalSeat']; + List isTaken = List.from(data['seat'][seatClass]['isTaken'] ?? []); - if (seatIndex < isTaken.length) { - isTaken[seatIndex] = false; // Reset status kursi + // Pastikan array memiliki panjang yang sama dengan totalSeat + if (isTaken.length < totalSeat) { + isTaken = List.filled(totalSeat, false); + } - await _firestore - .collection('tickets') - .doc(ticketId) - .collection('flights') - .doc(flightId) - .update({'seat.$seatClass.isTaken': isTaken}); - - log("Reset kursi $seatNumber di kelas $seatClass pada indeks $seatIndex"); - } + // Reset semua kursi yang dipilih + for (int index in indices) { + if (index < isTaken.length) { + isTaken[index] = false; } } + + // Update dalam satu operasi + await _firestore + .collection('tickets') + .doc(ticketId) + .collection('flights') + .doc(flightId) + .update({'seat.$seatClass.isTaken': isTaken}); + + log("Reset kursi untuk kelas $seatClass: $indices"); } } } @@ -453,6 +467,7 @@ class TransactionRepositoryImpl implements TransactionRepository { } String getSeatClass(String seatNumber) { + log("Getting seat class for: $seatNumber"); if (seatNumber.isNotEmpty) { return seatNumber[0].toLowerCase(); } @@ -460,9 +475,12 @@ class TransactionRepositoryImpl implements TransactionRepository { } int getSeatIndex(String seatNumber) { + log("Getting seat index for: $seatNumber"); if (seatNumber.length > 1) { try { - return int.parse(seatNumber.substring(1)) - 1; + int index = int.parse(seatNumber.substring(1)) - 1; + log("Calculated seat index: $index"); + return index; } catch (e) { log("Error parsing seat index: $e"); } diff --git a/lib/domain/models/transaction_model.dart b/lib/domain/models/transaction_model.dart index 00397fd..1e97a65 100644 --- a/lib/domain/models/transaction_model.dart +++ b/lib/domain/models/transaction_model.dart @@ -2,6 +2,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; class TransactionModel { final String id; + final String idBooking; final String ticketId; final String flightId; final double amount; @@ -18,6 +19,7 @@ class TransactionModel { TransactionModel({ required this.id, + required this.idBooking, required this.ticketId, required this.flightId, required this.amount, @@ -34,16 +36,27 @@ class TransactionModel { }); factory TransactionModel.fromJson(Map json) { + DateTime getDateTime(dynamic value) { + if (value is Timestamp) { + return value.toDate(); + } else if (value is DateTime) { + return value; + } else { + return DateTime.now(); + } + } + return TransactionModel( id: json['id'] ?? '', + idBooking: json['idBooking'] ?? '', ticketId: json['ticketId'] ?? '', flightId: json['flightId'] ?? '', amount: (json['amount'] ?? 0.0).toDouble(), method: json['method'] ?? '', status: json['status'] ?? 'pending', proofUrl: json['proofUrl'], - createdAt: (json['createdAt'] as Timestamp).toDate(), - expiryTime: (json['expiryTime'] as Timestamp).toDate(), + createdAt: getDateTime(json['createdAt']), + expiryTime: getDateTime(json['expiryTime']), flightDetails: json['flightDetails'] ?? {}, bandaraDetails: json['bandaraDetails'] ?? {}, porterServiceDetails: json['porterServiceDetails'], @@ -55,6 +68,7 @@ class TransactionModel { Map toJson() { return { 'id': id, + 'idBooking': idBooking, 'ticketId': ticketId, 'flightId': flightId, 'amount': amount, @@ -70,4 +84,4 @@ class TransactionModel { 'passenger': passenger, }; } -} \ No newline at end of file +} diff --git a/lib/presentation/screens/home/pages/upload_file_screen.dart b/lib/presentation/screens/home/pages/upload_file_screen.dart index fbd891c..fb79221 100644 --- a/lib/presentation/screens/home/pages/upload_file_screen.dart +++ b/lib/presentation/screens/home/pages/upload_file_screen.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:e_porter/_core/component/button/button_outline.dart'; import 'package:e_porter/_core/constants/colors.dart'; import 'package:e_porter/_core/constants/typography.dart'; import 'package:e_porter/_core/service/permission_service.dart'; @@ -55,7 +56,6 @@ class _UploadFileScreenState extends State { void _uploadToServer() async { try { - // Validasi data transaksi if (ticketId.isEmpty || transactionId.isEmpty || userId.isEmpty) { Get.snackbar( 'Error', @@ -190,21 +190,34 @@ class _UploadFileScreenState extends State { ), ), bottomNavigationBar: CustomeShadowCotainner( - child: ButtonFill( - text: 'Upload', - textColor: Colors.white, - onTap: () { - if (uploadedFiles.isNotEmpty) { - _submitFiles(); - } else { - Get.snackbar( - 'Peringatan', - 'Silahkan pilih file terlebih dahulu', - backgroundColor: Colors.red, - colorText: Colors.white, - ); - } - }, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ButtonOutline( + text: 'Nanti', + textColor: PrimaryColors.primary800, + onTap: () { + Get.offAllNamed(Routes.NAVBAR); + }, + ), + SizedBox(height: 10.h), + ButtonFill( + text: 'Upload', + textColor: Colors.white, + onTap: () { + if (uploadedFiles.isNotEmpty) { + _submitFiles(); + } else { + Get.snackbar( + 'Peringatan', + 'Silahkan pilih file terlebih dahulu', + backgroundColor: Colors.red, + colorText: Colors.white, + ); + } + }, + ), + ], ), ), ); @@ -418,7 +431,6 @@ class _UploadFileScreenState extends State { } void _submitFiles() { - // Check if all files are uploaded successfully bool allCompleted = uploadedFiles.every((file) => file.status == FileUploadStatus.completed); if (!allCompleted) {