From ea2b8035346fc117487f8d3d29db908a02f81724 Mon Sep 17 00:00:00 2001 From: Lars Behrends Date: Tue, 30 Dec 2025 15:58:07 +0100 Subject: [PATCH] feat: implement Players component with data fetching and loading state; remove mock data and enhance error handling in database service --- App.tsx | 38 +------ constants.ts | 191 ------------------------------------ pages/Cities.tsx | 18 +++- pages/CityProfile.tsx | 4 +- pages/Dashboard.tsx | 19 ++-- pages/Organizations.tsx | 67 ++++++++++--- pages/Players.tsx | 80 +++++++++++++++ pages/Projects.tsx | 16 ++- public/mock/orgs.json | 14 --- public/mock/players.json | 37 ------- public/mock/projects.json | 13 --- services/DatabaseService.ts | 28 ++---- 12 files changed, 184 insertions(+), 341 deletions(-) delete mode 100644 constants.ts create mode 100644 pages/Players.tsx delete mode 100644 public/mock/orgs.json delete mode 100644 public/mock/players.json delete mode 100644 public/mock/projects.json diff --git a/App.tsx b/App.tsx index 6886e9e..87e7673 100644 --- a/App.tsx +++ b/App.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Routes, Route, useNavigate, useLocation } from 'react-router-dom'; import Layout from './components/Layout'; import Dashboard from './pages/Dashboard'; +import Players from './pages/Players'; import PlayerProfile from './pages/PlayerProfile'; import SetupGuide from './pages/SetupGuide'; import Projects from './pages/Projects'; @@ -109,42 +110,7 @@ function App() { } /> {/* Player Routes */} - -
-

Bürgerverzeichnis

-
- -
-
- -
- {players.map(player => ( -
navigateToPlayer(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" - > -
-
- -
-
-
{player.username}
-
- {player.tags.slice(0, 2).map(t => {t})} -
-
-
-
- ))} -
- - } /> + } /> } /> {/* City Routes */} diff --git a/constants.ts b/constants.ts deleted file mode 100644 index afa63cd..0000000 --- a/constants.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { Player, Organization, Project } from './types'; - -export const MOCK_PLAYERS: Player[] = [ - { - uuid: '80301bff-74df-4579-bcfc-082ac8d26b5b', - username: 'kaiwastoshort', - isOnline: true, - tags: ['#Bürger', '#Händler'], - stats: { - playtimeHours: 482, - level: 45, - role: 'Bürger', - organizationId: 'org-3' // Moved to Old Haven - }, - inventory: [ - { id: 'minecraft:diamond_pickaxe', name: 'Behutsamkeit Hacke', count: 1, type: 'tool', rarity: 'epic', nbtSummary: 'Eff V, Rep.' }, - { id: 'minecraft:stone_bricks', name: 'Polierter Andesit', count: 64, type: 'block' }, - { id: 'minecraft:stone_bricks', name: 'Polierter Andesit', count: 32, type: 'block' }, - null, null, - { id: 'minecraft:cooked_beef', name: 'Steak', count: 12, type: 'consumable' }, - { id: 'minecraft:compass', name: 'Stadtkarte', count: 1, type: 'misc', nbtSummary: 'Ziel: Spawn' }, - null, null - ], - storyMarkdown: ` -# Der Bauplan von V - -> "Stein erinnert sich an das, was Eisen vergisst." - -**V** kam während der *Großen Expansion* ins Tal. Ursprünglich ein einfacher Maurer, stieg er nach dem Entwurf des **Aquädukt-Systems** schnell in den Rängen auf. - -### Bekannte Verbindungen -* Die Händlergilde -* Nördliche Entdecker - -### Aktuelles Ziel -Restaurierung des verfallenen Wachturms am östlichen Grat. - ` - }, - { - uuid: '8984c0b5-d912-4462-b189-c864fba4a1af', - username: 'DrKButz', - isOnline: false, - tags: ['#Bauunternehmer'], - stats: { - playtimeHours: 120, - level: 12, - role: 'Unternehmer', - organizationId: 'org-4' - }, - inventory: [ - { id: 'minecraft:redstone', name: 'Redstone Staub', count: 64, type: 'misc' }, - { id: 'minecraft:comparator', name: 'Komparator', count: 16, type: 'misc' }, - null, - { id: 'minecraft:book', name: 'Forschungsnotizen', count: 1, type: 'misc', nbtSummary: 'Seite 12: Logikgatter' }, - null, null, null, null, null - ], - storyMarkdown: ` -# Forschungslogbuch: - -Spezialisiert auf automatisierte Logistik. Gerüchten zufolge hat er eine Maschine gebaut, die Items schneller sortieren kann als jeder Mensch. - -* Sucht aktuell: Quarz -* Verkauft: Auto-Schmelzöfen - ` - }, - { - uuid: 'b3b84518-03a2-4b48-8551-448c3f7a7d77', - username: 'ceratic_test', - isOnline: true, - tags: ['#Bürger', '#Händler'], - stats: { - playtimeHours: 50, - level: 5, - role: 'Bürger', - organizationId: 'org-3' - }, - inventory: [], - storyMarkdown: 'Einfach nur eine einfache Bäckerin, die gutes Brot liebt.' - }, - { - uuid: '3c10008b-1eb2-4d89-8b8a-6d03c821eb09', - username: 'merkursun', - isOnline: true, - tags: ['#Bürger'], - stats: { - playtimeHours: 50, - level: 5, - role: 'Bürger', - organizationId: 'org-4' - }, - inventory: [], - storyMarkdown: 'Einfach nur eine einfache Bäckerin, die gutes Brot liebt.' - } -]; - -export const MOCK_ORGS: Organization[] = [ - /* { - id: 'org-2', - name: 'Butz Building GmbH', - type: 'Company', - description: 'Baugewerbe.', - memberCount: 1, - status: 'active' - },*/ - { - id: 'org-3', - name: 'Provisorium Null', - type: 'City', - description: 'Die erste Siedlung, eingebettet zwischen den Zwillingsgipfeln. Bekannt für das geschäftige Marktviertel und die alten Steinmauern.', - memberCount: 6, - status: 'active', - mayor: '', - establishedYear: 'Day 0', - bannerUrl: 'images/screenshots/2025-12-28_01.11.10.png', - gallery: [ - 'images/screenshots/2025-12-28_01.12.07.png', // Market - 'images/screenshots/2025-12-28_01.12.17.png', // Castle - 'images/screenshots/2025-12-28_01.12.24.png' // Walls - ], - cityStats: { - taxRate: 3.5, - biome: 'Ebene / Gebirge', - defenseRating: 8, - government: 'Feudalmonarchie', - specialty: 'Handel & Stein' - } - }, - { - id: 'org-4', - name: 'Sakura', - type: 'City', - description: 'Eine dunkle, biolumineszente Hafenstadt in den tiefen Höhlen. Heimat von Schmugglern und Händlern seltener Mineralien.', - memberCount: 2, - status: 'active', - mayor: 'Kampfzwerk', - establishedYear: 'Ära 2, Jahr 10', - bannerUrl: 'images/screenshots/2025-12-28_01.11.32.png', - gallery: [ - 'images/screenshots/2025-12-28_01.11.38.png', - 'images/screenshots/2025-12-28_01.11.44.png' - ], - cityStats: { - taxRate: 15.0, - biome: 'Deep Dark / Lush Caves', - defenseRating: 4, - government: 'Syndikat', - specialty: 'Schmuggelware & Erze' - } - }, -]; - -export const MOCK_PROJECTS: Project[] = [ - { - id: 'ven-1', - title: 'DrkButz Architektur & Mauerwerk', - description: 'Führendes Architekturbüro, spezialisiert auf gotische Strukturen und Verteidigungsmauern. Wir entwerfen dein Vermächtnis.', - category: 'Enterprise', - status: 'active', - progress: 85, // Reputation - owner: 'DrKButz', - employees: [], - hiring: false, - foundedDate: 'Zyklus 12', - associatedOrgId: 'org-4', // Old Haven - bannerUrl: 'images/screenshots/2025-12-28_01.11.49.png', - shopCatalog: [ - { id: 's2', name: 'Geländevermessung', description: '1 Stunde Beratung zur Geländevorbereitung.', price: 20, currency: 'Gold', stock: 10, type: 'service' }, - { id: 's3', name: 'Mauerbau', description: 'Wir bauen deine Außenmauern. Preis pro Chunk.', price: 100, currency: 'Gold', stock: 5, type: 'service', materialsRequired: 'Kunde stellt Steinziegel' }, - { id: 's4', name: 'Terraforming', description: 'Einebnung und Landschaftsbau.', price: 75, currency: 'Gold', stock: 2, type: 'service' } - ] - }, - { - id: 'ven-5', - title: 'Tinas Tavern', - description: 'Täglich frisches Brot zum Spawn-Markt geliefert. Lieferant der königlichen Garde.', - category: 'Enterprise', - status: 'active', - progress: 98, - owner: 'kaiwastoshort', - employees: [], - hiring: false, - associatedOrgId: 'org-3', - bannerUrl: 'images/screenshots/tinas.png', - shopCatalog: [ - { id: 'f1', name: 'Redstone Block', description: '', price: 2, currency: 'Diamonds', stock: 45, type: 'item' }, - { id: 'f2', name: 'Honey', description: '', price: 5, currency: 'Diamonds', stock: 12, type: 'item' }, - { id: 'f3', name: 'Slime', description: '', price: 10, currency: 'Diamonds', stock: 4, type: 'item' }, - { id: 'f4', name: 'Bier', description: '', price: 3, currency: 'Diamonds', stock: 20, type: 'item' } - ] - }, -]; \ No newline at end of file diff --git a/pages/Cities.tsx b/pages/Cities.tsx index ba35476..019ca59 100644 --- a/pages/Cities.tsx +++ b/pages/Cities.tsx @@ -53,6 +53,17 @@ const Cities: React.FC = ({ onSelectCity }) => { 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(); @@ -63,7 +74,7 @@ const Cities: React.FC = ({ onSelectCity }) => { // Count citizens (players with this organizationId) const allPlayers = dbService.getPlayers(); const citizenCount = allPlayers.filter(player => - player.stats.organizationId === city.id + player.organizationId === city.id ).length; // Count businesses/projects in this city @@ -87,11 +98,10 @@ const Cities: React.FC = ({ onSelectCity }) => { setCities(cityOrgs); setCitiesWithStats(citiesStats); - setLoading(false); }; - // Initial load - loadCities(); + // Load fresh data on mount + loadData(); // Subscribe to updates const unsub = dbService.subscribe(loadCities); diff --git a/pages/CityProfile.tsx b/pages/CityProfile.tsx index 84a78e1..bf8bcf2 100644 --- a/pages/CityProfile.tsx +++ b/pages/CityProfile.tsx @@ -270,8 +270,8 @@ const CityProfile: React.FC = () => { onClick={() => onSelectPlayer(player.uuid)} className="bg-surface border border-border p-4 rounded-xl flex items-center gap-4 hover:border-accentInfo/50 transition-colors cursor-pointer group hover:shadow-card hover:bg-surfaceHighlight/30" > -
- {player.username.charAt(0)} +
+
{player.username}
diff --git a/pages/Dashboard.tsx b/pages/Dashboard.tsx index ba4f3a9..fe94c92 100644 --- a/pages/Dashboard.tsx +++ b/pages/Dashboard.tsx @@ -48,19 +48,26 @@ const Dashboard: React.FC = () => { 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); + }; + // 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); + // Load fresh data on mount + loadData(); return unsubscribe; }, []); diff --git a/pages/Organizations.tsx b/pages/Organizations.tsx index 7147488..6ad66c2 100644 --- a/pages/Organizations.tsx +++ b/pages/Organizations.tsx @@ -1,7 +1,7 @@ -import React, { useState } from 'react'; -import { MOCK_ORGS } from '../constants'; +import React, { useState, useEffect } from 'react'; import { Organization } from '../types'; import { Icons } from '../components/IconSet'; +import { dbService } from '../services/DatabaseService'; const OrgCard = ({ org, onClick }: { org: Organization; onClick: () => void }) => (
void }) = const Organizations: React.FC<{ onSelectOrg: (id: string) => void }> = ({ onSelectOrg }) => { const [filter, setFilter] = useState<'all' | Organization['type']>('all'); + const [orgs, setOrgs] = useState([]); + const [loading, setLoading] = useState(true); - const filteredOrgs = MOCK_ORGS.filter(org => + 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 loadOrgs = () => { + const allOrgs = dbService.getOrgs(); + setOrgs(allOrgs); + }; + + // Load fresh data on mount + loadData(); + + // Subscribe to updates + const unsub = dbService.subscribe(loadOrgs); + return unsub; + }, []); + + const filteredOrgs = orgs.filter(org => filter === 'all' ? true : org.type === filter ); @@ -73,8 +100,8 @@ const Organizations: React.FC<{ onSelectOrg: (id: string) => void }> = ({ onSele key={tab.id} onClick={() => setFilter(tab.id as any)} className={`px-4 py-2 text-sm font-medium rounded-t-lg transition-colors relative top-[1px] ${ - filter === tab.id - ? 'text-textMain border-b-2 border-accentInfo bg-surfaceHighlight/20' + filter === tab.id + ? 'text-textMain border-b-2 border-accentInfo bg-surfaceHighlight/20' : 'text-textMuted hover:text-textMain hover:bg-white/5' }`} > @@ -83,19 +110,29 @@ const Organizations: React.FC<{ onSelectOrg: (id: string) => void }> = ({ onSele ))}
-
- {filteredOrgs.map(org => ( - onSelectOrg(org.id)} /> - ))} -
- - {filteredOrgs.length === 0 && ( -
-

Keine Organisationen gefunden.

+ {loading ? ( +
+
+ ) : ( + <> +
+ {filteredOrgs.map(org => ( +
+ onSelectOrg(org.id)} /> +
+ ))} +
+ + {filteredOrgs.length === 0 && ( +
+

Keine Organisationen gefunden.

+
+ )} + )}
); }; -export default Organizations; \ No newline at end of file +export default Organizations; diff --git a/pages/Players.tsx b/pages/Players.tsx new file mode 100644 index 0000000..9e168a8 --- /dev/null +++ b/pages/Players.tsx @@ -0,0 +1,80 @@ +import React, { useState, useEffect } from 'react'; +import { Player } from '../types'; +import { dbService } from '../services/DatabaseService'; + +interface PlayersProps { + onSelectPlayer: (id: string) => void; +} + +const Players: React.FC = ({ onSelectPlayer }) => { + const [players, setPlayers] = useState([]); + 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); + }; + + // Subscribe to database updates + const unsubDb = dbService.subscribe(() => { + setPlayers(dbService.getPlayers()); + }); + + // Load fresh data on mount + loadData(); + + return unsubDb; + }, []); + + return ( +
+
+

Bürgerverzeichnis

+
+ +
+
+ + {loading ? ( +
+
+
+ ) : ( +
+ {players.map(player => ( +
onSelectPlayer(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" + > +
+
+ +
+
+
{player.username}
+
+ {player.tags.slice(0, 2).map(t => {t})} +
+
+
+
+ ))} +
+ )} +
+ ); +}; + +export default Players; diff --git a/pages/Projects.tsx b/pages/Projects.tsx index 1af265d..6c4a203 100644 --- a/pages/Projects.tsx +++ b/pages/Projects.tsx @@ -138,14 +138,24 @@ const Projects: React.FC = ({ onSelectProject }) => { // Subscribe to auth and data updates 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 unsubAuth = authService.subscribe(setUser); const unsubDb = dbService.subscribe(() => { setProjects(dbService.getProjects()); }); - // Initial data load - setProjects(dbService.getProjects()); - setLoading(false); + // Load fresh data on mount + loadData(); return () => { unsubAuth(); diff --git a/public/mock/orgs.json b/public/mock/orgs.json deleted file mode 100644 index 41cf488..0000000 --- a/public/mock/orgs.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "id": 1, - "name": "Mock Organization", - "description": "This is a mock organization that appears when the backend is unavailable.", - "leader": "DrKButz", - "members": ["8984c0b5-d912-4462-b189-c864fba4a1af"], - "gallery": [], - "cityStats": { - "influence": 50, - "reputation": 75 - } - } -] \ No newline at end of file diff --git a/public/mock/players.json b/public/mock/players.json deleted file mode 100644 index ec89ce8..0000000 --- a/public/mock/players.json +++ /dev/null @@ -1,37 +0,0 @@ -[ - { - "uuid": "8984c0b5-d912-4462-b189-c864fba4a1af", - "name": "DrKButz", - "discordId": "mock_discord_id", - "storyMarkdown": "# Mock Player Story\n\nThis is a mock player story that appears when the backend is unavailable.", - "tags": ["admin", "developer"], - "stats": { - "health": 100, - "armor": 50, - "money": 10000 - }, - "inventory": [ - {"id": "weapon_pistol", "name": "Pistol", "quantity": 1}, - {"id": "item_medkit", "name": "Medkit", "quantity": 3} - ], - "isOnline": false, - "lastSeen": "2025-01-01T12:00:00Z" - }, - { - "uuid": "123e4567-e89b-12d3-a456-426614174000", - "name": "TestPlayer", - "discordId": null, - "storyMarkdown": "# Test Player\n\nThis is a test player for mock data purposes.", - "tags": ["player"], - "stats": { - "health": 80, - "armor": 30, - "money": 5000 - }, - "inventory": [ - {"id": "item_food", "name": "Food", "quantity": 5} - ], - "isOnline": true, - "lastSeen": "2025-01-01T10:00:00Z" - } -] diff --git a/public/mock/projects.json b/public/mock/projects.json deleted file mode 100644 index f87e129..0000000 --- a/public/mock/projects.json +++ /dev/null @@ -1,13 +0,0 @@ -[ - { - "id": 1, - "name": "Mock Project", - "description": "This is a mock project that appears when the backend is unavailable.", - "leader": "DrKButz", - "employees": ["8984c0b5-d912-4462-b189-c864fba4a1af"], - "shopCatalog": [], - "gallery": [], - "hiring": false, - "status": "active" - } -] diff --git a/services/DatabaseService.ts b/services/DatabaseService.ts index eeae540..c1bfaaf 100644 --- a/services/DatabaseService.ts +++ b/services/DatabaseService.ts @@ -1,20 +1,18 @@ -import { MOCK_PLAYERS, MOCK_ORGS, MOCK_PROJECTS } from '../constants'; import { Player, Organization, Project } from '../types'; const API_URL = 'https://vollidioten.ceraticsoft.de/api'; -const MOCK_DATA_HEADER = 'X-Mock-Data'; class DatabaseService { - private players: Player[] = MOCK_PLAYERS; - private orgs: Organization[] = MOCK_ORGS; - private projects: Project[] = MOCK_PROJECTS; + private players: Player[] = []; + private orgs: Organization[] = []; + private projects: Project[] = []; private listeners: Function[] = []; constructor() { this.fetchAll(); } - // Try to fetch real data from backend + // Fetch data from backend - always try to load fresh data async fetchAll() { try { console.log("Fetching data from API..."); @@ -24,11 +22,6 @@ class DatabaseService { fetch(`${API_URL}/projects`) ]); - // Check if we're getting mock data from nginx fallback - const isMockData = pRes.headers.get(MOCK_DATA_HEADER) === 'true' || - oRes.headers.get(MOCK_DATA_HEADER) === 'true' || - prRes.headers.get(MOCK_DATA_HEADER) === 'true'; - if (pRes.ok && oRes.ok && prRes.ok) { const playersData = await pRes.json(); const orgsData = await oRes.json(); @@ -41,19 +34,14 @@ class DatabaseService { this.projects = projectsData; this.notify(); - if (isMockData) { - console.warn("Backend unavailable - using mock data from nginx fallback"); - } else { - console.log("✅ Connected to Backend Database - using real data"); - } + console.log("✅ Connected to Backend Database - using real data"); } else { console.warn(`API returned errors: players=${pRes.status}, orgs=${oRes.status}, projects=${prRes.status}`); - console.warn("Using built-in mock data"); - // Data is already set to mock data in constructor + throw new Error("Backend unavailable"); } } catch (e) { - console.warn("Backend not available, using built-in Mock Data.", e); - // Fallback is already set in constructor + console.warn("Backend not available, data not loaded.", e); + throw e; // Re-throw so components can handle the error } }