Sidebars eingeführt auf Dashboard und Verwaltung
This commit is contained in:
parent
1aef5a5749
commit
81473882e5
172
app.js
172
app.js
@ -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
71
config-manager.js
Normal 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
1
config.enc
Normal file
@ -0,0 +1 @@
|
|||||||
|
G/kDLEJ/LddnnNnginIGYSM4Ax0g5pJaF0lrdOXke51cz3jSTrZxP7rjTXRlqLcoUJhPaVLvjb/DcyNYB/C339a+PFWyIdWYjSb6G4aPkD8J21yFWDDLpc08bXvoAx2PeE+Fc9v5mJUGDVv2wQoDvkHqIpN8ewrfRZ6+JF3OfQ==
|
||||||
@ -1,6 +1,31 @@
|
|||||||
const MySQLStore = require("express-mysql-session")(require("express-session"));
|
const session = require("express-session");
|
||||||
const db = require("../db");
|
const { configExists } = require("../config-manager");
|
||||||
|
|
||||||
const sessionStore = new MySQLStore({}, db);
|
let store = null;
|
||||||
|
|
||||||
module.exports = sessionStore;
|
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");
|
||||||
|
|
||||||
|
store = new MySQLStore({}, db);
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetSessionStore() {
|
||||||
|
store = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getSessionStore,
|
||||||
|
resetSessionStore,
|
||||||
|
};
|
||||||
|
|||||||
70
db.js
70
db.js
@ -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
|
function getPool() {
|
||||||
pool.getConnection((err, connection) => {
|
if (!pool) pool = initPool();
|
||||||
if (err) {
|
return pool;
|
||||||
console.error("❌ MySQL Pool Fehler:", err);
|
}
|
||||||
process.exit(1);
|
|
||||||
|
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",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
console.log("✅ MySQL Pool verbunden");
|
|
||||||
connection.release();
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = pool;
|
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;
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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
172
views/admin/database.ejs
Normal 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>
|
||||||
@ -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>
|
||||||
@ -48,9 +129,9 @@
|
|||||||
<div class="ms-auto">
|
<div class="ms-auto">
|
||||||
<a href="/dashboard" class="btn btn-outline-light btn-sm">⬅️ Dashboard</a>
|
<a href="/dashboard" class="btn btn-outline-light btn-sm">⬅️ Dashboard</a>
|
||||||
</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>
|
||||||
|
|
||||||
@ -208,6 +292,9 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
50
views/partials/admin-sidebar.ejs
Normal file
50
views/partials/admin-sidebar.ejs
Normal 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>
|
||||||
97
views/partials/sidebar.ejs
Normal file
97
views/partials/sidebar.ejs
Normal 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>
|
||||||
Loading…
Reference in New Issue
Block a user