import React, { useState, useEffect } from 'react'; import { MapMetadata, MapLayer, MapMarker } from '../types'; import { authService } from '../services/AuthService'; import { DiscordUser } from '../types'; const AdminMapManagement: React.FC = () => { const [user, setUser] = useState(null); const [mapMetadata, setMapMetadata] = useState(null); const [layers, setLayers] = useState([]); const [markers, setMarkers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // Map assembly state const [isAssembling, setIsAssembling] = useState(false); const [assemblyProgress, setAssemblyProgress] = useState(''); const [assemblyLog, setAssemblyLog] = useState([]); // Layer management state const [editingLayer, setEditingLayer] = useState(null); const [newLayer, setNewLayer] = useState({ name: '', is_active: true }); // Marker management state const [editingMarker, setEditingMarker] = useState(null); const [newMarker, setNewMarker] = useState({ name: '', type: 'poi' as const, x_coord: 0, z_coord: 0, description: '', linked_entity_type: null as string | null, linked_entity_id: null as number | null, icon_type: 'flag' as const, color: '#2563eb', is_public: true }); // Subscribe to auth changes useEffect(() => { const unsub = authService.subscribe((currentUser) => { setUser(currentUser); }); return unsub; }, []); // Load map data useEffect(() => { const loadData = async () => { if (!user?.isAdmin) return; try { setLoading(true); setError(null); // Load map metadata try { const metadataResponse = await fetch('/api/map/metadata'); if (metadataResponse.ok) { const metadata = await metadataResponse.json(); setMapMetadata(metadata); } } catch (metaErr) { console.log('Map metadata not available:', metaErr); } // Load layers const layersResponse = await fetch('/api/map/layers'); if (layersResponse.ok) { const layerData = await layersResponse.json(); setLayers(layerData); } // Load all markers (including private ones) const markersResponse = await fetch('/api/map/markers'); if (markersResponse.ok) { const markerData = await markersResponse.json(); setMarkers(markerData); } } catch (err) { console.error('Error loading map data:', err); setError(err instanceof Error ? err.message : 'Unbekannter Fehler'); } finally { setLoading(false); } }; loadData(); }, [user?.isAdmin]); // Map assembly function const handleAssembleMap = async () => { if (!user?.isAdmin || isAssembling) return; setIsAssembling(true); setAssemblyProgress('Starte Karten-Zusammenstellung...'); setAssemblyLog(['Starte Karten-Zusammenstellung...']); try { setAssemblyProgress('Überprüfe Kacheln...'); addToLog('Überprüfe Kacheln...'); // Check if tiles exist const tilesResponse = await fetch('/api/map/metadata'); if (!tilesResponse.ok) { setAssemblyProgress('Keine Kacheln gefunden. Bitte stellen Sie sicher, dass PNG-Dateien im Ordner backend/uploads/map/ vorhanden sind.'); addToLog('❌ Keine Kacheln gefunden'); setTimeout(() => { setIsAssembling(false); setAssemblyProgress(''); }, 3000); return; } setAssemblyProgress('Starte Karten-Zusammenstellung...'); addToLog('Starte Karten-Zusammenstellung...'); const response = await fetch('/api/map/assemble', { method: 'POST', headers: { 'Content-Type': 'application/json', } }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Server-Fehler: ${response.status} - ${errorText}`); } const result = await response.json(); if (result.success) { setAssemblyProgress('Karte erfolgreich zusammengestellt!'); addToLog('✅ Karte erfolgreich zusammengestellt!'); // Reload map metadata const metadataResponse = await fetch('/api/map/metadata'); if (metadataResponse.ok) { const metadata = await metadataResponse.json(); setMapMetadata(metadata); } setIsAssembling(false); setAssemblyProgress(''); alert('Weltkarte wurde erfolgreich zusammengestellt!'); } else { throw new Error(result.message || 'Fehler beim Zusammensetzen der Karte'); } } catch (err) { console.error('Error assembling map:', err); setAssemblyProgress(`Fehler: ${err.message}`); addToLog(`❌ Fehler: ${err.message}`); setTimeout(() => { setIsAssembling(false); setAssemblyProgress(''); }, 3000); } }; const addToLog = (message: string) => { setAssemblyLog(prev => [...prev, message]); }; // Layer management functions const handleCreateLayer = async () => { if (!user?.isAdmin) return; try { const response = await fetch('/api/map/layers', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(newLayer) }); if (response.ok) { const result = await response.json(); setLayers([...layers, result]); setNewLayer({ name: '', is_active: true }); } else { throw new Error('Fehler beim Erstellen des Layers'); } } catch (err) { console.error('Error creating layer:', err); setError(err instanceof Error ? err.message : 'Unbekannter Fehler'); } }; const handleUpdateLayer = async (layerId: string) => { if (!user?.isAdmin || !editingLayer) return; try { const response = await fetch(`/api/map/layers/${layerId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(editingLayer) }); if (response.ok) { const result = await response.json(); setLayers(layers.map(l => l.id === layerId ? result : l)); setEditingLayer(null); } else { throw new Error('Fehler beim Aktualisieren des Layers'); } } catch (err) { console.error('Error updating layer:', err); setError(err instanceof Error ? err.message : 'Unbekannter Fehler'); } }; const handleDeleteLayer = async (layerId: string) => { if (!user?.isAdmin) return; try { const response = await fetch(`/api/map/layers/${layerId}`, { method: 'DELETE' }); if (response.ok) { setLayers(layers.filter(l => l.id !== layerId)); } else { throw new Error('Fehler beim Löschen des Layers'); } } catch (err) { console.error('Error deleting layer:', err); setError(err instanceof Error ? err.message : 'Unbekannter Fehler'); } }; // Marker management functions const handleCreateMarker = async () => { if (!user?.isAdmin) return; try { const response = await fetch('/api/map/markers', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(newMarker) }); if (response.ok) { const result = await response.json(); setMarkers([...markers, result]); setNewMarker({ name: '', type: 'poi', x_coord: 0, z_coord: 0, description: '', linked_entity_type: null, linked_entity_id: null, icon_type: 'flag', color: '#2563eb', is_public: true }); } else { throw new Error('Fehler beim Erstellen des Markers'); } } catch (err) { console.error('Error creating marker:', err); setError(err instanceof Error ? err.message : 'Unbekannter Fehler'); } }; const handleUpdateMarker = async (markerId: string) => { if (!user?.isAdmin || !editingMarker) return; try { const response = await fetch(`/api/map/markers/${markerId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(editingMarker) }); if (response.ok) { const result = await response.json(); setMarkers(markers.map(m => m.id === markerId ? result : m)); setEditingMarker(null); } else { throw new Error('Fehler beim Aktualisieren des Markers'); } } catch (err) { console.error('Error updating marker:', err); setError(err instanceof Error ? err.message : 'Unbekannter Fehler'); } }; const handleDeleteMarker = async (markerId: string) => { if (!user?.isAdmin) return; try { const response = await fetch(`/api/map/markers/${markerId}`, { method: 'DELETE' }); if (response.ok) { setMarkers(markers.filter(m => m.id !== markerId)); } else { throw new Error('Fehler beim Löschen des Markers'); } } catch (err) { console.error('Error deleting marker:', err); setError(err instanceof Error ? err.message : 'Unbekannter Fehler'); } }; if (!user?.isAdmin) { return (
Zugriff verweigert: Admin-Berechtigungen erforderlich
); } if (loading) { return (
Lade Map-Management...
); } return (

Karten-Management

Admin-Tools für Weltkarte, Marker und Layer
{error && (
{error}
)} {/* Map Assembly Section */}

Karten-Zusammenstellung

{/* Assembly Controls */}

Karten-Status

{mapMetadata ? (
Status: Zusammengestellt
Größe: {mapMetadata.width} x {mapMetadata.height}px
Offset: X: {mapMetadata.offsetX}, Z: {mapMetadata.offsetZ}
Letztes Update: {new Date().toLocaleString()}
) : (
Status: Nicht zusammengestellt
)}
{mapMetadata ? 'Aktualisiert die bestehende Weltkarte mit neuen Kacheln aus backend/uploads/map/' : 'Erstellt die Weltkarte aus den PNG-Kacheln im Upload-Ordner' }
{/* Assembly Log */}

Zusammenstellungs-Log

{assemblyLog.length === 0 ? (
Keine Aktivitäten
) : ( assemblyLog.map((log, index) => (
{log}
)) )}

Anweisungen

1. Stellen Sie sicher, dass PNG-Kacheln im Ordner backend/uploads/map/ im Backend-Container vorhanden sind
2. Klicken Sie auf "Karte zusammensetzen"
3. Warten Sie, bis der Vorgang abgeschlossen ist
4. Die Karte ist dann unter /world-map verfügbar
Hinweis: Wenn "Keine Kacheln gefunden" angezeigt wird, befolgen Sie bitte die Anleitung in MAP_SETUP_GUIDE.md
{/* Layer Management */}

Layer-Verwaltung

{/* Create Layer */}

Neuen Layer erstellen

setNewLayer({...newLayer, name: e.target.value})} className="w-full px-3 py-2 bg-bgPrimary border border-border rounded" />
{/* Layer List */}

Bestehende Layer

{layers.length === 0 ? (
Keine Layer vorhanden
) : ( layers.map(layer => (
{layer.name}
{layer.is_active ? 'Aktiv' : 'Inaktiv'} • ID: {layer.id}
)) )}
{/* Edit Layer Modal */} {editingLayer && (

Layer bearbeiten

setEditingLayer({...editingLayer, name: e.target.value})} className="w-full px-3 py-2 bg-bgPrimary border border-border rounded" />
)}
{/* Marker Management */}

Marker-Verwaltung

{/* Create Marker */}

Neuen Marker erstellen

setNewMarker({...newMarker, name: e.target.value})} className="w-full px-3 py-2 bg-bgPrimary border border-border rounded" />
setNewMarker({...newMarker, x_coord: parseInt(e.target.value)})} className="px-3 py-2 bg-bgPrimary border border-border rounded" /> setNewMarker({...newMarker, z_coord: parseInt(e.target.value)})} className="px-3 py-2 bg-bgPrimary border border-border rounded" />
setNewMarker({...newMarker, description: e.target.value})} className="w-full px-3 py-2 bg-bgPrimary border border-border rounded" /> setNewMarker({...newMarker, color: e.target.value})} className="w-full px-3 py-2 bg-bgPrimary border border-border rounded" />
{/* Marker List */}

Bestehende Marker

{markers.length === 0 ? (
Keine Marker vorhanden
) : ( markers.map(marker => (
{marker.name}
{marker.type} • {marker.x_coord}, {marker.z_coord} • {marker.is_public ? 'Öffentlich' : 'Privat'}
{marker.description && (
{marker.description}
)}
)) )}
{/* Edit Marker Modal */} {editingMarker && (

Marker bearbeiten

setEditingMarker({...editingMarker, name: e.target.value})} className="w-full px-3 py-2 bg-bgPrimary border border-border rounded" />
setEditingMarker({...editingMarker, x_coord: parseInt(e.target.value)})} className="px-3 py-2 bg-bgPrimary border border-border rounded" /> setEditingMarker({...editingMarker, z_coord: parseInt(e.target.value)})} className="px-3 py-2 bg-bgPrimary border border-border rounded" />
setEditingMarker({...editingMarker, description: e.target.value})} className="w-full px-3 py-2 bg-bgPrimary border border-border rounded" /> setEditingMarker({...editingMarker, color: e.target.value})} className="w-full px-3 py-2 bg-bgPrimary border border-border rounded" />
)}
); }; export default AdminMapManagement;