439 lines
14 KiB
PHP
439 lines
14 KiB
PHP
<?php
|
|
ini_set('display_errors', 1);
|
|
error_reporting(E_ALL);
|
|
date_default_timezone_set("Europe/Berlin");
|
|
require_once "config/config.php";
|
|
include("Data.class.php");
|
|
$Data = new Data($pdo);
|
|
|
|
session_start();
|
|
|
|
// Login-Check
|
|
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
|
header("location: index.php");
|
|
exit;
|
|
}
|
|
|
|
// --- STATISTIKEN / DATEN HOLEN ---
|
|
|
|
// Status-Counts
|
|
$statusMap = [
|
|
0 => "Offen",
|
|
1 => "Bereit",
|
|
2 => "Erledigt",
|
|
3 => "Storniert"
|
|
];
|
|
|
|
$statusCounts = [
|
|
0 => 0,
|
|
1 => 0,
|
|
2 => 0,
|
|
3 => 0
|
|
];
|
|
|
|
$stmt = $pdo->query("SELECT status, COUNT(*) AS cnt FROM termine GROUP BY status");
|
|
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|
$s = (int)$row['status'];
|
|
if (isset($statusCounts[$s])) {
|
|
$statusCounts[$s] = (int)$row['cnt'];
|
|
}
|
|
}
|
|
|
|
// Nächste 5 Termine
|
|
$nowTs = time();
|
|
$nextStmt = $pdo->prepare("
|
|
SELECT t.*, u.name AS user_name
|
|
FROM termine t
|
|
LEFT JOIN users u ON u.id = t.user_id
|
|
WHERE t.startzeit >= :now
|
|
ORDER BY t.startzeit ASC
|
|
LIMIT 5
|
|
");
|
|
$nextStmt->execute([':now' => $nowTs]);
|
|
$nextEvents = $nextStmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// Letzte Änderungen (Activity Log)
|
|
$changesStmt = $pdo->query("
|
|
SELECT c.*, u.name AS user_name
|
|
FROM changes c
|
|
LEFT JOIN users u ON u.id = c.user_id
|
|
ORDER BY c.timestamp DESC
|
|
LIMIT 5
|
|
");
|
|
$recentChanges = $changesStmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// Einnahmen dieses Monats
|
|
$currentMonth = date('Y-m-01 00:00:00');
|
|
$monthStartTs = strtotime($currentMonth);
|
|
$nextMonthTs = strtotime('+1 month', $monthStartTs);
|
|
|
|
$gageStmt = $pdo->prepare("
|
|
SELECT
|
|
SUM(gage) AS sum_gage,
|
|
COUNT(*) AS count_terms
|
|
FROM termine
|
|
WHERE startzeit >= :start
|
|
AND startzeit < :end
|
|
AND status = 2 -- nur abgeschlossene Termine zählen!
|
|
");
|
|
$gageStmt->execute([
|
|
':start' => $monthStartTs,
|
|
':end' => $nextMonthTs
|
|
]);
|
|
$gageData = $gageStmt->fetch(PDO::FETCH_ASSOC);
|
|
$sumGage = (float)($gageData['sum_gage'] ?? 0);
|
|
$countGigs = (int)($gageData['count_terms'] ?? 0);
|
|
|
|
|
|
// Offene ToDos (Beispiele: Gage = 0, Endzeit = 0)
|
|
$todoGageStmt = $pdo->query("SELECT COUNT(*) FROM termine WHERE (gage = 0 OR gage IS NULL)");
|
|
$todoNoGage = (int)$todoGageStmt->fetchColumn();
|
|
|
|
$todoEndStmt = $pdo->query("SELECT COUNT(*) FROM termine WHERE (endzeit = 0 OR endzeit IS NULL)");
|
|
$todoNoEnd = (int)$todoEndStmt->fetchColumn();
|
|
|
|
?>
|
|
<style>
|
|
/* Allgemeines Dashboard-Layout */
|
|
.dashboard-section-title {
|
|
font-weight: 600;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.dashboard-subtitle {
|
|
color: #6c757d;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
/* Status-Kacheln */
|
|
.status-card {
|
|
border-radius: 12px;
|
|
padding: 14px 16px;
|
|
color: #fff;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
box-shadow: 0 3px 8px rgba(0,0,0,0.15);
|
|
}
|
|
.status-card {
|
|
transition: transform 0.12s ease, box-shadow 0.12s ease;
|
|
}
|
|
|
|
.status-card:hover {
|
|
transform: translateY(-3px);
|
|
box-shadow: 0 5px 14px rgba(0, 0, 0, 0.25);
|
|
cursor: pointer;
|
|
}
|
|
.status-label {
|
|
font-size: 0.9rem;
|
|
opacity: 0.9;
|
|
}
|
|
.status-value {
|
|
font-size: 1.6rem;
|
|
font-weight: 700;
|
|
}
|
|
|
|
/* Farben für Status */
|
|
.status-open { background: #a0a0a0; } /* blau */
|
|
.status-ready { background: #0d6efd; color: #212529; }
|
|
.status-done { background: #198754; }
|
|
.status-cancel { background: #dc3545; }
|
|
|
|
/* Karten fürs Dashboard */
|
|
.card-soft {
|
|
border-radius: 12px;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
|
}
|
|
|
|
/* Nächste Termine */
|
|
.next-event-title {
|
|
font-weight: 600;
|
|
font-size: 0.95rem;
|
|
}
|
|
.next-event-meta {
|
|
font-size: 0.85rem;
|
|
color: #6c757d;
|
|
}
|
|
|
|
/* Activity Log im Dashboard */
|
|
.activity-entry {
|
|
font-size: 0.9rem;
|
|
padding: 8px 0;
|
|
border-bottom: 1px solid #f1f3f5;
|
|
}
|
|
.activity-entry:last-child {
|
|
border-bottom: none;
|
|
}
|
|
.activity-time {
|
|
font-size: 0.8rem;
|
|
color: #6c757d;
|
|
}
|
|
|
|
/* ToDo Badges */
|
|
.todo-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
font-size: 0.9rem;
|
|
padding: 6px 0;
|
|
}
|
|
|
|
.status-link {
|
|
text-decoration: none;
|
|
display: block;
|
|
}
|
|
.status-link:hover .status-card {
|
|
transform: scale(1.03);
|
|
box-shadow: 0 6px 16px rgba(0,0,0,0.15);
|
|
}
|
|
|
|
|
|
/* Mobile spacing */
|
|
@media (max-width: 768px){
|
|
.status-card {
|
|
margin-bottom: 10px;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<div class="container mt-4 mb-5">
|
|
|
|
<!-- Überschrift + Quick-Action -->
|
|
<div class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-4">
|
|
<div>
|
|
<h3 class="mb-1">Dashboard</h3>
|
|
<div class="dashboard-subtitle">
|
|
Überblick über kommende Termine, Status & Änderungen.
|
|
</div>
|
|
</div>
|
|
<div class="mt-2 mt-md-0">
|
|
<a href="termin_create.php" class="btn btn-primary">
|
|
<i class="bi bi-plus-circle"></i> Neuer Termin
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- STATUS-KACHELN -->
|
|
<div class="row g-3 mb-4">
|
|
|
|
<div class="col-6 col-md-3">
|
|
<a href="termine.php?status=0" class="status-link">
|
|
<div class="status-card status-open">
|
|
<div class="status-label">Offen</div>
|
|
<div class="status-value"><?= $statusCounts[0]; ?></div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="col-6 col-md-3">
|
|
<a href="termine.php?status=1" class="status-link">
|
|
<div class="status-card status-ready">
|
|
<div class="status-label">Bereit</div>
|
|
<div class="status-value"><?= $statusCounts[1]; ?></div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="col-6 col-md-3">
|
|
<a href="termine.php?status=2" class="status-link">
|
|
<div class="status-card status-done">
|
|
<div class="status-label">Abgeschlossen</div>
|
|
<div class="status-value"><?= $statusCounts[2]; ?></div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="col-6 col-md-3">
|
|
<a href="termine.php?status=3" class="status-link">
|
|
<div class="status-card status-cancel">
|
|
<div class="status-label">Storniert</div>
|
|
<div class="status-value"><?= $statusCounts[3]; ?></div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- OBERE REIHE: Nächste Termine + ToDos -->
|
|
<div class="row g-4 mb-4">
|
|
|
|
<!-- Nächste Termine -->
|
|
<div class="col-12 col-lg-8">
|
|
<div class="card card-soft">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
<div class="dashboard-section-title">
|
|
<i class="bi bi-calendar-event me-1"></i> Nächste Termine
|
|
</div>
|
|
<a href="termine.php" class="btn btn-sm btn-outline-secondary">
|
|
Alle Termine
|
|
</a>
|
|
</div>
|
|
|
|
<?php if (count($nextEvents) === 0): ?>
|
|
<p class="text-muted mb-0">Keine kommenden Termine eingetragen.</p>
|
|
<?php else: ?>
|
|
<div class="list-group list-group-flush">
|
|
<?php foreach ($nextEvents as $ev): ?>
|
|
<?php
|
|
$startStr = date('d.m.Y H:i', $ev['startzeit']);
|
|
$statusLabel = $statusMap[$ev['status']] ?? $ev['status'];
|
|
$badgeClass = match((int)$ev['status']) {
|
|
0 => 'bg-secondary',
|
|
1 => 'bg-primary',
|
|
2 => 'bg-success',
|
|
3 => 'bg-danger',
|
|
default => 'bg-secondary'
|
|
};
|
|
?>
|
|
<a href="termin_show.php?id=<?= $ev['id']; ?>"
|
|
class="list-group-item list-group-item-action d-flex justify-content-between align-items-start">
|
|
<div class="me-3">
|
|
<div class="next-event-title"><?= htmlspecialchars($ev['location']); ?></div>
|
|
<div class="next-event-meta">
|
|
<i class="bi bi-clock"></i> <?= $startStr; ?>
|
|
<?php if (!empty($ev['user_name'])): ?>
|
|
· <i class="bi bi-person"></i> <?= htmlspecialchars($ev['user_name']); ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<span class="badge rounded-pill <?= $badgeClass; ?>">
|
|
<?= $statusLabel; ?>
|
|
</span>
|
|
</a>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ToDos / Quick Infos -->
|
|
<div class="col-12 col-lg-4">
|
|
<div class="card card-soft mb-3">
|
|
<div class="card-body">
|
|
<div class="dashboard-section-title mb-2">
|
|
<i class="bi bi-list-check me-1"></i> Offene Punkte
|
|
</div>
|
|
<div class="todo-item">
|
|
<span>Termine ohne Gage</span>
|
|
<span class="badge bg-warning text-dark"><?= $todoNoGage; ?></span>
|
|
</div>
|
|
<div class="todo-item">
|
|
<span>Termine ohne Endzeit</span>
|
|
<span class="badge bg-warning text-dark"><?= $todoNoEnd; ?></span>
|
|
</div>
|
|
<!-- ggf. weitere ToDos -->
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card card-soft">
|
|
<div class="card-body">
|
|
<div class="dashboard-section-title mb-1">
|
|
<i class="bi bi-cash-stack me-1"></i> Einnahmen <?= date('m.Y'); ?>
|
|
</div>
|
|
<p class="mb-1">
|
|
<strong><?= number_format($sumGage, 2, ',', '.'); ?> €</strong> Gesamt
|
|
</p>
|
|
<p class="mb-0 text-muted">
|
|
<?= $countGigs; ?> Termin(e) in diesem Monat.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- UNTERE REIHE: Letzte Änderungen -->
|
|
<div class="row g-4">
|
|
<div class="col-12 col-lg-8">
|
|
<div class="card card-soft">
|
|
<div class="card-body">
|
|
|
|
<div class="dashboard-section-title mb-2">
|
|
<i class="bi bi-clock-history me-1"></i> Letzte Änderungen
|
|
</div>
|
|
|
|
<?php if (count($recentChanges) === 0): ?>
|
|
<p class="text-muted mb-0">Noch keine Änderungen protokolliert.</p>
|
|
<?php else: ?>
|
|
|
|
<?php foreach ($recentChanges as $change): ?>
|
|
<?php
|
|
$timeStr = date('d.m.Y H:i', $change['timestamp']);
|
|
$userName = $change['user_name'] ?? 'Unbekannt';
|
|
$field = strtoupper($change['was']);
|
|
$termId = $change['termin_id'];
|
|
|
|
// --- ALT UND NEU LESEN ---
|
|
$alt = $change['alt'];
|
|
$neu = $change['neu'];
|
|
|
|
// Spezielle Formatierungen
|
|
if ($field === 'STATUS') {
|
|
$alt = $statusMap[$alt] ?? $alt;
|
|
$neu = $statusMap[$neu] ?? $neu;
|
|
}
|
|
|
|
if (in_array($field, ['STARTZEIT','ENDZEIT','REALENDZEIT'])) {
|
|
if ($alt > 0) $alt = date('d.m.Y H:i', $alt);
|
|
$neu = date('d.m.Y H:i', $neu);
|
|
}
|
|
|
|
if ($field === 'ANWESEND') {
|
|
$altArr = json_decode($alt, true) ?: [];
|
|
$neuArr = json_decode($neu, true) ?: [];
|
|
|
|
$alt = implode(', ', array_map(fn($id)=>$Data->getDataByID($id,"name"), $altArr));
|
|
$neu = implode(', ', array_map(fn($id)=>$Data->getDataByID($id,"name"), $neuArr));
|
|
}
|
|
|
|
// CREATE hat keinen alten Wert
|
|
$isCreate = ($field === 'CREATE');
|
|
$isDelete = ($field === 'DELETE');
|
|
?>
|
|
|
|
<!-- KLICKBARE ÄNDERUNG -->
|
|
<a href="termin_show.php?id=<?= $termId ?>"
|
|
class="text-decoration-none text-dark">
|
|
<div class="activity-entry">
|
|
|
|
<div class="activity-time">
|
|
<i class="bi bi-clock"></i> <?= $timeStr ?>
|
|
• <span class="text-primary">#<?= $termId ?></span>
|
|
</div>
|
|
|
|
<div>
|
|
<strong><?= htmlspecialchars($userName) ?></strong>
|
|
|
|
<?php if ($isCreate): ?>
|
|
|
|
hat einen neuen <strong>TERMIN</strong> erstellt.
|
|
|
|
<?php elseif ($isDelete): ?>
|
|
|
|
hat einen <strong>TERMIN</strong> gelöscht.
|
|
|
|
<?php else: ?>
|
|
|
|
hat <strong><?= htmlspecialchars($field) ?></strong> geändert:
|
|
<br>
|
|
<span class="text-danger fw-semibold"><?= htmlspecialchars($alt) ?></span>
|
|
→
|
|
<span class="text-success fw-semibold"><?= htmlspecialchars($neu) ?></span>
|
|
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|