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.
639 lines
34 KiB
TypeScript
639 lines
34 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { Project, ShopItem } from '../types';
|
|
import { Icons, ItemIcon } from '../components/IconSet';
|
|
import { dbService } from '../services/DatabaseService';
|
|
import { authService } from '../services/AuthService';
|
|
import { DiscordUser } from '../types';
|
|
import EditModal from '../components/EditModal';
|
|
import ShopManagementModal from '../components/ShopManagementModal';
|
|
import EmployeeManagementModal from '../components/EmployeeManagementModal';
|
|
import BannerManagementModal from '../components/BannerManagementModal';
|
|
import GalleryManagementModal from '../components/GalleryManagementModal';
|
|
import DeleteProjectModal from '../components/DeleteProjectModal';
|
|
|
|
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' | 'manage'>('overview');
|
|
const [user, setUser] = useState<DiscordUser | null>(null);
|
|
const [org, setOrg] = useState<any>(null);
|
|
const [ownerPlayer, setOwnerPlayer] = useState<any>(null);
|
|
const [isEditDescriptionOpen, setIsEditDescriptionOpen] = useState(false);
|
|
const [isEditHiringOpen, setIsEditHiringOpen] = useState(false);
|
|
const [isShopModalOpen, setIsShopModalOpen] = useState(false);
|
|
const [isEmployeeModalOpen, setIsEmployeeModalOpen] = useState(false);
|
|
const [isBannerModalOpen, setIsBannerModalOpen] = useState(false);
|
|
const [isGalleryModalOpen, setIsGalleryModalOpen] = useState(false);
|
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
|
|
|
// Subscribe to auth and data updates
|
|
useEffect(() => {
|
|
const unsubAuth = authService.subscribe(setUser);
|
|
return unsubAuth;
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
// Load associated org and owner player
|
|
if (project.associatedOrgId) {
|
|
const foundOrg = dbService.getOrg(project.associatedOrgId);
|
|
setOrg(foundOrg);
|
|
}
|
|
|
|
// Find owner player by username
|
|
const players = dbService.getPlayers();
|
|
const owner = players.find(p => p.username === project.owner);
|
|
setOwnerPlayer(owner);
|
|
}, [project]);
|
|
|
|
const hasShop = project.shopCatalog && project.shopCatalog.length > 0;
|
|
const isOwner = user?.linkedPlayerUuid && ownerPlayer && dbService.getPlayer(user.linkedPlayerUuid)?.username === project.owner;
|
|
|
|
// 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>
|
|
)}
|
|
{isOwner && (
|
|
<button
|
|
onClick={() => setActiveTab('manage')}
|
|
className={`pb-4 text-sm font-medium transition-colors border-b-2 ${activeTab === 'manage' ? 'border-accentInfo text-white' : 'border-transparent text-textMuted hover:text-white'}`}
|
|
>
|
|
Verwalten
|
|
</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 players = dbService.getPlayers();
|
|
const empPlayer = 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 === 'manage' && isOwner && (
|
|
<div className="space-y-8">
|
|
<div className="bg-surface/50 border border-border rounded-xl p-6">
|
|
<h3 className="text-lg font-bold text-white mb-6 flex items-center gap-2">
|
|
<Icons.Edit className="w-5 h-5 text-accentInfo" />
|
|
Unternehmen verwalten
|
|
</h3>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
{/* Edit Description */}
|
|
<div className="bg-surfaceHighlight/30 border border-border rounded-lg p-4">
|
|
<h4 className="text-sm font-semibold text-textMain mb-3">Manifest bearbeiten</h4>
|
|
<p className="text-xs text-textMuted mb-4">
|
|
Ändern Sie die Beschreibung und das Leitbild Ihres Unternehmens.
|
|
</p>
|
|
<button
|
|
onClick={() => setIsEditDescriptionOpen(true)}
|
|
className="w-full bg-accentInfo hover:bg-accentInfo/90 text-white text-sm font-medium py-2 px-4 rounded transition-colors"
|
|
>
|
|
Beschreibung bearbeiten
|
|
</button>
|
|
</div>
|
|
|
|
{/* Toggle Hiring */}
|
|
<div className="bg-surfaceHighlight/30 border border-border rounded-lg p-4">
|
|
<h4 className="text-sm font-semibold text-textMain mb-3">Stellenanzeigen</h4>
|
|
<p className="text-xs text-textMuted mb-4">
|
|
Aktuell: {project.hiring ? 'Stellen sind ausgeschrieben' : 'Keine offenen Stellen'}
|
|
</p>
|
|
<button
|
|
onClick={() => setIsEditHiringOpen(true)}
|
|
className="w-full bg-accentInfo hover:bg-accentInfo/90 text-white text-sm font-medium py-2 px-4 rounded transition-colors"
|
|
>
|
|
{project.hiring ? 'Stellenanzeigen beenden' : 'Stellen ausschreiben'}
|
|
</button>
|
|
</div>
|
|
|
|
{/* Shop Management */}
|
|
<div className="bg-surfaceHighlight/30 border border-border rounded-lg p-4">
|
|
<h4 className="text-sm font-semibold text-textMain mb-3">Shop verwalten</h4>
|
|
<p className="text-xs text-textMuted mb-4">
|
|
Fügen Sie Produkte, Dienstleistungen und Preise hinzu.
|
|
</p>
|
|
<button
|
|
onClick={() => setIsShopModalOpen(true)}
|
|
className="w-full bg-purple-500 hover:bg-purple-600 text-white text-sm font-medium py-2 px-4 rounded transition-colors"
|
|
>
|
|
Shop bearbeiten
|
|
</button>
|
|
</div>
|
|
|
|
{/* Employee Management */}
|
|
<div className="bg-surfaceHighlight/30 border border-border rounded-lg p-4">
|
|
<h4 className="text-sm font-semibold text-textMain mb-3">Mitarbeiter verwalten</h4>
|
|
<p className="text-xs text-textMuted mb-4">
|
|
Fügen Sie Mitarbeiter hinzu oder entfernen Sie sie.
|
|
</p>
|
|
<button
|
|
onClick={() => setIsEmployeeModalOpen(true)}
|
|
className="w-full bg-green-500 hover:bg-green-600 text-white text-sm font-medium py-2 px-4 rounded transition-colors"
|
|
>
|
|
Mitarbeiter verwalten
|
|
</button>
|
|
</div>
|
|
|
|
{/* Banner Management */}
|
|
<div className="bg-surfaceHighlight/30 border border-border rounded-lg p-4">
|
|
<h4 className="text-sm font-semibold text-textMain mb-3">Banner ändern</h4>
|
|
<p className="text-xs text-textMuted mb-4">
|
|
Ändern Sie das Titelbild Ihres Unternehmens.
|
|
</p>
|
|
<button
|
|
onClick={() => setIsBannerModalOpen(true)}
|
|
className="w-full bg-orange-500 hover:bg-orange-600 text-white text-sm font-medium py-2 px-4 rounded transition-colors"
|
|
>
|
|
Banner bearbeiten
|
|
</button>
|
|
</div>
|
|
|
|
{/* Gallery Management */}
|
|
<div className="bg-surfaceHighlight/30 border border-border rounded-lg p-4">
|
|
<h4 className="text-sm font-semibold text-textMain mb-3">Galerie verwalten</h4>
|
|
<p className="text-xs text-textMuted mb-4">
|
|
Fügen Sie Bilder zu Ihrem Portfolio hinzu.
|
|
</p>
|
|
<button
|
|
onClick={() => setIsGalleryModalOpen(true)}
|
|
className="w-full bg-blue-500 hover:bg-blue-600 text-white text-sm font-medium py-2 px-4 rounded transition-colors"
|
|
>
|
|
Galerie bearbeiten
|
|
</button>
|
|
</div>
|
|
|
|
{/* Danger Zone */}
|
|
<div className="bg-red-500/10 border border-red-500/20 rounded-lg p-4">
|
|
<h4 className="text-sm font-semibold text-red-400 mb-3">Gefahrenzone</h4>
|
|
<p className="text-xs text-red-300 mb-4">
|
|
Unwiderrufliche Aktionen für dieses Unternehmen.
|
|
</p>
|
|
<button
|
|
onClick={() => setIsDeleteModalOpen(true)}
|
|
className="w-full bg-red-500 hover:bg-red-600 text-white text-sm font-medium py-2 px-4 rounded transition-colors"
|
|
>
|
|
Unternehmen löschen
|
|
</button>
|
|
</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>
|
|
)}
|
|
|
|
{/* Edit Modals */}
|
|
<EditModal
|
|
isOpen={isEditDescriptionOpen}
|
|
title="Manifest bearbeiten"
|
|
initialValue={project.description}
|
|
multiline={true}
|
|
markdown={true}
|
|
onClose={() => setIsEditDescriptionOpen(false)}
|
|
onSave={async (newDescription) => {
|
|
try {
|
|
const response = await fetch(`https://vollidioten.ceraticsoft.de/api/projects/${project.id}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify({ description: newDescription })
|
|
});
|
|
|
|
if (response.ok) {
|
|
// Update local data
|
|
dbService.updateProject(project.id, { description: newDescription });
|
|
console.log('Description updated successfully');
|
|
} else {
|
|
const errorData = await response.json();
|
|
alert(`Fehler beim Aktualisieren: ${errorData.error || 'Unbekannter Fehler'}`);
|
|
}
|
|
} catch (err) {
|
|
console.error('Error updating description:', err);
|
|
alert('Netzwerkfehler beim Aktualisieren der Beschreibung');
|
|
}
|
|
}}
|
|
/>
|
|
|
|
<EditModal
|
|
isOpen={isEditHiringOpen}
|
|
title={project.hiring ? 'Stellenanzeigen beenden' : 'Stellen ausschreiben'}
|
|
initialValue={project.hiring ? 'Ja, Stellen sind derzeit ausgeschrieben.' : 'Nein, derzeit keine offenen Stellen.'}
|
|
multiline={false}
|
|
onClose={() => setIsEditHiringOpen(false)}
|
|
onSave={async (value) => {
|
|
const newHiringStatus = !project.hiring;
|
|
|
|
try {
|
|
const response = await fetch(`https://vollidioten.ceraticsoft.de/api/projects/${project.id}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify({ hiring: newHiringStatus })
|
|
});
|
|
|
|
if (response.ok) {
|
|
// Update local data
|
|
dbService.updateProject(project.id, { hiring: newHiringStatus });
|
|
console.log('Hiring status updated successfully:', newHiringStatus);
|
|
} else {
|
|
const errorData = await response.json();
|
|
alert(`Fehler beim Aktualisieren: ${errorData.error || 'Unbekannter Fehler'}`);
|
|
}
|
|
} catch (err) {
|
|
console.error('Error updating hiring status:', err);
|
|
alert('Netzwerkfehler beim Aktualisieren der Stellenanzeigen');
|
|
}
|
|
}}
|
|
/>
|
|
|
|
{/* Management Modals */}
|
|
<ShopManagementModal
|
|
isOpen={isShopModalOpen}
|
|
onClose={() => setIsShopModalOpen(false)}
|
|
projectId={project.id}
|
|
onUpdate={() => {
|
|
// Refresh project data
|
|
console.log('Shop updated, refreshing project data...');
|
|
// The dbService should automatically update via subscription
|
|
}}
|
|
/>
|
|
|
|
<EmployeeManagementModal
|
|
isOpen={isEmployeeModalOpen}
|
|
onClose={() => setIsEmployeeModalOpen(false)}
|
|
projectId={project.id}
|
|
onUpdate={() => {
|
|
// Refresh project data
|
|
console.log('Employees updated, refreshing project data...');
|
|
}}
|
|
/>
|
|
|
|
<BannerManagementModal
|
|
isOpen={isBannerModalOpen}
|
|
onClose={() => setIsBannerModalOpen(false)}
|
|
projectId={project.id}
|
|
currentBannerUrl={project.bannerUrl || ''}
|
|
onUpdate={() => {
|
|
// Refresh project data
|
|
console.log('Banner updated, refreshing project data...');
|
|
}}
|
|
/>
|
|
|
|
<GalleryManagementModal
|
|
isOpen={isGalleryModalOpen}
|
|
onClose={() => setIsGalleryModalOpen(false)}
|
|
projectId={project.id}
|
|
onUpdate={() => {
|
|
// Refresh project data
|
|
console.log('Gallery updated, refreshing project data...');
|
|
}}
|
|
/>
|
|
|
|
<DeleteProjectModal
|
|
isOpen={isDeleteModalOpen}
|
|
onClose={() => setIsDeleteModalOpen(false)}
|
|
projectId={project.id}
|
|
projectTitle={project.title}
|
|
onDelete={async () => {
|
|
try {
|
|
const response = await fetch(`https://vollidioten.ceraticsoft.de/api/projects/${project.id}`, {
|
|
method: 'DELETE',
|
|
credentials: 'include'
|
|
});
|
|
|
|
if (response.ok) {
|
|
alert('Projekt erfolgreich gelöscht!');
|
|
onBack(); // Navigate back to projects list
|
|
} else {
|
|
alert('Fehler beim Löschen des Projekts');
|
|
}
|
|
} catch (err) {
|
|
console.error('Error deleting project:', err);
|
|
alert('Netzwerkfehler beim Löschen');
|
|
}
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ProjectProfile;
|