146 lines
4.4 KiB
TypeScript
146 lines
4.4 KiB
TypeScript
import React, { useState, useEffect } from "react";
|
|
import { BrowserRouter as Router, Routes, Route, Link, useLocation, Navigate, useNavigate } from "react-router-dom";
|
|
import { getSeed, getTrips, getNextTripId } from "./api";
|
|
import ItemsPage from "./pages/ItemsPage";
|
|
import TripChecklist from "./pages/TripChecklist";
|
|
import TripsPage from "./pages/TripsPage";
|
|
import TagsPage from "./pages/TagsPage";
|
|
|
|
const APP_TITLE = "da packste dich weg";
|
|
|
|
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 />;
|
|
}
|
|
|
|
function Navigation() {
|
|
const location = useLocation();
|
|
const navItems = [
|
|
{ to: "/trips", label: "Trips" },
|
|
{ to: "/items", label: "Items" },
|
|
{ to: "/tags", label: "Tags" },
|
|
];
|
|
|
|
// Navigation für den Titel-Link
|
|
const navigate = useNavigate();
|
|
async function goToCurrentTrip() {
|
|
try {
|
|
const id = await getNextTripId();
|
|
navigate(`/trips/${id}`);
|
|
} catch {
|
|
alert("Kein anstehender Trip gefunden");
|
|
}
|
|
}
|
|
|
|
// Prüfe, ob ein einzelner Trip angezeigt wird
|
|
const isTripDetail = /^\/trips\/[^/]+$/.test(location.pathname);
|
|
|
|
return (
|
|
<nav className="mb-2 flex items-center gap-4 bg-white/80 backdrop-blur border-b border-gray-200 px-2 py-2 rounded-xl shadow-sm">
|
|
<button
|
|
className="text-2xl font-extrabold text-blue-700 tracking-tight mr-4 select-none hover:underline bg-transparent p-0"
|
|
style={{ background: "none", border: "none" }}
|
|
onClick={goToCurrentTrip}
|
|
title="Zum aktuellen Trip"
|
|
>
|
|
{APP_TITLE}
|
|
</button>
|
|
{navItems.map((item) => {
|
|
// Trips ist NICHT aktiv, wenn ein einzelner Trip angezeigt wird
|
|
const isActive =
|
|
!isTripDetail && location.pathname.startsWith(item.to);
|
|
return (
|
|
<Link
|
|
key={item.to}
|
|
to={item.to}
|
|
className={
|
|
"px-4 py-2 rounded font-medium transition " +
|
|
(isActive
|
|
? "bg-blue-600 text-white shadow"
|
|
: "text-blue-700 hover:bg-blue-100 hover:text-blue-900")
|
|
}
|
|
style={{
|
|
boxShadow: isActive ? "0 2px 8px 0 rgba(37,99,235,0.08)" : undefined,
|
|
border: isActive ? "2px solid #2563eb" : undefined,
|
|
}}
|
|
>
|
|
{item.label}
|
|
</Link>
|
|
);
|
|
})}
|
|
<button
|
|
className="ml-auto bg-gray-100 text-gray-700 px-4 py-2 rounded hover:bg-gray-200 transition"
|
|
onClick={async () => {
|
|
await getSeed();
|
|
window.location.reload();
|
|
}}
|
|
>
|
|
Seed-Daten erzeugen
|
|
</button>
|
|
</nav>
|
|
);
|
|
}
|
|
|
|
export default function App() {
|
|
const [trips, setTrips] = useState<any[]>([]);
|
|
const [dbError, setDbError] = useState<string | null>(null);
|
|
const navigate = useNavigate();
|
|
|
|
async function loadTrips() {
|
|
try {
|
|
const data = await getTrips();
|
|
setTrips(data);
|
|
setDbError(null);
|
|
} catch (err) {
|
|
setDbError("Warnung: Verbindung zur Datenbank fehlgeschlagen!");
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
loadTrips();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
document.title = APP_TITLE;
|
|
}, []);
|
|
|
|
async function goToCurrentTrip() {
|
|
try {
|
|
const id = await getNextTripId();
|
|
navigate(`/trips/${id}`);
|
|
} catch {
|
|
alert("Kein anstehender Trip gefunden");
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="p-4 max-w-5xl mx-auto">
|
|
<Navigation />
|
|
{dbError && (
|
|
<div className="mb-4 p-3 bg-red-100 text-red-700 rounded border border-red-300 font-semibold text-center">
|
|
{dbError}
|
|
</div>
|
|
)}
|
|
<Routes>
|
|
<Route path="/trips" element={<TripsPage />} />
|
|
<Route path="/trips/:id" element={<TripChecklist trips={trips} />} />
|
|
<Route path="/items" element={<ItemsPage />} />
|
|
<Route path="/tags" element={<TagsPage />} />
|
|
<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.
|