diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 68281b0..54b7235 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -154,10 +154,6 @@ export default function Layout({ children }: LayoutProps) { FiltersComponent: ContentFilters } - // Debug logs - console.log('Layout.tsx - State:', { viewMode, gridColumns, coverSize, currentView }) - console.log('Layout.tsx - viewContextValue:', viewContextValue) - return (
{/* Mobile Menu Overlay */} @@ -275,7 +271,6 @@ export default function Layout({ children }: LayoutProps) { value={gridColumns} onChange={(e) => { const newColumns = parseInt(e.target.value) - console.log('Layout.tsx - Grid slider changed:', newColumns) setGridColumns(newColumns) }} className="w-12 md:w-16 h-1 bg-slate-600 rounded-lg appearance-none cursor-pointer accent-brand-500" @@ -296,7 +291,6 @@ export default function Layout({ children }: LayoutProps) { value={coverSize} onChange={(e) => { const newSize = parseInt(e.target.value) - console.log('Layout.tsx - Cover slider changed:', newSize) setCoverSize(newSize) }} className="w-16 md:w-20 h-1 bg-slate-600 rounded-lg appearance-none cursor-pointer accent-brand-500" @@ -310,7 +304,6 @@ export default function Layout({ children }: LayoutProps) {
+
+ +
+ {/* Hero Section */} +
+ {item.title} +
+ +
+ {/* Poster Image - Overlapping */} + {item.title} + + {/* Title & Basic Info */} +
+
+ + {statusConfig.icon} + {statusConfig.label} + + {item.favorite && ( + + Favorit + + )} +
+ +

+ {item.title} +

+ +
+
+ {item.rating} +
+ {item.releaseYear} + {item.studio && {item.studio}} + {item.type === 'game' && item.platform && {item.platform}} + {item.type === 'adult' && item.platform && {item.platform}} +
+
+
+
+ + {/* Content Body */} +
+
+ + {/* Left Column: Description & Actions */} +
+ {/* Genres */} +
+ {item.genres.map(g => ( + + {g} + + ))} +
+ + {/* Description */} +
+

Beschreibung

+

+ {item.description || "Keine Beschreibung verfügbar."} +

+
+ + {/* Cast & Crew Section (Updated to include Adult) */} + {(item.type === 'movie' || item.type === 'series' || item.type === 'adult') && ( +
+

+ + {item.type === 'adult' ? 'Performer & Besetzung' : 'Besetzung & Crew'} +

+ + {/* Cast List */} + {item.cast && item.cast.length > 0 ? ( +
+ {item.cast.map((member, idx) => ( +
onSelectPerson && onSelectPerson(member.name)} + className="flex items-center gap-3 bg-slate-800/50 p-2 rounded-xl border border-white/5 hover:border-brand-500/30 transition-colors group cursor-pointer" + > +
+ {member.imageUrl ? ( + {member.name} + ) : ( +
+ +
+ )} +
+
+
{member.name}
+
{member.role}
+
+
+ ))} +
+ ) : ( +
+ +

Keine Informationen zur Besetzung verfügbar.

+
+ )} +
+ )} + + {/* Actions Bar */} +
+ + +
+
+ + {/* Right Column: Stats & Metadata */} +
+ {/* Stats Box */} +
+

+ + Statistiken +

+ + {/* Game Stats */} + {item.type === 'game' && ( +
+ {/* Playtime & Completion */} +
+
+ {/* Playtime Header */} +
+
+ Spielzeit +
+
+ {item.playtime || 0} Std. +
+
+ + {/* Completion Progress */} +
+
+
+ Fortschritt +
+
+ {item.completionRate || 0}% +
+
+
+
+
+
+
+ + {/* Decorative Icon */} + +
+ + {/* Achievements */} + {(item.achievementsTotal || 0) > 0 && ( +
+
+
+ Erfolge +
+
+ {Math.round(((item.achievementsUnlocked || 0) / (item.achievementsTotal || 1)) * 100)}% +
+
+ +
+
+
+ +
+ {item.achievementsUnlocked} freigeschaltet + {item.achievementsTotal} gesamt +
+
+ )} +
+ )} + + {/* Series Stats */} + {item.type === 'series' && ( +
+ {/* Enhanced Season Display */} +
+
+
+ Anzahl Staffeln +
+
{item.seasons || 1}
+
+ {/* Decorative Icon */} + + +
+ +
+
Gesamt Episoden
+
{item.episodesTotal || '?'}
+
+
+ +
+
+
+ Episoden-Fortschritt +
+
+ {Math.round(((item.episodesWatched || 0) / (item.episodesTotal || 1)) * 100)}% +
+
+ +
+
+
+ +
+
+ Bereits gesehen + {item.episodesWatched || 0} Episoden +
+
+ Verfügbar + {item.episodesTotal || '?'} Episoden +
+
+
+
+ )} + + {/* Movie & Adult Stats (Duration) */} + {(item.type === 'movie' || item.type === 'adult') && ( +
+
Laufzeit
+
{item.duration} Minuten
+
+ )} +
+ + {/* Additional Metadata Info Box */} +
+
+
+ Hinzugefügt am + {new Date(item.addedAt).toLocaleDateString()} +
+
+ Typ + {item.type} +
+
+ ID + {item.id.substring(0,8)}... +
+
+
+
+
+ + {/* Related Media Section */} + {relatedItems.length > 0 && ( +
+

+ + Ähnliche Titel +

+
+ {relatedItems.map(related => ( +
onSelectRelated(related)} + className="group cursor-pointer bg-dark-surface rounded-xl overflow-hidden border border-dark-border hover:border-brand-500/50 hover:shadow-lg transition-all" + > +
+ {related.title} +
+
+ + {related.rating} +
+
+
+

{related.title}

+

{related.releaseYear}

+
+
+ ))} +
+
+ )} +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/components/MediaListView.tsx b/src/components/MediaListView.tsx new file mode 100644 index 0000000..2e6fc3d --- /dev/null +++ b/src/components/MediaListView.tsx @@ -0,0 +1,129 @@ +import React from 'react'; +import { MediaItem } from '../types'; +import { Monitor, Star, Check, Circle, Clock, CheckCircle2, Gamepad2, Film, Tv } from 'lucide-react'; +import { STATUS_CONFIG } from '../constants'; + +interface MediaListViewProps { + items: MediaItem[]; + onSelect: (item: MediaItem) => void; + selectedId: string | null; + onToggleSelect: (id: string) => void; + multiSelectedIds: Set; +} + +export const MediaListView: React.FC = ({ + items, + onSelect, + selectedId, + onToggleSelect, + multiSelectedIds +}) => { + + const getTypeIcon = (type: string) => { + switch(type) { + case 'game': return ; + case 'movie': return ; + case 'series': return ; + default: return ; + } + }; + + const getStatusIcon = (status: string) => { + const config = STATUS_CONFIG[status as keyof typeof STATUS_CONFIG]; + const iconClass = `${config.color.replace('bg-', 'text-').replace('/10', '')}`; + + switch(config.icon) { + case 'check-circle': return ; + case 'monitor': return ; + case 'clock': return ; + case 'circle': return ; + default: return ; + } + }; + + return ( +
+ {/* Table Header */} +
+
{/* Checkbox */}
+
Type
+
Title
+
Platform
+
Year
+
Score
+
Stat
+
+ + {/* Table Body */} +
+ {items.length === 0 ? ( +
No items found
+ ) : ( + items.map((item, index) => { + const isRowSelected = selectedId === item.id; + const isChecked = multiSelectedIds.has(item.id); + + return ( +
onSelect(item)} + className={`flex items-center h-9 border-b border-slate-100 dark:border-slate-700/50 cursor-pointer transition-colors ${ + isRowSelected + ? 'bg-blue-500 text-white' + : index % 2 === 0 ? 'bg-slate-50 dark:bg-slate-800/30 hover:bg-blue-50 dark:hover:bg-slate-700' : 'bg-white dark:bg-transparent hover:bg-blue-50 dark:hover:bg-slate-700' + }`} + > + {/* Checkbox */} +
e.stopPropagation()}> + onToggleSelect(item.id)} + className={`rounded border-slate-300 dark:border-slate-600 ${isRowSelected ? 'text-white border-white' : 'text-blue-500'}`} + /> +
+ + {/* Icon */} +
+ {getTypeIcon(item.type)} +
+ + {/* Title */} +
+ {item.title} +
+ + {/* Platform (Hidden on mobile) */} + + + {/* Year */} +
+ {item.releaseYear} +
+ + {/* Rating */} +
+ {item.rating > 0 ? item.rating.toFixed(1) : '-'} +
+ + {/* Status Icon */} +
+ {getStatusIcon(item.status)} +
+
+ ); + }) + )} +
+ + {/* Footer Status Bar matching screenshot */} +
+ {items.length} Items listed +
+ {multiSelectedIds.size} Selected +
+
+ ); +}; \ No newline at end of file diff --git a/src/constants/index.ts b/src/constants/index.ts new file mode 100644 index 0000000..d058043 --- /dev/null +++ b/src/constants/index.ts @@ -0,0 +1,27 @@ +export const STATUS_CONFIG = { + completed: { + label: 'Completed', + color: 'bg-green-500/10', + icon: 'check-circle' + }, + watching: { + label: 'Watching', + color: 'bg-blue-500/10', + icon: 'monitor' + }, + plan_to_watch: { + label: 'Plan to Watch', + color: 'bg-yellow-500/10', + icon: 'clock' + }, + on_hold: { + label: 'On Hold', + color: 'bg-orange-500/10', + icon: 'circle' + }, + dropped: { + label: 'Dropped', + color: 'bg-red-500/10', + icon: 'circle' + } +} as const; diff --git a/src/pages/Adult copy.tsx b/src/pages/Adult copy.tsx index 326532d..d8bb3a4 100644 --- a/src/pages/Adult copy.tsx +++ b/src/pages/Adult copy.tsx @@ -16,8 +16,8 @@ import { useAdults } from '../hooks/useApi' import { Tooltip } from '../components/MicroInteractions' import { ViewContext } from '../components/Layout' -// Import from alternative frontend -import { MediaListView } from '../../../frontend/components/MediaListView' +// Import from components +import { MediaListView } from '../components/MediaListView' interface PaginatedResponse { items: T[] diff --git a/src/pages/Adult.tsx b/src/pages/Adult.tsx index 140bc48..31da077 100644 --- a/src/pages/Adult.tsx +++ b/src/pages/Adult.tsx @@ -16,9 +16,9 @@ import { useAdults } from '../hooks/useApi' import { Tooltip } from '../components/MicroInteractions' import { ViewContext } from '../components/Layout' -// Import from alternative frontend -import { MediaListView } from '../../../frontend/components/MediaListView' -import { MediaDetailView } from '../../../frontend/components/MediaDetailView' +// Import from components +import { MediaListView } from '../components/MediaListView' +import { MediaDetailView } from '../components/MediaDetailView' interface PaginatedResponse { items: T[] diff --git a/src/pages/Games.tsx b/src/pages/Games.tsx index b1cac2f..cc97019 100644 --- a/src/pages/Games.tsx +++ b/src/pages/Games.tsx @@ -5,9 +5,9 @@ import { ViewContext } from '../components/Layout' import { useNavigate } from 'react-router-dom' import { ComputerDesktopIcon as GamepadIcon } from '@heroicons/react/24/outline' -// Import from alternative frontend -import { MediaListView } from '../../../frontend/components/MediaListView' -import { MediaDetailView } from '../../../frontend/components/MediaDetailView' +// Import from components +import { MediaListView } from '../components/MediaListView' +import { MediaDetailView } from '../components/MediaDetailView' export default function Games() { const viewContext = useContext(ViewContext) diff --git a/src/pages/Movies.tsx b/src/pages/Movies.tsx index fbf3faf..a4e293a 100644 --- a/src/pages/Movies.tsx +++ b/src/pages/Movies.tsx @@ -6,9 +6,9 @@ import { useMovies } from '../hooks/useApi' import { ViewContext } from '../components/Layout' import { FilmIcon } from '@heroicons/react/24/outline' -// Import from alternative frontend -import { MediaListView } from '../../../frontend/components/MediaListView' -import { MediaDetailView } from '../../../frontend/components/MediaDetailView' +// Import from components +import { MediaListView } from '../components/MediaListView' +import { MediaDetailView } from '../components/MediaDetailView' export default function Movies() { const viewContext = useContext(ViewContext) diff --git a/src/pages/TVShows.tsx b/src/pages/TVShows.tsx index 6be5d0b..408e30d 100644 --- a/src/pages/TVShows.tsx +++ b/src/pages/TVShows.tsx @@ -6,9 +6,9 @@ import { TvIcon } from '@heroicons/react/24/outline' import { useNavigate } from 'react-router-dom' import { Play, Eye, Lock} from 'lucide-react' -// Import from alternative frontend -import { MediaListView } from '../../../frontend/components/MediaListView' -import { MediaDetailView } from '../../../frontend/components/MediaDetailView' +// Import from components +import { MediaListView } from '../components/MediaListView' +import { MediaDetailView } from '../components/MediaDetailView' export default function TVShows() { const viewContext = useContext(ViewContext) diff --git a/src/services/api.ts b/src/services/api.ts index bdc27de..f3b2680 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -4,14 +4,6 @@ import { PaginatedResponse } from '../types' // API base configuration const API_BASE_URL = (import.meta as any).env?.VITE_API_URL || '/api' -// Types for API responses -interface ApiResponse { - success: boolean - data?: T - message?: string - error?: string -} - interface LocalPaginatedResponse { items: T[] pagination: { @@ -228,6 +220,10 @@ export const tvShowsApi = { // Return fallback data if API fails return { items: [], + total: 0, + page: 1, + limit: 20, + totalPages: 1, pagination: { total: 0, per_page: 20, @@ -512,6 +508,10 @@ export const adultApi = { // Return fallback data if API fails return { items: [], + total: 0, + page: 1, + limit: 20, + totalPages: 1, pagination: { total: 0, per_page: 20, @@ -578,6 +578,10 @@ export const actorsApi = { // Return fallback data if API fails return { items: [], + total: 0, + page: 1, + limit: 20, + totalPages: 1, pagination: { total: 0, per_page: 20, diff --git a/src/types/index.ts b/src/types/index.ts index b79455a..1731b55 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -72,11 +72,22 @@ export interface SearchParams { } export interface PaginatedResponse { - data: T[] + items: T[] total: number page: number limit: number totalPages: number + pagination?: { + total: number + per_page: number + current_page: number + last_page: number + } + available_filters?: { + genres?: string[] + directors?: string[] + sources?: string[] + } } -export type ViewMode = 'grid' | 'list' | 'covers' +export type ViewMode = 'grid' | 'list' | 'covers' | 'cover' diff --git a/vite.config.ts b/vite.config.ts index 2e23dff..ca4a5c6 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -15,7 +15,7 @@ export default defineConfig({ } }, build: { - outDir: '../public/react', + outDir: '../public/dist', emptyOutDir: true } })