feat(fehlteile): fehlende teile je sammlungseintrag pflegen

Jeder Sammlungseintrag hat jetzt eine Fehlteil-Liste: Teilenummer,
Farbe, Anzahl und Notiz, abhakbar wenn das Teil nachgekauft ist. Ein
Eintrag ohne Nummer geht auch - dann reicht die Notiz, denn beim
Aufbauen kennt man die Nummer selten.

Die Vollstaendigkeit pflegt sich dabei selbst: Der erste offene Eintrag
setzt den Sammlungseintrag auf "Teile fehlen", der letzte erledigte
zurueck auf "vollstaendig". Damit stimmt der Filter in der Sammlung,
ohne dass man daran denken muss.

Offene Fehlteile stehen als Badge an jedem Eintrag - in der Sammlung
und auf der Set-Detailseite.

Refs Phase 2

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
unknown
2026-09-07 07:18:50 +02:00
co-authored by Claude Opus 5
parent baa94bf7e5
commit 5c5c5213bb
7 changed files with 414 additions and 6 deletions
@@ -2,9 +2,11 @@
import { useRouter } from 'next/navigation';
import { useState, useTransition } from 'react';
import { Loader2, Pencil, Trash2 } from 'lucide-react';
import { Loader2, Pencil, PuzzleIcon, Trash2 } from 'lucide-react';
import { toast } from 'sonner';
import { deleteCollectionItem, updateCollectionItem } from '@/app/(app)/sammlung/actions';
import type { MissingPartRow } from '@/lib/queries/collection';
import { MissingPartsDialog } from './missing-parts-dialog';
import type { CollectionStatus, Completeness, Condition } from '@/db/types';
import { formatDate, toDateInputValue } from '@/lib/format';
import {
@@ -54,6 +56,8 @@ export type CollectionItemRow = {
giftedBy: string | null;
lentOutTo: string | null;
notes: string | null;
setName: string;
missingParts: MissingPartRow[];
};
export function CollectionItemsSection({
@@ -67,6 +71,7 @@ export function CollectionItemsSection({
const [pending, startTransition] = useTransition();
const [editing, setEditing] = useState<CollectionItemRow | null>(null);
const [deleting, setDeleting] = useState<CollectionItemRow | null>(null);
const [parts, setParts] = useState<CollectionItemRow | null>(null);
function submit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
@@ -121,6 +126,11 @@ export function CollectionItemsSection({
{statusLabel(item.status)}
</Badge>
<Badge variant="secondary">{completenessLabel(item.completeness)}</Badge>
{item.missingParts.some((part) => !part.resolved) ? (
<Badge variant="outline">
{item.missingParts.filter((part) => !part.resolved).length} Fehlteile
</Badge>
) : null}
{item.locationName ? (
<span className="text-xs text-muted-foreground">{item.locationName}</span>
) : null}
@@ -140,6 +150,16 @@ export function CollectionItemsSection({
</p>
{item.notes ? <p className="text-xs">{item.notes}</p> : null}
</div>
<Button
variant="ghost"
size="icon-sm"
aria-label="Fehlteile"
title="Fehlteile"
onClick={() => setParts(item)}
disabled={pending}
>
<PuzzleIcon />
</Button>
<Button
variant="ghost"
size="icon-sm"
@@ -283,6 +303,16 @@ export function CollectionItemsSection({
</DialogContent>
</Dialog>
{parts ? (
<MissingPartsDialog
collectionItemId={parts.id}
setName={parts.setName}
parts={parts.missingParts}
open
onOpenChange={(open) => !open && setParts(null)}
/>
) : null}
<AlertDialog open={deleting !== null} onOpenChange={(open) => !open && setDeleting(null)}>
<AlertDialogContent>
<AlertDialogHeader>