286 lines
11 KiB
Plaintext
286 lines
11 KiB
Plaintext
<%# views/calendar/index.ejs %>
|
|
<%# Eingebettet in das bestehende layout.ejs via express-ejs-layouts %>
|
|
|
|
<style>
|
|
/* ── Kalender-Variablen ──────────────────────────────────────────────── */
|
|
:root {
|
|
--cal-slot-h: 40px;
|
|
--cal-time-w: 60px;
|
|
--cal-min-col: 160px;
|
|
--cal-border: #dee2e6;
|
|
--cal-hover: #e8f0fe;
|
|
--cal-now: #dc3545;
|
|
--cal-holiday: #fff3cd;
|
|
--cal-weekend: #f8f9fa;
|
|
}
|
|
|
|
/* ── Layout ─────────────────────────────────────────────────────────── */
|
|
#calendarPage { display:flex; flex-direction:column; height:calc(100vh - 70px); overflow:hidden; }
|
|
#calToolbar { flex-shrink:0; }
|
|
#calHolidayBanner { flex-shrink:0; display:none; }
|
|
#calBody { flex:1; display:flex; overflow:hidden; }
|
|
|
|
/* ── Sidebar ─────────────────────────────────────────────────────────── */
|
|
#calSidebar {
|
|
width: 220px;
|
|
flex-shrink: 0;
|
|
border-right: 1px solid var(--cal-border);
|
|
overflow-y: auto;
|
|
background: #fff;
|
|
padding: 12px;
|
|
}
|
|
.mini-cal-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(7, 1fr);
|
|
gap: 2px;
|
|
text-align: center;
|
|
font-size: 12px;
|
|
}
|
|
.mini-wd { color:#6c757d; font-weight:600; padding-bottom:3px; }
|
|
.mini-day { padding:3px 1px; border-radius:4px; cursor:pointer; line-height:1.6; }
|
|
.mini-day:hover { background:#e9ecef; }
|
|
.mini-day.today { background:#cfe2ff; color:#0d6efd; font-weight:600; }
|
|
.mini-day.selected { background:#0d6efd; color:#fff; font-weight:600; }
|
|
.mini-day.holiday { color:#dc3545; }
|
|
.mini-day.other-month { color:#ced4da; }
|
|
|
|
.doc-item {
|
|
display:flex; align-items:center; gap:8px;
|
|
padding:6px 4px; border-radius:6px; cursor:pointer;
|
|
font-size:13px; transition:background .12s;
|
|
user-select:none;
|
|
}
|
|
.doc-item:hover { background:#f8f9fa; }
|
|
.doc-dot { width:10px; height:10px; border-radius:50%; flex-shrink:0; }
|
|
.doc-check {
|
|
width:16px; height:16px; border-radius:3px; flex-shrink:0;
|
|
border:1.5px solid #ced4da; margin-left:auto;
|
|
display:flex; align-items:center; justify-content:center;
|
|
}
|
|
.doc-item.active .doc-check { background:#0d6efd; border-color:#0d6efd; }
|
|
|
|
/* ── Kalender-Bereich ────────────────────────────────────────────────── */
|
|
#calMain { flex:1; display:flex; flex-direction:column; overflow:hidden; }
|
|
#calColHeaders { display:flex; background:#fff; border-bottom:2px solid var(--cal-border); flex-shrink:0; z-index:10; }
|
|
#calScroll { flex:1; overflow-y:auto; overflow-x:hidden; }
|
|
#calGrid { display:flex; }
|
|
|
|
/* Zeitachse */
|
|
.cal-time-axis { width:var(--cal-time-w); flex-shrink:0; background:#f8f9fa; }
|
|
.cal-time-label {
|
|
height:var(--cal-slot-h); display:flex; align-items:flex-start; justify-content:flex-end;
|
|
padding:0 6px; font-size:11px; color:#6c757d; transform:translateY(-6px);
|
|
font-variant-numeric:tabular-nums;
|
|
}
|
|
.cal-time-label.hour { font-weight:600; color:#495057; }
|
|
|
|
/* Spalten */
|
|
#calColHeadersInner { display:flex; flex:1; overflow:hidden; }
|
|
#calColumnsInner { display:flex; flex:1; }
|
|
|
|
.col-header {
|
|
flex:1; min-width:var(--cal-min-col);
|
|
padding:8px 10px; border-left:1px solid var(--cal-border);
|
|
display:flex; align-items:center; gap:6px;
|
|
}
|
|
.col-header-name { font-weight:600; font-size:13px; }
|
|
.col-header-spec { font-size:11px; color:#6c757d; }
|
|
.col-header-count {
|
|
margin-left:auto; font-size:11px; background:#e9ecef;
|
|
padding:1px 7px; border-radius:20px; color:#6c757d; white-space:nowrap;
|
|
}
|
|
.col-header-color {
|
|
width:32px; height:20px; border-radius:4px; border:none;
|
|
cursor:pointer; padding:0 2px;
|
|
}
|
|
|
|
.doc-col { flex:1; min-width:var(--cal-min-col); border-left:1px solid var(--cal-border); position:relative; }
|
|
|
|
.slot-row {
|
|
height:var(--cal-slot-h); border-bottom:1px solid #f0f0f0;
|
|
cursor:pointer; position:relative; transition:background .08s;
|
|
}
|
|
.slot-row.hour-start { border-bottom-color:#dee2e6; }
|
|
.slot-row.weekend { background:var(--cal-weekend); }
|
|
.slot-row:hover { background:var(--cal-hover); }
|
|
|
|
/* Terminblock */
|
|
.appt-block {
|
|
position:absolute; left:3px; right:3px;
|
|
border-radius:5px; padding:3px 6px;
|
|
cursor:pointer; z-index:3; overflow:hidden;
|
|
border-left:3px solid; font-size:12px;
|
|
transition:filter .12s;
|
|
}
|
|
.appt-block:hover { filter:brightness(.9); }
|
|
.appt-block.cancelled { opacity:.45; text-decoration:line-through; }
|
|
.appt-block.completed { opacity:.65; }
|
|
.appt-patient { font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
.appt-time { font-size:10px; opacity:.75; font-variant-numeric:tabular-nums; }
|
|
|
|
/* Jetzt-Linie */
|
|
.now-line { position:absolute; left:0; right:0; height:2px; background:var(--cal-now); z-index:4; pointer-events:none; }
|
|
.now-dot { position:absolute; left:-4px; top:-4px; width:10px; height:10px; border-radius:50%; background:var(--cal-now); }
|
|
|
|
/* Scrollbar */
|
|
#calScroll::-webkit-scrollbar { width:5px; }
|
|
#calScroll::-webkit-scrollbar-thumb { background:#ced4da; border-radius:3px; }
|
|
</style>
|
|
|
|
<div id="calendarPage">
|
|
|
|
<%# ── Toolbar ── %>
|
|
<div id="calToolbar" class="d-flex align-items-center gap-2 p-2 border-bottom bg-white">
|
|
<i class="bi bi-calendar3 text-primary fs-5"></i>
|
|
<strong class="me-2">Kalender</strong>
|
|
|
|
<button class="btn btn-sm btn-outline-secondary" id="btnPrev">
|
|
<i class="bi bi-chevron-left"></i>
|
|
</button>
|
|
|
|
<button class="btn btn-sm btn-outline-secondary fw-semibold" id="btnDateDisplay" style="min-width:220px">
|
|
Lädt …
|
|
</button>
|
|
|
|
<button class="btn btn-sm btn-outline-secondary" id="btnNext">
|
|
<i class="bi bi-chevron-right"></i>
|
|
</button>
|
|
|
|
<button class="btn btn-sm btn-outline-primary" id="btnToday">Heute</button>
|
|
|
|
<div class="ms-auto d-flex gap-2">
|
|
<button class="btn btn-sm btn-primary" id="btnNewAppt">
|
|
<i class="bi bi-plus-lg me-1"></i>Neuer Termin
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<%# ── Feiertagsbanner ── %>
|
|
<div id="calHolidayBanner" class="alert alert-warning d-flex align-items-center gap-2 mb-0 rounded-0 py-2 px-3">
|
|
<i class="bi bi-star-fill text-warning"></i>
|
|
<span id="calHolidayText"></span>
|
|
</div>
|
|
|
|
<%# ── Haupt-Body ── %>
|
|
<div id="calBody">
|
|
|
|
<%# ── Sidebar ── %>
|
|
<div id="calSidebar">
|
|
<%# Mini-Kalender %>
|
|
<div class="mb-3">
|
|
<div id="miniCal"></div>
|
|
</div>
|
|
|
|
<hr class="my-2">
|
|
<div class="text-uppercase fw-bold" style="font-size:11px;color:#6c757d;letter-spacing:.06em;">Ärzte</div>
|
|
<div id="docList" class="mt-2"></div>
|
|
</div>
|
|
|
|
<%# ── Kalender-Spalten ── %>
|
|
<div id="calMain">
|
|
<div id="calColHeaders">
|
|
<div style="width:var(--cal-time-w);flex-shrink:0;"></div>
|
|
<div id="calColHeadersInner"></div>
|
|
</div>
|
|
<div id="calScroll">
|
|
<div id="calGrid">
|
|
<div class="cal-time-axis" id="calTimeAxis"></div>
|
|
<div id="calColumnsInner"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div><%# /calBody %>
|
|
</div><%# /calendarPage %>
|
|
|
|
<%# ── Modal: Termin ── %>
|
|
<div class="modal fade" id="apptModal" tabindex="-1">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="apptModalTitle">Neuer Termin</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="row g-2">
|
|
<div class="col-7">
|
|
<label class="form-label small fw-semibold">Arzt</label>
|
|
<select class="form-select form-select-sm" id="fDoctor"></select>
|
|
</div>
|
|
<div class="col-5">
|
|
<label class="form-label small fw-semibold">Status</label>
|
|
<select class="form-select form-select-sm" id="fStatus">
|
|
<option value="scheduled">Geplant</option>
|
|
<option value="completed">Abgeschlossen</option>
|
|
<option value="cancelled">Abgesagt</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label small fw-semibold">Patient</label>
|
|
<div class="position-relative">
|
|
<input type="text"
|
|
class="form-control form-control-sm"
|
|
id="fPatient"
|
|
placeholder="Name eingeben …"
|
|
autocomplete="off">
|
|
<input type="hidden" id="fPatientId">
|
|
<div id="patientDropdown"
|
|
class="position-absolute w-100 bg-white border rounded shadow-sm"
|
|
style="display:none; z-index:1060; top:100%; max-height:200px; overflow-y:auto;">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-4">
|
|
<label class="form-label small fw-semibold">Datum</label>
|
|
<input type="date" class="form-control form-control-sm" id="fDate">
|
|
</div>
|
|
<div class="col-4">
|
|
<label class="form-label small fw-semibold">Uhrzeit</label>
|
|
<select class="form-select form-select-sm" id="fTime"></select>
|
|
</div>
|
|
<div class="col-4">
|
|
<label class="form-label small fw-semibold">Dauer</label>
|
|
<select class="form-select form-select-sm" id="fDuration">
|
|
<option value="15">15 min</option>
|
|
<option value="30">30 min</option>
|
|
<option value="45">45 min</option>
|
|
<option value="60">60 min</option>
|
|
<option value="90">90 min</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label small fw-semibold">Notizen</label>
|
|
<textarea class="form-control form-control-sm" id="fNotes" rows="2" placeholder="Optional …"></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-sm btn-outline-danger me-auto" id="btnApptDelete" style="display:none">
|
|
<i class="bi bi-trash me-1"></i>Löschen
|
|
</button>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
|
<button type="button" class="btn btn-sm btn-primary" id="btnApptSave">
|
|
<i class="bi bi-check-lg me-1"></i>Speichern
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<%# ── Toast ── %>
|
|
<div class="position-fixed bottom-0 start-50 translate-middle-x p-3" style="z-index:9999">
|
|
<div id="calToast" class="toast align-items-center text-bg-dark border-0" role="alert">
|
|
<div class="d-flex">
|
|
<div class="toast-body" id="calToastMsg"></div>
|
|
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<%# ── Ärzte-Daten CSP-sicher übergeben (type="application/json" wird NICHT geblockt) ── %>
|
|
<script type="application/json" id="calDoctorsData"><%- JSON.stringify(doctors) %></script>
|
|
|
|
<%# ── Externes Script (script-src 'self' erlaubt dies) ── %>
|
|
<script src="/js/calendar.js"></script>
|