298 lines
14 KiB
TypeScript
298 lines
14 KiB
TypeScript
// src/pages/DreamPage.tsx
|
|
|
|
import {type NavigateFunction, useNavigate, useParams} from 'react-router-dom';
|
|
import {mockDreams} from '../data/MockDreams';
|
|
import MockUsers from '../data/MockUsers';
|
|
import User from '../types/User';
|
|
import type Dream from "../types/Dream.ts";
|
|
import {formatDateNumeric, formatDateWithTime} from '../utils/DateUtils';
|
|
import {lazy, Suspense} from 'react';
|
|
|
|
// Lazy load components
|
|
const DreamVR = lazy(() => import('../components/DreamVR'));
|
|
const SliderComponent = lazy(() => import('react-slick'));
|
|
const EEGChart = lazy(() => import('../components/DreamCharts').then(module => ({default: module.EEGChart})));
|
|
const VitalsChart = lazy(() => import('../components/DreamCharts').then(module => ({default: module.VitalsChart})));
|
|
const MovementChart = lazy(() => import('../components/DreamCharts').then(module => ({default: module.MovementChart})));
|
|
|
|
export default function DreamPage() {
|
|
const {id} = useParams<{ id: string }>();
|
|
const navigate: NavigateFunction = useNavigate();
|
|
|
|
const dream: Dream | undefined = mockDreams.find(d => d.id === Number(id));
|
|
const user: User | undefined = dream ? MockUsers.find(u => u.id === dream.userId) : undefined;
|
|
|
|
|
|
if (!dream) {
|
|
return (<div className="page p-4">
|
|
<button
|
|
onClick={() => navigate(-1)}
|
|
className="mb-4 hover:underline"
|
|
style={{color: 'var(--accent)'}}
|
|
>
|
|
← Zurück
|
|
</button>
|
|
<p style={{color: 'var(--text-muted)'}}>Traum nicht gefunden.</p>
|
|
</div>);
|
|
}
|
|
|
|
return (<div className="page min-h-screen pb-20">
|
|
{/* Header */}
|
|
<div className="relative" style={{background: 'var(--accent-gradient)'}}>
|
|
<div className="p-4 sm:p-6 md:p-8 text-white">
|
|
<button
|
|
onClick={() => navigate(-1)}
|
|
className="absolute top-4 left-4 p-2 rounded-full bg-white/20 backdrop-blur-sm"
|
|
>
|
|
←
|
|
</button>
|
|
<div className="flex flex-col sm:flex-row sm:items-center sm:space-x-4 mt-8">
|
|
{user && (<img
|
|
src={`/assets/profiles/${user.profilePicture}`}
|
|
alt={user.name}
|
|
className="w-12 h-12 sm:w-16 sm:h-16 rounded-full border-4 border-white/30 object-cover mx-auto sm:mx-0 mb-3 sm:mb-0"
|
|
/>)}
|
|
<div className="text-center sm:text-left">
|
|
<h1 className="font-bold text-xl sm:text-2xl md:text-3xl lg:text-4xl">
|
|
{dream.title}
|
|
</h1>
|
|
{user && (<p className="mt-1 text-white/80 text-sm sm:text-base">
|
|
{user.name} •{' '}
|
|
{formatDateWithTime(dream.date)}
|
|
</p>)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="p-4 sm:p-6 md:p-8 space-y-6 sm:space-y-8 max-w-4xl mx-auto">
|
|
<div className="dreamy-card rounded-xl sm:rounded-2xl p-4 sm:p-6">
|
|
<div className="flex items-center mb-4">
|
|
<span className="font-medium dreamy-text">Traum-Beschreibung</span>
|
|
</div>
|
|
|
|
{dream.input.inputType === 'image' && (
|
|
<div className="flex flex-col sm:flex-row gap-4 mb-4">
|
|
<div className="sm:w-1/3">
|
|
<img
|
|
src={`/assets/dreams/images/${dream.input.img}`}
|
|
alt={dream.input.imgAlt}
|
|
className="w-full rounded-lg shadow-lg object-cover"
|
|
/>
|
|
</div>
|
|
<div className="sm:w-2/3">
|
|
<p className="leading-relaxed text-base sm:text-lg">
|
|
{dream.input.description}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{dream.input.inputType === 'audio' && (
|
|
<div className="flex flex-col gap-4 mb-4">
|
|
<div className="w-full">
|
|
<audio
|
|
controls
|
|
src={`/assets/dreams/audio/${dream.input.audio}`}
|
|
className="w-full"
|
|
>
|
|
Ihr Browser unterstützt das Audio-Element nicht.
|
|
</audio>
|
|
</div>
|
|
<div className="w-full">
|
|
<p className="leading-relaxed text-base sm:text-lg">
|
|
{dream.input.transcript}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{dream.input.inputType === 'text' && (
|
|
<p className="leading-relaxed text-base sm:text-lg">
|
|
{dream.input.input}
|
|
</p>
|
|
)}
|
|
|
|
{dream.input.inputType === 'chip' && (
|
|
<div className="flex flex-col gap-4">
|
|
<p className="leading-relaxed text-base sm:text-lg">
|
|
{dream.input.text}
|
|
</p>
|
|
<Suspense fallback={<div
|
|
className="w-full h-64 mt-4 border border-gray-200 rounded-lg p-2 flex justify-center items-center">
|
|
<p>Lade EEG-Daten...</p>
|
|
</div>}>
|
|
<EEGChart chipInput={dream.input}/>
|
|
</Suspense>
|
|
<Suspense fallback={<div
|
|
className="w-full h-64 mt-4 border border-gray-200 rounded-lg p-2 flex justify-center items-center">
|
|
<p>Lade Vitaldaten...</p>
|
|
</div>}>
|
|
<VitalsChart chipInput={dream.input}/>
|
|
</Suspense>
|
|
<Suspense fallback={<div
|
|
className="w-full h-64 mt-4 border border-gray-200 rounded-lg p-2 flex justify-center items-center">
|
|
<p>Lade Bewegungsdaten...</p>
|
|
</div>}>
|
|
<MovementChart chipInput={dream.input}/>
|
|
</Suspense>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{dream.ai?.interpretation && dream.ai.interpretation !== '' && (<div
|
|
className="dreamy-card rounded-xl sm:rounded-2xl p-4 sm:p-6">
|
|
<div className="flex items-center mb-4">
|
|
<span className="font-medium dreamy-text">KI-Interpretation</span>
|
|
</div>
|
|
<p className="leading-relaxed">
|
|
{dream.ai.interpretation}
|
|
</p>
|
|
</div>)}
|
|
|
|
|
|
{/* Carousel for KI-Bild and KI-Video if both exist */}
|
|
{dream.ai?.image && dream.ai.video && dream.ai.image !== '' && dream.ai.video !== '' && (
|
|
<div className="dreamy-card rounded-xl sm:rounded-2xl p-4 sm:p-6">
|
|
<div className="flex items-center mb-4">
|
|
<span className="font-medium dreamy-text">KI-Medien</span>
|
|
</div>
|
|
<Suspense fallback={<div className="flex justify-center items-center h-[70vh]">
|
|
<p>Lade Medien-Karussell...</p>
|
|
</div>}>
|
|
<SliderComponent
|
|
dots={true}
|
|
infinite={true}
|
|
speed={500}
|
|
slidesToShow={1}
|
|
slidesToScroll={1}
|
|
className="max-w-full mx-auto carousel-container"
|
|
onInit={() => {
|
|
import('slick-carousel/slick/slick.css');
|
|
import('slick-carousel/slick/slick-theme.css');
|
|
}}
|
|
>
|
|
<div className="flex justify-center px-2">
|
|
<img
|
|
src={`/assets/dreams/images/${dream.ai.image}`}
|
|
alt="KI-generiertes Traumbild"
|
|
className="max-w-full w-full rounded-lg shadow-lg object-contain mx-auto"
|
|
style={{maxHeight: '70vh'}}
|
|
/>
|
|
</div>
|
|
<div className="flex justify-center px-2">
|
|
<video
|
|
controls
|
|
loop
|
|
src={`/assets/dreams/videos/${dream.ai.video}`}
|
|
className="max-w-full w-full rounded-lg shadow-lg object-contain mx-auto"
|
|
style={{maxHeight: '70vh'}}
|
|
>
|
|
Ihr Browser unterstützt das Video-Element nicht.
|
|
</video>
|
|
</div>
|
|
</SliderComponent>
|
|
</Suspense>
|
|
</div>)}
|
|
|
|
{/* Show KI-Bild alone if video doesn't exist */}
|
|
{dream.ai?.image && dream.ai.image !== '' && (!dream.ai.video || dream.ai.video === '') && (
|
|
<div className="dreamy-card rounded-xl sm:rounded-2xl p-4 sm:p-6">
|
|
<div className="flex items-center mb-4">
|
|
<span className="font-medium dreamy-text">KI-Bild</span>
|
|
</div>
|
|
<div className="flex justify-center">
|
|
<img
|
|
src={`/assets/dreams/images/${dream.ai.image}`}
|
|
alt="KI-generiertes Traumbild"
|
|
className="max-w-full w-full rounded-lg shadow-lg object-contain mx-auto"
|
|
style={{maxHeight: '70vh'}}
|
|
/>
|
|
</div>
|
|
</div>)}
|
|
|
|
{/* Show KI-Video alone if image doesn't exist */}
|
|
{dream.ai?.video && dream.ai.video !== '' && (!dream.ai.image || dream.ai.image === '') && (
|
|
<div className="dreamy-card rounded-xl sm:rounded-2xl p-4 sm:p-6">
|
|
<div className="flex items-center mb-4">
|
|
<span className="font-medium dreamy-text">KI-Video</span>
|
|
</div>
|
|
<div className="flex justify-center">
|
|
<video
|
|
controls
|
|
loop
|
|
src={`/assets/dreams/videos/${dream.ai.video}`}
|
|
className="max-w-full w-full rounded-lg shadow-lg object-contain mx-auto"
|
|
style={{maxHeight: '70vh'}}
|
|
>
|
|
Ihr Browser unterstützt das Video-Element nicht.
|
|
</video>
|
|
</div>
|
|
</div>)}
|
|
|
|
{dream.ai?.audio && dream.ai.audio !== '' && (<div
|
|
className="dreamy-card rounded-xl sm:rounded-2xl p-4 sm:p-6">
|
|
<div className="flex items-center mb-4">
|
|
<span className="font-medium dreamy-text">KI-Audio</span>
|
|
</div>
|
|
<div className="flex justify-center">
|
|
<audio
|
|
controls
|
|
autoPlay
|
|
loop
|
|
src={`/assets/dreams/audio/${dream.ai.audio}`}
|
|
className="w-full max-w-xs sm:max-w-md md:max-w-lg mx-auto"
|
|
>
|
|
Ihr Browser unterstützt das Audio-Element nicht.
|
|
</audio>
|
|
</div>
|
|
</div>)}
|
|
|
|
{/* VR Visualization */}
|
|
{(dream.id === 10 || dream.input.inputType === 'chip') && (
|
|
<div className="dreamy-card rounded-xl sm:rounded-2xl p-4 sm:p-6">
|
|
<div className="flex items-center mb-4">
|
|
<span className="font-medium dreamy-text">VR-Visualisierung</span>
|
|
</div>
|
|
<Suspense fallback={<div className="flex justify-center items-center h-[600px]">
|
|
<p>Lade VR-Visualisierung...</p>
|
|
</div>}>
|
|
<DreamVR dream={dream} height="600px"/>
|
|
</Suspense>
|
|
</div>
|
|
)}
|
|
|
|
<div className="dreamy-card rounded-xl sm:rounded-2xl p-4 sm:p-6">
|
|
<h2 className="text-lg font-semibold mb-3 dreamy-text">Details</h2>
|
|
<div className="space-y-3" style={{color: 'var(--text-muted)'}}>
|
|
<div className="flex flex-col sm:flex-row sm:justify-between">
|
|
<span className="font-medium mb-1 sm:mb-0">Eingabetyp</span>
|
|
<span className="capitalize">{dream.input.inputType}</span>
|
|
</div>
|
|
<div className="flex flex-col sm:flex-row sm:justify-between">
|
|
<span className="font-medium mb-1 sm:mb-0">Datum</span>
|
|
<span>
|
|
{formatDateNumeric(dream.date)}
|
|
</span>
|
|
</div>
|
|
<div className="flex flex-col sm:flex-row sm:justify-between">
|
|
<span className="font-medium mb-1 sm:mb-0">Tags</span>
|
|
<div className="flex flex-wrap gap-2">
|
|
{dream.tags.map((tag, index) => (
|
|
<span key={index} className="px-2 py-1 text-xs rounded-full"
|
|
style={{
|
|
background: 'var(--accent-gradient)',
|
|
color: 'white',
|
|
boxShadow: '0 2px 5px var(--shadow)'
|
|
}}>
|
|
{tag}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>);
|
|
}
|