Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions frontend/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { StatusBar } from '@/components/ui/StatusBar';
import { Header } from '@/components/passenger/Header';
import { TripCard } from '@/components/passenger/TripCard';
import { LostItemModal } from '@/components/passenger/LostItemModal';
import { MyReports } from '@/components/passenger/MyReports';
import { BottomNav, type NavTab } from '@/components/ui/BottomNav';
import { Toast } from '@/components/ui/Toast';
import { mockUser, formatRelativeTime } from '@/lib/mock-data';
Expand Down Expand Up @@ -298,6 +299,9 @@ export default function PassengerApp() {
))}
</div>
</section>

{/* What this passenger reported, and how each one ended */}
<MyReports />
</div>
);
};
Expand Down
73 changes: 73 additions & 0 deletions frontend/components/passenger/MyReports.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
'use client';

import { ITEM_CATEGORY_CONFIG } from '@/lib/types';
import { NOTIFICATION_STATUS_CONFIG, UI_LABELS } from '@/lib/labels';
import { formatRelativeTime } from '@/lib/mock-data';
import { useDemoReports } from '@/lib/hooks';

/**
* What this passenger reported, and how each one ended.
*
* The crew's answer used to reach the passenger as a four-second toast and
* nothing else: look away, or have the phone asleep, and there was no way to
* find out afterwards what the crew said. This is the record that outlives the
* toast — same data, read back from the handover.
*
* Renders nothing before the first report, rather than an empty box explaining
* its own emptiness on a screen the passenger opened to see their trips.
*/
export function MyReports() {
const reports = useDemoReports();

if (reports.length === 0) return null;

return (
<section className="mt-6">
<h2 className="text-app-sm font-medium text-app-granite mb-3">{UI_LABELS.myReports.title}</h2>
<ul className="space-y-2">
{reports.map((report) => {
const status = NOTIFICATION_STATUS_CONFIG[report.status];
const category = ITEM_CATEGORY_CONFIG[report.category];

return (
<li key={report.id} className="bg-white rounded-app-lg p-4 shadow-app-card">
<div className="flex items-start gap-3">
<span className="text-2xl" aria-hidden="true">
{category.icon}
</span>

<div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-2">
<h3 className="text-app-base font-medium text-app-charcoal">
{report.message}
</h3>
<span
className={`shrink-0 px-2 py-0.5 rounded-full text-app-xs font-medium ${status.color} ${status.textColor}`}
>
{status.label}
</span>
</div>

<p className="text-app-sm text-app-granite mt-0.5">{report.location}</p>
<p className="text-app-xs text-app-smoke mt-1">
{UI_LABELS.myReports.reportedAt} {formatRelativeTime(report.createdAt)}
{report.passengerInfo?.tripRoute && ` • ${report.passengerInfo.tripRoute}`}
</p>

{report.response?.notes && (
<div className="mt-3 pt-3 border-t border-app-cloud">
<p className="text-app-xs text-app-granite mb-0.5">
{UI_LABELS.myReports.staffNote}
</p>
<p className="text-app-sm text-app-charcoal">{report.response.notes}</p>
</div>
)}
</div>
</div>
</li>
);
})}
</ul>
</section>
);
}
2 changes: 2 additions & 0 deletions frontend/lib/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ export {
useReportLostItem,
useApiHealth,
} from './useApi';

export { useDemoReports } from './useDemoReports';
28 changes: 28 additions & 0 deletions frontend/lib/hooks/useDemoReports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use client';

import { useState, useEffect } from 'react';
import { readReports, subscribeReports } from '../demo-bus';
import type { StaffNotification } from '../types';

/**
* The reports filed from this browser, with whatever the crew answered.
*
* Empty until mount by design: localStorage does not exist on the server, so
* reading it during render would make the first paint disagree with the HTML
* Next sent. The subscription then keeps the list live — a crew answer landing
* in the other tab updates it without a reload.
*/
export function useDemoReports(): StaffNotification[] {
const [reports, setReports] = useState<StaffNotification[]>([]);

useEffect(() => {
// Loading from an external system, which is the case the
// set-state-in-effect rule's own docs call legitimate — there is no way to
// express "read storage on mount" without it.
// eslint-disable-next-line react-hooks/set-state-in-effect
setReports(readReports());
return subscribeReports(setReports);
}, []);

return reports;
}
7 changes: 7 additions & 0 deletions frontend/lib/labels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,13 @@ export const UI_LABELS = {
step3: 'Abholung koordinieren',
},

// The passenger's own reports and how each one ended
myReports: {
title: 'Meine Verlustmeldungen',
staffNote: 'Rückmeldung des Personals',
reportedAt: 'Gemeldet',
},

// Time
time: {
justNow: 'gerade eben',
Expand Down
Loading