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 fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session, joinedload from sqlalchemy.orm import Session, joinedload
from uuid import UUID 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, "deleted_checked_trip_item_ids": deleted_checked,
"created_trip_item_ids": created_ids, "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 React, { useState, useEffect } from "react";
import { BrowserRouter as Router, Routes, Route, Link, Navigate, useParams } from "react-router-dom"; import { BrowserRouter as Router, Routes, Route, Link, Navigate, useNavigate } from "react-router-dom";
import { getSeed, getTrips, getTripItems, toggleTripItem } from "./api"; import { getSeed, getTrips, getNextTripId } from "./api";
import ItemsPage from "./pages/ItemsPage"; import ItemsPage from "./pages/ItemsPage";
import TripChecklist from "./pages/TripChecklist"; 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() { export default function App() {
const [trips, setTrips] = useState<any[]>([]); const [trips, setTrips] = useState<any[]>([]);
const navigate = useNavigate();
async function loadTrips() { async function loadTrips() {
const data = await getTrips(); const data = await getTrips();
@ -16,12 +32,26 @@ export default function App() {
loadTrips(); loadTrips();
}, []); }, []);
async function goToCurrentTrip() {
try {
const id = await getNextTripId();
navigate(`/trips/${id}`);
} catch {
alert("Kein anstehender Trip gefunden");
}
}
return ( return (
<Router>
<div className="p-4 max-w-2xl mx-auto"> <div className="p-4 max-w-2xl mx-auto">
<h1 className="text-2xl font-bold mb-4">Packlist</h1> <h1 className="text-2xl font-bold mb-4">Packlist</h1>
<div className="flex gap-2 mb-4"> <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"> <Link to="/trips">
<button className="bg-blue-500 text-white px-4 py-2 rounded"> <button className="bg-blue-500 text-white px-4 py-2 rounded">
Alle Trips Alle Trips
@ -66,9 +96,11 @@ export default function App() {
element={<TripChecklist trips={trips} />} element={<TripChecklist trips={trips} />}
/> />
<Route path="/items" element={<ItemsPage />} /> <Route path="/items" element={<ItemsPage />} />
<Route path="/" element={<Navigate to="/trips" />} /> <Route path="/" element={<NextTripRedirect trips={trips} />} />
</Routes> </Routes>
</div> </div>
</Router>
); );
} }
// 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"); if (!res.ok) throw new Error("Failed to create item");
return res.json(); 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 ReactDOM from 'react-dom/client';
import App from './App'; import App from './App';
import './index.css'; import './index.css';
import { BrowserRouter } from "react-router-dom";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
ReactDOM.createRoot(document.getElementById('root')!).render( ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode> <React.StrictMode>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<BrowserRouter>
<App /> <App />
</BrowserRouter>
</QueryClientProvider> </QueryClientProvider>
</React.StrictMode> </React.StrictMode>
); );