feat: enhance TripsPage to include tag selection and marking for new trips
This commit is contained in:
parent
c966009ab5
commit
7e1bb2f77b
1 changed files with 110 additions and 38 deletions
|
|
@ -1,17 +1,26 @@
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { getSeed, getTrips, deleteTrip, createTrip } from "../api";
|
import { getSeed, getTrips, deleteTrip, createTrip, getTags } from "../api";
|
||||||
|
|
||||||
export default function TripsPage() {
|
export default function TripsPage() {
|
||||||
const [trips, setTrips] = useState<any[]>([]);
|
const [trips, setTrips] = useState<any[]>([]);
|
||||||
|
const [allTags, setAllTags] = useState<any[]>([]);
|
||||||
const [newTrip, setNewTrip] = useState({ name: "", start_date: "", end_date: "" });
|
const [newTrip, setNewTrip] = useState({ name: "", start_date: "", end_date: "" });
|
||||||
|
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
||||||
|
const [markedTags, setMarkedTags] = useState<string[]>([]);
|
||||||
const [creating, setCreating] = useState(false);
|
const [creating, setCreating] = useState(false);
|
||||||
|
const [showForm, setShowForm] = useState(false);
|
||||||
|
|
||||||
async function loadTrips() {
|
async function loadTrips() {
|
||||||
const data = await getTrips();
|
const data = await getTrips();
|
||||||
setTrips(data);
|
setTrips(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadTrips();
|
||||||
|
getTags().then(setAllTags);
|
||||||
|
}, []);
|
||||||
|
|
||||||
async function handleDeleteTrip(tripId: string) {
|
async function handleDeleteTrip(tripId: string) {
|
||||||
if (window.confirm("Diesen Trip wirklich löschen?")) {
|
if (window.confirm("Diesen Trip wirklich löschen?")) {
|
||||||
await deleteTrip(tripId);
|
await deleteTrip(tripId);
|
||||||
|
|
@ -27,20 +36,19 @@ export default function TripsPage() {
|
||||||
name: newTrip.name,
|
name: newTrip.name,
|
||||||
start_date: newTrip.start_date,
|
start_date: newTrip.start_date,
|
||||||
end_date: newTrip.end_date,
|
end_date: newTrip.end_date,
|
||||||
selected_tag_ids: [],
|
selected_tag_ids: selectedTags,
|
||||||
marked_tag_ids: [],
|
marked_tag_ids: markedTags,
|
||||||
});
|
});
|
||||||
setNewTrip({ name: "", start_date: "", end_date: "" });
|
setNewTrip({ name: "", start_date: "", end_date: "" });
|
||||||
|
setSelectedTags([]);
|
||||||
|
setMarkedTags([]);
|
||||||
|
setShowForm(false);
|
||||||
await loadTrips();
|
await loadTrips();
|
||||||
} finally {
|
} finally {
|
||||||
setCreating(false);
|
setCreating(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadTrips();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Markiere nächsten anstehenden Trip
|
// Markiere nächsten anstehenden Trip
|
||||||
const today = new Date().toISOString().slice(0, 10);
|
const today = new Date().toISOString().slice(0, 10);
|
||||||
const nextTripIdx = trips.findIndex(
|
const nextTripIdx = trips.findIndex(
|
||||||
|
|
@ -48,40 +56,104 @@ export default function TripsPage() {
|
||||||
);
|
);
|
||||||
const nextTripId = nextTripIdx !== -1 ? trips[nextTripIdx].id : null;
|
const nextTripId = nextTripIdx !== -1 ? trips[nextTripIdx].id : null;
|
||||||
|
|
||||||
|
function toggleTag(tagId: string) {
|
||||||
|
setSelectedTags((prev) =>
|
||||||
|
prev.includes(tagId) ? prev.filter((id) => id !== tagId) : [...prev, tagId]
|
||||||
|
);
|
||||||
|
// Wenn ein Tag abgewählt wird, auch aus markedTags entfernen
|
||||||
|
setMarkedTags((prev) => prev.filter((id) => id !== tagId));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMarkTag(tagId: string) {
|
||||||
|
if (!selectedTags.includes(tagId)) return;
|
||||||
|
setMarkedTags((prev) =>
|
||||||
|
prev.includes(tagId) ? prev.filter((id) => id !== tagId) : [...prev, tagId]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold mb-4">Packlist</h1>
|
<h1 className="text-2xl font-bold mb-4">Packlist</h1>
|
||||||
<form className="mb-4 flex gap-2 flex-wrap items-end" onSubmit={handleCreateTrip}>
|
<button
|
||||||
<input
|
className="mb-4 bg-blue-500 text-white px-4 py-2 rounded"
|
||||||
type="text"
|
onClick={() => setShowForm((v) => !v)}
|
||||||
placeholder="Trip-Name"
|
>
|
||||||
value={newTrip.name}
|
{showForm ? "Abbrechen" : "Neuen Trip anlegen"}
|
||||||
onChange={e => setNewTrip(t => ({ ...t, name: e.target.value }))}
|
</button>
|
||||||
className="border rounded px-2 py-1"
|
{showForm && (
|
||||||
required
|
<form className="mb-4 flex flex-col gap-2 p-4 border rounded bg-gray-50" onSubmit={handleCreateTrip}>
|
||||||
/>
|
<input
|
||||||
<input
|
type="text"
|
||||||
type="date"
|
placeholder="Trip-Name"
|
||||||
value={newTrip.start_date}
|
value={newTrip.name}
|
||||||
onChange={e => setNewTrip(t => ({ ...t, start_date: e.target.value }))}
|
onChange={e => setNewTrip(t => ({ ...t, name: e.target.value }))}
|
||||||
className="border rounded px-2 py-1"
|
className="border rounded px-2 py-1"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<input
|
<div className="flex gap-2">
|
||||||
type="date"
|
<input
|
||||||
value={newTrip.end_date}
|
type="date"
|
||||||
onChange={e => setNewTrip(t => ({ ...t, end_date: e.target.value }))}
|
value={newTrip.start_date}
|
||||||
className="border rounded px-2 py-1"
|
onChange={e => setNewTrip(t => ({ ...t, start_date: e.target.value }))}
|
||||||
required
|
className="border rounded px-2 py-1"
|
||||||
/>
|
required
|
||||||
<button
|
/>
|
||||||
type="submit"
|
<input
|
||||||
className="bg-green-500 text-white px-4 py-2 rounded"
|
type="date"
|
||||||
disabled={creating}
|
value={newTrip.end_date}
|
||||||
>
|
onChange={e => setNewTrip(t => ({ ...t, end_date: e.target.value }))}
|
||||||
{creating ? "Anlegen..." : "Neuen Trip anlegen"}
|
className="border rounded px-2 py-1"
|
||||||
</button>
|
required
|
||||||
</form>
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="mb-1 font-semibold">Tags auswählen:</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{allTags.map((tag: any) => {
|
||||||
|
const isSelected = selectedTags.includes(tag.id);
|
||||||
|
const isMarked = markedTags.includes(tag.id);
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
key={tag.id}
|
||||||
|
className={
|
||||||
|
"px-2 py-0.5 rounded cursor-pointer select-none transition " +
|
||||||
|
(isSelected
|
||||||
|
? isMarked
|
||||||
|
? "bg-yellow-200 text-yellow-900 font-bold border border-yellow-400"
|
||||||
|
: "bg-blue-100 text-blue-800 border border-blue-300"
|
||||||
|
: "bg-gray-100 text-gray-500 border border-gray-200")
|
||||||
|
}
|
||||||
|
onClick={() => toggleTag(tag.id)}
|
||||||
|
onDoubleClick={() => toggleMarkTag(tag.id)}
|
||||||
|
title={
|
||||||
|
isSelected
|
||||||
|
? isMarked
|
||||||
|
? "Doppelklick: Markierung entfernen"
|
||||||
|
: "Doppelklick: Tag markieren"
|
||||||
|
: "Klick: Tag auswählen"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
#{tag.name}
|
||||||
|
{isMarked && isSelected && (
|
||||||
|
<span className="ml-1 text-xs">★</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-500 mt-1">
|
||||||
|
Klick: auswählen/abwählen, Doppelklick: markieren
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="bg-green-500 text-white px-4 py-2 rounded mt-2"
|
||||||
|
disabled={creating}
|
||||||
|
>
|
||||||
|
{creating ? "Anlegen..." : "Trip anlegen"}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
|
||||||
{trips.map(trip => {
|
{trips.map(trip => {
|
||||||
const isPast = trip.start_date < today;
|
const isPast = trip.start_date < today;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue