mirror of
https://github.com/ceratic/project_vollidioten_website.git
synced 2026-05-14 00:16:47 +02:00
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:
350
pages/ProjectProfile.tsx
Normal file
350
pages/ProjectProfile.tsx
Normal file
@@ -0,0 +1,350 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Project, ShopItem } from '../types';
|
||||
import { MOCK_ORGS, MOCK_PLAYERS } from '../constants';
|
||||
import { Icons, ItemIcon } from '../components/IconSet';
|
||||
|
||||
interface ProjectProfileProps {
|
||||
project: Project;
|
||||
onBack: () => void;
|
||||
onSelectPlayer: (id: string) => void;
|
||||
onSelectOrg: (id: string) => void;
|
||||
}
|
||||
|
||||
const ProjectProfile: React.FC<ProjectProfileProps> = ({
|
||||
project,
|
||||
onBack,
|
||||
onSelectPlayer,
|
||||
onSelectOrg
|
||||
}) => {
|
||||
const [activeTab, setActiveTab] = useState<'overview' | 'shop'>('overview');
|
||||
|
||||
const org = project.associatedOrgId ? MOCK_ORGS.find(o => o.id === project.associatedOrgId) : null;
|
||||
const ownerPlayer = MOCK_PLAYERS.find(p => p.username === project.owner);
|
||||
const hasShop = project.shopCatalog && project.shopCatalog.length > 0;
|
||||
|
||||
// Group shop items
|
||||
const services = project.shopCatalog?.filter(i => i.type === 'service') || [];
|
||||
const products = project.shopCatalog?.filter(i => i.type !== 'service') || [];
|
||||
|
||||
return (
|
||||
<div className="animate-in slide-in-from-right-4 duration-300">
|
||||
<button onClick={onBack} className="flex items-center gap-2 text-sm text-textMuted hover:text-textMain mb-6 transition-colors group">
|
||||
<span className="group-hover:-translate-x-1 transition-transform">←</span> Zurück zum Verzeichnis
|
||||
</button>
|
||||
|
||||
{/* Banner Header */}
|
||||
<div className="relative h-64 md:h-80 rounded-2xl overflow-hidden border border-border mb-8 shadow-card group">
|
||||
<img
|
||||
src={project.bannerUrl || 'https://images.unsplash.com/photo-1542601906990-b4d3fb778b09?q=80&w=2070&auto=format&fit=crop'}
|
||||
alt={project.title}
|
||||
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"
|
||||
/>
|
||||
|
||||
{/* Decorative elements for Black Market */}
|
||||
{project.category === 'Black Market' && (
|
||||
<div className="absolute inset-0 bg-red-900/20 mix-blend-overlay" />
|
||||
)}
|
||||
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-background via-background/80 to-transparent" />
|
||||
|
||||
<div className="absolute bottom-0 left-0 right-0 p-8">
|
||||
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className={`text-xs font-mono px-2 py-1 rounded border border-white/5 uppercase tracking-wider bg-black/40 backdrop-blur ${
|
||||
project.category === 'Story Arc' ? 'text-orange-400' :
|
||||
project.category === 'Black Market' ? 'text-red-400' :
|
||||
'text-accentInfo'
|
||||
}`}>
|
||||
{project.category}
|
||||
</span>
|
||||
{project.status === 'active' && (
|
||||
<span className="flex items-center gap-1.5 text-xs font-bold text-accentSuccess bg-black/40 px-2 py-1 rounded backdrop-blur border border-white/5">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-accentSuccess animate-pulse" />
|
||||
AKTIV
|
||||
</span>
|
||||
)}
|
||||
{project.hiring && (
|
||||
<span className="flex items-center gap-1.5 text-xs font-bold text-purple-400 bg-black/40 px-2 py-1 rounded backdrop-blur border border-white/5">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-purple-400 animate-pulse" />
|
||||
STELLEN
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-white tracking-tight mb-2 drop-shadow-lg">{project.title}</h1>
|
||||
<div className="flex items-center gap-6 text-sm text-gray-300">
|
||||
<span
|
||||
className={`flex items-center gap-2 ${ownerPlayer ? 'cursor-pointer hover:text-white transition-colors group/owner' : ''}`}
|
||||
onClick={() => ownerPlayer && onSelectPlayer(ownerPlayer.uuid)}
|
||||
>
|
||||
<div className="w-5 h-5 rounded-full bg-gradient-to-br from-gray-700 to-gray-900 flex items-center justify-center text-[10px] border border-white/10 font-bold group-hover/owner:border-accentInfo group-hover/owner:text-accentInfo transition-colors">
|
||||
{project.owner.charAt(0)}
|
||||
</div>
|
||||
Inhaber: <span className="text-white font-medium group-hover/owner:underline decoration-accentInfo/50 underline-offset-4">{project.owner}</span>
|
||||
</span>
|
||||
{project.foundedDate && (
|
||||
<span className="flex items-center gap-1.5 text-textMuted">
|
||||
<Icons.Layers className="w-3.5 h-3.5" /> Gegr. {project.foundedDate}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{hasShop && (
|
||||
<button
|
||||
onClick={() => setActiveTab('shop')}
|
||||
className="flex items-center gap-2 bg-accentInfo hover:bg-accentInfo/90 text-white px-6 py-3 rounded-lg font-medium transition-colors shadow-lg shadow-accentInfo/20 border border-white/10 backdrop-blur-sm group/btn"
|
||||
>
|
||||
<Icons.ShoppingBag className="w-4 h-4 group-hover/btn:scale-110 transition-transform" />
|
||||
Katalog öffnen
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="flex gap-8 border-b border-border mb-8 px-2">
|
||||
<button
|
||||
onClick={() => setActiveTab('overview')}
|
||||
className={`pb-4 text-sm font-medium transition-colors border-b-2 ${activeTab === 'overview' ? 'border-accentInfo text-white' : 'border-transparent text-textMuted hover:text-white'}`}
|
||||
>
|
||||
Übersicht
|
||||
</button>
|
||||
{hasShop && (
|
||||
<button
|
||||
onClick={() => setActiveTab('shop')}
|
||||
className={`pb-4 text-sm font-medium transition-colors border-b-2 ${activeTab === 'shop' ? 'border-accentInfo text-white' : 'border-transparent text-textMuted hover:text-white'}`}
|
||||
>
|
||||
Katalog ({project.shopCatalog?.length})
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="min-h-[400px]">
|
||||
{activeTab === 'overview' && (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div className="lg:col-span-2 space-y-8">
|
||||
<section className="bg-surface/50 border border-border rounded-xl p-6">
|
||||
<h3 className="text-lg font-bold text-white mb-4">Manifest</h3>
|
||||
<p className="text-textMuted leading-relaxed whitespace-pre-line text-lg">
|
||||
{project.description}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Project Portfolio / Gallery */}
|
||||
{project.gallery && project.gallery.length > 0 && (
|
||||
<section>
|
||||
<h3 className="text-lg font-bold text-white mb-4 flex items-center gap-2">
|
||||
<Icons.Layers className="w-4 h-4 text-accentInfo" /> Portfolio
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{project.gallery.map((url, idx) => (
|
||||
<div key={idx} className="rounded-xl overflow-hidden aspect-video border border-border group relative">
|
||||
<img src={url} alt={`Portfolio ${idx}`} className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110" />
|
||||
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<section>
|
||||
<h3 className="text-lg font-bold text-white mb-4 flex items-center gap-2">
|
||||
<Icons.Users className="w-4 h-4 text-accentInfo" /> Personal
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{project.employees.map((emp, idx) => {
|
||||
const empPlayer = MOCK_PLAYERS.find(p => p.username === emp);
|
||||
return (
|
||||
<div
|
||||
key={idx}
|
||||
onClick={() => empPlayer && onSelectPlayer(empPlayer.uuid)}
|
||||
className={`flex items-center gap-3 bg-surface border border-border p-3 rounded-lg ${empPlayer ? 'cursor-pointer hover:border-accentInfo/50 hover:bg-surfaceHighlight/30 transition-all group' : ''}`}
|
||||
>
|
||||
<div className="w-8 h-8 bg-surfaceHighlight rounded flex items-center justify-center text-xs font-bold text-textMuted group-hover:text-textMain transition-colors">
|
||||
{emp.charAt(0)}
|
||||
</div>
|
||||
<span className="text-sm font-medium text-gray-300 group-hover:text-accentInfo transition-colors">{emp}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{project.employees.length === 0 && (
|
||||
<div className="text-textMuted italic text-sm">Kein weiteres Personal registriert.</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{/* Right Column: Statistics */}
|
||||
<div className="space-y-6">
|
||||
<div className="bg-surface border border-border rounded-xl p-6 h-fit">
|
||||
<h3 className="text-xs font-bold uppercase text-textMuted mb-6 flex items-center gap-2">
|
||||
<Icons.Terminal className="w-4 h-4" /> Unternehmensdaten
|
||||
</h3>
|
||||
<div className="space-y-5">
|
||||
|
||||
{/* Industry */}
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-textMuted">Branche</span>
|
||||
<span className={`text-sm font-medium ${
|
||||
project.category === 'Black Market' ? 'text-red-400' : 'text-white'
|
||||
}`}>{project.category}</span>
|
||||
</div>
|
||||
<div className="w-full bg-white/5 h-px" />
|
||||
|
||||
{/* Founded */}
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-textMuted">Gegründet</span>
|
||||
<span className="text-sm font-medium text-white">{project.foundedDate || 'N/A'}</span>
|
||||
</div>
|
||||
<div className="w-full bg-white/5 h-px" />
|
||||
|
||||
{/* Reputation */}
|
||||
<div>
|
||||
<div className="flex justify-between text-xs mb-2">
|
||||
<span className="text-textMuted">Ruf</span>
|
||||
<span className="font-mono text-white">{project.progress}/100</span>
|
||||
</div>
|
||||
<div className="h-1.5 bg-surfaceHighlight rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full ${project.category === 'Black Market' ? 'bg-red-500' : 'bg-accentInfo'}`}
|
||||
style={{ width: `${project.progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full bg-white/5 h-px" />
|
||||
|
||||
{/* Workforce */}
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-textMuted">Belegschaft</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Icons.Users className="w-3 h-3 text-textMuted" />
|
||||
<span className="text-sm font-medium text-white">{project.employees.length + 1}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Headquarters Link */}
|
||||
{org && (
|
||||
<div className="mt-4 pt-4 border-t border-white/5">
|
||||
<span className="text-xs text-textMuted block mb-2">Hauptsitz</span>
|
||||
<div
|
||||
onClick={() => onSelectOrg(org.id)}
|
||||
className="flex items-center gap-3 bg-surfaceHighlight/50 p-2 rounded-lg border border-white/5 cursor-pointer hover:border-accentInfo/50 hover:bg-surfaceHighlight transition-all group"
|
||||
>
|
||||
<div className="w-8 h-8 rounded bg-blue-500/20 text-blue-400 flex items-center justify-center font-bold text-xs border border-blue-500/20">
|
||||
{org.name.charAt(0)}
|
||||
</div>
|
||||
<span className="text-sm font-medium text-gray-200 group-hover:text-white transition-colors">{org.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'shop' && project.shopCatalog && (
|
||||
<div className="space-y-12">
|
||||
|
||||
{/* Services Section */}
|
||||
{services.length > 0 && (
|
||||
<section>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="p-2 bg-amber-500/10 rounded-lg text-amber-400">
|
||||
<Icons.Hammer className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-white">Dienstleistungen & Verträge</h2>
|
||||
<p className="text-sm text-textMuted">Fachkräfte für Spezialaufträge anheuern.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{services.map(item => (
|
||||
<div key={item.id} className="bg-surface border border-border rounded-xl p-6 flex flex-col hover:border-accentInfo/30 transition-all">
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<h3 className="text-lg font-bold text-white">{item.name}</h3>
|
||||
<span className="font-mono text-accentInfo font-bold bg-accentInfo/10 px-3 py-1 rounded">
|
||||
{item.price} {item.currency}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-textMuted mb-4">{item.description}</p>
|
||||
|
||||
{item.materialsRequired && (
|
||||
<div className="mt-auto mb-4 bg-surfaceHighlight/50 border border-white/5 rounded p-3 text-xs">
|
||||
<span className="font-bold text-gray-300 block mb-1">Materialanforderungen:</span>
|
||||
<span className="text-textMuted">{item.materialsRequired}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button className="mt-2 w-full py-2 bg-white/5 hover:bg-white/10 rounded text-sm font-medium transition-colors border border-white/5">
|
||||
Angebot anfordern
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Products Section */}
|
||||
{products.length > 0 && (
|
||||
<section>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="p-2 bg-blue-500/10 rounded-lg text-blue-400">
|
||||
<Icons.Box className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-white">Produktkatalog</h2>
|
||||
<p className="text-sm text-textMuted">Items, Bücher und Baupläne zur sofortigen Abholung.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{products.map(item => (
|
||||
<div key={item.id} className="bg-surface border border-border rounded-xl p-5 hover:border-accentInfo/30 transition-all group flex flex-col">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div className="w-12 h-12 bg-surfaceHighlight rounded-lg flex items-center justify-center text-textMuted group-hover:text-accentInfo transition-colors">
|
||||
{item.type === 'blueprint' ? <Icons.Scroll className="w-6 h-6" /> :
|
||||
<ItemIcon type="consumable" className="w-6 h-6" />}
|
||||
</div>
|
||||
<div className={`px-2 py-0.5 rounded text-[10px] font-bold uppercase border ${
|
||||
item.stock > 0 ? 'bg-green-500/10 text-green-400 border-green-500/20' : 'bg-red-500/10 text-red-400 border-red-500/20'
|
||||
}`}>
|
||||
{item.stock > 0 ? `${item.stock} auf Lager` : 'Ausverkauft'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg font-bold text-white mb-1">{item.name}</h3>
|
||||
<p className="text-sm text-textMuted mb-4 flex-1 line-clamp-2">{item.description}</p>
|
||||
|
||||
<div className="mt-auto pt-4 border-t border-white/5 flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[10px] text-textMuted uppercase">Preis</span>
|
||||
<span className="font-mono font-bold text-accentInfo">
|
||||
{item.price} <span className="text-xs font-sans text-gray-400">{item.currency}</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
disabled={item.stock === 0}
|
||||
className="px-4 py-2 bg-white/5 hover:bg-white/10 disabled:opacity-50 disabled:cursor-not-allowed rounded text-sm font-medium transition-colors"
|
||||
>
|
||||
Kaufen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectProfile;
|
||||
Reference in New Issue
Block a user