feat: / shows current/next trip.

This commit is contained in:
Felix Zett 2025-08-30 21:14:23 +02:00
parent 3186a9d549
commit 6e6c23b6ad
4 changed files with 108 additions and 54 deletions

View file

@ -1,4 +1,4 @@
from datetime import date
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session, joinedload
from uuid import UUID
@ -129,3 +129,16 @@ def reconfigure_trip(trip_id: UUID, payload: TripUpdate, db: Session = Depends(g
"deleted_checked_trip_item_ids": deleted_checked,
"created_trip_item_ids": created_ids,
}
@router.get("/next-id", response_model=UUID)
def get_next_trip_id(db: Session = Depends(get_db)):
today = date.today()
trip = (
db.query(models.Trip)
.filter(models.Trip.start_date >= today)
.order_by(models.Trip.start_date.asc())
.first()
)
if not trip:
raise HTTPException(status_code=404, detail="No upcoming trip found")
return trip.id

View file

@ -1,11 +1,27 @@
import React, { useState, useEffect } from "react";
import { BrowserRouter as Router, Routes, Route, Link, Navigate, useParams } from "react-router-dom";
import { getSeed, getTrips, getTripItems, toggleTripItem } from "./api";
import { BrowserRouter as Router, Routes, Route, Link, Navigate, useNavigate } from "react-router-dom";
import { getSeed, getTrips, getNextTripId } from "./api";
import ItemsPage from "./pages/ItemsPage";
import TripChecklist from "./pages/TripChecklist";
function NextTripRedirect({ trips }: { trips: any[] }) {
const [nextTripId, setNextTripId] = React.useState<string | null>(null);
const [error, setError] = React.useState<string | null>(null);
React.useEffect(() => {
getNextTripId()
.then(setNextTripId)
.catch(() => setError("Kein anstehender Trip gefunden"));
}, []);
if (error) return <div>{error}</div>;
if (!nextTripId) return <div>Lade...</div>;
return <Navigate to={`/trips/${nextTripId}`} replace />;
}
export default function App() {
const [trips, setTrips] = useState<any[]>([]);
const navigate = useNavigate();
async function loadTrips() {
const data = await getTrips();
@ -16,59 +32,75 @@ export default function App() {
loadTrips();
}, []);
async function goToCurrentTrip() {
try {
const id = await getNextTripId();
navigate(`/trips/${id}`);
} catch {
alert("Kein anstehender Trip gefunden");
}
}
return (
<Router>
<div className="p-4 max-w-2xl mx-auto">
<h1 className="text-2xl font-bold mb-4">Packlist</h1>
<div className="p-4 max-w-2xl mx-auto">
<h1 className="text-2xl font-bold mb-4">Packlist</h1>
<div className="flex gap-2 mb-4">
<Link to="/trips">
<button className="bg-blue-500 text-white px-4 py-2 rounded">
Alle Trips
</button>
</Link>
<Link to="/items">
<button className="bg-green-500 text-white px-4 py-2 rounded">
Alle Items
</button>
</Link>
<button
className="bg-gray-300 text-gray-800 px-4 py-2 rounded"
onClick={async () => {
await getSeed();
await loadTrips();
}}
>
Seed-Daten erzeugen
<div className="flex gap-2 mb-4">
<button
className="bg-yellow-500 text-white px-4 py-2 rounded"
onClick={goToCurrentTrip}
>
Zum aktuellen Trip
</button>
<Link to="/trips">
<button className="bg-blue-500 text-white px-4 py-2 rounded">
Alle Trips
</button>
</div>
<Routes>
<Route
path="/trips"
element={
<ul>
{trips.map((trip) => (
<li key={trip.id} className="mb-2">
<Link
to={`/trips/${trip.id}`}
className="text-blue-600 underline"
>
{trip.name} <span className="text-gray-500">({trip.start_date} {trip.end_date})</span>
</Link>
</li>
))}
</ul>
}
/>
<Route
path="/trips/:id"
element={<TripChecklist trips={trips} />}
/>
<Route path="/items" element={<ItemsPage />} />
<Route path="/" element={<Navigate to="/trips" />} />
</Routes>
</Link>
<Link to="/items">
<button className="bg-green-500 text-white px-4 py-2 rounded">
Alle Items
</button>
</Link>
<button
className="bg-gray-300 text-gray-800 px-4 py-2 rounded"
onClick={async () => {
await getSeed();
await loadTrips();
}}
>
Seed-Daten erzeugen
</button>
</div>
</Router>
<Routes>
<Route
path="/trips"
element={
<ul>
{trips.map((trip) => (
<li key={trip.id} className="mb-2">
<Link
to={`/trips/${trip.id}`}
className="text-blue-600 underline"
>
{trip.name} <span className="text-gray-500">({trip.start_date} {trip.end_date})</span>
</Link>
</li>
))}
</ul>
}
/>
<Route
path="/trips/:id"
element={<TripChecklist trips={trips} />}
/>
<Route path="/items" element={<ItemsPage />} />
<Route path="/" element={<NextTripRedirect trips={trips} />} />
</Routes>
</div>
);
}
// Wichtig: Der <App />-Inhalt muss innerhalb von <Router> verwendet werden!
// In main.tsx ist das bereits der Fall.

View file

@ -82,3 +82,9 @@ export async function createItem(name: string, tags: string[]): Promise<Item> {
if (!res.ok) throw new Error("Failed to create item");
return res.json();
}
export async function getNextTripId(): Promise<string> {
const res = await fetch(`${API_BASE}/trips/next-id`);
if (!res.ok) throw new Error("No upcoming trip found");
return res.json();
}

View file

@ -3,13 +3,16 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';
import { BrowserRouter } from "react-router-dom";
const queryClient = new QueryClient();
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
<BrowserRouter>
<App />
</BrowserRouter>
</QueryClientProvider>
</React.StrictMode>
);