Sidebars eingeführt auf Dashboard und Verwaltung

This commit is contained in:
Cay 2026-01-18 11:44:17 +00:00
parent 1aef5a5749
commit 81473882e5
19 changed files with 936 additions and 315 deletions

172
app.js
View File

@ -1,9 +1,18 @@
require("dotenv").config();
const express = require("express"); const express = require("express");
const session = require("express-session"); const session = require("express-session");
const helmet = require("helmet"); const helmet = require("helmet");
const sessionStore = require("./config/session"); const mysql = require("mysql2/promise");
require("dotenv").config();
// ✅ Verschlüsselte Config
const { configExists, saveConfig } = require("./config-manager");
// ✅ Reset-Funktionen (Soft-Restart)
const db = require("./db");
const { getSessionStore, resetSessionStore } = require("./config/session");
// ✅ Deine Routes (unverändert)
const adminRoutes = require("./routes/admin.routes"); const adminRoutes = require("./routes/admin.routes");
const dashboardRoutes = require("./routes/dashboard.routes"); const dashboardRoutes = require("./routes/dashboard.routes");
const patientRoutes = require("./routes/patient.routes"); const patientRoutes = require("./routes/patient.routes");
@ -19,20 +28,73 @@ const authRoutes = require("./routes/auth.routes");
const app = express(); const app = express();
/* ===============================
SETUP HTML
================================ */
function setupHtml(error = "") {
return `
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Praxissoftware Setup</title>
<style>
body{font-family:Arial;background:#f4f4f4;padding:30px}
.card{max-width:520px;margin:auto;background:#fff;padding:22px;border-radius:14px}
input{width:100%;padding:10px;margin:6px 0;border:1px solid #ccc;border-radius:8px}
button{padding:10px 14px;border:0;border-radius:8px;background:#111;color:#fff;cursor:pointer}
.err{color:#b00020;margin:10px 0}
.hint{color:#666;font-size:13px;margin-top:12px}
</style>
</head>
<body>
<div class="card">
<h2>🔧 Datenbank Einrichtung</h2>
${error ? `<div class="err">❌ ${error}</div>` : ""}
<form method="POST" action="/setup">
<label>DB Host</label>
<input name="host" placeholder="85.215.63.122" required />
<label>DB Benutzer</label>
<input name="user" placeholder="praxisuser" required />
<label>DB Passwort</label>
<input name="password" type="password" required />
<label>DB Name</label>
<input name="name" placeholder="praxissoftware" required />
<button type="submit"> Speichern</button>
</form>
<div class="hint">
Die Daten werden verschlüsselt gespeichert (<b>config.enc</b>).<br/>
Danach wirst du automatisch auf die Loginseite weitergeleitet.
</div>
</div>
</body>
</html>
`;
}
/* =============================== /* ===============================
MIDDLEWARE MIDDLEWARE
================================ */ ================================ */
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(helmet()); app.use(helmet());
// ✅ SessionStore dynamisch (Setup: MemoryStore, normal: MySQLStore)
app.use( app.use(
session({ session({
name: "praxis.sid", name: "praxis.sid",
secret: process.env.SESSION_SECRET, secret: process.env.SESSION_SECRET,
store: sessionStore, store: getSessionStore(),
resave: false, resave: false,
saveUninitialized: false, saveUninitialized: false,
}) }),
); );
const flashMiddleware = require("./middleware/flash.middleware"); const flashMiddleware = require("./middleware/flash.middleware");
@ -41,63 +103,91 @@ app.use(flashMiddleware);
app.use(express.static("public")); app.use(express.static("public"));
app.use("/uploads", express.static("uploads")); app.use("/uploads", express.static("uploads"));
app.set("view engine", "ejs"); app.set("view engine", "ejs");
app.use((req, res, next) => {
res.locals.user = req.session.user || null;
next();
});
/* =============================== /* ===============================
COMPANY SETTINGS SETUP ROUTES
================================ */ ================================ */
// Setup-Seite
app.get("/setup", (req, res) => {
if (configExists()) return res.redirect("/");
return res.status(200).send(setupHtml());
});
// Setup speichern + DB testen + Soft-Restart + Login redirect
app.post("/setup", async (req, res) => {
try {
const { host, user, password, name } = req.body;
if (!host || !user || !password || !name) {
return res.status(400).send(setupHtml("Bitte alle Felder ausfüllen."));
}
// ✅ DB Verbindung testen
const conn = await mysql.createConnection({
host,
user,
password,
database: name,
});
await conn.query("SELECT 1");
await conn.end();
// ✅ verschlüsselt speichern
saveConfig({
db: { host, user, password, name },
});
// ✅ Soft-Restart (DB Pool + SessionStore neu laden)
if (typeof db.resetPool === "function") {
db.resetPool();
}
resetSessionStore();
// ✅ automatisch zurück zur Loginseite
return res.redirect("/");
} catch (err) {
return res
.status(500)
.send(setupHtml("DB Verbindung fehlgeschlagen: " + err.message));
}
});
// Wenn keine config.enc → alles außer /setup auf Setup umleiten
app.use((req, res, next) => {
if (!configExists() && req.path !== "/setup") {
return res.redirect("/setup");
}
next();
});
/* ===============================
DEINE LOGIK (unverändert)
================================ */
app.use(companySettingsRoutes); app.use(companySettingsRoutes);
/* ===============================
AUTH / LOGIN
================================ */
app.use("/", authRoutes); app.use("/", authRoutes);
/* ===============================
DASHBOARD
================================ */
app.use("/dashboard", dashboardRoutes); app.use("/dashboard", dashboardRoutes);
/* ===============================
ADMIN
================================ */
app.use("/admin", adminRoutes); app.use("/admin", adminRoutes);
/* ===============================
PATIENTEN
================================ */
app.use("/patients", patientRoutes); app.use("/patients", patientRoutes);
app.use("/patients", patientMedicationRoutes); app.use("/patients", patientMedicationRoutes);
app.use("/patients", patientServiceRoutes); app.use("/patients", patientServiceRoutes);
/* ===============================
MEDIKAMENTE
================================ */
app.use("/medications", medicationRoutes); app.use("/medications", medicationRoutes);
console.log("🧪 /medications Router mounted"); console.log("🧪 /medications Router mounted");
/* ===============================
LEISTUNGEN
================================ */
app.use("/services", serviceRoutes); app.use("/services", serviceRoutes);
/* ===============================
DATEIEN
================================ */
app.use("/", patientFileRoutes); app.use("/", patientFileRoutes);
/* ===============================
WARTEZIMMER
================================ */
app.use("/", waitingRoomRoutes); app.use("/", waitingRoomRoutes);
/* ===============================
RECHNUNGEN
================================ */
app.use("/", invoiceRoutes); app.use("/", invoiceRoutes);
/* ===============================
LOGOUT
================================ */
app.get("/logout", (req, res) => { app.get("/logout", (req, res) => {
req.session.destroy(() => res.redirect("/")); req.session.destroy(() => res.redirect("/"));
}); });
@ -113,7 +203,7 @@ app.use((err, req, res, next) => {
/* =============================== /* ===============================
SERVER SERVER
================================ */ ================================ */
const PORT = 51777; const PORT = process.env.PORT || 51777;
const HOST = "127.0.0.1"; const HOST = "127.0.0.1";
app.listen(PORT, HOST, () => { app.listen(PORT, HOST, () => {

71
config-manager.js Normal file
View File

@ -0,0 +1,71 @@
const fs = require("fs");
const crypto = require("crypto");
const path = require("path");
const CONFIG_FILE = path.join(__dirname, "config.enc");
function getKey() {
const key = process.env.CONFIG_KEY;
if (!key) throw new Error("CONFIG_KEY fehlt in .env");
// stabil auf 32 bytes
return crypto.createHash("sha256").update(key).digest();
}
function encryptConfig(obj) {
const key = getKey();
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
const json = JSON.stringify(obj);
const encrypted = Buffer.concat([
cipher.update(json, "utf8"),
cipher.final(),
]);
const tag = cipher.getAuthTag();
return Buffer.concat([iv, tag, encrypted]).toString("base64");
}
function decryptConfig(str) {
const raw = Buffer.from(str, "base64");
const iv = raw.subarray(0, 12);
const tag = raw.subarray(12, 28);
const encrypted = raw.subarray(28);
const key = getKey();
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
decipher.setAuthTag(tag);
const decrypted = Buffer.concat([
decipher.update(encrypted),
decipher.final(),
]);
return JSON.parse(decrypted.toString("utf8"));
}
function configExists() {
return fs.existsSync(CONFIG_FILE);
}
function loadConfig() {
if (!configExists()) return null;
const enc = fs.readFileSync(CONFIG_FILE, "utf8").trim();
if (!enc) return null;
return decryptConfig(enc);
}
function saveConfig(obj) {
const enc = encryptConfig(obj);
fs.writeFileSync(CONFIG_FILE, enc, "utf8");
return true;
}
module.exports = {
configExists,
loadConfig,
saveConfig,
};

1
config.enc Normal file
View File

@ -0,0 +1 @@
G/kDLEJ/LddnnNnginIGYSM4Ax0g5pJaF0lrdOXke51cz3jSTrZxP7rjTXRlqLcoUJhPaVLvjb/DcyNYB/C339a+PFWyIdWYjSb6G4aPkD8J21yFWDDLpc08bXvoAx2PeE+Fc9v5mJUGDVv2wQoDvkHqIpN8ewrfRZ6+JF3OfQ==

View File

@ -1,6 +1,31 @@
const MySQLStore = require("express-mysql-session")(require("express-session")); const session = require("express-session");
const { configExists } = require("../config-manager");
let store = null;
function getSessionStore() {
if (store) return store;
// ✅ Setup-Modus (keine DB)
if (!configExists()) {
console.log("⚠️ Setup-Modus aktiv → SessionStore = MemoryStore");
store = new session.MemoryStore();
return store;
}
// ✅ Normalbetrieb (mit DB)
const MySQLStore = require("express-mysql-session")(session);
const db = require("../db"); const db = require("../db");
const sessionStore = new MySQLStore({}, db); store = new MySQLStore({}, db);
return store;
}
module.exports = sessionStore; function resetSessionStore() {
store = null;
}
module.exports = {
getSessionStore,
resetSessionStore,
};

70
db.js
View File

@ -1,24 +1,62 @@
const mysql = require("mysql2"); const mysql = require("mysql2");
const { loadConfig } = require("./config-manager");
const pool = mysql.createPool({ let pool = null;
host: "85.215.63.122",
user: "praxisuser", function initPool() {
password: "praxisuser", const config = loadConfig();
database: "praxissoftware",
// ✅ Setup-Modus: noch keine config.enc → kein Pool
if (!config || !config.db) return null;
return mysql.createPool({
host: config.db.host,
user: config.db.user,
password: config.db.password,
database: config.db.name,
waitForConnections: true, waitForConnections: true,
connectionLimit: 10, connectionLimit: 10,
queueLimit: 0 queueLimit: 0,
}); });
// Optionaler Test beim Start
pool.getConnection((err, connection) => {
if (err) {
console.error("❌ MySQL Pool Fehler:", err);
process.exit(1);
} }
console.log("✅ MySQL Pool verbunden");
connection.release();
});
module.exports = pool; function getPool() {
if (!pool) pool = initPool();
return pool;
}
function resetPool() {
pool = null;
}
/**
* Proxy damit alter Code weitergeht:
* const db = require("../db");
* await db.query(...)
*/
const dbProxy = new Proxy(
{},
{
get(target, prop) {
const p = getPool();
if (!p) {
throw new Error(
"❌ DB ist noch nicht konfiguriert (config.enc fehlt). Bitte zuerst Setup ausführen: http://127.0.0.1:51777/setup",
);
}
const value = p[prop];
if (typeof value === "function") {
return value.bind(p);
}
return value;
},
},
);
module.exports = dbProxy;
module.exports.getPool = getPool;
module.exports.resetPool = resetPool;

View File

@ -3,30 +3,52 @@ function requireLogin(req, res, next) {
return res.redirect("/"); return res.redirect("/");
} }
// optional, aber sauber
req.user = req.session.user; req.user = req.session.user;
next(); next();
} }
// ✅ NEU: Arzt-only (das war früher dein requireAdmin)
function requireArzt(req, res, next) {
console.log("ARZT CHECK:", req.session.user);
if (!req.session.user) {
return res.redirect("/");
}
if (req.session.user.role !== "arzt") {
return res
.status(403)
.send(
"⛔ Kein Zugriff (Arzt erforderlich). Rolle: " + req.session.user.role,
);
}
req.user = req.session.user;
next();
}
// ✅ NEU: Admin-only
function requireAdmin(req, res, next) { function requireAdmin(req, res, next) {
console.log("ADMIN CHECK:", req.session.user); console.log("ADMIN CHECK:", req.session.user);
if (!req.session.user) { if (!req.session.user) {
return res.send("NICHT EINGELOGGT"); return res.redirect("/");
} }
if (req.session.user.role !== "arzt") { if (req.session.user.role !== "admin") {
return res.send("KEIN ARZT: " + req.session.user.role); return res
.status(403)
.send(
"⛔ Kein Zugriff (Admin erforderlich). Rolle: " + req.session.user.role,
);
} }
// 🔑 DAS HAT GEFEHLT
req.user = req.session.user; req.user = req.session.user;
next(); next();
} }
module.exports = { module.exports = {
requireLogin, requireLogin,
requireAdmin requireArzt,
requireAdmin,
}; };

View File

@ -13,8 +13,11 @@ const {
updateUser, updateUser,
} = require("../controllers/admin.controller"); } = require("../controllers/admin.controller");
const { requireAdmin } = require("../middleware/auth.middleware"); const { requireArzt, requireAdmin } = require("../middleware/auth.middleware");
/* ==========================
VERWALTUNG (NUR ADMIN)
========================== */
router.get("/users", requireAdmin, listUsers); router.get("/users", requireAdmin, listUsers);
router.get("/create-user", requireAdmin, showCreateUser); router.get("/create-user", requireAdmin, showCreateUser);
router.post("/create-user", requireAdmin, postCreateUser); router.post("/create-user", requireAdmin, postCreateUser);
@ -23,7 +26,18 @@ router.post("/users/change-role/:id", requireAdmin, changeUserRole);
router.post("/users/reset-password/:id", requireAdmin, resetUserPassword); router.post("/users/reset-password/:id", requireAdmin, resetUserPassword);
router.post("/users/activate/:id", requireAdmin, activateUser); router.post("/users/activate/:id", requireAdmin, activateUser);
router.post("/users/deactivate/:id", requireAdmin, deactivateUser); router.post("/users/deactivate/:id", requireAdmin, deactivateUser);
router.get("/invoices", requireAdmin, showInvoiceOverview);
router.post("/users/update/:id", requireAdmin, updateUser); router.post("/users/update/:id", requireAdmin, updateUser);
/* ✅ Admin-Tools: Datenbankverwaltung */
router.get("/database", requireAdmin, (req, res) => {
res.render("admin/database", {
user: req.session.user,
});
});
/* ==========================
ABRECHNUNG (NUR ARZT)
========================== */
router.get("/invoices", requireArzt, showInvoiceOverview);
module.exports = router; module.exports = router;

View File

@ -1,23 +1,19 @@
const express = require("express"); const express = require("express");
const router = express.Router(); const router = express.Router();
const { requireAdmin } = require("../middleware/auth.middleware"); const { requireArzt } = require("../middleware/auth.middleware");
const uploadLogo = require("../middleware/uploadLogo"); const uploadLogo = require("../middleware/uploadLogo");
const { const {
getCompanySettings, getCompanySettings,
saveCompanySettings saveCompanySettings,
} = require("../controllers/companySettings.controller"); } = require("../controllers/companySettings.controller");
router.get( router.get("/admin/company-settings", requireArzt, getCompanySettings);
"/admin/company-settings",
requireAdmin,
getCompanySettings
);
router.post( router.post(
"/admin/company-settings", "/admin/company-settings",
requireAdmin, requireArzt,
uploadLogo.single("logo"), // 🔑 MUSS VOR DEM CONTROLLER KOMMEN uploadLogo.single("logo"), // 🔑 MUSS VOR DEM CONTROLLER KOMMEN
saveCompanySettings saveCompanySettings,
); );
module.exports = router; module.exports = router;

View File

@ -1,14 +1,8 @@
const express = require("express"); const express = require("express");
const router = express.Router(); const router = express.Router();
const {requireAdmin } = require("../middleware/auth.middleware"); const { requireArzt } = require("../middleware/auth.middleware");
const { createInvoicePdf } = require("../controllers/invoicePdf.controller"); const { createInvoicePdf } = require("../controllers/invoicePdf.controller");
router.post("/patients/:id/create-invoice", requireArzt, createInvoicePdf);
router.post(
"/patients/:id/create-invoice",
requireAdmin,
createInvoicePdf
);
module.exports = router; module.exports = router;

View File

@ -1,7 +1,7 @@
const express = require("express"); const express = require("express");
const router = express.Router(); const router = express.Router();
const { requireLogin, requireAdmin } = require("../middleware/auth.middleware"); const { requireLogin, requireArzt } = require("../middleware/auth.middleware");
const { const {
listPatients, listPatients,
@ -31,7 +31,7 @@ router.get("/:id/medications", requireLogin, showPatientMedications);
router.post("/waiting-room/:id", requireLogin, moveToWaitingRoom); router.post("/waiting-room/:id", requireLogin, moveToWaitingRoom);
router.get("/:id/overview", requireLogin, showPatientOverview); router.get("/:id/overview", requireLogin, showPatientOverview);
router.post("/:id/notes", requireLogin, addPatientNote); router.post("/:id/notes", requireLogin, addPatientNote);
router.post("/waiting-room/call/:id", requireAdmin, callFromWaitingRoom); router.post("/waiting-room/call/:id", requireArzt, callFromWaitingRoom);
router.post("/:id/discharge", requireLogin, dischargePatient); router.post("/:id/discharge", requireLogin, dischargePatient);
router.get("/:id/plan", requireLogin, showMedicationPlan); router.get("/:id/plan", requireLogin, showMedicationPlan);
router.post("/deactivate/:id", requireLogin, deactivatePatient); router.post("/deactivate/:id", requireLogin, deactivatePatient);

View File

@ -1,15 +1,15 @@
const express = require("express"); const express = require("express");
const router = express.Router(); const router = express.Router();
const { requireAdmin } = require("../middleware/auth.middleware"); const { requireArzt } = require("../middleware/auth.middleware");
const { const {
addMedication, addMedication,
endMedication, endMedication,
deleteMedication deleteMedication,
} = require("../controllers/patientMedication.controller"); } = require("../controllers/patientMedication.controller");
router.post("/:id/medications", requireAdmin, addMedication); router.post("/:id/medications", requireArzt, addMedication);
router.post("/patient-medications/end/:id", requireAdmin, endMedication); router.post("/patient-medications/end/:id", requireArzt, endMedication);
router.post("/patient-medications/delete/:id", requireAdmin, deleteMedication); router.post("/patient-medications/delete/:id", requireArzt, deleteMedication);
module.exports = router; module.exports = router;

View File

@ -1,7 +1,7 @@
const express = require("express"); const express = require("express");
const router = express.Router(); const router = express.Router();
const { requireLogin, requireAdmin } = require("../middleware/auth.middleware"); const { requireLogin, requireArzt } = require("../middleware/auth.middleware");
const { const {
addPatientService, addPatientService,
deletePatientService, deletePatientService,
@ -10,11 +10,11 @@ const {
} = require("../controllers/patientService.controller"); } = require("../controllers/patientService.controller");
router.post("/:id/services", requireLogin, addPatientService); router.post("/:id/services", requireLogin, addPatientService);
router.post("/services/delete/:id", requireAdmin, deletePatientService); router.post("/services/delete/:id", requireArzt, deletePatientService);
router.post( router.post(
"/services/update-price/:id", "/services/update-price/:id",
requireAdmin, requireArzt,
updatePatientServicePrice updatePatientServicePrice,
); );
router.post("/services/update-quantity/:id", updatePatientServiceQuantity); router.post("/services/update-quantity/:id", updatePatientServiceQuantity);

View File

@ -1,7 +1,7 @@
const express = require("express"); const express = require("express");
const router = express.Router(); const router = express.Router();
const { requireLogin, requireAdmin } = require("../middleware/auth.middleware"); const { requireLogin, requireArzt } = require("../middleware/auth.middleware");
const { const {
listServices, listServices,
showCreateService, showCreateService,
@ -10,17 +10,16 @@ const {
toggleService, toggleService,
listOpenServices, listOpenServices,
showServiceLogs, showServiceLogs,
listServicesAdmin listServicesAdmin,
} = require("../controllers/service.controller"); } = require("../controllers/service.controller");
router.get("/", requireLogin, listServicesAdmin); router.get("/", requireLogin, listServicesAdmin);
router.get("/", requireAdmin, listServices); router.get("/", requireArzt, listServices);
router.get("/create", requireAdmin, showCreateService); router.get("/create", requireArzt, showCreateService);
router.post("/create", requireAdmin, createService); router.post("/create", requireArzt, createService);
router.post("/:id/update-price", requireAdmin, updateServicePrice); router.post("/:id/update-price", requireArzt, updateServicePrice);
router.post("/:id/toggle", requireAdmin, toggleService); router.post("/:id/toggle", requireArzt, toggleService);
router.get("/open", requireLogin, listOpenServices); router.get("/open", requireLogin, listOpenServices);
router.get("/logs", requireAdmin, showServiceLogs); router.get("/logs", requireArzt, showServiceLogs);
module.exports = router; module.exports = router;

172
views/admin/database.ejs Normal file
View File

@ -0,0 +1,172 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<title>Datenbankverwaltung</title>
<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">
<script src="/js/bootstrap.bundle.min.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;
}
/* 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;
}
.nav-item.locked {
opacity: 0.5;
cursor: not-allowed;
}
.nav-item.locked:hover {
background: transparent;
color: #cbd5e1;
}
/* Main */
.main {
flex: 1;
padding: 24px;
overflow: auto;
}
</style>
</head>
<body>
<div class="layout">
<!-- ✅ ADMIN SIDEBAR -->
<%- include("../partials/admin-sidebar", { user, active: "database" }) %>
<!-- ✅ MAIN CONTENT -->
<div class="main">
<nav class="navbar navbar-dark bg-dark position-relative px-3 rounded mb-4">
<div class="position-absolute top-50 start-50 translate-middle d-flex align-items-center gap-2 text-white">
<i class="bi bi-hdd-stack fs-4"></i>
<span class="fw-semibold fs-5">Datenbankverwaltung</span>
</div>
<div class="ms-auto">
<a href="/dashboard" class="btn btn-outline-light btn-sm">⬅️ Dashboard</a>
</div>
</nav>
<div class="container-fluid">
<%- include("../partials/flash") %>
<div class="card shadow">
<div class="card-body">
<h4 class="mb-3">Datenbank Tools</h4>
<div class="alert alert-warning">
<b>Hinweis:</b> Diese Funktionen sind nur für <b>Admins</b> sichtbar und sollten mit Vorsicht benutzt werden.
</div>
<div class="row g-3">
<div class="col-md-6">
<div class="card border">
<div class="card-body">
<h5 class="card-title">📦 Backup</h5>
<p class="text-muted small mb-3">
Hier kannst du später ein Datenbank-Backup erstellen.
</p>
<button class="btn btn-outline-primary" disabled>
Backup erstellen (kommt später)
</button>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card border">
<div class="card-body">
<h5 class="card-title">♻️ Restore</h5>
<p class="text-muted small mb-3">
Hier kannst du später ein Backup wiederherstellen.
</p>
<button class="btn btn-outline-danger" disabled>
Restore starten (kommt später)
</button>
</div>
</div>
</div>
<div class="col-md-12">
<div class="card border">
<div class="card-body">
<h5 class="card-title">🔍 Systeminfo</h5>
<p class="text-muted small mb-0">
In diesem Bereich kannst du später z.B. DB Version, Tabellenstatus, Speicherplatz etc. anzeigen.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -14,6 +14,79 @@
<script src="/js/services-lock.js"></script> <script src="/js/services-lock.js"></script>
<style> <style>
body {
margin: 0;
background: #f4f6f9;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu;
}
.layout {
display: flex;
min-height: 100vh;
}
/* Sidebar (gleiches Design wie dein Dashboard) */
.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;
}
.nav-item.locked {
opacity: 0.5;
cursor: not-allowed;
}
.nav-item.locked:hover {
background: transparent;
color: #cbd5e1;
}
/* Main Content */
.main {
flex: 1;
padding: 24px;
overflow: auto;
}
/* Original Styles */
input.form-control { box-shadow: none !important; } input.form-control { box-shadow: none !important; }
input.form-control:disabled { input.form-control:disabled {
@ -37,9 +110,17 @@
</style> </style>
</head> </head>
<body class="bg-light"> <body>
<nav class="navbar navbar-dark bg-dark position-relative px-3"> <div class="layout">
<!-- ✅ ADMIN SIDEBAR -->
<%- include("partials/admin-sidebar", { active: "users" }) %>
<!-- ✅ MAIN -->
<div class="main">
<nav class="navbar navbar-dark bg-dark position-relative px-3 rounded mb-4">
<div class="position-absolute top-50 start-50 translate-middle d-flex align-items-center gap-2 text-white"> <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> <i class="bi bi-shield-lock fs-4"></i>
<span class="fw-semibold fs-5">User Verwaltung</span> <span class="fw-semibold fs-5">User Verwaltung</span>
@ -50,7 +131,7 @@
</div> </div>
</nav> </nav>
<div class="container mt-4"> <div class="container-fluid">
<%- include("partials/flash") %> <%- include("partials/flash") %>
<div class="card shadow"> <div class="card shadow">
@ -102,7 +183,7 @@
<tr class="<%= u.active ? '' : 'table-secondary' %>"> <tr class="<%= u.active ? '' : 'table-secondary' %>">
<!-- ✅ UPDATE-FORM (wie medications) --> <!-- ✅ UPDATE-FORM -->
<form method="POST" action="/admin/users/update/<%= u.id %>"> <form method="POST" action="/admin/users/update/<%= u.id %>">
<td><%= u.id %></td> <td><%= u.id %></td>
@ -159,6 +240,9 @@
<option value="arzt" <%= u.role === "arzt" ? "selected" : "" %>> <option value="arzt" <%= u.role === "arzt" ? "selected" : "" %>>
Arzt Arzt
</option> </option>
<option value="admin" <%= u.role === "admin" ? "selected" : "" %>>
Admin
</option>
</select> </select>
</td> </td>
@ -210,5 +294,8 @@
</div> </div>
</div> </div>
</div>
</div>
</body> </body>
</html> </html>

View File

@ -102,7 +102,7 @@
.waiting-grid { .waiting-grid {
display: grid; display: grid;
grid-template-columns: repeat(7, 1fr); grid-template-columns: repeat(7, 1fr);
grid-template-rows: repeat(3, 80px); grid-auto-rows: 80px;
gap: 12px; gap: 12px;
width: 100%; width: 100%;
} }
@ -117,7 +117,9 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
overflow: hidden; /* 🔥 DAS fehlt */ overflow: hidden;
text-decoration: none;
color: inherit;
} }
.waiting-slot.occupied { .waiting-slot.occupied {
@ -125,22 +127,11 @@
background: #eefdf5; background: #eefdf5;
} }
.waiting-grid {
grid-auto-flow: row dense;
}
.chair-icon {
width: 40px;
height: 40px;
object-fit: contain;
opacity: 0.25;
}
.patient-text { .patient-text {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: 4px; /* 🔥 Abstand zwischen Name und Datum */ gap: 4px;
} }
.waiting-slot.clickable { .waiting-slot.clickable {
@ -153,43 +144,21 @@
box-shadow: 0 0 0 2px #2563eb; box-shadow: 0 0 0 2px #2563eb;
} }
.nav-item.locked {
opacity: 0.5;
cursor: not-allowed;
}
.nav-item.locked:hover {
background: transparent;
color: #cbd5e1;
}
</style> </style>
</head> </head>
<body> <body>
<div class="layout"> <div class="layout">
<!-- SIDEBAR --> <!-- ✅ SIDEBAR ausgelagert -->
<div class="sidebar"> <%- include("partials/sidebar", { user, active: "patients" }) %>
<div class="logo">🩺 Praxis System</div>
<a href="/patients" class="nav-item active">
<i class="bi bi-people"></i> Patienten
</a>
<a href="/medications" class="nav-item">
<i class="bi bi-capsule"></i> Medikamente
</a>
<a href="/services/open" class="nav-item">
<i class="bi bi-receipt"></i> Offene Leistungen
</a>
<% if (user.role === 'arzt') { %>
<a href="/admin/invoices" class="nav-item">
<i class="bi bi-cash-coin"></i> Abrechnung
</a>
<a href="/admin/users" class="nav-item">
<i class="bi bi-gear"></i> Verwaltung
</a>
<% } %>
<div class="spacer"></div>
<a href="/logout" class="nav-item">
<i class="bi bi-box-arrow-right"></i> Logout
</a>
</div>
<!-- MAIN CONTENT --> <!-- MAIN CONTENT -->
<div class="main"> <div class="main">
@ -201,18 +170,16 @@
<%- include("partials/flash") %> <%- include("partials/flash") %>
<!-- ========================= <!-- =========================
WARTEZIMMER MONITOR (dein Original) WARTEZIMMER MONITOR
========================== --> ========================= -->
<div class="waiting-monitor"> <div class="waiting-monitor">
<h5 class="mb-3">🪑 Wartezimmer-Monitor</h5> <h5 class="mb-3">🪑 Wartezimmer-Monitor</h5>
<div class="waiting-grid"> <div class="waiting-grid">
<% const maxSlots = 21; <% if (waitingPatients && waitingPatients.length > 0) { %>
for (let i = 0; i < maxSlots; i++) {
const p = waitingPatients && waitingPatients[i]; <% waitingPatients.forEach(p => { %>
%>
<% if (p) { %>
<% if (user.role === 'arzt') { %> <% if (user.role === 'arzt') { %>
<a href="/patients/<%= p.id %>/overview" class="waiting-slot occupied clickable"> <a href="/patients/<%= p.id %>/overview" class="waiting-slot occupied clickable">
<div class="patient-text"> <div class="patient-text">
@ -232,13 +199,15 @@
</div> </div>
</div> </div>
<% } %> <% } %>
<% } %>
<% }) %>
<% } else { %>
<div class="text-muted">Keine Patienten im Wartezimmer.</div>
<% } %> <% } %>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
</body> </body>
</html> </html>

View File

@ -27,10 +27,6 @@
<button class="btn btn-primary w-100">Login</button> <button class="btn btn-primary w-100">Login</button>
</form> </form>
<div class="text-center mt-3">
<a href="/register">Registrieren</a>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,50 @@
<div class="sidebar">
<div class="logo">🔐 Admin Bereich</div>
<%
const role = user?.role || null;
const isAdmin = role === "admin";
function hrefIfAllowed(allowed, href) {
return allowed ? href : "#";
}
function lockClass(allowed) {
return allowed ? "" : "locked";
}
function lockClick(allowed) {
return allowed ? "" : 'onclick="return false;"';
}
%>
<a
href="<%= hrefIfAllowed(isAdmin, '/admin/users') %>"
class="nav-item <%= active === 'users' ? 'active' : '' %> <%= lockClass(isAdmin) %>"
<%- lockClick(isAdmin) %>
title="<%= isAdmin ? '' : 'Nur Admin' %>"
>
<i class="bi bi-people"></i> Userverwaltung
<% if (!isAdmin) { %>
<span style="margin-left:auto;"><i class="bi bi-lock-fill"></i></span>
<% } %>
</a>
<a
href="<%= hrefIfAllowed(isAdmin, '/admin/database') %>"
class="nav-item <%= active === 'database' ? 'active' : '' %> <%= lockClass(isAdmin) %>"
<%- lockClick(isAdmin) %>
title="<%= isAdmin ? '' : 'Nur Admin' %>"
>
<i class="bi bi-hdd-stack"></i> Datenbankverwaltung
<% if (!isAdmin) { %>
<span style="margin-left:auto;"><i class="bi bi-lock-fill"></i></span>
<% } %>
</a>
<div class="spacer"></div>
<a href="/dashboard" class="nav-item">
<i class="bi bi-arrow-left"></i> Dashboard
</a>
</div>

View File

@ -0,0 +1,97 @@
<div class="sidebar">
<div class="logo">🩺 Praxis System</div>
<%
const role = user?.role || null;
// ✅ Regeln:
// Arztbereich: NUR arzt
const canDoctorArea = role === "arzt";
// Verwaltung: NUR admin
const canAdminArea = role === "admin";
function hrefIfAllowed(allowed, href) {
return allowed ? href : "#";
}
function lockClass(allowed) {
return allowed ? "" : "locked";
}
function lockClick(allowed) {
return allowed ? "" : 'onclick="return false;"';
}
%>
<!-- Patienten -->
<a
href="<%= hrefIfAllowed(canDoctorArea, '/patients') %>"
class="nav-item <%= active === 'patients' ? 'active' : '' %> <%= lockClass(canDoctorArea) %>"
<%- lockClick(canDoctorArea) %>
title="<%= canDoctorArea ? '' : 'Nur Arzt' %>"
>
<i class="bi bi-people"></i> Patienten
<% if (!canDoctorArea) { %>
<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' %>"
>
<i class="bi bi-capsule"></i> Medikamente
<% if (!canDoctorArea) { %>
<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' %>"
>
<i class="bi bi-receipt"></i> Offene Leistungen
<% if (!canDoctorArea) { %>
<span style="margin-left:auto;"><i class="bi bi-lock-fill"></i></span>
<% } %>
</a>
<!-- Abrechnung -->
<a
href="<%= hrefIfAllowed(canDoctorArea, '/admin/invoices') %>"
class="nav-item <%= active === 'billing' ? 'active' : '' %> <%= lockClass(canDoctorArea) %>"
<%- lockClick(canDoctorArea) %>
title="<%= canDoctorArea ? '' : 'Nur Arzt' %>"
>
<i class="bi bi-cash-coin"></i> Abrechnung
<% if (!canDoctorArea) { %>
<span style="margin-left:auto;"><i class="bi bi-lock-fill"></i></span>
<% } %>
</a>
<!-- Verwaltung (nur Admin) -->
<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> Verwaltung
<% if (!canAdminArea) { %>
<span style="margin-left:auto;"><i class="bi bi-lock-fill"></i></span>
<% } %>
</a>
<div class="spacer"></div>
<a href="/logout" class="nav-item">
<i class="bi bi-box-arrow-right"></i> Logout
</a>
</div>