diff --git a/._index.html b/._index.html deleted file mode 100644 index 988359c..0000000 Binary files a/._index.html and /dev/null differ diff --git a/._package.json b/._package.json deleted file mode 100644 index 988359c..0000000 Binary files a/._package.json and /dev/null differ diff --git a/._postcss.config.js b/._postcss.config.js deleted file mode 100644 index 988359c..0000000 Binary files a/._postcss.config.js and /dev/null differ diff --git a/._src b/._src deleted file mode 100755 index 988359c..0000000 Binary files a/._src and /dev/null differ diff --git a/._tailwind.config.js b/._tailwind.config.js deleted file mode 100644 index 988359c..0000000 Binary files a/._tailwind.config.js and /dev/null differ diff --git a/backend/lib/mail.js b/backend/lib/mail.js index 8368e5f..ef35312 100644 --- a/backend/lib/mail.js +++ b/backend/lib/mail.js @@ -58,7 +58,7 @@ export async function sendTestMail(to) { const html = `

Dockly-Pfandsystem Demo - SMTP-Test

-

Diese Test-Mail bestaetigt, dass der Mail-Versand ueber +

Diese Test-Mail bestätigt, dass der Mail-Versand über ${process.env.SMTP_HOST || 'kein SMTP'} funktioniert.

Absender: ${FROM}

Zeitstempel: ${new Date().toISOString()}

diff --git a/backend/lib/seed.js b/backend/lib/seed.js index d671904..3dd2b45 100644 --- a/backend/lib/seed.js +++ b/backend/lib/seed.js @@ -1,12 +1,12 @@ import crypto from 'crypto'; -// Befuellt einen frischen Demo-Tenant mit Beispieldaten -// (5 Kunden, 3 Artikel, 6 Lieferungen, 2 Geraete). +// Befüllt einen frischen Demo-Tenant mit Beispieldaten +// (5 Kunden, 3 Artikel, 6 Lieferungen, 2 Geräte). export async function seedDemoTenant(conn, tenantId) { // Artikel const artikel = [ { id: crypto.randomUUID(), name: 'Brotkorb klein', pfand: 2.50 }, - { id: crypto.randomUUID(), name: 'Brotkorb gross', pfand: 5.00 }, + { id: crypto.randomUUID(), name: 'Brotkorb groß', pfand: 5.00 }, { id: crypto.randomUUID(), name: 'Transportbox', pfand: 10.00 } ]; for (const a of artikel) { @@ -18,11 +18,11 @@ export async function seedDemoTenant(conn, tenantId) { // Kunden const kunden = [ - { name: 'Cafe Sonnenschein', adresse: 'Bahnhofstr. 12, 10115 Berlin', zeit: '05:00', hinweis: 'Anlieferung Hintertuer. Code 1234#.' }, - { name: 'Restaurant Roma', adresse: 'Hauptstr. 88, 10117 Berlin', zeit: '06:30', hinweis: 'Schluesselkasten links neben Eingang.' }, - { name: 'Hotel Adlerhorst', adresse: 'Seestrasse 5, 14193 Berlin', zeit: '05:30', hinweis: 'Anlieferung Tiefgarage Ebene -1, Tor 3.' }, + { name: 'Cafe Sonnenschein', adresse: 'Bahnhofstr. 12, 10115 Berlin', zeit: '05:00', hinweis: 'Anlieferung Hintertür. Code 1234#.' }, + { name: 'Restaurant Roma', adresse: 'Hauptstr. 88, 10117 Berlin', zeit: '06:30', hinweis: 'Schlüsselkasten links neben Eingang.' }, + { name: 'Hotel Adlerhorst', adresse: 'Seestraße 5, 14193 Berlin', zeit: '05:30', hinweis: 'Anlieferung Tiefgarage Ebene -1, Tor 3.' }, { name: 'Kantine Werk Nord', adresse: 'Industriestr. 200, 13407 Berlin', zeit: '06:00', hinweis: 'Werkschutz Bescheid geben, Tor B.' }, - { name: 'Baeckerei Schmitt', adresse: 'Marktplatz 4, 10178 Berlin', zeit: '04:45', hinweis: 'Direkt in den Verkaufsraum stellen.' } + { name: 'Bäckerei Schmitt', adresse: 'Marktplatz 4, 10178 Berlin', zeit: '04:45', hinweis: 'Direkt in den Verkaufsraum stellen.' } ]; const kundenIds = []; for (const k of kunden) { @@ -35,7 +35,7 @@ export async function seedDemoTenant(conn, tenantId) { ); } - // Lieferungen (verteilt ueber die letzten 14 Tage) + // Lieferungen (verteilt über die letzten 14 Tage) for (let i = 0; i < 6; i++) { const kid = kundenIds[i % kundenIds.length]; const aid = artikel[i % artikel.length].id; @@ -47,12 +47,12 @@ export async function seedDemoTenant(conn, tenantId) { ); } - // Geraete - const geraete = [ + // Geräte + const geraeteList = [ { sn: 'OF-2024-001', typ: 'Ofen', bez: 'Miwe Condo 3-Etagen', kid: kundenIds[0] }, - { sn: 'KS-2024-007', typ: 'Kuehlschrank', bez: 'Liebherr GKv 5710', kid: kundenIds[2] } + { sn: 'KS-2024-007', typ: 'Kühlschrank', bez: 'Liebherr GKv 5710', kid: kundenIds[2] } ]; - for (const g of geraete) { + for (const g of geraeteList) { await conn.query( `INSERT INTO geraete (id, tenant_id, seriennummer, typ, bezeichnung, kunde_id, status) VALUES (UUID(), ?, ?, ?, ?, ?, 'aktiv')`, diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js index 636c299..d4598b7 100644 --- a/backend/middleware/auth.js +++ b/backend/middleware/auth.js @@ -3,7 +3,7 @@ import pool from '../config/database.js'; // Verifiziert JWT und stellt req.user bereit // req.user enthaelt: { id, email, name, rolle, tenant_id } -// tenant_id ist NULL fuer SuperAdmins. +// tenant_id ist NULL für SuperAdmins. export const authenticateToken = (req, res, next) => { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; @@ -14,7 +14,7 @@ export const authenticateToken = (req, res, next) => { jwt.verify(token, process.env.JWT_SECRET, (err, user) => { if (err) { - return res.status(403).json({ error: 'Ungueltiger oder abgelaufener Token' }); + return res.status(403).json({ error: 'Ungültiger oder abgelaufener Token' }); } req.user = user; next(); diff --git a/backend/package.json b/backend/package.json index 1a38926..d30390b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -2,7 +2,7 @@ "name": "pfandsystem-backend", "version": "1.0.0", "type": "module", - "description": "Backend API fuer Pfandsystem mit MySQL", + "description": "Backend API für Pfandsystem mit MySQL", "main": "server.js", "scripts": { "start": "node server.js", diff --git a/backend/routes/artikel.js b/backend/routes/artikel.js index dac6c9b..296e63e 100644 --- a/backend/routes/artikel.js +++ b/backend/routes/artikel.js @@ -69,7 +69,7 @@ router.delete('/:id', async (req, res) => { const [r] = await pool.query('DELETE FROM artikel WHERE id=? AND tenant_id=?', [req.params.id, req.user.tenant_id]); if (r.affectedRows === 0) return res.status(404).json({ error: 'Artikel nicht gefunden' }); - res.json({ message: 'Artikel geloescht' }); + res.json({ message: 'Artikel gelöscht' }); } catch (e) { res.status(500).json({ error: 'Serverfehler' }); } }); diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 8023e4b..3c8a696 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -46,13 +46,13 @@ router.post('/login', [ ); } - if (rows.length === 0) return res.status(401).json({ error: 'Ungueltige Anmeldedaten' }); + if (rows.length === 0) return res.status(401).json({ error: 'Ungültige Anmeldedaten' }); const m = rows[0]; const ok = await bcrypt.compare(password, m.password_hash); - if (!ok) return res.status(401).json({ error: 'Ungueltige Anmeldedaten' }); + if (!ok) return res.status(401).json({ error: 'Ungültige Anmeldedaten' }); - // Demo-Ablauf-Pruefung + // Demo-Ablauf-Prüfung if (m.tenant_id) { const [tr] = await pool.query( 'SELECT status, demo_expires_at, typ, name FROM tenants WHERE id = ?', @@ -114,7 +114,7 @@ router.post('/change-password', authenticateToken, [ if (!ok) return res.status(401).json({ error: 'Aktuelles Passwort ist falsch' }); const hash = await bcrypt.hash(req.body.newPassword, 10); await pool.query('UPDATE mitarbeiter SET password_hash = ? WHERE id = ?', [hash, req.user.id]); - res.json({ message: 'Passwort erfolgreich geaendert' }); + res.json({ message: 'Passwort erfolgreich geändert' }); } catch (e) { console.error('change-password-Fehler:', e); res.status(500).json({ error: 'Serverfehler' }); diff --git a/backend/routes/geraete.js b/backend/routes/geraete.js index d552f18..ac84b16 100644 --- a/backend/routes/geraete.js +++ b/backend/routes/geraete.js @@ -28,7 +28,7 @@ router.get('/:id', async (req, res) => { WHERE g.id = ? AND g.tenant_id = ?`, [req.params.id, req.user.tenant_id] ); - if (rows.length === 0) return res.status(404).json({ error: 'Geraet nicht gefunden' }); + if (rows.length === 0) return res.status(404).json({ error: 'Gerät nicht gefunden' }); res.json(rows[0]); } catch (e) { res.status(500).json({ error: 'Serverfehler' }); } }); @@ -63,7 +63,7 @@ router.post('/', [ res.status(201).json(rows[0]); } catch (e) { if (e.code === 'ER_DUP_ENTRY') return res.status(400).json({ error: 'Seriennummer bereits vergeben' }); - console.error('POST geraete:', e); + console.error('POST geräte:', e); res.status(500).json({ error: 'Serverfehler' }); } }); @@ -88,7 +88,7 @@ router.put('/:id', [ [seriennummer, typ, bezeichnung || null, kunde_id || null, status || 'aktiv', notiz || null, req.params.id, req.user.tenant_id] ); - if (r.affectedRows === 0) return res.status(404).json({ error: 'Geraet nicht gefunden' }); + if (r.affectedRows === 0) return res.status(404).json({ error: 'Gerät nicht gefunden' }); const [rows] = await pool.query('SELECT * FROM geraete WHERE id=? AND tenant_id=?', [req.params.id, req.user.tenant_id]); res.json(rows[0]); @@ -102,8 +102,8 @@ router.delete('/:id', async (req, res) => { try { const [r] = await pool.query('DELETE FROM geraete WHERE id=? AND tenant_id=?', [req.params.id, req.user.tenant_id]); - if (r.affectedRows === 0) return res.status(404).json({ error: 'Geraet nicht gefunden' }); - res.json({ message: 'Geraet geloescht' }); + if (r.affectedRows === 0) return res.status(404).json({ error: 'Gerät nicht gefunden' }); + res.json({ message: 'Gerät gelöscht' }); } catch (e) { res.status(500).json({ error: 'Serverfehler' }); } }); diff --git a/backend/routes/kunden-bilder.js b/backend/routes/kunden-bilder.js index ad55a76..065e9e9 100644 --- a/backend/routes/kunden-bilder.js +++ b/backend/routes/kunden-bilder.js @@ -51,7 +51,7 @@ router.patch('/bild/:id', async (req, res) => { const values = []; if (req.body.beschreibung !== undefined) { updates.push('beschreibung = ?'); values.push(req.body.beschreibung); } if (req.body.sortierung !== undefined) { updates.push('sortierung = ?'); values.push(parseInt(req.body.sortierung)); } - if (updates.length === 0) return res.status(400).json({ error: 'Keine Aenderungen' }); + if (updates.length === 0) return res.status(400).json({ error: 'Keine Änderungen' }); values.push(req.params.id, req.user.tenant_id); try { const [r] = await pool.query( @@ -73,11 +73,11 @@ router.delete('/bild/:id', async (req, res) => { if (rows.length === 0) return res.status(404).json({ error: 'Bild nicht gefunden' }); await pool.query('DELETE FROM kunden_bilder WHERE id = ? AND tenant_id = ?', [req.params.id, req.user.tenant_id]); - // Datei loeschen (best effort) + // Datei löschen (best effort) const filename = rows[0].dateiname.replace(/^\/uploads\//, ''); const fullPath = path.join(process.env.UPLOAD_DIR || '/app/uploads', filename); try { fs.unlinkSync(fullPath); } catch {} - res.json({ message: 'Bild geloescht' }); + res.json({ message: 'Bild gelöscht' }); } catch (e) { res.status(500).json({ error: 'Serverfehler' }); } }); diff --git a/backend/routes/kunden.js b/backend/routes/kunden.js index 8a179c8..233b61b 100644 --- a/backend/routes/kunden.js +++ b/backend/routes/kunden.js @@ -93,7 +93,7 @@ router.delete('/:id', async (req, res) => { const [r] = await pool.query('DELETE FROM kunden WHERE id=? AND tenant_id=?', [req.params.id, req.user.tenant_id]); if (r.affectedRows === 0) return res.status(404).json({ error: 'Kunde nicht gefunden' }); - res.json({ message: 'Kunde geloescht' }); + res.json({ message: 'Kunde gelöscht' }); } catch (e) { res.status(500).json({ error: 'Serverfehler' }); } }); diff --git a/backend/routes/lieferungen.js b/backend/routes/lieferungen.js index 1ed27f7..e8bebdb 100644 --- a/backend/routes/lieferungen.js +++ b/backend/routes/lieferungen.js @@ -70,7 +70,7 @@ router.delete('/:id', async (req, res) => { const [r] = await pool.query('DELETE FROM lieferungen WHERE id=? AND tenant_id=?', [req.params.id, req.user.tenant_id]); if (r.affectedRows === 0) return res.status(404).json({ error: 'Lieferung nicht gefunden' }); - res.json({ message: 'Lieferung geloescht' }); + res.json({ message: 'Lieferung gelöscht' }); } catch (e) { res.status(500).json({ error: 'Serverfehler' }); } }); diff --git a/backend/routes/mitarbeiter.js b/backend/routes/mitarbeiter.js index b08808a..7110c6c 100644 --- a/backend/routes/mitarbeiter.js +++ b/backend/routes/mitarbeiter.js @@ -27,7 +27,7 @@ router.post('/', requireRole(['admin']), [ const errors = validationResult(req); if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() }); try { - // Pruefe Limit + // Prüfe Limit const [[t]] = await pool.query('SELECT max_users FROM tenants WHERE id = ?', [req.user.tenant_id]); const [[c]] = await pool.query('SELECT COUNT(*) AS n FROM mitarbeiter WHERE tenant_id = ?', [req.user.tenant_id]); @@ -64,7 +64,7 @@ router.put('/:id', [ return res.status(403).json({ error: 'Keine Berechtigung' }); } if ((req.body.rolle || req.body.aktiv !== undefined) && req.user.rolle !== 'admin') { - return res.status(403).json({ error: 'Nur Admin darf Rolle/Aktiv aendern' }); + return res.status(403).json({ error: 'Nur Admin darf Rolle/Aktiv ändern' }); } const updates = []; @@ -72,7 +72,7 @@ router.put('/:id', [ if (req.body.name) { updates.push('name = ?'); values.push(req.body.name); } if (req.body.rolle) { updates.push('rolle = ?'); values.push(req.body.rolle); } if (req.body.aktiv !== undefined) { updates.push('aktiv = ?'); values.push(req.body.aktiv ? 1 : 0); } - if (updates.length === 0) return res.status(400).json({ error: 'Keine Aenderungen' }); + if (updates.length === 0) return res.status(400).json({ error: 'Keine Änderungen' }); values.push(req.params.id, req.user.tenant_id); try { @@ -90,12 +90,12 @@ router.put('/:id', [ }); router.delete('/:id', requireRole(['admin']), async (req, res) => { - if (req.params.id === req.user.id) return res.status(400).json({ error: 'Sie koennen sich nicht selbst loeschen' }); + if (req.params.id === req.user.id) return res.status(400).json({ error: 'Sie koennen sich nicht selbst löschen' }); try { const [r] = await pool.query('DELETE FROM mitarbeiter WHERE id=? AND tenant_id=?', [req.params.id, req.user.tenant_id]); if (r.affectedRows === 0) return res.status(404).json({ error: 'Mitarbeiter nicht gefunden' }); - res.json({ message: 'Mitarbeiter geloescht' }); + res.json({ message: 'Mitarbeiter gelöscht' }); } catch (e) { res.status(500).json({ error: 'Serverfehler' }); } }); diff --git a/backend/routes/ruecknahmen.js b/backend/routes/ruecknahmen.js index d4b2bab..d5775d0 100644 --- a/backend/routes/ruecknahmen.js +++ b/backend/routes/ruecknahmen.js @@ -59,7 +59,7 @@ router.post('/', upload.single('foto'), [ const [rows] = await pool.query(`${baseSelect} WHERE r.id = ?`, [result.insertId]); res.status(201).json(rows[0]); } catch (e) { - console.error('POST ruecknahmen:', e); + console.error('POST rücknahmen:', e); res.status(500).json({ error: 'Serverfehler' }); } }); @@ -68,8 +68,8 @@ router.delete('/:id', async (req, res) => { try { const [r] = await pool.query('DELETE FROM ruecknahmen WHERE id=? AND tenant_id=?', [req.params.id, req.user.tenant_id]); - if (r.affectedRows === 0) return res.status(404).json({ error: 'Ruecknahme nicht gefunden' }); - res.json({ message: 'Ruecknahme geloescht' }); + if (r.affectedRows === 0) return res.status(404).json({ error: 'Rücknahme nicht gefunden' }); + res.json({ message: 'Rücknahme gelöscht' }); } catch (e) { res.status(500).json({ error: 'Serverfehler' }); } }); diff --git a/backend/routes/tenants.js b/backend/routes/tenants.js index ef1e32f..763be8c 100644 --- a/backend/routes/tenants.js +++ b/backend/routes/tenants.js @@ -89,7 +89,7 @@ router.post('/', [ res.status(201).json({ tenant_id: tenantId, slug, admin_email: kontakt_email, - // Passwort wird ausnahmsweise im Response zurueckgegeben, damit der SuperAdmin + // Passwort wird ausnahmsweise im Response zurückgegeben, damit der SuperAdmin // es ggf. manuell weitergeben kann, falls die Mail nicht ankommt. admin_password_oneshot: plainPw, demo_expires_in_days: days @@ -103,7 +103,7 @@ router.post('/', [ } }); -// PATCH /api/tenants/:id -> Demo verlaengern, Status aendern, Limits aendern +// PATCH /api/tenants/:id -> Demo verlaengern, Status ändern, Limits ändern router.patch('/:id', async (req, res) => { const updates = []; const values = []; @@ -115,7 +115,7 @@ router.patch('/:id', async (req, res) => { if (req.body.status) { updates.push('status = ?'); values.push(req.body.status); } if (req.body.max_users) { updates.push('max_users = ?'); values.push(parseInt(req.body.max_users)); } if (req.body.typ) { updates.push('typ = ?'); values.push(req.body.typ); } - if (updates.length === 0) return res.status(400).json({ error: 'Keine Aenderungen' }); + if (updates.length === 0) return res.status(400).json({ error: 'Keine Änderungen' }); values.push(req.params.id); try { await pool.query(`UPDATE tenants SET ${updates.join(', ')} WHERE id = ?`, values); @@ -127,11 +127,11 @@ router.patch('/:id', async (req, res) => { } }); -// DELETE /api/tenants/:id -> Tenant inkl. aller Daten loeschen (CASCADE) +// DELETE /api/tenants/:id -> Tenant inkl. aller Daten löschen (CASCADE) router.delete('/:id', async (req, res) => { try { await pool.query('DELETE FROM tenants WHERE id = ?', [req.params.id]); - res.json({ message: 'Tenant geloescht' }); + res.json({ message: 'Tenant gelöscht' }); } catch (e) { res.status(500).json({ error: 'Serverfehler' }); } diff --git a/backend/server.js b/backend/server.js index 2e7a050..876ae13 100644 --- a/backend/server.js +++ b/backend/server.js @@ -44,7 +44,7 @@ app.use('/api/geraete', geraeteRoutes); app.use((err, req, res, next) => { console.error('Fehler:', err); if (err.name === 'MulterError') { - if (err.code === 'LIMIT_FILE_SIZE') return res.status(400).json({ error: 'Datei zu gross (max. 10MB)' }); + if (err.code === 'LIMIT_FILE_SIZE') return res.status(400).json({ error: 'Datei zu groß (max. 10MB)' }); return res.status(400).json({ error: err.message }); } res.status(err.status || 500).json({ error: err.message || 'Interner Serverfehler' }); diff --git a/nginx.conf b/nginx.conf index 8dbb5e1..9a9382d 100644 --- a/nginx.conf +++ b/nginx.conf @@ -7,7 +7,6 @@ http { sendfile on; keepalive_timeout 65; - # gzip gzip on; gzip_vary on; gzip_min_length 1024; @@ -19,8 +18,8 @@ http { root /usr/share/nginx/html; index index.html; - # API → Backend - location /api/ { + # API -> Backend (^~ damit Regex-Locations darunter nicht greifen) + location ^~ /api/ { proxy_pass http://backend:3001/api/; proxy_http_version 1.1; proxy_set_header Host $host; @@ -29,23 +28,24 @@ http { proxy_set_header X-Forwarded-Proto $scheme; } - # Uploads → Backend - location /uploads/ { + # Uploads -> Backend (^~ wichtig, sonst greift die .jpg-Regex-Location unten) + location ^~ /uploads/ { proxy_pass http://backend:3001/uploads/; proxy_http_version 1.1; proxy_set_header Host $host; + add_header Cache-Control "public, max-age=86400"; + } + + # Cache fuer statische Assets im SPA + location ~* \.(?:js|css|woff2?|ttf|eot|jpg|jpeg|png|gif|svg|ico|webp)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + try_files $uri =404; } # SPA fallback location / { try_files $uri $uri/ /index.html; } - - # Cache statische Assets - location ~* \.(?:js|css|woff2?|ttf|eot|jpg|jpeg|png|gif|svg|ico|webp)$ { - expires 1y; - add_header Cache-Control "public, immutable"; - try_files $uri =404; - } } } diff --git a/src/._App.jsx b/src/._App.jsx deleted file mode 100644 index 988359c..0000000 Binary files a/src/._App.jsx and /dev/null differ diff --git a/src/._api b/src/._api deleted file mode 100755 index 988359c..0000000 Binary files a/src/._api and /dev/null differ diff --git a/src/._components b/src/._components deleted file mode 100755 index 988359c..0000000 Binary files a/src/._components and /dev/null differ diff --git a/src/._index.css b/src/._index.css deleted file mode 100644 index 988359c..0000000 Binary files a/src/._index.css and /dev/null differ diff --git a/src/._main.jsx b/src/._main.jsx deleted file mode 100644 index 988359c..0000000 Binary files a/src/._main.jsx and /dev/null differ diff --git a/src/api/._client.js b/src/api/._client.js deleted file mode 100644 index 988359c..0000000 Binary files a/src/api/._client.js and /dev/null differ diff --git a/src/api/client.js b/src/api/client.js index 6bb4846..a460fe2 100644 --- a/src/api/client.js +++ b/src/api/client.js @@ -17,7 +17,7 @@ class ApiClient { const res = await fetch(`${API_BASE_URL}${endpoint}`, { ...options, headers }); const text = await res.text(); let data = null; - try { data = text ? JSON.parse(text) : null; } catch { throw new Error('Ungueltige Server-Antwort'); } + try { data = text ? JSON.parse(text) : null; } catch { throw new Error('Ungültige Server-Antwort'); } if (!res.ok) throw new Error(data?.error || `HTTP ${res.status}`); return data; } @@ -89,7 +89,7 @@ class ApiClient { } deleteLieferung(id) { return this.request(`/lieferungen/${id}`, { method: 'DELETE' }); } - // Ruecknahmen + // Rücknahmen getRuecknahmen() { return this.request('/ruecknahmen'); } async createRuecknahme(r, foto) { const fd = new FormData(); @@ -113,7 +113,7 @@ class ApiClient { updateMitarbeiter(id, m) { return this.request(`/mitarbeiter/${id}`, { method: 'PUT', body: JSON.stringify(m) }); } deleteMitarbeiter(id) { return this.request(`/mitarbeiter/${id}`, { method: 'DELETE' }); } - // Geraete + // Geräte getGeraete() { return this.request('/geraete'); } createGeraet(g) { return this.request('/geraete', { method: 'POST', body: JSON.stringify(g) }); } updateGeraet(id, g) { return this.request(`/geraete/${id}`, { method: 'PUT', body: JSON.stringify(g) }); } diff --git a/src/components/._Auth b/src/components/._Auth deleted file mode 100755 index 988359c..0000000 Binary files a/src/components/._Auth and /dev/null differ diff --git a/src/components/._Dashboard b/src/components/._Dashboard deleted file mode 100755 index 988359c..0000000 Binary files a/src/components/._Dashboard and /dev/null differ diff --git a/src/components/._Eingabe b/src/components/._Eingabe deleted file mode 100755 index 988359c..0000000 Binary files a/src/components/._Eingabe and /dev/null differ diff --git a/src/components/._Geraete.jsx b/src/components/._Geraete.jsx deleted file mode 100644 index 988359c..0000000 Binary files a/src/components/._Geraete.jsx and /dev/null differ diff --git a/src/components/._KundenVerwaltung.jsx b/src/components/._KundenVerwaltung.jsx deleted file mode 100644 index 988359c..0000000 Binary files a/src/components/._KundenVerwaltung.jsx and /dev/null differ diff --git a/src/components/._MitarbeiterVerwaltung.jsx b/src/components/._MitarbeiterVerwaltung.jsx deleted file mode 100644 index 988359c..0000000 Binary files a/src/components/._MitarbeiterVerwaltung.jsx and /dev/null differ diff --git a/src/components/._SuperAdmin.jsx b/src/components/._SuperAdmin.jsx deleted file mode 100644 index 988359c..0000000 Binary files a/src/components/._SuperAdmin.jsx and /dev/null differ diff --git a/src/components/._TourPlanung.jsx b/src/components/._TourPlanung.jsx deleted file mode 100644 index 988359c..0000000 Binary files a/src/components/._TourPlanung.jsx and /dev/null differ diff --git a/src/components/Auth/._Login.jsx b/src/components/Auth/._Login.jsx deleted file mode 100644 index 988359c..0000000 Binary files a/src/components/Auth/._Login.jsx and /dev/null differ diff --git a/src/components/Auth/._PasswordChange.jsx b/src/components/Auth/._PasswordChange.jsx deleted file mode 100644 index 988359c..0000000 Binary files a/src/components/Auth/._PasswordChange.jsx and /dev/null differ diff --git a/src/components/Auth/PasswordChange.jsx b/src/components/Auth/PasswordChange.jsx index 1ee4823..9db8efe 100644 --- a/src/components/Auth/PasswordChange.jsx +++ b/src/components/Auth/PasswordChange.jsx @@ -11,18 +11,18 @@ export default function PasswordChange() { const submit = async (e) => { e.preventDefault(); if (form.newPassword.length < 6) return setErr('Neues Passwort min. 6 Zeichen'); - if (form.newPassword !== form.confirm) return setErr('Passwoerter stimmen nicht ueberein'); + if (form.newPassword !== form.confirm) return setErr('Passwoerter stimmen nicht überein'); setBusy(true); try { await api.changePassword(form.currentPassword, form.newPassword); - setOk('Passwort geaendert'); + setOk('Passwort geändert'); setForm({ currentPassword: '', newPassword: '', confirm: '' }); } catch (e) { setErr(e.message); } finally { setBusy(false); } }; return (
-

Passwort aendern

+

Passwort ändern

{err &&
{err}
} {ok &&
{ok}
}
@@ -34,10 +34,10 @@ export default function PasswordChange() {
- +
- +
); } diff --git a/src/components/Dashboard/._Account.jsx b/src/components/Dashboard/._Account.jsx deleted file mode 100644 index 988359c..0000000 Binary files a/src/components/Dashboard/._Account.jsx and /dev/null differ diff --git a/src/components/Dashboard/._AdminDashboard.jsx b/src/components/Dashboard/._AdminDashboard.jsx deleted file mode 100644 index 988359c..0000000 Binary files a/src/components/Dashboard/._AdminDashboard.jsx and /dev/null differ diff --git a/src/components/Dashboard/._BildModal.jsx b/src/components/Dashboard/._BildModal.jsx deleted file mode 100644 index 988359c..0000000 Binary files a/src/components/Dashboard/._BildModal.jsx and /dev/null differ diff --git a/src/components/Dashboard/._Dashboard.jsx b/src/components/Dashboard/._Dashboard.jsx deleted file mode 100644 index 988359c..0000000 Binary files a/src/components/Dashboard/._Dashboard.jsx and /dev/null differ diff --git a/src/components/Dashboard/._TopNav.jsx b/src/components/Dashboard/._TopNav.jsx deleted file mode 100644 index 988359c..0000000 Binary files a/src/components/Dashboard/._TopNav.jsx and /dev/null differ diff --git a/src/components/Dashboard/._VorgangsListe.jsx b/src/components/Dashboard/._VorgangsListe.jsx deleted file mode 100644 index 988359c..0000000 Binary files a/src/components/Dashboard/._VorgangsListe.jsx and /dev/null differ diff --git a/src/components/Dashboard/AdminDashboard.jsx b/src/components/Dashboard/AdminDashboard.jsx index 38a978f..00f1595 100644 --- a/src/components/Dashboard/AdminDashboard.jsx +++ b/src/components/Dashboard/AdminDashboard.jsx @@ -18,7 +18,7 @@ function downloadCSV(rows, filename) { } export default function AdminDashboard() { - const [data, setData] = useState({ kunden: [], artikel: [], mitarbeiter: [], lieferungen: [], ruecknahmen: [] }); + const [data, setData] = useState({ kunden: [], artikel: [], mitarbeiter: [], lieferungen: [], rücknahmen: [] }); const [loading, setLoading] = useState(true); const [selectedKunde, setSelectedKunde] = useState(''); const [selectedMitarbeiter, setSelectedMitarbeiter] = useState(''); @@ -27,7 +27,7 @@ export default function AdminDashboard() { useEffect(() => { Promise.all([api.getKunden(), api.getArtikel(), api.getMitarbeiter(), api.getLieferungen(), api.getRuecknahmen()]) - .then(([k, a, m, l, r]) => setData({ kunden: k, artikel: a, mitarbeiter: m, lieferungen: l, ruecknahmen: r })) + .then(([k, a, m, l, r]) => setData({ kunden: k, artikel: a, mitarbeiter: m, lieferungen: l, rücknahmen: r })) .finally(() => setLoading(false)); }, []); @@ -41,7 +41,7 @@ export default function AdminDashboard() { }; const l = data.lieferungen.filter(x => x.kunde_id === selectedKunde && inRange(x) && (!selectedMitarbeiter || x.mitarbeiter_id === selectedMitarbeiter)); - const r = data.ruecknahmen.filter(x => x.kunde_id === selectedKunde && inRange(x) + const r = data.rücknahmen.filter(x => x.kunde_id === selectedKunde && inRange(x) && (!selectedMitarbeiter || x.mitarbeiter_id === selectedMitarbeiter)); const artikelIds = Array.from(new Set([...l.map(x => x.artikel_id), ...r.map(x => x.artikel_id)])); return artikelIds.map(aid => { @@ -70,7 +70,7 @@ export default function AdminDashboard() {
@@ -125,7 +125,7 @@ export default function AdminDashboard() {
) ) : ( -
Bitte Kunden waehlen, um Auswertung zu sehen.
+
Bitte Kunden wählen, um Auswertung zu sehen.
)} ); diff --git a/src/components/Dashboard/Dashboard.jsx b/src/components/Dashboard/Dashboard.jsx index 90cfa86..cdfb79b 100644 --- a/src/components/Dashboard/Dashboard.jsx +++ b/src/components/Dashboard/Dashboard.jsx @@ -29,9 +29,9 @@ export default function Dashboard({ user, onLogout }) { )} - {activeView === 'ruecknahmen' && ( -
- + {activeView === 'rücknahmen' && ( +
+
)} {activeView === 'tour' && } diff --git a/src/components/Dashboard/TopNav.jsx b/src/components/Dashboard/TopNav.jsx index 3ef2ace..b080bb9 100644 --- a/src/components/Dashboard/TopNav.jsx +++ b/src/components/Dashboard/TopNav.jsx @@ -1,55 +1,66 @@ import { useState, useRef, useEffect } from 'react'; import { FaPlusCircle, FaTruck, FaUndo, FaUsers, FaAddressBook, - FaChartBar, FaBars, FaUserCircle, FaSignOutAlt, FaBoxes, FaMapMarkedAlt + FaChartBar, FaUserCircle, FaSignOutAlt, FaBoxes, FaMapMarkedAlt } from 'react-icons/fa'; const mainItems = [ - { icon: FaPlusCircle, label: 'Vorgang', view: 'vorgang' }, - { icon: FaTruck, label: 'Lieferungen', view: 'lieferungen' }, - { icon: FaUndo, label: 'Ruecknahmen', view: 'ruecknahmen' }, - { icon: FaMapMarkedAlt,label: 'Tour', view: 'tour' }, + { icon: FaPlusCircle, label: 'Vorgang', view: 'vorgang' }, + { icon: FaTruck, label: 'Lieferungen', view: 'lieferungen' }, + { icon: FaUndo, label: 'Rücknahmen', view: 'ruecknahmen' }, + { icon: FaMapMarkedAlt, label: 'Tour', view: 'tour' }, ]; const adminItems = [ - { icon: FaChartBar, label: 'Auswertung', view: 'admin' }, - { icon: FaAddressBook, label: 'Kunden', view: 'kunden' }, - { icon: FaBoxes, label: 'Geraete', view: 'geraete' }, + { icon: FaChartBar, label: 'Auswertung', view: 'admin' }, + { icon: FaAddressBook, label: 'Kunden', view: 'kunden' }, + { icon: FaBoxes, label: 'Geräte', view: 'geraete' }, { icon: FaUsers, label: 'Mitarbeiter', view: 'mitarbeiter' }, ]; export default function TopNav({ user, activeView, setActiveView, onLogout }) { const isAdmin = user.rolle === 'admin'; - const items = mainItems; const [menuOpen, setMenuOpen] = useState(false); const menuRef = useRef(null); useEffect(() => { - const handler = e => { if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false); }; + const handler = (e) => { + if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false); + }; document.addEventListener('mousedown', handler); - return () => document.removeEventListener('mousedown', handler); + document.addEventListener('touchstart', handler); + return () => { + document.removeEventListener('mousedown', handler); + document.removeEventListener('touchstart', handler); + }; }, []); const select = (v) => { setActiveView(v); setMenuOpen(false); }; + const itemBaseCls = "flex w-full items-center gap-3 px-3 py-2.5 text-sm text-slate-700 hover:bg-slate-50 active:bg-slate-100"; + const itemActiveCls = "bg-brand-50 text-brand-900 font-medium"; + return (
-
-
- - Dockly-Pfandsystem +
+ {/* Brand */} +
+ + Dockly-Pfandsystem {user.tenant_typ === 'demo' && ( - Demo + Demo )}
-
); } @@ -104,8 +141,8 @@ export default function TopNav({ user, activeView, setActiveView, onLogout }) { function NavBtn({ icon: Icon, label, active, onClick }) { return ( ); diff --git a/src/components/Dashboard/VorgangsListe.jsx b/src/components/Dashboard/VorgangsListe.jsx index f0d9dff..3447781 100644 --- a/src/components/Dashboard/VorgangsListe.jsx +++ b/src/components/Dashboard/VorgangsListe.jsx @@ -77,7 +77,7 @@ export default function VorgangsListe({ refreshFlag, mode }) {
{item.anzahl}
-
Stueck
+
Stück
))} diff --git a/src/components/Eingabe/._VorgangForm.jsx b/src/components/Eingabe/._VorgangForm.jsx deleted file mode 100644 index 988359c..0000000 Binary files a/src/components/Eingabe/._VorgangForm.jsx and /dev/null differ diff --git a/src/components/Eingabe/VorgangForm.jsx b/src/components/Eingabe/VorgangForm.jsx index 7f1c49e..88dcc8e 100644 --- a/src/components/Eingabe/VorgangForm.jsx +++ b/src/components/Eingabe/VorgangForm.jsx @@ -28,7 +28,7 @@ export default function VorgangForm({ onSubmit }) { }; const submit = async (typ) => { - if (!kundeId || !artikelId) return setMsg('Bitte Kunde und Artikel waehlen'); + if (!kundeId || !artikelId) return setMsg('Bitte Kunde und Artikel wählen'); setBusy(true); setMsg(''); try { const data = { kunde_id: kundeId, artikel_id: artikelId, anzahl: parseInt(anzahl) }; @@ -45,7 +45,7 @@ export default function VorgangForm({ onSubmit }) {
@@ -72,7 +72,7 @@ export default function VorgangForm({ onSubmit }) { setFoto(e.target.files?.[0] || null)} /> @@ -80,7 +80,7 @@ export default function VorgangForm({ onSubmit }) {
- +
{infoOpen && kundenDetail && ( diff --git a/src/components/Geraete.jsx b/src/components/Geraete.jsx index 211e84b..6a3c970 100644 --- a/src/components/Geraete.jsx +++ b/src/components/Geraete.jsx @@ -11,7 +11,7 @@ const STATUS = { const empty = { seriennummer: '', typ: '', bezeichnung: '', kunde_id: '', status: 'aktiv', notiz: '' }; -export default function Geraete() { +export default function Geräte() { const [list, setList] = useState([]); const [kunden, setKunden] = useState([]); const [form, setForm] = useState(empty); @@ -43,7 +43,7 @@ export default function Geraete() { cancel(); setError(''); load(); } catch (e) { setError(e.message); } }; - const del = async (id) => { if (!confirm('Geraet loeschen?')) return; await api.deleteGeraet(id); load(); }; + const del = async (id) => { if (!confirm('Gerät löschen?')) return; await api.deleteGeraet(id); load(); }; const typen = Array.from(new Set(list.map(g => g.typ))); const view = list.filter(g => @@ -53,19 +53,19 @@ export default function Geraete() { return (
-

Geraete

-
{list.length} Geraete
+

Geräte

+
{list.length} Geräte
{error &&
{error}
}
-

{editId ? 'Geraet bearbeiten' : 'Neues Geraet'}

+

{editId ? 'Gerät bearbeiten' : 'Neues Gerät'}

setForm(f => ({ ...f, seriennummer: e.target.value }))} /> - setForm(f => ({ ...f, typ: e.target.value }))} /> @@ -137,7 +137,7 @@ export default function Geraete() { ))} - {view.length === 0 && Keine Geraete.} + {view.length === 0 && Keine Geräte.} )} diff --git a/src/components/KundenVerwaltung.jsx b/src/components/KundenVerwaltung.jsx index 51ca48b..0f8e73c 100644 --- a/src/components/KundenVerwaltung.jsx +++ b/src/components/KundenVerwaltung.jsx @@ -46,7 +46,7 @@ export default function KundenVerwaltung() { }; const del = async (id) => { - if (!confirm('Kunde wirklich loeschen?')) return; + if (!confirm('Kunde wirklich löschen?')) return; try { await api.deleteKunde(id); load(); } catch (e) { setError(e.message); } }; @@ -91,7 +91,7 @@ export default function KundenVerwaltung() {