'use client' import { useState, useEffect, useCallback } from 'react' import dynamic from 'next/dynamic' import { AOIResponse, AOITheme, AOIQuality, Difficulty, GeoJSONPolygon, LearningNode, GeoServiceHealth, DemoTemplate, } from './types' // Dynamic imports for map components (no SSR) const AOISelector = dynamic( () => import('@/components/geo-lernwelt/AOISelector'), { ssr: false, loading: () => } ) const UnityViewer = dynamic( () => import('@/components/geo-lernwelt/UnityViewer'), { ssr: false } ) // API base URL const GEO_SERVICE_URL = process.env.NEXT_PUBLIC_GEO_SERVICE_URL || 'http://localhost:8088' // Loading placeholder for map function MapLoadingPlaceholder() { return (
Karte wird geladen...
) } // Theme icons and colors const THEME_CONFIG: Record = { topographie: { icon: '🏔️', color: 'bg-amber-500', label: 'Topographie' }, landnutzung: { icon: '🏘️', color: 'bg-green-500', label: 'Landnutzung' }, orientierung: { icon: '🧭', color: 'bg-blue-500', label: 'Orientierung' }, geologie: { icon: '🪨', color: 'bg-stone-500', label: 'Geologie' }, hydrologie: { icon: '💧', color: 'bg-cyan-500', label: 'Hydrologie' }, vegetation: { icon: '🌲', color: 'bg-emerald-500', label: 'Vegetation' }, } export default function GeoLernweltPage() { // State const [serviceHealth, setServiceHealth] = useState(null) const [currentAOI, setCurrentAOI] = useState(null) const [drawnPolygon, setDrawnPolygon] = useState(null) const [selectedTheme, setSelectedTheme] = useState('topographie') const [quality, setQuality] = useState('medium') const [difficulty, setDifficulty] = useState('mittel') const [learningNodes, setLearningNodes] = useState([]) const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState(null) const [activeTab, setActiveTab] = useState<'map' | 'unity'>('map') const [demoTemplate, setDemoTemplate] = useState(null) // Check service health on mount useEffect(() => { checkServiceHealth() loadMainauTemplate() }, []) const checkServiceHealth = async () => { try { const res = await fetch(`${GEO_SERVICE_URL}/health`) if (res.ok) { const health = await res.json() setServiceHealth(health) } } catch (e) { console.error('Service health check failed:', e) setServiceHealth(null) } } const loadMainauTemplate = async () => { try { const res = await fetch(`${GEO_SERVICE_URL}/api/v1/aoi/templates/mainau`) if (res.ok) { const template = await res.json() setDemoTemplate(template) } } catch (e) { console.error('Failed to load Mainau template:', e) } } const handlePolygonDrawn = useCallback((polygon: GeoJSONPolygon) => { setDrawnPolygon(polygon) setError(null) }, []) const handleCreateAOI = async () => { if (!drawnPolygon) { setError('Bitte zeichne zuerst ein Gebiet auf der Karte.') return } setIsLoading(true) setError(null) try { const res = await fetch(`${GEO_SERVICE_URL}/api/v1/aoi`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ polygon: drawnPolygon, theme: selectedTheme, quality, }), }) if (!res.ok) { const errorData = await res.json() throw new Error(errorData.detail || 'Fehler beim Erstellen des Gebiets') } const aoi = await res.json() setCurrentAOI(aoi) // Poll for completion pollAOIStatus(aoi.aoi_id) } catch (e) { setError(e instanceof Error ? e.message : 'Unbekannter Fehler') } finally { setIsLoading(false) } } const pollAOIStatus = async (aoiId: string) => { const poll = async () => { try { const res = await fetch(`${GEO_SERVICE_URL}/api/v1/aoi/${aoiId}`) if (res.ok) { const aoi = await res.json() setCurrentAOI(aoi) if (aoi.status === 'completed') { // Load learning nodes generateLearningNodes(aoiId) } else if (aoi.status === 'failed') { setError('Verarbeitung fehlgeschlagen') } else { // Continue polling setTimeout(poll, 2000) } } } catch (e) { console.error('Polling error:', e) } } poll() } const generateLearningNodes = async (aoiId: string) => { try { const res = await fetch(`${GEO_SERVICE_URL}/api/v1/learning/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ aoi_id: aoiId, theme: selectedTheme, difficulty, node_count: 5, language: 'de', }), }) if (res.ok) { const data = await res.json() setLearningNodes(data.nodes) } } catch (e) { console.error('Failed to generate learning nodes:', e) } } const handleLoadDemo = () => { if (demoTemplate) { setDrawnPolygon(demoTemplate.polygon) setSelectedTheme(demoTemplate.suggested_themes[0] || 'topographie') } } return (
{/* Header */}
🌍

Geo-Lernwelt

Interaktive Erdkunde-Lernplattform

{/* Service Status */}
{serviceHealth?.status === 'healthy' ? 'Verbunden' : 'Verbinde...'}
{/* Tab Navigation */}
{/* Error Message */} {error && (
{error}
)} {activeTab === 'map' ? (
{/* Map Section (2/3) */}
{/* Map Card */}

Gebiet auf der Karte waehlen

{demoTemplate && ( )}

Zeichne ein Polygon (max. 4 km²) um das gewuenschte Lerngebiet

{/* Attribution */}
Kartendaten: © OpenStreetMap contributors (ODbL) | Hoehenmodell: © Copernicus DEM
{/* Settings Panel (1/3) */}
{/* Theme Selection */}

Lernthema

{(Object.keys(THEME_CONFIG) as AOITheme[]).map((theme) => { const config = THEME_CONFIG[theme] return ( ) })}
{/* Quality Selection */}

Qualitaet

{(['low', 'medium', 'high'] as AOIQuality[]).map((q) => ( ))}
{/* Difficulty Selection */}

Schwierigkeitsgrad

{(['leicht', 'mittel', 'schwer'] as Difficulty[]).map((d) => ( ))}
{/* Area Info */} {drawnPolygon && (

Ausgewaehltes Gebiet

Polygon gezeichnet ✓

Klicke "Lernwelt erstellen" um fortzufahren

)} {/* Create Button */} {/* AOI Status */} {currentAOI && (

Status

{currentAOI.status === 'queued' ? 'In Warteschlange...' : currentAOI.status === 'processing' ? 'Wird verarbeitet...' : currentAOI.status === 'completed' ? 'Fertig!' : 'Fehlgeschlagen'}
{currentAOI.area_km2 > 0 && (

Flaeche: {currentAOI.area_km2.toFixed(2)} km²

)}
)}
) : ( /* Unity 3D Viewer Tab */

3D-Lernwelt

Erkunde das Gebiet und bearbeite die Lernstationen

{learningNodes.length} Lernstationen
{currentAOI && currentAOI.status === 'completed' ? ( ) : (
Erstelle zuerst ein Lerngebiet im Tab "Gebiet waehlen"
)}
{/* Learning Nodes List */} {learningNodes.length > 0 && (

Lernstationen

{learningNodes.map((node, idx) => (
{idx + 1} {node.title}

{node.question}

{node.points} Punkte {node.approved && ( ✓ Freigegeben )}
))}
)}
)}
) }