diff --git a/apps/mana/apps/web/src/lib/modules/wardrobe/ListView.svelte b/apps/mana/apps/web/src/lib/modules/wardrobe/ListView.svelte index a696c0e87..64f390677 100644 --- a/apps/mana/apps/web/src/lib/modules/wardrobe/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/wardrobe/ListView.svelte @@ -1,12 +1,45 @@
- + + + {#if activeTab === 'garments'} + + {:else} + + {/if}
diff --git a/apps/mana/apps/web/src/lib/modules/wardrobe/components/OutfitCard.svelte b/apps/mana/apps/web/src/lib/modules/wardrobe/components/OutfitCard.svelte new file mode 100644 index 000000000..a8687cd91 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/wardrobe/components/OutfitCard.svelte @@ -0,0 +1,105 @@ + + + + +
+ {#if tryOnUrl} + {outfit.name} + + + Try-On + + {:else if resolvedGarments.length > 0} +
+ {#each resolvedGarments as g} + {@const mediaId = g.mediaIds[0]} +
+ {#if mediaId} + {g.name} + {/if} +
+ {/each} + {#if resolvedGarments.length < 4} + {#each Array(4 - resolvedGarments.length) as _, i (i)} +
+ {/each} + {/if} +
+ {:else} +
+ Leer +
+ {/if} + + {#if outfit.isFavorite} + + + + {/if} +
+
+

{outfit.name}

+
+ {outfit.garmentIds.length}{outfit.garmentIds.length === 1 ? ' Stück' : ' Stücke'} + {#if outfit.occasion} + · + {OCCASION_LABELS[outfit.occasion]} + {/if} +
+
+
diff --git a/apps/mana/apps/web/src/lib/modules/wardrobe/components/OutfitComposer.svelte b/apps/mana/apps/web/src/lib/modules/wardrobe/components/OutfitComposer.svelte new file mode 100644 index 000000000..27bb6182c --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/wardrobe/components/OutfitComposer.svelte @@ -0,0 +1,375 @@ + + + +
+ +
+
+

+ Kleiderschrank +

+ + {garments.length} + {garments.length === 1 ? 'Stück' : 'Stücke'} verfügbar + +
+ + {#if garments.length === 0} +
+

Nichts zum Kombinieren.

+

+ Lade zuerst ein paar Kleidungsstücke im Tab + Kleidung + hoch. +

+
+ {:else} +
+ {#each CATEGORY_ORDER as category} + {@const list = grouped[category]} + {#if list.length > 0} +
+

+ {CATEGORY_LABELS[category]} + · {list.length} +

+
+ {#each list as g (g.id)} + {@const mediaId = g.mediaIds[0]} + {@const selected = selectedIds.includes(g.id)} + + {/each} +
+
+ {/if} + {/each} +
+ {/if} +
+ + +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ Jahreszeit +
+ {#each Object.entries(SEASON_LABELS) as [season, label]} + {@const s = season as OutfitSeason} + {@const on = selectedSeasons.includes(s)} + + {/each} +
+
+ +
+ + +
+
+ +
+
+

+ Zusammenstellung + + · {selectedGarments.length} + {selectedGarments.length === 1 ? 'Stück' : 'Stücke'} + +

+
+ {#if selectedGarments.length === 0} +

+ Klicke links auf Kleidungsstücke, um sie dem Outfit hinzuzufügen. +

+ {:else} +
+ {#each selectedGarments as g (g.id)} + {@const mediaId = g.mediaIds[0]} +
+ {#if mediaId} + {g.name} + {/if} + +
+ {/each} +
+ {/if} +
+ + {#if error} + + {/if} + +
+ + {#if onCancel} + + {/if} +
+
+
diff --git a/apps/mana/apps/web/src/lib/modules/wardrobe/types.ts b/apps/mana/apps/web/src/lib/modules/wardrobe/types.ts index 25129a2c5..c8387a948 100644 --- a/apps/mana/apps/web/src/lib/modules/wardrobe/types.ts +++ b/apps/mana/apps/web/src/lib/modules/wardrobe/types.ts @@ -130,9 +130,16 @@ export function garmentPrimaryMediaId(garment: Pick): strin * lives in `picture.images` filtered by `wardrobeOutfitId === outfit.id` * — this pointer exists so the outfit detail view can render the latest * preview without re-querying. + * + * `imageUrl` is cached here (mana-media URL from the picture.images row) + * so OutfitCard's thumbnail renders without a second Dexie round-trip. + * The source of truth remains picture.images; if the user deletes that + * row the pointer goes stale but the card just falls back to the + * garment-collage render — no error. */ export interface OutfitTryOn { - imageId: string; // points at picture.images.id + imageId: string; // picture.images.id (UUID) + imageUrl: string; // mana-media URL, cached for cheap card rendering createdAt: string; // ISO prompt: string; model: string; diff --git a/apps/mana/apps/web/src/lib/modules/wardrobe/views/DetailOutfitView.svelte b/apps/mana/apps/web/src/lib/modules/wardrobe/views/DetailOutfitView.svelte new file mode 100644 index 000000000..a3035ec07 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/wardrobe/views/DetailOutfitView.svelte @@ -0,0 +1,281 @@ + + + +
+ + + {#if !outfit} + {#if outfit$.loading} +

Lädt…

+ {:else} +
+

Outfit nicht gefunden.

+

Gelöscht oder in einem anderen Space.

+
+ {/if} + {:else} +
+ +
+
+ {#if outfit.lastTryOn?.imageUrl} + Try-On Vorschau + {:else if resolvedGarments.length > 0} +
+ {#each resolvedGarments.slice(0, 4) as g} + {@const mediaId = g.mediaIds[0]} +
+ {#if mediaId} + {g.name} + {/if} +
+ {/each} +
+ {:else} +
+ Keine Kleidungsstücke +
+ {/if} +
+ + + + + {#if tryOns.length > 0} +
+

+ Try-On Verlauf +

+
+ {#each tryOns as t (t.id)} + {#if t.publicUrl} + {outfit.name} + {/if} + {/each} +
+
+ {/if} +
+ + +
+
+
+
+

{outfit.name}

+
+ + {outfit.garmentIds.length} + {outfit.garmentIds.length === 1 ? 'Stück' : 'Stücke'} + + {#if outfit.occasion} + · + {OCCASION_LABELS[outfit.occasion]} + {/if} + {#if outfit.season && outfit.season.length > 0} + · + {outfit.season.map((s) => SEASON_LABELS[s]).join(', ')} + {/if} +
+
+
+ + + + +
+
+ + {#if outfit.description} +

{outfit.description}

+ {/if} + + {#if outfit.tags.length > 0} +
+ {#each outfit.tags as tag} + + {tag} + + {/each} +
+ {/if} +
+ + +
+

+ Zusammenstellung +

+ {#if resolvedGarments.length > 0} +
+ {#each resolvedGarments as g (g.id)} + {@const mediaId = g.mediaIds[0]} + +
+ {#if mediaId} + {g.name} + {/if} +
+
+

{g.name}

+

+ {CATEGORY_LABELS_SINGULAR[g.category]} +

+
+
+ {/each} +
+ {:else} +

+ Referenzierte Kleidungsstücke wurden entfernt oder gehören zu einem anderen Space. +

+ {/if} +
+ + +
+ + +
+
+
+ {/if} +
diff --git a/apps/mana/apps/web/src/lib/modules/wardrobe/views/OutfitsView.svelte b/apps/mana/apps/web/src/lib/modules/wardrobe/views/OutfitsView.svelte new file mode 100644 index 000000000..6930f64c9 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/wardrobe/views/OutfitsView.svelte @@ -0,0 +1,78 @@ + + + +
+
+
+

Outfits

+ {#if outfits.length > 0} +

+ {outfits.length} + {outfits.length === 1 ? 'Zusammenstellung' : 'Zusammenstellungen'} +

+ {/if} +
+ + + Neues Outfit + +
+ + {#if outfits.length > 0} +
+ {#each outfits as outfit (outfit.id)} + + {/each} +
+ {:else if garments.length === 0} +
+ +

Noch keine Outfits.

+

+ Füge zuerst ein paar Kleidungsstücke im Tab "Kleidung" hinzu — danach lassen sie sich hier + zu Outfits kombinieren. +

+
+ {:else} +
+ +

Noch keine Outfits.

+

+ Kombiniere deine Kleidungsstücke zu Looks, die du dann mit KI an dir selbst anprobieren + kannst. +

+ + + Erstes Outfit komponieren + +
+ {/if} +
diff --git a/apps/mana/apps/web/src/routes/(app)/wardrobe/compose/[[outfitId]]/+page.svelte b/apps/mana/apps/web/src/routes/(app)/wardrobe/compose/[[outfitId]]/+page.svelte new file mode 100644 index 000000000..5fa34d0fa --- /dev/null +++ b/apps/mana/apps/web/src/routes/(app)/wardrobe/compose/[[outfitId]]/+page.svelte @@ -0,0 +1,94 @@ + + + + {outfitId ? 'Outfit bearbeiten' : 'Neues Outfit'} · Mana + + + +
+
+ + + +

+ {outfitId ? 'Outfit bearbeiten' : 'Neues Outfit'} +

+
+ + {#if outfitId && !outfit && !existingOutfit$.loading} +
+

Outfit nicht gefunden.

+

Gelöscht oder in einem anderen Space.

+
+ {:else} + + {#key outfitId ?? 'new'} + + {/key} + {/if} +
+
diff --git a/apps/mana/apps/web/src/routes/(app)/wardrobe/outfit/[id]/+page.svelte b/apps/mana/apps/web/src/routes/(app)/wardrobe/outfit/[id]/+page.svelte new file mode 100644 index 000000000..21654132c --- /dev/null +++ b/apps/mana/apps/web/src/routes/(app)/wardrobe/outfit/[id]/+page.svelte @@ -0,0 +1,19 @@ + + + + Outfit · Mana + + + + + {#key id} + + {/key} +