From 8b7ce448a6dbd7085b9a821d6b26103a9c5cfed2 Mon Sep 17 00:00:00 2001 From: Sascha Wolf Date: Tue, 11 Aug 2026 09:37:24 +0200 Subject: [PATCH] fix(ui): echte Umlaute in allen sichtbaren Texten MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Menüs, Dialoge, Schaltflächen, Platzhalter, Hinweise und die Fehlermeldungen der API standen in Ersatzschreibweise (oe/ae/ue). Sie erscheinen als Toast oder Beschriftung direkt in der Oberfläche und gehören damit richtig geschrieben; sechs sichtbare Texte mit ss statt ß sind gleich mit erledigt ("Schließen", "Straße", "zu groß"). Unangetastet bleibt bewusst: - Kommentare im Quelltext - die sind durchgängig ASCII, und der Diff wäre dreimal so groß ohne Nutzen für den Benutzer - Protokollzeilen (console.*) - die landen im journal, nicht in der App - Datenbankwerte und interne Kennungen: 'uebernommen', 'empfaenger', 'vorschlaege', 'laeuft-bereits' stecken in Enums, SQL und Zustandsvergleichen. Ein Umlaut darin hätte still falsche Ergebnisse geliefert - server/src/mail/extract.js - die Muster decken absichtlich beide Schreibweisen ab: /gr(ü|ue)(ß|ss)e/ - scanner/utils/crypto.js - der Ersatzschlüssel darf sich nicht ändern, sonst sind gespeicherte Portal-Passwörter nicht mehr lesbar Geprüft mit einem Rundgang durch die Ansichten und Dialoge: sichtbarer Text plus title/aria-label/placeholder enthalten keine Ersatzschreibweisen mehr. Co-Authored-By: Claude Opus 5 (1M context) --- server/src/mail/graph.js | 8 +++--- server/src/routes/auth.js | 2 +- server/src/routes/connections.js | 12 ++++---- server/src/routes/events.js | 20 ++++++------- server/src/routes/leads.js | 22 +++++++-------- server/src/routes/mail.js | 28 +++++++++---------- server/src/routes/opportunities.js | 24 ++++++++-------- server/src/routes/scanner.js | 4 +-- server/src/routes/users.js | 12 ++++---- server/src/server.js | 2 +- server/src/userFields.js | 4 +-- src/components/Header.tsx | 4 +-- src/components/Toast.tsx | 2 +- src/components/calendar/Calendar.tsx | 16 +++++------ src/components/calendar/EventDetailModal.tsx | 4 +-- src/components/calendar/EventModal.tsx | 8 +++--- src/components/calendar/UserDetailModal.tsx | 6 ++-- src/components/calendar/calendarUtils.ts | 4 +-- src/components/calendar/mentions.tsx | 4 +-- src/components/leads/LeadGrid.tsx | 2 +- src/components/leads/LeadList.tsx | 4 +-- src/components/leads/LeadModal.tsx | 14 +++++----- src/components/leads/LeadNetwork.tsx | 6 ++-- src/components/leads/Leads.tsx | 8 +++--- src/components/mail/MailLinkDialog.tsx | 2 +- .../opportunities/Opportunities.tsx | 8 +++--- .../opportunities/OpportunityGrid.tsx | 4 +-- .../opportunities/OpportunityList.tsx | 6 ++-- .../opportunities/OpportunityModal.tsx | 14 +++++----- .../opportunities/OpportunityPipeline.tsx | 2 +- src/components/settings/ColorPicker.tsx | 2 +- src/components/settings/Settings.tsx | 12 ++++---- src/components/settings/UserManagement.tsx | 12 ++++---- src/components/ui/AvatarPicker.tsx | 4 +-- src/components/ui/DictationField.tsx | 6 ++-- src/components/ui/Modal.tsx | 4 +-- src/components/whisper/WhisperSettings.tsx | 28 +++++++++---------- src/lib/api.ts | 2 +- src/lib/dictation.tsx | 2 +- src/lib/imageCompress.ts | 2 +- src/lib/whisper.tsx | 4 +-- src/lib/whisper.worker.ts | 2 +- 42 files changed, 168 insertions(+), 168 deletions(-) diff --git a/server/src/mail/graph.js b/server/src/mail/graph.js index 6e37431..c69b417 100644 --- a/server/src/mail/graph.js +++ b/server/src/mail/graph.js @@ -71,7 +71,7 @@ async function fetchAccessToken() { } if (!data?.access_token) { - throw new GraphError('Anmeldung lieferte kein Token zurueck.', { status: response.status }); + throw new GraphError('Anmeldung lieferte kein Token zurück.', { status: response.status }); } return data; @@ -189,7 +189,7 @@ export async function testConnection() { ok: false, stage: 'token', error: err.message, - hint: 'Tenant-ID, Client-ID und Client-Secret in der .env pruefen.', + hint: 'Tenant-ID, Client-ID und Client-Secret in der .env prüfen.', }; } @@ -211,8 +211,8 @@ export async function testConnection() { err.code === 'ErrorAccessDenied' || err.status === 403 ? 'Zugriff verweigert: Anwendungsberechtigung Mail.Read erteilt und Administratorzustimmung gegeben? Erlaubt die ApplicationAccessPolicy dieses Postfach?' : err.code === 'ResourceNotFound' || err.status === 404 - ? `Postfach ${MAILBOX} wurde nicht gefunden - Adresse pruefen.` - : 'Berechtigungen und Zugriffsrichtlinie in Exchange Online pruefen.'; + ? `Postfach ${MAILBOX} wurde nicht gefunden - Adresse prüfen.` + : 'Berechtigungen und Zugriffsrichtlinie in Exchange Online prüfen.'; return { ok: false, stage: 'mailbox', error: err.message, code: err.code ?? null, hint }; } } diff --git a/server/src/routes/auth.js b/server/src/routes/auth.js index 13c64a2..f45fa03 100644 --- a/server/src/routes/auth.js +++ b/server/src/routes/auth.js @@ -81,7 +81,7 @@ router.patch('/me', requireAuth, async (req, res, next) => { } if (fields.length === 0) { - return res.status(400).json({ error: 'Keine Aenderungen uebergeben.' }); + return res.status(400).json({ error: 'Keine Änderungen übergeben.' }); } try { diff --git a/server/src/routes/connections.js b/server/src/routes/connections.js index 4b30d95..51b77ac 100644 --- a/server/src/routes/connections.js +++ b/server/src/routes/connections.js @@ -51,7 +51,7 @@ router.post('/', async (req, res, next) => { const toId = Number(req.body?.to_lead_id); if (!Number.isInteger(fromId) || !Number.isInteger(toId)) { - return res.status(400).json({ error: 'Beide Kontakte muessen angegeben werden.' }); + return res.status(400).json({ error: 'Beide Kontakte müssen angegeben werden.' }); } if (fromId === toId) { return res.status(400).json({ error: 'Ein Kontakt kann nicht mit sich selbst verbunden werden.' }); @@ -90,13 +90,13 @@ router.post('/', async (req, res, next) => { // PATCH /api/connections/:id router.patch('/:id', async (req, res, next) => { const id = Number(req.params.id); - if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Verbindungs-ID.' }); + if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Verbindungs-ID.' }); const fields = []; const values = []; if (req.body?.type !== undefined) { - if (!TYPES.has(req.body.type)) return res.status(400).json({ error: 'Ungueltiger Verbindungstyp.' }); + if (!TYPES.has(req.body.type)) return res.status(400).json({ error: 'Ungültiger Verbindungstyp.' }); fields.push('type = ?'); values.push(req.body.type); } @@ -107,13 +107,13 @@ router.patch('/:id', async (req, res, next) => { if (req.body?.strength !== undefined) { const strength = Number(req.body.strength); if (![1, 2, 3].includes(strength)) { - return res.status(400).json({ error: 'Verbindungsstaerke muss 1, 2 oder 3 sein.' }); + return res.status(400).json({ error: 'Verbindungsstärke muss 1, 2 oder 3 sein.' }); } fields.push('strength = ?'); values.push(strength); } - if (fields.length === 0) return res.status(400).json({ error: 'Keine Aenderungen uebergeben.' }); + if (fields.length === 0) return res.status(400).json({ error: 'Keine Änderungen übergeben.' }); try { values.push(id); @@ -138,7 +138,7 @@ router.patch('/:id', async (req, res, next) => { // DELETE /api/connections/:id router.delete('/:id', async (req, res, next) => { const id = Number(req.params.id); - if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Verbindungs-ID.' }); + if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Verbindungs-ID.' }); try { const [result] = await pool.query('DELETE FROM crm_lead_connections WHERE id = ?', [id]); diff --git a/server/src/routes/events.js b/server/src/routes/events.js index 014a5b4..e5628eb 100644 --- a/server/src/routes/events.js +++ b/server/src/routes/events.js @@ -47,11 +47,11 @@ function emptyToNull(value) { /** Teilnehmer-IDs aus dem Request pruefen und deduplizieren. */ async function normalizeParticipants(raw) { if (raw === undefined) return undefined; - if (!Array.isArray(raw)) return { error: 'Teilnehmer muessen als Liste uebergeben werden.' }; + if (!Array.isArray(raw)) return { error: 'Teilnehmer müssen als Liste übergeben werden.' }; const ids = [...new Set(raw.map((value) => Number(value)))]; if (ids.some((id) => !Number.isInteger(id) || id <= 0)) { - return { error: 'Ungueltige Teilnehmer-ID.' }; + return { error: 'Ungültige Teilnehmer-ID.' }; } if (ids.length === 0) return { ids: [] }; @@ -142,7 +142,7 @@ router.post('/', async (req, res, next) => { const startsAt = normalizeDateTime(req.body?.starts_at); const endsAt = normalizeDateTime(req.body?.ends_at); if (!startsAt || !endsAt) { - return res.status(400).json({ error: 'Start und Ende muessen gueltige Zeitpunkte sein.' }); + return res.status(400).json({ error: 'Start und Ende müssen gültige Zeitpunkte sein.' }); } if (endsAt < startsAt) { return res.status(400).json({ error: 'Das Ende darf nicht vor dem Start liegen.' }); @@ -186,7 +186,7 @@ function mayModify(user, event) { // PATCH /api/events/:id router.patch('/:id', async (req, res, next) => { const id = Number(req.params.id); - if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Termin-ID.' }); + if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Termin-ID.' }); const conn = await pool.getConnection(); try { @@ -196,7 +196,7 @@ router.patch('/:id', async (req, res, next) => { if (!mayModify(req.user, existing)) { return res .status(403) - .json({ error: 'Diesen Termin darf nur der Ersteller oder ein Administrator aendern.' }); + .json({ error: 'Diesen Termin darf nur der Ersteller oder ein Administrator ändern.' }); } const fields = []; @@ -217,7 +217,7 @@ router.patch('/:id', async (req, res, next) => { let startsAt = existing.starts_at; if (req.body?.starts_at !== undefined) { startsAt = normalizeDateTime(req.body.starts_at); - if (!startsAt) return res.status(400).json({ error: 'Ungueltiger Startzeitpunkt.' }); + if (!startsAt) return res.status(400).json({ error: 'Ungültiger Startzeitpunkt.' }); fields.push('starts_at = ?'); values.push(startsAt); } @@ -225,7 +225,7 @@ router.patch('/:id', async (req, res, next) => { let endsAt = existing.ends_at; if (req.body?.ends_at !== undefined) { endsAt = normalizeDateTime(req.body.ends_at); - if (!endsAt) return res.status(400).json({ error: 'Ungueltiger Endzeitpunkt.' }); + if (!endsAt) return res.status(400).json({ error: 'Ungültiger Endzeitpunkt.' }); fields.push('ends_at = ?'); values.push(endsAt); } @@ -243,7 +243,7 @@ router.patch('/:id', async (req, res, next) => { if (participants?.error) return res.status(400).json({ error: participants.error }); if (fields.length === 0 && !participants) { - return res.status(400).json({ error: 'Keine Aenderungen uebergeben.' }); + return res.status(400).json({ error: 'Keine Änderungen übergeben.' }); } await conn.beginTransaction(); @@ -265,7 +265,7 @@ router.patch('/:id', async (req, res, next) => { // DELETE /api/events/:id - Teilnehmer entfernt die DB per ON DELETE CASCADE router.delete('/:id', async (req, res, next) => { const id = Number(req.params.id); - if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Termin-ID.' }); + if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Termin-ID.' }); try { const [rows] = await pool.query('SELECT created_by FROM crm_events WHERE id = ? LIMIT 1', [id]); @@ -274,7 +274,7 @@ router.delete('/:id', async (req, res, next) => { if (!mayModify(req.user, existing)) { return res .status(403) - .json({ error: 'Diesen Termin darf nur der Ersteller oder ein Administrator loeschen.' }); + .json({ error: 'Diesen Termin darf nur der Ersteller oder ein Administrator löschen.' }); } await pool.query('DELETE FROM crm_events WHERE id = ?', [id]); diff --git a/server/src/routes/leads.js b/server/src/routes/leads.js index cd7c640..2002403 100644 --- a/server/src/routes/leads.js +++ b/server/src/routes/leads.js @@ -115,7 +115,7 @@ router.post('/', async (req, res, next) => { : Number(req.body.owner_id); if (Number.isNaN(ownerId) || !(await userExists(ownerId))) { - return res.status(400).json({ error: 'Zustaendiger Benutzer existiert nicht.' }); + return res.status(400).json({ error: 'Zuständiger Benutzer existiert nicht.' }); } const columns = [...TEXT_FIELDS, 'status', 'tags', 'owner_id', 'created_by']; @@ -283,7 +283,7 @@ router.get('/mail-pool/sources', async (req, res, next) => { */ router.post('/mail-pool/promote', async (req, res, next) => { const ids = Array.isArray(req.body?.ids) ? req.body.ids.map(Number).filter(Number.isInteger) : []; - if (ids.length === 0) return res.status(400).json({ error: 'Keine Eintraege uebergeben.' }); + if (ids.length === 0) return res.status(400).json({ error: 'Keine Einträge übergeben.' }); const keepPool = req.body?.keep_pool === true; @@ -323,7 +323,7 @@ router.post('/mail-pool/promote', async (req, res, next) => { */ router.post('/:id/promote', async (req, res, next) => { const id = Number(req.params.id); - if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Lead-ID.' }); + if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Lead-ID.' }); const keepPool = req.body?.keep_pool === true; const targetLeadId = @@ -331,7 +331,7 @@ router.post('/:id/promote', async (req, res, next) => { ? null : Number(req.body.target_lead_id); if (targetLeadId !== null && !Number.isInteger(targetLeadId)) { - return res.status(400).json({ error: 'Ungueltige Ziel-Kontakt-ID.' }); + return res.status(400).json({ error: 'Ungültige Ziel-Kontakt-ID.' }); } try { @@ -345,7 +345,7 @@ router.post('/:id/promote', async (req, res, next) => { ? poolLead.owner_id ?? req.user.id : Number(req.body.owner_id); if (!Number.isInteger(ownerId) || !(await userExists(ownerId))) { - return res.status(400).json({ error: 'Zustaendiger Benutzer existiert nicht.' }); + return res.status(400).json({ error: 'Zuständiger Benutzer existiert nicht.' }); } const leadId = await copyPoolLeadToContacts(poolLead, { userId: req.user.id, ownerId }); @@ -403,7 +403,7 @@ router.post('/:id/promote', async (req, res, next) => { // PATCH /api/leads/:id router.patch('/:id', async (req, res, next) => { const id = Number(req.params.id); - if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Lead-ID.' }); + if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Lead-ID.' }); const fields = []; const values = []; @@ -412,14 +412,14 @@ router.patch('/:id', async (req, res, next) => { if (req.body?.[field] === undefined) continue; const value = emptyToNull(req.body[field]); if ((field === 'first_name' || field === 'last_name') && !value) { - return res.status(400).json({ error: 'Vorname und Nachname duerfen nicht leer sein.' }); + return res.status(400).json({ error: 'Vorname und Nachname dürfen nicht leer sein.' }); } fields.push(`${field} = ?`); values.push(value); } if (req.body?.status !== undefined) { - if (!STATUSES.has(req.body.status)) return res.status(400).json({ error: 'Ungueltiger Status.' }); + if (!STATUSES.has(req.body.status)) return res.status(400).json({ error: 'Ungültiger Status.' }); fields.push('status = ?'); values.push(req.body.status); } @@ -433,13 +433,13 @@ router.patch('/:id', async (req, res, next) => { const ownerId = req.body.owner_id === null || req.body.owner_id === '' ? null : Number(req.body.owner_id); if (ownerId !== null && (Number.isNaN(ownerId) || !(await userExists(ownerId)))) { - return res.status(400).json({ error: 'Zustaendiger Benutzer existiert nicht.' }); + return res.status(400).json({ error: 'Zuständiger Benutzer existiert nicht.' }); } fields.push('owner_id = ?'); values.push(ownerId); } - if (fields.length === 0) return res.status(400).json({ error: 'Keine Aenderungen uebergeben.' }); + if (fields.length === 0) return res.status(400).json({ error: 'Keine Änderungen übergeben.' }); try { values.push(id); @@ -457,7 +457,7 @@ router.patch('/:id', async (req, res, next) => { // DELETE /api/leads/:id - Verbindungen entfernt die DB per ON DELETE CASCADE router.delete('/:id', async (req, res, next) => { const id = Number(req.params.id); - if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Lead-ID.' }); + if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Lead-ID.' }); try { const [result] = await pool.query('DELETE FROM crm_leads WHERE id = ?', [id]); diff --git a/server/src/routes/mail.js b/server/src/routes/mail.js index 9665d4a..dd25acd 100644 --- a/server/src/routes/mail.js +++ b/server/src/routes/mail.js @@ -128,7 +128,7 @@ router.post('/settings', requireAdmin, async (req, res, next) => { const allowed = Object.keys(MAIL_SETTING_DEFAULTS); const incoming = Object.entries(req.body ?? {}).filter(([key]) => allowed.includes(key)); if (incoming.length === 0) { - return res.status(400).json({ error: 'Keine bekannten Einstellungen uebergeben.' }); + return res.status(400).json({ error: 'Keine bekannten Einstellungen übergeben.' }); } for (const [key, value] of incoming) { @@ -180,7 +180,7 @@ router.post('/sync', requireAdmin, async (req, res, next) => { return res.status(409).json({ error: `Zugangsdaten fehlen: ${missingCredentials().join(', ')}` }); } if (isMailSyncRunning()) { - return res.status(409).json({ error: 'Ein Abgleich laeuft bereits. Bitte kurz warten.' }); + return res.status(409).json({ error: 'Ein Abgleich läuft bereits. Bitte kurz warten.' }); } // Vollstaendiger Neuaufbau auf Wunsch: Delta-Verweis verwerfen. Vorhandene @@ -188,7 +188,7 @@ router.post('/sync', requireAdmin, async (req, res, next) => { if (req.body?.reset === true) await resetDelta(); const result = await runMailSync({ trigger: 'manuell' }); - if (result.skipped) return res.status(409).json({ error: `Abgleich uebersprungen: ${result.skipped}` }); + if (result.skipped) return res.status(409).json({ error: `Abgleich übersprungen: ${result.skipped}` }); if (!result.ok) return res.status(502).json({ error: result.error, result }); res.json(result); } catch (err) { @@ -259,7 +259,7 @@ router.get('/messages', async (req, res, next) => { // GET /api/mail/messages/:id router.get('/messages/:id', async (req, res, next) => { const id = Number(req.params.id); - if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Mail-ID.' }); + if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Mail-ID.' }); try { const [rows] = await pool.query('SELECT * FROM crm_mail_messages WHERE id = ? LIMIT 1', [id]); @@ -323,7 +323,7 @@ router.get('/messages/:id', async (req, res, next) => { // POST /api/mail/messages/:id/links - Mail an Kontakt und/oder Opportunity haengen router.post('/messages/:id/links', async (req, res, next) => { const id = Number(req.params.id); - if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Mail-ID.' }); + if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Mail-ID.' }); const leadId = req.body?.lead_id === undefined || req.body.lead_id === null ? null : Number(req.body.lead_id); const opportunityId = @@ -335,10 +335,10 @@ router.post('/messages/:id/links', async (req, res, next) => { return res.status(400).json({ error: 'Kontakt oder Opportunity angeben.' }); } if (leadId !== null && !Number.isInteger(leadId)) { - return res.status(400).json({ error: 'Ungueltige Kontakt-ID.' }); + return res.status(400).json({ error: 'Ungültige Kontakt-ID.' }); } if (opportunityId !== null && !Number.isInteger(opportunityId)) { - return res.status(400).json({ error: 'Ungueltige Opportunity-ID.' }); + return res.status(400).json({ error: 'Ungültige Opportunity-ID.' }); } try { @@ -378,7 +378,7 @@ router.post('/messages/:id/links', async (req, res, next) => { } if (created.length === 0) { - return res.status(409).json({ error: 'Diese Verknuepfung besteht bereits.' }); + return res.status(409).json({ error: 'Diese Verknüpfung besteht bereits.' }); } res.status(201).json({ ok: true, created }); @@ -390,11 +390,11 @@ router.post('/messages/:id/links', async (req, res, next) => { // DELETE /api/mail/links/:id router.delete('/links/:id', async (req, res, next) => { const id = Number(req.params.id); - if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Verknuepfungs-ID.' }); + if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Verknüpfungs-ID.' }); try { const [result] = await pool.query('DELETE FROM crm_mail_links WHERE id = ?', [id]); - if (result.affectedRows === 0) return res.status(404).json({ error: 'Verknuepfung nicht gefunden.' }); + if (result.affectedRows === 0) return res.status(404).json({ error: 'Verknüpfung nicht gefunden.' }); res.json({ ok: true }); } catch (err) { next(err); @@ -416,7 +416,7 @@ router.get('/candidates', async (req, res, next) => { const where = []; const params = []; if (status !== 'alle') { - if (!CANDIDATE_STATUSES.has(status)) return res.status(400).json({ error: 'Ungueltiger Status.' }); + if (!CANDIDATE_STATUSES.has(status)) return res.status(400).json({ error: 'Ungültiger Status.' }); where.push('c.status = ?'); params.push(status); } @@ -465,7 +465,7 @@ async function loadCandidate(id) { // POST /api/mail/candidates/:id/adopt - Vorschlag zum Kontakt machen router.post('/candidates/:id/adopt', async (req, res, next) => { const id = Number(req.params.id); - if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Vorschlags-ID.' }); + if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Vorschlags-ID.' }); try { const candidate = await loadCandidate(id); @@ -479,7 +479,7 @@ router.post('/candidates/:id/adopt', async (req, res, next) => { req.body?.owner_id === undefined || req.body.owner_id === null ? req.user.id : Number(req.body.owner_id); - if (!Number.isInteger(ownerId)) return res.status(400).json({ error: 'Ungueltige Benutzer-ID.' }); + if (!Number.isInteger(ownerId)) return res.status(400).json({ error: 'Ungültige Benutzer-ID.' }); /* * Von Hand uebernommen heisst: richtiger Kontakt. Der Vorbereich "Aus Mails" @@ -505,7 +505,7 @@ router.post('/candidates/:id/adopt', async (req, res, next) => { // POST /api/mail/candidates/:id/reject router.post('/candidates/:id/reject', async (req, res, next) => { const id = Number(req.params.id); - if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Vorschlags-ID.' }); + if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Vorschlags-ID.' }); try { const candidate = await loadCandidate(id); diff --git a/server/src/routes/opportunities.js b/server/src/routes/opportunities.js index d0b45a7..d8ff72d 100644 --- a/server/src/routes/opportunities.js +++ b/server/src/routes/opportunities.js @@ -28,7 +28,7 @@ function emptyToNull(value) { function normalizeDate(value) { if (value === null || value === undefined || String(value).trim() === '') return { value: null }; const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(value).trim()); - if (!match) return { error: 'Ungueltiges Datum (erwartet JJJJ-MM-TT).' }; + if (!match) return { error: 'Ungültiges Datum (erwartet JJJJ-MM-TT).' }; const [, year, month, day] = match; const probe = new Date(Number(year), Number(month) - 1, Number(day)); @@ -140,13 +140,13 @@ async function userExists(id) { /** Prueft die Ansprechpartner-Liste `[{ lead_id, role }]`. */ async function normalizeContacts(raw) { if (raw === undefined) return undefined; - if (!Array.isArray(raw)) return { error: 'Ansprechpartner muessen als Liste uebergeben werden.' }; + if (!Array.isArray(raw)) return { error: 'Ansprechpartner müssen als Liste übergeben werden.' }; const seen = new Map(); for (const entry of raw) { const leadId = Number(entry?.lead_id ?? entry); if (!Number.isInteger(leadId) || leadId <= 0) { - return { error: 'Ungueltige Kontakt-ID bei den Ansprechpartnern.' }; + return { error: 'Ungültige Kontakt-ID bei den Ansprechpartnern.' }; } const role = emptyToNull(entry?.role); if (role !== null && role.length > 120) { @@ -209,7 +209,7 @@ router.post('/', async (req, res, next) => { // etwas anderes gesetzt. const stageGiven = req.body?.stage !== undefined && req.body.stage !== null && req.body.stage !== ''; if (stageGiven && !STAGES.has(req.body.stage)) { - return res.status(400).json({ error: 'Ungueltige Phase.' }); + return res.status(400).json({ error: 'Ungültige Phase.' }); } const stage = stageGiven ? req.body.stage : 'Neu'; @@ -241,7 +241,7 @@ router.post('/', async (req, res, next) => { ? req.user.id : Number(req.body.owner_id); if (Number.isNaN(ownerId) || !(await userExists(ownerId))) { - return res.status(400).json({ error: 'Zustaendiger Benutzer existiert nicht.' }); + return res.status(400).json({ error: 'Zuständiger Benutzer existiert nicht.' }); } const contacts = await normalizeContacts(req.body?.contacts ?? []); @@ -289,7 +289,7 @@ router.post('/', async (req, res, next) => { // PATCH /api/opportunities/:id router.patch('/:id', async (req, res, next) => { const id = Number(req.params.id); - if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Opportunity-ID.' }); + if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Opportunity-ID.' }); const conn = await pool.getConnection(); try { @@ -301,7 +301,7 @@ router.patch('/:id', async (req, res, next) => { if (!existing) return res.status(404).json({ error: 'Opportunity nicht gefunden.' }); if (!mayModify(req.user, existing)) { return res.status(403).json({ - error: 'Diese Opportunity darf nur der Ersteller, der Zustaendige oder ein Administrator aendern.', + error: 'Diese Opportunity darf nur der Ersteller, der Zuständige oder ein Administrator ändern.', }); } @@ -319,7 +319,7 @@ router.patch('/:id', async (req, res, next) => { } if (req.body?.stage !== undefined) { - if (!STAGES.has(req.body.stage)) return res.status(400).json({ error: 'Ungueltige Phase.' }); + if (!STAGES.has(req.body.stage)) return res.status(400).json({ error: 'Ungültige Phase.' }); fields.push('stage = ?'); values.push(req.body.stage); } @@ -367,7 +367,7 @@ router.patch('/:id', async (req, res, next) => { const ownerId = req.body.owner_id === null || req.body.owner_id === '' ? null : Number(req.body.owner_id); if (ownerId !== null && (Number.isNaN(ownerId) || !(await userExists(ownerId)))) { - return res.status(400).json({ error: 'Zustaendiger Benutzer existiert nicht.' }); + return res.status(400).json({ error: 'Zuständiger Benutzer existiert nicht.' }); } fields.push('owner_id = ?'); values.push(ownerId); @@ -377,7 +377,7 @@ router.patch('/:id', async (req, res, next) => { if (contacts?.error) return res.status(400).json({ error: contacts.error }); if (fields.length === 0 && !contacts) { - return res.status(400).json({ error: 'Keine Aenderungen uebergeben.' }); + return res.status(400).json({ error: 'Keine Änderungen übergeben.' }); } await conn.beginTransaction(); @@ -402,7 +402,7 @@ router.patch('/:id', async (req, res, next) => { // DELETE /api/opportunities/:id - Ansprechpartner raeumt die DB per CASCADE router.delete('/:id', async (req, res, next) => { const id = Number(req.params.id); - if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Opportunity-ID.' }); + if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Opportunity-ID.' }); try { const [rows] = await pool.query( @@ -413,7 +413,7 @@ router.delete('/:id', async (req, res, next) => { if (!existing) return res.status(404).json({ error: 'Opportunity nicht gefunden.' }); if (!mayModify(req.user, existing)) { return res.status(403).json({ - error: 'Diese Opportunity darf nur der Ersteller, der Zustaendige oder ein Administrator loeschen.', + error: 'Diese Opportunity darf nur der Ersteller, der Zuständige oder ein Administrator löschen.', }); } diff --git a/server/src/routes/scanner.js b/server/src/routes/scanner.js index e0207f3..c83a98f 100644 --- a/server/src/routes/scanner.js +++ b/server/src/routes/scanner.js @@ -273,7 +273,7 @@ router.post('/portals/sync-session', async (req, res) => { try { parsedState = typeof sessionState === 'string' ? JSON.parse(sessionState) : sessionState; } catch { - return res.status(400).json({ error: 'Ungueltiges Format - es muss JSON sein.' }); + return res.status(400).json({ error: 'Ungültiges Format - es muss JSON sein.' }); } // Drei Formen sind erlaubt: Playwright storageState, reines Cookie-Array @@ -289,7 +289,7 @@ router.post('/portals/sync-session', async (req, res) => { const cookies = rawCookies.map(normalizeCookie).filter(Boolean); if (cookies.length === 0) { - return res.status(400).json({ error: 'Keine gueltigen Cookies gefunden.' }); + return res.status(400).json({ error: 'Keine gültigen Cookies gefunden.' }); } portal.sessionState = JSON.stringify({ cookies, origins }); diff --git a/server/src/routes/users.js b/server/src/routes/users.js index 384c6e6..22f084f 100644 --- a/server/src/routes/users.js +++ b/server/src/routes/users.js @@ -120,7 +120,7 @@ router.post('/', async (req, res, next) => { // PATCH /api/users/:id - bearbeitet Benutzer inkl. Admin router.patch('/:id', async (req, res, next) => { const id = Number(req.params.id); - if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Benutzer-ID.' }); + if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Benutzer-ID.' }); try { const [existingRows] = await pool.query('SELECT * FROM crm_users WHERE id = ? LIMIT 1', [id]); @@ -156,7 +156,7 @@ router.patch('/:id', async (req, res, next) => { const nextRole = req.body?.role !== undefined ? String(req.body.role) : existing.role; if (req.body?.role !== undefined) { - if (!ROLES.has(nextRole)) return res.status(400).json({ error: 'Ungueltige Rolle.' }); + if (!ROLES.has(nextRole)) return res.status(400).json({ error: 'Ungültige Rolle.' }); fields.push('role = ?'); values.push(nextRole); } @@ -187,7 +187,7 @@ router.patch('/:id', async (req, res, next) => { } if (fields.length === 0) { - return res.status(400).json({ error: 'Keine Aenderungen uebergeben.' }); + return res.status(400).json({ error: 'Keine Änderungen übergeben.' }); } values.push(id); @@ -209,9 +209,9 @@ router.patch('/:id', async (req, res, next) => { // DELETE /api/users/:id router.delete('/:id', async (req, res, next) => { const id = Number(req.params.id); - if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Benutzer-ID.' }); + if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Benutzer-ID.' }); if (id === req.user.id) { - return res.status(400).json({ error: 'Das eigene Konto kann nicht geloescht werden.' }); + return res.status(400).json({ error: 'Das eigene Konto kann nicht gelöscht werden.' }); } try { @@ -224,7 +224,7 @@ router.delete('/:id', async (req, res, next) => { if (target.role === 'admin' && target.is_active && (await countOtherActiveAdmins(id)) === 0) { return res .status(409) - .json({ error: 'Der letzte aktive Administrator kann nicht geloescht werden.' }); + .json({ error: 'Der letzte aktive Administrator kann nicht gelöscht werden.' }); } await pool.query('DELETE FROM crm_users WHERE id = ?', [id]); diff --git a/server/src/server.js b/server/src/server.js index 0c5a6ee..05d70f7 100644 --- a/server/src/server.js +++ b/server/src/server.js @@ -69,7 +69,7 @@ app.use((err, req, res, next) => { // nicht als unbehandelter Serverfehler im Log landen. const status = err?.status || err?.statusCode; if (err?.type === 'entity.parse.failed' || status === 400) { - return res.status(400).json({ error: 'Ungueltige Anfrage (JSON konnte nicht gelesen werden).' }); + return res.status(400).json({ error: 'Ungültige Anfrage (JSON konnte nicht gelesen werden).' }); } if (err?.type === 'entity.too.large' || status === 413) { return res.status(413).json({ error: 'Die Anfrage ist zu gross (max. 6 MB).' }); diff --git a/server/src/userFields.js b/server/src/userFields.js index 784fdce..6432356 100644 --- a/server/src/userFields.js +++ b/server/src/userFields.js @@ -18,7 +18,7 @@ const AVATAR_MAX_LENGTH = 3_000_000; export function normalizeColor(value) { if (value === null || value === undefined || String(value).trim() === '') return { value: null }; const color = String(value).trim().toLowerCase(); - if (!COLOR_RE.test(color)) return { error: 'Ungueltige Farbe (erwartet z. B. #6ba04a).' }; + if (!COLOR_RE.test(color)) return { error: 'Ungültige Farbe (erwartet z. B. #6ba04a).' }; return { value: color }; } @@ -30,7 +30,7 @@ export function normalizeAvatar(value) { return { error: 'Das Foto ist zu gross. Bitte ein kleineres Bild verwenden.' }; } if (!AVATAR_RE.test(avatar)) { - return { error: 'Ungueltiges Foto (erwartet eine Bild-Data-URL oder eine http(s)-Adresse).' }; + return { error: 'Ungültiges Foto (erwartet eine Bild-Data-URL oder eine http(s)-Adresse).' }; } return { value: avatar }; } diff --git a/src/components/Header.tsx b/src/components/Header.tsx index f354da5..f71262a 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -21,8 +21,8 @@ export const Header: React.FC = ({ title, subtitle, onToggleSidebar @@ -927,13 +927,13 @@ export const Calendar: React.FC = ({ {/* Anstehende Termine */}