mirror of
https://github.com/ceratic/project_vollidioten_website.git
synced 2026-05-14 00:16:47 +02:00
144 lines
4.9 KiB
TypeScript
144 lines
4.9 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { Organization } from '../types';
|
|
import { Icons } from '../components/IconSet';
|
|
import { dbService } from '../services/DatabaseService';
|
|
|
|
interface CitiesProps {
|
|
onSelectCity: (id: string) => void;
|
|
}
|
|
|
|
const CityCard = ({ city, onClick }: { city: Organization; onClick: () => void }) => (
|
|
<div
|
|
onClick={onClick}
|
|
className="group relative h-64 rounded-2xl overflow-hidden cursor-pointer border border-border hover:border-accentInfo/50 transition-all duration-300 shadow-card"
|
|
>
|
|
{/* Background Image */}
|
|
<div className="absolute inset-0">
|
|
<img
|
|
src={city.bannerUrl || 'https://via.placeholder.com/800x400'}
|
|
alt={city.name}
|
|
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"
|
|
/>
|
|
<div className="absolute inset-0 bg-gradient-to-t from-background via-background/60 to-transparent" />
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="absolute inset-0 p-6 flex flex-col justify-end">
|
|
<div className="flex justify-between items-end">
|
|
<div>
|
|
<div className="text-xs font-bold text-accentInfo mb-1 uppercase tracking-widest">{city.establishedYear || 'Unbekannte Ära'}</div>
|
|
<h2 className="text-3xl font-bold text-white mb-2 group-hover:translate-x-1 transition-transform">{city.name}</h2>
|
|
<p className="text-sm text-gray-300 line-clamp-2 max-w-sm">
|
|
{city.description}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex flex-col items-end gap-2">
|
|
<div className="flex items-center gap-2 px-3 py-1 bg-black/40 backdrop-blur rounded-full border border-white/10">
|
|
<Icons.Users className="w-4 h-4 text-textMuted" />
|
|
<span className="text-sm font-medium text-white">{city.memberCount} Bürger</span>
|
|
</div>
|
|
<div className="px-3 py-1 bg-accentInfo/20 backdrop-blur rounded-full border border-accentInfo/30 text-xs font-bold text-accentInfo uppercase">
|
|
{city.status}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
const Cities: React.FC<CitiesProps> = ({ onSelectCity }) => {
|
|
const [cities, setCities] = useState<Organization[]>([]);
|
|
const [citiesWithStats, setCitiesWithStats] = useState<any[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
const loadData = async () => {
|
|
setLoading(true);
|
|
try {
|
|
await dbService.fetchAll();
|
|
} catch (error) {
|
|
console.warn('Failed to fetch fresh data:', error);
|
|
// Continue with cached data
|
|
}
|
|
setLoading(false);
|
|
};
|
|
|
|
const loadCities = () => {
|
|
// Get all organizations from database
|
|
const allOrgs = dbService.getOrgs();
|
|
const cityOrgs = allOrgs.filter(org => org.type === 'City');
|
|
|
|
// Calculate dynamic stats for each city
|
|
const citiesStats = cityOrgs.map(city => {
|
|
// Count citizens (players with this organizationId)
|
|
const allPlayers = dbService.getPlayers();
|
|
const citizenCount = allPlayers.filter(player =>
|
|
player.organizationId === city.id
|
|
).length;
|
|
|
|
// Count businesses/projects in this city
|
|
const allProjects = dbService.getProjects();
|
|
const businessCount = allProjects.filter(project =>
|
|
project.associatedOrgId === city.id
|
|
).length;
|
|
|
|
return {
|
|
...city,
|
|
memberCount: citizenCount,
|
|
businessCount: businessCount,
|
|
// Override static memberCount with dynamic count
|
|
stats: {
|
|
citizens: citizenCount,
|
|
businesses: businessCount,
|
|
...city.cityStats
|
|
}
|
|
};
|
|
});
|
|
|
|
setCities(cityOrgs);
|
|
setCitiesWithStats(citiesStats);
|
|
};
|
|
|
|
// Load fresh data on mount
|
|
loadData();
|
|
|
|
// Subscribe to updates
|
|
const unsub = dbService.subscribe(loadCities);
|
|
return unsub;
|
|
}, []);
|
|
|
|
return (
|
|
<div className="animate-in fade-in slide-in-from-bottom-2 space-y-8">
|
|
<div>
|
|
<h1 className="text-3xl font-bold mb-1">Große Siedlungen</h1>
|
|
<p className="text-textMuted">Entdecke die blühenden Zentren der Zivilisation im Obsidian-Tal.</p>
|
|
</div>
|
|
|
|
{loading ? (
|
|
<div className="flex justify-center py-20">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-accentInfo"></div>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<div className="grid grid-cols-1 gap-6">
|
|
{citiesWithStats.map(city => (
|
|
<div key={city.id}>
|
|
<CityCard city={city} onClick={() => onSelectCity(city.id)} />
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{citiesWithStats.length === 0 && (
|
|
<div className="text-center py-20 text-textMuted">
|
|
<p>Noch keine Städte gegründet.</p>
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Cities;
|