import React, { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { Player } from '../types'; import { dbService } from '../services/DatabaseService'; import { authService } from '../services/AuthService'; import InventoryGrid from '../components/InventoryGrid'; import EditModal from '../components/EditModal'; import { Icons } from '../components/IconSet'; // Helper function to get advancement icon path const getAdvancementIconPath = (advancementId: string): string => { // Remove 'minecraft:' prefix if present const cleanId = advancementId.replace('minecraft:', ''); // Map advancement ID to icon path return `/assets/advancement/${cleanId}.png`; }; // Helper function to get advancement category const getAdvancementCategory = (advancementId: string): string => { const cleanId = advancementId.replace('minecraft:', ''); if (cleanId.startsWith('adventure/')) return 'Abenteuer'; if (cleanId.startsWith('nether/')) return 'Nether'; if (cleanId.startsWith('end/')) return 'End'; if (cleanId.startsWith('husbandry/')) return 'Tierhaltung'; if (cleanId.startsWith('story/')) return 'Geschichte'; return 'Sonstige'; }; // Helper function to organize advancements by category const organizeAdvancementsByCategory = (advancements: any[]) => { const categories: { [key: string]: any[] } = {}; advancements.forEach(advancement => { const category = getAdvancementCategory(advancement.id); if (!categories[category]) { categories[category] = []; } categories[category].push(advancement); }); return categories; }; const PlayerProfile: React.FC = () => { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const [player, setPlayer] = useState(null); const [currentUser, setCurrentUser] = useState(authService.getUser()); // Edit State const [isEditStoryOpen, setIsEditStoryOpen] = useState(false); const [isEditTagsOpen, setIsEditTagsOpen] = useState(false); const [isEditOrgOpen, setIsEditOrgOpen] = useState(false); const [ownedProjects, setOwnedProjects] = useState([]); const [loading, setLoading] = useState(true); const [activeTab, setActiveTab] = useState<'story' | 'stats' | 'projects'>('story'); const handleNavigateToOrg = (orgId: string) => { const org = dbService.getOrg(orgId); if (org?.type === 'City') { navigate(`/cities/${orgId}`); } else { navigate(`/organizations/${orgId}`); } }; const handleNavigateToProject = (projectId: string) => { navigate(`/projects/${projectId}`); }; // Is this the logged-in user's profile? const isOwner = currentUser?.linkedPlayerUuid === player?.uuid; const playerOrg = player ? dbService.getOrg(player.organizationId || '') : null; // Check if player is already linked to anyone in our mock/real DB logic // Since the Player object doesn't expose 'discordId' publicly in types yet (it's hidden in DB), // we use a heuristic or add it. For now, we assume if currentUser has NO link, they can claim. const canClaim = !!currentUser && !currentUser.linkedPlayerUuid && !isOwner; useEffect(() => { if (!id) { navigate('/players'); return; } // Load player data - try to fetch from API first, fallback to cache const loadPlayerData = async () => { try { const playerData = await dbService.fetchPlayer(id); if (playerData) { setPlayer(playerData); } else { // Fallback to cache if API fails const cachedPlayer = dbService.getPlayer(id); if (cachedPlayer) { setPlayer(cachedPlayer); } else { navigate('/players'); return; } } } catch (error) { console.error('Error loading player data:', error); // Fallback to cache const cachedPlayer = dbService.getPlayer(id); if (cachedPlayer) { setPlayer(cachedPlayer); } else { navigate('/players'); return; } } setLoading(false); }; loadPlayerData(); }, [id, navigate]); useEffect(() => { if (!player) return; console.log(player); // Load owned projects const allProjects = dbService.getProjects(); const playerProjects = allProjects.filter(p => p.owner === player.username); setOwnedProjects(playerProjects); // Subscribe to auth to show/hide edit buttons const unsub = authService.subscribe(u => setCurrentUser(u)); return unsub; }, [player]); if (loading || !player) { return (
); } const handleSaveStory = (newStory: string) => { // Update local DB dbService.updatePlayer(player.uuid, { storyMarkdown: newStory }); // Update local state setPlayer(prev => ({ ...prev, storyMarkdown: newStory })); }; const handleSaveTags = async (tagsString: string) => { const tags = tagsString.split(',').map(tag => tag.trim()).filter(tag => tag.length > 0); // Update via API const response = await fetch(`https://vollidioten.ceraticsoft.de/api/players/${player.uuid}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ tags }) }); if (response.ok) { // Update local state setPlayer(prev => ({ ...prev, tags })); } else { alert('Fehler beim Aktualisieren der Tags'); } }; const handleSaveOrganization = async (orgId: string) => { const response = await fetch(`https://vollidioten.ceraticsoft.de/api/players/${player.uuid}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ organizationId: orgId || null }) }); if (response.ok) { // Update local state setPlayer(prev => ({ ...prev, organizationId: orgId || undefined })); } else { alert('Fehler beim Aktualisieren der Zugehörigkeit'); } }; const handleClaim = async () => { if (confirm(`Möchtest du den Charakter "${player.username}" mit deinem Discord-Account verknüpfen?`)) { const success = await dbService.linkPlayer(player.uuid); if (success) { // Force refresh auth to get updated linkedPlayerUuid await authService.checkSession(); alert("Erfolgreich verknüpft! Du kannst nun Bearbeitungen vornehmen."); } else { alert("Fehler beim Verknüpfen. Bitte Backend Logs prüfen."); } } }; // Simple markdown renderer replacement for demo purposes const renderMarkdown = (text: string) => { return text.split('\n').map((line, i) => { if (line.startsWith('# ')) return

{line.replace('# ', '')}

; if (line.startsWith('### ')) return

{line.replace('### ', '')}

; if (line.startsWith('> ')) return
{line.replace('> ', '')}
; if (line.startsWith('* ')) return
  • {line.replace('* ', '')}
  • ; return

    {line}

    ; }); }; console.log(player); return (
    {canClaim && ( )}
    {/* Header */}
    {isOwner && (
    Dein Profil
    )}
    {isOwner && (
    Ändern
    )}

    {player.username}

    {player.isOnline && ( Online )}
    {(player.tags || []).map(tag => ( {tag} ))} {isOwner && ( )}
    {player.minecraftStats?.statistics?.general?.["minecraft:play_time"] ? `${Math.round((player.minecraftStats.statistics.general["minecraft:play_time"] || 0) / 20 / 3600)}h` : '0h' }
    Lvl {player.minecraftStats?.char?.xpLevel || player.minecraftStats?.level || 1}
    {player.minecraftStats && player.minecraftStats.char && player.minecraftStats.statistics && ( <>
    {player.minecraftStats.char.health}/{player.minecraftStats.char.maxHealth} HP
    {player.minecraftStats.char.foodLevel}/20 Food
    {player.minecraftStats.statistics.general?.["minecraft:mob_kills"] || 0} Kills
    )}
    {/* Left Col: Inventory & Org */}

    Zugehörigkeit

    {playerOrg ? (
    handleNavigateToOrg(playerOrg.id)} className="flex items-center gap-3 cursor-pointer hover:bg-surfaceHighlight/50 -m-1 p-1 rounded transition-colors group" >
    {playerOrg.name.charAt(0)}
    {playerOrg.name}
    {player.stats?.role || 'Unbekannt'} • Klick zum Anzeigen
    ) : (
    Freiberufler
    Keine Zugehörigkeit
    )}
    {/* Right Col: Tabs */}
    {/* Tab Navigation */}
    {player.minecraftStats && ( )} {ownedProjects.length > 0 && ( )}
    {/* Tab Content */}
    {activeTab === 'story' && (
    Markdown {isOwner && ( )}
    {renderMarkdown(player.storyMarkdown || '')}
    )} {activeTab === 'stats' && player.minecraftStats && (
    {/* Last Sync */}
    Letzte Synchronisation
    {new Date(player.minecraftStats.lastSync).toLocaleString('de-DE', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' })}
    {/* Statistics */} {player.minecraftStats.statistics && (
    {/* General Stats */} {player.minecraftStats.statistics.general && Object.keys(player.minecraftStats.statistics.general).length > 0 && (

    Allgemein

    {Object.entries(player.minecraftStats.statistics.general) .sort(([, a], [, b]) => (b as number) - (a as number)) .map(([key, value]) => (
    {key.replace('minecraft:', '')} {value.toLocaleString()}
    ))}
    )} {/* Kills */} {player.minecraftStats.statistics.kills && Object.keys(player.minecraftStats.statistics.kills).length > 0 && (

    Kills

    {Object.entries(player.minecraftStats.statistics.kills) .sort(([, a], [, b]) => (b as number) - (a as number)) .map(([key, value]) => (
    {key.replace('minecraft:', '')} {value}
    ))}
    )} {/* Killed By */} {player.minecraftStats.statistics.killed_by && Object.keys(player.minecraftStats.statistics.killed_by).length > 0 && (

    Gestorben durch

    {Object.entries(player.minecraftStats.statistics.killed_by) .sort(([, a], [, b]) => (b as number) - (a as number)) .map(([key, value]) => (
    {key.replace('minecraft:', '')} {value}
    ))}
    )}
    )} {/* Advancements */} {player.minecraftStats.advancements && player.minecraftStats.advancements.length > 0 && (

    Erfolge ({player.minecraftStats.advancements.length})

    {Object.entries(organizeAdvancementsByCategory(player.minecraftStats.advancements)) .sort(([a], [b]) => a.localeCompare(b)) .map(([category, advancements]) => (
    {category} ({advancements.length})
    {advancements.map((advancement) => (
    {advancement.title} { // Fallback to a default icon if the specific advancement icon doesn't exist e.currentTarget.src = '/assets/advancement/advancement_categories.png'; }} />
    {advancement.id.replace('minecraft:', '').split('/')[1] || advancement.id.replace('minecraft:', '')}
    ))}
    ))}
    )}
    )} {activeTab === 'projects' && ownedProjects.length > 0 && (
    {ownedProjects.map((project) => (
    handleNavigateToProject(project.id)} className="bg-surfaceHighlight/30 border border-border rounded-lg p-4 hover:border-accentInfo/50 transition-all cursor-pointer group" >

    {project.title}

    {project.category}

    {project.description.split(' ').length > 50 ? project.description.split(' ').slice(0, 50).join(' ') + '...' : project.description}

    Gegründet: {project.foundedDate}
    {project.shopCatalog && project.shopCatalog.length > 0 && ( Shop ({project.shopCatalog.length}) )} {project.employees.length + 1}
    Klick zum Anzeigen →
    ))}
    )}
    {/* Modals */} setIsEditStoryOpen(false)} onSave={handleSaveStory} /> setIsEditTagsOpen(false)} onSave={handleSaveTags} />
    ); }; export default PlayerProfile;