- market.php: Buy new aircraft (6 types: Dash8 to A380) - aircraft.php: Fleet management, crew assignment, route planning - finances.php: Account balance, transactions, loans - Database.php: Extended with loan/transaction/aircraft methods - schema.sql: Aircraft types table, 10M starting balance, INSERT OR IGNORE - dashboard.php: Updated with fleet/financial overview - Fixed: duplicate function declarations in Database.php - Fixed: initializeSchema() with idempotent error handling
490 lines
22 KiB
PHP
490 lines
22 KiB
PHP
<?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);
|
||
|
||
// Kredite und Finanzen
|
||
$totalDebt = $db->getTotalDebtWithInterest($userId);
|
||
$maintenanceDue = $db->checkMaintenanceDue($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">🌍 Flughäfen</a></li>
|
||
<li><a href="<?= BASE_PATH ?>aircraft.php" class="navbar-item">✈️ Flotte</a></li>
|
||
<li><a href="<?= BASE_PATH ?>finances.php" class="navbar-item">💰 Finanzen</a></li>
|
||
<li><a href="<?= BASE_PATH ?>market.php" class="navbar-item">🛒 Markt</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 class="stat-card">
|
||
<div class="stat-icon">🏦</div>
|
||
<div class="stat-value <?= $totalDebt > 0 ? 'text-warning' : '' ?>">
|
||
<?= formatMoney($totalDebt) ?>
|
||
</div>
|
||
<div class="stat-label">Schulden</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="stat-icon">🔧</div>
|
||
<div class="stat-value <?= count($maintenanceDue) > 0 ? 'text-danger' : '' ?>">
|
||
<?= count($maintenanceDue) ?>
|
||
</div>
|
||
<div class="stat-label">Wartung fällig</div>
|
||
</div>
|
||
|
||
<?php if (!empty($maintenanceDue)): ?>
|
||
<div class="alert alert-warning mt-md mb-none">
|
||
⚠️ Bei <?= count($maintenanceDue) ?> Flugzeug(en) ist die Wartung fällig!
|
||
<a href="<?= BASE_PATH ?>aircraft.php" class="btn btn-sm btn-warning ml-md">Jetzt warten</a>
|
||
</div>
|
||
<?php endif; ?>
|
||
</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>
|