feat: add TagsPage component and integrate it into the main application
This commit is contained in:
parent
1cfaf70803
commit
067d8f2a6a
2 changed files with 160 additions and 2 deletions
|
|
@ -4,6 +4,7 @@ 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";
|
||||
|
||||
function NextTripRedirect({ trips }: { trips: any[] }) {
|
||||
const [nextTripId, setNextTripId] = React.useState<string | null>(null);
|
||||
|
|
@ -55,12 +56,17 @@ export default function App() {
|
|||
</button>
|
||||
<Link to="/trips">
|
||||
<button className="bg-blue-500 text-white px-4 py-2 rounded">
|
||||
Alle Trips
|
||||
Trips
|
||||
</button>
|
||||
</Link>
|
||||
<Link to="/items">
|
||||
<button className="bg-green-500 text-white px-4 py-2 rounded">
|
||||
Alle Items
|
||||
Items
|
||||
</button>
|
||||
</Link>
|
||||
<Link to="/tags">
|
||||
<button className="bg-purple-500 text-white px-4 py-2 rounded">
|
||||
Tags
|
||||
</button>
|
||||
</Link>
|
||||
<button
|
||||
|
|
@ -78,6 +84,7 @@ export default function App() {
|
|||
<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>
|
||||
|
|
|
|||
151
frontend/src/pages/TagsPage.tsx
Normal file
151
frontend/src/pages/TagsPage.tsx
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { getTags } from "../api";
|
||||
import type { Tag } from "../api";
|
||||
|
||||
const API_BASE = "http://localhost:8000";
|
||||
|
||||
export default function TagsPage() {
|
||||
const [tags, setTags] = useState<Tag[]>([]);
|
||||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
const [editName, setEditName] = useState("");
|
||||
const [editMandatory, setEditMandatory] = useState(false);
|
||||
const [newName, setNewName] = useState("");
|
||||
const [newMandatory, setNewMandatory] = useState(false);
|
||||
|
||||
async function loadTags() {
|
||||
setTags(await getTags());
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadTags();
|
||||
}, []);
|
||||
|
||||
async function handleSave(id: string) {
|
||||
await fetch(`${API_BASE}/tags/${id}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name: editName, mandatory: editMandatory }),
|
||||
});
|
||||
setEditingId(null);
|
||||
await loadTags();
|
||||
}
|
||||
|
||||
async function handleDelete(id: string) {
|
||||
if (!window.confirm("Tag wirklich löschen?")) return;
|
||||
await fetch(`${API_BASE}/tags/${id}`, { method: "DELETE" });
|
||||
await loadTags();
|
||||
}
|
||||
|
||||
async function handleCreate(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
await fetch(`${API_BASE}/tags/`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name: newName, mandatory: newMandatory }),
|
||||
});
|
||||
setNewName("");
|
||||
setNewMandatory(false);
|
||||
await loadTags();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4 max-w-xl mx-auto">
|
||||
<h1 className="text-2xl font-bold mb-4">Alle Tags</h1>
|
||||
<form className="flex gap-2 mb-6" onSubmit={handleCreate}>
|
||||
<input
|
||||
className="border rounded px-2 py-1 flex-1"
|
||||
placeholder="Neuer Tag..."
|
||||
value={newName}
|
||||
onChange={e => setNewName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<label className="flex items-center gap-1 text-sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={newMandatory}
|
||||
onChange={e => setNewMandatory(e.target.checked)}
|
||||
/>
|
||||
mandatory
|
||||
</label>
|
||||
<button
|
||||
className="bg-green-500 text-white px-3 py-1 rounded"
|
||||
type="submit"
|
||||
>
|
||||
Hinzufügen
|
||||
</button>
|
||||
</form>
|
||||
<ul>
|
||||
{tags.map(tag =>
|
||||
editingId === tag.id ? (
|
||||
<li key={tag.id} className="flex gap-2 items-center mb-2">
|
||||
<input
|
||||
className="border rounded px-2 py-1 flex-1"
|
||||
value={editName}
|
||||
onChange={e => setEditName(e.target.value)}
|
||||
/>
|
||||
<label className="flex items-center gap-1 text-sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={editMandatory}
|
||||
onChange={e => setEditMandatory(e.target.checked)}
|
||||
/>
|
||||
mandatory
|
||||
</label>
|
||||
<button
|
||||
className="bg-blue-500 text-white px-2 py-1 rounded"
|
||||
onClick={() => handleSave(tag.id)}
|
||||
type="button"
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
<button
|
||||
className="bg-gray-300 px-2 py-1 rounded"
|
||||
onClick={() => setEditingId(null)}
|
||||
type="button"
|
||||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
</li>
|
||||
) : (
|
||||
<li
|
||||
key={tag.id}
|
||||
className="flex gap-2 items-center mb-2 group"
|
||||
>
|
||||
<span
|
||||
className={
|
||||
"px-2 py-0.5 rounded text-sm " +
|
||||
(tag.mandatory
|
||||
? "border-2 border-yellow-400 bg-yellow-50 font-bold"
|
||||
: "bg-gray-100")
|
||||
}
|
||||
>
|
||||
#{tag.name}
|
||||
{tag.mandatory && (
|
||||
<span className="ml-1 text-yellow-500 font-bold">*</span>
|
||||
)}
|
||||
</span>
|
||||
<button
|
||||
className="text-xs text-blue-500 underline"
|
||||
onClick={() => {
|
||||
setEditingId(tag.id);
|
||||
setEditName(tag.name);
|
||||
setEditMandatory(tag.mandatory);
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Bearbeiten
|
||||
</button>
|
||||
<button
|
||||
className="text-xs text-red-500 underline opacity-0 group-hover:opacity-100"
|
||||
onClick={() => handleDelete(tag.id)}
|
||||
type="button"
|
||||
>
|
||||
Löschen
|
||||
</button>
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue