mirror of
https://github.com/ceratic/project_vollidioten_website.git
synced 2026-05-14 00:16:47 +02:00
- Created DatabaseManager component for managing database access via phpMyAdmin. - Developed LinkPlayer component to link Discord accounts with game characters, including user authentication and error handling. - Added mock data files for players, organizations, and projects to handle backend unavailability. - Implemented AuthService for managing user authentication and session checks. - Created DatabaseService to fetch and manage player, organization, and project data with fallback to mock data. - Added HTML page for handling authentication unavailability. - Developed a test script for validating Docker setup and required files.
148 lines
6.7 KiB
TypeScript
148 lines
6.7 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { dbService } from '../services/DatabaseService';
|
|
import { Icons } from '../components/IconSet';
|
|
import { Player, Project, Organization } from '../types';
|
|
|
|
const StatCard = ({ label, value, trend, icon: Icon }: any) => (
|
|
<div className="bg-surface/50 border border-border p-6 rounded-xl hover:border-accentInfo/30 transition-all duration-300 group">
|
|
<div className="flex justify-between items-start mb-6">
|
|
<div className="p-3 bg-surfaceHighlight rounded-lg text-textMuted group-hover:text-textMain transition-colors">
|
|
<Icon className="w-6 h-6" />
|
|
</div>
|
|
{trend && (
|
|
<span className={`text-xs font-medium px-2.5 py-1 rounded-full ${
|
|
trend > 0 ? 'bg-accentSuccess/10 text-accentSuccess' : 'bg-accentWarn/10 text-accentWarn'
|
|
}`}>
|
|
{trend > 0 ? '+' : ''}{trend}%
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div className="text-4xl font-bold text-textMain font-mono mb-2 tracking-tight">{value}</div>
|
|
<div className="text-sm text-textMuted font-medium">{label}</div>
|
|
</div>
|
|
);
|
|
|
|
const ProjectCard = ({ project }: { project: any }) => (
|
|
<div className="flex items-center gap-6 py-4 border-b border-white/5 last:border-0 group hover:bg-white/[0.02] px-2 rounded transition-colors -mx-2">
|
|
<div className={`w-2.5 h-2.5 rounded-full shadow-sm ${
|
|
project.status === 'active' ? 'bg-accentInfo' :
|
|
project.status === 'recruiting' ? 'bg-accentSuccess' : 'bg-textMuted'
|
|
}`} />
|
|
<div className="flex-1">
|
|
<div className="text-base font-medium text-textMain group-hover:text-accentInfo transition-colors">{project.title}</div>
|
|
<div className="text-xs text-textMuted mt-0.5 uppercase tracking-wide">Inhaber: {project.owner}</div>
|
|
</div>
|
|
<div className="w-32 hidden sm:block">
|
|
<div className="h-2 bg-surfaceHighlight rounded-full overflow-hidden">
|
|
<div className="h-full bg-accentInfo/80" style={{ width: `${project.progress}%` }} />
|
|
</div>
|
|
</div>
|
|
<div className="text-sm font-mono text-textMuted w-12 text-right">{project.progress}%</div>
|
|
</div>
|
|
);
|
|
|
|
const Dashboard: React.FC = () => {
|
|
const [players, setPlayers] = useState<Player[]>([]);
|
|
const [projects, setProjects] = useState<Project[]>([]);
|
|
const [orgs, setOrgs] = useState<Organization[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
// Subscribe to database updates
|
|
const unsubscribe = dbService.subscribe(() => {
|
|
setPlayers(dbService.getPlayers());
|
|
setProjects(dbService.getProjects());
|
|
setOrgs(dbService.getOrgs());
|
|
setLoading(false);
|
|
});
|
|
|
|
// Initial data load
|
|
setPlayers(dbService.getPlayers());
|
|
setProjects(dbService.getProjects());
|
|
setOrgs(dbService.getOrgs());
|
|
setLoading(false);
|
|
|
|
return unsubscribe;
|
|
}, []);
|
|
|
|
const activeProjectsCount = projects.filter(p => p.status === 'active' || p.status === 'recruiting').length;
|
|
|
|
return (
|
|
<div className="space-y-12">
|
|
{/* Intro Section */}
|
|
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4 pb-4 border-b border-border">
|
|
<div>
|
|
<h2 className="text-sm font-bold text-accentInfo tracking-widest uppercase mb-2">Live-Telemetrie</h2>
|
|
<h1 className="text-4xl md:text-5xl font-bold tracking-tight text-white">Tal-Übersicht</h1>
|
|
</div>
|
|
<p className="text-textMuted text-right hidden md:block max-w-xs leading-relaxed">
|
|
Echtzeit-Datenaggregation aus dem Zentralarchiv.
|
|
</p>
|
|
</div>
|
|
|
|
{/* KPI Grid */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
|
<StatCard label="Registrierte Bürger" value={loading ? '...' : players.length} trend={12} icon={Icons.Users} />
|
|
<StatCard label="Aktive Unternehmen" value={loading ? '...' : activeProjectsCount} trend={5} icon={Icons.Layers} />
|
|
<StatCard label="Organisationen" value={loading ? '...' : orgs.length} trend={0} icon={Icons.Map} />
|
|
</div>
|
|
|
|
{/* Content Grid */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12">
|
|
{/* Projects List - Wider */}
|
|
<div className="lg:col-span-7">
|
|
<div className="flex justify-between items-end mb-6">
|
|
<h3 className="text-xl font-semibold">Top Unternehmen</h3>
|
|
<button className="text-sm text-textMuted hover:text-accentInfo transition-colors">Verzeichnis ansehen →</button>
|
|
</div>
|
|
<div className="bg-surface/30 border border-border rounded-2xl p-6 backdrop-blur-sm">
|
|
{loading ? (
|
|
<div className="flex justify-center py-8">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-accentInfo"></div>
|
|
</div>
|
|
) : (
|
|
projects.slice(0, 5).map(p => (
|
|
<div key={p.id}>
|
|
<ProjectCard project={p} />
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Decree / News - Narrower */}
|
|
<div className="lg:col-span-5 flex flex-col">
|
|
<h3 className="text-xl font-semibold mb-6">Offizieller Erlass</h3>
|
|
<div className="flex-1 bg-gradient-to-b from-surface/50 to-surface/20 border border-border rounded-2xl p-8 relative overflow-hidden group">
|
|
{/* Decorative background element */}
|
|
<div className="absolute -top-10 -right-10 w-40 h-40 bg-accentInfo/10 rounded-full blur-3xl group-hover:bg-accentInfo/20 transition-all duration-700" />
|
|
|
|
<div className="relative z-10">
|
|
<div className="mb-6">
|
|
<span className="text-xs font-mono px-2 py-1 bg-accentInfo/20 text-accentInfo rounded border border-accentInfo/20">NEU</span>
|
|
<span className="text-xs font-mono px-2 py-1 ml-2 text-textMuted">VOR 12 MIN</span>
|
|
</div>
|
|
|
|
<div className="prose prose-invert prose-lg text-textMuted leading-relaxed">
|
|
<p className="text-textMain italic text-lg font-serif mb-6">
|
|
"Im Auftrag des Bürgermeisters ist jeglicher Handel innerhalb der inneren Mauern für das kommende Fest steuerbefreit."
|
|
</p>
|
|
<p className="text-sm">
|
|
Bürger werden ermutigt, ihre Stände im Marktviertel aufzubauen, bevor der Mond am 14. Tag aufgeht.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="absolute bottom-6 left-8 flex gap-2">
|
|
<span className="text-xs text-textMuted hover:text-white cursor-pointer">#Wirtschaft</span>
|
|
<span className="text-xs text-textMuted hover:text-white cursor-pointer">#Events</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Dashboard;
|