feat(phase1): complete base structure - landing page, auth, dashboard, airports, API, DB schema, 60 airports CSV

This commit is contained in:
2026-04-24 15:53:59 +02:00
parent 2960bd4c2f
commit ab5f97acbf
18 changed files with 4926 additions and 0 deletions

461
www/public/dashboard.php Normal file
View File

@@ -0,0 +1,461 @@
<?php
/**
* Airline Tycoon - Dashboard
* Hauptseite für eingeloggte Spieler
*/
require_once dirname(__DIR__) . '/src/bootstrap.php';
requireLogin();
$userId = Session::getUserId();
$user = $db->getUserById($userId);
if (!$user) {
Session::logout();
redirect('/login.php');
}
Session::refreshUserData($db);
// Aktualisiere User-Daten
$user = $db->getUserById($userId);
$stats = $db->getUserStats($userId);
$todayStats = $db->getTodayStats($userId);
$aircraft = $db->getUserAircraft($userId);
$routes = $db->getUserRoutes($userId);
// Level-Fortschritt berechnen
$expForNextLevel = $config['game']['level_up_threshold'] * pow($config['game']['level_multiplier'], $user['level'] - 1);
$levelProgress = ($user['experience'] / $expForNextLevel) * 100;
?>
<!DOCTYPE html>
<html lang="de" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Airline Tycoon Dashboard - Verwalte deine Airline">
<meta name="csrf-token" content="<?= h(csrf_token()) ?>">
<title>Dashboard - Airline Tycoon</title>
<link rel="stylesheet" href="<?= BASE_PATH ?>assets/css/style.css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>✈️</text></svg>">
</head>
<body>
<!-- Navigation -->
<nav class="navbar">
<div class="navbar-container">
<a href="<?= BASE_PATH ?>dashboard.php" class="navbar-brand">✈️ <?= h($user['airline_name']) ?></a>
<button class="navbar-toggle" aria-label="Menü">☰</button>
<ul class="navbar-menu">
<li><a href="<?= BASE_PATH ?>dashboard.php" class="navbar-item active">📊 Dashboard</a></li>
<li><a href="<?= BASE_PATH ?>airports.php" class="navbar-item">🌍 Weltkarte</a></li>
<li><a href="<?= BASE_PATH ?>airports.php?action=fleet" class="navbar-item">✈️ Flotte</a></li>
<li><a href="<?= BASE_PATH ?>airports.php?action=routes" class="navbar-item">🛫 Routen</a></li>
<li>
<span class="navbar-item">
💰 <?= formatMoney($user['balance']) ?>
</span>
</li>
<li>
<a href="<?= BASE_PATH ?>logout.php" class="navbar-item">🚪 Abmelden</a>
</li>
<li>
<button class="theme-toggle" id="themeToggle" aria-label="Design wechseln">🌙</button>
</li>
</ul>
</div>
</nav>
<main class="container p-lg">
<!-- Willkommensnachricht -->
<div class="panel mb-lg">
<h1>👋 Willkommen, <?= h($user['username']) ?>!</h1>
<p class="mt-sm text-secondary">
Level <?= h($user['level']) ?> | <?= h($user['airline_name']) ?>
</p>
<!-- Level Fortschritt -->
<div class="mt-md">
<div class="d-flex flex-between mb-sm">
<span>Erfahrung</span>
<span><?= number_format($user['experience'], 0, ',', '.') ?> / <?= number_format($expForNextLevel, 0, ',', '.') ?> XP</span>
</div>
<div class="progress">
<div class="progress-bar" style="width: <?= min(100, $levelProgress) ?>%">
<?= number_format($levelProgress, 1, ',', '.') ?>%
</div>
</div>
</div>
</div>
<!-- Stats Grid -->
<div class="stats-grid mb-lg">
<div class="stat-card">
<div class="stat-icon">💰</div>
<div class="stat-value"><?= formatMoney($user['balance']) ?></div>
<div class="stat-label">Kontostand</div>
</div>
<div class="stat-card">
<div class="stat-icon">✈️</div>
<div class="stat-value"><?= count($aircraft) ?></div>
<div class="stat-label">Flugzeuge</div>
</div>
<div class="stat-card">
<div class="stat-icon">🛫</div>
<div class="stat-value"><?= count($routes) ?></div>
<div class="stat-label">Routen</div>
</div>
<div class="stat-card">
<div class="stat-icon">👥</div>
<div class="stat-value"><?= formatNumber($todayStats['total_passengers'] ?? 0) ?></div>
<div class="stat-label">Passagiere (heute)</div>
</div>
<div class="stat-card">
<div class="stat-icon">📈</div>
<div class="stat-value"><?= formatMoney($todayStats['total_profit'] ?? 0) ?></div>
<div class="stat-label">Gewinn (heute)</div>
</div>
<div class="stat-card">
<div class="stat-icon">🏆</div>
<div class="stat-value"><?= formatNumber($stats['total_flights'] ?? 0) ?></div>
<div class="stat-label">Flüge (gesamt)</div>
</div>
</div>
<!-- Quick Actions -->
<div class="panel mb-lg">
<h2 class="panel-header">⚡ Schnellaktionen</h2>
<div class="d-flex flex-wrap gap-md mt-md">
<a href="<?= BASE_PATH ?>airports.php" class="btn btn-primary">
🛫 Neue Route erstellen
</a>
<button class="btn btn-secondary" onclick="openModal('purchaseAircraftModal')">
🛒 Flugzeug kaufen
</button>
<a href="<?= BASE_PATH ?>airports.php" class="btn btn-outline">
🌍 Weltkarte ansehen
</a>
</div>
</div>
<!-- Aircraft & Routes -->
<div class="d-grid" style="grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: var(--space-lg);">
<!-- Meine Flugzeuge -->
<div class="panel">
<h2 class="panel-header">✈️ Meine Flotte</h2>
<?php if (empty($aircraft)): ?>
<div class="card text-center p-lg">
<p class="text-muted">Du hast noch keine Flugzeuge.</p>
<button class="btn btn-primary mt-md" onclick="openModal('purchaseAircraftModal')">
🛒 Jetzt Flugzeug kaufen
</button>
</div>
<?php else: ?>
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Typ</th>
<th>Zustand</th>
<th>Flüge</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
<?php foreach ($aircraft as $plane): ?>
<tr>
<td><?= h($plane['name']) ?></td>
<td><?= h($plane['aircraft_type']) ?></td>
<td>
<span class="badge <?= $plane['condition'] > 70 ? 'badge-success' : ($plane['condition'] > 30 ? 'badge-warning' : 'badge-danger') ?>">
<?= number_format($plane['condition'], 1, ',', '.') ?>%
</span>
</td>
<td><?= formatNumber($plane['flights_count']) ?></td>
<td>
<?php
// Finde eine Route für dieses Flugzeug
$hasRoute = false;
foreach ($routes as $route) {
$hasRoute = true;
break;
}
if ($hasRoute): ?>
<button class="btn btn-sm btn-success"
onclick="startFlightFromAircraft(<?= $plane['id'] ?>)">
▶️ Fliegen
</button>
<?php else: ?>
<span class="text-muted">Keine Route</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
<!-- Meine Routen -->
<div class="panel">
<h2 class="panel-header">🛫 Meine Routen</h2>
<?php if (empty($routes)): ?>
<div class="card text-center p-lg">
<p class="text-muted">Du hast noch keine Routen.</p>
<a href="<?= BASE_PATH ?>airports.php" class="btn btn-primary mt-md">
🛫 Jetzt Route erstellen
</a>
</div>
<?php else: ?>
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>Von</th>
<th>Nach</th>
<th>Distanz</th>
<th>Nachfrage</th>
</tr>
</thead>
<tbody>
<?php foreach ($routes as $route): ?>
<tr>
<td>
<strong><?= h($route['from_iata']) ?></strong>
<br><small><?= h($route['from_city']) ?></small>
</td>
<td>
<strong><?= h($route['to_iata']) ?></strong>
<br><small><?= h($route['to_city']) ?></small>
</td>
<td><?= formatNumber($route['distance_km']) ?> km</td>
<td>
<div class="progress" style="width: 80px; height: 12px;">
<div class="progress-bar" style="width: <?= $route['demand_level'] ?>%">
<?= $route['demand_level'] ?>%
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
<!-- Letzte Flüge -->
<div class="panel mt-lg">
<h2 class="panel-header">📜 Letzte Flüge</h2>
<?php
$recentFlights = $db->select(
"SELECT f.*, a1.iata_code as from_iata, a2.iata_code as to_iata,
ac.name as aircraft_name, ac.aircraft_type
FROM flights f
JOIN routes r ON f.route_id = r.id
JOIN airports a1 ON r.from_airport_id = a1.id
JOIN airports a2 ON r.to_airport_id = a2.id
JOIN aircraft ac ON f.aircraft_id = ac.id
WHERE f.user_id = ?
ORDER BY f.created_at DESC
LIMIT 10",
[$userId]
);
?>
<?php if (empty($recentFlights)): ?>
<p class="text-center text-muted p-lg">Noch keine Flüge durchgeführt.</p>
<?php else: ?>
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>Flug</th>
<th>Flugzeug</th>
<th>Passagiere</th>
<th>Umsatz</th>
<th>Gewinn</th>
<th>Zeit</th>
</tr>
</thead>
<tbody>
<?php foreach ($recentFlights as $flight): ?>
<tr>
<td>
<strong><?= h($flight['from_iata']) ?></strong> →
<strong><?= h($flight['to_iata']) ?></strong>
</td>
<td><?= h($flight['aircraft_name']) ?></td>
<td><?= formatNumber($flight['passengers']) ?></td>
<td><?= formatMoney($flight['revenue']) ?></td>
<td class="<?= $flight['profit'] >= 0 ? 'text-success' : 'text-danger' ?>">
<?= formatMoney($flight['profit']) ?>
</td>
<td><?= timeAgo($flight['created_at']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</main>
<!-- Flugzeug kaufen Modal -->
<div id="purchaseAircraftModal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title">🛒 Flugzeug kaufen</h3>
<button class="modal-close" onclick="closeModal('purchaseAircraftModal')">×</button>
</div>
<div class="modal-body">
<p class="mb-md">Dein Kontostand: <strong><?= formatMoney($user['balance']) ?></strong></p>
<div class="d-grid gap-md">
<?php foreach ($config['aircraft_types'] as $type => $specs): ?>
<?php
$canAfford = $user['balance'] >= $specs['purchase_price'];
?>
<div class="card <?= $canAfford ? '' : 'text-muted' ?>"
style="<?= $canAfford ? '' : 'opacity: 0.6;' ?>">
<div class="d-flex flex-between flex-center">
<div>
<h4><?= h($type) ?></h4>
<p class="text-sm">
Sitze: <?= $specs['seat_capacity'] ?> |
Reichweite: <?= formatNumber($specs['range_km']) ?> km
</p>
</div>
<div class="text-right">
<div class="stat-value text-primary"><?= formatMoney($specs['purchase_price']) ?></div>
<?php if ($canAfford): ?>
<button class="btn btn-sm btn-success mt-sm"
onclick="purchaseAircraft('<?= h($type) ?>')">
Kaufen
</button>
<?php else: ?>
<span class="badge badge-danger">Zu teuer</span>
<?php endif; ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
<!-- Flug starten Modal -->
<div id="flightModal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title">▶️ Flug starten</h3>
<button class="modal-close" onclick="closeModal('flightModal')">×</button>
</div>
<div class="modal-body">
<p>Wähle ein Flugzeug für diese Route:</p>
<div id="flightAircraftList" class="d-grid gap-md mt-md">
<!-- Wird per JavaScript gefüllt -->
</div>
</div>
</div>
</div>
<!-- Toast Container -->
<div id="toastContainer" class="toast-container"></div>
<script>
// Globale Daten für JavaScript
const GAME_DATA = {
routes: <?= json_encode($routes) ?>,
aircraft: <?= json_encode($aircraft) ?>,
userBalance: <?= $user['balance'] ?>
};
function startFlightFromAircraft(aircraftId) {
const modal = document.getElementById('flightModal');
const list = document.getElementById('flightAircraftList');
// Gefilterte Liste erstellen
const aircraftHtml = GAME_DATA.aircraft.map(ac => {
if (ac.id == aircraftId) {
return `
<div class="card">
<div class="d-flex flex-between flex-center">
<div>
<h4>${ac.name}</h4>
<p class="text-sm">${ac.aircraft_type} | Sitze: ${ac.seat_capacity}</p>
<p class="text-sm">Zustand: ${ac.condition}%</p>
</div>
<button class="btn btn-success" onclick="executeFlightForAircraft(${ac.id})">
▶️ Flug starten!
</button>
</div>
</div>
`;
}
return '';
}).join('');
list.innerHTML = aircraftHtml;
openModal('flightModal');
}
async function executeFlightForAircraft(aircraftId) {
// Einfach die erste Route verwenden für Demo
if (GAME_DATA.routes.length > 0) {
closeModal('flightModal');
try {
const result = await api.post('game.php?action=flight', {
route_id: GAME_DATA.routes[0].id,
aircraft_id: aircraftId
});
if (result.success) {
showToast(`Flug erfolgreich! +${formatMoney(result.profit)}`, 'success');
setTimeout(() => location.reload(), 1500);
} else {
showToast(result.error || 'Flug fehlgeschlagen', 'error');
}
} catch (error) {
showToast('Netzwerkfehler', 'error');
}
} else {
showToast('Keine Route verfügbar!', 'warning');
}
}
function purchaseAircraft(type) {
api.post('game.php?action=purchase', { aircraft_type: type })
.then(result => {
if (result.success) {
showToast(`${type} gekauft für ${formatMoney(result.price)}`, 'success');
closeModal('purchaseAircraftModal');
setTimeout(() => location.reload(), 1500);
} else {
showToast(result.error || 'Kauf fehlgeschlagen', 'error');
}
});
}
function updateDashboard() {
// Dashboard aktualisieren (wird bei Bedarf erweitert)
location.reload();
}
</script>
<script src="<?= BASE_PATH ?>assets/js/main.js"></script>
</body>
</html>