feat: / shows current/next trip.
This commit is contained in:
parent
3186a9d549
commit
6e6c23b6ad
4 changed files with 108 additions and 54 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
Loading…
Reference in a new issue