feat: enhance TripChecklist to load and display items with associated tags and group items by marked tags
This commit is contained in:
parent
8fe64792d7
commit
d385e40171
3 changed files with 108 additions and 3 deletions
|
|
@ -15,7 +15,10 @@ def list_trip_items(trip_id: UUID, db: Session = Depends(get_db)):
|
|||
raise HTTPException(status_code=404, detail="Trip not found")
|
||||
items = (
|
||||
db.query(models.TripItem)
|
||||
.options(joinedload(models.TripItem.tag))
|
||||
.options(
|
||||
joinedload(models.TripItem.tag),
|
||||
joinedload(models.TripItem.item).joinedload(models.Item.tags), # <--- Item mit Tags laden
|
||||
)
|
||||
.filter(models.TripItem.trip_id == trip_id)
|
||||
.order_by(
|
||||
func.regexp_replace(
|
||||
|
|
@ -34,6 +37,7 @@ def list_trip_items(trip_id: UUID, db: Session = Depends(get_db)):
|
|||
name_calculated=ti.name_calculated,
|
||||
checked=ti.checked,
|
||||
tag=ti.tag,
|
||||
item=ti.item,
|
||||
) for ti in items
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
from typing import List, Optional
|
||||
from uuid import UUID
|
||||
from datetime import date
|
||||
|
|
@ -60,6 +59,7 @@ class TripItemOut(BaseModel):
|
|||
name_calculated: str
|
||||
checked: bool
|
||||
tag: Optional[TagOut] = None
|
||||
item: Optional[ItemOut] = None
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,11 @@ export default function TripChecklist({ trips }: { trips: any[] }) {
|
|||
marked_tag_ids: newMarked.map((t) => t.id),
|
||||
});
|
||||
setMarkedTags(newMarked);
|
||||
// Items neu laden
|
||||
if (id) {
|
||||
const updated = await getTripItems(id);
|
||||
setItems(updated);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRemoveTag(tagId: string) {
|
||||
|
|
@ -55,6 +60,11 @@ export default function TripChecklist({ trips }: { trips: any[] }) {
|
|||
});
|
||||
setSelectedTags(newSelected);
|
||||
setMarkedTags(newMarked);
|
||||
// Items neu laden
|
||||
if (id) {
|
||||
const updated = await getTripItems(id);
|
||||
setItems(updated);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAddTag(tagId: string) {
|
||||
|
|
@ -70,10 +80,34 @@ export default function TripChecklist({ trips }: { trips: any[] }) {
|
|||
});
|
||||
setSelectedTags(newSelected);
|
||||
setTagInput("");
|
||||
// Items neu laden
|
||||
if (id) {
|
||||
const updated = await getTripItems(id);
|
||||
setItems(updated);
|
||||
}
|
||||
}
|
||||
|
||||
if (!trip) return <div>Trip not found</div>;
|
||||
|
||||
// Gruppiere Items nach Kategorie (item.tag)
|
||||
const itemsWithoutTag = items.filter((item) => !item.tag);
|
||||
// Map: tagId -> { tag, items: [...] }
|
||||
const itemsByTag: Record<string, { tag: any; items: any[] }> = {};
|
||||
items
|
||||
.filter((item) => item.tag)
|
||||
.forEach((item) => {
|
||||
const tagId = item.tag.id;
|
||||
if (!itemsByTag[tagId]) {
|
||||
itemsByTag[tagId] = { tag: item.tag, items: [] };
|
||||
}
|
||||
itemsByTag[tagId].items.push(item);
|
||||
});
|
||||
|
||||
// Sortiere Kategorien alphabetisch nach Tag-Name
|
||||
const sortedTagGroups = Object.values(itemsByTag).sort((a, b) =>
|
||||
a.tag.name.localeCompare(b.tag.name)
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Tag-Liste */}
|
||||
|
|
@ -155,6 +189,56 @@ export default function TripChecklist({ trips }: { trips: any[] }) {
|
|||
</div>
|
||||
{/* ...Rest der Checklist... */}
|
||||
<ul>
|
||||
{/* Einträge ohne Kategorie */}
|
||||
{itemsWithoutTag.map((item) => (
|
||||
<li
|
||||
key={item.id}
|
||||
className={
|
||||
"flex items-center gap-2 cursor-pointer select-none px-2 py-1 rounded " +
|
||||
(item.checked ? "bg-green-100" : "hover:bg-gray-100")
|
||||
}
|
||||
onClick={async () => {
|
||||
await toggleTripItem(item.id);
|
||||
const updated = await getTripItems(id!);
|
||||
setItems(updated);
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={item.checked}
|
||||
readOnly
|
||||
tabIndex={-1}
|
||||
className="pointer-events-none"
|
||||
/>
|
||||
<span className={item.checked ? "line-through text-gray-400" : ""}>
|
||||
{item.name_calculated}
|
||||
</span>
|
||||
{item.item && item.item.tags && item.item.tags.length > 0 && (
|
||||
<span className="ml-2 flex gap-1">
|
||||
{[...item.item.tags]
|
||||
.slice()
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((tag: any) => (
|
||||
<span
|
||||
key={tag.id}
|
||||
className="text-xs text-gray-400 bg-gray-100 rounded px-1 py-0.5"
|
||||
>
|
||||
#{tag.name}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
|
||||
{/* Einträge mit Kategorie */}
|
||||
{sortedTagGroups.map(({ tag, items }) => (
|
||||
<React.Fragment key={tag.id}>
|
||||
<li className="mt-4 mb-1 font-semibold text-gray-700 flex items-center gap-2">
|
||||
<span className="text-xs bg-gray-200 text-gray-700 rounded px-2 py-0.5">
|
||||
#{tag.name}
|
||||
</span>
|
||||
</li>
|
||||
{items.map((item) => (
|
||||
<li
|
||||
key={item.id}
|
||||
|
|
@ -178,8 +262,25 @@ export default function TripChecklist({ trips }: { trips: any[] }) {
|
|||
<span className={item.checked ? "line-through text-gray-400" : ""}>
|
||||
{item.name_calculated}
|
||||
</span>
|
||||
{item.item && item.item.tags && item.item.tags.length > 0 && (
|
||||
<span className="ml-2 flex gap-1">
|
||||
{[...item.item.tags]
|
||||
.slice()
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((tag: any) => (
|
||||
<span
|
||||
key={tag.id}
|
||||
className="text-xs text-gray-400 bg-gray-100 rounded px-1 py-0.5"
|
||||
>
|
||||
#{tag.name}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue