initial commit
This commit is contained in:
@@ -0,0 +1,439 @@
|
||||
<?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>
|
||||
Reference in New Issue
Block a user