Merge pull request #821 from Tria-plc/quick-fix-main

Passenger list report filter updates
This commit is contained in:
Stephanos A.
2026-07-19 22:53:18 +03:00
committed by GitHub
2 changed files with 42 additions and 17 deletions

View File

@@ -328,7 +328,7 @@ export class ReportsService {
_count: { select: { seats: true } },
},
},
seat: { include: { coach: { select: { number: true, coachType: { select: { name: true } } } } } },
seat: { select: { seatNumber: true, bedPosition: true, coach: { select: { number: true, coachType: { select: { name: true, seatClasses: { select: { name: true, bedPosition: true } } } } } } } },
},
orderBy: [{ seat: { coach: { number: 'asc' } } }, { seat: { seatNumber: 'asc' } }],
});
@@ -356,13 +356,25 @@ export class ReportsService {
passportNumber: bs.passportNumber,
passportCountry: bs.passportCountry,
seatLabel: bs.seatLabelSnapshot,
seatNumber: bs.seat?.seatNumber ?? null,
seatClassName: (() => {
const classes = bs.seat?.coach?.coachType?.seatClasses ?? [];
const matched = bs.seat?.bedPosition
? classes.find((sc: any) => sc.bedPosition?.toLowerCase() === bs.seat!.bedPosition!.toLowerCase())
: null;
return (matched ?? classes[0])?.name ?? bs.seat?.coach?.coachType?.name ?? null;
})(),
coachNumber: bs.seat?.coach?.number ?? null,
coachType: (bs.seat?.coach as any)?.coachType?.name ?? null,
coachType: bs.seat?.coach?.coachType?.name ?? null,
nationality: bs.passportCountry
? (bs.passportCountry === 'Djibouti' ? 'Djiboutian' : bs.passportCountry)
: bs.idDocumentType === 'NATIONAL_ID' ? 'Ethiopian' : null,
origin: bs.booking.originStationId ? (stationName.get(bs.booking.originStationId) ?? null) : null,
destination: bs.booking.destinationStationId ? (stationName.get(bs.booking.destinationStationId) ?? null) : null,
amountPaidMinor: bs.booking.totalMinor,
currency: bs.booking.currency ?? 'ETB',
isGroupBooking: (bs.booking._count?.seats ?? 0) > 1,
bookingStatus: bs.booking.status,
}));
}

View File

@@ -27,13 +27,17 @@ interface PassengerRow {
passportNumber: string | null;
passportCountry: string | null;
seatLabel: string | null;
seatNumber: string | null;
seatClassName: string | null;
coachNumber: string | null;
coachType: string | null;
nationality: string | null;
origin: string | null;
destination: string | null;
amountPaidMinor: number;
currency: string;
isGroupBooking: boolean;
bookingStatus: string;
}
type Tab = 'occupancy' | 'list';
@@ -44,6 +48,7 @@ export default function PassengersReportPage() {
const [listSearch, setListSearch] = useState('');
const [filterCoach, setFilterCoach] = useState('');
const [filterOrigin, setFilterOrigin] = useState('');
const [filterSeatClass, setFilterSeatClass] = useState('');
const { data: schedulesRaw, isLoading: loadingSchedules } = useQuery<ScheduleOption[]>({
queryKey: ['report-schedules'],
@@ -65,11 +70,13 @@ export default function PassengersReportPage() {
const coachOptions = [...new Set(passengerList.map(p => p.coachNumber).filter(Boolean))].sort() as string[];
const originOptions = [...new Set(passengerList.map(p => p.origin).filter(Boolean))].sort() as string[];
const seatClassOptions = [...new Set(passengerList.map(p => p.seatClassName).filter(Boolean))].sort() as string[];
const filteredList = passengerList
.filter(p => {
if (filterCoach && p.coachNumber !== filterCoach) return false;
if (filterOrigin && p.origin !== filterOrigin) return false;
if (filterSeatClass && p.seatClassName !== filterSeatClass) return false;
if (listSearch.trim()) {
const q = listSearch.toLowerCase();
return (
@@ -126,7 +133,7 @@ export default function PassengersReportPage() {
<select
className="input"
value={scheduleId}
onChange={e => { setScheduleId(e.target.value); setTab('occupancy'); setListSearch(''); setFilterCoach(''); setFilterOrigin(''); }}
onChange={e => { setScheduleId(e.target.value); setTab('occupancy'); setListSearch(''); setFilterCoach(''); setFilterOrigin(''); setFilterSeatClass(''); }}
disabled={loadingSchedules}
>
<option value="">{loadingSchedules ? 'Loading schedules…' : 'Select a schedule…'}</option>
@@ -294,6 +301,14 @@ export default function PassengersReportPage() {
value={listSearch}
onChange={e => setListSearch(e.target.value)}
/>
<select className="input w-40" value={filterSeatClass} onChange={e => setFilterSeatClass(e.target.value)}>
<option value="">All classes</option>
{seatClassOptions.map(c => <option key={c} value={c}>{c}</option>)}
</select>
<select className="input w-36" value={filterOrigin} onChange={e => setFilterOrigin(e.target.value)}>
<option value="">All origins</option>
{originOptions.map(o => <option key={o} value={o}>{o}</option>)}
</select>
{passengerList.length > 0 && (
<ActionButton icon={Download} variant="secondary" onClick={doExportList}>Export CSV</ActionButton>
)}
@@ -314,20 +329,11 @@ export default function PassengersReportPage() {
{filteredList.map((p, i) => (
<tr key={`${p.bookingRef}-${i}`} className="hover:bg-muted/30">
<td className="py-2 pr-4 font-medium">{p.passengerName}</td>
<td className="py-2 pr-4 text-xs">
{p.passportNumber ? (
<>
<span className="text-muted-foreground">{p.passportCountry ?? 'Intl'}</span>
<span className="ml-1 font-mono">{p.passportNumber}</span>
</>
) : (
<span className="text-muted-foreground">{p.idDocumentNumber ?? '—'}</span>
)}
<td className="py-2 pr-4 text-xs whitespace-nowrap">
{[p.nationality, p.passportNumber].filter(Boolean).join(' · ') || '—'}
</td>
<td className="py-2 pr-4 font-mono text-xs">
{p.coachNumber && p.seatLabel
? <>{p.coachNumber} · {p.seatLabel}{p.coachType && <span className="font-sans text-muted-foreground ml-1">({p.coachType})</span>}</>
: (p.coachNumber ?? p.seatLabel ?? '—')}
<td className="py-2 pr-4 font-mono text-xs whitespace-nowrap">
{[p.coachNumber, p.seatNumber ?? p.seatLabel, p.seatClassName].filter(Boolean).join(' · ') || '—'}
</td>
<td className="py-2 pr-4 text-muted-foreground text-xs">
{p.origin && p.destination ? `${p.origin}${p.destination}` : (p.origin ?? p.destination ?? '—')}
@@ -342,7 +348,14 @@ export default function PassengersReportPage() {
)}
</div>
</td>
<td className="py-2 font-mono text-xs">{p.bookingRef}</td>
<td className="py-2 font-mono text-xs">
<div className="flex items-center gap-1.5">
{p.bookingRef}
{p.bookingStatus !== 'CONFIRMED' && p.bookingStatus !== 'BOARDED' && (
<span className="inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-semibold bg-yellow-100 text-yellow-700 dark:bg-yellow-900/40 dark:text-yellow-300">{p.bookingStatus.replace('_', ' ')}</span>
)}
</div>
</td>
</tr>
))}
{filteredList.length === 0 && (