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 a4da5b208..82a07f98f 100644 --- a/apps/edr-passenger-api/src/modules/reports/reports.controller.ts +++ b/apps/edr-passenger-api/src/modules/reports/reports.controller.ts @@ -18,6 +18,12 @@ 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('passengers') @ApiOperation({ summary: 'Passengers report for a specific schedule' }) getOccupancyReport(@Query('scheduleId') scheduleId: string) { 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 f4141ecb6..9037188e2 100644 --- a/apps/edr-passenger-api/src/modules/reports/reports.service.ts +++ b/apps/edr-passenger-api/src/modules/reports/reports.service.ts @@ -286,6 +286,24 @@ export class ReportsService { }; } + async listSchedulesForPicker() { + const schedules = await this.prisma.trainSchedule.findMany({ + select: { + id: true, + departureAt: true, + train: { select: { number: true } }, + originStation: { select: { name: true } }, + destinationStation: { select: { name: true } }, + }, + orderBy: { departureAt: 'desc' }, + take: 200, + }); + return schedules.map(s => ({ + id: s.id, + label: `${s.train.number} · ${s.originStation.name} → ${s.destinationStation.name} · ${new Date(s.departureAt).toLocaleString('en-GB', { dateStyle: 'medium', timeStyle: 'short' })}`, + })); + } + async getReport(reportId: string) { return this.prisma.operationalReport.findUnique({ where: { id: reportId } }); } 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 8b7ef240f..83b803331 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 @@ -7,15 +7,10 @@ import { apiClient } from '@/lib/api-client'; import ActionButton from '@/components/ui/ActionButton'; import { formatDateTime } from '@/lib/utils'; +interface ScheduleOption { id: string; label: string; } + interface PassengersReport { - schedule: { - id: string; - trainName: string; - origin: string; - destination: string; - departureAt: string; - arrivalAt: string; - }; + schedule: { id: string; trainName: string; origin: string; destination: string; departureAt: string; arrivalAt: string; }; summary: { totalSeats: number; totalPassengers: number; occupancyRate: number }; byCoach: { coachNumber: string; coachType: string; totalSeats: number; booked: number; occupancyRate: number }[]; byClass: { className: string; totalSeats: number; booked: number; occupancyRate: number }[]; @@ -25,8 +20,20 @@ interface PassengersReport { export default function PassengersReportPage() { const [scheduleId, setScheduleId] = useState(''); + const [search, setSearch] = useState(''); const [submittedId, setSubmittedId] = useState(''); + const { data: schedules = [], isLoading: loadingSchedules } = useQuery({ + queryKey: ['report-schedules'], + queryFn: () => apiClient.get('/reports/schedules'), + }); + + const filtered = search.trim() + ? schedules.filter(s => s.label.toLowerCase().includes(search.toLowerCase())) + : schedules; + + const selected = schedules.find(s => s.id === scheduleId); + const { data, isLoading, isError } = useQuery({ queryKey: ['passengers-report', submittedId], queryFn: () => apiClient.get(`/reports/passengers?scheduleId=${submittedId}`), @@ -54,20 +61,43 @@ export default function PassengersReportPage() {

Occupancy and passenger breakdown for a schedule

- {/* Schedule ID input */} + {/* Schedule picker */}
-
- - setScheduleId(e.target.value)} - /> +
+ +
+ { setSearch(e.target.value); setScheduleId(''); }} + onFocus={(e) => { setSearch(e.target.value); }} + /> + {(search || scheduleId) && ( + + )} +
+ {search && !scheduleId && ( +
+ {filtered.length === 0 + ?

No schedules found

+ : filtered.map(s => ( + + )) + } +
+ )}
- setSubmittedId(scheduleId)} disabled={!scheduleId.trim() || isLoading}> + setSubmittedId(scheduleId)} disabled={!scheduleId || isLoading}> Load Report {data && ( @@ -77,7 +107,7 @@ export default function PassengersReportPage() { )}
{isLoading &&

Loading…

} - {isError &&

Failed to load report. Check the schedule ID.

} + {isError &&

Failed to load report.

}
{data && ( @@ -158,7 +188,6 @@ export default function PassengersReportPage() { {/* By Class + By Origin/Destination */}
- {/* By Class */}

By Class

@@ -179,7 +208,6 @@ export default function PassengersReportPage() {
- {/* By Origin */}

By Boarding Station

@@ -193,7 +221,6 @@ export default function PassengersReportPage() {
- {/* By Destination */}

By Alighting Station