1306 lines
58 KiB
PHP
1306 lines
58 KiB
PHP
<?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";
|
||
?>
|