'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 (
)
}
// 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
)}
))}
)}
)}
)
}