Äanderungen der Sidebars und design

This commit is contained in:
Cay 2026-01-19 19:01:05 +00:00
parent 10e83f53da
commit 860b41ab28
14 changed files with 1321 additions and 814 deletions

View File

@ -88,7 +88,7 @@ async function postCreateUser(req, res) {
password,
role,
fachrichtung,
arztnummer
arztnummer,
);
req.session.flash = {
@ -159,7 +159,7 @@ async function resetUserPassword(req, res) {
};
}
res.redirect("/admin/users");
}
},
);
}
@ -254,7 +254,7 @@ async function showInvoiceOverview(req, res) {
GROUP BY p.id
ORDER BY total DESC
`,
[`%${search}%`]
[`%${search}%`],
);
res.render("admin/admin_invoice_overview", {
@ -266,6 +266,7 @@ async function showInvoiceOverview(req, res) {
search,
fromYear,
toYear,
view, // ✅ WICHTIG: damit EJS weiß welche Tabelle angezeigt wird
});
} catch (err) {
console.error(err);

View File

@ -4,7 +4,9 @@
"cancel": "Abbrechen",
"search": "Suchen",
"reset": "Reset",
"dashboard": "Dashboard"
"dashboard": "Dashboard",
"year": "Jahr",
"month": "Monat"
},
"sidebar": {
"patients": "Patienten",
@ -22,5 +24,17 @@
"adminSidebar": {
"users": "Userverwaltung",
"database": "Datenbankverwaltung"
},
"adminInvoice": {
"annualSales": "Jahresumsatz",
"quarterlySales": "Quartalsumsatz.",
"monthSales": "Monatsumsatz",
"patientsSales": "Umsatz pro Patient",
"doctorSales": "Umsatz pro Arzt",
"filter": "Filtern",
"invoiceOverview": "Rechnungsübersicht",
"search": "Suchen",
"patient": "Patient",
"searchPatient": "Patienten suchen"
}
}

View File

@ -4,7 +4,9 @@
"cancel": "Cancelar",
"search": "Buscar",
"reset": "Resetear",
"dashboard": "Panel"
"dashboard": "Panel",
"year": "Ano",
"month": "mes"
},
"sidebar": {
"patients": "Pacientes",
@ -23,5 +25,17 @@
"adminSidebar": {
"users": "Administración de usuarios",
"database": "Administración de base de datos"
},
"adminInvoice": {
"annualSales": "facturación anual",
"quarterlySales": "ingresos trimestrales.",
"monthSales": "facturación mensual",
"patientsSales": "Ingresos por paciente",
"doctorSales": "Facturación por médico",
"filter": "filtro",
"invoiceOverview": "Resumen de facturas",
"search": "buscar",
"patient": "paciente",
"searchPatient": "Buscar pacientes"
}
}

View File

@ -0,0 +1,16 @@
document.addEventListener("DOMContentLoaded", () => {
const alerts = document.querySelectorAll(".auto-hide-flash");
if (!alerts.length) return;
setTimeout(() => {
alerts.forEach((el) => {
el.classList.add("flash-hide");
// nach der Animation aus dem DOM entfernen
setTimeout(() => {
el.remove();
}, 700);
});
}, 3000); // ✅ 3 Sekunden
});

View File

@ -0,0 +1,124 @@
document.addEventListener("DOMContentLoaded", () => {
const radios = document.querySelectorAll(".patient-radio");
const sidebarPatientInfo = document.getElementById("sidebarPatientInfo");
const sbOverview = document.getElementById("sbOverview");
const sbHistory = document.getElementById("sbHistory");
const sbEdit = document.getElementById("sbEdit");
const sbMeds = document.getElementById("sbMeds");
const sbWaitingRoomWrapper = document.getElementById("sbWaitingRoomWrapper");
const sbActiveWrapper = document.getElementById("sbActiveWrapper");
const sbUploadForm = document.getElementById("sbUploadForm");
const sbUploadInput = document.getElementById("sbUploadInput");
const sbUploadBtn = document.getElementById("sbUploadBtn");
if (
!radios.length ||
!sidebarPatientInfo ||
!sbOverview ||
!sbHistory ||
!sbEdit ||
!sbMeds ||
!sbWaitingRoomWrapper ||
!sbActiveWrapper ||
!sbUploadForm ||
!sbUploadInput ||
!sbUploadBtn
) {
return;
}
// ✅ Sicherheit: Upload blocken falls nicht aktiv
sbUploadForm.addEventListener("submit", (e) => {
if (!sbUploadForm.action || sbUploadForm.action.endsWith("#")) {
e.preventDefault();
}
});
radios.forEach((radio) => {
radio.addEventListener("change", () => {
const id = radio.dataset.id;
const firstname = radio.dataset.firstname;
const lastname = radio.dataset.lastname;
const waiting = radio.dataset.waiting === "1";
const active = radio.dataset.active === "1";
// ✅ Patient Info
sidebarPatientInfo.innerHTML = `
<div class="patient-name">
<strong>${firstname} ${lastname}</strong>
</div>
<div class="patient-meta text-muted">
ID: ${id}
</div>
`;
// ✅ Übersicht
sbOverview.href = "/patients/" + id;
sbOverview.classList.remove("disabled");
// ✅ Verlauf
sbHistory.href = "/patients/" + id + "/overview";
sbHistory.classList.remove("disabled");
// ✅ Bearbeiten
sbEdit.href = "/patients/edit/" + id;
sbEdit.classList.remove("disabled");
// ✅ Medikamente
sbMeds.href = "/patients/" + id + "/medications";
sbMeds.classList.remove("disabled");
// ✅ Wartezimmer (NUR wenn Patient aktiv ist)
if (!active) {
sbWaitingRoomWrapper.innerHTML = `
<div class="nav-item disabled">
<i class="bi bi-door-open"></i> Ins Wartezimmer (Patient inaktiv)
</div>
`;
} else if (waiting) {
sbWaitingRoomWrapper.innerHTML = `
<div class="nav-item disabled">
<i class="bi bi-hourglass-split"></i> Wartet bereits
</div>
`;
} else {
sbWaitingRoomWrapper.innerHTML = `
<form method="POST" action="/patients/waiting-room/${id}" style="margin:0;">
<button type="submit" class="nav-item nav-btn">
<i class="bi bi-door-open"></i> Ins Wartezimmer
</button>
</form>
`;
}
// ✅ Sperren / Entsperren
if (active) {
sbActiveWrapper.innerHTML = `
<form method="POST" action="/patients/deactivate/${id}" style="margin:0;">
<button type="submit" class="nav-item nav-btn">
<i class="bi bi-lock-fill"></i> Sperren
</button>
</form>
`;
} else {
sbActiveWrapper.innerHTML = `
<form method="POST" action="/patients/activate/${id}" style="margin:0;">
<button type="submit" class="nav-item nav-btn">
<i class="bi bi-unlock-fill"></i> Entsperren
</button>
</form>
`;
}
// ✅ Upload nur aktiv wenn Patient ausgewählt
sbUploadForm.action = "/patients/" + id + "/files";
sbUploadInput.disabled = false;
sbUploadBtn.disabled = false;
});
});
});

View File

@ -2,14 +2,98 @@
<html lang="de">
<head>
<meta charset="UTF-8" />
<title>Rechnungsübersicht</title>
<title><%= t.adminInvoice.invoiceOverview %></title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/css/bootstrap.min.css" />
<link rel="stylesheet" href="/bootstrap-icons/bootstrap-icons.min.css" />
<style>
body {
margin: 0;
background: #f4f6f9;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, Ubuntu;
}
.layout {
display: flex;
min-height: 100vh;
}
/* Sidebar */
.sidebar {
width: 240px;
background: #111827;
color: white;
padding: 20px;
display: flex;
flex-direction: column;
}
.logo {
font-size: 18px;
font-weight: 700;
margin-bottom: 30px;
display: flex;
align-items: center;
gap: 10px;
}
.nav-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 15px;
border-radius: 8px;
color: #cbd5e1;
text-decoration: none;
margin-bottom: 6px;
font-size: 14px;
}
.nav-item:hover {
background: #1f2937;
color: white;
}
.nav-item.active {
background: #2563eb;
color: white;
}
.sidebar .spacer {
flex: 1;
}
/* Main */
.main {
flex: 1;
padding: 0;
background: #f4f6f9;
overflow: hidden;
display: flex;
flex-direction: column;
}
</style>
</head>
<body class="bg-light">
<body>
<%
// ✅ active aus view setzen (view kommt vom Controller)
let active = "sales_year";
if (view === "quarter") active = "sales_quarter";
if (view === "month") active = "sales_month";
if (view === "patient") active = "sales_patient";
if (view === "year") active = "sales_year";
%>
<div class="layout">
<!-- ✅ neue Invoice Sidebar -->
<%- include("../partials/invoice_sidebar", { active, t }) %>
<!-- MAIN CONTENT -->
<div class="main">
<!-- =========================
NAVBAR
========================== -->
@ -18,13 +102,12 @@
class="position-absolute top-50 start-50 translate-middle d-flex align-items-center gap-2 text-white"
>
<i class="bi bi-calculator fs-4"></i>
<span class="fw-semibold fs-5">Rechnungsübersicht</span>
<span class="fw-semibold fs-5"><%= t.adminInvoice.invoiceOverview %></span>
</div>
<!-- 🔵 RECHTS: DASHBOARD -->
<div class="ms-auto">
<a href="/dashboard" class="btn btn-outline-primary btn-sm">
⬅️ Dashboard
⬅️ <%= t.global.dashboard %>
</a>
</div>
</nav>
@ -34,6 +117,9 @@
========================== -->
<div class="container-fluid mt-4">
<form method="get" class="row g-2 mb-4">
<!-- ✅ view beibehalten -->
<input type="hidden" name="view" value="<%= view %>" />
<div class="col-auto">
<input
type="number"
@ -55,25 +141,27 @@
</div>
<div class="col-auto">
<button class="btn btn-outline-secondary">Filtern</button>
<button class="btn btn-outline-secondary">
Filter
</button>
</div>
</form>
<!-- =========================
GRID 4 SPALTEN
========================== -->
<!-- ✅ NUR EINE TABELLE -->
<div class="row g-3">
<!-- =========================
JAHRESUMSATZ
========================== -->
<div class="col-xl-3 col-lg-6">
<% if (view === "year") { %>
<!-- Jahresumsatz -->
<div class="col-12">
<div class="card h-100">
<div class="card-header fw-semibold">Jahresumsatz</div>
<div class="card-header fw-semibold">
<%= t.adminInvoice.annualSales %>
</div>
<div class="card-body p-0">
<table class="table table-sm table-striped mb-0">
<table class="table table-striped mb-0">
<thead>
<tr>
<th>Jahr</th>
<th><%= t.global.year %></th>
<th class="text-end">€</th>
</tr>
</thead>
@ -84,12 +172,12 @@
Keine Daten
</td>
</tr>
<% } %> <% yearly.forEach(y => { %>
<% } %>
<% yearly.forEach(y => { %>
<tr>
<td><%= y.year %></td>
<td class="text-end fw-semibold">
<%= Number(y.total).toFixed(2) %>
</td>
<td class="text-end fw-semibold"><%= Number(y.total).toFixed(2) %></td>
</tr>
<% }) %>
</tbody>
@ -98,17 +186,19 @@
</div>
</div>
<!-- =========================
QUARTALSUMSATZ
========================== -->
<div class="col-xl-3 col-lg-6">
<% } else if (view === "quarter") { %>
<!-- Quartalsumsatz -->
<div class="col-12">
<div class="card h-100">
<div class="card-header fw-semibold">Quartalsumsatz</div>
<div class="card-header fw-semibold">
<%= t.adminInvoice.quarterlySales %>
</div>
<div class="card-body p-0">
<table class="table table-sm table-striped mb-0">
<table class="table table-striped mb-0">
<thead>
<tr>
<th>Jahr</th>
<th><%= t.global.year %></th>
<th>Q</th>
<th class="text-end">€</th>
</tr>
@ -120,13 +210,13 @@
Keine Daten
</td>
</tr>
<% } %> <% quarterly.forEach(q => { %>
<% } %>
<% quarterly.forEach(q => { %>
<tr>
<td><%= q.year %></td>
<td>Q<%= q.quarter %></td>
<td class="text-end fw-semibold">
<%= Number(q.total).toFixed(2) %>
</td>
<td class="text-end fw-semibold"><%= Number(q.total).toFixed(2) %></td>
</tr>
<% }) %>
</tbody>
@ -135,17 +225,19 @@
</div>
</div>
<!-- =========================
MONATSUMSATZ
========================== -->
<div class="col-xl-3 col-lg-6">
<% } else if (view === "month") { %>
<!-- Monatsumsatz -->
<div class="col-12">
<div class="card h-100">
<div class="card-header fw-semibold">Monatsumsatz</div>
<div class="card-header fw-semibold">
<%= t.adminInvoice.monthSales %>
</div>
<div class="card-body p-0">
<table class="table table-sm table-striped mb-0">
<table class="table table-striped mb-0">
<thead>
<tr>
<th>Monat</th>
<th><%= t.global.month %></th>
<th class="text-end">€</th>
</tr>
</thead>
@ -156,12 +248,12 @@
Keine Daten
</td>
</tr>
<% } %> <% monthly.forEach(m => { %>
<% } %>
<% monthly.forEach(m => { %>
<tr>
<td><%= m.month %></td>
<td class="text-end fw-semibold">
<%= Number(m.total).toFixed(2) %>
</td>
<td class="text-end fw-semibold"><%= Number(m.total).toFixed(2) %></td>
</tr>
<% }) %>
</tbody>
@ -170,15 +262,18 @@
</div>
</div>
<!-- =========================
UMSATZ PRO PATIENT
========================== -->
<div class="col-xl-3 col-lg-6">
<% } else if (view === "patient") { %>
<!-- Umsatz pro Patient -->
<div class="col-12">
<div class="card h-100">
<div class="card-header fw-semibold">Umsatz pro Patient</div>
<div class="card-header fw-semibold">
<%= t.adminInvoice.patientsSales %>
</div>
<div class="card-body p-2">
<!-- 🔍 Suche -->
<form method="get" class="mb-2 d-flex gap-2">
<!-- ✅ view beibehalten -->
<input type="hidden" name="view" value="<%= view %>" />
<input type="hidden" name="fromYear" value="<%= fromYear %>" />
<input type="hidden" name="toYear" value="<%= toYear %>" />
@ -187,23 +282,25 @@
name="q"
value="<%= search %>"
class="form-control form-control-sm"
placeholder="Patient suchen..."
placeholder="<%= t.adminInvoice.patientsSales %>"
/>
<button class="btn btn-sm btn-outline-primary">Suchen</button>
<button class="btn btn-sm btn-outline-primary">
<%= t.adminInvoice.search %>
</button>
<a
href="/admin/invoices?fromYear=<%= fromYear %>&toYear=<%= toYear %>"
href="/admin/invoices?view=patient&fromYear=<%= fromYear %>&toYear=<%= toYear %>"
class="btn btn-sm btn-outline-secondary"
>
Reset
<%= t.global.reset %>
</a>
</form>
<table class="table table-sm table-striped mb-0">
<table class="table table-striped mb-0">
<thead>
<tr>
<th>Patient</th>
<th><%= t.adminInvoice.patient %></th>
<th class="text-end">€</th>
</tr>
</thead>
@ -214,12 +311,12 @@
Keine Daten
</td>
</tr>
<% } %> <% patients.forEach(p => { %>
<% } %>
<% patients.forEach(p => { %>
<tr>
<td><%= p.patient %></td>
<td class="text-end fw-semibold">
<%= Number(p.total).toFixed(2) %>
</td>
<td class="text-end fw-semibold"><%= Number(p.total).toFixed(2) %></td>
</tr>
<% }) %>
</tbody>
@ -227,6 +324,11 @@
</div>
</div>
</div>
<% } %>
</div>
</div>
</div>
</div>
</body>

View File

@ -2,7 +2,7 @@
<html lang="de">
<head>
<meta charset="UTF-8" />
<title>Praxis System</title>
<title>Dashboard</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/css/bootstrap.min.css" />
@ -21,193 +21,75 @@
min-height: 100vh;
}
/* Sidebar */
.sidebar {
width: 240px;
background: #111827;
color: white;
padding: 20px;
/* ✅ erzwingt Sidebar links */
.sidebar-wrap {
width: 260px;
flex: 0 0 260px;
}
/* ✅ Main rechts */
.main {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
.logo {
font-size: 18px;
font-weight: 700;
margin-bottom: 30px;
display: flex;
align-items: center;
gap: 10px;
}
.nav-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 15px;
border-radius: 8px;
color: #cbd5e1;
text-decoration: none;
margin-bottom: 6px;
font-size: 14px;
}
.nav-item:hover {
background: #1f2937;
color: white;
}
.nav-item.active {
background: #2563eb;
color: white;
}
.sidebar .spacer {
flex: 1;
}
/* Main */
.main {
flex: 1;
padding: 25px 30px;
}
.topbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
}
.topbar h3 {
margin: 0;
}
.main {
flex: 1;
padding: 24px;
background: #f4f6f9;
overflow: hidden;
display: flex;
flex-direction: column;
}
.waiting-monitor {
flex: 1;
display: flex;
flex-direction: column;
margin-top: 10px;
}
.waiting-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
grid-auto-rows: 80px;
gap: 12px;
width: 100%;
}
.waiting-slot {
border: 2px dashed #cbd5e1;
border-radius: 10px;
background: #f8fafc;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
text-decoration: none;
color: inherit;
}
.waiting-slot.occupied {
border-style: solid;
background: #eefdf5;
}
.patient-text {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.waiting-slot.clickable {
cursor: pointer;
transition: 0.15s ease;
}
.waiting-slot.clickable:hover {
transform: scale(1.03);
box-shadow: 0 0 0 2px #2563eb;
}
.nav-item.locked {
opacity: 0.5;
cursor: not-allowed;
}
.nav-item.locked:hover {
background: transparent;
color: #cbd5e1;
.content {
padding: 24px;
}
</style>
</head>
<body>
<div class="layout">
<!-- ✅ SIDEBAR ausgelagert -->
<%- include("partials/sidebar", { user, active: "patients" }) %>
<!-- MAIN CONTENT -->
<div class="main">
<div class="topbar">
<h3>Willkommen, <%= user.username %></h3>
<!-- ✅ Sidebar links fix -->
<div class="sidebar-wrap">
<%- include("partials/sidebar", { user, active: "dashboard" }) %>
</div>
<!-- Flash Messages -->
<!-- ✅ Main rechts -->
<div class="main">
<!-- ✅ Schwarzer Balken wie patients -->
<nav class="navbar navbar-dark bg-dark position-relative px-3">
<div
class="position-absolute top-50 start-50 translate-middle d-flex align-items-center gap-2 text-white"
>
<i class="bi bi-house-door fs-4"></i>
<span class="fw-semibold fs-5">
Willkommen, <%= (user.username || '').toUpperCase() %>
</span>
</div>
<div class="ms-auto">
<a href="/logout" class="btn btn-outline-light btn-sm">
Logout
</a>
</div>
</nav>
<div class="content">
<%- include("partials/flash") %>
<!-- =========================
WARTEZIMMER MONITOR
========================= -->
<div class="waiting-monitor">
<h5 class="mb-3">🪑 Wartezimmer-Monitor</h5>
<div class="card shadow">
<div class="card-body">
<h5 class="mb-0">🪑 Wartezimmer-Monitor</h5>
<div class="waiting-grid">
<div class="text-muted mt-2">
<% if (waitingPatients && waitingPatients.length > 0) { %>
<% waitingPatients.forEach(p => { %>
<% if (user.role === 'arzt') { %>
<a href="/patients/<%= p.id %>/overview" class="waiting-slot occupied clickable">
<div class="patient-text">
<div class="name"><%= p.firstname %> <%= p.lastname %></div>
<div class="birthdate">
<%= new Date(p.birthdate).toLocaleDateString("de-DE") %>
</div>
</div>
</a>
Patienten im Wartezimmer: <%= waitingPatients.length %>
<% } else { %>
<div class="waiting-slot occupied">
<div class="patient-text">
<div class="name"><%= p.firstname %> <%= p.lastname %></div>
<div class="birthdate">
<%= new Date(p.birthdate).toLocaleDateString("de-DE") %>
</div>
</div>
</div>
<% } %>
<% }) %>
<% } else { %>
<div class="text-muted">Keine Patienten im Wartezimmer.</div>
Keine Patienten im Wartezimmer.
<% } %>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/js/bootstrap.bundle.min.js"></script>
<script src="/js/flash_auto_hide.js"></script>
</body>
</html>

View File

@ -75,6 +75,6 @@
<!-- ✅ Zurück zum Dashboard -->
<a href="/dashboard" class="nav-item">
<i class="bi bi-arrow-left"></i> Dashboard
<i class="bi bi-arrow-left"></i> <%= t.global.dashboard %>
</a>
</div>

View File

@ -0,0 +1,36 @@
<div class="sidebar">
<div class="logo">
<i class="bi bi-cash-coin"></i>
Invoice Menü
</div>
<a
href="/admin/invoices?view=year"
class="nav-item <%= active === 'sales_year' ? 'active' : '' %>"
>
<i class="bi bi-calendar3"></i> <%= t.adminInvoice.annualSales %>
</a>
<a
href="/admin/invoices?view=quarter"
class="nav-item <%= active === 'sales_quarter' ? 'active' : '' %>"
>
<i class="bi bi-calendar2-week"></i> <%= t.adminInvoice.quarterlySales %>
</a>
<a
href="/admin/invoices?view=month"
class="nav-item <%= active === 'sales_month' ? 'active' : '' %>"
>
<i class="bi bi-calendar2"></i> <%= t.adminInvoice.monthSales %>
</a>
<a
href="/admin/invoices?view=patient"
class="nav-item <%= active === 'sales_patient' ? 'active' : '' %>"
>
<i class="bi bi-people"></i> <%= t.adminInvoice.patientsSales %>
</a>
<div class="spacer"></div>
</div>

View File

@ -0,0 +1,101 @@
<div class="sidebar">
<div class="logo">
<i class="bi bi-person-lines-fill"></i>
Patient
</div>
<!-- ✅ Patient Badge -->
<% if (patient) { %>
<div class="patient-badge">
<div class="patient-name">
<strong><%= patient.firstname %> <%= patient.lastname %></strong>
</div>
</div>
<% } else { %>
<div class="patient-badge">
<div class="patient-name">
<strong>Kein Patient gewählt</strong>
</div>
<div class="patient-meta">
Bitte auswählen
</div>
</div>
<% } %>
</div>
<style>
.sidebar {
width: 240px;
background: #111827;
color: white;
padding: 20px;
display: flex;
flex-direction: column;
}
.logo {
font-size: 18px;
font-weight: 700;
margin-bottom: 18px;
display: flex;
align-items: center;
gap: 10px;
}
.patient-badge {
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 12px;
margin-bottom: 15px;
}
.patient-name {
font-size: 14px;
margin-bottom: 4px;
}
.patient-meta {
font-size: 12px;
opacity: 0.85;
}
.nav-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 15px;
border-radius: 8px;
color: #cbd5e1;
text-decoration: none;
margin-bottom: 6px;
font-size: 14px;
border: 0;
background: transparent;
width: 100%;
}
.nav-item:hover {
background: #1f2937;
color: white;
}
.nav-item.active {
background: #2563eb;
color: white;
}
.nav-item.disabled {
opacity: 0.45;
pointer-events: none;
}
.nav-btn {
cursor: pointer;
text-align: left;
}
.spacer {
flex: 1;
}
</style>

View File

@ -0,0 +1,177 @@
<div class="sidebar">
<div class="sidebar-header">
<div class="logo">
<i class="bi bi-person-lines-fill"></i>
Patient
</div>
<!-- ✅ Patient Info wird per JS ersetzt -->
<div class="patient-badge" id="sidebarPatientInfo">
<div class="patient-name">
<strong>Kein Patient gewählt</strong>
</div>
<div class="patient-meta text-muted">Bitte Patient auswählen</div>
</div>
</div>
<!-- ✅ Übersicht -->
<a id="sbOverview" href="#" class="nav-item disabled">
<i class="bi bi-clipboard2-check"></i> Übersicht
</a>
<!-- ✅ Verlauf -->
<a id="sbHistory" href="#" class="nav-item disabled">
<i class="bi bi-clock-history"></i> Verlauf
</a>
<!-- ✅ Bearbeiten -->
<a id="sbEdit" href="#" class="nav-item disabled">
<i class="bi bi-pencil-square"></i> Bearbeiten
</a>
<!-- ✅ Medikamente -->
<a id="sbMeds" href="#" class="nav-item disabled">
<i class="bi bi-capsule"></i> Medikamente
</a>
<!-- ✅ Wartezimmer -->
<div id="sbWaitingRoomWrapper">
<div class="nav-item disabled">
<i class="bi bi-door-open"></i> Ins Wartezimmer
</div>
</div>
<!-- ✅ Sperren / Entsperren -->
<div id="sbActiveWrapper">
<div class="nav-item disabled">
<i class="bi bi-lock-fill"></i> Sperren / Entsperren
</div>
</div>
<!-- ✅ Upload -->
<div class="sidebar-upload">
<div style="font-weight: 600; margin: 10px 0 6px 0; color: #e5e7eb">
<i class="bi bi-paperclip"></i> Datei Upload
</div>
<form
id="sbUploadForm"
method="POST"
action="#"
enctype="multipart/form-data"
>
<input
id="sbUploadInput"
type="file"
name="file"
class="form-control form-control-sm mb-2"
disabled
required
/>
<button
id="sbUploadBtn"
type="submit"
class="btn btn-sm btn-outline-light w-100"
disabled
>
📎 Hochladen
</button>
</form>
<div class="sidebar-muted" style="margin-top: 6px">
Nur aktiv nach Patientenauswahl
</div>
</div>
<div class="spacer"></div>
</div>
<style>
.sidebar {
width: 240px;
background: #111827;
color: white;
padding: 20px;
display: flex;
flex-direction: column;
min-height: 100vh;
}
.logo {
font-size: 18px;
font-weight: 700;
margin-bottom: 18px;
display: flex;
align-items: center;
gap: 10px;
}
.patient-badge {
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 12px;
margin-bottom: 15px;
}
.patient-name {
font-size: 14px;
margin-bottom: 4px;
}
.patient-meta {
font-size: 12px;
opacity: 0.85;
}
.nav-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 15px;
border-radius: 8px;
color: #cbd5e1;
text-decoration: none;
margin-bottom: 6px;
font-size: 14px;
border: 0;
background: transparent;
width: 100%;
}
.nav-item:hover {
background: #1f2937;
color: white;
}
.nav-item.active {
background: #2563eb;
color: white;
}
.nav-item.disabled {
opacity: 0.45;
pointer-events: none;
}
.nav-btn {
cursor: pointer;
text-align: left;
}
.sidebar-upload {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid rgba(255, 255, 255, 0.12);
}
.sidebar-muted {
font-size: 12px;
opacity: 0.75;
}
.spacer {
flex: 1;
}
</style>

View File

@ -1,42 +1,11 @@
<div class="sidebar">
<!-- ✅ Logo + Sprachbuttons -->
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:30px;">
<div class="logo" style="margin:0;">
🩺 Praxis System
</div>
<!-- ✅ Sprache oben rechts -->
<div style="display:flex; gap:6px;">
<a
href="/lang/de"
class="btn btn-sm btn-outline-light <%= lang === 'de' ? 'active' : '' %>"
style="padding:2px 8px; font-size:12px;"
title="Deutsch"
>
DE
</a>
<a
href="/lang/es"
class="btn btn-sm btn-outline-light <%= lang === 'es' ? 'active' : '' %>"
style="padding:2px 8px; font-size:12px;"
title="Español"
>
ES
</a>
</div>
</div>
<%
const role = user?.role || null;
// ✅ Regeln:
// Arztbereich: NUR arzt
const canDoctorArea = role === "arzt";
// Verwaltung: NUR admin
const canAdminArea = role === "admin";
// ✅ Regeln
const canDoctorArea = role === "arzt"; // nur Arzt
const canAdminArea = role === "admin"; // nur Admin
const canPatients = role === "arzt" || role === "mitarbeiter";
const canStaffArea = role === "arzt" || role === "mitarbeiter"; // Medikamente + offene Leistungen
function hrefIfAllowed(allowed, href) {
return allowed ? href : "#";
@ -51,41 +20,55 @@
}
%>
<div class="sidebar">
<div class="logo">
<i class="bi bi-hospital"></i>
Praxis System
</div>
<!-- Dashboard -->
<a
href="/dashboard"
class="nav-item <%= active === 'dashboard' ? 'active' : '' %>"
>
<i class="bi bi-house-door"></i> Dashboard
</a>
<!-- Patienten -->
<a
href="<%= hrefIfAllowed(canDoctorArea, '/patients') %>"
class="nav-item <%= active === 'patients' ? 'active' : '' %> <%= lockClass(canDoctorArea) %>"
<%- lockClick(canDoctorArea) %>
title="<%= canDoctorArea ? '' : 'Nur Arzt' %>"
href="<%= hrefIfAllowed(canPatients, '/patients') %>"
class="nav-item <%= active === 'patients' ? 'active' : '' %> <%= lockClass(canPatients) %>"
<%- lockClick(canPatients) %>
title="<%= canPatients ? '' : 'Nur Arzt oder Mitarbeiter' %>"
>
<i class="bi bi-people"></i> <%= t.sidebar.patients %>
<% if (!canDoctorArea) { %>
<i class="bi bi-people"></i> Patienten
<% if (!canPatients) { %>
<span style="margin-left:auto;"><i class="bi bi-lock-fill"></i></span>
<% } %>
</a>
<!-- Medikamente -->
<a
href="<%= hrefIfAllowed(canDoctorArea, '/medications') %>"
class="nav-item <%= active === 'medications' ? 'active' : '' %> <%= lockClass(canDoctorArea) %>"
<%- lockClick(canDoctorArea) %>
title="<%= canDoctorArea ? '' : 'Nur Arzt' %>"
href="<%= hrefIfAllowed(canStaffArea, '/medications') %>"
class="nav-item <%= active === 'medications' ? 'active' : '' %> <%= lockClass(canStaffArea) %>"
<%- lockClick(canStaffArea) %>
title="<%= canStaffArea ? '' : 'Nur Arzt oder Mitarbeiter' %>"
>
<i class="bi bi-capsule"></i> <%= t.sidebar.medications %>
<% if (!canDoctorArea) { %>
<i class="bi bi-capsule"></i> Medikamente
<% if (!canStaffArea) { %>
<span style="margin-left:auto;"><i class="bi bi-lock-fill"></i></span>
<% } %>
</a>
<!-- Offene Leistungen -->
<a
href="<%= hrefIfAllowed(canDoctorArea, '/services/open') %>"
class="nav-item <%= active === 'services' ? 'active' : '' %> <%= lockClass(canDoctorArea) %>"
<%- lockClick(canDoctorArea) %>
title="<%= canDoctorArea ? '' : 'Nur Arzt' %>"
href="<%= hrefIfAllowed(canStaffArea, '/services/open') %>"
class="nav-item <%= active === 'services' ? 'active' : '' %> <%= lockClass(canStaffArea) %>"
<%- lockClick(canStaffArea) %>
title="<%= canStaffArea ? '' : 'Nur Arzt oder Mitarbeiter' %>"
>
<i class="bi bi-receipt"></i> <%= t.sidebar.servicesOpen %>
<% if (!canDoctorArea) { %>
<i class="bi bi-receipt"></i> Offene Leistungen
<% if (!canStaffArea) { %>
<span style="margin-left:auto;"><i class="bi bi-lock-fill"></i></span>
<% } %>
</a>
@ -97,20 +80,20 @@
<%- lockClick(canDoctorArea) %>
title="<%= canDoctorArea ? '' : 'Nur Arzt' %>"
>
<i class="bi bi-cash-coin"></i> <%= t.sidebar.billing %>
<i class="bi bi-cash-stack"></i> Abrechnung
<% if (!canDoctorArea) { %>
<span style="margin-left:auto;"><i class="bi bi-lock-fill"></i></span>
<% } %>
</a>
<!-- Verwaltung (nur Admin) -->
<!-- Verwaltung -->
<a
href="<%= hrefIfAllowed(canAdminArea, '/admin/users') %>"
class="nav-item <%= active === 'admin' ? 'active' : '' %> <%= lockClass(canAdminArea) %>"
<%- lockClick(canAdminArea) %>
title="<%= canAdminArea ? '' : 'Nur Admin' %>"
>
<i class="bi bi-gear"></i> <%= t.sidebar.admin %>
<i class="bi bi-gear"></i> Verwaltung
<% if (!canAdminArea) { %>
<span style="margin-left:auto;"><i class="bi bi-lock-fill"></i></span>
<% } %>
@ -122,3 +105,58 @@
<i class="bi bi-box-arrow-right"></i> Logout
</a>
</div>
<style>
.sidebar {
width: 260px;
background: #111827;
color: white;
padding: 20px;
display: flex;
flex-direction: column;
min-height: 100vh;
}
.logo {
font-size: 18px;
font-weight: 700;
margin-bottom: 18px;
display: flex;
align-items: center;
gap: 10px;
}
.nav-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 15px;
border-radius: 10px;
color: #cbd5e1;
text-decoration: none;
margin-bottom: 6px;
font-size: 14px;
border: 0;
background: transparent;
width: 100%;
}
.nav-item:hover {
background: #1f2937;
color: white;
}
.nav-item.active {
background: #2563eb;
color: white;
}
.nav-item.locked {
opacity: 0.45;
pointer-events: none;
}
.spacer {
flex: 1;
}
</style>

View File

@ -2,24 +2,63 @@
<html lang="de">
<head>
<meta charset="UTF-8" />
<title>Patientenübersicht</title>
<title>
Patient: <%= patient.firstname %> <%= patient.lastname %>
</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/css/bootstrap.min.css" />
<link rel="stylesheet" href="/bootstrap-icons/bootstrap-icons.min.css" />
<style>
body {
margin: 0;
background: #f4f6f9;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, Ubuntu;
}
.layout {
display: flex;
min-height: 100vh;
}
.main {
flex: 1;
padding: 0;
background: #f4f6f9;
overflow: hidden;
display: flex;
flex-direction: column;
}
</style>
</head>
<body class="bg-light">
<body>
<div class="layout">
<!-- ✅ Sidebar -->
<%- include("partials/patient_overview_dashboard_sidebar", { patient, active: "overview" }) %>
<!-- ✅ MAIN -->
<div class="main">
<!-- NAVBAR -->
<nav class="navbar navbar-dark bg-dark position-relative px-3">
<div
class="position-absolute top-50 start-50 translate-middle d-flex align-items-center gap-2 text-white"
>
<span style="font-size: 1.4rem">👥</span>
<span class="fw-semibold fs-5">Patientenübersicht</span>
<i class="bi bi-person-badge fs-4"></i>
<span class="fw-semibold fs-5">
<%= patient.firstname %> <%= patient.lastname %>
</span>
</div>
<div class="ms-auto">
<div class="ms-auto d-flex gap-2">
<a href="/patients" class="btn btn-outline-light btn-sm">
⬅️ Patientenübersicht
</a>
<a href="/dashboard" class="btn btn-outline-primary btn-sm">
⬅️ Dashboard
Dashboard
</a>
</div>
</nav>
@ -31,8 +70,8 @@
<h4>👤 <%= patient.firstname %> <%= patient.lastname %></h4>
<p class="text-muted mb-3">
Geboren am <%= new
Date(patient.birthdate).toLocaleDateString("de-DE") %>
Geboren am
<%= new Date(patient.birthdate).toLocaleDateString("de-DE") %>
</p>
<ul class="list-group">
@ -44,8 +83,8 @@
</li>
<li class="list-group-item">
<strong>Adresse:</strong>
<%= patient.street || "" %> <%= patient.house_number || "" %>, <%=
patient.postal_code || "" %> <%= patient.city || "" %>
<%= patient.street || "" %> <%= patient.house_number || "" %>,
<%= patient.postal_code || "" %> <%= patient.city || "" %>
</li>
</ul>
</div>
@ -132,10 +171,7 @@
<tbody>
<% invoices.forEach(i => { %>
<tr>
<td>
<%= new Date(i.invoice_date).toLocaleDateString("de-DE")
%>
</td>
<td><%= new Date(i.invoice_date).toLocaleDateString("de-DE") %></td>
<td><%= Number(i.total_amount).toFixed(2) %> €</td>
<td>
<% if (i.file_path) { %>
@ -146,7 +182,9 @@
>
📄 Öffnen
</a>
<% } else { %> - <% } %>
<% } else { %>
-
<% } %>
</td>
</tr>
<% }) %>
@ -159,5 +197,9 @@
</div>
</div>
</div>
</div>
</div>
<script src="/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@ -4,19 +4,65 @@
<meta charset="UTF-8" />
<title>Patientenübersicht</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/css/bootstrap.min.css" />
<link rel="stylesheet" href="/bootstrap-icons/bootstrap-icons.min.css" />
<script src="/js/flash_auto_hide.js"></script>
<style>
body {
margin: 0;
background: #f4f6f9;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, Ubuntu;
}
.layout {
display: flex;
min-height: 100vh;
}
.main {
flex: 1;
padding: 0;
background: #f4f6f9;
overflow: hidden;
display: flex;
flex-direction: column;
}
.radio-col {
width: 45px;
}
.auto-hide-flash {
transition: opacity 0.6s ease, transform 0.6s ease;
}
.auto-hide-flash.flash-hide {
opacity: 0;
transform: translateY(-10px);
pointer-events: none;
}
</style>
</head>
<body class="bg-light">
<body>
<div class="layout">
<!-- ✅ Sidebar -->
<%- include("partials/patient_sidebar", { active: "patients_list", patient: null }) %>
<!-- ✅ MAIN -->
<div class="main">
<nav class="navbar navbar-dark bg-dark position-relative px-3">
<!-- 🟢 ZENTRIERTER TITEL -->
<div
class="position-absolute top-50 start-50 translate-middle d-flex align-items-center gap-2 text-white"
>
<span style="font-size: 1.4rem">👥</span>
<i class="bi bi-people fs-4"></i>
<span class="fw-semibold fs-5">Patientenübersicht</span>
</div>
<!-- 🔵 RECHTS: DASHBOARD -->
<div class="ms-auto">
<a href="/dashboard" class="btn btn-outline-primary btn-sm">
⬅️ Dashboard
@ -27,9 +73,10 @@
<div class="container-fluid mt-4">
<%- include("partials/flash") %>
<!-- Aktionen oben -->
<div class="d-flex gap-2 mb-3">
<a href="/patients/create" class="btn btn-success"> + Neuer Patient </a>
<a href="/patients/create" class="btn btn-success">
+ Neuer Patient
</a>
</div>
<div class="card shadow">
@ -66,7 +113,9 @@
</div>
<div class="col-md-3 d-flex gap-2">
<button class="btn btn-primary w-100">Suchen</button>
<button type="submit" class="btn btn-primary w-100">
Suchen
</button>
<a href="/patients" class="btn btn-secondary w-100">
Zurücksetzen
</a>
@ -75,12 +124,10 @@
<!-- Tabelle -->
<div class="table-responsive">
<table
class="table table-bordered table-hover align-middle table-sm"
>
<table class="table table-bordered table-hover align-middle table-sm">
<thead class="table-dark">
<tr>
<th>ID</th>
<th class="radio-col">✔</th>
<th>Name</th>
<th>N.I.E. / DNI</th>
<th>Geschlecht</th>
@ -93,9 +140,9 @@
<th>Notizen</th>
<th>Erstellt</th>
<th>Geändert</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<% if (patients.length === 0) { %>
<tr>
@ -103,17 +150,34 @@
Keine Patienten gefunden
</td>
</tr>
<% } %> <% patients.forEach(p => { %>
<tr>
<td><%= p.id %></td>
<% } %>
<% patients.forEach(p => { %>
<tr>
<td class="text-center">
<input
type="radio"
name="selectedPatient"
class="form-check-input patient-radio"
data-id="<%= p.id %>"
data-firstname="<%= p.firstname %>"
data-lastname="<%= p.lastname %>"
data-waiting="<%= p.waiting_room ? '1' : '0' %>"
data-active="<%= p.active ? '1' : '0' %>"
/>
</td>
<td>
<strong><%= p.firstname %> <%= p.lastname %></strong>
</td>
<td><strong><%= p.firstname %> <%= p.lastname %></strong></td>
<td><%= p.dni || "-" %></td>
<td>
<% if (p.gender === 'm') { %>m <% } else if (p.gender ===
'w') { %>w <% } else if (p.gender === 'd') { %>d <% } else {
%>-<% } %>
<% if (p.gender === 'm') { %>m
<% } else if (p.gender === 'w') { %>w
<% } else if (p.gender === 'd') { %>d
<% } else { %>-<% } %>
</td>
<td>
@ -134,7 +198,7 @@
<% if (p.active) { %>
<span class="badge bg-success">Aktiv</span>
<% } else { %>
<span class="badge bg-secondary">Inaktiv</span>
<span class="badge bg-danger">Inaktiv</span>
<% } %>
</td>
@ -144,127 +208,23 @@
<td><%= new Date(p.created_at).toLocaleString("de-DE") %></td>
<td><%= new Date(p.updated_at).toLocaleString("de-DE") %></td>
<!-- AKTIONEN -->
<td class="text-nowrap">
<div class="dropdown">
<button
class="btn btn-sm btn-outline-secondary"
data-bs-toggle="dropdown"
>
Auswahl ▾
</button>
<ul
class="dropdown-menu dropdown-menu-end position-fixed"
>
<!-- ✏️ BEARBEITEN -->
<li>
<a
class="dropdown-item"
href="/patients/edit/<%= p.id %>"
>
✏️ Bearbeiten
</a>
</li>
<li><hr class="dropdown-divider" /></li>
<!-- 🪑 WARTEZIMMER -->
<% if (p.waiting_room) { %>
<li>
<span class="dropdown-item text-muted">
🪑 Wartet bereits
</span>
</li>
<% } else { %>
<li>
<form
method="POST"
action="/patients/waiting-room/<%= p.id %>"
>
<button class="dropdown-item">
🪑 Ins Wartezimmer
</button>
</form>
</li>
<% } %>
<li><hr class="dropdown-divider" /></li>
<!-- 💊 MEDIKAMENTE -->
<li>
<a
class="dropdown-item"
href="/patients/<%= p.id %>/medications"
>
💊 Medikamente
</a>
</li>
<li><hr class="dropdown-divider" /></li>
<!-- 🔒 STATUS -->
<li>
<% if (p.active) { %>
<form
method="POST"
action="/patients/deactivate/<%= p.id %>"
>
<button class="dropdown-item text-warning">
🔒 Sperren
</button>
</form>
<% } else { %>
<form
method="POST"
action="/patients/activate/<%= p.id %>"
>
<button class="dropdown-item text-success">
🔓 Entsperren
</button>
</form>
<% } %>
</li>
<!-- 📋 ÜBERSICHT -->
<li>
<a class="dropdown-item" href="/patients/<%= p.id %>">
📋 Übersicht
</a>
</li>
<li><hr class="dropdown-divider" /></li>
<!-- 📎 DATEI-UPLOAD -->
<li class="px-3 py-2">
<form
method="POST"
action="/patients/<%= p.id %>/files"
enctype="multipart/form-data"
>
<input
type="file"
name="file"
class="form-control form-control-sm mb-2"
required
/>
<button class="btn btn-sm btn-secondary w-100">
📎 Hochladen
</button>
</form>
</li>
</ul>
</div>
</td>
</tr>
<% }) %>
</tbody>
</table>
</div>
<div class="text-muted mt-2" style="font-size: 13px;">
Patient auswählen → Sidebar links zeigt Aktionen ✅
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/js/bootstrap.bundle.min.js"></script>
<!-- ✅ Helmet-safe -->
<script src="/js/patients_sidebar.js"></script>
</body>
</html>