mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
Merge pull request #821 from Tria-plc/quick-fix-main
Passenger list report filter updates
This commit is contained in:
@@ -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,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
Reference in New Issue
Block a user