feat(phase2): aircraft market, fleet management, finances, loans

- 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
This commit is contained in:
2026-04-24 17:27:02 +02:00
parent ab5f97acbf
commit 12ddf5a185
9 changed files with 2052 additions and 36 deletions

371
www/public/aircraft.php Normal file
View File

@@ -0,0 +1,371 @@
<?php
/**
* Airline Tycoon - Flotten-Übersicht
* Zeigt alle Flugzeuge des Spielers mit Zustand und Wartungsstatus
*/
require_once __DIR__ . '/../src/bootstrap.php';
// Auth Check
if (!Session::isLoggedIn()) {
header('Location: login.php');
exit;
}
$userId = Session::getUserId();
$user = $db->getUserById($userId);
$aircraft = $db->getUserAircraft($userId);
$config = $db->getConfig();
// Wartungsstatus prüfen
$maintenanceDue = $db->checkMaintenanceDue($userId);
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flotte - <?= htmlspecialchars($user['airline_name']) ?> - <?= $config['app']['name'] ?></title>
<link rel="stylesheet" href="<?= $basePath ?>assets/css/style.css">
<link href="https://fonts.googleapis.com/css2?family=Bangers&family=Patrick+Hand&family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">
</head>
<body data-theme="<?= $theme ?? 'light' ?>">
<!-- Navbar -->
<nav class="navbar">
<div class="navbar-brand">
<a href="<?= $basePath ?>dashboard.php" class="navbar-logo">✈️ <?= $config['app']['name'] ?></a>
</div>
<button class="navbar-toggle" aria-label="Menü">☰</button>
<div class="navbar-menu">
<a href="<?= $basePath ?>dashboard.php" class="navbar-item">Dashboard</a>
<a href="<?= $basePath ?>airports.php" class="navbar-item">Flughäfen</a>
<a href="<?= $basePath ?>aircraft.php" class="navbar-item active">Flotte</a>
<a href="<?= $basePath ?>finances.php" class="navbar-item">Finanzen</a>
<a href="<?= $basePath ?>market.php" class="navbar-item">Markt</a>
<button class="theme-toggle" id="themeToggle" title="Theme wechseln">🌙</button>
</div>
</nav>
<!-- Main Content -->
<main class="container">
<!-- Header -->
<section class="hero hero-sm">
<div class="hero-content">
<h1 class="hero-title">🛫 Deine Flotte</h1>
<p class="hero-subtitle">Verwalte deine Flugzeuge, prüfe den Zustand und buche Wartungen</p>
</div>
</section>
<!-- Wartungshinweis -->
<?php if (!empty($maintenanceDue)): ?>
<div class="alert alert-warning mb-lg">
<div class="alert-icon">⚠️</div>
<div class="alert-content">
<strong>Wartung fällig!</strong>
<p>Bei <?= count($maintenanceDue) ?> Flugzeug(en) ist die Wartung überfällig.</p>
</div>
</div>
<?php endif; ?>
<!-- Statistik-Grid -->
<div class="stats-grid mt-lg">
<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"><?= number_format(array_sum(array_column($aircraft, 'seat_capacity')), 0, ',', '.') ?></div>
<div class="stat-label">Gesamtkapazität</div>
</div>
<div class="stat-card">
<div class="stat-icon">🛡️</div>
<div class="stat-value"><?= count($maintenanceDue) ?></div>
<div class="stat-label">Wartung fällig</div>
</div>
<div class="stat-card">
<div class="stat-icon">📅</div>
<div class="stat-value"><?= $config['game']['maintenance_interval'] ?></div>
<div class="stat-label">Flüge bis Wartung</div>
</div>
</div>
<!-- Flugzeugliste -->
<section class="panel mt-xl">
<div class="panel-header">
<h2 class="panel-title">🛩️ Alle Flugzeuge</h2>
<a href="<?= $basePath ?>market.php" class="btn btn-primary">
Flugzeug kaufen
</a>
</div>
<?php if (empty($aircraft)): ?>
<div class="empty-state">
<div class="empty-icon">✈️</div>
<h3>Keine Flugzeuge vorhanden</h3>
<p>Du besitzt noch keine Flugzeuge. Kaufe dein erstes Flugzeug im Markt!</p>
<a href="<?= $basePath ?>market.php" class="btn btn-primary btn-lg mt-md">Zum Markt</a>
</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>Nächste Wartung</th>
<th>Reichweite</th>
<th>Kapazität</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<?php foreach ($aircraft as $plane): ?>
<?php
$conditionClass = $plane['condition'] >= 80 ? 'text-success' : ($plane['condition'] >= 50 ? 'text-warning' : 'text-danger');
$maintenanceProgress = min(100, ($plane['flights_count'] / $config['game']['maintenance_interval']) * 100);
$needsMaintenance = $plane['flights_count'] >= $config['game']['maintenance_interval'];
?>
<tr>
<td>
<strong><?= htmlspecialchars($plane['name']) ?></strong>
</td>
<td>
<span class="badge"><?= htmlspecialchars($plane['aircraft_type']) ?></span>
</td>
<td>
<div class="d-flex flex-col align-start gap-sm">
<span class="<?= $conditionClass ?>">
<?= number_format($plane['condition'], 1, ',', '.') ?>%
</span>
<div class="progress" style="width: 80px;">
<div class="progress-bar" style="width: <?= $plane['condition'] ?>%"></div>
</div>
</div>
</td>
<td><?= number_format($plane['flights_count'], 0, ',', '.') ?></td>
<td>
<?php if ($needsMaintenance): ?>
<span class="badge badge-danger">⚠️ Jetzt!</span>
<?php else: ?>
<span class="text-secondary">
<?= number_format($config['game']['maintenance_interval'] - $plane['flights_count'], 0, ',', '.') ?> Flüge
</span>
<?php endif; ?>
</td>
<td><?= number_format($plane['range_km'], 0, ',', '.') ?> km</td>
<td><?= number_format($plane['seat_capacity'], 0, ',', '.') ?> Pax</td>
<td>
<div class="d-flex gap-sm">
<?php if ($needsMaintenance): ?>
<button class="btn btn-sm btn-warning"
onclick="performMaintenance(<?= $plane['id'] ?>)"
data-modal="maintenanceModal">
🔧 Wartung
</button>
<?php endif; ?>
<button class="btn btn-sm btn-secondary"
onclick="showAircraftDetails(<?= $plane['id'] ?>)">
📋 Details
</button>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</section>
<!-- Flugzeug-Typen Übersicht -->
<section class="panel mt-xl">
<h2 class="panel-title">📊 Flugzeugtypen</h2>
<div class="aircraft-types-grid mt-md">
<?php foreach ($config['aircraft_types'] as $type => $specs): ?>
<div class="aircraft-type-card">
<h3 class="aircraft-type-name"><?= htmlspecialchars($type) ?></h3>
<div class="aircraft-type-stats">
<div class="aircraft-type-stat">
<span class="stat-label">Preis:</span>
<span class="stat-value"><?= number_format($specs['purchase_price'], 0, ',', '.') ?> €</span>
</div>
<div class="aircraft-type-stat">
<span class="stat-label">Reichweite:</span>
<span class="stat-value"><?= number_format($specs['range_km'], 0, ',', '.') ?> km</span>
</div>
<div class="aircraft-type-stat">
<span class="stat-label">Kapazität:</span>
<span class="stat-value"><?= $specs['seat_capacity'] ?> Pax</span>
</div>
<div class="aircraft-type-stat">
<span class="stat-label">Crew-Kosten:</span>
<span class="stat-value"><?= $specs['crew_cost_per_tick'] ?? 500 ?> €/Tick</span>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</section>
</main>
<!-- Wartungs-Modal -->
<div class="modal-overlay" id="maintenanceModal">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title">🔧 Wartung durchführen</h3>
<button class="modal-close" onclick="closeModal('maintenanceModal')">×</button>
</div>
<div class="modal-body">
<div id="maintenanceInfo">
<p>Flugzeug: <strong id="maintenanceAircraftName"></strong></p>
<p>Zustand: <span id="maintenanceCondition"></span></p>
<p>Geschätzte Kosten: <strong id="maintenanceCost"></strong></p>
</div>
<div class="alert alert-info mt-md">
<p>Nach der Wartung wird der Zustand auf 100% zurückgesetzt und der Flugzähler zurückgesetzt.</p>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal('maintenanceModal')">Abbrechen</button>
<button class="btn btn-warning" id="confirmMaintenanceBtn">Wartung durchführen</button>
</div>
</div>
</div>
<!-- Details-Modal -->
<div class="modal-overlay" id="detailsModal">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title">📋 Flugzeug-Details</h3>
<button class="modal-close" onclick="closeModal('detailsModal')">×</button>
</div>
<div class="modal-body" id="aircraftDetails">
<!-- Dynamisch geladen -->
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal('detailsModal')">Schließen</button>
</div>
</div>
</div>
<!-- Toast Container -->
<div id="toastContainer" class="toast-container"></div>
<script src="<?= $basePath ?>assets/js/main.js"></script>
<script>
let selectedAircraftId = null;
async function performMaintenance(aircraftId) {
selectedAircraftId = aircraftId;
try {
const response = await fetch(`api/game.php?action=aircraft_details&id=${aircraftId}`);
const data = await response.json();
if (data.success) {
document.getElementById('maintenanceAircraftName').textContent = data.aircraft.name;
document.getElementById('maintenanceCondition').textContent = data.aircraft.condition.toFixed(1) + '%';
document.getElementById('maintenanceCost').textContent = formatMoney(data.maintenance_cost);
openModal('maintenanceModal');
}
} catch (error) {
showToast('Fehler beim Laden der Daten', 'error');
}
}
document.getElementById('confirmMaintenanceBtn').addEventListener('click', async () => {
if (!selectedAircraftId) return;
try {
const formData = new FormData();
formData.append('action', 'maintenance');
formData.append('aircraft_id', selectedAircraftId);
const response = await fetch('api/game.php', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
showToast('Wartung erfolgreich durchgeführt!', 'success');
closeModal('maintenanceModal');
setTimeout(() => location.reload(), 1000);
} else {
showToast(data.error || 'Wartung fehlgeschlagen', 'error');
}
} catch (error) {
showToast('Netzwerkfehler', 'error');
}
});
async function showAircraftDetails(aircraftId) {
try {
const response = await fetch(`api/game.php?action=aircraft_details&id=${aircraftId}`);
const data = await response.json();
if (data.success) {
const aircraft = data.aircraft;
const history = data.maintenance_history || [];
let html = `
<div class="aircraft-detail-header">
<h4>${aircraft.name}</h4>
<span class="badge">${aircraft.aircraft_type}</span>
</div>
<div class="aircraft-detail-grid">
<div class="detail-item">
<span class="detail-label">Zustand:</span>
<span class="detail-value ${aircraft.condition >= 80 ? 'text-success' : aircraft.condition >= 50 ? 'text-warning' : 'text-danger'}">
${aircraft.condition.toFixed(1)}%
</span>
</div>
<div class="detail-item">
<span class="detail-label">Flüge gesamt:</span>
<span class="detail-value">${aircraft.flights_count}</span>
</div>
<div class="detail-item">
<span class="detail-label">Reichweite:</span>
<span class="detail-value">${aircraft.range_km.toLocaleString()} km</span>
</div>
<div class="detail-item">
<span class="detail-label">Kapazität:</span>
<span class="detail-value">${aircraft.seat_capacity} Pax</span>
</div>
<div class="detail-item">
<span class="detail-label">Kaufpreis:</span>
<span class="detail-value">${formatMoney(aircraft.purchase_price)}</span>
</div>
<div class="detail-item">
<span class="detail-label">Nächste Wartung in:</span>
<span class="detail-value">${50 - (aircraft.flights_count % 50)} Flügen</span>
</div>
</div>
`;
if (history.length > 0) {
html += `<h4 class="mt-lg">Wartungshistorie</h4><ul class="maintenance-history">`;
history.forEach(h => {
html += `<li>${h.created_at}: ${h.maintenance_type} - ${formatMoney(h.cost)} (${h.condition_before}% → ${h.condition_after}%)</li>`;
});
html += `</ul>`;
}
document.getElementById('aircraftDetails').innerHTML = html;
openModal('detailsModal');
}
} catch (error) {
showToast('Fehler beim Laden der Details', 'error');
}
}
function formatMoney(amount) {
return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(amount);
}
</script>
</body>
</html>