feat: Initialize project with Vite, React, and TypeScript

Sets up the foundational structure for the Obsidian | RP Plattform. This includes configuring Vite as the build tool, integrating React for the UI, and establishing TypeScript for type safety. Also includes initial styling and placeholder data to define the application's core interfaces.
This commit is contained in:
Lars Behrends
2025-12-28 02:15:09 +01:00
parent 4ab4a1d64a
commit d1b797a320
23 changed files with 2514 additions and 8 deletions

168
App.tsx Normal file
View File

@@ -0,0 +1,168 @@
import React, { useState } from 'react';
import Layout from './components/Layout';
import Dashboard from './pages/Dashboard';
import PlayerProfile from './pages/PlayerProfile';
import SetupGuide from './pages/SetupGuide';
import Projects from './pages/Projects';
import Organizations from './pages/Organizations';
import Cities from './pages/Cities';
import CityProfile from './pages/CityProfile';
import ProjectProfile from './pages/ProjectProfile';
import DatapackGenerator from './pages/DatapackGenerator';
import { MOCK_PLAYERS, MOCK_ORGS, MOCK_PROJECTS } from './constants';
import { Icons } from './components/IconSet';
function App() {
const [activeTab, setActiveTab] = useState('dashboard');
const [selectedPlayerId, setSelectedPlayerId] = useState<string | null>(null);
const [selectedCityId, setSelectedCityId] = useState<string | null>(null);
const [selectedProjectId, setSelectedProjectId] = useState<string | null>(null);
const [selectedOrgId, setSelectedOrgId] = useState<string | null>(null);
const handleNavigate = (tab: string) => {
setActiveTab(tab);
if (tab !== 'players') setSelectedPlayerId(null);
if (tab !== 'cities') setSelectedCityId(null);
if (tab !== 'projects') setSelectedProjectId(null);
if (tab !== 'organizations') setSelectedOrgId(null);
};
// Helper to jump to a player from another view
const navigateToPlayer = (id: string) => {
setSelectedPlayerId(id);
setActiveTab('players');
};
// Helper to jump to a project from another view
const navigateToProject = (id: string) => {
setSelectedProjectId(id);
setActiveTab('projects');
};
// Helper to jump to an org/city
const navigateToOrg = (id: string) => {
const org = MOCK_ORGS.find(o => o.id === id);
if (org?.type === 'City') {
setSelectedCityId(id);
setActiveTab('cities');
} else {
setSelectedOrgId(id);
setActiveTab('organizations');
}
};
const renderContent = () => {
if (activeTab === 'dashboard') return <Dashboard />;
if (activeTab === 'projects') {
if (selectedProjectId) {
const project = MOCK_PROJECTS.find(p => p.id === selectedProjectId);
if (project) return (
<ProjectProfile
project={project}
onBack={() => setSelectedProjectId(null)}
onSelectPlayer={navigateToPlayer}
onSelectOrg={navigateToOrg}
/>
);
}
return <Projects onSelectProject={setSelectedProjectId} />;
}
if (activeTab === 'organizations') {
if (selectedOrgId) {
const org = MOCK_ORGS.find(o => o.id === selectedOrgId);
if (org) return (
<CityProfile
city={org}
onBack={() => setSelectedOrgId(null)}
backLabel="Zurück zum Verzeichnis"
onSelectPlayer={navigateToPlayer}
onSelectProject={navigateToProject}
/>
);
}
return <Organizations onSelectOrg={setSelectedOrgId} />;
}
if (activeTab === 'setup') return <SetupGuide />;
if (activeTab === 'datapack') return <DatapackGenerator />;
if (activeTab === 'cities') {
if (selectedCityId) {
const city = MOCK_ORGS.find(o => o.id === selectedCityId);
if (city) return (
<CityProfile
city={city}
onBack={() => setSelectedCityId(null)}
backLabel="Zurück zu Städte"
onSelectPlayer={navigateToPlayer}
onSelectProject={navigateToProject}
/>
);
}
return <Cities onSelectCity={setSelectedCityId} />;
}
if (activeTab === 'players') {
if (selectedPlayerId) {
const player = MOCK_PLAYERS.find(p => p.uuid === selectedPlayerId);
if (player) return <PlayerProfile player={player} onBack={() => setSelectedPlayerId(null)} />;
}
return (
<div className="animate-in fade-in">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold">Bürgerverzeichnis</h2>
<div className="relative">
<Icons.Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-textMuted" />
<input
type="text"
placeholder="Filtern nach Tag oder Name..."
className="bg-surfaceHighlight border border-border rounded-lg pl-10 pr-4 py-2 text-sm text-textMain focus:border-accentInfo focus:outline-none w-64 transition-colors"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
{MOCK_PLAYERS.map(player => (
<div
key={player.uuid}
onClick={() => setSelectedPlayerId(player.uuid)}
className="group bg-surface border border-border p-4 rounded-xl cursor-pointer hover:border-accentInfo/50 transition-all duration-200 hover:shadow-card"
>
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-md flex items-center justify-center font-bold text-lg text-textMuted group-hover:text-textMain transition-colors">
<img src={"https://minotar.net/armor/bust/"+player.username+"/500.png"}></img>
</div>
<div>
<div className="font-semibold text-textMain group-hover:text-accentInfo transition-colors">{player.username}</div>
<div className="text-xs text-textMuted mt-1 flex gap-2">
{player.tags.slice(0, 2).map(t => <span key={t}>{t}</span>)}
</div>
</div>
</div>
</div>
))}
</div>
</div>
);
}
return (
<div className="flex flex-col items-center justify-center h-[50vh] text-textMuted">
<Icons.Box className="w-12 h-12 mb-4 opacity-20" />
<p>Modul <strong>{activeTab}</strong> wird derzeit gewartet.</p>
</div>
);
};
return (
<Layout activeTab={activeTab} onNavigate={handleNavigate}>
{renderContent()}
</Layout>
);
}
export default App;