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:
@@ -8,7 +8,7 @@ CREATE TABLE IF NOT EXISTS users (
|
|||||||
email VARCHAR(100) UNIQUE NOT NULL,
|
email VARCHAR(100) UNIQUE NOT NULL,
|
||||||
password_hash VARCHAR(255) NOT NULL,
|
password_hash VARCHAR(255) NOT NULL,
|
||||||
airline_name VARCHAR(100) DEFAULT '',
|
airline_name VARCHAR(100) DEFAULT '',
|
||||||
balance DECIMAL(15,2) DEFAULT 500000.00,
|
balance DECIMAL(15,2) DEFAULT 10000000.00,
|
||||||
level INTEGER DEFAULT 1,
|
level INTEGER DEFAULT 1,
|
||||||
experience INTEGER DEFAULT 0,
|
experience INTEGER DEFAULT 0,
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
@@ -92,10 +92,55 @@ CREATE TABLE IF NOT EXISTS daily_stats (
|
|||||||
total_revenue DECIMAL(15,2) DEFAULT 0.00,
|
total_revenue DECIMAL(15,2) DEFAULT 0.00,
|
||||||
total_profit DECIMAL(15,2) DEFAULT 0.00,
|
total_profit DECIMAL(15,2) DEFAULT 0.00,
|
||||||
fuel_cost DECIMAL(15,2) DEFAULT 0.00,
|
fuel_cost DECIMAL(15,2) DEFAULT 0.00,
|
||||||
|
maintenance_cost DECIMAL(15,2) DEFAULT 0.00,
|
||||||
UNIQUE(user_id, date),
|
UNIQUE(user_id, date),
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Kredite
|
||||||
|
CREATE TABLE IF NOT EXISTS loans (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
principal DECIMAL(15,2) NOT NULL,
|
||||||
|
current_amount DECIMAL(15,2) NOT NULL,
|
||||||
|
interest_rate DECIMAL(5,4) DEFAULT 0.02,
|
||||||
|
daily_interest DECIMAL(15,2) DEFAULT 0.00,
|
||||||
|
days_outstanding INTEGER DEFAULT 0,
|
||||||
|
status VARCHAR(20) DEFAULT 'active',
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
due_date DATE,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Transaktionen (alle Buchungen)
|
||||||
|
CREATE TABLE IF NOT EXISTS transactions (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
type VARCHAR(50) NOT NULL,
|
||||||
|
category VARCHAR(50) NOT NULL,
|
||||||
|
amount DECIMAL(15,2) NOT NULL,
|
||||||
|
balance_after DECIMAL(15,2) NOT NULL,
|
||||||
|
description TEXT DEFAULT '',
|
||||||
|
reference_id INTEGER,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Wartungshistorie
|
||||||
|
CREATE TABLE IF NOT EXISTS maintenance_history (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
aircraft_id INTEGER NOT NULL,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
maintenance_type VARCHAR(50) DEFAULT 'scheduled',
|
||||||
|
cost DECIMAL(15,2) NOT NULL,
|
||||||
|
condition_before DECIMAL(5,2) DEFAULT 100.00,
|
||||||
|
condition_after DECIMAL(5,2) DEFAULT 100.00,
|
||||||
|
notes TEXT DEFAULT '',
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (aircraft_id) REFERENCES aircraft(id),
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
);
|
||||||
|
|
||||||
-- Events (Werbeaktionen, Wettbewerbe)
|
-- Events (Werbeaktionen, Wettbewerbe)
|
||||||
CREATE TABLE IF NOT EXISTS events (
|
CREATE TABLE IF NOT EXISTS events (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
@@ -114,3 +159,27 @@ CREATE INDEX IF NOT EXISTS idx_flights_user ON flights(user_id);
|
|||||||
CREATE INDEX IF NOT EXISTS idx_flights_route ON flights(route_id);
|
CREATE INDEX IF NOT EXISTS idx_flights_route ON flights(route_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_aircraft_user ON aircraft(user_id);
|
CREATE INDEX IF NOT EXISTS idx_aircraft_user ON aircraft(user_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_airports_iata ON airports(iata_code);
|
CREATE INDEX IF NOT EXISTS idx_airports_iata ON airports(iata_code);
|
||||||
|
|
||||||
|
-- Flugzeugtypen (Referenztabelle)
|
||||||
|
CREATE TABLE IF NOT EXISTS aircraft_types (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
code VARCHAR(20) UNIQUE NOT NULL,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
manufacturer VARCHAR(50) NOT NULL,
|
||||||
|
range_km INTEGER NOT NULL,
|
||||||
|
capacity INTEGER NOT NULL,
|
||||||
|
buy_price DECIMAL(15,2) NOT NULL,
|
||||||
|
crew_cost_per_tick DECIMAL(10,2) NOT NULL,
|
||||||
|
fuel_per_km DECIMAL(6,4) DEFAULT 0.1500,
|
||||||
|
maintenance_interval INTEGER DEFAULT 50,
|
||||||
|
description TEXT DEFAULT '',
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO aircraft_types (code, name, manufacturer, range_km, capacity, buy_price, crew_cost_per_tick, fuel_per_km, maintenance_interval, description) VALUES
|
||||||
|
('dash8-100', 'Dash 8-100', 'Bombardier', 1500, 37, 500000.00, 200.00, 0.12, 50, 'Kleiner RegionalturboProp mit 37 Sitzen'),
|
||||||
|
('atr72-500', 'ATR 72-500', 'ATR', 1650, 70, 1500000.00, 300.00, 0.15, 50, 'Mittlerer RegionalturboProp mit 70 Sitzen'),
|
||||||
|
('a320-200', 'A320-200', 'Airbus', 5700, 150, 8000000.00, 600.00, 0.25, 50, 'Kurz-/Mittelstrecken-Jet mit 150 Sitzen'),
|
||||||
|
('b737-800', 'B737-800', 'Boeing', 5700, 162, 9500000.00, 650.00, 0.26, 50, 'Kurz-/Mittelstrecken-Jet mit 162 Sitzen'),
|
||||||
|
('b777-200', 'B777-200', 'Boeing', 12000, 300, 35000000.00, 1200.00, 0.55, 50, 'Langstrecken-Widebody mit 300 Sitzen'),
|
||||||
|
('a380-800', 'A380-800', 'Airbus', 15200, 525, 85000000.00, 2000.00, 0.80, 50, 'Doppelstöckiger Langstrecken-Airliner mit 525 Sitzen');
|
||||||
|
|||||||
371
www/public/aircraft.php
Normal file
371
www/public/aircraft.php
Normal 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>
|
||||||
@@ -49,6 +49,37 @@ switch ($action) {
|
|||||||
handleLeaderboard();
|
handleLeaderboard();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// NEU: Kredite
|
||||||
|
case 'take_loan':
|
||||||
|
handleTakeLoan();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'repay_loan':
|
||||||
|
handleRepayLoan();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'loans':
|
||||||
|
handleLoans();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// NEU: Wartung
|
||||||
|
case 'maintenance':
|
||||||
|
handleMaintenance();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'aircraft_details':
|
||||||
|
handleAircraftDetails();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// NEU: Finanzen
|
||||||
|
case 'transactions':
|
||||||
|
handleTransactions();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'finance_summary':
|
||||||
|
handleFinanceSummary();
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
jsonResponse(['error' => 'Unbekannte Aktion'], 400);
|
jsonResponse(['error' => 'Unbekannte Aktion'], 400);
|
||||||
}
|
}
|
||||||
@@ -329,3 +360,216 @@ function handleLeaderboard() {
|
|||||||
'count' => count($leaders)
|
'count' => count($leaders)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// KREDIT HANDLERS
|
||||||
|
// ============================================
|
||||||
|
function handleTakeLoan() {
|
||||||
|
global $db, $config;
|
||||||
|
|
||||||
|
$userId = Session::getUserId();
|
||||||
|
|
||||||
|
// CSRF Check
|
||||||
|
if (!Session::validateCsrfToken($_POST['csrf_token'] ?? '')) {
|
||||||
|
jsonResponse(['error' => 'Ungültiges CSRF-Token'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$amount = (float) ($_POST['amount'] ?? 0);
|
||||||
|
|
||||||
|
if ($amount <= 0) {
|
||||||
|
jsonResponse(['error' => 'Ungültiger Betrag'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$loanId = $db->createLoan($userId, $amount);
|
||||||
|
|
||||||
|
// Update user balance
|
||||||
|
$user = $db->getUserById($userId);
|
||||||
|
|
||||||
|
jsonResponse([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Kredit über ' . number_format($amount, 0, ',', '.') . ' € aufgenommen!',
|
||||||
|
'loan_id' => $loanId,
|
||||||
|
'amount' => $amount,
|
||||||
|
'new_balance' => $user['balance'] + $amount,
|
||||||
|
'interest_rate' => $config['loans']['interest_rate'],
|
||||||
|
'daily_interest' => $amount * $config['loans']['interest_rate']
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
jsonResponse(['error' => $e->getMessage()], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRepayLoan() {
|
||||||
|
global $db;
|
||||||
|
|
||||||
|
$userId = Session::getUserId();
|
||||||
|
|
||||||
|
// CSRF Check
|
||||||
|
if (!Session::validateCsrfToken($_POST['csrf_token'] ?? '')) {
|
||||||
|
jsonResponse(['error' => 'Ungültiges CSRF-Token'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$loanId = (int) ($_POST['loan_id'] ?? 0);
|
||||||
|
$amount = (float) ($_POST['amount'] ?? 0);
|
||||||
|
|
||||||
|
if ($loanId <= 0 || $amount <= 0) {
|
||||||
|
jsonResponse(['error' => 'Ungültige Parameter'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = $db->repayLoan($userId, $loanId, $amount);
|
||||||
|
|
||||||
|
jsonResponse([
|
||||||
|
'success' => true,
|
||||||
|
'message' => number_format($result['repaid'], 2, ',', '.') . ' € zurückgezahlt!',
|
||||||
|
'repaid' => $result['repaid'],
|
||||||
|
'remaining' => $result['remaining'],
|
||||||
|
'new_balance' => $result['new_balance']
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
jsonResponse(['error' => $e->getMessage()], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLoans() {
|
||||||
|
global $db;
|
||||||
|
|
||||||
|
$userId = Session::getUserId();
|
||||||
|
$loans = $db->getUserLoans($userId);
|
||||||
|
$totalDebt = $db->getTotalDebt($userId);
|
||||||
|
$totalDebtWithInterest = $db->getTotalDebtWithInterest($userId);
|
||||||
|
|
||||||
|
jsonResponse([
|
||||||
|
'success' => true,
|
||||||
|
'loans' => $loans,
|
||||||
|
'total_debt' => $totalDebt,
|
||||||
|
'total_debt_with_interest' => $totalDebtWithInterest,
|
||||||
|
'count' => count($loans)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// WARTUNG HANDLERS
|
||||||
|
// ============================================
|
||||||
|
function handleMaintenance() {
|
||||||
|
global $db, $config;
|
||||||
|
|
||||||
|
$userId = Session::getUserId();
|
||||||
|
|
||||||
|
// CSRF Check
|
||||||
|
if (!Session::validateCsrfToken($_POST['csrf_token'] ?? '')) {
|
||||||
|
jsonResponse(['error' => 'Ungültiges CSRF-Token'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$aircraftId = (int) ($_POST['aircraft_id'] ?? 0);
|
||||||
|
|
||||||
|
if ($aircraftId <= 0) {
|
||||||
|
jsonResponse(['error' => 'Flugzeug-ID erforderlich'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = $db->performMaintenance($userId, $aircraftId);
|
||||||
|
|
||||||
|
jsonResponse([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Wartung erfolgreich durchgeführt!',
|
||||||
|
'cost' => $result['cost'],
|
||||||
|
'condition_before' => $result['condition_before'],
|
||||||
|
'condition_after' => $result['condition_after'],
|
||||||
|
'new_balance' => $result['new_balance']
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
jsonResponse(['error' => $e->getMessage()], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAircraftDetails() {
|
||||||
|
global $db;
|
||||||
|
|
||||||
|
$userId = Session::getUserId();
|
||||||
|
$aircraftId = (int) ($_GET['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($aircraftId <= 0) {
|
||||||
|
jsonResponse(['error' => 'Flugzeug-ID erforderlich'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$aircraft = $db->selectOne(
|
||||||
|
"SELECT * FROM aircraft WHERE id = ? AND user_id = ?",
|
||||||
|
[$aircraftId, $userId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$aircraft) {
|
||||||
|
jsonResponse(['error' => 'Flugzeug nicht gefunden'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get maintenance history for this aircraft
|
||||||
|
$maintenanceHistory = $db->select(
|
||||||
|
"SELECT * FROM maintenance_history WHERE aircraft_id = ? ORDER BY created_at DESC LIMIT 10",
|
||||||
|
[$aircraftId]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get aircraft config for maintenance cost estimate
|
||||||
|
$config = $db->getConfig();
|
||||||
|
$aircraftConfig = $config['aircraft_types'][$aircraft['aircraft_type']] ?? [];
|
||||||
|
$maintenanceCost = $aircraftConfig['maintenance_base_cost'] ?? 50000;
|
||||||
|
|
||||||
|
// Adjust for condition
|
||||||
|
$conditionLoss = 100 - $aircraft['condition'];
|
||||||
|
$estimatedCost = $maintenanceCost + ($conditionLoss * 0.01 * $maintenanceCost);
|
||||||
|
|
||||||
|
jsonResponse([
|
||||||
|
'success' => true,
|
||||||
|
'aircraft' => $aircraft,
|
||||||
|
'maintenance_history' => $maintenanceHistory,
|
||||||
|
'maintenance_cost' => $estimatedCost,
|
||||||
|
'maintenance_interval' => $config['game']['maintenance_interval']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// FINANZEN HANDLERS
|
||||||
|
// ============================================
|
||||||
|
function handleTransactions() {
|
||||||
|
global $db;
|
||||||
|
|
||||||
|
$userId = Session::getUserId();
|
||||||
|
$limit = min(100, (int) ($_GET['limit'] ?? 50));
|
||||||
|
|
||||||
|
$transactions = $db->getUserTransactions($userId, $limit);
|
||||||
|
|
||||||
|
jsonResponse([
|
||||||
|
'success' => true,
|
||||||
|
'transactions' => $transactions,
|
||||||
|
'count' => count($transactions)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFinanceSummary() {
|
||||||
|
global $db;
|
||||||
|
|
||||||
|
$userId = Session::getUserId();
|
||||||
|
$user = $db->getUserById($userId);
|
||||||
|
|
||||||
|
$today = date('Y-m-d');
|
||||||
|
$weekAgo = date('Y-m-d', strtotime('-7 days'));
|
||||||
|
$monthAgo = date('Y-m-d', strtotime('-30 days'));
|
||||||
|
|
||||||
|
$todayStats = $db->getTransactionSummary($userId, $today, $today);
|
||||||
|
$weekStats = $db->getTransactionSummary($userId, $weekAgo, $today);
|
||||||
|
$monthStats = $db->getTransactionSummary($userId, $monthAgo, $today);
|
||||||
|
|
||||||
|
$loans = $db->getUserLoans($userId);
|
||||||
|
$totalDebt = $db->getTotalDebtWithInterest($userId);
|
||||||
|
|
||||||
|
jsonResponse([
|
||||||
|
'success' => true,
|
||||||
|
'balance' => $user['balance'],
|
||||||
|
'net_worth' => $user['balance'] - $totalDebt,
|
||||||
|
'total_debt' => $totalDebt,
|
||||||
|
'today' => $todayStats,
|
||||||
|
'week' => $weekStats,
|
||||||
|
'month' => $monthStats,
|
||||||
|
'active_loans' => count($loans)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1048,3 +1048,373 @@ a:hover {
|
|||||||
height: 48px;
|
height: 48px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
PHASE 2: AIRCRAFT, MARKET & FINANCES STYLES
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
/* Aircraft Market Grid */
|
||||||
|
.aircraft-market-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
|
gap: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-aircraft-card {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 3px solid var(--text-primary);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
padding: var(--space-lg);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
transition: transform var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-aircraft-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-aircraft-card.disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
filter: grayscale(30%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-aircraft-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
padding-bottom: var(--space-md);
|
||||||
|
border-bottom: 2px dashed var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-aircraft-name {
|
||||||
|
font-family: var(--font-primary);
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-aircraft-price {
|
||||||
|
font-family: var(--font-primary);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-aircraft-specs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spec-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: var(--space-xs) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spec-label {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spec-value {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cannot-afford {
|
||||||
|
text-align: center;
|
||||||
|
padding: var(--space-md);
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Aircraft Types Grid */
|
||||||
|
.aircraft-types-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||||
|
gap: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.aircraft-type-card {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.aircraft-type-name {
|
||||||
|
font-family: var(--font-primary);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-bottom: var(--space-sm);
|
||||||
|
padding-bottom: var(--space-sm);
|
||||||
|
border-bottom: 1px dashed var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.aircraft-type-stats {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.aircraft-type-stat {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Balance Banner */
|
||||||
|
.balance-banner {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--accent-gradient);
|
||||||
|
color: white;
|
||||||
|
padding: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-value {
|
||||||
|
font-family: var(--font-primary);
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Period Stats */
|
||||||
|
.period-stats {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-card {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-card h3 {
|
||||||
|
font-family: var(--font-primary);
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
padding-bottom: var(--space-sm);
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-amounts {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-row.total {
|
||||||
|
font-weight: bold;
|
||||||
|
padding-top: var(--space-sm);
|
||||||
|
border-top: 1px dashed var(--border-color);
|
||||||
|
margin-top: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loan Info */
|
||||||
|
.loan-info {
|
||||||
|
background: var(--info-bg);
|
||||||
|
border: 2px solid var(--accent-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loan-info h3 {
|
||||||
|
font-family: var(--font-primary);
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loan-info ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loan-info li {
|
||||||
|
padding: var(--space-xs) 0;
|
||||||
|
border-bottom: 1px dashed var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loan-info li:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Aircraft Detail Modal */
|
||||||
|
.aircraft-detail-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
padding-bottom: var(--space-md);
|
||||||
|
border-bottom: 2px dashed var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.aircraft-detail-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maintenance-history {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: var(--space-md) 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maintenance-history li {
|
||||||
|
padding: var(--space-sm);
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
margin-bottom: var(--space-sm);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Info Grid */
|
||||||
|
.info-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||||
|
gap: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card h3 {
|
||||||
|
font-family: var(--font-primary);
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-list {
|
||||||
|
list-style: disc;
|
||||||
|
padding-left: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-list li {
|
||||||
|
padding: var(--space-xs) 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Maintenance Progress */
|
||||||
|
.maintenance-progress {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.maintenance-bar {
|
||||||
|
flex: 1;
|
||||||
|
height: 8px;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maintenance-bar-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--comic-green);
|
||||||
|
transition: width var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.maintenance-bar-fill.warning {
|
||||||
|
background: var(--comic-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.maintenance-bar-fill.danger {
|
||||||
|
background: var(--comic-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* D-flex utilities */
|
||||||
|
.d-flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-col {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-start {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-sm {
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-md {
|
||||||
|
gap: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-100 {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive table container */
|
||||||
|
.table-container {
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Aircraft condition colors */
|
||||||
|
.condition-excellent {
|
||||||
|
color: var(--comic-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition-good {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition-warning {
|
||||||
|
color: var(--comic-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition-danger {
|
||||||
|
color: var(--comic-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Aircraft table */
|
||||||
|
.aircraft-table td {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ $todayStats = $db->getTodayStats($userId);
|
|||||||
$aircraft = $db->getUserAircraft($userId);
|
$aircraft = $db->getUserAircraft($userId);
|
||||||
$routes = $db->getUserRoutes($userId);
|
$routes = $db->getUserRoutes($userId);
|
||||||
|
|
||||||
|
// Kredite und Finanzen
|
||||||
|
$totalDebt = $db->getTotalDebtWithInterest($userId);
|
||||||
|
$maintenanceDue = $db->checkMaintenanceDue($userId);
|
||||||
|
|
||||||
// Level-Fortschritt berechnen
|
// Level-Fortschritt berechnen
|
||||||
$expForNextLevel = $config['game']['level_up_threshold'] * pow($config['game']['level_multiplier'], $user['level'] - 1);
|
$expForNextLevel = $config['game']['level_up_threshold'] * pow($config['game']['level_multiplier'], $user['level'] - 1);
|
||||||
$levelProgress = ($user['experience'] / $expForNextLevel) * 100;
|
$levelProgress = ($user['experience'] / $expForNextLevel) * 100;
|
||||||
@@ -47,9 +51,10 @@ $levelProgress = ($user['experience'] / $expForNextLevel) * 100;
|
|||||||
<button class="navbar-toggle" aria-label="Menü">☰</button>
|
<button class="navbar-toggle" aria-label="Menü">☰</button>
|
||||||
<ul class="navbar-menu">
|
<ul class="navbar-menu">
|
||||||
<li><a href="<?= BASE_PATH ?>dashboard.php" class="navbar-item active">📊 Dashboard</a></li>
|
<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" class="navbar-item">🌍 Flughäfen</a></li>
|
||||||
<li><a href="<?= BASE_PATH ?>airports.php?action=fleet" class="navbar-item">✈️ Flotte</a></li>
|
<li><a href="<?= BASE_PATH ?>aircraft.php" class="navbar-item">✈️ Flotte</a></li>
|
||||||
<li><a href="<?= BASE_PATH ?>airports.php?action=routes" class="navbar-item">🛫 Routen</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>
|
<li>
|
||||||
<span class="navbar-item">
|
<span class="navbar-item">
|
||||||
💰 <?= formatMoney($user['balance']) ?>
|
💰 <?= formatMoney($user['balance']) ?>
|
||||||
@@ -124,6 +129,29 @@ $levelProgress = ($user['experience'] / $expForNextLevel) * 100;
|
|||||||
<div class="stat-value"><?= formatNumber($stats['total_flights'] ?? 0) ?></div>
|
<div class="stat-value"><?= formatNumber($stats['total_flights'] ?? 0) ?></div>
|
||||||
<div class="stat-label">Flüge (gesamt)</div>
|
<div class="stat-label">Flüge (gesamt)</div>
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
<!-- Quick Actions -->
|
<!-- Quick Actions -->
|
||||||
|
|||||||
439
www/public/finances.php
Normal file
439
www/public/finances.php
Normal 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>
|
||||||
198
www/public/market.php
Normal file
198
www/public/market.php
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Airline Tycoon - Flugzeug-Markt
|
||||||
|
* Kauf von neuen Flugzeugen
|
||||||
|
*/
|
||||||
|
|
||||||
|
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();
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Markt - <?= 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">Finanzen</a>
|
||||||
|
<a href="<?= $basePath ?>market.php" class="navbar-item active">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">🛒 Flugzeug-Markt</h1>
|
||||||
|
<p class="hero-subtitle">Kaufe neue Flugzeuge für deine Airline</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Kontostand Hinweis -->
|
||||||
|
<div class="balance-banner panel mt-lg">
|
||||||
|
<div class="balance-info">
|
||||||
|
<span class="balance-label">Dein Kontostand:</span>
|
||||||
|
<span class="balance-value" id="currentBalance"><?= number_format($user['balance'], 2, ',', '.') ?> €</span>
|
||||||
|
</div>
|
||||||
|
<div class="balance-actions">
|
||||||
|
<a href="<?= $basePath ?>finances.php" class="btn btn-sm btn-secondary">
|
||||||
|
💰 Kontoauszug
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Flugzeugtypen -->
|
||||||
|
<section class="panel mt-xl">
|
||||||
|
<h2 class="panel-title">🛩️ Verfügbare Flugzeuge</h2>
|
||||||
|
<p class="text-secondary mb-lg">Wähle einen Flugzeugtyp und gib ihm einen Namen.</p>
|
||||||
|
|
||||||
|
<div class="aircraft-market-grid">
|
||||||
|
<?php foreach ($config['aircraft_types'] as $type => $specs): ?>
|
||||||
|
<?php
|
||||||
|
$canAfford = $user['balance'] >= $specs['purchase_price'];
|
||||||
|
$fuelEff = $specs['fuel_efficiency'] ?? 1.0;
|
||||||
|
?>
|
||||||
|
<div class="market-aircraft-card <?= $canAfford ? '' : 'disabled' ?>">
|
||||||
|
<div class="market-aircraft-header">
|
||||||
|
<h3 class="market-aircraft-name"><?= htmlspecialchars($type) ?></h3>
|
||||||
|
<span class="market-aircraft-price <?= $canAfford ? 'text-success' : 'text-danger' ?>">
|
||||||
|
<?= number_format($specs['purchase_price'], 0, ',', '.') ?> €
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="market-aircraft-specs">
|
||||||
|
<div class="spec-row">
|
||||||
|
<span class="spec-label">✈️ Sitzplätze:</span>
|
||||||
|
<span class="spec-value"><?= $specs['seat_capacity'] ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="spec-row">
|
||||||
|
<span class="spec-label">🌍 Reichweite:</span>
|
||||||
|
<span class="spec-value"><?= number_format($specs['range_km'], 0, ',', '.') ?> km</span>
|
||||||
|
</div>
|
||||||
|
<div class="spec-row">
|
||||||
|
<span class="spec-label">⛽ Effizienz:</span>
|
||||||
|
<span class="spec-value">
|
||||||
|
<?php if ($fuelEff < 0.9): ?>
|
||||||
|
🟢 Sehr gut
|
||||||
|
<?php elseif ($fuelEff < 1.0): ?>
|
||||||
|
🟡 Gut
|
||||||
|
<?php else: ?>
|
||||||
|
🟠 Normal
|
||||||
|
<?php endif; ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="spec-row">
|
||||||
|
<span class="spec-label">💨 Geschwindigkeit:</span>
|
||||||
|
<span class="spec-value"><?= $specs['speed_kmh'] ?? 800 ?> km/h</span>
|
||||||
|
</div>
|
||||||
|
<div class="spec-row">
|
||||||
|
<span class="spec-label">👨✈️ Crew-Kosten:</span>
|
||||||
|
<span class="spec-value"><?= $specs['crew_cost_per_tick'] ?? 500 ?> €/Tick</span>
|
||||||
|
</div>
|
||||||
|
<div class="spec-row">
|
||||||
|
<span class="spec-label">🔧 Wartung:</span>
|
||||||
|
<span class="spec-value"><?= number_format($specs['maintenance_base_cost'] ?? 50000, 0, ',', '.') ?> €</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($canAfford): ?>
|
||||||
|
<form class="purchase-form mt-md" data-ajax>
|
||||||
|
<input type="hidden" name="action" value="purchase">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= $csrfToken ?>">
|
||||||
|
<input type="hidden" name="aircraft_type" value="<?= htmlspecialchars($type) ?>">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" name="name" class="form-input"
|
||||||
|
placeholder="Flugzeugname (z.B. 'Hans')"
|
||||||
|
maxlength="30" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary btn-lg w-100">
|
||||||
|
✈️ Kaufen für <?= number_format($specs['purchase_price'], 0, ',', '.') ?> €
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="cannot-afford mt-md">
|
||||||
|
<span class="badge badge-danger">Nicht genug Guthaben</span>
|
||||||
|
<p class="text-secondary mt-sm">
|
||||||
|
Du brauchst noch <?= number_format($specs['purchase_price'] - $user['balance'], 0, ',', '.') ?> €
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Informationen -->
|
||||||
|
<section class="panel mt-xl">
|
||||||
|
<h2 class="panel-title">📖 Informationen</h2>
|
||||||
|
<div class="info-grid mt-md">
|
||||||
|
<div class="info-card">
|
||||||
|
<h3>💡 Tipps zum Flugzeugkauf</h3>
|
||||||
|
<ul class="info-list">
|
||||||
|
<li>Propeller-Flugzeuge sind günstig und gut für kurze Strecken</li>
|
||||||
|
<li>Schmale Rumpf-Flugzeuge (Narrowbody) sind vielseitig einsetzbar</li>
|
||||||
|
<li>Widebody-Flugzeuge sind für Langstrecken gedacht</li>
|
||||||
|
<li>Der A380 ist das größte Passagierflugzeug der Welt</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<h3>🔧 Wartung</h3>
|
||||||
|
<ul class="info-list">
|
||||||
|
<li>Alle 50 Flüge muss ein Flugzeug zur Wartung</li>
|
||||||
|
<li>Die Wartung stellt den Zustand auf 100% zurück</li>
|
||||||
|
<li>Vernachlässigte Wartung erhöht die Kosten</li>
|
||||||
|
<li>Ein Flugzeug mit weniger als 20% Zustand kann nicht mehr fliegen</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<h3>⛽ Treibstoffkosten</h3>
|
||||||
|
<ul class="info-list">
|
||||||
|
<li>Treibstoff kostet <?= $config['game']['fuel_cost_per_km'] ?> € pro km</li>
|
||||||
|
<li>Effizientere Flugzeuge verbrauchen weniger</li>
|
||||||
|
<li>Die Treibstoffkosten werden pro Flug vom Gewinn abgezogen</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Toast Container -->
|
||||||
|
<div id="toastContainer" class="toast-container"></div>
|
||||||
|
|
||||||
|
<script src="<?= $basePath ?>assets/js/main.js"></script>
|
||||||
|
<script>
|
||||||
|
// Erfolg beim Kauf
|
||||||
|
window.purchaseSuccess = function(data) {
|
||||||
|
document.getElementById('currentBalance').textContent =
|
||||||
|
new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(data.new_balance);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -46,7 +46,15 @@ class Database {
|
|||||||
|
|
||||||
if (file_exists($schemaPath)) {
|
if (file_exists($schemaPath)) {
|
||||||
$schema = file_get_contents($schemaPath);
|
$schema = file_get_contents($schemaPath);
|
||||||
$this->connection->exec($schema);
|
try {
|
||||||
|
$this->connection->exec($schema);
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
// Schema already exists (tables/data already present) - ignore
|
||||||
|
if (strpos($e->getMessage(), 'UNIQUE constraint failed') === false &&
|
||||||
|
strpos($e->getMessage(), 'table already exists') === false) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,15 +195,22 @@ class Database {
|
|||||||
throw new Exception("Unbekannter Flugzeugtyp: {$type}");
|
throw new Exception("Unbekannter Flugzeugtyp: {$type}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->insert('aircraft', [
|
$aircraftId = $this->insert('aircraft', [
|
||||||
'user_id' => $userId,
|
'user_id' => $userId,
|
||||||
'aircraft_type' => $type,
|
'aircraft_type' => $type,
|
||||||
'name' => $name ?: $type,
|
'name' => $name ?: $type,
|
||||||
'purchase_price' => $price,
|
'purchase_price' => $price,
|
||||||
'seat_capacity' => $config['seat_capacity'],
|
'seat_capacity' => $config['seat_capacity'],
|
||||||
'range_km' => $config['range_km'],
|
'range_km' => $config['range_km'],
|
||||||
'fuel_efficiency' => $config['fuel_efficiency']
|
'fuel_efficiency' => $config['fuel_efficiency'],
|
||||||
|
'condition' => 100.00
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Log transaction
|
||||||
|
$this->logTransaction($userId, 'aircraft_purchase', 'expense', -$price,
|
||||||
|
"Flugzeug {$type} \"{$name}\" gekauft", $aircraftId);
|
||||||
|
|
||||||
|
return $aircraftId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Route Methoden
|
// Route Methoden
|
||||||
@@ -286,12 +301,10 @@ class Database {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Balance aktualisieren
|
// Balance aktualisieren
|
||||||
$this->update('users', ['balance' => $this->config['game']['start_balance']],
|
|
||||||
'id = ?', [$userId]); // Placeholder, wird unten korrekt gemacht
|
|
||||||
|
|
||||||
$user = $this->getUserById($userId);
|
$user = $this->getUserById($userId);
|
||||||
|
$newBalance = $user['balance'] + $calc['profit'];
|
||||||
$this->update('users', [
|
$this->update('users', [
|
||||||
'balance' => $user['balance'] + $calc['profit'],
|
'balance' => $newBalance,
|
||||||
'experience' => $user['experience'] + $this->config['game']['experience_per_flight'] +
|
'experience' => $user['experience'] + $this->config['game']['experience_per_flight'] +
|
||||||
($calc['passengers'] * $this->config['game']['experience_per_passenger'])
|
($calc['passengers'] * $this->config['game']['experience_per_passenger'])
|
||||||
], 'id = ?', [$userId]);
|
], 'id = ?', [$userId]);
|
||||||
@@ -302,6 +315,12 @@ class Database {
|
|||||||
'flights_count' => $aircraft['flights_count'] + 1
|
'flights_count' => $aircraft['flights_count'] + 1
|
||||||
], 'id = ?', [$aircraftId]);
|
], 'id = ?', [$aircraftId]);
|
||||||
|
|
||||||
|
// Transaktionen loggen
|
||||||
|
$this->logTransaction($userId, 'flight', 'income', $calc['revenue'],
|
||||||
|
"Flug mit {$aircraft['name']}: {$calc['passengers']} Passagiere", $flightId);
|
||||||
|
$this->logTransaction($userId, 'fuel', 'expense', -$calc['fuel_cost'],
|
||||||
|
"Treibstoff für Flug", $flightId);
|
||||||
|
|
||||||
// Tagesstatistik aktualisieren
|
// Tagesstatistik aktualisieren
|
||||||
$today = date('Y-m-d');
|
$today = date('Y-m-d');
|
||||||
$existing = $this->selectOne(
|
$existing = $this->selectOne(
|
||||||
@@ -329,7 +348,7 @@ class Database {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_merge($calc, ['flight_id' => $flightId]);
|
return array_merge($calc, ['flight_id' => $flightId, 'new_balance' => $newBalance]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Statistik Methoden
|
// Statistik Methoden
|
||||||
@@ -370,4 +389,254 @@ class Database {
|
|||||||
public function getConfig(): array {
|
public function getConfig(): array {
|
||||||
return $this->config;
|
return $this->config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// LOAN METHODS
|
||||||
|
// ============================================
|
||||||
|
public function getTotalDebt(int $userId): float {
|
||||||
|
$result = $this->selectOne(
|
||||||
|
"SELECT COALESCE(SUM(current_amount), 0) as total FROM loans WHERE user_id = ? AND status = 'active'",
|
||||||
|
[$userId]
|
||||||
|
);
|
||||||
|
return (float) $result['total'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalDebtWithInterest(int $userId): float {
|
||||||
|
$result = $this->selectOne(
|
||||||
|
"SELECT COALESCE(SUM(current_amount + daily_interest * days_outstanding), 0) as total
|
||||||
|
FROM loans WHERE user_id = ? AND status = 'active'",
|
||||||
|
[$userId]
|
||||||
|
);
|
||||||
|
return (float) $result['total'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createLoan(int $userId, float $amount): int {
|
||||||
|
$config = $this->config['loans'];
|
||||||
|
|
||||||
|
if ($amount < $config['min_amount']) {
|
||||||
|
throw new Exception("Mindestkreditbetrag: " . number_format($config['min_amount'], 0, ',', '.') . " €");
|
||||||
|
}
|
||||||
|
|
||||||
|
$maxAmount = $config['max_amount'];
|
||||||
|
$currentDebt = $this->getTotalDebt($userId);
|
||||||
|
|
||||||
|
if ($currentDebt + $amount > $maxAmount) {
|
||||||
|
throw new Exception("Kreditlimit überschritten! Maximal verfügbar: " .
|
||||||
|
number_format($maxAmount - $currentDebt, 0, ',', '.') . " €");
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->getUserById($userId);
|
||||||
|
if (!$user) {
|
||||||
|
throw new Exception("Benutzer nicht gefunden");
|
||||||
|
}
|
||||||
|
|
||||||
|
$interest = $amount * $config['interest_rate'];
|
||||||
|
|
||||||
|
return $this->insert('loans', [
|
||||||
|
'user_id' => $userId,
|
||||||
|
'principal' => $amount,
|
||||||
|
'current_amount' => $amount,
|
||||||
|
'interest_rate' => $config['interest_rate'],
|
||||||
|
'daily_interest' => $interest,
|
||||||
|
'days_outstanding' => 0,
|
||||||
|
'status' => 'active',
|
||||||
|
'created_at' => date('Y-m-d H:i:s'),
|
||||||
|
'due_date' => date('Y-m-d', strtotime('+365 days'))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function repayLoan(int $userId, int $loanId, float $amount): array {
|
||||||
|
$loan = $this->selectOne("SELECT * FROM loans WHERE id = ? AND user_id = ? AND status = 'active'",
|
||||||
|
[$loanId, $userId]);
|
||||||
|
|
||||||
|
if (!$loan) {
|
||||||
|
throw new Exception("Kredit nicht gefunden");
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->getUserById($userId);
|
||||||
|
$repayAmount = min($amount, $loan['current_amount'] + ($loan['daily_interest'] * $loan['days_outstanding']));
|
||||||
|
|
||||||
|
if ($repayAmount > $user['balance']) {
|
||||||
|
throw new Exception("Nicht genug Guthaben für Rückzahlung");
|
||||||
|
}
|
||||||
|
|
||||||
|
$totalOwed = $loan['current_amount'] + ($loan['daily_interest'] * $loan['days_outstanding']);
|
||||||
|
$remaining = $totalOwed - $repayAmount;
|
||||||
|
|
||||||
|
if ($remaining <= 0) {
|
||||||
|
// Loan fully paid
|
||||||
|
$this->update('loans', ['status' => 'paid'], 'id = ?', [$loanId]);
|
||||||
|
$newBalance = $user['balance'] - $repayAmount;
|
||||||
|
} else {
|
||||||
|
// Partial repayment
|
||||||
|
$this->update('loans', [
|
||||||
|
'current_amount' => $remaining,
|
||||||
|
'daily_interest' => $remaining * $loan['interest_rate']
|
||||||
|
], 'id = ?', [$loanId]);
|
||||||
|
$newBalance = $user['balance'] - $repayAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->update('users', ['balance' => $newBalance], 'id = ?', [$userId]);
|
||||||
|
$this->logTransaction($userId, 'loan_repayment', 'expense', -$repayAmount,
|
||||||
|
"Rückzahlung Kredit #{$loanId}", $loanId);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'repaid' => $repayAmount,
|
||||||
|
'remaining' => $remaining > 0 ? $remaining : 0,
|
||||||
|
'new_balance' => $newBalance
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function processDailyLoanInterest(int $userId): float {
|
||||||
|
$loans = $this->getUserLoans($userId);
|
||||||
|
$totalInterest = 0;
|
||||||
|
|
||||||
|
foreach ($loans as $loan) {
|
||||||
|
$totalInterest += $loan['daily_interest'];
|
||||||
|
$this->update('loans', [
|
||||||
|
'days_outstanding' => $loan['days_outstanding'] + 1
|
||||||
|
], 'id = ?', [$loanId ?? $loan['id']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $totalInterest;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// MAINTENANCE METHODS
|
||||||
|
// ============================================
|
||||||
|
public function performMaintenance(int $userId, int $aircraftId, string $type = 'scheduled'): array {
|
||||||
|
$aircraft = $this->selectOne("SELECT * FROM aircraft WHERE id = ? AND user_id = ?",
|
||||||
|
[$aircraftId, $userId]);
|
||||||
|
|
||||||
|
if (!$aircraft) {
|
||||||
|
throw new Exception("Flugzeug nicht gefunden");
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = $this->config['aircraft_types'][$aircraft['aircraft_type']];
|
||||||
|
$maintenanceCost = $config['maintenance_base_cost'] ?? 50000;
|
||||||
|
|
||||||
|
// Cost increases if condition is low
|
||||||
|
$conditionLoss = 100 - $aircraft['condition'];
|
||||||
|
$totalCost = $maintenanceCost + ($conditionLoss * 0.01 * $maintenanceCost);
|
||||||
|
|
||||||
|
$user = $this->getUserById($userId);
|
||||||
|
if ($user['balance'] < $totalCost) {
|
||||||
|
throw new Exception("Nicht genug Guthaben für Wartung!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore condition
|
||||||
|
$newCondition = min(100, $aircraft['condition'] + $this->config['game']['maintenance_condition_restore']);
|
||||||
|
|
||||||
|
// Update aircraft
|
||||||
|
$this->update('aircraft', [
|
||||||
|
'condition' => $newCondition,
|
||||||
|
'flights_count' => 0 // Reset flight counter after maintenance
|
||||||
|
], 'id = ?', [$aircraftId]);
|
||||||
|
|
||||||
|
// Deduct cost
|
||||||
|
$newBalance = $user['balance'] - $totalCost;
|
||||||
|
$this->update('users', ['balance' => $newBalance], 'id = ?', [$userId]);
|
||||||
|
|
||||||
|
// Log transaction
|
||||||
|
$this->logTransaction($userId, 'maintenance', 'expense', -$totalCost,
|
||||||
|
"Wartung {$aircraft['name']} ({$type})", $aircraftId);
|
||||||
|
|
||||||
|
// Maintenance history
|
||||||
|
$this->insert('maintenance_history', [
|
||||||
|
'aircraft_id' => $aircraftId,
|
||||||
|
'user_id' => $userId,
|
||||||
|
'maintenance_type' => $type,
|
||||||
|
'cost' => $totalCost,
|
||||||
|
'condition_before' => $aircraft['condition'],
|
||||||
|
'condition_after' => $newCondition,
|
||||||
|
'created_at' => date('Y-m-d H:i:s')
|
||||||
|
]);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'cost' => $totalCost,
|
||||||
|
'condition_before' => $aircraft['condition'],
|
||||||
|
'condition_after' => $newCondition,
|
||||||
|
'new_balance' => $newBalance
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMaintenanceHistory(int $userId, int $limit = 20): array {
|
||||||
|
return $this->select(
|
||||||
|
"SELECT mh.*, a.name as aircraft_name, a.aircraft_type
|
||||||
|
FROM maintenance_history mh
|
||||||
|
JOIN aircraft a ON mh.aircraft_id = a.id
|
||||||
|
WHERE mh.user_id = ?
|
||||||
|
ORDER BY mh.created_at DESC
|
||||||
|
LIMIT ?",
|
||||||
|
[$userId, $limit]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkMaintenanceDue(int $userId): array {
|
||||||
|
$aircraft = $this->getUserAircraft($userId);
|
||||||
|
$due = [];
|
||||||
|
|
||||||
|
foreach ($aircraft as $plane) {
|
||||||
|
if ($plane['flights_count'] >= $this->config['game']['maintenance_interval']) {
|
||||||
|
$due[] = $plane;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $due;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// TRANSACTION METHODS
|
||||||
|
// ============================================
|
||||||
|
public function logTransaction(int $userId, string $type, string $category, float $amount, string $description = '', ?int $referenceId = null): int {
|
||||||
|
$user = $this->getUserById($userId);
|
||||||
|
|
||||||
|
return $this->insert('transactions', [
|
||||||
|
'user_id' => $userId,
|
||||||
|
'type' => $type,
|
||||||
|
'category' => $category,
|
||||||
|
'amount' => $amount,
|
||||||
|
'description' => $description,
|
||||||
|
'reference_id' => $referenceId,
|
||||||
|
'balance_after' => $user['balance'],
|
||||||
|
'created_at' => date('Y-m-d H:i:s')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserTransactions(int $userId, int $limit = 50): array {
|
||||||
|
return $this->select(
|
||||||
|
"SELECT * FROM transactions WHERE user_id = ? ORDER BY created_at DESC LIMIT ?",
|
||||||
|
[$userId, $limit]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTransactionSummary(int $userId, string $startDate, string $endDate): array {
|
||||||
|
$result = $this->selectOne(
|
||||||
|
"SELECT
|
||||||
|
COALESCE(SUM(CASE WHEN amount > 0 THEN amount ELSE 0 END), 0) as total_income,
|
||||||
|
COALESCE(SUM(CASE WHEN amount < 0 THEN ABS(amount) ELSE 0 END), 0) as total_expenses
|
||||||
|
FROM transactions
|
||||||
|
WHERE user_id = ? AND DATE(created_at) BETWEEN ? AND ?",
|
||||||
|
[$userId, $startDate, $endDate]
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'total_income' => (float) $result['total_income'],
|
||||||
|
'total_expenses' => (float) $result['total_expenses']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// LOAN METHODS
|
||||||
|
// ============================================
|
||||||
|
public function getUserLoans(int $userId): array {
|
||||||
|
return $this->select(
|
||||||
|
"SELECT * FROM loans WHERE user_id = ? AND status = 'active' ORDER BY created_at DESC",
|
||||||
|
[$userId]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLoanById(int $loanId): ?array {
|
||||||
|
return $this->selectOne("SELECT * FROM loans WHERE id = ?", [$loanId]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,52 +23,80 @@ return [
|
|||||||
|
|
||||||
// Spiel
|
// Spiel
|
||||||
'game' => [
|
'game' => [
|
||||||
'start_balance' => 500000.00,
|
'start_balance' => 10000000.00,
|
||||||
'start_aircraft' => 'Dash 8-100',
|
'start_aircraft' => 'Dash 8-100',
|
||||||
'fuel_cost_per_km' => 0.15,
|
'fuel_cost_per_km' => 0.15,
|
||||||
'maintenance_cost_rate' => 0.001,
|
'maintenance_cost_rate' => 0.001,
|
||||||
|
'maintenance_interval' => 50,
|
||||||
|
'maintenance_condition_restore' => 10.00,
|
||||||
'experience_per_flight' => 10,
|
'experience_per_flight' => 10,
|
||||||
'experience_per_passenger' => 1,
|
'experience_per_passenger' => 1,
|
||||||
'level_up_threshold' => 1000,
|
'level_up_threshold' => 1000,
|
||||||
'level_multiplier' => 1.5
|
'level_multiplier' => 1.5
|
||||||
],
|
],
|
||||||
|
|
||||||
// Flugzeugtypen
|
// Kredite
|
||||||
|
'loans' => [
|
||||||
|
'max_amount' => 50000000.00,
|
||||||
|
'interest_rate' => 0.02,
|
||||||
|
'min_amount' => 100000.00
|
||||||
|
],
|
||||||
|
|
||||||
|
// Flugzeugtypen (6 Typen)
|
||||||
'aircraft_types' => [
|
'aircraft_types' => [
|
||||||
'Dash 8-100' => [
|
'Dash 8-100' => [
|
||||||
'seat_capacity' => 35,
|
'seat_capacity' => 37,
|
||||||
'range_km' => 1500,
|
'range_km' => 1500,
|
||||||
'purchase_price' => 8500000,
|
'purchase_price' => 500000,
|
||||||
'fuel_efficiency' => 1.2,
|
'fuel_efficiency' => 1.2,
|
||||||
'speed_kmh' => 500
|
'speed_kmh' => 500,
|
||||||
|
'crew_cost_per_tick' => 200,
|
||||||
|
'maintenance_base_cost' => 10000
|
||||||
],
|
],
|
||||||
'Boeing 737-300' => [
|
'ATR 72-500' => [
|
||||||
'seat_capacity' => 126,
|
'seat_capacity' => 70,
|
||||||
'range_km' => 4200,
|
'range_km' => 1650,
|
||||||
'purchase_price' => 45000000,
|
'purchase_price' => 1500000,
|
||||||
'fuel_efficiency' => 1.0,
|
'fuel_efficiency' => 1.15,
|
||||||
'speed_kmh' => 830
|
'speed_kmh' => 510,
|
||||||
|
'crew_cost_per_tick' => 300,
|
||||||
|
'maintenance_base_cost' => 15000
|
||||||
],
|
],
|
||||||
'Airbus A320' => [
|
'A320-200' => [
|
||||||
'seat_capacity' => 150,
|
'seat_capacity' => 150,
|
||||||
'range_km' => 5700,
|
'range_km' => 5700,
|
||||||
'purchase_price' => 65000000,
|
'purchase_price' => 8000000,
|
||||||
'fuel_efficiency' => 0.95,
|
'fuel_efficiency' => 1.0,
|
||||||
'speed_kmh' => 840
|
'speed_kmh' => 840,
|
||||||
|
'crew_cost_per_tick' => 600,
|
||||||
|
'maintenance_base_cost' => 50000
|
||||||
],
|
],
|
||||||
'Boeing 747-400' => [
|
'B737-800' => [
|
||||||
'seat_capacity' => 416,
|
'seat_capacity' => 162,
|
||||||
'range_km' => 13400,
|
'range_km' => 5700,
|
||||||
'purchase_price' => 220000000,
|
'purchase_price' => 9500000,
|
||||||
'fuel_efficiency' => 0.8,
|
'fuel_efficiency' => 0.98,
|
||||||
'speed_kmh' => 910
|
'speed_kmh' => 850,
|
||||||
|
'crew_cost_per_tick' => 650,
|
||||||
|
'maintenance_base_cost' => 55000
|
||||||
],
|
],
|
||||||
'Airbus A380' => [
|
'B777-200' => [
|
||||||
|
'seat_capacity' => 300,
|
||||||
|
'range_km' => 12000,
|
||||||
|
'purchase_price' => 35000000,
|
||||||
|
'fuel_efficiency' => 0.85,
|
||||||
|
'speed_kmh' => 905,
|
||||||
|
'crew_cost_per_tick' => 1200,
|
||||||
|
'maintenance_base_cost' => 150000
|
||||||
|
],
|
||||||
|
'A380-800' => [
|
||||||
'seat_capacity' => 525,
|
'seat_capacity' => 525,
|
||||||
'range_km' => 15200,
|
'range_km' => 15200,
|
||||||
'purchase_price' => 350000000,
|
'purchase_price' => 85000000,
|
||||||
'fuel_efficiency' => 0.75,
|
'fuel_efficiency' => 0.75,
|
||||||
'speed_kmh' => 900
|
'speed_kmh' => 900,
|
||||||
|
'crew_cost_per_tick' => 2000,
|
||||||
|
'maintenance_base_cost' => 350000
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user