diff --git a/controllers/admin.controller.js b/controllers/admin.controller.js
index 87a6c7e..be32a6f 100644
--- a/controllers/admin.controller.js
+++ b/controllers/admin.controller.js
@@ -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);
diff --git a/locales/de.json b/locales/de.json
index 0a20449..cc1517c 100644
--- a/locales/de.json
+++ b/locales/de.json
@@ -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"
}
}
diff --git a/locales/es.json b/locales/es.json
index 3c4eedd..9dbf96a 100644
--- a/locales/es.json
+++ b/locales/es.json
@@ -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"
}
}
diff --git a/public/js/flash_auto_hide.js b/public/js/flash_auto_hide.js
new file mode 100644
index 0000000..3813d03
--- /dev/null
+++ b/public/js/flash_auto_hide.js
@@ -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
+});
diff --git a/public/js/patients_sidebar.js b/public/js/patients_sidebar.js
new file mode 100644
index 0000000..0b63905
--- /dev/null
+++ b/public/js/patients_sidebar.js
@@ -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 = `
+
+ ${firstname} ${lastname}
+
+
+ ID: ${id}
+
+ `;
+
+ // ✅ Ü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 = `
+
+ Ins Wartezimmer (Patient inaktiv)
+
+ `;
+ } else if (waiting) {
+ sbWaitingRoomWrapper.innerHTML = `
+
+ Wartet bereits
+
+ `;
+ } else {
+ sbWaitingRoomWrapper.innerHTML = `
+
+ `;
+ }
+
+ // ✅ Sperren / Entsperren
+ if (active) {
+ sbActiveWrapper.innerHTML = `
+
+ `;
+ } else {
+ sbActiveWrapper.innerHTML = `
+
+ `;
+ }
+
+ // ✅ Upload nur aktiv wenn Patient ausgewählt
+ sbUploadForm.action = "/patients/" + id + "/files";
+ sbUploadInput.disabled = false;
+ sbUploadBtn.disabled = false;
+ });
+ });
+});
diff --git a/views/admin/admin_invoice_overview.ejs b/views/admin/admin_invoice_overview.ejs
index 80ed2c1..82eadd6 100644
--- a/views/admin/admin_invoice_overview.ejs
+++ b/views/admin/admin_invoice_overview.ejs
@@ -2,230 +2,332 @@
- Rechnungsübersicht
+ <%= t.adminInvoice.invoiceOverview %>
+
+
-
-
-
+
+
+ <%- include("../partials/invoice_sidebar", { active, t }) %>
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
- | Jahr |
- € |
-
-
-
- <% if (yearly.length === 0) { %>
-
- |
- Keine Daten
- |
-
- <% } %> <% yearly.forEach(y => { %>
-
- | <%= y.year %> |
-
- <%= Number(y.total).toFixed(2) %>
- |
-
- <% }) %>
-
-
-
+ NAVBAR
+ ========================== -->
+
+
+
+
-
-
-
-
-
-
-
- | Jahr |
- Q |
- € |
-
-
-
- <% if (quarterly.length === 0) { %>
-
- |
- Keine Daten
- |
-
- <% } %> <% quarterly.forEach(q => { %>
-
- | <%= q.year %> |
- Q<%= q.quarter %> |
-
- <%= Number(q.total).toFixed(2) %>
- |
-
- <% }) %>
-
-
+ FILTER: JAHR VON / BIS
+ ========================== -->
+
-
-
-
-
-
-
-
-
-
- | Monat |
- € |
-
-
-
- <% if (monthly.length === 0) { %>
-
- |
- Keine Daten
- |
-
- <% } %> <% monthly.forEach(m => { %>
-
- | <%= m.month %> |
-
- <%= Number(m.total).toFixed(2) %>
- |
-
- <% }) %>
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
- | Patient |
- € |
-
-
-
- <% if (patients.length === 0) { %>
-
- |
- Keine Daten
- |
-
- <% } %> <% patients.forEach(p => { %>
-
- | <%= p.patient %> |
-
- <%= Number(p.total).toFixed(2) %>
- |
-
- <% }) %>
-
-
+
+
+
+
+
+
+ <% if (view === "year") { %>
+
+
+
+
+
+
+
+
+
+ | <%= t.global.year %> |
+ € |
+
+
+
+ <% if (yearly.length === 0) { %>
+
+ |
+ Keine Daten
+ |
+
+ <% } %>
+
+ <% yearly.forEach(y => { %>
+
+ | <%= y.year %> |
+ <%= Number(y.total).toFixed(2) %> |
+
+ <% }) %>
+
+
+
+
+
+
+ <% } else if (view === "quarter") { %>
+
+
+
+
+
+
+
+
+
+ | <%= t.global.year %> |
+ Q |
+ € |
+
+
+
+ <% if (quarterly.length === 0) { %>
+
+ |
+ Keine Daten
+ |
+
+ <% } %>
+
+ <% quarterly.forEach(q => { %>
+
+ | <%= q.year %> |
+ Q<%= q.quarter %> |
+ <%= Number(q.total).toFixed(2) %> |
+
+ <% }) %>
+
+
+
+
+
+
+ <% } else if (view === "month") { %>
+
+
+
+
+
+
+
+
+
+ | <%= t.global.month %> |
+ € |
+
+
+
+ <% if (monthly.length === 0) { %>
+
+ |
+ Keine Daten
+ |
+
+ <% } %>
+
+ <% monthly.forEach(m => { %>
+
+ | <%= m.month %> |
+ <%= Number(m.total).toFixed(2) %> |
+
+ <% }) %>
+
+
+
+
+
+
+ <% } else if (view === "patient") { %>
+
+
+
+
+
+
+
+
+
+
+
+ | <%= t.adminInvoice.patient %> |
+ € |
+
+
+
+ <% if (patients.length === 0) { %>
+
+ |
+ Keine Daten
+ |
+
+ <% } %>
+
+ <% patients.forEach(p => { %>
+
+ | <%= p.patient %> |
+ <%= Number(p.total).toFixed(2) %> |
+
+ <% }) %>
+
+
+
+
+
+
+ <% } %>
+
diff --git a/views/dashboard.ejs b/views/dashboard.ejs
index 5d06917..b28690a 100644
--- a/views/dashboard.ejs
+++ b/views/dashboard.ejs
@@ -2,7 +2,7 @@
-
Praxis System
+
Dashboard
@@ -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;
}
-
- <%- include("partials/sidebar", { user, active: "patients" }) %>
+
+
-
+
-
-
Willkommen, <%= user.username %>
-
+
+
-
-
-
🪑 Wartezimmer-Monitor
+
+ <%- include("partials/flash") %>
-
- <% if (waitingPatients && waitingPatients.length > 0) { %>
+
+
+
🪑 Wartezimmer-Monitor
- <% waitingPatients.forEach(p => { %>
-
- <% if (user.role === 'arzt') { %>
-
-
-
<%= p.firstname %> <%= p.lastname %>
-
- <%= new Date(p.birthdate).toLocaleDateString("de-DE") %>
-
-
-
+
+ <% if (waitingPatients && waitingPatients.length > 0) { %>
+ Patienten im Wartezimmer: <%= waitingPatients.length %>
<% } else { %>
-
-
-
<%= p.firstname %> <%= p.lastname %>
-
- <%= new Date(p.birthdate).toLocaleDateString("de-DE") %>
-
-
-
+ Keine Patienten im Wartezimmer.
<% } %>
-
- <% }) %>
-
- <% } else { %>
-
Keine Patienten im Wartezimmer.
- <% } %>
+
+
+
+
+
diff --git a/views/partials/admin-sidebar.ejs b/views/partials/admin-sidebar.ejs
index e134499..dfa965f 100644
--- a/views/partials/admin-sidebar.ejs
+++ b/views/partials/admin-sidebar.ejs
@@ -75,6 +75,6 @@
- Dashboard
+ <%= t.global.dashboard %>
diff --git a/views/partials/invoice_sidebar.ejs b/views/partials/invoice_sidebar.ejs
new file mode 100644
index 0000000..1dcd948
--- /dev/null
+++ b/views/partials/invoice_sidebar.ejs
@@ -0,0 +1,36 @@
+
diff --git a/views/partials/patient_overview_dashboard_sidebar.ejs b/views/partials/patient_overview_dashboard_sidebar.ejs
new file mode 100644
index 0000000..4a8fc84
--- /dev/null
+++ b/views/partials/patient_overview_dashboard_sidebar.ejs
@@ -0,0 +1,101 @@
+
+
+
diff --git a/views/partials/patient_sidebar.ejs b/views/partials/patient_sidebar.ejs
new file mode 100644
index 0000000..ca38e7e
--- /dev/null
+++ b/views/partials/patient_sidebar.ejs
@@ -0,0 +1,177 @@
+
+
+
diff --git a/views/partials/sidebar.ejs b/views/partials/sidebar.ejs
index 1353948..39df3f4 100644
--- a/views/partials/sidebar.ejs
+++ b/views/partials/sidebar.ejs
@@ -1,91 +1,74 @@
+<%
+ const role = user?.role || null;
+
+ // ✅ 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 : "#";
+ }
+
+ function lockClass(allowed) {
+ return allowed ? "" : "locked";
+ }
+
+ function lockClick(allowed) {
+ return allowed ? "" : 'onclick="return false;"';
+ }
+%>
+