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

439
www/public/finances.php Normal file
View File

@@ -0,0 +1,439 @@
<?php
/**
* Airline Tycoon - Finanz-Übersicht
* Zeigt alle Finanzen, Transaktionen, Kredite und Bilanzen
*/
require_once __DIR__ . '/../src/bootstrap.php';
// Auth Check
if (!Session::isLoggedIn()) {
header('Location: login.php');
exit;
}
$userId = Session::getUserId();
$user = $db->getUserById($userId);
$config = $db->getConfig();
$csrfToken = Session::generateCsrfToken();
// Kredite holen
$loans = $db->getUserLoans($userId);
$totalDebt = $db->getTotalDebt($userId);
$totalDebtWithInterest = $db->getTotalDebtWithInterest($userId);
// Transaktionen holen
$transactions = $db->getUserTransactions($userId, 50);
// Heutige Statistik
$today = date('Y-m-d');
$todayStats = $db->getTransactionSummary($userId, $today, $today);
// Letzte 7 Tage
$weekAgo = date('Y-m-d', strtotime('-7 days'));
$weekStats = $db->getTransactionSummary($userId, $weekAgo, $today);
// Letzte 30 Tage
$monthAgo = date('Y-m-d', strtotime('-30 days'));
$monthStats = $db->getTransactionSummary($userId, $monthAgo, $today);
// Wartungshistorie
$maintenanceHistory = $db->getMaintenanceHistory($userId, 10);
// Net Worth (Balance - Schulden)
$netWorth = $user['balance'] - $totalDebtWithInterest;
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Finanzen - <?= 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">Flotte</a>
<a href="<?= $basePath ?>finances.php" class="navbar-item active">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">💰 Finanzen</h1>
<p class="hero-subtitle">Deine Einnahmen, Ausgaben und Kredite im Überblick</p>
</div>
</section>
<!-- Kontostand & Net Worth -->
<div class="stats-grid mt-lg">
<div class="stat-card">
<div class="stat-icon">💵</div>
<div class="stat-value <?= $user['balance'] >= 0 ? 'text-success' : 'text-danger' ?>">
<?= number_format($user['balance'], 0, ',', '.') ?> €
</div>
<div class="stat-label">Kontostand</div>
</div>
<div class="stat-card">
<div class="stat-icon">📊</div>
<div class="stat-value <?= $netWorth >= 0 ? 'text-success' : 'text-danger' ?>">
<?= number_format($netWorth, 0, ',', '.') ?> €
</div>
<div class="stat-label">Net Worth</div>
</div>
<div class="stat-card">
<div class="stat-icon">🏦</div>
<div class="stat-value <?= $totalDebt > 0 ? 'text-warning' : 'text-success' ?>">
<?= number_format($totalDebtWithInterest, 0, ',', '.') ?> €
</div>
<div class="stat-label">Kredit-Schuld</div>
</div>
<div class="stat-card">
<div class="stat-icon">📈</div>
<div class="stat-value">
<?= $config['loans']['interest_rate'] * 100 ?>%
</div>
<div class="stat-label">Kredit-Zins/Ticket</div>
</div>
</div>
<!-- Zeitraum-Statistiken -->
<section class="panel mt-xl">
<h2 class="panel-title">📅 Bilanz nach Zeitraum</h2>
<div class="period-stats mt-md">
<div class="period-card">
<h3>Heute</h3>
<div class="period-amounts">
<div class="amount-row">
<span>Einnahmen:</span>
<span class="text-success">+<?= number_format($todayStats['total_income'] ?? 0, 2, ',', '.') ?> €</span>
</div>
<div class="amount-row">
<span>Ausgaben:</span>
<span class="text-danger">-<?= number_format($todayStats['total_expenses'] ?? 0, 2, ',', '.') ?> €</span>
</div>
<div class="amount-row total">
<span>Saldo:</span>
<span class="<?= ($todayStats['total_income'] - $todayStats['total_expenses']) >= 0 ? 'text-success' : 'text-danger' ?>">
<?= number_format(($todayStats['total_income'] ?? 0) - ($todayStats['total_expenses'] ?? 0), 2, ',', '.') ?> €
</span>
</div>
</div>
</div>
<div class="period-card">
<h3>Letzte 7 Tage</h3>
<div class="period-amounts">
<div class="amount-row">
<span>Einnahmen:</span>
<span class="text-success">+<?= number_format($weekStats['total_income'] ?? 0, 2, ',', '.') ?> €</span>
</div>
<div class="amount-row">
<span>Ausgaben:</span>
<span class="text-danger">-<?= number_format($weekStats['total_expenses'] ?? 0, 2, ',', '.') ?> €</span>
</div>
<div class="amount-row total">
<span>Saldo:</span>
<span class="<?= ($weekStats['total_income'] - $weekStats['total_expenses']) >= 0 ? 'text-success' : 'text-danger' ?>">
<?= number_format(($weekStats['total_income'] ?? 0) - ($weekStats['total_expenses'] ?? 0), 2, ',', '.') ?> €
</span>
</div>
</div>
</div>
<div class="period-card">
<h3>Letzte 30 Tage</h3>
<div class="period-amounts">
<div class="amount-row">
<span>Einnahmen:</span>
<span class="text-success">+<?= number_format($monthStats['total_income'] ?? 0, 2, ',', '.') ?> €</span>
</div>
<div class="amount-row">
<span>Ausgaben:</span>
<span class="text-danger">-<?= number_format($monthStats['total_expenses'] ?? 0, 2, ',', '.') ?> €</span>
</div>
<div class="amount-row total">
<span>Saldo:</span>
<span class="<?= ($monthStats['total_income'] - $monthStats['total_expenses']) >= 0 ? 'text-success' : 'text-danger' ?>">
<?= number_format(($monthStats['total_income'] ?? 0) - ($monthStats['total_expenses'] ?? 0), 2, ',', '.') ?> €
</span>
</div>
</div>
</div>
</div>
</section>
<!-- Kredite -->
<section class="panel mt-xl">
<div class="panel-header">
<h2 class="panel-title">🏦 Kredite</h2>
<button class="btn btn-primary" onclick="openModal('takeLoanModal')">
Kredit aufnehmen
</button>
</div>
<?php if (empty($loans)): ?>
<div class="empty-state">
<div class="empty-icon">🏦</div>
<h3>Keine Kredite</h3>
<p>Du hast keine Kredite offen. Nimm einen Kredit auf, um dein Geschäft zu finanzieren!</p>
</div>
<?php else: ?>
<div class="table-container mt-md">
<table class="table">
<thead>
<tr>
<th>Datum</th>
<th>Originalbetrag</th>
<th>Aktueller Betrag</th>
<th>Tageszins</th>
<th>Tage offen</th>
<th>Gesamtzins</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
<?php foreach ($loans as $loan):
$totalInterest = $loan['daily_interest'] * $loan['days_outstanding'];
$totalOwed = $loan['current_amount'] + $totalInterest;
?>
<tr>
<td><?= date('d.m.Y', strtotime($loan['created_at'])) ?></td>
<td><?= number_format($loan['principal'], 2, ',', '.') ?> €</td>
<td><?= number_format($loan['current_amount'], 2, ',', '.') ?> €</td>
<td><?= number_format($loan['daily_interest'], 2, ',', '.') ?> €</td>
<td><?= $loan['days_outstanding'] ?></td>
<td><?= number_format($totalInterest, 2, ',', '.') ?> €</td>
<td>
<button class="btn btn-sm btn-warning"
onclick="repayLoan(<?= $loan['id'] ?>, <?= $totalOwed ?>)">
💵 Zurückzahlen
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<div class="loan-info panel mt-lg">
<h3>📖 Kredit-Informationen</h3>
<ul>
<li>Maximaler Kreditbetrag: <?= number_format($config['loans']['max_amount'], 0, ',', '.') ?> €</li>
<li>Zinssatz: <?= $config['loans']['interest_rate'] * 100 ?>% pro Spieltag</li>
<li>Mindestkreditbetrag: <?= number_format($config['loans']['min_amount'], 0, ',', '.') ?> €</li>
</ul>
</div>
</section>
<!-- Letzte Transaktionen -->
<section class="panel mt-xl">
<h2 class="panel-title">📜 Letzte Transaktionen</h2>
<?php if (empty($transactions)): ?>
<div class="empty-state">
<div class="empty-icon">📋</div>
<h3>Keine Transaktionen</h3>
<p>Es gibt noch keine Transaktionen.</p>
</div>
<?php else: ?>
<div class="table-container mt-md">
<table class="table">
<thead>
<tr>
<th>Datum</th>
<th>Typ</th>
<th>Beschreibung</th>
<th>Betrag</th>
<th>Saldo</th>
</tr>
</thead>
<tbody>
<?php foreach ($transactions as $tx): ?>
<tr>
<td><?= date('d.m.Y H:i', strtotime($tx['created_at'])) ?></td>
<td>
<?php
$typeColors = [
'flight' => 'badge-primary',
'fuel' => 'badge-warning',
'maintenance' => 'badge-danger',
'aircraft_purchase' => 'badge-info',
'loan' => 'badge-secondary',
'loan_repayment' => 'badge-warning'
];
$badgeClass = $typeColors[$tx['type']] ?? 'badge-secondary';
?>
<span class="badge <?= $badgeClass ?>"><?= htmlspecialchars($tx['type']) ?></span>
</td>
<td><?= htmlspecialchars($tx['description']) ?></td>
<td class="<?= $tx['amount'] >= 0 ? 'text-success' : 'text-danger' ?>">
<?= number_format($tx['amount'], 2, ',', '.') ?> €
</td>
<td><?= number_format($tx['balance_after'], 2, ',', '.') ?> €</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</section>
<!-- Wartungshistorie -->
<?php if (!empty($maintenanceHistory)): ?>
<section class="panel mt-xl">
<h2 class="panel-title">🔧 Wartungshistorie</h2>
<div class="table-container mt-md">
<table class="table">
<thead>
<tr>
<th>Datum</th>
<th>Flugzeug</th>
<th>Typ</th>
<th>Zustand vorher</th>
<th>Zustand nachher</th>
<th>Kosten</th>
</tr>
</thead>
<tbody>
<?php foreach ($maintenanceHistory as $m): ?>
<tr>
<td><?= date('d.m.Y', strtotime($m['created_at'])) ?></td>
<td><?= htmlspecialchars($m['aircraft_name']) ?></td>
<td><span class="badge"><?= htmlspecialchars($m['maintenance_type']) ?></span></td>
<td><?= number_format($m['condition_before'], 1, ',', '.') ?>%</td>
<td><?= number_format($m['condition_after'], 1, ',', '.') ?>%</td>
<td class="text-danger">-<?= number_format($m['cost'], 2, ',', '.') ?> €</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</section>
<?php endif; ?>
</main>
<!-- Kredit aufnehmen Modal -->
<div class="modal-overlay" id="takeLoanModal">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title">🏦 Kredit aufnehmen</h3>
<button class="modal-close" onclick="closeModal('takeLoanModal')">×</button>
</div>
<form action="api/game.php" method="POST" data-ajax>
<input type="hidden" name="action" value="take_loan">
<input type="hidden" name="csrf_token" value="<?= $csrfToken ?>">
<div class="modal-body">
<div class="form-group">
<label class="form-label">Kreditbetrag (€)</label>
<input type="number" name="amount" class="form-input"
min="<?= $config['loans']['min_amount'] ?>"
max="<?= $config['loans']['max_amount'] - $totalDebt ?>"
step="10000"
placeholder="z.B. 1000000" required>
<small class="form-hint">
Min: <?= number_format($config['loans']['min_amount'], 0, ',', '.') ?> € |
Max: <?= number_format($config['loans']['max_amount'] - $totalDebt, 0, ',', '.') ?> € verfügbar
</small>
</div>
<div class="alert alert-info mt-md">
<p><strong>Zinssatz:</strong> <?= $config['loans']['interest_rate'] * 100 ?>% pro Spieltag</p>
<p><strong>Tageszins für max. Kredit:</strong> <?= number_format(($config['loans']['max_amount'] - $totalDebt) * $config['loans']['interest_rate'], 2, ',', '.') ?> €</p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeModal('takeLoanModal')">Abbrechen</button>
<button type="submit" class="btn btn-primary">Kredit aufnehmen</button>
</div>
</form>
</div>
</div>
<!-- Kredit zurückzahlen Modal -->
<div class="modal-overlay" id="repayLoanModal">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title">💵 Kredit zurückzahlen</h3>
<button class="modal-close" onclick="closeModal('repayLoanModal')">×</button>
</div>
<form action="api/game.php" method="POST" data-ajax>
<input type="hidden" name="action" value="repay_loan">
<input type="hidden" name="csrf_token" value="<?= $csrfToken ?>">
<input type="hidden" name="loan_id" id="repayLoanId">
<div class="modal-body">
<p>Gesamtbetrag inkl. Zinsen: <strong id="repayTotalAmount"></strong></p>
<div class="form-group mt-md">
<label class="form-label">Zurückzahlungsbetrag (€)</label>
<input type="number" name="amount" class="form-input"
min="100" step="1000"
id="repayAmountInput" required>
<small class="form-hint">Du kannst auch nur einen Teil zurückzahlen.</small>
</div>
<div class="alert alert-warning mt-md">
<p>💡 Du kannst den Kredit jederzeit (teilweise oder vollständig) zurückzahlen.</p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeModal('repayLoanModal')">Abbrechen</button>
<button type="submit" class="btn btn-warning">Zurückzahlen</button>
</div>
</form>
</div>
</div>
<!-- Toast Container -->
<div id="toastContainer" class="toast-container"></div>
<script src="<?= $basePath ?>assets/js/main.js"></script>
<script>
let currentLoanId = null;
let currentTotalOwed = 0;
function repayLoan(loanId, totalOwed) {
currentLoanId = loanId;
currentTotalOwed = totalOwed;
document.getElementById('repayLoanId').value = loanId;
document.getElementById('repayTotalAmount').textContent =
new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(totalOwed);
document.getElementById('repayAmountInput').max = totalOwed;
document.getElementById('repayAmountInput').value = totalOwed;
openModal('repayLoanModal');
}
// Erfolg-Callbacks
window.loanSuccess = function(data) {
showToast('Kredit über ' + formatMoney(data.amount) + ' aufgenommen!', 'success');
setTimeout(() => location.reload(), 1500);
};
window.repaySuccess = function(data) {
showToast('Kredit zurückgezahlt: ' + formatMoney(data.repaid), 'success');
setTimeout(() => location.reload(), 1500);
};
function formatMoney(amount) {
return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(amount);
}
</script>
</body>
</html>