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 = ` + + `; + } else if (waiting) { + sbWaitingRoomWrapper.innerHTML = ` + + `; + } 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 }) %> - -
-
-
- -
- -
- -
- -
- -
-
- - -
+ +
-
-
-
Jahresumsatz
-
- - - - - - - - - <% if (yearly.length === 0) { %> - - - - <% } %> <% yearly.forEach(y => { %> - - - - - <% }) %> - -
Jahr
- Keine Daten -
<%= y.year %> - <%= Number(y.total).toFixed(2) %> -
-
+ NAVBAR + ========================== --> +
+ + + -
-
-
Quartalsumsatz
-
- - - - - - - - - - <% if (quarterly.length === 0) { %> - - - - <% } %> <% quarterly.forEach(q => { %> - - - - - - <% }) %> - -
JahrQ
- Keine Daten -
<%= q.year %>Q<%= q.quarter %> - <%= Number(q.total).toFixed(2) %> -
+ FILTER: JAHR VON / BIS + ========================== --> +
+
+ + + +
+
-
-
- -
-
-
Monatsumsatz
-
- - - - - - - - - <% if (monthly.length === 0) { %> - - - - <% } %> <% monthly.forEach(m => { %> - - - - - <% }) %> - -
Monat
- Keine Daten -
<%= m.month %> - <%= Number(m.total).toFixed(2) %> -
+
+
-
-
- -
-
-
Umsatz pro Patient
-
- - - - - - - - - - - Reset - - - - - - - - - - - - <% if (patients.length === 0) { %> - - - - <% } %> <% patients.forEach(p => { %> - - - - - <% }) %> - -
Patient
- Keine Daten -
<%= p.patient %> - <%= Number(p.total).toFixed(2) %> -
+
+
+ + + +
+ <% if (view === "year") { %> + + +
+
+
+ <%= t.adminInvoice.annualSales %> +
+
+ + + + + + + + + <% if (yearly.length === 0) { %> + + + + <% } %> + + <% yearly.forEach(y => { %> + + + + + <% }) %> + +
<%= t.global.year %>
+ Keine Daten +
<%= y.year %><%= Number(y.total).toFixed(2) %>
+
+
+
+ + <% } else if (view === "quarter") { %> + + +
+
+
+ <%= t.adminInvoice.quarterlySales %> +
+
+ + + + + + + + + + <% if (quarterly.length === 0) { %> + + + + <% } %> + + <% quarterly.forEach(q => { %> + + + + + + <% }) %> + +
<%= t.global.year %>Q
+ Keine Daten +
<%= q.year %>Q<%= q.quarter %><%= Number(q.total).toFixed(2) %>
+
+
+
+ + <% } else if (view === "month") { %> + + +
+
+
+ <%= t.adminInvoice.monthSales %> +
+
+ + + + + + + + + <% if (monthly.length === 0) { %> + + + + <% } %> + + <% monthly.forEach(m => { %> + + + + + <% }) %> + +
<%= t.global.month %>
+ Keine Daten +
<%= m.month %><%= Number(m.total).toFixed(2) %>
+
+
+
+ + <% } else if (view === "patient") { %> + + +
+
+
+ <%= t.adminInvoice.patientsSales %> +
+
+
+ + + + + + + + + + + <%= t.global.reset %> + +
+ + + + + + + + + + <% if (patients.length === 0) { %> + + + + <% } %> + + <% patients.forEach(p => { %> + + + + + <% }) %> + +
<%= t.adminInvoice.patient %>
+ Keine Daten +
<%= 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;"'; + } +%> + +
+ +
+
+

👤 <%= patient.firstname %> <%= patient.lastname %>

- -
- -
-
-
-
💊 Aktuelle Medikamente
+

+ Geboren am + <%= new Date(patient.birthdate).toLocaleDateString("de-DE") %> +

-
- <% if (medications.length === 0) { %> -

Keine aktiven Medikamente

- <% } else { %> - - - - - - - - - - <% medications.forEach(m => { %> - - - - - - <% }) %> - -
MedikamentVarianteAnweisung
<%= m.medication_name %><%= m.variant_dosage %><%= m.dosage_instruction || "-" %>
- <% } %> -
+
    +
  • + E-Mail: <%= patient.email || "-" %> +
  • +
  • + Telefon: <%= patient.phone || "-" %> +
  • +
  • + Adresse: + <%= patient.street || "" %> <%= patient.house_number || "" %>, + <%= patient.postal_code || "" %> <%= patient.city || "" %> +
  • +
-
- -
-
-
-
🧾 Rechnungen
+ +
+ +
+
+
+
💊 Aktuelle Medikamente
-
- <% if (invoices.length === 0) { %> -

Keine Rechnungen vorhanden

- <% } else { %> - - - - - - - - - - <% invoices.forEach(i => { %> - - - - - - <% }) %> - -
DatumBetragPDF
- <%= new Date(i.invoice_date).toLocaleDateString("de-DE") - %> - <%= Number(i.total_amount).toFixed(2) %> € - <% if (i.file_path) { %> - - 📄 Öffnen - - <% } else { %> - <% } %> -
- <% } %> +
+ <% if (medications.length === 0) { %> +

Keine aktiven Medikamente

+ <% } else { %> + + + + + + + + + + <% medications.forEach(m => { %> + + + + + + <% }) %> + +
MedikamentVarianteAnweisung
<%= m.medication_name %><%= m.variant_dosage %><%= m.dosage_instruction || "-" %>
+ <% } %> +
+
+
+
+ + +
+
+
+
🧾 Rechnungen
+ +
+ <% if (invoices.length === 0) { %> +

Keine Rechnungen vorhanden

+ <% } else { %> + + + + + + + + + + <% invoices.forEach(i => { %> + + + + + + <% }) %> + +
DatumBetragPDF
<%= new Date(i.invoice_date).toLocaleDateString("de-DE") %><%= Number(i.total_amount).toFixed(2) %> € + <% if (i.file_path) { %> + + 📄 Öffnen + + <% } else { %> + - + <% } %> +
+ <% } %> +
+
+ + diff --git a/views/patients.ejs b/views/patients.ejs index d5b2cef..8274e53 100644 --- a/views/patients.ejs +++ b/views/patients.ejs @@ -4,267 +4,227 @@ Patientenübersicht + + + + + - - + +
+ + <%- include("partials/patient_sidebar", { active: "patients_list", patient: null }) %> -
- <%- include("partials/flash") %> + +
+ -
-
- -
-
- -
+
+ <%- include("partials/flash") %> -
- -
+ -
- -
+
+
+ + +
+ +
-
- - - Zurücksetzen - -
- +
+ +
- -
- - - - - - - - - - - - - - - - - - - - - <% if (patients.length === 0) { %> - - - - <% } %> <% patients.forEach(p => { %> - - +
+ +
- - +
+ + + Zurücksetzen + +
+ - + +
+
IDNameN.I.E. / DNIGeschlechtGeburtstagE-MailTelefonAdresseLandStatusNotizenErstelltGeändertAktionen
- Keine Patienten gefunden -
<%= p.id %><%= p.firstname %> <%= p.lastname %><%= p.dni || "-" %> - <% if (p.gender === 'm') { %>m <% } else if (p.gender === - 'w') { %>w <% } else if (p.gender === 'd') { %>d <% } else { - %>-<% } %> -
+ + + + + + + + + + + + + + + + + - - - - - - - - - - + <% if (patients.length === 0) { %> + + + <% } %> - - + <% patients.forEach(p => { %> + + - - + - - - -
  • + - - <% if (p.waiting_room) { %> -
  • - - 🪑 Wartet bereits - -
  • - <% } else { %> -
  • -
    - - -
  • - <% } %> + + -
  • + - -
  • - - 💊 Medikamente - -
  • + -
  • - - -
  • +
  • - -
  • - - 📋 Übersicht - -
  • + -
  • + + + + <% }) %> + +
    NameN.I.E. / DNIGeschlechtGeburtstagE-MailTelefonAdresseLandStatusNotizenErstelltGeändert
    - <%= new Date(p.birthdate).toLocaleDateString("de-DE") %> - <%= p.email || "-" %><%= p.phone || "-" %> - <%= p.street || "" %> <%= p.house_number || "" %>
    - <%= p.postal_code || "" %> <%= p.city || "" %> -
    <%= p.country || "-" %> - <% if (p.active) { %> - Aktiv - <% } else { %> - Inaktiv +
    + Keine Patienten gefunden +
    - <%= p.notes ? p.notes.substring(0, 80) : "-" %> -
    + + <%= new Date(p.created_at).toLocaleString("de-DE") %><%= new Date(p.updated_at).toLocaleString("de-DE") %> + <%= p.firstname %> <%= p.lastname %> + - <%= p.dni || "-" %> + <% if (p.gender === 'm') { %>m + <% } else if (p.gender === 'w') { %>w + <% } else if (p.gender === 'd') { %>d + <% } else { %>-<% } %> + + <%= new Date(p.birthdate).toLocaleDateString("de-DE") %> + <%= p.email || "-" %><%= p.phone || "-" %> + <%= p.street || "" %> <%= p.house_number || "" %>
    + <%= p.postal_code || "" %> <%= p.city || "" %> +
    <%= p.country || "-" %> <% if (p.active) { %> -
    - -
    + Aktiv <% } else { %> -
    - -
    + Inaktiv <% } %> - +
    + <%= p.notes ? p.notes.substring(0, 80) : "-" %> + <%= new Date(p.created_at).toLocaleString("de-DE") %><%= new Date(p.updated_at).toLocaleString("de-DE") %>
    +
    - -
  • -
    - - -
    -
  • - -
    - - - <% }) %> - - +
    + Patient auswählen → Sidebar links zeigt Aktionen ✅ +
    +
    + + +