feat: add world map functionality and admin map management

- Added world map page with interactive marker display
- Implemented admin map management for marker CRUD operations
- Added map layers and markers seed data to database
- Integrated new routes for map functionality
- Updated database configuration for production environment
- Added documentation page route
- Enhanced package.json with required dependencies for map features
This commit is contained in:
Lars Behrends
2026-01-02 05:08:07 +01:00
parent ea2b803534
commit 065a6e657d
152 changed files with 5024 additions and 35 deletions

View File

@@ -7,6 +7,43 @@ 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();
@@ -241,7 +278,7 @@ const PlayerProfile: React.FC = () => {
<span className="font-mono text-textMain">
{player.minecraftStats?.statistics?.general?.["minecraft:play_time"]
? `${Math.round((player.minecraftStats.statistics.general["minecraft:play_time"] || 0) / 20 / 3600)}h`
: `${player.minecraftStats?.playtimeHours || 0}h`
: '0h'
}
</span>
</div>
@@ -280,8 +317,7 @@ const PlayerProfile: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Left Col: Inventory & Org */}
<div className="lg:col-span-1 space-y-6">
<InventoryGrid items={player.inventory || []} />
<div className="lg:col-span-1 space-y-6">
<div className="bg-surface border border-border rounded-xl p-4 shadow-card">
<h3 className="text-xs font-bold uppercase tracking-wider text-textMuted mb-3">Zugehörigkeit</h3>
@@ -302,7 +338,7 @@ const PlayerProfile: React.FC = () => {
{playerOrg.name}
</div>
<div className="text-xs text-textMuted">
{player.minecraftStats?.role || 'Unbekannt'} Klick zum Anzeigen
{player.stats?.role || 'Unbekannt'} Klick zum Anzeigen
</div>
</div>
</div>
@@ -464,13 +500,43 @@ const PlayerProfile: React.FC = () => {
{player.minecraftStats.advancements && player.minecraftStats.advancements.length > 0 && (
<div>
<h4 className="text-md font-semibold mb-3 text-textMain">Erfolge ({player.minecraftStats.advancements.length})</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
{player.minecraftStats.advancements.map((advancement) => (
<div key={advancement.id} className="p-3 bg-surfaceHighlight/20 rounded border border-white/5">
<div className="text-sm font-medium text-textMain">{advancement.title}</div>
<div className="text-xs text-textMuted font-mono">{advancement.id.replace('minecraft:', '')}</div>
</div>
))}
<div className="space-y-6">
{Object.entries(organizeAdvancementsByCategory(player.minecraftStats.advancements))
.sort(([a], [b]) => a.localeCompare(b))
.map(([category, advancements]) => (
<div key={category} className="bg-surfaceHighlight/20 rounded-lg border border-white/5 p-4">
<div className="flex items-center gap-3 mb-3">
<span className="text-sm font-semibold text-accentInfo uppercase tracking-wide">{category}</span>
<span className="text-xs text-textMuted">({advancements.length})</span>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-4 gap-1">
{advancements.map((advancement) => (
<div
key={advancement.id}
className="group cursor-pointer hover:bg-white/5 p-2 rounded transition-all"
title={advancement.title}
>
<div className="flex flex-col items-center gap-2">
<div className="bg-white/5 rounded-lg flex items-center justify-center border border-white/10 group-hover:border-white/20 transition-colors">
<img
src={getAdvancementIconPath(advancement.id)}
alt={advancement.title}
className="w-28 h-28 object-contain"
onError={(e) => {
// Fallback to a default icon if the specific advancement icon doesn't exist
e.currentTarget.src = '/assets/advancement/advancement_categories.png';
}}
/>
</div>
<div className="text-xs text-center text-textMuted font-mono truncate w-full">
{advancement.id.replace('minecraft:', '').split('/')[1] || advancement.id.replace('minecraft:', '')}
</div>
</div>
</div>
))}
</div>
</div>
))}
</div>
</div>
)}