diff --git a/apps/edr-passenger-api/src/modules/reports/reports.controller.ts b/apps/edr-passenger-api/src/modules/reports/reports.controller.ts index 7df394090..90c8f51b8 100644 --- a/apps/edr-passenger-api/src/modules/reports/reports.controller.ts +++ b/apps/edr-passenger-api/src/modules/reports/reports.controller.ts @@ -18,10 +18,10 @@ export class ReportsController { return this.service.generateReport(dto); } - @Get("schedules") - @ApiOperation({ summary: "List schedules for the passengers report picker" }) - listSchedulesForPicker() { - return this.service.listSchedulesForPicker(); + @Get('schedules') + @ApiOperation({ summary: 'List schedules for the passengers report picker' }) + listSchedulesForPicker(@Query('all') all?: string) { + return this.service.listSchedulesForPicker(all === 'true'); } @Get("passengers/list") diff --git a/apps/edr-passenger-api/src/modules/reports/reports.service.ts b/apps/edr-passenger-api/src/modules/reports/reports.service.ts index 9460aa5a9..f74bf74b8 100644 --- a/apps/edr-passenger-api/src/modules/reports/reports.service.ts +++ b/apps/edr-passenger-api/src/modules/reports/reports.service.ts @@ -411,10 +411,10 @@ export class ReportsService { }; } - async listSchedulesForPicker() { + async listSchedulesForPicker(all = false) { const now = new Date(); const schedules = await this.prisma.trainSchedule.findMany({ - where: { departureAt: { gte: now } }, + where: all ? undefined : { departureAt: { gte: now } }, select: { id: true, departureAt: true, @@ -423,7 +423,7 @@ export class ReportsService { originStation: { select: { name: true } }, destinationStation: { select: { name: true } }, }, - orderBy: { departureAt: 'asc' }, + orderBy: { departureAt: all ? 'desc' : 'asc' }, take: 200, }); return schedules.map((s) => ({ @@ -439,8 +439,9 @@ export class ReportsService { async getPassengerList(scheduleId: string) { const seats = await this.prisma.bookingSeat.findMany({ where: { + scheduleId, leg: 1, - booking: { scheduleId, status: { in: ["CONFIRMED", "BOARDED"] } }, + booking: { status: { in: ["CONFIRMED", "BOARDED"] } }, }, include: { booking: { @@ -507,8 +508,9 @@ export class ReportsService { // Booked seats — exclude dining coaches const bookingSeats = await this.prisma.bookingSeat.findMany({ where: { + scheduleId, leg: 1, - booking: { scheduleId, status: { in: ['CONFIRMED', 'BOARDED', 'PENDING_PAYMENT'] } }, + booking: { status: { in: ['CONFIRMED', 'BOARDED', 'PENDING_PAYMENT'] } }, seat: { coach: { coachType: { type: { not: 'dining' } } } }, }, include: { diff --git a/apps/edr-passenger-web/backoffice/src/app/reports/passengers/page.tsx b/apps/edr-passenger-web/backoffice/src/app/reports/passengers/page.tsx index a028faa8c..d4d78ab01 100644 --- a/apps/edr-passenger-web/backoffice/src/app/reports/passengers/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/reports/passengers/page.tsx @@ -56,11 +56,9 @@ interface PassengerRow { seatClassName: string | null; coachNumber: string | null; coachType: string | null; - coachSeat: string | null; nationality: string | null; origin: string | null; destination: string | null; - departureAt: string | null; amountPaidMinor: number; currency: string; isGroupBooking: boolean; @@ -73,7 +71,6 @@ export default function PassengersReportPage() { const [scheduleId, setScheduleId] = useState(""); const [tab, setTab] = useState("occupancy"); const [listSearch, setListSearch] = useState(""); - const [filterCoach, setFilterCoach] = useState(""); const [filterOrigin, setFilterOrigin] = useState(""); const [filterSeatClass, setFilterSeatClass] = useState(""); const [filterCoachNumber, setFilterCoachNumber] = useState(""); @@ -81,8 +78,8 @@ export default function PassengersReportPage() { const { data: schedulesRaw, isLoading: loadingSchedules } = useQuery< ScheduleOption[] >({ - queryKey: ["report-schedules"], - queryFn: () => apiClient.get("/reports/schedules"), + queryKey: ["report-schedules-all"], + queryFn: () => apiClient.get("/reports/schedules?all=true"), }); const schedules = schedulesRaw ?? []; @@ -102,7 +99,7 @@ export default function PassengersReportPage() { enabled: !!scheduleId, }); - const coachOptions = [ + const coachNumberOptions = [ ...new Set(passengerList.map((p) => p.coachNumber).filter(Boolean)), ].sort() as string[]; const seatClassOptions = [ @@ -111,11 +108,9 @@ export default function PassengersReportPage() { const originOptions = [ ...new Set(passengerList.map((p) => p.origin).filter(Boolean)), ].sort() as string[]; - const coachNumberOptions = coachOptions; const filteredList = passengerList .filter((p) => { - if (filterCoach && p.coachNumber !== filterCoach) return false; if (filterCoachNumber && p.coachNumber !== filterCoachNumber) return false; if (filterOrigin && p.origin !== filterOrigin) return false; if (filterSeatClass && p.seatClassName !== filterSeatClass) return false; @@ -161,29 +156,24 @@ export default function PassengersReportPage() { }; const doExportList = () => { - if (!passengerList.length) return; - const headers = [ - "#", - "Name", - "Coach·Seat", - "Origin", - "Destination", - "Date", - "Booking Ref", - ]; - const rows = passengerList.map((p, i) => + if (!filteredList.length) return; + const headers = ['Name', 'Nationality', 'Passport Number', 'Seat Class', 'Coach', 'Seat', 'Origin', 'Destination', 'Amount Paid (ETB)', 'Booking Ref']; + const rows = filteredList.map((p) => [ - String(i + 1), p.passengerName, - p.coachSeat, - p.origin, - p.destination, - p.departureAt ? formatDateTime(p.departureAt) : "—", + p.nationality ?? '—', + p.passportNumber ?? '—', + p.seatClassName ?? '—', + p.coachNumber ?? '—', + p.seatNumber ?? '—', + p.origin ?? '—', + p.destination ?? '—', + (p.amountPaidMinor / 100).toFixed(2), p.bookingRef, ].map((v) => `"${String(v).replace(/"/g, '""')}"`), ); downloadCsv( - [headers.join(","), ...rows.map((r) => r.join(","))].join("\n"), + [headers.join(','), ...rows.map((r) => r.join(','))].join('\n'), `passengers-${scheduleId}.csv`, ); }; @@ -211,7 +201,6 @@ export default function PassengersReportPage() { setScheduleId(e.target.value); setTab("occupancy"); setListSearch(""); - setFilterCoach(""); setFilterCoachNumber(""); setFilterOrigin(""); setFilterSeatClass(""); @@ -520,94 +509,41 @@ export default function PassengersReportPage() { - - - - - - - - - - + + + + + + {filteredList.map((p, i) => ( - - + + - - - + + ))} {filteredList.length === 0 && ( - + )}
NameNationalityPassportCoachSeatClassOriginDestinationAmount PaidBooking RefNameNationalitySeat Class · Coach · SeatRouteAmount PaidBooking Ref
- {p.passengerName} -
{p.passengerName} - {p.passportNumber ? ( - <> - - {p.passportCountry ?? "Intl"} - - - {p.passportNumber} - - - ) : ( - - {p.idDocumentNumber ?? "—"} - + {p.nationality ?? '—'} + {p.passportNumber && ( + {p.passportNumber} )} - {p.coachNumber && p.seatLabel ? ( - <> - {p.coachNumber} · {p.seatLabel} - {p.coachType && ( - - ({p.coachType}) - - )} - - ) : ( - (p.coachNumber ?? p.seatLabel ?? "—") - )} - - {p.origin && p.destination - ? `${p.origin} → ${p.destination}` - : (p.origin ?? p.destination ?? "—")} - -
- - {(p.amountPaidMinor / 100).toLocaleString( - "en-US", - { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }, - )}{" "} - {p.currency} - - {p.isGroupBooking && ( - - Group - - )} -
+ {p.seatClassName ?? '—'} + {p.coachNumber && · {p.coachNumber}} + {p.seatNumber && · #{p.seatNumber}}
- {p.bookingRef} + + {p.origin && p.destination ? `${p.origin} → ${p.destination}` : (p.origin ?? p.destination ?? '—')} + {(p.amountPaidMinor / 100).toLocaleString('en-US', { minimumFractionDigits: 2 })} {p.currency} + {p.bookingRef}
- No passengers found - No passengers found