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:
@@ -33,6 +33,8 @@ type Props = {
|
||||
wishlists: WishlistTarget[];
|
||||
/** Preselected target, used by the embedded view on a wishlist page. */
|
||||
defaultWishlistId?: string;
|
||||
/** A list created from a filtered view belongs to that collector. */
|
||||
collectorId?: string;
|
||||
emptyState?: React.ReactNode;
|
||||
};
|
||||
|
||||
@@ -43,7 +45,13 @@ type Props = {
|
||||
* On a desktop the "Auswählen" button does the same job. Sets that are already
|
||||
* in the collection cannot be selected - they do not belong on a wishlist.
|
||||
*/
|
||||
export function CatalogGrid({ sets, wishlists, defaultWishlistId, emptyState }: Props) {
|
||||
export function CatalogGrid({
|
||||
sets,
|
||||
wishlists,
|
||||
defaultWishlistId,
|
||||
collectorId,
|
||||
emptyState,
|
||||
}: Props) {
|
||||
const router = useRouter();
|
||||
const [selecting, setSelecting] = useState(false);
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||
@@ -102,6 +110,7 @@ export function CatalogGrid({ sets, wishlists, defaultWishlistId, emptyState }:
|
||||
title: newTitle,
|
||||
occasion: 'other',
|
||||
isPublic: 'on',
|
||||
collectorId: collectorId ?? '',
|
||||
});
|
||||
if (!created.ok) {
|
||||
toast.error(created.error);
|
||||
|
||||
@@ -38,32 +38,47 @@ export function SetActions({
|
||||
setName,
|
||||
owned,
|
||||
wishlists,
|
||||
collectors,
|
||||
}: {
|
||||
setId: string;
|
||||
setName: string;
|
||||
owned: boolean;
|
||||
wishlists: Array<{ id: string; title: string }>;
|
||||
/** More than one turns "Zur Sammlung" into a question of "for whom". */
|
||||
collectors: Array<{ id: string; name: string }>;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const [pending, startTransition] = useTransition();
|
||||
const [wishlistOpen, setWishlistOpen] = useState(false);
|
||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||
const [targetId, setTargetId] = useState(wishlists[0]?.id ?? NEW_LIST);
|
||||
const [collectorOpen, setCollectorOpen] = useState(false);
|
||||
const [collectorId, setCollectorId] = useState(collectors[0]?.id ?? '');
|
||||
const [newTitle, setNewTitle] = useState('');
|
||||
const [priority, setPriority] = useState('2');
|
||||
|
||||
function addToCollection() {
|
||||
function addToCollection(forCollectorId?: string) {
|
||||
startTransition(async () => {
|
||||
const result = await addSetToCollection(setId);
|
||||
const result = await addSetToCollection(setId, forCollectorId);
|
||||
if (!result.ok) {
|
||||
toast.error(result.error);
|
||||
return;
|
||||
}
|
||||
toast.success('Zur Sammlung hinzugefügt.');
|
||||
const owner = collectors.find((entry) => entry.id === forCollectorId);
|
||||
toast.success(
|
||||
owner ? `Zur Sammlung von ${owner.name} hinzugefügt.` : 'Zur Sammlung hinzugefügt.',
|
||||
);
|
||||
setCollectorOpen(false);
|
||||
router.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
// With a single collector there is nothing to ask.
|
||||
function onAddClick() {
|
||||
if (collectors.length > 1) setCollectorOpen(true);
|
||||
else addToCollection();
|
||||
}
|
||||
|
||||
function addToWishlist() {
|
||||
startTransition(async () => {
|
||||
let wishlistId = targetId;
|
||||
@@ -111,7 +126,7 @@ export function SetActions({
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button size="lg" onClick={addToCollection} disabled={pending}>
|
||||
<Button size="lg" onClick={onAddClick} disabled={pending}>
|
||||
{pending ? <Loader2 className="animate-spin" /> : <Plus />}
|
||||
Zur Sammlung hinzufügen
|
||||
</Button>
|
||||
@@ -138,6 +153,39 @@ export function SetActions({
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<Dialog open={collectorOpen} onOpenChange={setCollectorOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Wer bekommt das Set?</DialogTitle>
|
||||
<DialogDescription>{setName}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Field label="Sammler" htmlFor="add-collector" required>
|
||||
<NativeSelect
|
||||
id="add-collector"
|
||||
value={collectorId}
|
||||
onChange={(event) => setCollectorId(event.target.value)}
|
||||
>
|
||||
{collectors.map((entry) => (
|
||||
<option key={entry.id} value={entry.id}>
|
||||
{entry.name}
|
||||
</option>
|
||||
))}
|
||||
</NativeSelect>
|
||||
</Field>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setCollectorOpen(false)} disabled={pending}>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button onClick={() => addToCollection(collectorId)} disabled={pending}>
|
||||
{pending ? <Loader2 className="animate-spin" /> : <Plus />}
|
||||
Hinzufügen
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={wishlistOpen} onOpenChange={setWishlistOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
|
||||
Reference in New Issue
Block a user