From 16dbd44831946bb1218a029652ace186266a275a Mon Sep 17 00:00:00 2001 From: Felix Zett Date: Sun, 21 Sep 2025 15:52:00 +0200 Subject: [PATCH] refactor tags: create Tag component and update usages across ItemRow, TagAutocompleteInput, ItemsPage, TagsPage, and TripChecklist --- frontend/src/components/ItemRow.tsx | 41 ++++++--------- frontend/src/components/Tag.tsx | 52 +++++++++++++++++++ .../src/components/TagAutocompleteInput.tsx | 26 ++++++---- frontend/src/pages/ItemsPage.tsx | 18 +++---- frontend/src/pages/TagsPage.tsx | 18 ++----- frontend/src/pages/TripChecklist.tsx | 45 +++++----------- 6 files changed, 109 insertions(+), 91 deletions(-) create mode 100644 frontend/src/components/Tag.tsx diff --git a/frontend/src/components/ItemRow.tsx b/frontend/src/components/ItemRow.tsx index 01b30d3..d85554a 100644 --- a/frontend/src/components/ItemRow.tsx +++ b/frontend/src/components/ItemRow.tsx @@ -1,11 +1,11 @@ import React, { useState, useRef } from "react"; -import { Item, Tag } from "../api"; -import { getTagColor } from "../utils/tagColors"; +import { Item, Tag as TagType } from "../api"; import TagAutocompleteInput from "./TagAutocompleteInput"; +import Tag from "./Tag"; interface ItemRowProps { item: Item; - allTags: Tag[]; + allTags: TagType[]; onUpdateName: (id: string, name: string) => void; onDeleteTag: (itemId: string, tagId: string) => void; onAddTag: (itemId: string, tagId: string) => void; @@ -34,7 +34,6 @@ export default function ItemRow({ setIsEditing(false); } - // --- Dim category part if "/" exists --- function renderNameWithCategory(name: string) { if (!name.includes("/")) return name; const [cat, rest] = name.split("/", 2); @@ -46,7 +45,6 @@ export default function ItemRow({ ); } - // Trip-Namen lookup const tripName = item.trip_id && trips ? trips.find((t) => t.id === item.trip_id)?.name @@ -58,7 +56,6 @@ export default function ItemRow({ onMouseEnter={() => setHover(true)} onMouseLeave={() => { setHover(false); - // Only close add tag input if not focused if ( !addingTag || (inputRef.current && document.activeElement !== inputRef.current) @@ -92,29 +89,22 @@ export default function ItemRow({ )} {item.tags.map((tag) => ( - - #{tag.name} - {tag.mandatory && !} - {hover && ( - - )} - + tag={tag} + isMarked={false} + hoveredTag={null} + onRemoveTag={() => onDeleteTag(item.id, tag.id)} + showRemove={hover} + /> ))} {tripName && ( - - {tripName} - + )} {hover && !addingTag && ( @@ -149,7 +139,6 @@ export default function ItemRow({ /> )} - {/* Löschen-Button am Ende der Zeile */} {hover && ( + )} + + ); +} \ No newline at end of file diff --git a/frontend/src/components/TagAutocompleteInput.tsx b/frontend/src/components/TagAutocompleteInput.tsx index ae83705..dbc09ae 100644 --- a/frontend/src/components/TagAutocompleteInput.tsx +++ b/frontend/src/components/TagAutocompleteInput.tsx @@ -1,9 +1,10 @@ import React, { useState, useImperativeHandle, forwardRef, useRef } from "react"; -import { Tag } from "../api"; +import { Tag as TagType } from "../api"; +import Tag from "./Tag"; interface TagAutocompleteInputProps { - allTags: Tag[]; - selectedTags: Tag[]; + allTags: TagType[]; + selectedTags: TagType[]; onAddTag: (tagId: string, viaTab?: boolean) => void; onEscape?: () => void; placeholder?: string; @@ -86,14 +87,19 @@ const TagAutocompleteInput = forwardRef )} {filteredSuggestions.length > 0 && ( -
    +
      {filteredSuggestions.map((tag, idx) => (
    • { onAddTag(tag.id); setInput(""); @@ -101,10 +107,12 @@ const TagAutocompleteInput = forwardRef setAutocompleteIndex(idx)} > - #{tag.name} - {tag.mandatory && ( - ! - )} +
    • ))}
    diff --git a/frontend/src/pages/ItemsPage.tsx b/frontend/src/pages/ItemsPage.tsx index 5f011a2..76397d4 100644 --- a/frontend/src/pages/ItemsPage.tsx +++ b/frontend/src/pages/ItemsPage.tsx @@ -12,7 +12,7 @@ import { } from "../api"; import ItemList from "../components/ItemList"; import TagFilter from "../components/TagFilter"; -import { getTagColor } from "../utils/tagColors"; +import Tag from "../components/Tag"; async function fetchTrips(): Promise<{ id: string; name: string }[]> { const res = await fetch("http://localhost:8000/trips/"); @@ -287,18 +287,12 @@ export default function ItemsPage() { {!newItemTripId && (
    {tags.map((tag) => ( - toggleNewItemTag(tag.id)} - title={tag.mandatory ? "Pflicht-Tag (mandatory)" : ""} - > - #{tag.name} - {tag.mandatory && !} - + tag={tag} + isMarked={newItemTags.includes(tag.id)} + onToggleMark={toggleNewItemTag} + /> ))}
    )} diff --git a/frontend/src/pages/TagsPage.tsx b/frontend/src/pages/TagsPage.tsx index bedd395..803da6d 100644 --- a/frontend/src/pages/TagsPage.tsx +++ b/frontend/src/pages/TagsPage.tsx @@ -1,12 +1,11 @@ import React, { useEffect, useState } from "react"; import { getTags } from "../api"; -import type { Tag } from "../api"; -import { getTagColor } from "../utils/tagColors"; +import Tag from "../components/Tag"; const API_BASE = "http://localhost:8000"; export default function TagsPage() { - const [tags, setTags] = useState([]); + const [tags, setTags] = useState([]); const [editingId, setEditingId] = useState(null); const [editName, setEditName] = useState(""); const [editMandatory, setEditMandatory] = useState(false); @@ -15,7 +14,6 @@ export default function TagsPage() { async function loadTags() { const loaded = await getTags(); - // Alphabetisch sortieren setTags(loaded.sort((a, b) => a.name.localeCompare(b.name))); } @@ -128,20 +126,14 @@ export default function TagsPage() { className="flex items-center group" style={{ marginBottom: "0.5rem" }} > - { setEditingId(tag.id); setEditName(tag.name); setEditMandatory(tag.mandatory); }} - title="Bearbeiten" - > - #{tag.name} - {tag.mandatory && !} - + /> ) )} diff --git a/frontend/src/pages/TripChecklist.tsx b/frontend/src/pages/TripChecklist.tsx index 6580823..d44c32c 100644 --- a/frontend/src/pages/TripChecklist.tsx +++ b/frontend/src/pages/TripChecklist.tsx @@ -3,7 +3,8 @@ import React, { useState, useEffect, useRef } from "react"; import { useParams } from "react-router-dom"; import { getTripItems, toggleTripItem, updateTrip, getTags } from "../api"; import TagAutocompleteInput from "../components/TagAutocompleteInput"; -import { getTagColor } from "../utils/tagColors"; +import TripTag from "../components/Tag"; +import Tag from "../components/Tag"; export default function TripChecklist({ trips }: { trips: any[] }) { const { id } = useParams(); @@ -282,36 +283,18 @@ export default function TripChecklist({ trips }: { trips: any[] }) { {selectedTags.length === 0 ? ( keine ) : ( - selectedTags.map((tag: any) => { - const isMarked = markedTags.some((mt: any) => mt.id === tag.id); - const color = getTagColor(tag.id); - return ( - handleToggleMark(tag.id)} - onMouseEnter={() => setHoveredTag(tag.id)} - onMouseLeave={() => setHoveredTag(null)} - > - #{tag.name} - {tag.mandatory && !} - {hoveredTag === tag.id && ( - - )} - - ); - }) + selectedTags.map((tag: any) => ( + mt.id === tag.id)} + hoveredTag={hoveredTag} + onToggleMark={handleToggleMark} + onRemoveTag={handleRemoveTag} + onMouseEnter={setHoveredTag} + onMouseLeave={() => setHoveredTag(null)} + /> + )) )} {/* "+Tag" link and dropdown */} {!addingTag ? (