feat(sammler): mehrere sammler mit eigener sammlung und wunschliste
Der Katalog bleibt gemeinsam - ein Set existiert einmal, unabhaengig davon, wer es hat. Besitz und Wunschlisten gehoeren dagegen je einem Sammler. Damit beantwortet "hat er das schon?" jetzt die Frage "hat *er* das schon?": ein Set, das nur die Schwester besitzt, faellt fuer den Bruder wieder unter "fehlt noch" und darf auf seine Wunschliste. Wichtig fuer den Alltag: Solange es nur einen Sammler gibt, taucht die Unterscheidung nirgends in der Oberflaeche auf. Kein Umschalter, keine Abfrage beim Hinzufuegen, keine Badges - die App bleibt exakt so einfach wie vorher. Erst der zweite Sammler schaltet all das frei. Die Besitzlogik liegt weiterhin an genau einer Stelle (src/lib/ownership.ts) und hat nur eine Dimension dazubekommen. Das automatische "erfuellt" auf Wunschlisten fragt jetzt ebenfalls je Liste: ein Wunsch auf Max' Liste ist erfuellt, wenn Max das Set bekommt - nicht wenn Mia es hat. Listen ohne Sammler verhalten sich wie bisher. Die Migration legt einen Sammler "Sammlung" an und ordnet ihm alles Vorhandene zu; umbenennen laesst er sich unter Verwaltung -> Sammler. Der Seed uebernimmt ihn statt einen dritten anzulegen. Refs Phase 3 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import { Boxes, Euro, ImageOff, Layers, PackageSearch } from 'lucide-react';
|
||||
import { CollectorFilter } from '@/components/layout/collector-filter';
|
||||
import { CollectionFilters } from '@/components/sammlung/collection-filters';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { formatDate, formatNumber, pluralize } from '@/lib/format';
|
||||
import { collectorBadgeClass } from '@/lib/collector-colors';
|
||||
import { setImageSrc } from '@/lib/images';
|
||||
import { completenessLabel, statusLabel } from '@/lib/labels';
|
||||
import { formatEuro } from '@/lib/money';
|
||||
@@ -15,6 +17,7 @@ import {
|
||||
listCollectionItems,
|
||||
type CollectionFilters as Filters,
|
||||
} from '@/lib/queries/collection';
|
||||
import { listCollectors, resolveCollectorId } from '@/lib/queries/collectors';
|
||||
import { listLocations, listManufacturers } from '@/lib/queries/master-data';
|
||||
import { requireUser } from '@/lib/session';
|
||||
import type { CollectionStatus, Completeness } from '@/db/types';
|
||||
@@ -32,18 +35,23 @@ export default async function SammlungPage({ searchParams }: { searchParams: Sea
|
||||
await requireUser();
|
||||
const params = await searchParams;
|
||||
|
||||
const collectors = listCollectors();
|
||||
const collectorId = resolveCollectorId(single(params.sammler));
|
||||
const activeCollector = collectors.find((row) => row.id === collectorId) ?? null;
|
||||
|
||||
const filters: Filters = {
|
||||
q: single(params.q),
|
||||
status: single(params.status) as CollectionStatus | undefined,
|
||||
completeness: single(params.zustand) as Completeness | undefined,
|
||||
locationId: single(params.standort),
|
||||
manufacturerId: single(params.hersteller),
|
||||
collectorId,
|
||||
};
|
||||
|
||||
const items = listCollectionItems(filters);
|
||||
const openParts = countOpenMissingParts(items.map((item) => item.id));
|
||||
const stats = getCollectionStats();
|
||||
const missing = countMissingSets();
|
||||
const stats = getCollectionStats(collectorId);
|
||||
const missing = countMissingSets(collectorId);
|
||||
|
||||
const tiles = [
|
||||
{ icon: Boxes, label: 'Sets', value: formatNumber(stats.setCount) },
|
||||
@@ -56,9 +64,18 @@ export default async function SammlungPage({ searchParams }: { searchParams: Sea
|
||||
<div className="space-y-5">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">Sammlung</h1>
|
||||
<p className="text-sm text-muted-foreground">Alles, was tatsächlich da ist.</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{activeCollector
|
||||
? `Alles, was ${activeCollector.name} tatsächlich hat.`
|
||||
: 'Alles, was tatsächlich da ist.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<CollectorFilter
|
||||
collectors={collectors.map((row) => ({ id: row.id, name: row.name, color: row.color }))}
|
||||
allLabel="Alle zusammen"
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
||||
{tiles.map((tile) => (
|
||||
<Card key={tile.label}>
|
||||
@@ -134,6 +151,11 @@ export default async function SammlungPage({ searchParams }: { searchParams: Sea
|
||||
{item.completeness !== 'unknown' ? (
|
||||
<Badge variant="secondary">{completenessLabel(item.completeness)}</Badge>
|
||||
) : null}
|
||||
{collectors.length > 1 && item.collectorName ? (
|
||||
<Badge className={collectorBadgeClass(item.collectorColor)}>
|
||||
{item.collectorName}
|
||||
</Badge>
|
||||
) : null}
|
||||
{openParts.get(item.id) ? (
|
||||
<Badge variant="outline">{openParts.get(item.id)} Fehlteile</Badge>
|
||||
) : null}
|
||||
|
||||
Reference in New Issue
Block a user