Files
intranet/web/intranet_repo/projects/seelsorge/content/visit.php
T

1306 lines
58 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php /* Name: visit.php
Beschreibung: Verwaltung von Besuchsstellen (Redesign)
- Moderne Karten-Tabelle mit Haushalt-Gruppierung
- Detail-Modal beim Klick auf Zeile
- Edit-Modal für einzelne Personen */
// Workaround: Festlegung für Profilaufrufe (JS-Funktion)
$isPageVisit = true;
$profile_container_name = 'profile_visit';
$profile_overlay_id = 'profile_visit_overlay';
$current_user_id = $_SESSION['user_id']; // Aktuell angemeldeter Benutzer
// Priester-Liste für Dropdown
$emailCol = null;
$emailCandidates = ['email', 'mail', 'email_address', 'e_mail', 'user_email', 'emailadresse', 'mailadresse'];
$inList = "'" . implode("','", array_map(function($c){ return str_replace("'", "''", $c); }, $emailCandidates)) . "'";
$sql_email_col = "SELECT column_name FROM information_schema.columns WHERE table_name = 'user_general_information' AND column_name IN ($inList) LIMIT 1";
$res_email_col = $pdo->query($sql_email_col);
if ($res_email_col && $r = $res_email_col->fetch()) { $emailCol = $r['column_name']; }
$priests = [];
if ($emailCol !== null) {
$sql_priests = "SELECT u.user_id, u.first_name, u.last_name, u.\"$emailCol\" AS email FROM user_general_information u JOIN user_permissions p ON p.user_id = u.user_id WHERE p.\"PriesterIn\" = 1 ORDER BY u.last_name ASC, u.first_name ASC";
} else {
$sql_priests = "SELECT u.user_id, u.first_name, u.last_name, '' AS email FROM user_general_information u JOIN user_permissions p ON p.user_id = u.user_id WHERE p.\"PriesterIn\" = 1 ORDER BY u.last_name ASC, u.first_name ASC";
}
$stmt = $pdo->prepare($sql_priests);
if ($stmt) { $stmt->execute(); while ($row = $stmt->fetch()) $priests[] = $row; }
// Profilbilder der Priester ermitteln
$priestImages = [];
foreach ($priests as $p) {
$pid = (int)$p['user_id'];
$imgs = glob($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/profile_pictures/{$pid}-*.jpg");
if (!empty($imgs)) {
usort($imgs, function($a, $b) { return filemtime($b) - filemtime($a); });
$priestImages[$pid] = str_replace($_SERVER['DOCUMENT_ROOT'], '', $imgs[0]);
} else {
$priestImages[$pid] = '/intranet_repo/general_files/profile_pictures/default.jpg';
}
}
if (!function_exists('renderPriestOptions')) {
function renderPriestOptions($priests) {
$out = "";
foreach ($priests as $p) {
$uid = (int)($p['user_id'] ?? 0);
$first = trim((string)($p['first_name'] ?? ''));
$last = trim((string)($p['last_name'] ?? ''));
$email = trim((string)($p['email'] ?? ''));
if ($first === '' && $last === '') {
$label = ($email !== '') ? $email : ("User #".$uid);
} else {
if ($last !== '' && $first !== '') { $label = $last . ", " . $first; }
elseif ($last !== '') { $label = $last; }
else { $label = $first; }
}
$out .= "<option value=\"{$uid}\">" . htmlspecialchars($label, ENT_QUOTES) . "</option>";
}
return $out;
}
}
?>
<style>
/* --- Shell --- */
.visit-shell {
padding: 18px;
width: 100%;
background: linear-gradient(135deg, var(--gray-50) 0%, var(--theme-light, var(--nak-blue-light)) 100%);
min-height: 100%;
}
.visit-card {
overflow: hidden;
border: 1px solid rgba(120, 140, 160, 0.22);
border-radius: 0;
background: rgba(255,255,255,0.86);
box-shadow: 0 20px 45px rgba(31, 38, 46, 0.10), 0 1px 0 rgba(255,255,255,0.9) inset;
}
.visit-list-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 16px 18px 12px 18px;
border-bottom: 1px solid rgba(120, 140, 160, 0.16);
background: linear-gradient(180deg, rgba(255,255,255,0.92), rgba(255,255,255,0.72));
}
.visit-list-title {
font-size: 1.05em;
font-weight: 800;
color: hsl(210, 20%, 15%);
}
.visit-list-actions {
display: flex;
gap: 8px;
align-items: center;
}
.visit-action-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 7px 14px;
border: 1px solid hsl(210, 18%, 84%);
border-radius: 0;
background: #FFFFFF;
color: hsl(210, 20%, 22%);
font-size: 0.86em;
font-weight: 850;
cursor: pointer;
box-shadow: 0 4px 12px rgba(31,38,46,0.06);
text-decoration: none;
transition: background 0.18s ease;
}
.visit-action-btn:hover { background: hsl(210, 57%, 96%); }
.visit-action-btn-primary {
color: #FFFFFF;
background: linear-gradient(135deg, var(--nak-purple), #5c1865);
border-color: #5c1865;
}
.visit-action-btn-primary:hover { background: linear-gradient(135deg, #5c1865, #4a1352); }
/* --- Table (admin-style) --- */
table.visit-table {
display: table !important;
width: calc(100% - 24px);
margin: 8px 12px 14px 12px;
border-collapse: separate;
border-spacing: 0 8px;
padding: 0;
background: transparent;
overflow: visible;
}
.visit-table:after { display: none; }
.visit-table thead th {
position: sticky;
top: 0;
padding: 10px 14px;
color: hsl(210, 16%, 46%);
font-size: 0.78em;
font-weight: 800;
text-align: left;
letter-spacing: 0.04em;
text-transform: uppercase;
background: transparent !important;
border: none !important;
box-shadow: none;
}
.visit-table thead th::after { display: none; }
.visit-table tbody tr.visit-row {
position: relative;
cursor: pointer;
background: rgba(255, 255, 255, 0.92);
border-radius: 0;
box-shadow: 0 6px 18px rgba(31, 38, 46, 0.06);
transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
}
.visit-table tbody tr.visit-row:hover,
.visit-table tbody tr.visit-row:focus {
transform: translateY(-1px);
background: #FFFFFF;
box-shadow: 0 12px 26px rgba(31, 38, 46, 0.13);
outline: none !important;
filter: none !important;
}
.visit-table td {
padding: 12px 14px !important;
vertical-align: middle;
border: none !important;
background: transparent !important;
}
.visit-table tbody tr.visit-row td:first-child {
position: static !important;
width: 18px;
min-width: 18px;
max-width: 18px;
padding-right: 0 !important;
border-radius: 0;
}
.visit-table tbody tr.visit-row td:last-child {
border-radius: 0;
}
/* Status dot (first column) */
.visit-status-dot {
display: block;
width: 14px;
height: 14px;
border-radius: 50%;
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
}
.visit-status-dot.is-blue { background: var(--nak-blue); }
.visit-status-dot.is-red { background: var(--nak-red); }
.visit-status-dot.is-orange { background: var(--nak-orange); }
.visit-status-dot.is-green { background: var(--nak-darkgreen); }
/* Status badge (pill below name) */
.visit-status-badge {
display: inline-flex;
align-items: center;
padding: 2px 7px;
border-radius: 0;
font-size: 0.72em;
font-weight: 800;
}
.visit-status-badge.is-blue {
color: hsl(210, 57%, 30%);
background: hsl(210, 57%, 92%);
}
.visit-status-badge.is-red {
color: hsl(0, 60%, 36%);
background: hsl(0, 65%, 93%);
}
.visit-status-badge.is-orange {
color: hsl(30, 70%, 30%);
background: hsl(35, 80%, 92%);
}
.visit-status-badge.is-green {
color: hsl(145, 50%, 28%);
background: hsl(145, 50%, 92%);
}
/* Name column */
.visit-name-text {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 1em;
font-weight: 800;
color: hsl(210, 20%, 15%);
}
/* Info column */
.visit-info-cell {
font-size: 0.9em;
color: hsl(210, 14%, 40%);
font-weight: 600;
}
.visit-info-cell .visit-planned-by {
display: inline-flex;
align-items: center;
gap: 6px;
margin-top: 4px;
font-size: 0.88em;
color: hsl(210, 57%, 40%);
font-weight: 700;
}
.visit-info-cell .visit-planned-by img {
width: 24px;
height: 24px;
border-radius: 50%;
object-fit: cover;
}
.visit-empty-row td {
padding: 24px !important;
text-align: center !important;
color: hsl(210, 20%, 45%);
background: #FFFFFF !important;
border-radius: 0;
}
/* --- Detail Modal --- */
.visit-detail-overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.45);
z-index: 9999;
display: none;
align-items: center;
justify-content: center;
padding: 18px;
}
.visit-detail-card {
position: relative;
width: min(640px, calc(100vw - 24px));
max-height: min(780px, calc(100vh - 24px));
overflow: auto;
background: #FFFFFF;
border-radius: 0;
box-shadow: 0 24px 70px rgba(0,0,0,0.36);
}
.visit-detail-hero {
padding: 24px;
color: #FFFFFF;
background: radial-gradient(circle at 10% 0%, rgba(255,255,255,0.24), transparent 32%),
linear-gradient(135deg, var(--nak-purple), #5c1865);
border-radius: 0;
}
.visit-detail-hero-title {
font-size: 1.3em;
font-weight: 850;
margin-bottom: 6px;
}
.visit-detail-hero-sub {
color: rgba(255,255,255,0.8);
font-weight: 600;
}
.visit-detail-close {
position: absolute;
top: 14px;
right: 14px;
width: 34px;
height: 34px;
border-radius: 0;
border: 1px solid rgba(255,255,255,0.25);
background: rgba(255,255,255,0.16);
color: #FFFFFF;
font-size: 1.35em;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
}
.visit-detail-body {
padding: 22px 24px 24px 24px;
}
.visit-detail-section-title {
margin: 16px 0 8px 0;
font-size: 0.88em;
font-weight: 850;
text-transform: uppercase;
letter-spacing: 0.04em;
color: hsl(210, 18%, 36%);
}
.visit-detail-section-title:first-child { margin-top: 0; }
.visit-detail-member {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 14px;
border: 1px solid hsl(210, 18%, 88%);
border-radius: 0;
background: hsl(210, 12%, 98%);
margin-bottom: 8px;
}
.visit-detail-member-info {
font-weight: 700;
color: hsl(210, 20%, 18%);
}
.visit-detail-member-info .age {
font-size: 0.88em;
color: hsl(210, 14%, 46%);
font-weight: 600;
margin-left: 8px;
}
.visit-detail-member-edit {
width: 32px;
height: 32px;
border-radius: 0;
border: 1px solid hsl(210, 18%, 84%);
background: #FFF;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.9em;
transition: background 0.18s ease;
}
.visit-detail-member-edit:hover { background: hsl(210, 57%, 96%); }
.visit-detail-info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 12px;
}
.visit-detail-info-item {
padding: 10px 12px;
border: 1px solid hsl(210, 18%, 90%);
border-radius: 0;
background: hsl(210, 12%, 98.5%);
}
.visit-detail-info-label {
font-size: 0.72em;
font-weight: 850;
text-transform: uppercase;
letter-spacing: 0.04em;
color: hsl(210, 14%, 46%);
margin-bottom: 2px;
}
.visit-detail-info-value {
font-weight: 750;
color: hsl(210, 20%, 18%);
font-size: 0.95em;
}
.visit-detail-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 18px;
padding-top: 16px;
border-top: 1px solid hsl(210, 18%, 90%);
}
.visit-detail-actions .visit-action-btn { font-size: 0.88em; padding: 9px 16px; }
/* --- Edit/Picker Modals (kept from original) --- */
.visit-modal-overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.45);
z-index: 10000;
display: none;
align-items: center;
justify-content: center;
padding: 18px;
}
.visit-modal {
width: min(820px, 96vw);
max-height: 92vh;
overflow: auto;
background: #FFFFFF;
border-radius: 0;
box-shadow: 0 24px 70px rgba(0,0,0,0.36);
}
.visit-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 24px;
border-bottom: 1px solid hsl(210, 18%, 90%);
background: linear-gradient(180deg, rgba(255,255,255,0.95), rgba(255,255,255,0.8));
position: sticky;
top: 0;
z-index: 1;
border-radius: 0;
}
.visit-modal-header h2 { margin: 0; font-size: 1.15em; font-weight: 850; color: hsl(210, 20%, 15%); }
.visit-modal-close {
cursor: pointer;
border: 1px solid hsl(210, 18%, 84%);
background: #FFF;
border-radius: 0;
width: 34px;
height: 34px;
font-size: 1.3em;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
}
.visit-modal-close:hover { background: hsl(210, 12%, 95%); }
.visit-modal-body { padding: 20px 24px 24px 24px; }
.visit-picker-search {
width: 100%;
box-sizing: border-box;
padding: 12px 14px;
border-radius: 0;
border: 1px solid hsl(210, 18%, 84%);
background: hsl(210, 12%, 98%);
font-size: 1em;
font-weight: 650;
outline: none;
margin-bottom: 12px;
}
.visit-picker-search:focus { border-color: var(--nak-purple); background: #FFF; }
.visit-picker-list { display: flex; flex-direction: column; gap: 8px; }
.visit-picker-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 14px;
border-radius: 0;
border: 1px solid hsl(210, 18%, 88%);
background: #FFF;
cursor: pointer;
transition: background 0.18s ease, transform 0.18s ease;
}
.visit-picker-item:hover { background: hsl(210, 57%, 97%); transform: translateY(-1px); }
.visit-picker-name { font-weight: 800; color: hsl(210, 20%, 15%); }
.visit-picker-sub { font-size: 0.82em; color: hsl(210, 14%, 46%); margin-top: 2px; }
.visit-form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.visit-form-grid .full { grid-column: 1 / -1; }
.visit-form-field label {
display: block;
font-weight: 750;
margin-bottom: 6px;
font-size: 0.88em;
color: hsl(210, 18%, 36%);
text-transform: uppercase;
letter-spacing: 0.04em;
}
.visit-form-field input,
.visit-form-field select {
width: 100%;
box-sizing: border-box;
padding: 12px 14px;
border-radius: 0;
border: 1px solid hsl(210, 18%, 84%);
background: hsl(210, 12%, 98%);
font-size: 1em;
font-weight: 650;
outline: none;
}
.visit-form-field input:focus,
.visit-form-field select:focus { border-color: var(--nak-purple); background: #FFF; }
.visit-modal-actions {
display: flex;
gap: 8px;
justify-content: flex-end;
margin-top: 18px;
padding-top: 16px;
border-top: 1px solid hsl(210, 18%, 90%);
}
.visit-modal-actions button { cursor: pointer; border: none; border-radius: 0; padding: 10px 18px; font-weight: 850; font-size: 0.88em; }
.visit-btn-save { background: linear-gradient(135deg, hsl(145, 55%, 42%), hsl(145, 55%, 34%)); color: #fff; }
.visit-btn-cancel { background: hsl(210, 12%, 92%); color: hsl(210, 20%, 30%); }
.visit-btn-delete { background: hsl(0, 72%, 95%); color: hsl(0, 72%, 42%); border: 1px solid hsl(0, 72%, 88%); margin-right: auto; }
.visit-phone-row { display: flex; align-items: stretch; width: 100%; }
.visit-input-prefix {
display: flex;
align-items: center;
padding: 0 12px;
border: 1px solid hsl(210, 18%, 84%);
border-right: none;
border-radius: 0;
background: hsl(210, 12%, 95%);
font-weight: 800;
font-size: 0.9em;
white-space: nowrap;
color: hsl(210, 14%, 40%);
}
.visit-phone-row input.visit-phone-input {
width: 100%;
box-sizing: border-box;
padding: 12px 14px;
border: 1px solid hsl(210, 18%, 84%);
border-left: none;
border-radius: 0;
background: hsl(210, 12%, 98%);
font-size: 1em;
font-weight: 650;
outline: none;
}
@media (max-width: 780px) {
.visit-shell { padding: 10px; }
.visit-list-header {
align-items: flex-start;
flex-direction: column;
padding: 14px;
gap: 8px;
}
.visit-list-actions {
width: 100%;
justify-content: space-between;
}
table.visit-table {
display: block !important;
width: auto;
margin: 8px;
padding: 0;
border-spacing: 0;
}
.visit-table thead { display: none; }
.visit-table tbody {
display: grid;
gap: 10px;
}
.visit-table tbody tr.visit-row {
display: grid;
grid-template-columns: 18px minmax(0, 1fr);
gap: 0 12px;
padding: 14px;
border: 1px solid rgba(120, 140, 160, 0.16);
border-radius: 0;
box-shadow: 0 8px 22px rgba(31, 38, 46, 0.08);
}
.visit-table tbody tr.visit-row td {
display: block;
padding: 0 !important;
border-radius: 0 !important;
}
.visit-table tbody tr.visit-row td:first-child {
grid-row: 1 / span 2;
width: 18px;
min-width: 18px;
max-width: 18px;
display: flex;
align-items: center;
}
.visit-table tbody tr.visit-row td:nth-child(2) {
align-self: end;
min-width: 0;
}
.visit-table tbody tr.visit-row td:nth-child(3) {
align-self: start;
min-width: 0;
padding-top: 5px !important;
}
.visit-name-text {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.visit-empty-row {
display: block !important;
}
.visit-empty-row td {
display: block;
}
.visit-detail-info-grid { grid-template-columns: 1fr; }
.visit-detail-card { width: calc(100vw - 12px); max-height: calc(100dvh - 12px); }
}
@media (max-width: 480px) {
.visit-shell { padding: 8px; }
.visit-card { border-radius: 0; }
.visit-list-title { font-size: 1em; }
.visit-table tbody tr.visit-row {
gap: 0 10px;
padding: 10px;
}
.visit-name-text { font-size: 0.96em; }
.visit-info-cell { font-size: 0.85em; }
.visit-action-btn { font-size: 0.78em; padding: 6px 10px; }
}
</style>
<div class="visit-shell">
<div class="visit-card">
<div class="visit-list-header">
<div class="visit-list-title">Besuchsstellen</div>
<div class="visit-list-actions">
<button class="visit-action-btn" onclick="openVisitPickerModal()" title="Besuchsstelle auswählen und bearbeiten"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16" style="vertical-align:middle;margin-right:4px;"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg>Bearbeiten</button>
<button class="visit-action-btn visit-action-btn-primary" onclick="openVisitAddModal()" title="Neue Besuchsstelle anlegen">+ Neu</button>
</div>
</div>
<?php
// Daten abrufen
$sql = "SELECT * FROM seelsorge_besuchsstellen
ORDER BY CASE
WHEN reservation IS NOT NULL THEN 0
WHEN priority = 2 OR ((CURRENT_DATE - letzter_besuch) > 180) THEN 1
WHEN priority = 1 OR ((CURRENT_DATE - letzter_besuch) > 60) THEN 2
ELSE 3
END, last_name ASC";
$result = $pdo->query($sql);
$visitEntriesForJs = [];
$households = []; // Gruppierung nach Adresse
while ($row = $result->fetch()) {
$visitEntriesForJs[] = [
'besuch_id' => (int)$row['besuch_id'],
'last_name' => $row['last_name'] ?? '',
'first_name' => $row['first_name'] ?? '',
'birth_date' => $row['birth_date'] ?? '',
'address_street' => $row['address_street'] ?? '',
'address_plz' => $row['address_plz'] ?? '',
'address_link' => $row['address_link'] ?? '',
'phone' => $row['phone'] ?? '',
'comment' => $row['comment'] ?? '',
'letzter_besuch' => $row['letzter_besuch'] ?? '',
'priority' => (int)($row['priority'] ?? 0),
'pr_bezirk' => ($row['pr_bezirk'] === null ? '' : (int)$row['pr_bezirk']),
'reservation' => $row['reservation'],
];
// Gruppierung nach Adresse
$key = ($row['address_street'] ?? '') . '||' . ($row['address_plz'] ?? '');
if (!isset($households[$key])) {
$households[$key] = [
'members' => [],
'address_street' => $row['address_street'] ?? '',
'address_plz' => $row['address_plz'] ?? '',
'address_link' => $row['address_link'] ?? '',
'phone' => $row['phone'] ?? '',
'reservation' => $row['reservation'],
'last_name' => $row['last_name'] ?? '',
];
}
$households[$key]['members'][] = $row;
// Reservation: wenn mindestens eine Person reserviert ist
if ($row['reservation'] !== null) {
$households[$key]['reservation'] = $row['reservation'];
// Name und Profilbild des Reservierenden für Modal-Anzeige
$resIdModal = (int)$row['reservation'];
$stmtResModal = $pdo->prepare("SELECT first_name, last_name FROM user_general_information WHERE user_id = ?");
$stmtResModal->execute([$resIdModal]);
$resUserModal = $stmtResModal->fetch();
$households[$key]['reservation_name'] = trim(($resUserModal['first_name'] ?? '') . ' ' . ($resUserModal['last_name'] ?? ''));
$profileImgModal = glob($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/profile_pictures/{$resIdModal}-*.jpg");
if (!empty($profileImgModal)) {
usort($profileImgModal, function($a, $b) { return filemtime($b) - filemtime($a); });
$households[$key]['reservation_img'] = str_replace($_SERVER['DOCUMENT_ROOT'], '', $profileImgModal[0]);
} else {
$households[$key]['reservation_img'] = '/intranet_repo/general_files/profile_pictures/default.jpg';
}
}
}
?>
<table class="visit-table" id="visittable">
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Letzter Besuch</th>
</tr>
</thead>
<tbody>
<?php if (empty($households)): ?>
<tr class="visit-empty-row"><td colspan="3" style="text-align:center;padding:24px;color:hsl(210,14%,50%);font-weight:600;">Keine Einträge vorhanden.</td></tr>
<?php else: ?>
<?php foreach ($households as $hKey => $household):
$members = $household['members'];
$firstMember = $members[0];
// Status bestimmen (schlimmster Status im Haushalt)
$worstStatus = 'green';
$worstMonths = 0;
$isReserved = ($household['reservation'] !== null);
foreach ($members as $m) {
$mMonths = floor((strtotime(date('Y-m-d')) - strtotime($m['letzter_besuch'])) / (60 * 60 * 24 * 30));
if ($mMonths > $worstMonths) $worstMonths = $mMonths;
if ((int)$m['priority'] == 2 || $mMonths >= 6) { $worstStatus = 'red'; }
elseif (($worstStatus !== 'red') && ((int)$m['priority'] == 1 || $mMonths >= 2)) { $worstStatus = 'orange'; }
}
if ($isReserved) $worstStatus = 'blue';
// Status-Label
$statusLabels = ['blue' => 'Geplant', 'red' => 'Dringend', 'orange' => 'Fällig', 'green' => 'Aktuell'];
$statusLabel = $statusLabels[$worstStatus] ?? '';
// Name: Nachname, Vorname1 & Vorname2 ...
$lastName = $firstMember['last_name'];
$firstNames = array_map(function($m) { return $m['first_name']; }, $members);
$nameDisplay = htmlspecialchars($lastName) . ', ' . htmlspecialchars(implode(' & ', $firstNames));
// Letzter Besuch (ältester im Haushalt)
$oldestVisit = null;
foreach ($members as $m) {
if ($oldestVisit === null || strtotime($m['letzter_besuch']) < strtotime($oldestVisit)) {
$oldestVisit = $m['letzter_besuch'];
}
}
$daysSince = floor((strtotime(date('Y-m-d')) - strtotime($oldestVisit)) / (60 * 60 * 24));
$weeksSince = floor($daysSince / 7);
$monthsSince = floor($daysSince / 30);
if ($oldestVisit === '2024-07-11') {
$visitText = 'Initialer Eintrag';
} elseif ($daysSince >= 25) {
$visitText = ($monthsSince == 1) ? 'vor einem Monat' : "vor {$monthsSince} Monaten";
} elseif ($daysSince >= 7) {
$visitText = ($weeksSince == 1) ? 'vor einer Woche' : "vor {$weeksSince} Wochen";
} else {
$visitText = ($daysSince == 1) ? 'vor einem Tag' : "vor {$daysSince} Tagen";
}
// Reservierender
$reserverHtml = '';
if ($isReserved && (int)$household['reservation'] != (int)$current_user_id) {
$resId = (int)$household['reservation'];
$profileImg = glob($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/profile_pictures/{$resId}-*.jpg");
if (!empty($profileImg)) {
usort($profileImg, function($a, $b) { return filemtime($b) - filemtime($a); });
$resImgPath = str_replace($_SERVER['DOCUMENT_ROOT'], '', $profileImg[0]);
} else {
$resImgPath = '/intranet_repo/general_files/profile_pictures/default.jpg';
}
$stmtRes = $pdo->prepare("SELECT first_name, last_name FROM user_general_information WHERE user_id = ?");
$stmtRes->execute([$resId]);
$resUser = $stmtRes->fetch();
$resName = trim(($resUser['first_name'] ?? '') . ' ' . ($resUser['last_name'] ?? ''));
if (empty($resName)) $resName = 'Geplant';
$reserverHtml = "<div class='visit-planned-by'><img src='" . htmlspecialchars($resImgPath) . "' alt=''>Geplant von " . htmlspecialchars($resName) . "</div>";
} elseif ($isReserved && (int)$household['reservation'] == (int)$current_user_id) {
$reserverHtml = "<div class='visit-planned-by' style='color:hsl(145,55%,34%);'><svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor' width='14' height='14' style='vertical-align:middle;margin-right:4px;'><path d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/></svg>Von Ihnen geplant</div>";
}
// JSON data for modal
$householdJson = htmlspecialchars(json_encode([
'members' => array_map(function($m) use ($priests, $priestImages) {
$dob = new DateTime($m['birth_date']);
$age = (new DateTime())->diff($dob)->y;
$priestName = '';
$priestId = 0;
$priestImg = '/intranet_repo/general_files/profile_pictures/default.jpg';
foreach ($priests as $p) {
if ((int)$p['user_id'] === (int)$m['pr_bezirk']) {
$first = trim($p['first_name'] ?? '');
$last = trim($p['last_name'] ?? '');
$priestName = trim("$first $last");
$priestId = (int)$p['user_id'];
$priestImg = $priestImages[$priestId] ?? '/intranet_repo/general_files/profile_pictures/default.jpg';
break;
}
}
return [
'besuch_id' => (int)$m['besuch_id'],
'last_name' => $m['last_name'],
'first_name' => $m['first_name'],
'birth_date' => $m['birth_date'],
'birth_formatted' => date('d.m.Y', strtotime($m['birth_date'])),
'age' => $age,
'phone' => $m['phone'] ?? '',
'comment' => $m['comment'] ?? '',
'priority' => (int)($m['priority'] ?? 0),
'pr_bezirk' => ($m['pr_bezirk'] === null ? '' : (int)$m['pr_bezirk']),
'priest_name' => $priestName,
'priest_id' => $priestId,
'priest_img' => $priestImg,
'address_street' => $m['address_street'] ?? '',
'address_plz' => $m['address_plz'] ?? '',
'address_link' => $m['address_link'] ?? '',
'letzter_besuch' => $m['letzter_besuch'] ?? '',
'reservation' => $m['reservation'],
];
}, $members),
'address_street' => $household['address_street'],
'address_plz' => $household['address_plz'],
'address_link' => $household['address_link'],
'phone' => $household['phone'],
'reservation' => $household['reservation'],
'reservation_name' => $household['reservation_name'] ?? '',
'reservation_img' => $household['reservation_img'] ?? '',
'status' => $worstStatus,
], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT), ENT_QUOTES);
?>
<tr class="visit-row" id="household_<?= md5($hKey) ?>" onclick='openHouseholdDetail(<?= $householdJson ?>)' data-household-key="<?= htmlspecialchars($hKey) ?>" tabindex="0" role="button" aria-label="<?= $nameDisplay ?> Details anzeigen">
<td>
<span class="visit-status-dot is-<?= $worstStatus ?>"></span>
</td>
<td>
<span class="visit-name-text"><?= $nameDisplay ?></span>
<div style="margin-top:6px;">
<span class="visit-status-badge is-<?= $worstStatus ?>"><?= $statusLabel ?></span>
</div>
</td>
<td>
<div class="visit-info-cell">
<div><?= $visitText ?></div>
<?= $reserverHtml ?>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<!-- Detail Modal -->
<div class="visit-detail-overlay" id="visitDetailOverlay" onclick="if(event.target===this)closeHouseholdDetail()">
<div class="visit-detail-card">
<button class="visit-detail-close" onclick="closeHouseholdDetail()">×</button>
<div class="visit-detail-hero">
<div class="visit-detail-hero-title" id="detailHeroTitle"></div>
<div class="visit-detail-hero-sub" id="detailHeroSub"></div>
</div>
<div class="visit-detail-body" id="detailBody"></div>
</div>
</div>
<!-- Picker Modal -->
<div class="visit-modal-overlay" id="visitPickerModalOverlay" aria-hidden="true">
<div class="visit-modal" role="dialog" aria-modal="true" aria-labelledby="visitPickerTitle">
<div class="visit-modal-header">
<h2 id="visitPickerTitle">Besuchsstelle auswählen</h2>
<button class="visit-modal-close" type="button" onclick="closeVisitPickerModal()" aria-label="Schließen">×</button>
</div>
<div class="visit-modal-body">
<input id="visitPickerSearch" class="visit-picker-search" type="text" placeholder="Suche nach Name…" oninput="onVisitPickerSearchInput()" autocomplete="off">
<div id="visitPickerList" class="visit-picker-list"></div>
</div>
</div>
</div>
<!-- Edit Modal -->
<div class="visit-modal-overlay" id="visitEntryModalOverlay" aria-hidden="true">
<div class="visit-modal" role="dialog" aria-modal="true" aria-labelledby="visitModalTitle" style="width:min(640px, 96vw);">
<div class="visit-modal-header">
<h2 id="visitModalTitle">Eintrag bearbeiten</h2>
<button class="visit-modal-close" type="button" onclick="closeVisitModal()" aria-label="Schließen">×</button>
</div>
<div class="visit-modal-body">
<form id="visitEntryForm" onsubmit="submitVisitEntryForm(event)">
<input type="hidden" name="mode" id="modal_mode" value="edit">
<input type="hidden" name="besuch_id" id="modal_besuch_id" value="">
<div class="visit-form-grid">
<div class="visit-form-field">
<label>Nachname *</label>
<input type="text" name="last_name" id="modal_last_name" required>
</div>
<div class="visit-form-field">
<label>Vorname *</label>
<input type="text" name="first_name" id="modal_first_name" required>
</div>
<div class="visit-form-field">
<label>Geburtsdatum *</label>
<input type="date" name="birth_date" id="modal_birth_date" required>
</div>
<div class="visit-form-field">
<label>Telefon</label>
<div class="visit-phone-row">
<div class="visit-input-prefix">+49</div>
<input class="visit-phone-input" type="tel" name="phone" id="modal_phone" inputmode="numeric" autocomplete="tel-national">
</div>
</div>
<div class="visit-form-field full">
<label>Straße *</label>
<input type="text" name="address_street" id="modal_address_street" required>
</div>
<div class="visit-form-field">
<label>PLZ *</label>
<input type="text" name="address_plz" id="modal_address_plz" required>
</div>
<div class="visit-form-field">
<label>Link zur Adresse *</label>
<input type="text" name="address_link" id="modal_address_link" required>
</div>
<div class="visit-form-field">
<label>Letzter Besuch</label>
<input type="date" name="letzter_besuch" id="modal_letzter_besuch" required>
</div>
<div class="visit-form-field">
<label>Priorität</label>
<select name="priority" id="modal_priority">
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
</div>
<div class="visit-form-field full">
<label>Priester *</label>
<select name="pr_bezirk" id="modal_pr_bezirk" required>
<option value="">— auswählen —</option>
<?php echo renderPriestOptions($priests); ?>
</select>
</div>
<div class="visit-form-field full">
<label>Hinweis</label>
<input type="text" name="comment" id="modal_comment">
</div>
</div>
<div class="visit-modal-actions">
<button type="button" class="visit-btn-delete" id="modal_delete_btn" onclick="deleteVisitEntryFromModal()">Löschen</button>
<button type="button" class="visit-btn-cancel" onclick="closeVisitModal()">Abbrechen</button>
<button type="submit" class="visit-btn-save">Speichern</button>
</div>
</form>
</div>
</div>
</div>
<script>
// VISIT_ENTRIES for picker
window.VISIT_ENTRIES = <?= json_encode($visitEntriesForJs, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
// --- Detail Modal ---
function openHouseholdDetail(data) {
const overlay = document.getElementById('visitDetailOverlay');
const title = document.getElementById('detailHeroTitle');
const sub = document.getElementById('detailHeroSub');
const body = document.getElementById('detailBody');
const members = data.members || [];
const lastName = members[0]?.last_name || '';
const firstNames = members.map(m => m.first_name).join(' & ');
title.textContent = lastName + ', ' + firstNames;
sub.textContent = data.address_street + ' · ' + data.address_plz;
let html = '';
// Members section
html += '<div class="visit-detail-section-title">Haushaltsmitglieder</div>';
members.forEach(m => {
html += `<div class="visit-detail-member">
<div class="visit-detail-member-info">
${escHtml(m.first_name)} ${escHtml(m.last_name)}
<span class="age">geb. ${escHtml(m.birth_formatted)} (${m.age} Jahre)</span>
</div>
<button class="visit-detail-member-edit" onclick="event.stopPropagation(); editMemberFromDetail(${m.besuch_id})" title="Bearbeiten"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>
</div>`;
});
// Info grid
html += '<div class="visit-detail-section-title">Details</div>';
html += '<div class="visit-detail-info-grid">';
html += `<div class="visit-detail-info-item"><div class="visit-detail-info-label">Adresse</div><div class="visit-detail-info-value"><a href="${escHtml(data.address_link)}" target="_blank" style="color:var(--theme-color);text-decoration:none;">${escHtml(data.address_street)}, ${escHtml(data.address_plz)}</a></div></div>`;
const phone = members[0]?.phone || data.phone || '';
const phoneFormatted = phone ? ('0' + phone) : '—';
html += `<div class="visit-detail-info-item"><div class="visit-detail-info-label">Telefon</div><div class="visit-detail-info-value">${phone ? '<a href="tel:0'+escHtml(phone)+'" style="color:var(--theme-color);text-decoration:none;">'+escHtml(phoneFormatted)+'</a>' : '—'}</div></div>`;
const priestName = members[0]?.priest_name || '—';
const priestId = members[0]?.priest_id || 0;
const priestImg = members[0]?.priest_img || '/intranet_repo/general_files/profile_pictures/default.jpg';
if (priestId > 0) {
html += `<div class="visit-detail-info-item"><div class="visit-detail-info-label">Priester</div><div class="visit-detail-info-value">`;
html += `<span style="display:inline-flex;align-items:center;gap:8px;cursor:pointer;" onclick="closeHouseholdDetail();openCloseProfile(${priestId}, '<?= $profile_container_name ?>', '<?= $profile_overlay_id ?>')">`;
html += `<img src="${escHtml(priestImg)}" alt="" style="width:28px;height:28px;border-radius:50%;object-fit:cover;">`;
html += `<span style="color:var(--theme-color);text-decoration:none;">${escHtml(priestName)}</span>`;
html += `</span></div></div>`;
} else {
html += `<div class="visit-detail-info-item"><div class="visit-detail-info-label">Priester</div><div class="visit-detail-info-value">${escHtml(priestName)}</div></div>`;
}
const comment = members.map(m => m.comment).filter(c => c).join('; ') || '—';
html += `<div class="visit-detail-info-item"><div class="visit-detail-info-label">Hinweis</div><div class="visit-detail-info-value">${escHtml(comment)}</div></div>`;
html += '</div>';
// Reservation info
if (data.reservation) {
html += '<div class="visit-detail-section-title">Geplanter Besuch</div>';
const resName = data.reservation_name || ('Nutzer #' + data.reservation);
const resImg = data.reservation_img || '/intranet_repo/general_files/profile_pictures/default.jpg';
const resId = parseInt(data.reservation);
html += '<div class="visit-detail-info-item" style="grid-column:1/-1;"><div class="visit-detail-info-label">Geplant von</div><div class="visit-detail-info-value">';
html += `<span style="display:inline-flex;align-items:center;gap:8px;cursor:pointer;" onclick="closeHouseholdDetail();openCloseProfile(${resId}, '<?= $profile_container_name ?>', '<?= $profile_overlay_id ?>')">`;
html += `<img src="${escHtml(resImg)}" alt="" style="width:28px;height:28px;border-radius:50%;object-fit:cover;">`;
html += `<span style="color:var(--theme-color);text-decoration:none;">${escHtml(resName)}</span>`;
html += `</span>`;
html += '</div></div>';
}
// Actions
const firstBesuchId = members[0]?.besuch_id || 0;
html += '<div class="visit-detail-actions">';
if (!data.reservation) {
html += `<button class="visit-action-btn visit-action-btn-primary" onclick="doReservation(${firstBesuchId})">Reservieren</button>`;
} else if (parseInt(data.reservation) === <?= (int)$current_user_id ?>) {
html += `<button class="visit-action-btn" style="color:hsl(145,55%,34%);border-color:hsl(145,55%,80%);background:hsl(145,55%,96%);" onclick="doComplete(${firstBesuchId})"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16" style="vertical-align:middle;margin-right:4px;"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>Abschließen</button>`;
}
html += `<button class="visit-action-btn" onclick="doPrioritize(${firstBesuchId})">Priorisieren</button>`;
html += '</div>';
body.innerHTML = html;
overlay.style.display = 'flex';
}
function closeHouseholdDetail() {
document.getElementById('visitDetailOverlay').style.display = 'none';
}
function escHtml(str) {
if (!str) return '';
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
function editMemberFromDetail(besuchId) {
closeHouseholdDetail();
const entry = window.VISIT_ENTRIES.find(e => e.besuch_id === besuchId);
if (entry) openVisitEditModalFromEntry(entry);
}
function doReservation(besuchId) {
closeHouseholdDetail();
setVisitReservation(besuchId);
}
function doComplete(besuchId) {
closeHouseholdDetail();
removeVisitReservation(besuchId);
}
function doPrioritize(besuchId) {
closeHouseholdDetail();
prioritizePerson(besuchId);
}
// --- Existing functions (updated for household) ---
function updateVisitDate(besuch_id) {
fetch('content/update_visit_date.php?besuch_id=' + besuch_id)
.then(r => r.json())
.then(data => {
if (data.status === 'success') {
window.location.reload();
}
});
}
function setVisitReservation(besuch_id) {
fetch('content/set_visit_reservation.php?besuch_id=' + besuch_id)
.then(r => r.json())
.then(data => {
if (data.status === 'success') {
window.location.reload();
}
});
}
function prioritizePerson(besuch_id) {
fetch('content/prioritize_person.php?besuch_id=' + besuch_id)
.then(r => r.json())
.then(data => {
window.location.reload();
});
}
function removeVisitReservation(besuch_id) {
fetch('content/remove_visit_reservation.php?besuch_id=' + besuch_id)
.then(r => r.json())
.then(data => {
if (data.status === 'success') {
updateVisitDate(besuch_id);
}
});
}
// --- Picker Modal ---
function openVisitPickerModal() {
renderVisitPickerList('');
document.getElementById('visitPickerModalOverlay').style.display = 'flex';
const inp = document.getElementById('visitPickerSearch');
inp.value = '';
setTimeout(() => inp.focus(), 30);
}
function closeVisitPickerModal() {
document.getElementById('visitPickerModalOverlay').style.display = 'none';
}
function onVisitPickerSearchInput() {
renderVisitPickerList(document.getElementById('visitPickerSearch').value || '');
}
function normalizeStr(s) { return (s || '').toString().toLowerCase(); }
function matchesQuery(entry, query) {
const q = normalizeStr(query).trim();
if (!q) return true;
const ln = normalizeStr(entry.last_name);
const fn = normalizeStr(entry.first_name);
const a = (ln + ' ' + fn).trim();
const b = (fn + ' ' + ln).trim();
const tokens = q.split(/\s+/).filter(Boolean);
return tokens.every(t => a.includes(t) || b.includes(t));
}
function renderVisitPickerList(query) {
const list = document.getElementById('visitPickerList');
list.innerHTML = '';
if (!Array.isArray(window.VISIT_ENTRIES) || window.VISIT_ENTRIES.length === 0) {
list.innerHTML = '<div style="opacity:.75;padding:8px;">Keine Einträge vorhanden.</div>';
return;
}
const filtered = window.VISIT_ENTRIES.filter(e => matchesQuery(e, query));
if (filtered.length === 0) {
list.innerHTML = '<div style="opacity:.75;padding:8px;">Keine Treffer.</div>';
return;
}
filtered.forEach(entry => {
const item = document.createElement('div');
item.className = 'visit-picker-item';
item.onclick = () => { closeVisitPickerModal(); openVisitEditModalFromEntry(entry); };
const left = document.createElement('div');
const name = document.createElement('div');
name.className = 'visit-picker-name';
name.textContent = (entry.last_name || '') + ', ' + (entry.first_name || '');
left.appendChild(name);
const sub = document.createElement('div');
sub.className = 'visit-picker-sub';
sub.textContent = (entry.address_street || '') + (entry.address_plz ? (' · ' + entry.address_plz) : '');
left.appendChild(sub);
const right = document.createElement('div');
right.style.cssText = 'opacity:0.5;font-size:0.8em;font-weight:700;';
right.textContent = '#' + entry.besuch_id;
item.appendChild(left);
item.appendChild(right);
list.appendChild(item);
});
}
// --- Edit Modal ---
function openVisitEditModalFromEntry(e) {
document.getElementById('visitModalTitle').innerText = 'Eintrag bearbeiten';
document.getElementById('modal_mode').value = 'edit';
document.getElementById('modal_besuch_id').value = e.besuch_id || '';
document.getElementById('modal_last_name').value = e.last_name || '';
document.getElementById('modal_first_name').value = e.first_name || '';
document.getElementById('modal_birth_date').value = e.birth_date || '';
document.getElementById('modal_address_street').value = e.address_street || '';
document.getElementById('modal_address_plz').value = e.address_plz || '';
document.getElementById('modal_address_link').value = e.address_link || '';
document.getElementById('modal_phone').value = e.phone || '';
document.getElementById('modal_comment').value = e.comment || '';
document.getElementById('modal_letzter_besuch').value = e.letzter_besuch || '';
document.getElementById('modal_priority').value = (e.priority ?? '0');
document.getElementById('modal_pr_bezirk').value = (e.pr_bezirk ?? '');
document.getElementById('modal_delete_btn').style.display = 'inline-block';
showVisitModal();
}
function openVisitAddModal() {
document.getElementById('visitModalTitle').innerText = 'Neue Besuchsstelle anlegen';
document.getElementById('modal_mode').value = 'add';
document.getElementById('modal_besuch_id').value = '';
document.getElementById('modal_last_name').value = '';
document.getElementById('modal_first_name').value = '';
document.getElementById('modal_birth_date').value = '';
document.getElementById('modal_address_street').value = '';
document.getElementById('modal_address_plz').value = '';
document.getElementById('modal_address_link').value = '';
document.getElementById('modal_phone').value = '';
document.getElementById('modal_comment').value = '';
document.getElementById('modal_letzter_besuch').value = '2024-07-11';
document.getElementById('modal_priority').value = '0';
document.getElementById('modal_pr_bezirk').value = '';
document.getElementById('modal_delete_btn').style.display = 'none';
showVisitModal();
}
function showVisitModal() {
const overlay = document.getElementById('visitEntryModalOverlay');
overlay.style.display = 'flex';
overlay.setAttribute('aria-hidden', 'false');
setTimeout(() => document.getElementById('modal_last_name')?.focus(), 30);
}
function closeVisitModal() {
const overlay = document.getElementById('visitEntryModalOverlay');
if (overlay && overlay.contains(document.activeElement)) document.activeElement.blur();
overlay.setAttribute('aria-hidden', 'true');
overlay.style.display = 'none';
}
// Close on backdrop/escape
document.addEventListener('click', function(e) {
const picker = document.getElementById('visitPickerModalOverlay');
const edit = document.getElementById('visitEntryModalOverlay');
if (picker && picker.style.display === 'flex' && e.target === picker) closeVisitPickerModal();
if (edit && edit.style.display === 'flex' && e.target === edit) closeVisitModal();
});
document.addEventListener('keydown', function(e) {
if (e.key !== 'Escape') return;
const detail = document.getElementById('visitDetailOverlay');
const edit = document.getElementById('visitEntryModalOverlay');
const picker = document.getElementById('visitPickerModalOverlay');
if (edit && edit.style.display === 'flex') return closeVisitModal();
if (picker && picker.style.display === 'flex') return closeVisitPickerModal();
if (detail && detail.style.display === 'flex') return closeHouseholdDetail();
});
async function submitVisitEntryForm(ev) {
ev.preventDefault();
const mode = document.getElementById('modal_mode').value;
const form = document.getElementById('visitEntryForm');
const fd = new FormData(form);
let endpoint = '';
if (mode === 'edit') endpoint = 'content/update_visit_entry.php';
if (mode === 'add') endpoint = 'content/add_visit_entry.php';
if (!endpoint) return;
try {
const res = await fetch(endpoint, { method: 'POST', body: fd });
const text = await res.text();
let data;
try { data = JSON.parse(text); } catch (e) { alert('Server hat kein JSON geliefert.'); return; }
if (!res.ok) { alert(data.message || ('HTTP Fehler: ' + res.status)); return; }
if (data.status === 'success') {
window.location.reload();
} else {
alert(data.message || 'Fehler beim Speichern.');
}
} catch (err) {
alert('Fehler beim Speichern (Netzwerk/Server).');
}
}
async function deleteVisitEntryFromModal() {
const besuch_id = document.getElementById('modal_besuch_id').value;
if (!besuch_id) return;
if (!confirm('Eintrag wirklich löschen?')) return;
try {
const res = await fetch('content/delete_visit_entry.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
body: 'besuch_id=' + encodeURIComponent(besuch_id)
});
const data = await res.json();
if (data.status === 'success') {
window.location.reload();
} else {
alert(data.message || 'Fehler beim Löschen.');
}
} catch (err) {
alert('Fehler beim Löschen (Netzwerk/Server).');
}
}
</script>
<?php
// Focus handling
if (isset($_GET['focus_id'])) {
$focus_id = (int)$_GET['focus_id'];
echo "<script>document.addEventListener('DOMContentLoaded', function() { var row = document.getElementById('row_{$focus_id}'); if(row) row.scrollIntoView({behavior:'smooth',block:'center'}); });</script>";
}
include $_SERVER['DOCUMENT_ROOT']."/intranet_repo/projects/seelsorge/content/profile.php";
?>