feat: tag display with mandatory indicators and improve tag color structure

This commit is contained in:
Felix Zett 2025-09-16 21:54:01 +02:00
parent 71af5b911c
commit f20eef7556
4 changed files with 68 additions and 70 deletions

View file

@ -95,12 +95,11 @@ export default function ItemRow({
<span <span
key={tag.id} key={tag.id}
className={ className={
tag.mandatory `${getTagColor(tag.id).bg} text-sm rounded px-1 py-0.5 flex items-center font-medium`
? "border-2 border-yellow-400 text-sm rounded px-1 py-0.5 flex items-center"
: `${getTagColor(tag.id)} text-sm rounded px-1 py-0.5 flex items-center`
} }
> >
#{tag.name} #{tag.name}
{tag.mandatory && <span className="text-red-500 font-bold ml-0.5">!</span>}
{hover && ( {hover && (
<button <button
onClick={() => onDeleteTag(item.id, tag.id)} onClick={() => onDeleteTag(item.id, tag.id)}
@ -113,7 +112,7 @@ export default function ItemRow({
))} ))}
{tripName && ( {tripName && (
<span className="ml-2 px-2 py-0.5 rounded bg-yellow-100 text-yellow-800 text-xs font-semibold"> <span className="ml-2 px-2 py-0.5 rounded bg-gray-100 text-gray-800 text-sm font-semibold">
{tripName} {tripName}
</span> </span>
)} )}

View file

@ -180,15 +180,14 @@ export default function ItemsPage() {
<span <span
key={tag.id} key={tag.id}
className={ className={
tag.mandatory `${getTagColor(tag.id).bg} text-sm rounded px-1 py-0.5 flex items-center cursor-pointer font-medium` +
? "border-2 border-yellow-400 text-sm rounded px-1 py-0.5 flex items-center cursor-pointer" (newItemTags.includes(tag.id) ? " ring-2 ring-blue-400 font-bold" : "")
: `${getTagColor(tag.id)} text-sm rounded px-1 py-0.5 flex items-center cursor-pointer`
+ (newItemTags.includes(tag.id) ? " ring-2 ring-blue-400 font-bold" : "")
} }
onClick={() => toggleNewItemTag(tag.id)} onClick={() => toggleNewItemTag(tag.id)}
title={tag.mandatory ? "Pflicht-Tag (mandatory)" : ""} title={tag.mandatory ? "Pflicht-Tag (mandatory)" : ""}
> >
#{tag.name} #{tag.name}
{tag.mandatory && <span className="text-red-500 font-bold ml-0.5">!</span>}
</span> </span>
))} ))}
{/* Hinweis, falls Trip gewählt */} {/* Hinweis, falls Trip gewählt */}

View file

@ -1,6 +1,7 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { getTags } from "../api"; import { getTags } from "../api";
import type { Tag } from "../api"; import type { Tag } from "../api";
import { getTagColor } from "../utils/tagColors";
const API_BASE = "http://localhost:8000"; const API_BASE = "http://localhost:8000";
@ -13,7 +14,9 @@ export default function TagsPage() {
const [newMandatory, setNewMandatory] = useState(false); const [newMandatory, setNewMandatory] = useState(false);
async function loadTags() { async function loadTags() {
setTags(await getTags()); const loaded = await getTags();
// Alphabetisch sortieren
setTags(loaded.sort((a, b) => a.name.localeCompare(b.name)));
} }
useEffect(() => { useEffect(() => {
@ -74,74 +77,71 @@ export default function TagsPage() {
Hinzufügen Hinzufügen
</button> </button>
</form> </form>
<ul> <ul className="flex flex-wrap gap-4">
{tags.map(tag => {tags.map(tag =>
editingId === tag.id ? ( editingId === tag.id ? (
<li key={tag.id} className="flex gap-2 items-center mb-2"> <li
key={tag.id}
className="flex flex-col items-start p-4 mb-2 bg-white border-2 border-blue-300 rounded-xl shadow"
style={{ minWidth: "220px", maxWidth: "320px" }}
>
<input <input
className="border rounded px-2 py-1 flex-1" className="border rounded px-3 py-2 w-full text-lg font-semibold mb-2"
value={editName} value={editName}
onChange={e => setEditName(e.target.value)} onChange={e => setEditName(e.target.value)}
/> />
<label className="flex items-center gap-1 text-sm"> <label className="flex items-center gap-2 text-base mb-4">
<input <input
type="checkbox" type="checkbox"
checked={editMandatory} checked={editMandatory}
onChange={e => setEditMandatory(e.target.checked)} onChange={e => setEditMandatory(e.target.checked)}
/> />
mandatory <span className="font-semibold">mandatory</span>
</label> </label>
<button <div className="flex gap-2 w-full justify-end">
className="bg-blue-500 text-white px-2 py-1 rounded" <button
onClick={() => handleSave(tag.id)} className="bg-blue-500 text-white px-3 py-1 rounded"
type="button" onClick={() => handleSave(tag.id)}
> type="button"
Speichern >
</button> Speichern
<button </button>
className="bg-gray-300 px-2 py-1 rounded" <button
onClick={() => setEditingId(null)} className="bg-red-500 text-white px-3 py-1 rounded"
type="button" onClick={() => handleDelete(tag.id)}
> type="button"
Abbrechen >
</button> Löschen
</button>
<button
className="bg-gray-300 px-3 py-1 rounded"
onClick={() => setEditingId(null)}
type="button"
>
Abbrechen
</button>
</div>
</li> </li>
) : ( ) : (
<li <li
key={tag.id} key={tag.id}
className="flex gap-2 items-center mb-2 group" className="flex items-center group"
style={{ marginBottom: "0.5rem" }}
> >
<span <span
className={ className={
"px-2 py-0.5 rounded text-sm " + `${getTagColor(tag.id).bg} px-4 py-2 rounded-xl text-lg cursor-pointer font-semibold`
(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={() => { onClick={() => {
setEditingId(tag.id); setEditingId(tag.id);
setEditName(tag.name); setEditName(tag.name);
setEditMandatory(tag.mandatory); setEditMandatory(tag.mandatory);
}} }}
type="button" title="Bearbeiten"
> >
Bearbeiten #{tag.name}
</button> {tag.mandatory && <span className="text-red-500 font-bold ml-1">!</span>}
<button </span>
className="text-xs text-red-500 underline opacity-0 group-hover:opacity-100"
onClick={() => handleDelete(tag.id)}
type="button"
>
Löschen
</button>
</li> </li>
) )
)} )}

View file

@ -1,23 +1,23 @@
export const TAG_COLORS = [ export const TAG_COLORS = [
"bg-blue-200 text-blue-900", { bg: "bg-blue-200 text-blue-900", border: "border-blue-700" },
"bg-green-200 text-green-900", { bg: "bg-green-200 text-green-900", border: "border-green-700" },
"bg-yellow-200 text-yellow-900", { bg: "bg-yellow-200 text-yellow-900", border: "border-yellow-700" },
"bg-pink-200 text-pink-900", { bg: "bg-pink-200 text-pink-900", border: "border-pink-700" },
"bg-purple-200 text-purple-900", { bg: "bg-purple-200 text-purple-900", border: "border-purple-700" },
"bg-orange-200 text-orange-900", { bg: "bg-orange-200 text-orange-900", border: "border-orange-700" },
"bg-teal-200 text-teal-900", { bg: "bg-teal-200 text-teal-900", border: "border-teal-700" },
"bg-red-200 text-red-900", { bg: "bg-red-200 text-red-900", border: "border-red-700" },
"bg-indigo-200 text-indigo-900", { bg: "bg-indigo-200 text-indigo-900", border: "border-indigo-700" },
"bg-lime-200 text-lime-900", { bg: "bg-lime-200 text-lime-900", border: "border-lime-700" },
"bg-cyan-200 text-cyan-900", { bg: "bg-cyan-200 text-cyan-900", border: "border-cyan-700" },
"bg-amber-200 text-amber-900", { bg: "bg-amber-200 text-amber-900", border: "border-amber-700" },
"bg-fuchsia-200 text-fuchsia-900", { bg: "bg-fuchsia-200 text-fuchsia-900", border: "border-fuchsia-700" },
"bg-rose-200 text-rose-900", { bg: "bg-rose-200 text-rose-900", border: "border-rose-700" },
"bg-emerald-200 text-emerald-900", { bg: "bg-emerald-200 text-emerald-900", border: "border-emerald-700" },
"bg-violet-200 text-violet-900", { bg: "bg-violet-200 text-violet-900", border: "border-violet-700" },
"bg-sky-200 text-sky-900", { bg: "bg-sky-200 text-sky-900", border: "border-sky-700" },
"bg-orange-100 text-orange-800", { bg: "bg-orange-100 text-orange-800", border: "border-orange-700" },
"bg-green-100 text-green-800", { bg: "bg-green-100 text-green-800", border: "border-green-700" },
]; ];
export function getTagColor(tagId: string) { export function getTagColor(tagId: string) {