Feat: Lacks overall total functionality

This commit is contained in:
orangdeso 2025-03-27 23:39:11 +07:00
parent 491ba3b55f
commit 4e3d4b5d33
8 changed files with 252 additions and 59 deletions

View File

@ -1,6 +1,4 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:e_porter/_core/service/logger_service.dart';
import '../../domain/models/ticket_model.dart';
import '../../domain/repositories/ticket_repository.dart';
@ -21,8 +19,7 @@ class TicketRepositoryImpl implements TicketRepository {
final startOfDay = DateTime(leavingDate.year, leavingDate.month, leavingDate.day);
final endOfDay = startOfDay.add(Duration(days: 1));
logger.d(
"Fetching tickets with parameters: from = $from, to = $to, leavingDate between = ${Timestamp.fromDate(startOfDay)} and ${Timestamp.fromDate(endOfDay)}");
// logger.d("Fetching tickets with parameters: from = $from, to = $to, leavingDate between = ${Timestamp.fromDate(startOfDay)} and ${Timestamp.fromDate(endOfDay)}");
final snapshot = await collection
.where('from', isEqualTo: from)
@ -31,9 +28,9 @@ class TicketRepositoryImpl implements TicketRepository {
.where('leavingDate', isLessThan: Timestamp.fromDate(endOfDay))
.get();
logger.d("Number of tickets found: ${snapshot.docs.length}");
// logger.d("Number of tickets found: ${snapshot.docs.length}");
snapshot.docs.forEach((doc) {
logger.d("Doc ID: ${doc.id} => ${doc.data()}");
// logger.d("Doc ID: ${doc.id} => ${doc.data()}");
});
return snapshot.docs.map((doc) => TicketModel.fromDocument(doc)).toList();
@ -52,9 +49,9 @@ class TicketRepositoryImpl implements TicketRepository {
}
final snapshot = await query.get();
logger.d("Number of flights found for ticket $ticketId with seatClass '$flightClass': ${snapshot.docs.length}");
// logger.d("Number of flights found for ticket $ticketId with seatClass '$flightClass': ${snapshot.docs.length}");
snapshot.docs.forEach((doc) {
logger.d("Flight Doc ID: ${doc.id} => ${doc.data()}");
// logger.d("Flight Doc ID: ${doc.id} => ${doc.data()}");
});
return snapshot.docs.map((doc) => FlightModel.fromDocument(doc)).toList();
@ -72,7 +69,7 @@ class TicketRepositoryImpl implements TicketRepository {
.doc(flightId)
.get();
logger.d("getFlightById - TicketID: $ticketId, FlightID: $flightId, Data: ${doc.data()}");
// logger.d("getFlightById - TicketID: $ticketId, FlightID: $flightId, Data: ${doc.data()}");
return FlightModel.fromDocument(doc);
}

View File

@ -18,6 +18,9 @@ class CardFlightInformation extends StatelessWidget {
final String passenger;
final String? transiAirplane;
final String? stop;
final String? departurePorter;
final String? arrivalPorter;
final String? transitPorter;
const CardFlightInformation({
Key? key,
@ -31,6 +34,9 @@ class CardFlightInformation extends StatelessWidget {
required this.passenger,
this.transiAirplane,
this.stop,
this.departurePorter,
this.arrivalPorter,
this.transitPorter,
});
@override
@ -85,12 +91,23 @@ class CardFlightInformation extends StatelessWidget {
servicePorter != null ? _buildText(context, text: servicePorter!) : SizedBox.shrink(),
],
),
if (departurePorter != null && departurePorter!.isNotEmpty) ... [
SizedBox(height: 4.h),
TypographyStyles.small(departurePorter!, color: GrayColors.gray600, fontWeight: FontWeight.w400),
],
if (arrivalPorter != null && arrivalPorter!.isNotEmpty) ... [
SizedBox(height: 4.h),
TypographyStyles.small(arrivalPorter!, color: GrayColors.gray600, fontWeight: FontWeight.w400),
],
if (transitPorter != null && transitPorter!.isNotEmpty) ... [
SizedBox(height: 4.h),
TypographyStyles.small(transitPorter!, color: GrayColors.gray600, fontWeight: FontWeight.w400),
],
SizedBox(height: 4.h),
TypographyStyles.small(
'$passenger Dewasa',
color: GrayColors.gray600,
fontWeight: FontWeight.w400,
letterSpacing: 0.2,
)
],
),

View File

@ -1,5 +1,7 @@
// ignore_for_file: unnecessary_null_comparison
import 'dart:developer';
import 'package:e_porter/_core/component/appbar/appbar_component.dart';
import 'package:e_porter/_core/component/card/custome_shadow_cotainner.dart';
import 'package:e_porter/_core/component/icons/icons_library.dart';
@ -253,7 +255,7 @@ class _ChooseSeatScreenState extends State<ChooseSeatScreen> {
if (selectedSeatNumbers.any((seat) => seat.isEmpty)) {
return;
}
logger.d('Kursi: $selectedSeatNumbers');
log('Nomor Kursi: $selectedSeatNumbers');
Get.back(result: selectedSeatNumbers);
},
),

View File

@ -1,5 +1,4 @@
// ignore_for_file: unnecessary_null_comparison
import 'package:e_porter/_core/component/appbar/appbar_component.dart';
import 'package:e_porter/_core/constants/colors.dart';
import 'package:e_porter/_core/constants/typography.dart';
@ -9,8 +8,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import '../../../../_core/service/logger_service.dart';
import '../../../controllers/ticket_controller.dart';
class SearchTicketsScreen extends StatefulWidget {
@ -141,8 +138,7 @@ class _SearchTicketsScreenState extends State<SearchTicketsScreen> {
"ticketDate": ticketDate,
"passenger": passengerCount,
};
logger.d(
'ID Ticket: $ticketId \nID Flight: $flightId \nTicket Date: $ticketDate \nPassenger: $passengerCount');
// logger.d('ID Ticket: $ticketId \nID Flight: $flightId \nTicket Date: $ticketDate \nPassenger: $passengerCount');
Get.toNamed(Routes.TICKETBOOKINGSTEP1, arguments: argument);
},
),

View File

@ -7,7 +7,6 @@ import 'package:e_porter/_core/component/card/custome_shadow_cotainner.dart';
import 'package:e_porter/_core/component/icons/icons_library.dart';
import 'package:e_porter/_core/constants/colors.dart';
import 'package:e_porter/_core/constants/typography.dart';
import 'package:e_porter/_core/service/logger_service.dart';
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/ticket_model.dart';
@ -90,7 +89,7 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
}
final userId = userData.uid;
await profilController.fetchPassangerById(userId);
logger.d('User ID: $userId');
// logger.d('User ID: $userId');
}
bool isAllPassengersFilled() {
@ -176,7 +175,7 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
textColor: Colors.white,
backgroundColor: isAllPassengersFilled() ? PrimaryColors.primary800 : GrayColors.gray400,
onTap: () {
logger.d('Selected Passengers: $selectedPassengers');
// logger.d('Selected Passengers: $selectedPassengers');
if (selectedPassengers.any((p) => p == null)) {
SnackbarHelper.showError('Error', 'Harap lengkapi slot penumpang');
} else {
@ -216,7 +215,7 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
} else if (snapshot.hasData && snapshot.data != null) {
final user = snapshot.data!;
_loggedUser = user;
logger.d('Data user: ${user.noId}');
// logger.d('Data user: ${user.noId}');
return CustomeShadowCotainner(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -376,7 +375,7 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
shrinkWrap: true,
itemBuilder: (context, index) {
final passenger = profilController.passengerList[index];
logger.d("Passenger Models : ${passenger.noId}");
// logger.d("Passenger Models : ${passenger.noId}");
return Padding(
padding: EdgeInsets.only(top: 16.h),
child: _buildAddPassenger(
@ -574,7 +573,7 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
itemBuilder: (context, pIndex) {
final passenger = profilController.passengerList[pIndex];
final isUsed = usedNoIds.contains(passenger.noId);
logger.d("Passenger Models : ${passenger.noId}");
// logger.d("Passenger Models : ${passenger.noId}");
return Padding(
padding: EdgeInsets.only(top: 16.h),
child: _buildAddPassenger(

View File

@ -1,7 +1,6 @@
import 'package:e_porter/_core/component/appbar/appbar_component.dart';
import 'package:e_porter/_core/component/card/custome_shadow_cotainner.dart';
import 'package:e_porter/_core/constants/colors.dart';
import 'package:e_porter/_core/service/logger_service.dart';
import 'package:e_porter/domain/models/user_entity.dart';
import 'package:e_porter/presentation/controllers/ticket_controller.dart';
import 'package:e_porter/presentation/screens/routes/app_rountes.dart';
@ -63,7 +62,7 @@ class _TicketBookingStep2ScreenState extends State<TicketBookingStep2Screen> {
selectedPassengers = args['selectedPassenger'] ?? [];
selectedSeatNumbers = args['selectedSeatNumbers'] ?? List.filled(passenger, '');
logger.d('Ticket ID: $ticketId \nFlight ID: $flightId');
// logger.d('Ticket ID: $ticketId \nFlight ID: $flightId');
}
@override
@ -152,7 +151,7 @@ class _TicketBookingStep2ScreenState extends State<TicketBookingStep2Screen> {
'selectedPassenger': selectedPassengers,
'numberSeat': selectedSeatNumbers
};
logger.d('Number Seat: $selectedSeatNumbers \n Passenger: $selectedPassengers');
// logger.d('Number Seat: $selectedSeatNumbers \n Passenger: $selectedPassengers');
Get.toNamed(Routes.TICKETBOOKINGSTEP3, arguments: argument);
},
),

View File

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:e_porter/_core/component/card/custome_shadow_cotainner.dart';
import 'package:e_porter/_core/component/icons/icons_library.dart';
import 'package:e_porter/_core/constants/colors.dart';
@ -49,6 +51,7 @@ class _TicketBookingStep3ScreenState extends State<TicketBookingStep3Screen> {
String? departureTime;
String? arrivalTime;
double totalPrice = 0.0;
double totalPriceService = 0.0;
PorterServiceModel? selectedDepartureService;
PorterServiceModel? selectedArrivalService;
@ -190,7 +193,7 @@ class _TicketBookingStep3ScreenState extends State<TicketBookingStep3Screen> {
@override
Widget build(BuildContext context) {
double totalPrice = calculateTotalPrice(flightData?.price.toDouble() ?? 0.0, passenger);
totalPrice = calculateTotalPrice(flightData?.price.toDouble() ?? 0.0, passenger);
double grandTotal = totalPrice + totalPriceService;
return Scaffold(
@ -295,16 +298,37 @@ class _TicketBookingStep3ScreenState extends State<TicketBookingStep3Screen> {
selectedPorterServices['transit'] = selectedTransitService;
}
final argument = {
'tickedId': ticketId,
'ticketId': ticketId,
'flightId': flightId,
'ticketDate': ticketDate,
'date': ticketDate,
'passenger': passenger,
'selectedPassenger': selectedPassengers,
'numberSeat': numberSeat,
'totalPrice': totalPrice,
'grandTotal': grandTotal,
'selectedServiceLabels': selectedServiceLabels,
'selectedPorter': selectedPorterServices,
'selectedPorterServices': selectedPorterServices,
};
log('Ticket ID: $ticketId');
log('Flight ID: $flightId');
log('Ticket Date: $ticketDate');
log('Opsi Penerbangan: $selectedServiceLabels');
log('Layanan Porter: $selectedPorterServices');
// final Map<String, dynamic> debugPorterServices = {};
// selectedPorterServices.forEach((key, value) {
// if (value != null) {
// debugPorterServices[key] = {
// 'id': value.id,
// 'name': value.name,
// 'price': value.price,
// };
// } else {
// debugPorterServices[key] = null;
// }
// });
// log('Layanan Porter: $debugPorterServices');
Get.toNamed(Routes.TICKETBOOKINGSTEP4, arguments: argument);
},
),

View File

@ -10,8 +10,11 @@ import 'package:get/get.dart';
import 'package:intl/intl.dart';
import '../../../../_core/component/appbar/appbar_component.dart';
import '../../../../_core/component/icons/icons_library.dart';
import '../../../../_core/service/logger_service.dart';
import '../../../../domain/models/porter_service_model.dart';
import '../../../../domain/models/ticket_model.dart';
import '../../../../domain/models/user_entity.dart';
import '../../../controllers/ticket_controller.dart';
import '../component/card_flight_information.dart';
class TicketBookingStep4Screen extends StatefulWidget {
@ -28,27 +31,91 @@ class _TicketBookingStep4ScreenState extends State<TicketBookingStep4Screen> {
late final int passenger;
late final List<PassengerModel?> selectedPassengers;
late List<String> numberSeat;
late double? grandTotal;
late List<String> selectedServiceLabels;
late Map<String, PorterServiceModel?> selectedPorterServices;
double? totalPrice;
double? grandTotal;
final TicketController ticketController = Get.find<TicketController>();
FlightModel? flightData;
String? departureTime;
String? arrivalTime;
@override
void initState() {
super.initState();
final args = Get.arguments as Map<String, dynamic>;
ticketId = args['ticketId'] ?? '';
flightId = args['flightId'] ?? '';
ticketDate = args['date'] ?? '';
ticketDate = args['date'];
passenger = args['passenger'] ?? 0;
selectedPassengers = args['selectedPassenger'] ?? [];
numberSeat = args['numberSeat'] ?? '';
grandTotal = args['grandTotal'] ?? 0;
totalPrice = args['totalPrice'] ?? 0.0;
grandTotal = args['grandTotal'] ?? 0.0;
selectedServiceLabels = args['selectedServiceLabels'] ?? [];
selectedPorterServices = args['selectedPorterServices'] ?? {};
// log('Ticket ID Step 4: $ticketId');
// log('Opsi Penerbangan Step 4: $selectedServiceLabels');
// if (selectedServiceLabels.isNotEmpty) {
// log('Opsi Penerbangan Step 4: ${selectedServiceLabels.join(", ")}');
// } else {
// log('Tidak ada opsi penerbangan yang dipilih');
// }
// selectedPorterServices.forEach((key, value) {
// if (value != null) {
// log('Porter $key: ${value.name} - Rp ${value.price}');
// }
// });
fetchDataFlight();
}
Future<void> fetchDataFlight() async {
try {
FlightModel flight = await ticketController.getFlightById(ticketId: ticketId, flightId: flightId);
setState(() {
flightData = flight;
departureTime = DateFormat.jm().format(flightData!.departureTime);
arrivalTime = DateFormat.jm().format(flightData!.arrivalTime);
});
} catch (e) {
logger.e('Terjadi kesalahan: $e');
}
}
String getPorterInfo(String type) {
final porter = selectedPorterServices[type];
if (porter != null) {
return '${porter.name}';
}
return '';
}
String getPorterPrice(String type) {
final porter = selectedPorterServices[type];
if (porter != null) {
try {
return NumberFormat.decimalPattern('id_ID').format(porter.price);
} catch (e) {
print("Error formatting porter price: $e");
return '0';
}
}
return '0';
}
@override
Widget build(BuildContext context) {
final hasDeparturePorter =
selectedPorterServices.containsKey('departure') && selectedPorterServices['departure'] != null;
final hasArrivalPorter = selectedPorterServices.containsKey('arrival') && selectedPorterServices['arrival'] != null;
final hasTransitPorter = selectedPorterServices.containsKey('transit') && selectedPorterServices['transit'] != null;
return Scaffold(
backgroundColor: GrayColors.gray50,
appBar: ProgressAppbarComponent(
@ -68,14 +135,18 @@ class _TicketBookingStep4ScreenState extends State<TicketBookingStep4Screen> {
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: CardFlightInformation(
date: "Sen, 27 Jan 2025",
time: "12.20 - 06.00 AM",
departureCity: "Yogyakarta",
arrivalCity: "Lombok",
plane: "Citilink (103)",
seatClass: "Economy",
servicePorter: "Fast Track (FT)",
passenger: "2",
date: "$ticketDate",
time: "$departureTime - $arrivalTime",
departureCity: "${flightData?.cityDeparture}",
arrivalCity: "${flightData?.cityArrival}",
plane: "${flightData?.airLines} (${flightData?.code})",
seatClass: "${flightData?.flightClass}",
passenger: "$passenger",
transiAirplane: "${flightData?.transitAirplane}",
departurePorter: hasDeparturePorter ? "Keberangkatan (${getPorterInfo('departure')})" : null,
arrivalPorter: hasArrivalPorter ? "Kedatangan (${getPorterInfo('arrival')})" : null,
transitPorter: hasTransitPorter ? "Transit (${getPorterInfo('transit')})" : null,
stop: "${flightData?.stop}",
),
),
SizedBox(height: 32.h),
@ -86,13 +157,13 @@ class _TicketBookingStep4ScreenState extends State<TicketBookingStep4Screen> {
SizedBox(height: 20.h),
CustomeShadowCotainner(
sizeRadius: 0.r,
height: MediaQuery.of(context).size.height * 0.5,
height: MediaQuery.of(context).size.height * 0.4,
child: Column(
children: [
Row(
children: [
TypographyStyles.body(
"Citilink (103)",
"${flightData?.airLines} (${flightData?.code})",
color: GrayColors.gray800,
fontWeight: FontWeight.w500,
),
@ -101,30 +172,23 @@ class _TicketBookingStep4ScreenState extends State<TicketBookingStep4Screen> {
],
),
SizedBox(height: 2.h),
Row(
children: [
TypographyStyles.small(
"Fast Track (FT)",
color: GrayColors.gray600,
letterSpacing: 0.2,
fontWeight: FontWeight.w400,
),
SizedBox(width: 10.w),
CircleAvatar(radius: 2.r, backgroundColor: Color(0xFFD9D9D9)),
SizedBox(width: 10.w),
TypographyStyles.small("Economy", color: GrayColors.gray600, fontWeight: FontWeight.w400),
],
_buildRowPorterWithClass(
detailDeparturePorter: hasDeparturePorter ? "${getPorterInfo('departure')}" : null,
detailArrivalPorter: hasArrivalPorter ? "${getPorterInfo('arrival')}" : null,
detailTransitPorter: hasTransitPorter ? "${getPorterInfo('transit')}" : null,
detailSeatClass: "${flightData?.flightClass}",
),
SizedBox(height: 16.h),
Row(
children: [
TypographyStyles.caption(
"Rp 1.200.000",
"Rp ${NumberFormat.decimalPattern('id_ID').format(flightData?.price ?? 0.0)}",
color: GrayColors.gray600,
fontWeight: FontWeight.w400,
),
SizedBox(width: 8.w),
TypographyStyles.small("x 2", color: GrayColors.gray600, fontWeight: FontWeight.w400)
TypographyStyles.small("x ${passenger}",
color: GrayColors.gray600, fontWeight: FontWeight.w400)
],
),
Padding(
@ -140,14 +204,14 @@ class _TicketBookingStep4ScreenState extends State<TicketBookingStep4Screen> {
fontWeight: FontWeight.w400,
),
TypographyStyles.body(
"Rp 2.400.000",
"Rp ${NumberFormat.decimalPattern('id_ID').format(totalPrice ?? 0.0)}",
color: GrayColors.gray600,
fontWeight: FontWeight.w500,
),
],
),
SizedBox(height: 2.h),
_buildRowText(context, text: "Layanan porter (Fast Track)", valueText: "50.000"),
_buildTextService(),
SizedBox(height: 2.h),
_buildRowText(context, text: "Biaya layanan", valueText: "10.000"),
],
@ -167,6 +231,84 @@ class _TicketBookingStep4ScreenState extends State<TicketBookingStep4Screen> {
));
}
Widget _buildRowPorterWithClass({
required String? detailDeparturePorter,
required String? detailArrivalPorter,
required String? detailTransitPorter,
required String detailSeatClass,
}) {
return Row(
children: [
if (detailDeparturePorter != null && detailDeparturePorter.isNotEmpty) ...[
TypographyStyles.small(
detailDeparturePorter,
color: GrayColors.gray600,
fontWeight: FontWeight.w400,
),
SizedBox(width: 10.w),
CircleAvatar(radius: 2.r, backgroundColor: Color(0xFFD9D9D9)),
SizedBox(width: 10.w),
],
if (detailArrivalPorter != null && detailArrivalPorter.isNotEmpty) ...[
TypographyStyles.small(
detailArrivalPorter,
color: GrayColors.gray600,
fontWeight: FontWeight.w400,
),
SizedBox(width: 10.w),
CircleAvatar(radius: 2.r, backgroundColor: Color(0xFFD9D9D9)),
SizedBox(width: 10.h),
],
if (detailTransitPorter != null && detailTransitPorter.isNotEmpty) ...[
TypographyStyles.small(
detailTransitPorter,
color: GrayColors.gray600,
fontWeight: FontWeight.w400,
),
SizedBox(width: 10.w),
CircleAvatar(radius: 2.r, backgroundColor: Color(0xFFD9D9D9)),
SizedBox(width: 10.h),
],
TypographyStyles.small(detailSeatClass, color: GrayColors.gray600, fontWeight: FontWeight.w400),
],
);
}
Widget _buildTextService() {
final hasDeparturePorter =
selectedPorterServices.containsKey('departure') && selectedPorterServices['departure'] != null;
final hasArrivalPorter = selectedPorterServices.containsKey('arrival') && selectedPorterServices['arrival'] != null;
final hasTransitPorter = selectedPorterServices.containsKey('transit') && selectedPorterServices['transit'] != null;
final hasAnyPorter = hasDeparturePorter || hasArrivalPorter || hasTransitPorter;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!hasAnyPorter)
_buildContainerText(left: 0.w, label: 'Layanan Porter', price: '-')
else
_buildContainerText(left: 0.w, label: 'Layanan Porter', price: ''),
if (hasDeparturePorter)
_buildContainerText(
left: 8.w,
label: "Keberangkatan (${getPorterInfo('departure')})",
price: "Rp ${getPorterPrice('departure')}",
),
if (hasArrivalPorter)
_buildContainerText(
left: 8.w,
label: "Kedatangan (${getPorterInfo('arrival')})",
price: "Rp ${getPorterPrice('arrival')}",
),
if (hasTransitPorter)
_buildContainerText(
left: 8.w,
label: "Transit (${getPorterInfo('transit')})",
price: "Rp ${getPorterPrice('transit')}",
),
],
);
}
Widget _buildRowText(
BuildContext context, {
required String text,
@ -188,4 +330,21 @@ class _TicketBookingStep4ScreenState extends State<TicketBookingStep4Screen> {
],
);
}
Widget _buildContainerText({
required String label,
required String price,
required double left,
}) {
return Container(
padding: EdgeInsets.only(left: left),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TypographyStyles.caption(label, color: GrayColors.gray600, fontWeight: FontWeight.w400),
TypographyStyles.caption(price, color: GrayColors.gray600, fontWeight: FontWeight.w400),
],
),
);
}
}