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

373
www/public/airports.php Normal file
View File

@@ -0,0 +1,373 @@
<?php
/**
* Airline Tycoon - Airports & World Map
* Interaktive Weltkarte mit Touch-Support
*/
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);
$user = $db->getUserById($userId);
// Airports laden falls noch nicht in DB
$airports = $db->getAllAirports();
if (empty($airports)) {
$db->loadAirportsFromCsv($config['paths']['data'] . '/airports.csv');
$airports = $db->getAllAirports();
}
$aircraft = $db->getUserAircraft($userId);
$routes = $db->getUserRoutes($userId);
// Route erstellen
$message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'create_route') {
if (!validate_csrf($_POST['csrf_token'] ?? '')) {
$message = 'Ungültige Anfrage.';
} else {
$fromId = (int) ($_POST['from_airport'] ?? 0);
$toId = (int) ($_POST['to_airport'] ?? 0);
if ($fromId > 0 && $toId > 0 && $fromId !== $toId) {
$fromAirport = $db->selectOne("SELECT * FROM airports WHERE id = ?", [$fromId]);
$toAirport = $db->selectOne("SELECT * FROM airports WHERE id = ?", [$toId]);
if ($fromAirport && $toAirport) {
$distance = $db->calculateDistance(
$fromAirport['latitude'], $fromAirport['longitude'],
$toAirport['latitude'], $toAirport['longitude']
);
// Prüfen ob Route bereits existiert
$existing = $db->selectOne(
"SELECT id FROM routes WHERE user_id = ? AND from_airport_id = ? AND to_airport_id = ?",
[$userId, $fromId, $toId]
);
if ($existing) {
$message = 'Diese Route existiert bereits!';
} else {
$db->createRoute($userId, $fromId, $toId, $distance);
$message = 'Route erfolgreich erstellt!';
}
}
} else {
$message = 'Bitte wähle zwei verschiedene Flughäfen.';
}
}
}
?>
<!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 Weltkarte - Alle Flughäfen">
<meta name="csrf-token" content="<?= h(csrf_token()) ?>">
<title>Weltkarte - 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">📊 Dashboard</a></li>
<li><a href="<?= BASE_PATH ?>airports.php" class="navbar-item active">🌍 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>
<button class="theme-toggle" id="themeToggle" aria-label="Design wechseln">🌙</button>
</li>
</ul>
</div>
</nav>
<main class="container p-lg">
<?php if ($message): ?>
<div class="panel mb-lg" style="background: rgba(78, 205, 196, 0.2); border-color: var(--comic-blue);">
<p> <?= h($message) ?></p>
</div>
<?php endif; ?>
<!-- Tabs -->
<div class="d-flex gap-sm mb-lg">
<a href="<?= BASE_PATH ?>airports.php" class="btn <?= !isset($_GET['action']) ? 'btn-primary' : 'btn-outline' ?>">
🗺️ Weltkarte
</a>
<a href="<?= BASE_PATH ?>airports.php?action=fleet" class="btn <?= ($_GET['action'] ?? '') === 'fleet' ? 'btn-primary' : 'btn-outline' ?>">
✈️ Flotte
</a>
<a href="<?= BASE_PATH ?>airports.php?action=routes" class="btn <?= ($_GET['action'] ?? '') === 'routes' ? 'btn-primary' : 'btn-outline' ?>">
🛫 Routen
</a>
</div>
<?php if (!isset($_GET['action']) || $_GET['action'] === 'map'): ?>
<!-- Weltkarte -->
<div class="panel">
<h2 class="panel-header">🗺️ Interaktive Weltkarte</h2>
<p class="text-secondary mb-md">
Klicke oder tippe auf einen Flughafen um eine Route zu erstellen.
</p>
<div class="d-flex gap-lg">
<!-- Karte -->
<div id="mapContainer" style="flex: 2; min-height: 500px; position: relative;">
<!-- Map wird per JS initialisiert -->
</div>
<!-- Flughafen Info -->
<div id="airportInfo" class="card" style="flex: 1; min-width: 250px; display: none;">
<h3>Flughafen-Info</h3>
<p class="text-muted">Wähle einen Flughafen auf der Karte</p>
</div>
</div>
</div>
<!-- Routen erstellen Panel -->
<div class="panel mt-lg">
<h2 class="panel-header">🛫 Neue Route erstellen</h2>
<form method="POST" class="d-flex flex-wrap gap-md mt-md">
<input type="hidden" name="csrf_token" value="<?= h(csrf_token()) ?>">
<input type="hidden" name="action" value="create_route">
<div class="form-group" style="flex: 1; min-width: 200px;">
<label class="form-label" for="fromAirport">Von</label>
<select id="fromAirport" name="from_airport" class="form-select" required>
<option value="">Flughafen wählen...</option>
<?php foreach ($airports as $airport): ?>
<option value="<?= $airport['id'] ?>">
<?= h($airport['iata_code']) ?> - <?= h($airport['city']) ?>, <?= h($airport['country']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="d-flex flex-center" style="padding-top: 24px;">
<span style="font-size: 2rem;">→</span>
</div>
<div class="form-group" style="flex: 1; min-width: 200px;">
<label class="form-label" for="toAirport">Nach</label>
<select id="toAirport" name="to_airport" class="form-select" required>
<option value="">Flughafen wählen...</option>
<?php foreach ($airports as $airport): ?>
<option value="<?= $airport['id'] ?>">
<?= h($airport['iata_code']) ?> - <?= h($airport['city']) ?>, <?= h($airport['country']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="d-flex flex-center" style="padding-top: 24px;">
<button type="submit" class="btn btn-primary">
✈️ Route erstellen
</button>
</div>
</form>
</div>
<!-- Alle Flughäfen Liste -->
<div class="panel mt-lg">
<h2 class="panel-header">📋 Alle Flughäfen (<?= count($airports) ?>)</h2>
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>IATA</th>
<th>Name</th>
<th>Stadt</th>
<th>Land</th>
<th>Kontinent</th>
<th>Passagiere/Jahr</th>
</tr>
</thead>
<tbody>
<?php foreach ($airports as $airport): ?>
<tr onclick="selectAirportFromList(<?= $airport['id'] ?>)" style="cursor: pointer;">
<td><strong><?= h($airport['iata_code']) ?></strong></td>
<td><?= h($airport['name']) ?></td>
<td><?= h($airport['city']) ?></td>
<td><?= h($airport['country']) ?></td>
<td><?= h($airport['continent']) ?></td>
<td><?= formatNumber($airport['passenger_volume']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php elseif ($_GET['action'] === 'fleet'): ?>
<!-- Flottenverwaltung -->
<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>
<a href="<?= BASE_PATH ?>dashboard.php" class="btn btn-primary mt-md">
🛒 Flugzeug kaufen
</a>
</div>
<?php else: ?>
<div class="d-grid" style="grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: var(--space-md);">
<?php foreach ($aircraft as $plane): ?>
<div class="card">
<div class="d-flex gap-md">
<div class="card-icon">✈️</div>
<div>
<h3><?= h($plane['name']) ?></h3>
<p class="text-secondary"><?= h($plane['aircraft_type']) ?></p>
</div>
</div>
<div class="d-grid mt-md" style="grid-template-columns: 1fr 1fr; gap: var(--space-sm);">
<div>
<strong>Sitze:</strong> <?= $plane['seat_capacity'] ?>
</div>
<div>
<strong>Reichweite:</strong> <?= formatNumber($plane['range_km']) ?> km
</div>
<div>
<strong>Zustand:</strong>
<span class="badge <?= $plane['condition'] > 70 ? 'badge-success' : ($plane['condition'] > 30 ? 'badge-warning' : 'badge-danger') ?>">
<?= number_format($plane['condition'], 1, ',', '.') ?>%
</span>
</div>
<div>
<strong>Flüge:</strong> <?= formatNumber($plane['flights_count']) ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<?php elseif ($_GET['action'] === 'routes'): ?>
<!-- Routenverwaltung -->
<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">
🗺️ Route erstellen
</a>
</div>
<?php else: ?>
<div class="d-grid" style="grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: var(--space-md);">
<?php foreach ($routes as $route): ?>
<div class="card">
<div class="d-flex flex-center gap-md">
<div class="text-center">
<strong><?= h($route['from_iata']) ?></strong><br>
<small><?= h($route['from_city']) ?></small>
</div>
<div style="font-size: 1.5rem;">✈️</div>
<div class="text-center">
<strong><?= h($route['to_iata']) ?></strong><br>
<small><?= h($route['to_city']) ?></small>
</div>
</div>
<div class="d-flex flex-between mt-md">
<span>Distanz:</span>
<strong><?= formatNumber($route['distance_km']) ?> km</strong>
</div>
<div class="d-flex flex-between">
<span>Nachfrage:</span>
<div class="progress" style="width: 100px; height: 16px;">
<div class="progress-bar" style="width: <?= $route['demand_level'] ?>%">
<?= $route['demand_level'] ?>%
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
</main>
<!-- Toast Container -->
<div id="toastContainer" class="toast-container"></div>
<script>
const AIRPORTS_DATA = <?= json_encode($airports) ?>;
const ROUTES_DATA = <?= json_encode($routes) ?>;
const AIRCRAFT_DATA = <?= json_encode($aircraft) ?>;
</script>
<script src="<?= BASE_PATH ?>assets/js/main.js"></script>
<script>
// Airport aus Liste auswählen
function selectAirportFromList(airportId) {
const airport = AIRPORTS_DATA.find(a => a.id == airportId);
if (airport && typeof selectAirport === 'function') {
selectAirport(airport);
// Scroll zur Karte
document.querySelector('#mapContainer')?.scrollIntoView({ behavior: 'smooth' });
}
}
// Event Listener für Airport-Auswahl
document.addEventListener('airportSelected', function(e) {
const airport = e.detail;
const infoPanel = document.getElementById('airportInfo');
if (infoPanel && airport) {
infoPanel.style.display = 'block';
infoPanel.innerHTML = `
<h3>✈️ ${airport.iata_code}</h3>
<p><strong>${airport.name}</strong></p>
<p>${airport.city}, ${airport.country}</p>
<hr>
<p><strong>Kontinent:</strong> ${airport.continent}</p>
<p><strong>Passagiere:</strong> ${formatNumber(airport.passenger_volume)}/Jahr</p>
<p><strong>Nachfrage:</strong> ${(airport.demand_multiplier * 100).toFixed(0)}%</p>
<div class="mt-md">
<p><strong>Von hier:</strong></p>
<select class="form-select" onchange="setFromAirport(${airport.id}, this.value)">
<option value="">Route von ${airport.iata_code} nach...</option>
${AIRPORTS_DATA.filter(a => a.id != airport.id).map(a =>
`<option value="${a.id}">${a.iata_code} - ${a.city}</option>`
).join('')}
</select>
</div>
`;
}
});
function setFromAirport(fromId, toId) {
if (toId) {
document.querySelector('select[name="from_airport"]').value = fromId;
document.querySelector('select[name="to_airport"]').value = toId;
}
}
</script>
</body>
</html>