Frühstückskonten Verwaltung mit PIN-Transaktionshistorie und PDF-Export

This commit is contained in:
Alf
2026-07-14 08:51:51 +02:00
parent 9ad54ae241
commit 5af7e0f809
18 changed files with 2356 additions and 709 deletions

1283
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,8 @@
"ejs": "^3.1.9", "ejs": "^3.1.9",
"express": "^4.18.2", "express": "^4.18.2",
"express-session": "^1.17.3", "express-session": "^1.17.3",
"sqlite3": "^6.0.1" "pdfkit": "^0.19.1",
"sqlite3": "^5.1.6"
}, },
"devDependencies": { "devDependencies": {
"nodemon": "^3.0.2" "nodemon": "^3.0.2"

258
server.js
View File

@ -3,6 +3,8 @@ const session = require('express-session');
const sqlite3 = require('sqlite3').verbose(); const sqlite3 = require('sqlite3').verbose();
const bcrypt = require('bcryptjs'); const bcrypt = require('bcryptjs');
const path = require('path'); const path = require('path');
const PDFDocument = require('pdfkit');
const fs = require('fs');
const app = express(); const app = express();
const PORT = process.env.PORT || 3000; const PORT = process.env.PORT || 3000;
@ -150,6 +152,17 @@ function requireAdmin(req, res, next) {
res.status(403).send('Zugriff verweigert: Nur für Administratoren'); res.status(403).send('Zugriff verweigert: Nur für Administratoren');
} }
// Helper function to get account by PIN
function getAccountByPin(pin, callback) {
db.all("SELECT * FROM accounts", (err, accounts) => {
if (err) {
return callback(err, null);
}
const matchingAccount = accounts.find(acc => bcrypt.compareSync(pin, acc.pin));
callback(null, matchingAccount);
});
}
// Routes // Routes
app.get('/', (req, res) => { app.get('/', (req, res) => {
if (req.session.userId) { if (req.session.userId) {
@ -161,14 +174,14 @@ app.get('/', (req, res) => {
// Login routes // Login routes
app.get('/login', (req, res) => { app.get('/login', (req, res) => {
res.render('login', { error: null }); res.render('login', { error: null, user: null });
}); });
app.post('/login', (req, res) => { app.post('/login', (req, res) => {
const { username, password } = req.body; const { username, password } = req.body;
db.get("SELECT * FROM users WHERE username = ?", [username], (err, user) => { db.get("SELECT * FROM users WHERE username = ?", [username], (err, user) => {
if (err || !user) { if (err || !user) {
return res.render('login', { error: 'Benutzername oder Passwort falsch' }); return res.render('login', { error: 'Benutzername oder Passwort falsch', user: null });
} }
if (bcrypt.compareSync(password, user.password)) { if (bcrypt.compareSync(password, user.password)) {
req.session.userId = user.id; req.session.userId = user.id;
@ -176,7 +189,7 @@ app.post('/login', (req, res) => {
req.session.role = user.role; req.session.role = user.role;
res.redirect('/dashboard'); res.redirect('/dashboard');
} else { } else {
res.render('login', { error: 'Benutzername oder Passwort falsch' }); res.render('login', { error: 'Benutzername oder Passwort falsch', user: null });
} }
}); });
}); });
@ -209,9 +222,9 @@ app.get('/dashboard', requireAuth, (req, res) => {
db.get("SELECT COUNT(*) as count, SUM(balance) as total_balance FROM accounts", (err, stats) => { db.get("SELECT COUNT(*) as count, SUM(balance) as total_balance FROM accounts", (err, stats) => {
res.render('dashboard', { res.render('dashboard', {
user: req.user, user: req.user,
isAdmin, isAdmin: isAdmin,
transactions, transactions: transactions,
stats stats: stats
}); });
}); });
}); });
@ -226,14 +239,14 @@ app.get('/accounts', requireAuth, (req, res) => {
} }
res.render('accounts', { res.render('accounts', {
user: req.user, user: req.user,
accounts, accounts: accounts,
isAdmin: req.user.role === 'admin' isAdmin: req.user.role === 'admin'
}); });
}); });
}); });
app.get('/accounts/new', requireAuth, (req, res) => { app.get('/accounts/new', requireAuth, (req, res) => {
res.render('account_new', { user: req.user }); res.render('account_new', { user: req.user, error: null });
}); });
app.post('/accounts', requireAuth, (req, res) => { app.post('/accounts', requireAuth, (req, res) => {
@ -287,8 +300,8 @@ app.get('/accounts/:id', requireAuth, (req, res) => {
res.render('account_detail', { res.render('account_detail', {
user: req.user, user: req.user,
account, account: account,
transactions, transactions: transactions,
isAdmin: req.user.role === 'admin' isAdmin: req.user.role === 'admin'
}); });
}); });
@ -302,7 +315,7 @@ app.get('/accounts/:id/edit', requireAuth, (req, res) => {
if (err || !account) { if (err || !account) {
return res.status(404).send('Konto nicht gefunden'); return res.status(404).send('Konto nicht gefunden');
} }
res.render('account_edit', { user: req.user, account }); res.render('account_edit', { user: req.user, account: account, error: null });
}); });
}); });
@ -331,6 +344,108 @@ app.post('/accounts/:id', requireAuth, (req, res) => {
}); });
}); });
// PDF Export route
app.get('/accounts/:id/pdf', requireAuth, (req, res) => {
const accountId = req.params.id;
db.get("SELECT * FROM accounts WHERE id = ?", [accountId], (err, account) => {
if (err || !account) {
return res.status(404).send('Konto nicht gefunden');
}
// Get all transactions for this account
db.all(`
SELECT t.*, u.username as created_by_name, i.name as item_name
FROM transactions t
LEFT JOIN users u ON t.created_by = u.id
LEFT JOIN items i ON t.item_id = i.id
WHERE t.account_id = ?
ORDER BY t.created_at DESC
`, [accountId], (err, transactions) => {
if (err) {
console.error('Error fetching transactions for PDF:', err);
return res.status(500).send('Fehler beim Generieren des PDFs');
}
// Create PDF document
const doc = new PDFDocument({ margin: 30 });
// Set response headers for PDF download
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `attachment; filename="Kontoauszug_${account.name.replace(/\s+/g, '_')}_${new Date().toISOString().split('T')[0]}.pdf"`);
// Pipe the PDF to the response
doc.pipe(res);
// Add content to PDF
doc.fontSize(20).text(`Kontoauszug für ${account.name}`, { align: 'center' });
doc.moveDown();
doc.fontSize(14).text(`Kontostand: ${account.balance.toFixed(2)}`);
doc.fontSize(12).text(`Erstellt am: ${new Date(account.created_at).toLocaleDateString('de-DE')}`);
doc.moveDown(2);
doc.fontSize(16).text('Transaktionshistorie:', { underline: true });
doc.moveDown();
// Table headers
const table = {
headers: ['Datum', 'Typ', 'Betrag', 'Beschreibung', 'Artikel', 'Durchgeführt von'],
rows: []
};
transactions.forEach(tx => {
table.rows.push([
new Date(tx.created_at).toLocaleString('de-DE'),
tx.type === 'deposit' ? 'Einzahlung' : 'Abbuchung',
`${tx.amount.toFixed(2)}`,
tx.description || '-',
tx.item_name || '-',
tx.created_by_name || 'System'
]);
});
// Draw table
const tableTop = doc.y;
const columnSpacing = 10;
const columnWidths = [80, 60, 50, 120, 80, 80];
// Draw headers
doc.font('Helvetica-Bold');
let x = 30;
table.headers.forEach((header, i) => {
doc.text(header, x, tableTop, { width: columnWidths[i], align: 'left' });
x += columnWidths[i] + columnSpacing;
});
doc.font('Helvetica');
// Draw rows
let y = tableTop + 20;
transactions.forEach((tx, rowIndex) => {
x = 30;
const row = table.rows[rowIndex];
row.forEach((cell, i) => {
doc.text(cell, x, y, { width: columnWidths[i], align: 'left' });
x += columnWidths[i] + columnSpacing;
});
y += 20;
// Add horizontal line between rows
if (rowIndex < transactions.length - 1) {
doc.moveTo(30, y - 5).lineTo(550, y - 5).stroke('#cccccc');
}
});
doc.moveDown(2);
doc.fontSize(10).text(`Generiert am: ${new Date().toLocaleString('de-DE')}`, { align: 'right' });
doc.fontSize(10).text(`Generiert von: ${req.user.username}`, { align: 'right' });
// Finalize PDF
doc.end();
});
});
});
// Deposit routes // Deposit routes
app.get('/accounts/:id/deposit', requireAuth, (req, res) => { app.get('/accounts/:id/deposit', requireAuth, (req, res) => {
const accountId = req.params.id; const accountId = req.params.id;
@ -339,7 +454,7 @@ app.get('/accounts/:id/deposit', requireAuth, (req, res) => {
if (err || !account) { if (err || !account) {
return res.status(404).send('Konto nicht gefunden'); return res.status(404).send('Konto nicht gefunden');
} }
res.render('deposit', { user: req.user, account }); res.render('deposit', { user: req.user, account: account, error: null });
}); });
}); });
@ -388,8 +503,8 @@ app.get('/accounts/:id/withdraw', requireAuth, (req, res) => {
res.render('withdraw', { res.render('withdraw', {
user: req.user, user: req.user,
account, account: account,
items, items: items,
error: req.query.error error: req.query.error
}); });
}); });
@ -480,12 +595,12 @@ app.get('/items', requireAuth, requireAdmin, (req, res) => {
console.error('Error fetching items:', err); console.error('Error fetching items:', err);
items = []; items = [];
} }
res.render('items', { user: req.user, items }); res.render('items', { user: req.user, items: items });
}); });
}); });
app.get('/items/new', requireAuth, requireAdmin, (req, res) => { app.get('/items/new', requireAuth, requireAdmin, (req, res) => {
res.render('item_new', { user: req.user }); res.render('item_new', { user: req.user, error: null, name: '', price: '', description: '' });
}); });
app.post('/items', requireAuth, requireAdmin, (req, res) => { app.post('/items', requireAuth, requireAdmin, (req, res) => {
@ -496,7 +611,8 @@ app.post('/items', requireAuth, requireAdmin, (req, res) => {
return res.render('item_new', { return res.render('item_new', {
user: req.user, user: req.user,
error: 'Ungültiger Preis', error: 'Ungültiger Preis',
name, description name: name,
description: description
}); });
} }
@ -507,7 +623,8 @@ app.post('/items', requireAuth, requireAdmin, (req, res) => {
return res.render('item_new', { return res.render('item_new', {
user: req.user, user: req.user,
error: 'Fehler beim Anlegen des Artikels', error: 'Fehler beim Anlegen des Artikels',
name, description name: name,
description: description
}); });
} }
res.redirect('/items'); res.redirect('/items');
@ -521,7 +638,7 @@ app.get('/items/:id/edit', requireAuth, requireAdmin, (req, res) => {
if (err || !item) { if (err || !item) {
return res.status(404).send('Artikel nicht gefunden'); return res.status(404).send('Artikel nicht gefunden');
} }
res.render('item_edit', { user: req.user, item }); res.render('item_edit', { user: req.user, item: item, error: null });
}); });
}); });
@ -563,12 +680,12 @@ app.get('/users', requireAuth, requireAdmin, (req, res) => {
console.error('Error fetching users:', err); console.error('Error fetching users:', err);
users = []; users = [];
} }
res.render('users', { user: req.user, users }); res.render('users', { user: req.user, users: users });
}); });
}); });
app.get('/users/new', requireAuth, requireAdmin, (req, res) => { app.get('/users/new', requireAuth, requireAdmin, (req, res) => {
res.render('user_new', { user: req.user }); res.render('user_new', { user: req.user, error: null, username: '', role: 'employee' });
}); });
app.post('/users', requireAuth, requireAdmin, (req, res) => { app.post('/users', requireAuth, requireAdmin, (req, res) => {
@ -582,7 +699,8 @@ app.post('/users', requireAuth, requireAdmin, (req, res) => {
return res.render('user_new', { return res.render('user_new', {
user: req.user, user: req.user,
error: 'Fehler beim Anlegen des Benutzers', error: 'Fehler beim Anlegen des Benutzers',
username, role username: username,
role: role
}); });
} }
res.redirect('/users'); res.redirect('/users');
@ -608,40 +726,43 @@ app.post('/users/:id/delete', requireAuth, requireAdmin, (req, res) => {
// PIN check route for visitors // PIN check route for visitors
app.get('/pin-check', (req, res) => { app.get('/pin-check', (req, res) => {
res.render('pin_check', { error: null }); res.render('pin_check', { error: null, success: false, account: null, transactions: null, user: null });
}); });
app.post('/pin-check', (req, res) => { app.post('/pin-check', (req, res) => {
const { pin } = req.body; const { pin } = req.body;
db.get("SELECT * FROM accounts WHERE pin = ?", [pin], (err, account) => { if (!pin) {
return res.render('pin_check', { error: 'Bitte geben Sie eine PIN ein', success: false, account: null, transactions: null, user: null });
}
getAccountByPin(pin, (err, account) => {
if (err || !account) { if (err || !account) {
// Try hashed PIN return res.render('pin_check', { error: 'Ungültige PIN', success: false, account: null, transactions: null, user: null });
db.all("SELECT * FROM accounts", (err, accounts) => {
if (err) {
return res.render('pin_check', { error: 'Ungültige PIN' });
}
const matchingAccount = accounts.find(acc => bcrypt.compareSync(pin, acc.pin));
if (!matchingAccount) {
return res.render('pin_check', { error: 'Ungültige PIN' });
}
// Show balance
res.render('pin_check', {
error: null,
account: matchingAccount,
success: true
});
});
return;
} }
// Show balance // Get last 10 transactions for this account
res.render('pin_check', { db.all(`
error: null, SELECT t.*, u.username as created_by_name, i.name as item_name
account, FROM transactions t
success: true LEFT JOIN users u ON t.created_by = u.id
LEFT JOIN items i ON t.item_id = i.id
WHERE t.account_id = ?
ORDER BY t.created_at DESC
LIMIT 10
`, [account.id], (err, transactions) => {
if (err) {
console.error('Error fetching transactions for PIN check:', err);
transactions = [];
}
res.render('pin_check', {
error: null,
success: true,
account: account,
transactions: transactions,
user: null
});
}); });
}); });
}); });
@ -654,20 +775,39 @@ app.post('/api/pin-check', (req, res) => {
return res.json({ error: 'PIN ist erforderlich' }); return res.json({ error: 'PIN ist erforderlich' });
} }
db.all("SELECT * FROM accounts", (err, accounts) => { getAccountByPin(pin, (err, account) => {
if (err) { if (err || !account) {
return res.json({ error: 'Datenbankfehler' });
}
const matchingAccount = accounts.find(acc => bcrypt.compareSync(pin, acc.pin));
if (!matchingAccount) {
return res.json({ error: 'Ungültige PIN' }); return res.json({ error: 'Ungültige PIN' });
} }
res.json({ // Get last 10 transactions
success: true, db.all(`
name: matchingAccount.name, SELECT t.*, u.username as created_by_name, i.name as item_name
balance: matchingAccount.balance FROM transactions t
LEFT JOIN users u ON t.created_by = u.id
LEFT JOIN items i ON t.item_id = i.id
WHERE t.account_id = ?
ORDER BY t.created_at DESC
LIMIT 10
`, [account.id], (err, transactions) => {
if (err) {
console.error('Error fetching transactions:', err);
transactions = [];
}
res.json({
success: true,
name: account.name,
balance: account.balance,
transactions: transactions.map(tx => ({
date: tx.created_at,
type: tx.type,
amount: tx.amount,
description: tx.description,
item: tx.item_name,
by: tx.created_by_name
}))
});
}); });
}); });
}); });

2
token.txt Normal file
View File

@ -0,0 +1,2 @@
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

View File

@ -1,53 +1,92 @@
<%- include('layout', { title: 'Kontodetails - ' + account.name }) %> <!DOCTYPE html>
<html lang="de">
<% override body %> <head>
<h2>Kontodetails: <%= account.name %></h2> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<div class="account-info"> <title>Kontodetails - <%= typeof account !== 'undefined' ? account.name : 'Frühstückskonten' %></title>
<p><strong>Kontostand:</strong> <span class="balance"><%= account.balance.toFixed(2) %> €</span></p> <link rel="stylesheet" href="/styles.css">
<p><strong>Erstellt am:</strong> <%= new Date(account.created_at).toLocaleString('de-DE') %></p> </head>
<body>
<div class="container">
<header>
<h1>Frühstückskonten Verwaltung</h1>
<% if (typeof user !== 'undefined' && user) { %>
<nav>
<span>Angemeldet als: <strong><%= user.username %></strong> (<%= user.role %>) |
<a href="/dashboard">Dashboard</a> |
<a href="/accounts">Konten</a>
<% if (user.role === 'admin') { %>
| <a href="/items">Artikel</a>
| <a href="/users">Benutzer</a>
<% } %>
| <a href="/logout">Abmelden</a>
</span>
</nav>
<% } %>
</header>
<main>
<% if (typeof account !== 'undefined' && account) { %>
<h2>Kontodetails: <%= account.name %></h2>
<div class="account-info">
<p><strong>Kontostand:</strong> <span class="balance"><%= account.balance.toFixed(2) %> €</span></p>
<p><strong>Erstellt am:</strong> <%= new Date(account.created_at).toLocaleString('de-DE') %></p>
</div>
<div class="actions">
<a href="/accounts/<%= account.id %>/deposit" class="btn">Geld einzahlen</a>
<a href="/accounts/<%= account.id %>/withdraw" class="btn">Betrag abbuchen</a>
<a href="/accounts/<%= account.id %>/pdf" class="btn">PDF Export</a>
<% if (typeof isAdmin !== 'undefined' && isAdmin) { %>
<a href="/accounts/<%= account.id %>/edit" class="btn secondary">Konto bearbeiten</a>
<% } %>
<a href="/accounts" class="btn secondary">Zurück zur Liste</a>
</div>
<h3>Transaktionshistorie</h3>
<table class="transaction-table">
<thead>
<tr>
<th>Datum</th>
<th>Typ</th>
<th>Betrag</th>
<th>Beschreibung</th>
<th>Artikel</th>
<th>Durchgeführt von</th>
</tr>
</thead>
<tbody>
<% if (typeof transactions !== 'undefined' && transactions.length > 0) { %>
<% transactions.forEach(function(transaction) { %>
<tr>
<td><%= new Date(transaction.created_at).toLocaleString('de-DE') %></td>
<td class="type-<%= transaction.type %>">
<%= transaction.type === 'deposit' ? 'Einzahlung' : 'Abbuchung' %>
</td>
<td class="amount-<%= transaction.type %>">
<%= transaction.amount.toFixed(2) %> €
</td>
<td><%= transaction.description %></td>
<td><%= transaction.item_name || '-' %></td>
<td><%= transaction.created_by_name || 'System' %></td>
</tr>
<% }); %>
<% } else { %>
<tr>
<td colspan="6">Keine Transaktionen für dieses Konto.</td>
</tr>
<% } %>
</tbody>
</table>
<% } else { %>
<p>Konto nicht gefunden.</p>
<% } %>
</main>
<footer>
<p>&copy; 2024 Frühstückskonten Verwaltung</p>
</footer>
</div> </div>
</body>
<div class="actions"> </html>
<a href="/accounts/<%= account.id %>/deposit" class="btn">Geld einzahlen</a>
<a href="/accounts/<%= account.id %>/withdraw" class="btn">Betrag abbuchen</a>
<% if (isAdmin) { %>
<a href="/accounts/<%= account.id %>/edit" class="btn secondary">Konto bearbeiten</a>
<% } %>
<a href="/accounts" class="btn secondary">Zurück zur Liste</a>
</div>
<h3>Transaktionshistorie</h3>
<table class="transaction-table">
<thead>
<tr>
<th>Datum</th>
<th>Typ</th>
<th>Betrag</th>
<th>Beschreibung</th>
<th>Artikel</th>
<th>Durchgeführt von</th>
</tr>
</thead>
<tbody>
<% transactions.forEach(transaction => { %>
<tr>
<td><%= new Date(transaction.created_at).toLocaleString('de-DE') %></td>
<td class="type-<%= transaction.type %>">
<%= transaction.type === 'deposit' ? 'Einzahlung' : 'Abbuchung' %>
</td>
<td class="amount-<%= transaction.type %>">
<%= transaction.amount.toFixed(2) %> €
</td>
<td><%= transaction.description %></td>
<td><%= transaction.item_name || '-' %></td>
<td><%= transaction.created_by_name || 'System' %></td>
</tr>
<% }); %>
</tbody>
</table>
<% if (transactions.length === 0) { %>
<p>Keine Transaktionen für dieses Konto.</p>
<% } %>
<% end %>

View File

@ -1,25 +1,60 @@
<%- include('layout', { title: 'Konto bearbeiten - ' + account.name }) %> <!DOCTYPE html>
<html lang="de">
<% override body %> <head>
<h2>Konto bearbeiten: <%= account.name %></h2> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<form action="/accounts/<%= account.id %>", method="POST"> <title>Konto bearbeiten - <%= typeof account !== 'undefined' ? account.name : 'Frühstückskonten' %></title>
<div class="form-group"> <link rel="stylesheet" href="/styles.css">
<label for="name">Name:</label> </head>
<input type="text" id="name" name="name" value="<%= account.name %>", required autofocus> <body>
</div> <div class="container">
<div class="form-group"> <header>
<label for="pin">Neue PIN (4-stellig, leer lassen zum Behalten):</label> <h1>Frühstückskonten Verwaltung</h1>
<input type="password" id="pin" name="pin" pattern="\d{4}" <% if (typeof user !== 'undefined' && user) { %>
title="Bitte genau 4 Ziffern eingeben" maxlength="4"> <nav>
</div> <span>Angemeldet als: <strong><%= user.username %></strong> (<%= user.role %>) |
<button type="submit" class="btn">Speichern</button> <a href="/dashboard">Dashboard</a> |
<a href="/accounts/<%= account.id %>", class="btn secondary">Abbrechen</a> <a href="/accounts">Konten</a>
</form> <% if (user.role === 'admin') { %>
| <a href="/items">Artikel</a>
<div class="danger-zone"> | <a href="/users">Benutzer</a>
<h4>Achtung: Kontolöschung</h4> <% } %>
<p>Das Löschen eines Kontos löscht auch alle zugehörigen Transaktionen!</p> | <a href="/logout">Abmelden</a>
<!-- Löschfunktion könnte hier hinzugefügt werden --> </span>
</nav>
<% } %>
</header>
<main>
<% if (typeof account !== 'undefined' && account) { %>
<h2>Konto bearbeiten: <%= account.name %></h2>
<form action="/accounts/<%= account.id %>", method="POST">
<div class="form-group">
<label for="name">Name:</label>
<input type="text" id="name" name="name" value="<%= account.name %>", required autofocus>
</div>
<div class="form-group">
<label for="pin">Neue PIN (4-stellig, leer lassen zum Behalten):</label>
<input type="password" id="pin" name="pin" pattern="\d{4}"
title="Bitte genau 4 Ziffern eingeben" maxlength="4">
</div>
<button type="submit" class="btn">Speichern</button>
<a href="/accounts/<%= account.id %>", class="btn secondary">Abbrechen</a>
</form>
<div class="danger-zone">
<h4>Achtung: Kontolöschung</h4>
<p>Das Löschen eines Kontos löscht auch alle zugehörigen Transaktionen!</p>
</div>
<% } else { %>
<p>Konto nicht gefunden.</p>
<% } %>
</main>
<footer>
<p>&copy; 2024 Frühstückskonten Verwaltung</p>
</footer>
</div> </div>
<% end %> </body>
</html>

View File

@ -1,23 +1,59 @@
<%- include('layout', { title: 'Neues Konto anlegen' }) %> <!DOCTYPE html>
<html lang="de">
<% override body %> <head>
<h2>Neues Konto anlegen</h2> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<form action="/accounts" method="POST"> <title>Neues Konto anlegen - Frühstückskonten Verwaltung</title>
<div class="form-group"> <link rel="stylesheet" href="/styles.css">
<label for="name">Name:</label> </head>
<input type="text" id="name" name="name" required autofocus> <body>
</div> <div class="container">
<div class="form-group"> <header>
<label for="pin">PIN (4-stellig):</label> <h1>Frühstückskonten Verwaltung</h1>
<input type="password" id="pin" name="pin" required pattern="\d{4}" <% if (typeof user !== 'undefined' && user) { %>
title="Bitte genau 4 Ziffern eingeben" maxlength="4"> <nav>
</div> <span>Angemeldet als: <strong><%= user.username %></strong> (<%= user.role %>) |
<div class="form-group"> <a href="/dashboard">Dashboard</a> |
<label for="initialBalance">Anfangsguthaben (€):</label> <a href="/accounts">Konten</a>
<input type="number" id="initialBalance" name="initialBalance" step="0.01" min="0" value="0.00"> <% if (user.role === 'admin') { %>
</div> | <a href="/items">Artikel</a>
<button type="submit" class="btn">Konto anlegen</button> | <a href="/users">Benutzer</a>
<a href="/accounts" class="btn secondary">Abbrechen</a> <% } %>
</form> | <a href="/logout">Abmelden</a>
<% end %> </span>
</nav>
<% } %>
</header>
<main>
<h2>Neues Konto anlegen</h2>
<% if (typeof error !== 'undefined' && error) { %>
<div class="error"><%= error %></div>
<% } %>
<form action="/accounts" method="POST">
<div class="form-group">
<label for="name">Name:</label>
<input type="text" id="name" name="name" required autofocus>
</div>
<div class="form-group">
<label for="pin">PIN (4-stellig):</label>
<input type="password" id="pin" name="pin" required pattern="\d{4}"
title="Bitte genau 4 Ziffern eingeben" maxlength="4">
</div>
<div class="form-group">
<label for="initialBalance">Anfangsguthaben (€):</label>
<input type="number" id="initialBalance" name="initialBalance" step="0.01" min="0" value="0.00">
</div>
<button type="submit" class="btn">Konto anlegen</button>
<a href="/accounts" class="btn secondary">Abbrechen</a>
</form>
</main>
<footer>
<p>&copy; 2024 Frühstückskonten Verwaltung</p>
</footer>
</div>
</body>
</html>

View File

@ -1,39 +1,77 @@
<%- include('layout', { title: 'Konten' }) %> <!DOCTYPE html>
<html lang="de">
<% override body %> <head>
<h2>Kontenverwaltung</h2> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<div class="actions"> <title>Konten - Frühstückskonten Verwaltung</title>
<a href="/accounts/new" class="btn">Neues Konto anlegen</a> <link rel="stylesheet" href="/styles.css">
</head>
<body>
<div class="container">
<header>
<h1>Frühstückskonten Verwaltung</h1>
<% if (typeof user !== 'undefined' && user) { %>
<nav>
<span>Angemeldet als: <strong><%= user.username %></strong> (<%= user.role %>) |
<a href="/dashboard">Dashboard</a> |
<a href="/accounts">Konten</a>
<% if (user.role === 'admin') { %>
| <a href="/items">Artikel</a>
| <a href="/users">Benutzer</a>
<% } %>
| <a href="/logout">Abmelden</a>
</span>
</nav>
<% } %>
</header>
<main>
<h2>Kontenverwaltung</h2>
<div class="actions">
<a href="/accounts/new" class="btn">Neues Konto anlegen</a>
</div>
<table class="data-table">
<thead>
<tr>
<th>Name</th>
<th>Kontostand</th>
<th>Erstellt am</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<% if (typeof accounts !== 'undefined' && accounts.length > 0) { %>
<% accounts.forEach(function(account) { %>
<tr>
<td><%= account.name %></td>
<td class="balance"><%= account.balance.toFixed(2) %> €</td>
<td><%= new Date(account.created_at).toLocaleDateString('de-DE') %></td>
<td>
<a href="/accounts/<%= account.id %>">Details</a>
| <a href="/accounts/<%= account.id %>/deposit">Einzahlen</a>
| <a href="/accounts/<%= account.id %>/withdraw">Abbuchen</a>
<% if (typeof isAdmin !== 'undefined' && isAdmin) { %>
| <a href="/accounts/<%= account.id %>/edit">Bearbeiten</a>
<% } %>
</td>
</tr>
<% }); %>
<% } else { %>
<tr>
<td colspan="4">Keine Konten vorhanden</td>
</tr>
<% } %>
</tbody>
</table>
<p>Gesamt: <%= typeof accounts !== 'undefined' ? accounts.length : 0 %> Konten</p>
</main>
<footer>
<p>&copy; 2024 Frühstückskonten Verwaltung</p>
</footer>
</div> </div>
</body>
<table class="data-table"> </html>
<thead>
<tr>
<th>Name</th>
<th>Kontostand</th>
<th>Erstellt am</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<% accounts.forEach(account => { %>
<tr>
<td><%= account.name %></td>
<td class="balance"><%= account.balance.toFixed(2) %> €</td>
<td><%= new Date(account.created_at).toLocaleDateString('de-DE') %></td>
<td>
<a href="/accounts/<%= account.id %>">Details</a>
| <a href="/accounts/<%= account.id %>/deposit">Einzahlen</a>
| <a href="/accounts/<%= account.id %>/withdraw">Abbuchen</a>
<% if (isAdmin) { %>
| <a href="/accounts/<%= account.id %>/edit">Bearbeiten</a>
<% } %>
</td>
</tr>
<% }); %>
</tbody>
</table>
<p>Gesamt: <%= accounts.length %> Konten</p>
<% end %>

View File

@ -1,55 +1,93 @@
<%- include('layout', { title: 'Dashboard' }) %> <!DOCTYPE html>
<html lang="de">
<% override body %> <head>
<h2>Dashboard</h2> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<div class="dashboard-stats"> <title>Dashboard - Frühstückskonten Verwaltung</title>
<div class="stat-card"> <link rel="stylesheet" href="/styles.css">
<h3>Konten</h3> </head>
<p class="stat-value"><%= stats.count %></p> <body>
</div> <div class="container">
<div class="stat-card"> <header>
<h3>Gesamtguthaben</h3> <h1>Frühstückskonten Verwaltung</h1>
<p class="stat-value"><%= (stats.total_balance || 0).toFixed(2) %> €</p> <% if (typeof user !== 'undefined' && user) { %>
</div> <nav>
<span>Angemeldet als: <strong><%= user.username %></strong> (<%= user.role %>) |
<a href="/dashboard">Dashboard</a> |
<a href="/accounts">Konten</a>
<% if (user.role === 'admin') { %>
| <a href="/items">Artikel</a>
| <a href="/users">Benutzer</a>
<% } %>
| <a href="/logout">Abmelden</a>
</span>
</nav>
<% } %>
</header>
<main>
<h2>Dashboard</h2>
<div class="dashboard-stats">
<div class="stat-card">
<h3>Konten</h3>
<p class="stat-value"><%= typeof stats !== 'undefined' ? stats.count : 0 %></p>
</div>
<div class="stat-card">
<h3>Gesamtguthaben</h3>
<p class="stat-value"><%= typeof stats !== 'undefined' ? (stats.total_balance || 0).toFixed(2) : '0.00' %> €</p>
</div>
</div>
<h3>Letzte Transaktionen</h3>
<table class="transaction-table">
<thead>
<tr>
<th>Datum</th>
<th>Konto</th>
<th>Typ</th>
<th>Betrag</th>
<th>Beschreibung</th>
<th>Artikel</th>
<th>Durchgeführt von</th>
</tr>
</thead>
<tbody>
<% if (typeof transactions !== 'undefined' && transactions.length > 0) { %>
<% transactions.forEach(function(transaction) { %>
<tr>
<td><%= new Date(transaction.created_at).toLocaleString('de-DE') %></td>
<td><%= transaction.account_name %></td>
<td class="type-<%= transaction.type %>">
<%= transaction.type === 'deposit' ? 'Einzahlung' : 'Abbuchung' %>
</td>
<td class="amount-<%= transaction.type %>">
<%= transaction.amount.toFixed(2) %> €
</td>
<td><%= transaction.description %></td>
<td><%= transaction.item_name || '-' %></td>
<td><%= transaction.created_by_name || 'System' %></td>
</tr>
<% }); %>
<% } else { %>
<tr>
<td colspan="7">Keine Transaktionen vorhanden</td>
</tr>
<% } %>
</tbody>
</table>
<div class="actions">
<a href="/accounts/new" class="btn">Neues Konto anlegen</a>
<% if (typeof isAdmin !== 'undefined' && isAdmin) { %>
<a href="/items/new" class="btn">Neuen Artikel anlegen</a>
<% } %>
</div>
</main>
<footer>
<p>&copy; 2024 Frühstückskonten Verwaltung</p>
</footer>
</div> </div>
</body>
<h3>Letzte Transaktionen</h3> </html>
<table class="transaction-table">
<thead>
<tr>
<th>Datum</th>
<th>Konto</th>
<th>Typ</th>
<th>Betrag</th>
<th>Beschreibung</th>
<th>Artikel</th>
<th>Durchgeführt von</th>
</tr>
</thead>
<tbody>
<% transactions.forEach(transaction => { %>
<tr>
<td><%= new Date(transaction.created_at).toLocaleString('de-DE') %></td>
<td><%= transaction.account_name %></td>
<td class="type-<%= transaction.type %>">
<%= transaction.type === 'deposit' ? 'Einzahlung' : 'Abbuchung' %>
</td>
<td class="amount-<%= transaction.type %>">
<%= transaction.amount.toFixed(2) %> €
</td>
<td><%= transaction.description %></td>
<td><%= transaction.item_name || '-' %></td>
<td><%= transaction.created_by_name || 'System' %></td>
</tr>
<% }); %>
</tbody>
</table>
<div class="actions">
<a href="/accounts/new" class="btn">Neues Konto anlegen</a>
<% if (isAdmin) { %>
<a href="/items/new" class="btn">Neuen Artikel anlegen</a>
<% } %>
</div>
<% end %>

View File

@ -1,24 +1,56 @@
<%- include('layout', { title: 'Geld einzahlen - ' + account.name }) %> <!DOCTYPE html>
<html lang="de">
<% override body %> <head>
<h2>Geld einzahlen für: <%= account.name %></h2> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<p>Aktueller Kontostand: <strong><%= account.balance.toFixed(2) %> €</strong></p> <title>Geld einzahlen - <%= typeof account !== 'undefined' ? account.name : 'Frühstückskonten' %></title>
<link rel="stylesheet" href="/styles.css">
<form action="/accounts/<%= account.id %>/deposit" method="POST"> </head>
<div class="form-group"> <body>
<label for="amount">Betrag (€):</label> <div class="container">
<input type="number" id="amount" name="amount" step="0.01" min="0.01" required autofocus> <header>
</div> <h1>Frühstückskonten Verwaltung</h1>
<div class="form-group"> <% if (typeof user !== 'undefined' && user) { %>
<label for="description">Beschreibung (optional):</label> <nav>
<input type="text" id="description" name="description"> <span>Angemeldet als: <strong><%= user.username %></strong> (<%= user.role %>) |
</div> <a href="/dashboard">Dashboard</a> |
<button type="submit" class="btn">Einzahlen</button> <a href="/accounts">Konten</a>
<a href="/accounts/<%= account.id %>", class="btn secondary">Abbrechen</a> <% if (user.role === 'admin') { %>
</form> | <a href="/items">Artikel</a>
| <a href="/users">Benutzer</a>
<% if (error) { %> <% } %>
<div class="error"><%= error %></div> | <a href="/logout">Abmelden</a>
<% } %> </span>
<% end %> </nav>
<% } %>
</header>
<main>
<% if (typeof account !== 'undefined' && account) { %>
<h2>Geld einzahlen für: <%= account.name %></h2>
<p>Aktueller Kontostand: <strong><%= account.balance.toFixed(2) %> €</strong></p>
<form action="/accounts/<%= account.id %>/deposit" method="POST">
<div class="form-group">
<label for="amount">Betrag (€):</label>
<input type="number" id="amount" name="amount" step="0.01" min="0.01" required autofocus>
</div>
<div class="form-group">
<label for="description">Beschreibung (optional):</label>
<input type="text" id="description" name="description">
</div>
<button type="submit" class="btn">Einzahlen</button>
<a href="/accounts/<%= account.id %>", class="btn secondary">Abbrechen</a>
</form>
<% } else { %>
<p>Konto nicht gefunden.</p>
<% } %>
</main>
<footer>
<p>&copy; 2024 Frühstückskonten Verwaltung</p>
</footer>
</div>
</body>
</html>

View File

@ -1,29 +1,65 @@
<%- include('layout', { title: 'Artikel bearbeiten - ' + item.name }) %> <!DOCTYPE html>
<html lang="de">
<% override body %> <head>
<h2>Artikel bearbeiten: <%= item.name %></h2> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<form action="/items/<%= item.id %>", method="POST"> <title>Artikel bearbeiten - <%= typeof item !== 'undefined' ? item.name : 'Frühstückskonten' %></title>
<div class="form-group"> <link rel="stylesheet" href="/styles.css">
<label for="name">Name:</label> </head>
<input type="text" id="name" name="name" required autofocus <body>
value="<%= item.name %>"> <div class="container">
</div> <header>
<div class="form-group"> <h1>Frühstückskonten Verwaltung</h1>
<label for="price">Preis (€):</label> <% if (typeof user !== 'undefined' && user) { %>
<input type="number" id="price" name="price" step="0.01" min="0.01" required <nav>
value="<%= item.price.toFixed(2) %>"> <span>Angemeldet als: <strong><%= user.username %></strong> (<%= user.role %>) |
</div> <a href="/dashboard">Dashboard</a> |
<div class="form-group"> <a href="/accounts">Konten</a>
<label for="description">Beschreibung (optional):</label> <% if (user.role === 'admin') { %>
<input type="text" id="description" name="description" | <a href="/items">Artikel</a>
value="<%= item.description || '' %>"> | <a href="/users">Benutzer</a>
</div> <% } %>
<button type="submit" class="btn">Speichern</button> | <a href="/logout">Abmelden</a>
<a href="/items" class="btn secondary">Abbrechen</a> </span>
</form> </nav>
<% } %>
<% if (error) { %> </header>
<div class="error"><%= error %></div>
<% } %> <main>
<% end %> <% if (typeof item !== 'undefined' && item) { %>
<h2>Artikel bearbeiten: <%= item.name %></h2>
<% if (typeof error !== 'undefined' && error) { %>
<div class="error"><%= error %></div>
<% } %>
<form action="/items/<%= item.id %>", method="POST">
<div class="form-group">
<label for="name">Name:</label>
<input type="text" id="name" name="name" required autofocus
value="<%= item.name %>">
</div>
<div class="form-group">
<label for="price">Preis (€):</label>
<input type="number" id="price" name="price" step="0.01" min="0.01" required
value="<%= item.price.toFixed(2) %>">
</div>
<div class="form-group">
<label for="description">Beschreibung (optional):</label>
<input type="text" id="description" name="description"
value="<%= item.description || '' %>">
</div>
<button type="submit" class="btn">Speichern</button>
<a href="/items" class="btn secondary">Abbrechen</a>
</form>
<% } else { %>
<p>Artikel nicht gefunden.</p>
<% } %>
</main>
<footer>
<p>&copy; 2024 Frühstückskonten Verwaltung</p>
</footer>
</div>
</body>
</html>

View File

@ -1,29 +1,61 @@
<%- include('layout', { title: 'Neuen Artikel anlegen' }) %> <!DOCTYPE html>
<html lang="de">
<% override body %> <head>
<h2>Neuen Artikel anlegen</h2> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<form action="/items" method="POST"> <title>Neuen Artikel anlegen - Frühstückskonten Verwaltung</title>
<div class="form-group"> <link rel="stylesheet" href="/styles.css">
<label for="name">Name:</label> </head>
<input type="text" id="name" name="name" required autofocus <body>
value="<%= locals.name || '' %>"> <div class="container">
</div> <header>
<div class="form-group"> <h1>Frühstückskonten Verwaltung</h1>
<label for="price">Preis (€):</label> <% if (typeof user !== 'undefined' && user) { %>
<input type="number" id="price" name="price" step="0.01" min="0.01" required <nav>
value="<%= locals.price || '' %>"> <span>Angemeldet als: <strong><%= user.username %></strong> (<%= user.role %>) |
</div> <a href="/dashboard">Dashboard</a> |
<div class="form-group"> <a href="/accounts">Konten</a>
<label for="description">Beschreibung (optional):</label> <% if (user.role === 'admin') { %>
<input type="text" id="description" name="description" | <a href="/items">Artikel</a>
value="<%= locals.description || '' %>"> | <a href="/users">Benutzer</a>
</div> <% } %>
<button type="submit" class="btn">Artikel anlegen</button> | <a href="/logout">Abmelden</a>
<a href="/items" class="btn secondary">Abbrechen</a> </span>
</form> </nav>
<% } %>
<% if (error) { %> </header>
<div class="error"><%= error %></div>
<% } %> <main>
<% end %> <h2>Neuen Artikel anlegen</h2>
<% if (typeof error !== 'undefined' && error) { %>
<div class="error"><%= error %></div>
<% } %>
<form action="/items" method="POST">
<div class="form-group">
<label for="name">Name:</label>
<input type="text" id="name" name="name" required autofocus
value="<%= typeof name !== 'undefined' ? name : '' %>">
</div>
<div class="form-group">
<label for="price">Preis (€):</label>
<input type="number" id="price" name="price" step="0.01" min="0.01" required
value="<%= typeof price !== 'undefined' ? price : '' %>">
</div>
<div class="form-group">
<label for="description">Beschreibung (optional):</label>
<input type="text" id="description" name="description"
value="<%= typeof description !== 'undefined' ? description : '' %>">
</div>
<button type="submit" class="btn">Artikel anlegen</button>
<a href="/items" class="btn secondary">Abbrechen</a>
</form>
</main>
<footer>
<p>&copy; 2024 Frühstückskonten Verwaltung</p>
</footer>
</div>
</body>
</html>

View File

@ -1,39 +1,77 @@
<%- include('layout', { title: 'Artikel' }) %> <!DOCTYPE html>
<html lang="de">
<% override body %> <head>
<h2>Artikelverwaltung</h2> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<div class="actions"> <title>Artikel - Frühstückskonten Verwaltung</title>
<a href="/items/new" class="btn">Neuen Artikel anlegen</a> <link rel="stylesheet" href="/styles.css">
</head>
<body>
<div class="container">
<header>
<h1>Frühstückskonten Verwaltung</h1>
<% if (typeof user !== 'undefined' && user) { %>
<nav>
<span>Angemeldet als: <strong><%= user.username %></strong> (<%= user.role %>) |
<a href="/dashboard">Dashboard</a> |
<a href="/accounts">Konten</a>
<% if (user.role === 'admin') { %>
| <a href="/items">Artikel</a>
| <a href="/users">Benutzer</a>
<% } %>
| <a href="/logout">Abmelden</a>
</span>
</nav>
<% } %>
</header>
<main>
<h2>Artikelverwaltung</h2>
<div class="actions">
<a href="/items/new" class="btn">Neuen Artikel anlegen</a>
</div>
<table class="data-table">
<thead>
<tr>
<th>Name</th>
<th>Preis</th>
<th>Beschreibung</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<% if (typeof items !== 'undefined' && items.length > 0) { %>
<% items.forEach(function(item) { %>
<tr>
<td><%= item.name %></td>
<td><%= item.price.toFixed(2) %> €</td>
<td><%= item.description || '-' %></td>
<td>
<a href="/items/<%= item.id %>/edit">Bearbeiten</a>
|
<form action="/items/<%= item.id %>/delete" method="POST" style="display: inline;">
<button type="submit" class="btn-danger"
onclick="return confirm('Artikel wirklich löschen?')">Löschen</button>
</form>
</td>
</tr>
<% }); %>
<% } else { %>
<tr>
<td colspan="4">Keine Artikel vorhanden</td>
</tr>
<% } %>
</tbody>
</table>
<p>Gesamt: <%= typeof items !== 'undefined' ? items.length : 0 %> Artikel</p>
</main>
<footer>
<p>&copy; 2024 Frühstückskonten Verwaltung</p>
</footer>
</div> </div>
</body>
<table class="data-table"> </html>
<thead>
<tr>
<th>Name</th>
<th>Preis</th>
<th>Beschreibung</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<% items.forEach(item => { %>
<tr>
<td><%= item.name %></td>
<td><%= item.price.toFixed(2) %> €</td>
<td><%= item.description || '-' %></td>
<td>
<a href="/items/<%= item.id %>/edit">Bearbeiten</a>
|
<form action="/items/<%= item.id %>/delete" method="POST" style="display: inline;">
<button type="submit" class="btn-danger"
onclick="return confirm('Artikel wirklich löschen?')">Löschen</button>
</form>
</td>
</tr>
<% }); %>
</tbody>
</table>
<p>Gesamt: <%= items.length %> Artikel</p>
<% end %>

View File

@ -1,26 +1,48 @@
<%- include('layout', { title: 'Anmeldung' }) %> <!DOCTYPE html>
<html lang="de">
<% override body %> <head>
<div class="login-container"> <meta charset="UTF-8">
<h2>Anmeldung</h2> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<form action="/login" method="POST"> <title>Anmeldung - Frühstückskonten Verwaltung</title>
<div class="form-group"> <link rel="stylesheet" href="/styles.css">
<label for="username">Benutzername:</label> </head>
<input type="text" id="username" name="username" required autofocus> <body>
<div class="container">
<header>
<h1>Frühstückskonten Verwaltung</h1>
</header>
<main>
<div class="login-container">
<h2>Anmeldung</h2>
<% if (typeof error !== 'undefined' && error) { %>
<div class="error"><%= error %></div>
<% } %>
<form action="/login" method="POST">
<div class="form-group">
<label for="username">Benutzername:</label>
<input type="text" id="username" name="username" required autofocus>
</div>
<div class="form-group">
<label for="password">Passwort:</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit" class="btn">Anmelden</button>
</form>
<p class="login-hint">
<strong>Standard-Anmeldung:</strong><br>
Benutzername: admin<br>
Passwort: admin123
</p>
<p class="pin-check-link">
<a href="/pin-check">Kontostand mit PIN abfragen</a>
</p>
</div> </div>
<div class="form-group"> </main>
<label for="password">Passwort:</label>
<input type="password" id="password" name="password" required> <footer>
</div> <p>&copy; 2024 Frühstückskonten Verwaltung</p>
<button type="submit" class="btn">Anmelden</button> </footer>
</form>
<p class="login-hint">
<strong>Standard-Anmeldung:</strong><br>
Benutzername: admin<br>
Passwort: admin123
</p>
<p class="pin-check-link">
<a href="/pin-check">Kontostand mit PIN abfragen</a>
</p>
</div> </div>
<% end %> </body>
</html>

View File

@ -1,29 +1,90 @@
<%- include('layout', { title: 'Kontostand abfragen' }) %> <!DOCTYPE html>
<html lang="de">
<% override body %> <head>
<div class="pin-check-container"> <meta charset="UTF-8">
<% if (!success) { %> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<h2>Kontostand abfragen</h2> <title>Kontostand abfragen - Frühstückskonten Verwaltung</title>
<p>Bitte geben Sie Ihre 4-stellige PIN ein, um Ihren Kontostand abzufragen.</p> <link rel="stylesheet" href="/styles.css">
</head>
<form action="/pin-check" method="POST"> <body>
<div class="form-group"> <div class="container">
<label for="pin">PIN:</label> <header>
<input type="password" id="pin" name="pin" required autofocus <h1>Frühstückskonten Verwaltung</h1>
pattern="\d{4}" title="Bitte genau 4 Ziffern eingeben" maxlength="4"> </header>
</div>
<button type="submit" class="btn">Kontostand abfragen</button> <main>
</form> <div class="pin-check-container">
<% if (typeof success === 'undefined' || !success) { %>
<p><a href="/login">Zur Mitarbeiter-Anmeldung</a></p> <h2>Kontostand abfragen</h2>
<% } else { %> <p>Bitte geben Sie Ihre 4-stellige PIN ein, um Ihren Kontostand und die letzten Buchungen abzufragen.</p>
<h2>Kontostand für <%= account.name %></h2>
<div class="balance-display"> <% if (typeof error !== 'undefined' && error) { %>
<p>Ihr aktueller Kontostand beträgt:</p> <div class="error"><%= error %></div>
<p class="balance-large"><%= account.balance.toFixed(2) %> €</p> <% } %>
<form action="/pin-check" method="POST">
<div class="form-group">
<label for="pin">PIN:</label>
<input type="password" id="pin" name="pin" required autofocus
pattern="\d{4}" title="Bitte genau 4 Ziffern eingeben" maxlength="4">
</div>
<button type="submit" class="btn">Kontostand abfragen</button>
</form>
<p><a href="/login">Zur Mitarbeiter-Anmeldung</a></p>
<% } else { %>
<h2>Kontostand für <%= typeof account !== 'undefined' ? account.name : 'Unbekannt' %></h2>
<div class="balance-display">
<p>Ihr aktueller Kontostand beträgt:</p>
<p class="balance-large"><%= typeof account !== 'undefined' ? account.balance.toFixed(2) : '0.00' %> €</p>
</div>
<% if (typeof transactions !== 'undefined' && transactions.length > 0) { %>
<h3>Letzte Buchungen:</h3>
<table class="transaction-table">
<thead>
<tr>
<th>Datum</th>
<th>Typ</th>
<th>Betrag</th>
<th>Beschreibung</th>
<th>Artikel</th>
<th>Durchgeführt von</th>
</tr>
</thead>
<tbody>
<% transactions.forEach(function(transaction) { %>
<tr>
<td><%= new Date(transaction.created_at).toLocaleString('de-DE') %></td>
<td class="type-<%= transaction.type %>">
<%= transaction.type === 'deposit' ? 'Einzahlung' : 'Abbuchung' %>
</td>
<td class="amount-<%= transaction.type %>">
<%= transaction.amount.toFixed(2) %> €
</td>
<td><%= transaction.description %></td>
<td><%= transaction.item_name || '-' %></td>
<td><%= transaction.created_by_name || 'System' %></td>
</tr>
<% }); %>
</tbody>
</table>
<% } else { %>
<p>Keine Buchungen in den letzten 10 Transaktionen.</p>
<% } %>
<div class="actions" style="margin-top: 20px;">
<a href="/pin-check" class="btn">Neue Abfrage</a>
<a href="/login" class="btn secondary">Zur Mitarbeiter-Anmeldung</a>
</div>
<% } %>
</div> </div>
<p><a href="/pin-check" class="btn">Neue Abfrage</a></p> </main>
<p><a href="/login">Zur Mitarbeiter-Anmeldung</a></p>
<% } %> <footer>
<p>&copy; 2024 Frühstückskonten Verwaltung</p>
</footer>
</div> </div>
<% end %> </body>
</html>

View File

@ -1,30 +1,62 @@
<%- include('layout', { title: 'Neuen Benutzer anlegen' }) %> <!DOCTYPE html>
<html lang="de">
<% override body %> <head>
<h2>Neuen Benutzer anlegen</h2> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<form action="/users" method="POST"> <title>Neuen Benutzer anlegen - Frühstückskonten Verwaltung</title>
<div class="form-group"> <link rel="stylesheet" href="/styles.css">
<label for="username">Benutzername:</label> </head>
<input type="text" id="username" name="username" required autofocus <body>
value="<%= locals.username || '' %>"> <div class="container">
</div> <header>
<div class="form-group"> <h1>Frühstückskonten Verwaltung</h1>
<label for="password">Passwort:</label> <% if (typeof user !== 'undefined' && user) { %>
<input type="password" id="password" name="password" required> <nav>
</div> <span>Angemeldet als: <strong><%= user.username %></strong> (<%= user.role %>) |
<div class="form-group"> <a href="/dashboard">Dashboard</a> |
<label for="role">Rolle:</label> <a href="/accounts">Konten</a>
<select id="role" name="role" required> <% if (user.role === 'admin') { %>
<option value="employee" <%= locals.role === 'employee' ? 'selected' : '' %>>Mitarbeiter</option> | <a href="/items">Artikel</a>
<option value="admin" <%= locals.role === 'admin' ? 'selected' : '' %>>Administrator</option> | <a href="/users">Benutzer</a>
</select> <% } %>
</div> | <a href="/logout">Abmelden</a>
<button type="submit" class="btn">Benutzer anlegen</button> </span>
<a href="/users" class="btn secondary">Abbrechen</a> </nav>
</form> <% } %>
</header>
<% if (error) { %>
<div class="error"><%= error %></div> <main>
<% } %> <h2>Neuen Benutzer anlegen</h2>
<% end %>
<% if (typeof error !== 'undefined' && error) { %>
<div class="error"><%= error %></div>
<% } %>
<form action="/users" method="POST">
<div class="form-group">
<label for="username">Benutzername:</label>
<input type="text" id="username" name="username" required autofocus
value="<%= typeof username !== 'undefined' ? username : '' %>">
</div>
<div class="form-group">
<label for="password">Passwort:</label>
<input type="password" id="password" name="password" required>
</div>
<div class="form-group">
<label for="role">Rolle:</label>
<select id="role" name="role" required>
<option value="employee" <%= typeof role !== 'undefined' && role === 'employee' ? 'selected' : '' %>>Mitarbeiter</option>
<option value="admin" <%= typeof role !== 'undefined' && role === 'admin' ? 'selected' : '' %>>Administrator</option>
</select>
</div>
<button type="submit" class="btn">Benutzer anlegen</button>
<a href="/users" class="btn secondary">Abbrechen</a>
</form>
</main>
<footer>
<p>&copy; 2024 Frühstückskonten Verwaltung</p>
</footer>
</div>
</body>
</html>

View File

@ -1,39 +1,77 @@
<%- include('layout', { title: 'Benutzerverwaltung' }) %> <!DOCTYPE html>
<html lang="de">
<% override body %> <head>
<h2>Benutzerverwaltung</h2> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<div class="actions"> <title>Benutzerverwaltung - Frühstückskonten Verwaltung</title>
<a href="/users/new" class="btn">Neuen Benutzer anlegen</a> <link rel="stylesheet" href="/styles.css">
</head>
<body>
<div class="container">
<header>
<h1>Frühstückskonten Verwaltung</h1>
<% if (typeof user !== 'undefined' && user) { %>
<nav>
<span>Angemeldet als: <strong><%= user.username %></strong> (<%= user.role %>) |
<a href="/dashboard">Dashboard</a> |
<a href="/accounts">Konten</a>
<% if (user.role === 'admin') { %>
| <a href="/items">Artikel</a>
| <a href="/users">Benutzer</a>
<% } %>
| <a href="/logout">Abmelden</a>
</span>
</nav>
<% } %>
</header>
<main>
<h2>Benutzerverwaltung</h2>
<div class="actions">
<a href="/users/new" class="btn">Neuen Benutzer anlegen</a>
</div>
<table class="data-table">
<thead>
<tr>
<th>Benutzername</th>
<th>Rolle</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<% if (typeof users !== 'undefined' && users.length > 0) { %>
<% users.forEach(function(userItem) { %>
<tr>
<td><%= userItem.username %></td>
<td><%= userItem.role %></td>
<td>
<% if (userItem.id !== user.id) { %>
<form action="/users/<%= userItem.id %>/delete" method="POST" style="display: inline;">
<button type="submit" class="btn-danger"
onclick="return confirm('Benutzer wirklich löschen?')">Löschen</button>
</form>
<% } else { %>
<span class="disabled">(Eigener Account)</span>
<% } %>
</td>
</tr>
<% }); %>
<% } else { %>
<tr>
<td colspan="3">Keine Benutzer vorhanden</td>
</tr>
<% } %>
</tbody>
</table>
<p>Gesamt: <%= typeof users !== 'undefined' ? users.length : 0 %> Benutzer</p>
</main>
<footer>
<p>&copy; 2024 Frühstückskonten Verwaltung</p>
</footer>
</div> </div>
</body>
<table class="data-table"> </html>
<thead>
<tr>
<th>Benutzername</th>
<th>Rolle</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<% users.forEach(user => { %>
<tr>
<td><%= user.username %></td>
<td><%= user.role %></td>
<td>
<% if (user.id !== currentUser.id) { %>
<form action="/users/<%= user.id %>/delete" method="POST" style="display: inline;">
<button type="submit" class="btn-danger"
onclick="return confirm('Benutzer wirklich löschen?')">Löschen</button>
</form>
<% else %>
<span class="disabled">(Eigener Account)</span>
<% } %>
</td>
</tr>
<% }); %>
</tbody>
</table>
<p>Gesamt: <%= users.length %> Benutzer</p>
<% end %>

View File

@ -1,75 +1,113 @@
<%- include('layout', { title: 'Betrag abbuchen - ' + account.name }) %> <!DOCTYPE html>
<html lang="de">
<% override body %> <head>
<h2>Betrag abbuchen für: <%= account.name %></h2> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<p>Aktueller Kontostand: <strong><%= account.balance.toFixed(2) %> €</strong></p> <title>Betrag abbuchen - <%= typeof account !== 'undefined' ? account.name : 'Frühstückskonten' %></title>
<link rel="stylesheet" href="/styles.css">
<% if (error) { %> </head>
<div class="error"> <body>
<% if (error === 'insufficient_funds') { %> <div class="container">
Nicht genügend Guthaben auf dem Konto! <header>
<% else if (error === 'invalid_amount') { %> <h1>Frühstückskonten Verwaltung</h1>
Bitte einen gültigen Betrag eingeben! <% if (typeof user !== 'undefined' && user) { %>
<% else if (error === 'invalid_item') { %> <nav>
Ungültiger Artikel ausgewählt! <span>Angemeldet als: <strong><%= user.username %></strong> (<%= user.role %>) |
<% else { %> <a href="/dashboard">Dashboard</a> |
<%= error %> <a href="/accounts">Konten</a>
<% if (user.role === 'admin') { %>
| <a href="/items">Artikel</a>
| <a href="/users">Benutzer</a>
<% } %>
| <a href="/logout">Abmelden</a>
</span>
</nav>
<% } %> <% } %>
</div> </header>
<% } %>
<form action="/accounts/<%= account.id %>/withdraw" method="POST">
<div class="form-section">
<h3>Option 1: Artikel auswählen</h3>
<div class="form-group">
<label for="itemId">Artikel:</label>
<select id="itemId" name="itemId">
<option value="">-- Artikel auswählen --</option>
<% items.forEach(item => { %>
<option value="<%= item.id %>">
<%= item.name %> (<%= item.price.toFixed(2) %> €)
<% if (item.description) { %>
- <%= item.description %>
<% } %>
</option>
<% }); %>
</select>
</div>
<div class="form-group">
<label for="description">Bemerkung (optional):</label>
<input type="text" id="description" name="description">
</div>
</div>
<div class="form-section"> <main>
<h3>Option 2: Manueller Betrag</h3> <% if (typeof account !== 'undefined' && account) { %>
<div class="form-group"> <h2>Betrag abbuchen für: <%= account.name %></h2>
<label for="customAmount">Betrag (€):</label>
<input type="number" id="customAmount" name="customAmount" step="0.01" min="0.01"> <p>Aktueller Kontostand: <strong><%= account.balance.toFixed(2) %> €</strong></p>
</div>
<div class="form-group"> <% if (typeof error !== 'undefined' && error) { %>
<label for="description2">Beschreibung (optional):</label> <div class="error">
<input type="text" id="description2" name="description"> <% if (error === 'insufficient_funds') { %>
</div> Nicht genügend Guthaben auf dem Konto!
</div> <% } else if (error === 'invalid_amount') { %>
Bitte einen gültigen Betrag eingeben!
<% } else if (error === 'invalid_item') { %>
Ungültiger Artikel ausgewählt!
<% } else { %>
<%= error %>
<% } %>
</div>
<% } %>
<form action="/accounts/<%= account.id %>/withdraw" method="POST">
<div class="form-section">
<h3>Option 1: Artikel auswählen</h3>
<div class="form-group">
<label for="itemId">Artikel:</label>
<select id="itemId" name="itemId">
<option value="">-- Artikel auswählen --</option>
<% if (typeof items !== 'undefined' && items.length > 0) { %>
<% items.forEach(function(item) { %>
<option value="<%= item.id %>">
<%= item.name %> (<%= item.price.toFixed(2) %> €)
<% if (item.description) { %>
- <%= item.description %>
<% } %>
</option>
<% }); %>
<% } %>
</select>
</div>
<div class="form-group">
<label for="description">Bemerkung (optional):</label>
<input type="text" id="description" name="description">
</div>
</div>
<div class="form-section">
<h3>Option 2: Manueller Betrag</h3>
<div class="form-group">
<label for="customAmount">Betrag (€):</label>
<input type="number" id="customAmount" name="customAmount" step="0.01" min="0.01">
</div>
<div class="form-group">
<label for="description2">Beschreibung (optional):</label>
<input type="text" id="description2" name="description">
</div>
</div>
<button type="submit" class="btn">Abbuchen</button>
<a href="/accounts/<%= account.id %>", class="btn secondary">Abbrechen</a>
</form>
<script>
// Ensure only one option is used at a time
document.querySelector('select[name="itemId"]').addEventListener('change', function() {
if (this.value) {
document.getElementById('customAmount').value = '';
}
});
document.getElementById('customAmount').addEventListener('input', function() {
if (this.value) {
document.querySelector('select[name="itemId"]').value = '';
}
});
</script>
<% } else { %>
<p>Konto nicht gefunden.</p>
<% } %>
</main>
<button type="submit" class="btn">Abbuchen</button> <footer>
<a href="/accounts/<%= account.id %>", class="btn secondary">Abbrechen</a> <p>&copy; 2024 Frühstückskonten Verwaltung</p>
</form> </footer>
</div>
<script> </body>
// Ensure only one option is used at a time </html>
document.querySelector('select[name="itemId"]').addEventListener('change', function() {
if (this.value) {
document.getElementById('customAmount').value = '';
}
});
document.getElementById('customAmount').addEventListener('input', function() {
if (this.value) {
document.querySelector('select[name="itemId"]').value = '';
}
});
</script>
<% end %>