891 lines
31 KiB
PHP
891 lines
31 KiB
PHP
<?php
|
||
|
||
date_default_timezone_set("Europe/Berlin");
|
||
require_once "config/config.php";
|
||
include("Data.class.php");
|
||
$Data = new Data($pdo);
|
||
|
||
/* ===============================
|
||
KALENDER HASH – Hybrid Phase 1
|
||
=============================== */
|
||
function buildCalendarHash(array $termin): string {
|
||
$data = [
|
||
'start' => (int)$termin['startzeit'],
|
||
'end' => (int)$termin['endzeit'],
|
||
'title' => trim((string)$termin['location']),
|
||
'desc' => trim((string)$termin['beschreibung']),
|
||
];
|
||
|
||
return hash('sha256', json_encode($data));
|
||
}
|
||
|
||
|
||
$statusMap = [
|
||
"0" => "Offen",
|
||
"1" => "Bereit",
|
||
"2" => "Erledigt",
|
||
"3" => "Storniert"
|
||
];
|
||
|
||
require_once "config/session.php";
|
||
|
||
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||
header("location: index.php");
|
||
exit;
|
||
}
|
||
|
||
|
||
if(isset($_GET["id"])){
|
||
$id = $_GET["id"];
|
||
}
|
||
if(isset($_POST["id"])){
|
||
$id = $_POST["id"];
|
||
}
|
||
|
||
/* ===============================
|
||
TERMIN LADEN (für Kalenderstatus)
|
||
=============================== */
|
||
$stmt = $pdo->prepare("SELECT * FROM termine WHERE id = :id");
|
||
$stmt->execute([':id' => $id]);
|
||
$termin = $stmt->fetch(PDO::FETCH_ASSOC);
|
||
|
||
// ===============================
|
||
// RECHNUNG ZU DIESEM TERMIN?
|
||
// ===============================
|
||
$invoice = null;
|
||
try {
|
||
$stInv = $pdo->prepare("SELECT id, invoice_no FROM invoices WHERE termine_id = :tid ORDER BY id DESC LIMIT 1");
|
||
$stInv->execute([':tid' => $id]);
|
||
$invoice = $stInv->fetch(PDO::FETCH_ASSOC) ?: null;
|
||
} catch (Throwable $e) {
|
||
// Falls Tabellen noch nicht existieren oder so: einfach ignorieren
|
||
$invoice = null;
|
||
}
|
||
|
||
|
||
/* ===============================
|
||
KALENDER STATUS BERECHNEN
|
||
=============================== */
|
||
$currentHash = buildCalendarHash($termin);
|
||
|
||
if (empty($termin['calendar_event_id'])) {
|
||
$calendarStatus = 'missing'; // 🔴
|
||
} elseif ($termin['calendar_hash'] !== $currentHash) {
|
||
$calendarStatus = 'outdated'; // 🟡
|
||
} else {
|
||
$calendarStatus = 'ok'; // 🟢
|
||
}
|
||
|
||
|
||
|
||
$per_page = 5; // Anzahl der Changelog-Einträge pro Seite
|
||
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
||
if ($page < 1) $page = 1;
|
||
|
||
$offset = ($page - 1) * $per_page;
|
||
|
||
// Gesamtanzahl Einträge
|
||
$count_sql = $pdo->prepare("SELECT COUNT(*) FROM changes WHERE termin_id = :id");
|
||
$count_sql->execute([':id' => $id]);
|
||
$total_entries = $count_sql->fetchColumn();
|
||
$total_pages = ceil($total_entries / $per_page);
|
||
|
||
// Daten holen
|
||
$sql = $pdo->prepare("
|
||
SELECT * FROM changes
|
||
WHERE termin_id = :id
|
||
ORDER BY timestamp DESC
|
||
LIMIT :limit OFFSET :offset
|
||
");
|
||
|
||
$sql->bindValue(':id', $id, PDO::PARAM_INT);
|
||
$sql->bindValue(':limit', $per_page, PDO::PARAM_INT);
|
||
$sql->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||
$sql->execute();
|
||
|
||
$changes = $sql->fetchAll(PDO::FETCH_ASSOC);
|
||
|
||
|
||
if(isset($_POST['save'])){
|
||
|
||
if(isset($_POST['inputStartzeit_ts']) && !empty($_POST['inputStartzeit_ts'])){
|
||
$startzeit = $_POST['inputStartzeit_ts'];
|
||
if($Data->getTerminDataById($id, "startzeit") != $startzeit){
|
||
$Data->createChanges($id, $_SESSION["id"], "startzeit", $Data->getTerminDataById($id, "startzeit"), $startzeit);
|
||
$Data->changeTermin($id, "startzeit", $startzeit);
|
||
}
|
||
}
|
||
|
||
if(isset($_POST['inputEndzeit_ts']) && !empty($_POST['inputEndzeit_ts'])){
|
||
$endzeit = $_POST['inputEndzeit_ts'];
|
||
if($Data->getTerminDataById($id, "endzeit") != $endzeit){
|
||
$Data->createChanges($id, $_SESSION["id"], "endzeit", $Data->getTerminDataById($id, "endzeit"), $endzeit);
|
||
$Data->changeTermin($id, "endzeit", $endzeit);
|
||
}
|
||
}
|
||
|
||
if(isset($_POST['inputLocation'])){
|
||
$location = $_POST['inputLocation'];
|
||
if($Data->getTerminDataById($id, "location") != $location){
|
||
$Data->createChanges($id, $_SESSION["id"], "location", $Data->getTerminDataById($id, "location"), $location);
|
||
$Data->changeTermin($id, "location", $location);
|
||
}
|
||
}
|
||
|
||
if(isset($_POST['inputGage'])){
|
||
$gage = $_POST['inputGage'];
|
||
if($Data->getTerminDataById($id, "gage") != $gage){
|
||
$Data->createChanges($id, $_SESSION["id"], "gage", $Data->getTerminDataById($id, "gage"), $gage);
|
||
$Data->changeTermin($id, "gage", $gage);
|
||
}
|
||
}
|
||
|
||
if (isset($_POST['users'])) {
|
||
// Neue Daten
|
||
$userIDs_new = array_map('intval', $_POST['users']);
|
||
$json_new = json_encode($userIDs_new);
|
||
|
||
// Alte Daten holen und in Array umwandeln
|
||
$old_json = $Data->getTerminDataById($id, "anwesend");
|
||
$userIDs_old = json_decode($old_json, true);
|
||
|
||
// Falls alt NULL oder kein Array
|
||
if (!is_array($userIDs_old)) {
|
||
$userIDs_old = [];
|
||
}
|
||
|
||
// Arrays vergleichen – unabhängig von Reihenfolge
|
||
if (array_diff($userIDs_old, $userIDs_new) || array_diff($userIDs_new, $userIDs_old)) {
|
||
|
||
// Changelog schreiben
|
||
$Data->createChanges(
|
||
$id,
|
||
$_SESSION["id"],
|
||
"anwesend",
|
||
json_encode($userIDs_old),
|
||
$json_new
|
||
);
|
||
|
||
// Speichern
|
||
$Data->changeTermin($id, "anwesend", $json_new);
|
||
}
|
||
}
|
||
|
||
if (isset($_POST['inputBeschreibung'])) {
|
||
$beschreibung = trim($_POST['inputBeschreibung']);
|
||
if ($Data->getTerminDataById($id, "beschreibung") != $beschreibung) {
|
||
$Data->createChanges($id, $_SESSION["id"], "beschreibung", "", "");
|
||
$Data->changeTermin($id, "beschreibung", $beschreibung);
|
||
}
|
||
}
|
||
|
||
|
||
if(isset($_POST['status'])){
|
||
$status = $_POST['status'];
|
||
if($Data->getTerminDataById($id, "status") != $status){
|
||
$Data->createChanges($id, $_SESSION["id"], "status", $Data->getTerminDataById($id, "status"), $status);
|
||
$Data->changeTermin($id, "status", $status);
|
||
if($status == "3"){
|
||
$Data->deleteCalendarEvent($id);
|
||
header("location: termin_show.php?id=" . $id);
|
||
exit;
|
||
}
|
||
}
|
||
}
|
||
|
||
$Data->updateCalendarEvent($id);
|
||
|
||
header("location: termin_show.php?id=" . $id);
|
||
exit;
|
||
}
|
||
|
||
include('navbar.php');
|
||
|
||
?>
|
||
<style>
|
||
/* ---------- CHANGES TABLE / TIMELINE OPTIMIERUNG ---------- */
|
||
|
||
.select2-container--classic .select2-selection--multiple {
|
||
border-radius: 8px;
|
||
padding: 6px;
|
||
border-color: #ced4da;
|
||
}
|
||
|
||
.select2-container--classic .select2-selection--multiple .select2-selection__choice {
|
||
background: #0d6efd;
|
||
color: white;
|
||
border: none;
|
||
padding: 3px 8px;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove {
|
||
color: white;
|
||
margin-right: 5px;
|
||
}
|
||
|
||
|
||
.changelog-table {
|
||
border-collapse: separate !important;
|
||
border-spacing: 0 12px !important; /* Abstand zwischen den Reihen */
|
||
}
|
||
|
||
.changelog-row {
|
||
background: #ffffff;
|
||
border-radius: 10px;
|
||
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
|
||
}
|
||
|
||
.changelog-date {
|
||
font-weight: 600;
|
||
color: #444;
|
||
white-space: nowrap;
|
||
padding-top: 12px !important;
|
||
}
|
||
|
||
.change-entry {
|
||
padding: 14px 18px !important;
|
||
border-left: 4px solid #0d6efd;
|
||
background: #f8f9fa !important;
|
||
border-radius: 8px;
|
||
font-size: 15px;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.change-entry strong {
|
||
color: #000;
|
||
}
|
||
|
||
.change-old {
|
||
color: #dc3545 !important;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.change-new {
|
||
color: #198754 !important;
|
||
font-weight: 700;
|
||
}
|
||
|
||
/* Mobile Version */
|
||
@media (max-width: 768px) {
|
||
.changelog-table {
|
||
border-spacing: 0 10px !important;
|
||
}
|
||
|
||
.change-entry {
|
||
font-size: 16px;
|
||
padding: 16px 16px !important;
|
||
}
|
||
|
||
.changelog-date {
|
||
font-size: 14px;
|
||
margin-bottom: 4px;
|
||
display: block;
|
||
}
|
||
}
|
||
|
||
/* MOBILE: Datum/Uhrzeit schmaler machen */
|
||
@media (max-width: 768px) {
|
||
|
||
/* Datum/Uhrzeit-Spalte schmaler */
|
||
.changelog-date {
|
||
width: 95px !important; /* vorher ~150–180px */
|
||
font-size: 13px !important; /* leicht verkleinern */
|
||
text-align: left;
|
||
}
|
||
|
||
/* Änderungstext bekommt mehr Platz */
|
||
.change-entry {
|
||
font-size: 15px !important;
|
||
padding: 14px 14px !important;
|
||
}
|
||
|
||
/* Tabelle anpassen */
|
||
.changelog-table th:first-child {
|
||
width: 95px !important;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
||
<!-- Bootstrap CSS -->
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||
|
||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||
|
||
<!-- Bootstrap Icons -->
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||
|
||
<title>ANJUMA Dashboard</title>
|
||
</head>
|
||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||
<div class="container">
|
||
<div class="row justify-content-center">
|
||
<div class="col-12 col-md-10">
|
||
|
||
<form action="termin_show.php" method="post">
|
||
|
||
<div class="row g-3">
|
||
<!-- Startzeit (nicht änderbar) -->
|
||
<div class="col-md-6">
|
||
<label class="form-label fw-bold">Startzeit</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text"><i class="bi bi-clock-fill"></i></span>
|
||
<input
|
||
type="datetime-local"
|
||
class="form-control"
|
||
id="inputStartzeit"
|
||
value="<?php echo date('Y-m-d\TH:i', $Data->getTerminDataById($id, 'startzeit')); ?>"
|
||
>
|
||
<input type="hidden" name="inputStartzeit_ts" id="inputStartzeit_ts">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Endzeit -->
|
||
<div class="col-md-6">
|
||
<label class="form-label fw-bold">Endzeit</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text"><i class="bi bi-clock-fill"></i></span>
|
||
<input
|
||
type="datetime-local"
|
||
class="form-control"
|
||
id="inputEndzeit"
|
||
value="<?php echo date('Y-m-d\TH:i', $Data->getTerminDataById($id, 'endzeit')); ?>"
|
||
>
|
||
<input type="hidden" name="inputEndzeit_ts" id="inputEndzeit_ts">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Location -->
|
||
<div class="col-md-6">
|
||
<label class="form-label fw-bold">Location</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text"><i class="bi bi-geo-alt"></i></span>
|
||
<input type="text" class="form-control" name="inputLocation"
|
||
value="<?php echo $Data->getTerminDataById($id, 'location') ?>">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Gage -->
|
||
<div class="col-md-6">
|
||
<label class="form-label fw-bold">Gage</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text"><i class="bi bi-cash-stack"></i></span>
|
||
<input type="text" class="form-control" name="inputGage"
|
||
value="<?php echo $Data->getTerminDataById($id, 'gage') ?>">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Beschreibung -->
|
||
<div class="col-12">
|
||
<label class="form-label fw-bold">Beschreibung</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text"><i class="bi bi-card-text"></i></span>
|
||
<textarea
|
||
class="form-control"
|
||
name="inputBeschreibung"
|
||
id="beschreibungField"
|
||
rows="1"
|
||
style="overflow:hidden; resize:none;"
|
||
><?php
|
||
echo htmlspecialchars($Data->getTerminDataById($id, 'beschreibung'));
|
||
?></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Beteiligte Personen -->
|
||
<div class="col-md-6">
|
||
<label class="form-label fw-bold">Anwesend</label>
|
||
<select class="form-select select2-users" name="users[]" multiple>
|
||
<?php
|
||
$userList = $pdo->query("SELECT id, name FROM users ORDER BY name")->fetchAll(PDO::FETCH_ASSOC);
|
||
$selectedUsers = json_decode($Data->getTerminDataById($id, "anwesend"), true) ?: [];
|
||
|
||
foreach ($userList as $u) {
|
||
$sel = in_array($u['id'], $selectedUsers) ? "selected" : "";
|
||
echo "<option value='{$u['id']}' $sel>{$u['name']}</option>";
|
||
}
|
||
?>
|
||
</select>
|
||
</div>
|
||
|
||
|
||
</div>
|
||
|
||
<!-- Status -->
|
||
<div class="btn-group my-4" role="group">
|
||
<label class="btn btn-primary">
|
||
<input type="radio" name="status" value="0" <?php if($Data->getTerminDataById($id, "status") == 0){ echo "checked"; } ?>> Offen
|
||
</label>
|
||
|
||
<label class="btn btn-warning">
|
||
<input type="radio" name="status" value="1" <?php if($Data->getTerminDataById($id, "status") == 1){ echo "checked"; } ?>> Bereit
|
||
</label>
|
||
|
||
<label class="btn btn-success">
|
||
<input type="radio" name="status" value="2" <?php if($Data->getTerminDataById($id, "status") == 2){ echo "checked"; } ?>> Abgeschlossen
|
||
</label>
|
||
|
||
<label class="btn btn-danger">
|
||
<input type="radio" name="status" value="3" <?php if($Data->getTerminDataById($id, "status") == 3){ echo "checked"; } ?>> Storniert
|
||
</label>
|
||
</div>
|
||
|
||
<input type="hidden" name="id" value="<?php echo $id ?>">
|
||
|
||
|
||
<?php if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin'): ?>
|
||
<button type="button" class="btn btn-danger ms-3"
|
||
data-bs-toggle="modal" data-bs-target="#deleteModal">
|
||
<i class="bi bi-trash"></i> Termin löschen
|
||
</button>
|
||
<?php endif; ?>
|
||
|
||
<button type="submit" name="save" class="btn btn-primary">
|
||
<i class="bi bi-save"></i> Speichern
|
||
</button>
|
||
|
||
<?php
|
||
$terminStatus = (int)$Data->getTerminDataById($id, "status");
|
||
?>
|
||
|
||
<?php if ($terminStatus === 2): ?>
|
||
<?php if ($invoice): ?>
|
||
<a class="btn btn-outline-success ms-3"
|
||
href="/api/invoice_pdf.php?id=<?= (int)$invoice['id'] ?>"
|
||
target="_blank">
|
||
<i class="bi bi-receipt"></i> PDF öffnen (<?= htmlspecialchars($invoice['invoice_no']) ?>)
|
||
</a>
|
||
<?php else: ?>
|
||
<button type="button" class="btn btn-success ms-3" id="btnInvoiceOpen">
|
||
<i class="bi bi-receipt"></i> Rechnung erstellen
|
||
</button>
|
||
<?php endif; ?>
|
||
<?php endif; ?>
|
||
|
||
|
||
<!-- ===============================
|
||
KALENDER STATUS (Hybrid Phase 1)
|
||
=============================== -->
|
||
<div class="mb-3">
|
||
|
||
<?php if ($calendarStatus === 'ok'): ?>
|
||
<span class="badge bg-success">
|
||
📅 Kalender aktuell
|
||
<?php if (!empty($termin['calendar_synced_at'])): ?>
|
||
<small>(<?= date('d.m.Y H:i', $termin['calendar_synced_at']) ?>)</small>
|
||
<?php endif; ?>
|
||
</span>
|
||
|
||
<?php elseif ($calendarStatus === 'outdated'): ?>
|
||
<span class="badge bg-warning text-dark">
|
||
📅 Kalender nicht aktuell
|
||
</span>
|
||
|
||
<?php else: ?>
|
||
<span class="badge bg-danger">
|
||
📅 Kein Kalender-Eintrag
|
||
</span>
|
||
<?php endif; ?>
|
||
|
||
</div>
|
||
|
||
<?php if ($calendarStatus === 'outdated'): ?>
|
||
<div class="alert alert-warning py-2">
|
||
⚠️ Der Termin wurde geändert, der Kalendereintrag ist nicht mehr aktuell.
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
<!-- Changelog -->
|
||
<div class="container mt-5">
|
||
<div class="row justify-content-center">
|
||
<div class="col-12 col-md-10 col-lg-10">
|
||
|
||
<table class="table changelog-table">
|
||
<thead class="table-dark">
|
||
<tr>
|
||
<th style="width: 180px;">Datum/Uhrzeit</th>
|
||
<th>Änderung</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php
|
||
foreach ($changes as $row) {
|
||
|
||
$datum = $Data->timestampToDate($row['timestamp']);
|
||
$name = $Data->getDataByID($row['user_id'], "name");
|
||
$feld = strtoupper($row['was']);
|
||
$alt = $row['alt'];
|
||
$neu = $row['neu'];
|
||
|
||
if ($row['was'] === 'anwesend'){
|
||
$alt = json_decode($row["alt"], true) ?: [];
|
||
$neu = json_decode($row["neu"], true) ?: [];
|
||
|
||
$alt = array_map(fn($uid) => $Data->getDataByID($uid, "name"), $alt);
|
||
$neu = array_map(fn($uid) => $Data->getDataByID($uid, "name"), $neu);
|
||
}
|
||
|
||
if ($row['was'] === 'status') {
|
||
$alt = $statusMap[$alt] ?? $alt;
|
||
$neu = $statusMap[$neu] ?? $neu;
|
||
}
|
||
|
||
if (in_array($row['was'], ['startzeit','endzeit','realendzeit'])) {
|
||
if ($alt > 0) $alt = date('d.m.Y H:i', $alt);
|
||
$neu = date('d.m.Y H:i', $neu);
|
||
}
|
||
|
||
echo "<tr class='changelog-row'>
|
||
<td class='changelog-date'>$datum</td>
|
||
<td>
|
||
<div class='change-entry'>";
|
||
|
||
if ($row['was'] === 'create') {
|
||
echo "<strong>$name</strong> hat diesen <strong>Termin</strong> erstellt!";
|
||
}
|
||
elseif ($row['was'] === 'anwesend') {
|
||
echo "<strong>$name</strong> hat <strong>$feld</strong> geändert von
|
||
<span class='change-old'>".implode(', ', $alt)."</span>
|
||
zu <span class='change-new'>".implode(', ', $neu)."</span>.";
|
||
}
|
||
elseif ($row['was'] === 'startzeit' && $row['alt'] == 0) {
|
||
echo "<strong>$name</strong> hat <strong>STARTZEIT</strong> geändert zu
|
||
<span class='change-new'>$neu</span>.";
|
||
}
|
||
elseif ($row['was'] === 'beschreibung') {
|
||
echo "<strong>$name</strong> hat die <strong>Beschreibung</strong> geändert!";
|
||
}else{
|
||
echo "<strong>$name</strong> hat <strong>$feld</strong> geändert von
|
||
<span class='change-old'>$alt</span>
|
||
zu <span class='change-new'>$neu</span>.";
|
||
}
|
||
|
||
echo " </div>
|
||
</td>
|
||
</tr>";
|
||
}
|
||
?>
|
||
</tbody>
|
||
</table>
|
||
<!-- Pagination -->
|
||
<nav>
|
||
<ul class="pagination justify-content-center">
|
||
|
||
<!-- Prev Button -->
|
||
<li class="page-item <?php if ($page <= 1) echo 'disabled'; ?>">
|
||
<a class="page-link" href="?id=<?php echo $id; ?>&page=<?php echo $page - 1; ?>">
|
||
« Zurück
|
||
</a>
|
||
</li>
|
||
|
||
<!-- Seitenzahlen -->
|
||
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
|
||
<li class="page-item <?php if ($i == $page) echo 'active'; ?>">
|
||
<a class="page-link" href="?id=<?php echo $id; ?>&page=<?php echo $i; ?>">
|
||
<?php echo $i; ?>
|
||
</a>
|
||
</li>
|
||
<?php endfor; ?>
|
||
|
||
<!-- Next Button -->
|
||
<li class="page-item <?php if ($page >= $total_pages) echo 'disabled'; ?>">
|
||
<a class="page-link" href="?id=<?php echo $id; ?>&page=<?php echo $page + 1; ?>">
|
||
Weiter »
|
||
</a>
|
||
</li>
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Invoice Modal -->
|
||
<div class="modal fade" id="invoiceModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog modal-dialog-centered">
|
||
<form class="modal-content" id="invoiceForm">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">Rechnung erstellen</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
|
||
<div class="mb-2">
|
||
<label class="form-label mb-1">Kunde / Firma *</label>
|
||
<input type="text" class="form-control" id="invCustomerName" required placeholder="z.B. Lug Lounge GmbH">
|
||
</div>
|
||
|
||
<div class="mb-2">
|
||
<label class="form-label mb-1">Straße & Nr.</label>
|
||
<input type="text" class="form-control" id="invStreet" placeholder="Musterstraße 1">
|
||
</div>
|
||
|
||
<div class="row g-2">
|
||
<div class="col-5">
|
||
<label class="form-label mb-1">PLZ</label>
|
||
<input type="text" class="form-control" id="invZip" placeholder="00000">
|
||
</div>
|
||
<div class="col-7">
|
||
<label class="form-label mb-1">Ort</label>
|
||
<input type="text" class="form-control" id="invCity" placeholder="Nienburg">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-2">
|
||
<label class="form-label mb-1">Land</label>
|
||
<input type="text" class="form-control" id="invCountry" value="DE">
|
||
</div>
|
||
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||
<button type="submit" class="btn btn-success" id="btnCreateInvoice">Erstellen & PDF</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Delete Modal -->
|
||
<div class="modal fade" id="deleteModal" tabindex="-1">
|
||
<div class="modal-dialog modal-dialog-centered">
|
||
<div class="modal-content">
|
||
|
||
<div class="modal-header bg-danger text-white">
|
||
<h5 class="modal-title">Termin löschen?</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
|
||
<div class="modal-body">
|
||
<p>Bist du sicher, dass du diesen Termin löschen möchtest?</p>
|
||
<ul>
|
||
<li>Der Termin wird vollständig entfernt</li>
|
||
<li>Alle Änderungen (Changelog) werden gelöscht</li>
|
||
<li>Kalendereintrag wird entfernt</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||
|
||
<form action="termin_delete.php" method="POST">
|
||
<input type="hidden" name="id" value="<?= $id ?>">
|
||
<button type="submit" class="btn btn-danger">
|
||
<i class="bi bi-trash"></i> Ja, löschen
|
||
</button>
|
||
</form>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- MODAL: Termin existiert -->
|
||
<div class="modal fade" id="terminConflictModal" tabindex="-1">
|
||
<div class="modal-dialog modal-dialog-centered">
|
||
<div class="modal-content">
|
||
|
||
<div class="modal-header bg-danger text-white">
|
||
<h5 class="modal-title">Achtung – Terminüberschneidung!</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
|
||
<div class="modal-body">
|
||
<p>Für dieses Datum existiert bereits mindestens ein Termin:</p>
|
||
<div id="terminConflictList" class="border rounded p-2 bg-light"></div>
|
||
</div>
|
||
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-bs-dismiss="modal">Okay</button>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||
|
||
<script>
|
||
document.addEventListener("DOMContentLoaded", () => {
|
||
|
||
const startInput = document.getElementById("inputStartzeit");
|
||
|
||
startInput.addEventListener("change", async () => {
|
||
if (!startInput.value) return;
|
||
|
||
// ISO → Timestamp
|
||
const ts = Math.floor(new Date(startInput.value).getTime() / 1000);
|
||
|
||
const res = await fetch(`check_date.php?ts=${ts}`);
|
||
const data = await res.json();
|
||
|
||
if (Array.isArray(data) && data.length > 0) {
|
||
|
||
let html = "";
|
||
data.forEach(t => {
|
||
html += `
|
||
<div class="p-2 mb-2 bg-white rounded border">
|
||
<strong>Termin #${t.id}</strong><br>
|
||
Start: ${new Date(t.startzeit * 1000).toLocaleString()}<br>
|
||
Location: ${t.location}
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
document.getElementById("terminConflictList").innerHTML = html;
|
||
|
||
// Modal anzeigen
|
||
const myModal = new bootstrap.Modal(document.getElementById("terminConflictModal"));
|
||
myModal.show();
|
||
}
|
||
});
|
||
|
||
});
|
||
|
||
|
||
document.addEventListener("DOMContentLoaded", function() {
|
||
|
||
// Select2 aktivieren
|
||
$('.select2-users').select2({
|
||
placeholder: "Anwesende auswählen…",
|
||
allowClear: true,
|
||
width: '100%',
|
||
theme: "classic"
|
||
});
|
||
|
||
});
|
||
|
||
|
||
function convertToTimestamp(idInput, idHidden) {
|
||
const input = document.getElementById(idInput);
|
||
const hidden = document.getElementById(idHidden);
|
||
|
||
if (input && hidden && input.value) {
|
||
const ts = Math.floor(new Date(input.value).getTime() / 1000);
|
||
hidden.value = ts;
|
||
}
|
||
}
|
||
|
||
document.addEventListener("DOMContentLoaded", () => {
|
||
const startzeit = document.getElementById("inputStartzeit");
|
||
const endzeit = document.getElementById("inputEndzeit");
|
||
const real = document.getElementById("inputRealEndzeit");
|
||
|
||
if (endzeit) {
|
||
endzeit.addEventListener("change", () => {
|
||
convertToTimestamp("inputEndzeit", "inputEndzeit_ts");
|
||
});
|
||
}
|
||
|
||
if (real) {
|
||
real.addEventListener("change", () => {
|
||
convertToTimestamp("inputRealEndzeit", "inputRealEndzeit_ts");
|
||
});
|
||
}
|
||
|
||
if (startzeit) {
|
||
startzeit.addEventListener("change", () => {
|
||
convertToTimestamp("inputStartzeit", "inputStartzeit_ts");
|
||
});
|
||
}
|
||
});
|
||
|
||
function autoResizeTextarea(el) {
|
||
el.style.height = 'auto';
|
||
el.style.height = (el.scrollHeight) + 'px';
|
||
}
|
||
|
||
document.addEventListener("DOMContentLoaded", function() {
|
||
const textarea = document.getElementById("beschreibungField");
|
||
|
||
if (textarea) {
|
||
// Beim Laden automatisch anpassen
|
||
autoResizeTextarea(textarea);
|
||
|
||
// Beim Eingeben automatisch anpassen
|
||
textarea.addEventListener('input', function() {
|
||
autoResizeTextarea(textarea);
|
||
});
|
||
}
|
||
});
|
||
|
||
// === Rechnung erstellen (Termin Show) ===
|
||
const API_INVOICE_CREATE = '/api/invoice_create_from_termin.php';
|
||
const API_INVOICE_PDF = '/api/invoice_pdf.php?id=';
|
||
const terminIdForInvoice = <?= (int)$id ?>;
|
||
|
||
document.addEventListener("DOMContentLoaded", () => {
|
||
const btnOpen = document.getElementById("btnInvoiceOpen");
|
||
if (!btnOpen) return;
|
||
|
||
const invoiceModalEl = document.getElementById('invoiceModal');
|
||
const invoiceModal = new bootstrap.Modal(invoiceModalEl);
|
||
|
||
btnOpen.addEventListener('click', () => {
|
||
// reset
|
||
document.getElementById('invCustomerName').value = '';
|
||
document.getElementById('invStreet').value = '';
|
||
document.getElementById('invZip').value = '';
|
||
document.getElementById('invCity').value = '';
|
||
document.getElementById('invCountry').value = 'DE';
|
||
|
||
invoiceModal.show();
|
||
});
|
||
|
||
document.getElementById('invoiceForm').addEventListener('submit', async (ev) => {
|
||
ev.preventDefault();
|
||
|
||
const cname = document.getElementById('invCustomerName').value.trim();
|
||
if (!cname) { alert('Bitte Kunde/Firma eintragen'); return; }
|
||
|
||
const btn = document.getElementById('btnCreateInvoice');
|
||
btn.disabled = true;
|
||
btn.textContent = 'Erstelle…';
|
||
|
||
try {
|
||
const fd = new FormData();
|
||
fd.append('termin_id', terminIdForInvoice);
|
||
fd.append('customer_name', cname);
|
||
fd.append('customer_street', document.getElementById('invStreet').value.trim());
|
||
fd.append('customer_zip', document.getElementById('invZip').value.trim());
|
||
fd.append('customer_city', document.getElementById('invCity').value.trim());
|
||
fd.append('customer_country', document.getElementById('invCountry').value.trim() || 'DE');
|
||
|
||
const res = await fetch(API_INVOICE_CREATE, { method: 'POST', body: fd });
|
||
const json = await res.json();
|
||
|
||
if (!json.ok) throw new Error(json.error || 'Rechnung konnte nicht erstellt werden');
|
||
|
||
invoiceModal.hide();
|
||
|
||
// PDF öffnen
|
||
window.open(API_INVOICE_PDF + json.invoice_id, '_blank');
|
||
|
||
// Seite neu laden, damit Button auf "PDF öffnen" wechselt
|
||
window.location.reload();
|
||
|
||
} catch (e) {
|
||
alert('Fehler: ' + e.message);
|
||
} finally {
|
||
btn.disabled = false;
|
||
btn.textContent = 'Erstellen & PDF';
|
||
}
|
||
});
|
||
});
|
||
|
||
</script>
|
||
|