Feat: Done for active transaction in boarding pass and features transaction history
This commit is contained in:
parent
a9e1390a4a
commit
6edf8d531e
|
@ -680,7 +680,7 @@
|
||||||
"languageVersion": "3.4"
|
"languageVersion": "3.4"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"generated": "2025-05-14T07:20:56.034581Z",
|
"generated": "2025-05-14T18:46:38.233635Z",
|
||||||
"generator": "pub",
|
"generator": "pub",
|
||||||
"generatorVersion": "3.5.0",
|
"generatorVersion": "3.5.0",
|
||||||
"flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0",
|
"flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0",
|
||||||
|
|
|
@ -93,6 +93,18 @@ class HistoryRepositoryImpl implements HistoryRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<List<TransactionModel>> getHistoryTransactionsStream(String userId) {
|
||||||
|
return getTransactionsStream(userId, 'active').map((allPaid) {
|
||||||
|
final startOfToday = DateTime(
|
||||||
|
DateTime.now().year,
|
||||||
|
DateTime.now().month,
|
||||||
|
DateTime.now().day,
|
||||||
|
);
|
||||||
|
return allPaid.where((tx) => tx.departureDate.isBefore(startOfToday)).toList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<TransactionModel?> getTransactionFromFirestore(String ticketId, String transactionId) async {
|
Future<TransactionModel?> getTransactionFromFirestore(String ticketId, String transactionId) async {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -52,6 +52,7 @@ class FlightModel {
|
||||||
final String transitAirplane;
|
final String transitAirplane;
|
||||||
final String stop;
|
final String stop;
|
||||||
final int price;
|
final int price;
|
||||||
|
final String gate;
|
||||||
final String airlineLogo;
|
final String airlineLogo;
|
||||||
final Map<String, SeatInfo> seat;
|
final Map<String, SeatInfo> seat;
|
||||||
|
|
||||||
|
@ -73,6 +74,7 @@ class FlightModel {
|
||||||
required this.transitAirplane,
|
required this.transitAirplane,
|
||||||
required this.stop,
|
required this.stop,
|
||||||
required this.price,
|
required this.price,
|
||||||
|
required this.gate,
|
||||||
required this.airlineLogo,
|
required this.airlineLogo,
|
||||||
required this.seat,
|
required this.seat,
|
||||||
});
|
});
|
||||||
|
@ -105,6 +107,7 @@ class FlightModel {
|
||||||
transitAirplane: data['transitAirplane'] ?? '',
|
transitAirplane: data['transitAirplane'] ?? '',
|
||||||
stop: data['stop'] ?? '',
|
stop: data['stop'] ?? '',
|
||||||
price: data['price'] ?? 0,
|
price: data['price'] ?? 0,
|
||||||
|
gate: data['gate'] ?? '',
|
||||||
airlineLogo: data['airlineLogo'] ?? '',
|
airlineLogo: data['airlineLogo'] ?? '',
|
||||||
seat: seatMap,
|
seat: seatMap,
|
||||||
);
|
);
|
||||||
|
@ -128,6 +131,7 @@ class FlightModel {
|
||||||
'transitAirplane': transitAirplane,
|
'transitAirplane': transitAirplane,
|
||||||
'stop': stop,
|
'stop': stop,
|
||||||
'price': price,
|
'price': price,
|
||||||
|
'gate': gate,
|
||||||
'airlineLogo': airlineLogo,
|
'airlineLogo': airlineLogo,
|
||||||
'seat': seat.map((key, value) => MapEntry(key, value.toMap())),
|
'seat': seat.map((key, value) => MapEntry(key, value.toMap())),
|
||||||
};
|
};
|
||||||
|
|
|
@ -39,6 +39,10 @@ class TransactionModel {
|
||||||
required this.numberSeat,
|
required this.numberSeat,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
DateTime get departureDate => (flightDetails['departureTime'] is int)
|
||||||
|
? DateTime.fromMillisecondsSinceEpoch(flightDetails['departureTime'] as int)
|
||||||
|
: DateTime.now();
|
||||||
|
|
||||||
factory TransactionModel.fromJson(Map<String, dynamic> json) {
|
factory TransactionModel.fromJson(Map<String, dynamic> json) {
|
||||||
DateTime getDateTime(dynamic value) {
|
DateTime getDateTime(dynamic value) {
|
||||||
if (value is Timestamp) {
|
if (value is Timestamp) {
|
||||||
|
|
|
@ -2,5 +2,6 @@ import '../models/transaction_model.dart';
|
||||||
|
|
||||||
abstract class HistoryRepository {
|
abstract class HistoryRepository {
|
||||||
Stream<List<TransactionModel>> getTransactionsStream(String userId, String status);
|
Stream<List<TransactionModel>> getTransactionsStream(String userId, String status);
|
||||||
|
Stream<List<TransactionModel>> getHistoryTransactionsStream(String userId);
|
||||||
Future<TransactionModel?> getTransactionFromFirestore(String ticketId, String transactionId);
|
Future<TransactionModel?> getTransactionFromFirestore(String ticketId, String transactionId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,16 +7,18 @@ class HistoryUseCase {
|
||||||
|
|
||||||
HistoryUseCase(this._repository);
|
HistoryUseCase(this._repository);
|
||||||
|
|
||||||
// Mendapatkan transaksi dengan status pending secara realtime
|
|
||||||
Stream<List<TransactionModel>> getPendingTransactionsStream(String userId) {
|
Stream<List<TransactionModel>> getPendingTransactionsStream(String userId) {
|
||||||
return _repository.getTransactionsStream(userId, 'pending');
|
return _repository.getTransactionsStream(userId, 'pending');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mendapatkan transaksi dengan status active secara realtime
|
|
||||||
Stream<List<TransactionModel>> getActiveTransactionsStream(String userId) {
|
Stream<List<TransactionModel>> getActiveTransactionsStream(String userId) {
|
||||||
return _repository.getTransactionsStream(userId, 'active');
|
return _repository.getTransactionsStream(userId, 'active');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Stream<List<TransactionModel>> getHistoryTransactionsStream(String uid) {
|
||||||
|
return _repository.getHistoryTransactionsStream(uid);
|
||||||
|
}
|
||||||
|
|
||||||
Future<TransactionModel?> getTransactionFromFirestore(String ticketId, String transactionId) {
|
Future<TransactionModel?> getTransactionFromFirestore(String ticketId, String transactionId) {
|
||||||
return _repository.getTransactionFromFirestore(ticketId, transactionId);
|
return _repository.getTransactionFromFirestore(ticketId, transactionId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ class HistoryController extends GetxController {
|
||||||
|
|
||||||
final RxList<TransactionModel> pendingTransactions = <TransactionModel>[].obs;
|
final RxList<TransactionModel> pendingTransactions = <TransactionModel>[].obs;
|
||||||
final RxList<TransactionModel> activeTransactions = <TransactionModel>[].obs;
|
final RxList<TransactionModel> activeTransactions = <TransactionModel>[].obs;
|
||||||
|
final RxList<TransactionModel> historyTransactions = <TransactionModel>[].obs;
|
||||||
final Rx<TransactionModel?> selectedTransaction = Rx<TransactionModel?>(null);
|
final Rx<TransactionModel?> selectedTransaction = Rx<TransactionModel?>(null);
|
||||||
|
|
||||||
final RxBool isLoading = true.obs;
|
final RxBool isLoading = true.obs;
|
||||||
|
@ -18,6 +19,7 @@ class HistoryController extends GetxController {
|
||||||
|
|
||||||
StreamSubscription? _pendingSubscription;
|
StreamSubscription? _pendingSubscription;
|
||||||
StreamSubscription? _activeSubscription;
|
StreamSubscription? _activeSubscription;
|
||||||
|
StreamSubscription? _historySubscription;
|
||||||
|
|
||||||
String _currentUserId = '';
|
String _currentUserId = '';
|
||||||
|
|
||||||
|
@ -42,15 +44,39 @@ class HistoryController extends GetxController {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
_activeSubscription = _historyUseCase.getActiveTransactionsStream(userId).listen((transactions) {
|
// _activeSubscription = _historyUseCase.getActiveTransactionsStream(userId).listen((transactions) {
|
||||||
log('HistoryController: active transactions updated, count: ${transactions.length}');
|
// log('HistoryController: active transactions updated, count: ${transactions.length}');
|
||||||
|
|
||||||
final sortedTransactions = _sortTransactionsByCreatedAt(transactions);
|
// final sortedTransactions = _sortTransactionsByCreatedAt(transactions);
|
||||||
activeTransactions.value = sortedTransactions;
|
// activeTransactions.value = sortedTransactions;
|
||||||
|
|
||||||
|
// isLoading.value = false;
|
||||||
|
// }, onError: (error) {
|
||||||
|
// log('HistoryController: Error mendapatkan transaksi active: $error');
|
||||||
|
// isLoading.value = false;
|
||||||
|
// });
|
||||||
|
|
||||||
|
_activeSubscription = _historyUseCase.getActiveTransactionsStream(userId).map((list) {
|
||||||
|
final todayStart = DateTime(
|
||||||
|
DateTime.now().year,
|
||||||
|
DateTime.now().month,
|
||||||
|
DateTime.now().day,
|
||||||
|
);
|
||||||
|
return list.where((tx) {
|
||||||
|
return tx.departureDate.isAtSameMomentAs(todayStart) || tx.departureDate.isAfter(todayStart);
|
||||||
|
}).toList();
|
||||||
|
}).listen((filtered) {
|
||||||
|
activeTransactions.value = _sortTransactionsByCreatedAt(filtered);
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}, onError: (error) {
|
}, onError: (e) {
|
||||||
log('HistoryController: Error mendapatkan transaksi active: $error');
|
log('Error mendapatkan transaksi active: $e');
|
||||||
|
isLoading.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
_historySubscription = _historyUseCase.getHistoryTransactionsStream(userId).listen((historyList) {
|
||||||
|
historyTransactions.value = _sortTransactionsByCreatedAt(historyList);
|
||||||
|
isLoading.value = false;
|
||||||
|
}, onError: (_) {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -119,6 +145,7 @@ class HistoryController extends GetxController {
|
||||||
|
|
||||||
pendingTransactions.clear();
|
pendingTransactions.clear();
|
||||||
activeTransactions.clear();
|
activeTransactions.clear();
|
||||||
|
historyTransactions.clear();
|
||||||
|
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
|
|
||||||
|
@ -129,7 +156,7 @@ class HistoryController extends GetxController {
|
||||||
}
|
}
|
||||||
|
|
||||||
String getUserId() {
|
String getUserId() {
|
||||||
if (_pendingSubscription != null || _activeSubscription != null) {
|
if (_pendingSubscription != null || _activeSubscription != null || _historySubscription != null) {
|
||||||
return _currentUserId;
|
return _currentUserId;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
|
@ -138,8 +165,10 @@ class HistoryController extends GetxController {
|
||||||
void _cleanupStreams() {
|
void _cleanupStreams() {
|
||||||
_pendingSubscription?.cancel();
|
_pendingSubscription?.cancel();
|
||||||
_activeSubscription?.cancel();
|
_activeSubscription?.cancel();
|
||||||
|
_historySubscription?.cancel();
|
||||||
_pendingSubscription = null;
|
_pendingSubscription = null;
|
||||||
_activeSubscription = null;
|
_activeSubscription = null;
|
||||||
|
_historySubscription = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -8,7 +8,28 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
|
||||||
class CardTransaction extends StatelessWidget {
|
class CardTransaction extends StatelessWidget {
|
||||||
const CardTransaction({super.key});
|
final String bookingCode;
|
||||||
|
final String airlineLogo;
|
||||||
|
final String departureCode;
|
||||||
|
final String arrivalCode;
|
||||||
|
final String ticketDate;
|
||||||
|
final String flightClass;
|
||||||
|
final String servicePorter;
|
||||||
|
final String duration;
|
||||||
|
final String price;
|
||||||
|
|
||||||
|
const CardTransaction({
|
||||||
|
Key? key,
|
||||||
|
required this.bookingCode,
|
||||||
|
required this.airlineLogo,
|
||||||
|
required this.departureCode,
|
||||||
|
required this.arrivalCode,
|
||||||
|
required this.ticketDate,
|
||||||
|
required this.flightClass,
|
||||||
|
required this.servicePorter,
|
||||||
|
required this.duration,
|
||||||
|
required this.price,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -27,7 +48,7 @@ class CardTransaction extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
TypographyStyles.small("Kode Booking", color: Colors.white, fontWeight: FontWeight.w400),
|
TypographyStyles.small("Kode Booking", color: Colors.white, fontWeight: FontWeight.w400),
|
||||||
TypographyStyles.body("I2L8JRL", color: Colors.white)
|
TypographyStyles.body(bookingCode, color: Colors.white)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
|
@ -46,66 +67,80 @@ class CardTransaction extends StatelessWidget {
|
||||||
bottomLeft: Radius.circular(10.r),
|
bottomLeft: Radius.circular(10.r),
|
||||||
bottomRight: Radius.circular(10.r),
|
bottomRight: Radius.circular(10.r),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: IntrinsicHeight(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
SvgPicture.asset('assets/images/citilink.svg', width: 40.w, height: 10.h),
|
children: [
|
||||||
Column(
|
Align(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
alignment: Alignment.center,
|
||||||
children: [
|
child: Image.network(airlineLogo, width: 40.w),
|
||||||
Row(
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
TypographyStyles.small(
|
||||||
|
ticketDate,
|
||||||
|
color: GrayColors.gray600,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
SizedBox(width: 10.w),
|
||||||
|
CircleAvatar(radius: 2.r, backgroundColor: Color(0xFFD9D9D9)),
|
||||||
|
SizedBox(width: 10.w),
|
||||||
|
TypographyStyles.small(duration, color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 6.h),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
TypographyStyles.body(departureCode, color: GrayColors.gray800),
|
||||||
|
SizedBox(width: 10.w),
|
||||||
|
SvgPicture.asset(
|
||||||
|
'assets/icons/ic_right.svg',
|
||||||
|
color: GrayColors.gray800,
|
||||||
|
width: 14.w,
|
||||||
|
height: 14.h,
|
||||||
|
),
|
||||||
|
SizedBox(width: 10.w),
|
||||||
|
TypographyStyles.body(arrivalCode, color: GrayColors.gray800),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 6.h),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
TypographyStyles.small(
|
||||||
|
flightClass,
|
||||||
|
color: GrayColors.gray600,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
SizedBox(width: 10.w),
|
||||||
|
CircleAvatar(radius: 2.r, backgroundColor: Color(0xFFD9D9D9)),
|
||||||
|
SizedBox(width: 10.w),
|
||||||
|
Expanded(child: TypographyStyles.small(servicePorter, color: GrayColors.gray600, fontWeight: FontWeight.w400)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomRight,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
TypographyStyles.small(
|
TypographyStyles.small('Total Harga', color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
||||||
'Sen, 27 Jan 2025 ',
|
TypographyStyles.caption(price, color: PrimaryColors.primary800)
|
||||||
color: GrayColors.gray600,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
SizedBox(width: 10.w),
|
|
||||||
CircleAvatar(radius: 2.r, backgroundColor: Color(0xFFD9D9D9)),
|
|
||||||
SizedBox(width: 10.w),
|
|
||||||
TypographyStyles.small('5j 40m', color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 6.h),
|
)
|
||||||
Row(
|
],
|
||||||
children: [
|
),
|
||||||
TypographyStyles.body('YIA', color: GrayColors.gray800),
|
|
||||||
SizedBox(width: 10.w),
|
|
||||||
SvgPicture.asset(
|
|
||||||
'assets/icons/ic_right.svg',
|
|
||||||
color: GrayColors.gray800,
|
|
||||||
width: 14.w,
|
|
||||||
height: 14.h,
|
|
||||||
),
|
|
||||||
SizedBox(width: 10.w),
|
|
||||||
TypographyStyles.body('LOP', color: GrayColors.gray800),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(height: 6.h),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
TypographyStyles.small(
|
|
||||||
'Economy',
|
|
||||||
color: GrayColors.gray600,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
SizedBox(width: 10.w),
|
|
||||||
CircleAvatar(radius: 2.r, backgroundColor: Color(0xFFD9D9D9)),
|
|
||||||
SizedBox(width: 10.w),
|
|
||||||
TypographyStyles.small('Fast Track (FT)', color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
TypographyStyles.small('Total Harga', color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
|
||||||
TypographyStyles.caption("Rp 1.410.000", color: PrimaryColors.primary800)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
|
@ -7,7 +7,6 @@ import 'package:e_porter/presentation/screens/routes/app_rountes.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
import '../../../../_core/service/preferences_service.dart';
|
import '../../../../_core/service/preferences_service.dart';
|
||||||
import '../../../../_core/utils/map_helper.dart';
|
import '../../../../_core/utils/map_helper.dart';
|
||||||
import '../../../../domain/models/transaction_model.dart';
|
import '../../../../domain/models/transaction_model.dart';
|
||||||
|
|
|
@ -210,7 +210,7 @@ class _PrintBoardingPassScreenState extends State<PrintBoardingPassScreen> {
|
||||||
context: context,
|
context: context,
|
||||||
services: services,
|
services: services,
|
||||||
flightClass: transaction.flightDetails['flightClass'],
|
flightClass: transaction.flightDetails['flightClass'],
|
||||||
gate: 'Gate',
|
gate: transaction.flightDetails['gate'],
|
||||||
seatNumber: seatNumber,
|
seatNumber: seatNumber,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
import 'package:e_porter/_core/constants/colors.dart';
|
import 'package:e_porter/_core/constants/colors.dart';
|
||||||
|
import 'package:e_porter/_core/constants/typography.dart';
|
||||||
|
import 'package:e_porter/_core/utils/formatter/date_helper.dart';
|
||||||
|
import 'package:e_porter/presentation/controllers/history_controller.dart';
|
||||||
import 'package:e_porter/presentation/screens/boarding_pass/component/card_transaction.dart';
|
import 'package:e_porter/presentation/screens/boarding_pass/component/card_transaction.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
import '../../../../_core/component/appbar/appbar_component.dart';
|
import '../../../../_core/component/appbar/appbar_component.dart';
|
||||||
|
|
||||||
|
@ -14,6 +18,8 @@ class transactionHistory extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _transactionHistoryState extends State<transactionHistory> {
|
class _transactionHistoryState extends State<transactionHistory> {
|
||||||
|
final _historyController = Get.find<HistoryController>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
@ -29,12 +35,61 @@ class _transactionHistoryState extends State<transactionHistory> {
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
|
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
|
||||||
child: ListView.builder(
|
child: Obx(
|
||||||
itemCount: 5,
|
() {
|
||||||
itemBuilder: (context, index) {
|
final list = _historyController.historyTransactions;
|
||||||
return Padding(
|
if (list.isEmpty) {
|
||||||
padding: EdgeInsets.only(bottom: 16.h),
|
return Center(
|
||||||
child: CardTransaction(),
|
child: TypographyStyles.caption(
|
||||||
|
'Tidak ada riwayat transaksi',
|
||||||
|
color: GrayColors.gray400,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh: () => _historyController.refreshTransactions(_historyController.getUserId()),
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: list.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final transaction = list[index];
|
||||||
|
|
||||||
|
final Map<String, dynamic>? svcMap = transaction.porterServiceDetails;
|
||||||
|
|
||||||
|
final services = <String>[];
|
||||||
|
if (svcMap?['departure'] != null) {
|
||||||
|
services.add(svcMap!['departure']['name'] as String);
|
||||||
|
}
|
||||||
|
if (svcMap?['transit'] != null) {
|
||||||
|
services.add(svcMap!['transit']['name'] as String);
|
||||||
|
}
|
||||||
|
if (svcMap?['arrival'] != null) {
|
||||||
|
services.add(svcMap!['arrival']['name'] as String);
|
||||||
|
}
|
||||||
|
|
||||||
|
final departureDate =
|
||||||
|
DateFormatterHelper.formatFlightDate(transaction.flightDetails['departureTime']);
|
||||||
|
final duration = DateFormatterHelper.calculateFlightDuration(
|
||||||
|
transaction.flightDetails['departureTime'], transaction.flightDetails['arrivalTime']);
|
||||||
|
final price = NumberFormat.currency(locale: 'id_ID', symbol: 'Rp ', decimalDigits: 0)
|
||||||
|
.format(transaction.amount);
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 16.h),
|
||||||
|
child: CardTransaction(
|
||||||
|
bookingCode: transaction.idBooking,
|
||||||
|
airlineLogo: transaction.flightDetails['airlineLogo'],
|
||||||
|
departureCode: transaction.flightDetails['codeDeparture'],
|
||||||
|
arrivalCode: transaction.flightDetails['codeArrival'],
|
||||||
|
ticketDate: departureDate,
|
||||||
|
flightClass: transaction.flightDetails['flightClass'],
|
||||||
|
servicePorter: services.join(", "),
|
||||||
|
duration: duration,
|
||||||
|
price: price,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -312,6 +312,7 @@ class _TicketBookingStep4ScreenState extends State<TicketBookingStep4Screen> {
|
||||||
'stop': flightData?.stop,
|
'stop': flightData?.stop,
|
||||||
'airlineLogo': flightData?.airlineLogo,
|
'airlineLogo': flightData?.airlineLogo,
|
||||||
'price': flightData?.price,
|
'price': flightData?.price,
|
||||||
|
'gate': flightData?.gate,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Persiapkan data porter service jika ada
|
// Persiapkan data porter service jika ada
|
||||||
|
|
|
@ -159,6 +159,7 @@ class AppRoutes {
|
||||||
GetPage(
|
GetPage(
|
||||||
name: Routes.TRANSACTIONHISTORY,
|
name: Routes.TRANSACTIONHISTORY,
|
||||||
page: () => transactionHistory(),
|
page: () => transactionHistory(),
|
||||||
|
binding: HistoryBinding(),
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: Routes.DETAILTICKET,
|
name: Routes.DETAILTICKET,
|
||||||
|
|
Loading…
Reference in New Issue