Aenderungen der Dashboard Icons

This commit is contained in:
Cay 2026-01-06 18:56:37 +00:00
parent 851250a734
commit 7ab67a839b
23 changed files with 2660 additions and 181 deletions

1
app.js
View File

@ -77,6 +77,7 @@ app.use("/", patientFileRoutes);
app.use("/medications", medicationRoutes);
app.use("/patients", patientMedicationRoutes);
// ===============================
// PATIENT INS WARTEZIMMER
// ===============================

View File

@ -22,13 +22,19 @@ async function createInvoicePdf(req, res) {
const [rows] = await db.promise().query(`
SELECT
ps.quantity,
s.name,
COALESCE(ps.price_override, s.price) AS price
COALESCE(ps.price_override, s.price) AS price,
CASE
WHEN UPPER(TRIM(?)) = 'ES'
THEN COALESCE(NULLIF(s.name_es, ''), s.name_de)
ELSE s.name_de
END AS name
FROM patient_services ps
JOIN services s ON ps.service_id = s.id
WHERE ps.patient_id = ?
AND ps.invoice_id IS NULL
`, [patientId]);
`, [patient.country, patientId]);
if (rows.length === 0) {
return res.send("Keine Leistungen vorhanden");
@ -36,7 +42,7 @@ async function createInvoicePdf(req, res) {
const services = rows.map(s => ({
quantity: Number(s.quantity),
name_de: s.name,
name: s.name,
price: Number(s.price),
total: Number(s.price) * Number(s.quantity)
}));

View File

@ -1,8 +1,10 @@
const db = require("../db");
function listMedications(req, res) {
// 📋 LISTE
function listMedications(req, res, next) {
const sql = `
SELECT
v.id,
m.name AS medication,
f.name AS form,
v.dosage,
@ -14,9 +16,38 @@ function listMedications(req, res) {
`;
db.query(sql, (err, rows) => {
if (err) return res.send("Datenbankfehler");
res.render("medications", { rows });
if (err) return next(err);
res.render("medications", {
rows,
user: req.session.user
});
});
}
module.exports = { listMedications };
// 💾 UPDATE
function updateMedication(req, res, next) {
const { medication, form, dosage, package: pkg } = req.body;
const id = req.params.id;
const sql = `
UPDATE medication_variants
SET
dosage = ?,
package = ?
WHERE id = ?
`;
db.query(sql, [dosage, pkg, id], err => {
if (err) return next(err);
req.session.flash = { type: "success", message: "Medikament gespeichert"};
res.redirect("/medications");
});
}
module.exports = {
listMedications,
updateMedication
};

View File

@ -60,6 +60,50 @@ function listServices(req, res) {
}
}
function listServicesAdmin(req, res) {
const { q, onlyActive } = req.query;
let sql = `
SELECT
id,
name_de,
name_es,
category,
price,
price_c70,
active
FROM services
WHERE 1=1
`;
const params = [];
if (q) {
sql += `
AND (
name_de LIKE ?
OR name_es LIKE ?
OR category LIKE ?
)
`;
params.push(`%${q}%`, `%${q}%`, `%${q}%`);
}
if (onlyActive === "1") {
sql += " AND active = 1";
}
sql += " ORDER BY name_de";
db.query(sql, params, (err, services) => {
if (err) return res.send("Datenbankfehler");
res.render("services", {
services,
user: req.session.user,
query: { q, onlyActive }
});
});
}
function showCreateService(req, res) {
res.render("service_create", {
@ -184,15 +228,26 @@ function listOpenServices(req, res, next) {
p.id AS patient_id,
p.firstname,
p.lastname,
p.country,
ps.id AS patient_service_id,
ps.quantity,
COALESCE(ps.price_override, s.price) AS price,
s.name
-- 🌍 Sprachabhängiger Servicename
CASE
WHEN UPPER(TRIM(p.country)) = 'ES'
THEN COALESCE(NULLIF(s.name_es, ''), s.name_de)
ELSE s.name_de
END AS name
FROM patient_services ps
JOIN patients p ON ps.patient_id = p.id
JOIN services s ON ps.service_id = s.id
WHERE ps.invoice_id IS NULL
ORDER BY p.lastname, p.firstname
ORDER BY
p.lastname,
p.firstname,
name
`;
db.query(sql, (err, rows) => {
@ -206,6 +261,7 @@ function listOpenServices(req, res, next) {
}
function showServiceLogs(req, res) {
db.query(
`
@ -238,5 +294,6 @@ module.exports = {
updateServicePrice,
toggleService,
listOpenServices,
showServiceLogs
showServiceLogs,
listServicesAdmin
};

17
package-lock.json generated
View File

@ -10,6 +10,7 @@
"dependencies": {
"bcrypt": "^6.0.0",
"bootstrap": "^5.3.8",
"bootstrap-icons": "^1.13.1",
"docxtemplater": "^3.67.6",
"dotenv": "^17.2.3",
"ejs": "^3.1.10",
@ -1951,6 +1952,22 @@
"@popperjs/core": "^2.11.8"
}
},
"node_modules/bootstrap-icons": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.13.1.tgz",
"integrity": "sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",

View File

@ -14,6 +14,7 @@
"dependencies": {
"bcrypt": "^6.0.0",
"bootstrap": "^5.3.8",
"bootstrap-icons": "^1.13.1",
"docxtemplater": "^3.67.6",
"dotenv": "^17.2.3",
"ejs": "^3.1.10",

2106
public/bootstrap-icons/bootstrap-icons.css vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

7
public/js/bootstrap.bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,28 @@
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll(".lock-btn").forEach(btn => {
btn.addEventListener("click", () => {
const row = btn.closest("tr");
// Alle Zeilen sperren
document.querySelectorAll("tr").forEach(r => {
r.querySelectorAll("input").forEach(i => i.disabled = true);
const save = r.querySelector(".save-btn");
if (save) save.disabled = true;
});
// Aktuelle Zeile entsperren
row.querySelectorAll("input").forEach(i => i.disabled = false);
row.querySelector(".save-btn").disabled = false;
// Button ändern
btn.textContent = "🔒";
btn.title = "Bearbeitung gesperrt";
// Fokus
const firstInput = row.querySelector("input");
if (firstInput) firstInput.focus();
});
});
});

View File

@ -2,8 +2,14 @@ const express = require("express");
const router = express.Router();
const { requireLogin } = require("../middleware/auth.middleware");
const { listMedications } = require("../controllers/medication.controller");
const {
listMedications,
updateMedication
} = require("../controllers/medication.controller");
router.get("/", requireLogin, listMedications);
// 🆕 UPDATE pro Zeile
router.post("/update/:id", requireLogin, updateMedication);
module.exports = router;

View File

@ -9,9 +9,11 @@ const {
updateServicePrice,
toggleService,
listOpenServices,
showServiceLogs
showServiceLogs,
listServicesAdmin
} = require("../controllers/service.controller");
router.get("/", requireLogin, listServicesAdmin);
router.get("/", requireAdmin, listServices);
router.get("/create", requireAdmin, showCreateService);
router.post("/create", requireAdmin, createService);

View File

@ -7,11 +7,25 @@
</head>
<body>
<nav class="navbar navbar-dark bg-dark px-3">
<span class="navbar-brand">📜 Service-Änderungsprotokoll</span>
<a href="/dashboard" class="btn btn-outline-light btn-sm">Dashboard</a>
<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.3rem;">📜</span>
<span class="fw-semibold fs-5">Service-Änderungsprotokoll</span>
</div>
<!-- 🔵 RECHTS: DASHBOARD -->
<div class="ms-auto">
<a href="/dashboard" class="btn btn-outline-light btn-sm">
⬅️ Dashboard
</a>
</div>
</nav>
<div class="container mt-4">
<table class="table table-sm table-bordered">

View File

@ -6,18 +6,30 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap 5 -->
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/bootstrap-icons/bootstrap-icons.min.css">
</head>
<body class="bg-light">
<!-- NAVBAR -->
<nav class="navbar navbar-dark bg-dark px-3">
<span class="navbar-brand">User Verwaltung</span>
<div>
<a href="/dashboard" class="btn btn-outline-light btn-sm me-2">Dashboard</a>
<a href="/logout" class="btn btn-outline-danger btn-sm">Logout</a>
<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">
<i class="bi bi-shield-lock fs-4"></i>
<span class="fw-semibold fs-5">User Verwaltung</span>
</div>
<!-- 🔵 RECHTS: DASHBOARD -->
<div class="ms-auto">
<a href="/dashboard" class="btn btn-outline-primary btn-sm">
⬅️ Dashboard
</a>
</div>
</nav>
<!-- CONTENT -->
<div class="container mt-4">
<%- include("partials/flash") %>

View File

@ -6,14 +6,30 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="/bootstrap-icons/bootstrap-icons.min.css">
</head>
<body class="bg-light">
<nav class="navbar navbar-dark bg-dark px-3">
<span class="navbar-brand">Dashboard</span>
<a href="/logout" class="btn btn-outline-light btn-sm">Logout</a>
<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">
<i class="bi bi-speedometer2 fs-4"></i>
<span class="fw-semibold fs-5">Dashboard</span>
</div>
<!-- 🔴 RECHTS: LOGOUT -->
<div class="ms-auto">
<a href="/logout" class="btn btn-outline-light btn-sm">
Logout
</a>
</div>
</nav>
<div class="container-fluid mt-4">
<!-- Flash Messages -->

View File

@ -7,9 +7,22 @@
</head>
<body class="bg-light">
<nav class="navbar navbar-dark bg-dark px-3">
<span class="navbar-brand">Medikamentenübersicht</span>
<a href="/dashboard" class="btn btn-outline-light btn-sm">Dashboard</a>
<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.3rem;">💊</span>
<span class="fw-semibold fs-5">Medikamentenübersicht</span>
</div>
<!-- 🔵 RECHTS: DASHBOARD -->
<div class="ms-auto">
<a href="/dashboard" class="btn btn-outline-light btn-sm">
⬅️ Dashboard
</a>
</div>
</nav>
<div class="container mt-4">
@ -30,13 +43,48 @@
<tbody>
<% rows.forEach(r => { %>
<tr>
<td><%= r.medication %></td>
<td><%= r.form %></td>
<td><%= r.dosage %></td>
<td><%= r.package %></td>
<form method="POST"
action="/medications/update/<%= r.id %>">
<td>
<input type="text"
name="medication"
value="<%= r.medication %>"
class="form-control form-control-sm"
required>
</td>
<td>
<input type="text"
name="form"
value="<%= r.form %>"
class="form-control form-control-sm">
</td>
<td>
<input type="text"
name="dosage"
value="<%= r.dosage %>"
class="form-control form-control-sm">
</td>
<td class="d-flex gap-2">
<input type="text"
name="package"
value="<%= r.package %>"
class="form-control form-control-sm">
<button class="btn btn-sm btn-outline-success"
type="submit">
💾
</button>
</td>
</form>
</tr>
<% }) %>
</tbody>
</table>
</div>

View File

@ -8,7 +8,24 @@
<body>
<div class="container mt-4">
<h3>🧾 Offene Leistungen</h3>
<div class="position-relative mb-3">
<!-- 🟢 ZENTRIERTER TITEL -->
<div class="position-absolute top-50 start-50 translate-middle
d-flex align-items-center gap-2">
<span style="font-size:1.4rem;">📄</span>
<h3 class="mb-0">Offene Rechnungen</h3>
</div>
<!-- 🔵 RECHTS: DASHBOARD -->
<div class="text-end">
<a href="/dashboard" class="btn btn-outline-primary btn-sm">
⬅️ Dashboard
</a>
</div>
</div>
<% let currentPatient = null; %>
@ -39,7 +56,7 @@
<div class="border rounded p-2 mb-2 d-flex align-items-center gap-2">
<strong class="flex-grow-1">
<%= r.name_de %>
<%= r.name %>
</strong>
<form method="POST"

View File

@ -219,7 +219,7 @@
<hr>
<!-- Heutige Leistungen anzeigen alle-->
<!-- Heutige Leistungen anzeigen-->
<% if (!todayServices || todayServices.length === 0) { %>
<p class="text-muted">
Noch keine Leistungen für heute erfasst.

View File

@ -8,9 +8,22 @@
</head>
<body class="bg-light">
<nav class="navbar navbar-dark bg-dark px-3">
<span class="navbar-brand">Patientenübersicht</span>
<a href="/dashboard" class="btn btn-outline-light btn-sm">Dashboard</a>
<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>
<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
</a>
</div>
</nav>
<div class="container-fluid mt-4">
@ -136,77 +149,98 @@
<td><%= new Date(p.updated_at).toLocaleString("de-DE") %></td>
<!-- AKTIONEN -->
<td>
<!-- 🔘 OBERE AKTIONEN -->
<div class="d-flex flex-wrap gap-1 mb-2">
<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) { %>
<button class="btn btn-sm btn-secondary" disabled>
🪑 Wartet
</button>
<li>
<span class="dropdown-item text-muted">
🪑 Wartet bereits
</span>
</li>
<% } else { %>
<li>
<form method="POST"
action="/patients/waiting-room/<%= p.id %>"
class="d-inline">
<button class="btn btn-sm btn-outline-primary">
🪑 Wartezimmer
action="/patients/waiting-room/<%= p.id %>">
<button class="dropdown-item">
🪑 Ins Wartezimmer
</button>
</form>
</li>
<% } %>
<!-- ✏️ BEARBEITEN -->
<a href="/patients/edit/<%= p.id %>"
class="btn btn-sm btn-info">
✏️ Bearbeiten
</a>
<li><hr class="dropdown-divider"></li>
<!-- 💊 MEDIKAMENTE -->
<a href="/patients/<%= p.id %>/medications"
class="btn btn-sm btn-outline-primary">
<li>
<a class="dropdown-item"
href="/patients/<%= p.id %>/medications">
💊 Medikamente
</a>
</li>
<!-- 🔒 AKTIV / INAKTIV -->
<li><hr class="dropdown-divider"></li>
<!-- 🔒 STATUS -->
<li>
<% if (p.active) { %>
<form method="POST"
action="/patients/deactivate/<%= p.id %>"
class="d-inline">
<button class="btn btn-sm btn-warning">
Sperren
action="/patients/deactivate/<%= p.id %>">
<button class="dropdown-item text-warning">
🔒 Sperren
</button>
</form>
<% } else { %>
<form method="POST"
action="/patients/activate/<%= p.id %>"
class="d-inline">
<button class="btn btn-sm btn-danger">
Entsperren
action="/patients/activate/<%= p.id %>">
<button class="dropdown-item text-success">
🔓 Entsperren
</button>
</form>
<% } %>
</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>
<!-- 📎 DATEI-UPLOAD (UNTEN) -->
<form method="POST"
action="/patients/<%= p.id %>/files"
enctype="multipart/form-data"
class="d-flex gap-1">
<input type="file"
name="file"
required
class="form-control form-control-sm"
style="max-width:220px">
<button class="btn btn-sm btn-secondary">
📎 Datei hochladen
</button>
</form>
</td>
</tr>
<% }) %>
@ -218,6 +252,6 @@
</div>
</div>
<script src="/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@ -4,18 +4,35 @@
<meta charset="UTF-8">
<title>Leistungen</title>
<link rel="stylesheet" href="/css/bootstrap.min.css">
<script src="/js/services-lock.js"></script> ✔ erlaubt
</head>
<body>
<nav class="navbar navbar-dark bg-dark px-3">
<span class="navbar-brand">🧾 Leistungen</span>
<a href="/dashboard" class="btn btn-outline-light btn-sm">Dashboard</a>
<!-- NAVBAR -->
<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.3rem;">🧾</span>
<span class="fw-semibold fs-5">Leistungen</span>
</div>
<!-- DASHBOARD -->
<div class="ms-auto">
<a href="/dashboard" class="btn btn-outline-light btn-sm">
⬅️ Dashboard
</a>
</div>
</nav>
<!-- CONTENT -->
<div class="container mt-4">
<h4>Leistungen</h4>
<!-- SUCHFORMULAR -->
<form method="GET" action="/services" class="row g-2 mb-3">
<div class="col-md-6">
@ -50,11 +67,24 @@
</form>
<!-- NEUE LEISTUNG -->
<a href="/services/create" class="btn btn-success mb-3">
Neue Leistung
</a>
<!-- TABELLE -->
<table class="table table-bordered table-sm align-middle">
<!-- FIXE SPALTENBREITEN -->
<colgroup>
<col style="width:35%">
<col style="width:25%">
<col style="width:10%">
<col style="width:10%">
<col style="width:8%">
<col style="width:12%">
</colgroup>
<thead class="table-light">
<tr>
<th>Bezeichnung (DE)</th>
@ -65,44 +95,70 @@
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<tbody>
<% services.forEach(s => { %>
<tr class="<%= s.active ? '' : 'table-secondary' %>">
<td><%= s.name %></td>
<!-- DE -->
<td><%= s.name_de %></td>
<!-- ES -->
<td><%= s.name_es || "-" %></td>
<!-- FORM BEGINNT -->
<form method="POST" action="/services/<%= s.id %>/update-price">
<!-- PREIS -->
<td>
<input name="price"
value="<%= s.price %>"
class="form-control form-control-sm">
class="form-control form-control-sm text-end w-100"
disabled>
</td>
<!-- PREIS C70 -->
<td>
<input name="price_c70"
value="<%= s.price_c70 %>"
class="form-control form-control-sm">
class="form-control form-control-sm text-end w-100"
disabled>
</td>
<td>
<!-- STATUS -->
<td class="text-center">
<%= s.active ? 'Aktiv' : 'Inaktiv' %>
</td>
<td class="d-flex gap-1">
<button class="btn btn-sm btn-primary">
💾 Speichern
<!-- AKTIONEN -->
<td class="d-flex justify-content-center gap-2">
<!-- SPEICHERN -->
<button type="submit"
class="btn btn-sm btn-primary save-btn"
disabled>
💾
</button>
<!-- SPERREN / ENTSPERREN -->
<button type="button"
class="btn btn-sm btn-outline-warning lock-btn"
title="Bearbeiten freigeben">
🔓
</button>
</td>
</form>
<form method="POST" action="/services/<%= s.id %>/toggle">
<button class="btn btn-sm btn-outline-warning">
🔄 Aktiv/Inaktiv
</button>
</form>
</td>
</tr>
<% }) %>
</tbody>
</table>
</div>
</body>
</html>

View File

@ -4,14 +4,29 @@
<meta charset="UTF-8">
<title>Wartezimmer</title>
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/bootstrap-icons/bootstrap-icons.min.css">
</head>
<body>
<nav class="navbar navbar-dark bg-dark px-3">
<span class="navbar-brand">🪑 Wartezimmer</span>
<a href="/dashboard" class="btn btn-outline-light btn-sm">Dashboard</a>
<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>
<span class="fw-semibold fs-5">Wartezimmer</span>
</div>
<!-- 🔵 RECHTS: DASHBOARD -->
<div class="ms-auto">
<a href="/dashboard" class="btn btn-outline-primary btn-sm">
⬅️ Dashboard
</a>
</div>
</nav>
<div class="container mt-4">
<!-- ✅ EINMAL Flash anzeigen -->