# Plan: Layout Hamburger + Media Browser Fixes + Retroactive Import ## Kontext Vier unabhängige Bereiche: 1. **Layout**: Sidebar hat kein Mobile-Support, kein Hamburger-Menü → Content füllt nicht volle Breite auf kleinen Screens 2. **Media Browser Previews**: glTF-Assets zeigen nur Icon-Placeholder; CadFile-Thumbnails wären als Preview nutzbar 3. **Media Browser Filter-Defaults**: Aktuell kein Default-Filter → alle Types (inkl. GLB/STL) sichtbar; gewünscht: Default nur still + turntable 4. **Retroactive Import**: Bestehende `cad_files.thumbnail_path` und `order_lines.result_path` sind nicht als `media_assets` erfasst --- ## Betroffene Dateien | Datei | Änderung | |-------|----------| | `frontend/src/components/layout/Layout.tsx` | Hamburger-Menü + Mobile-Overlay | | `frontend/src/pages/MediaBrowser.tsx` | Filter-Chips + Previews + Default-Filter | | `frontend/src/api/media.ts` | `asset_types[]` statt `asset_type` + `thumbnail_url` Feld | | `backend/app/domains/media/schemas.py` | `thumbnail_url: str | None` Feld | | `backend/app/domains/media/router.py` | `asset_types` Multi-Query-Param + thumbnail_url befüllen | | `backend/app/domains/media/service.py` | `get_thumbnail_url(asset)` Helper | | `backend/app/api/routers/admin.py` | `POST /api/admin/import-media-assets` Endpoint | | `frontend/src/pages/Admin.tsx` | Button "Import Existing Media" | --- ## Tasks (in Reihenfolge) ### Task 1: Layout — Hamburger-Menü + Mobile Sidebar - **Datei**: `frontend/src/components/layout/Layout.tsx` - **Was**: - State `sidebarOpen: boolean` (default: `false` auf mobile, `true` auf desktop via window.innerWidth) - Hamburger-Button (`Menu`-Icon aus lucide) in einem mobilen Header-Bar (nur sichtbar `< md`, also `md:hidden`) - Sidebar: auf mobile `fixed left-0 top-0 h-full z-40 transform transition-transform`, bei `sidebarOpen`: `translate-x-0`, sonst `-translate-x-full`; auf Desktop immer sichtbar (`md:relative md:translate-x-0`) - Overlay-Backdrop: halbtransparentes `div` hinter Sidebar, nur auf mobile sichtbar wenn open, click schließt Sidebar - Close-Button (X) oben in Sidebar auf mobile - Content-Bereich: `flex-1 overflow-auto min-w-0` damit er immer volle restliche Breite nutzt - **Akzeptanzkriterium**: Auf <768px Hamburger sichtbar, Sidebar aus-/einblendbar; auf ≥768px Sidebar immer sichtbar ### Task 2: Backend — `asset_types[]` Multi-Filter + `thumbnail_url` - **Datei**: `backend/app/domains/media/router.py`, `backend/app/domains/media/schemas.py`, `backend/app/domains/media/service.py` - **Was**: - `list_assets` Endpoint: Zusätzlichen Query-Param `asset_types: list[MediaAssetType] = Query(default=[])` hinzufügen - Filter-Logik: wenn `asset_types` nicht leer → `WHERE asset_type IN (asset_types)`; sonst wenn `asset_type` gesetzt → wie bisher - `MediaAssetOut`: neues Feld `thumbnail_url: str | None = None` - `service.py`: neue Funktion `get_thumbnail_url(asset) -> str | None` — gibt `/api/cad/{cad_file_id}/thumbnail` zurück wenn `cad_file_id` gesetzt (unabhängig von asset_type) - In `list_assets` und `get_asset`: `a.thumbnail_url = service.get_thumbnail_url(a)` setzen (analog zu `download_url`) - **Akzeptanzkriterium**: `GET /api/media/?asset_types=still&asset_types=turntable` gibt nur still+turntable zurück; jedes Asset mit `cad_file_id` hat `thumbnail_url` gesetzt ### Task 3: Frontend — Media Browser Filter-Chips + Previews - **Datei**: `frontend/src/pages/MediaBrowser.tsx`, `frontend/src/api/media.ts` - **Was**: - `api/media.ts`: `MediaFilter.asset_types?: MediaAssetType[]` (statt `asset_type`); `getMediaAssets` sendet `asset_types` als repeated params; `MediaAsset` bekommt `thumbnail_url: string | null` - `MediaBrowser.tsx`: - State: `activeTypes: Set` — Default: `new Set(['still', 'turntable'])` - Filter-UI: Chip-Grid mit allen Types; `still`/`turntable`/`thumbnail` in der Hauptreihe; `gltf_geometry`/`gltf_production`/`blend_production`/`stl_low`/`stl_high` hinter "Advanced" Toggle (collapsed by default) - Chip aktiv = farbiger Hintergrund entsprechend `TYPE_COLORS`; inaktiv = grau - Chip-Klick toggled den Type aus `activeTypes` - `getMediaAssets({ asset_types: [...activeTypes], ... })` - `AssetCard`: wenn `isImageAsset(type)` → `download_url`; wenn `thumbnail_url` vorhanden → `thumbnail_url` als Preview; sonst Icon - Video-Assets (`turntable`): Video-Poster via `thumbnail_url` (falls vorhanden) mit `