From 700e677c455849d5cc6cb02ba08f058eaf33ec1e Mon Sep 17 00:00:00 2001 From: Jan Date: Mon, 13 Apr 2026 13:22:34 +0200 Subject: [PATCH] =?UTF-8?q?die=20AG-=C3=9Cbersicht=20bekommt=20die=20gleic?= =?UTF-8?q?he=20Dropdown-Auswahl=20wie=20mobil=20statt=20Horizontal-Scroll?= =?UTF-8?q?,=20und=20ich=20r=C3=A4ume=20die=20Umlaut-Geschichte=20sauber?= =?UTF-8?q?=20auf,=20damit=20wir=20wieder=20normales=20UTF-8=20im=20Code?= =?UTF-8?q?=20haben=20statt=20dieser=20ASCII-Ausweichman=C3=B6ver.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prisma/seed.ts | 4 +- src/app/api/budgets/[id]/route.ts | 2 +- src/app/api/expenses/[id]/approve/route.ts | 2 +- src/app/api/expenses/[id]/route.ts | 2 +- src/app/api/expenses/route.ts | 2 +- src/app/api/import/csv/route.ts | 14 ++-- src/app/api/periods/[id]/route.ts | 8 +-- src/app/api/periods/current/route.ts | 4 +- src/app/api/periods/route.ts | 2 +- src/app/api/users/[id]/password/route.ts | 2 +- src/app/api/users/[id]/route.ts | 6 +- src/app/api/working-groups/[id]/route.ts | 4 +- src/app/api/working-groups/route.ts | 2 +- src/components/dashboard/budget-column.tsx | 8 +-- src/components/dashboard/dashboard-shell.tsx | 72 +++++++++----------- 15 files changed, 64 insertions(+), 70 deletions(-) diff --git a/prisma/seed.ts b/prisma/seed.ts index 840d94c..744d717 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -181,7 +181,7 @@ async function main() { await prisma.expense.create({ data: { title: "Muster: Kabelbinder", - description: "Beispiel fuer einen automatisch freigegebenen Infrastrukturposten.", + description: "Beispiel für einen automatisch freigegebenen Infrastrukturposten.", amount: 24.5, creatorId: technikUser.id, agId: technik.id, @@ -206,7 +206,7 @@ async function main() { await prisma.expense.create({ data: { title: "Muster: Vereinssoftware Abo", - description: "Monatlich wiederkehrendes Beispielabo fuer die Uebersicht.", + description: "Monatlich wiederkehrendes Beispielabo für die Übersicht.", amount: 19, creatorId: financeUser.id, agId: technik.id, diff --git a/src/app/api/budgets/[id]/route.ts b/src/app/api/budgets/[id]/route.ts index a61ff53..9bfe55d 100644 --- a/src/app/api/budgets/[id]/route.ts +++ b/src/app/api/budgets/[id]/route.ts @@ -99,7 +99,7 @@ export async function DELETE(_: Request, { params }: Context) { } if (!canManageBudgets(viewer.role)) { - return NextResponse.json({ error: "Nur Vorstand oder Finanz-AG duerfen Budgets loeschen." }, { status: 403 }); + return NextResponse.json({ error: "Nur Vorstand oder Finanz-AG dürfen Budgets löschen." }, { status: 403 }); } const budget = await prisma.budget.findUnique({ diff --git a/src/app/api/expenses/[id]/approve/route.ts b/src/app/api/expenses/[id]/approve/route.ts index 64cb66a..30ee78f 100644 --- a/src/app/api/expenses/[id]/approve/route.ts +++ b/src/app/api/expenses/[id]/approve/route.ts @@ -120,7 +120,7 @@ export async function POST(request: Request, { params }: Context) { entityType: "expense", entityId: expense.id, entityLabel: expense.title, - summary: `${parsed.data.approvalType} fuer ${expense.title} wurde gesetzt.`, + summary: `${parsed.data.approvalType} für ${expense.title} wurde gesetzt.`, metadata: { approvalType: parsed.data.approvalType, approvalThreshold, diff --git a/src/app/api/expenses/[id]/route.ts b/src/app/api/expenses/[id]/route.ts index 7de6a5b..17c0240 100644 --- a/src/app/api/expenses/[id]/route.ts +++ b/src/app/api/expenses/[id]/route.ts @@ -35,7 +35,7 @@ export async function DELETE(_: Request, { params }: Context) { if (!isAdminDelete && !isOwnPendingExpense) { return NextResponse.json( - { error: "Du darfst nur eigene ungepruefte Ausgaben loeschen." }, + { error: "Du darfst nur eigene ungeprüfte Ausgaben löschen." }, { status: 403 } ); } diff --git a/src/app/api/expenses/route.ts b/src/app/api/expenses/route.ts index 039858b..3321307 100644 --- a/src/app/api/expenses/route.ts +++ b/src/app/api/expenses/route.ts @@ -33,7 +33,7 @@ export async function POST(request: Request) { const parsed = expenseSchema.safeParse(body); if (!parsed.success) { - return NextResponse.json({ error: "Bitte Titel, Betrag und AG korrekt ausfuellen." }, { status: 400 }); + return NextResponse.json({ error: "Bitte Titel, Betrag und AG korrekt ausfüllen." }, { status: 400 }); } if (!canCreateExpenseForGroup(viewer.role, viewer.workingGroupId, parsed.data.agId)) { diff --git a/src/app/api/import/csv/route.ts b/src/app/api/import/csv/route.ts index 209a60d..b63e791 100644 --- a/src/app/api/import/csv/route.ts +++ b/src/app/api/import/csv/route.ts @@ -58,14 +58,14 @@ export async function POST(request: Request) { const uploadedFile = formData?.get("file"); if (!(uploadedFile instanceof File)) { - return NextResponse.json({ error: "Bitte eine CSV-Datei auswaehlen." }, { status: 400 }); + return NextResponse.json({ error: "Bitte eine CSV-Datei auswählen." }, { status: 400 }); } const content = await uploadedFile.text(); const rows = parseCsv(content); if (rows.length < 2) { - return NextResponse.json({ error: "Die CSV-Datei enthaelt keine Daten." }, { status: 400 }); + return NextResponse.json({ error: "Die CSV-Datei enthält keine Daten." }, { status: 400 }); } const headers = rows[0]; @@ -81,7 +81,7 @@ export async function POST(request: Request) { if (userRows.some((entry) => !entry.passwordHash)) { return NextResponse.json( - { error: "Dieses Backup stammt aus einem alten Format ohne Passwort-Hashes und kann nicht vollstaendig eingespielt werden." }, + { error: "Dieses Backup stammt aus einem alten Format ohne Passwort-Hashes und kann nicht vollständig eingespielt werden." }, { status: 400 } ); } @@ -119,7 +119,7 @@ export async function POST(request: Request) { const endsAt = toDate(row.periodEndsAt); if (!startsAt || !endsAt) { - throw new Error(`Zeitraum ${row.periodName || row.id} enthaelt kein gueltiges Datum.`); + throw new Error(`Zeitraum ${row.periodName || row.id} enthält kein gültiges Datum.`); } await tx.accountingPeriod.create({ @@ -169,7 +169,7 @@ export async function POST(request: Request) { const totalBudget = toNumber(row.totalBudget); if (totalBudget === null) { - throw new Error(`Budget ${row.budgetName || row.id} enthaelt keinen gueltigen Betrag.`); + throw new Error(`Budget ${row.budgetName || row.id} enthält keinen gültigen Betrag.`); } await tx.budget.create({ @@ -189,7 +189,7 @@ export async function POST(request: Request) { const amount = toNumber(row.amount); if (amount === null) { - throw new Error(`Ausgabe ${row.title || row.id} enthaelt keinen gueltigen Betrag.`); + throw new Error(`Ausgabe ${row.title || row.id} enthält keinen gültigen Betrag.`); } await tx.expense.create({ @@ -216,7 +216,7 @@ export async function POST(request: Request) { const timestamp = toDate(row.createdAt); if (!timestamp) { - throw new Error(`Freigabe ${row.id} enthaelt keinen gueltigen Zeitstempel.`); + throw new Error(`Freigabe ${row.id} enthält keinen gültigen Zeitstempel.`); } await tx.approval.create({ diff --git a/src/app/api/periods/[id]/route.ts b/src/app/api/periods/[id]/route.ts index 00e5eab..4b53232 100644 --- a/src/app/api/periods/[id]/route.ts +++ b/src/app/api/periods/[id]/route.ts @@ -20,7 +20,7 @@ export async function DELETE(_: Request, { params }: Context) { } if (!canManageBudgets(viewer.role)) { - return NextResponse.json({ error: "Nur Vorstand oder Finanz-AG duerfen Zeitraeume loeschen." }, { status: 403 }); + return NextResponse.json({ error: "Nur Vorstand oder Finanz-AG dürfen Zeiträume löschen." }, { status: 403 }); } const period = await prisma.accountingPeriod.findUnique({ @@ -40,12 +40,12 @@ export async function DELETE(_: Request, { params }: Context) { } if (period.isCurrent) { - return NextResponse.json({ error: "Der aktuell aktive Zeitraum kann nicht geloescht werden." }, { status: 400 }); + return NextResponse.json({ error: "Der aktuell aktive Zeitraum kann nicht gelöscht werden." }, { status: 400 }); } if (period._count.budgets > 0 || period._count.expenses > 0) { return NextResponse.json( - { error: "Dieser Zeitraum enthaelt noch Budgets oder Ausgaben und kann deshalb nicht geloescht werden." }, + { error: "Dieser Zeitraum enthält noch Budgets oder Ausgaben und kann deshalb nicht gelöscht werden." }, { status: 400 } ); } @@ -60,7 +60,7 @@ export async function DELETE(_: Request, { params }: Context) { entityType: "period", entityId: period.id, entityLabel: period.name, - summary: `Zeitraum ${period.name} wurde geloescht.`, + summary: `Zeitraum ${period.name} wurde gelöscht.`, metadata: { rollback: { kind: "period.delete", diff --git a/src/app/api/periods/current/route.ts b/src/app/api/periods/current/route.ts index 6a45248..13e3577 100644 --- a/src/app/api/periods/current/route.ts +++ b/src/app/api/periods/current/route.ts @@ -18,14 +18,14 @@ export async function PATCH(request: Request) { } if (!canManageBudgets(viewer.role)) { - return NextResponse.json({ error: "Nur Vorstand oder Finanz-AG duerfen den aktuellen Zeitraum wechseln." }, { status: 403 }); + return NextResponse.json({ error: "Nur Vorstand oder Finanz-AG dürfen den aktuellen Zeitraum wechseln." }, { status: 403 }); } const body = await request.json().catch(() => null); const parsed = currentPeriodSchema.safeParse(body); if (!parsed.success) { - return NextResponse.json({ error: "Bitte einen gueltigen Zeitraum auswaehlen." }, { status: 400 }); + return NextResponse.json({ error: "Bitte einen gültigen Zeitraum auswählen." }, { status: 400 }); } const period = await prisma.accountingPeriod.findUnique({ diff --git a/src/app/api/periods/route.ts b/src/app/api/periods/route.ts index d26daa1..a4d0425 100644 --- a/src/app/api/periods/route.ts +++ b/src/app/api/periods/route.ts @@ -22,7 +22,7 @@ export async function POST(request: Request) { } if (!canManageBudgets(viewer.role)) { - return NextResponse.json({ error: "Nur Vorstand oder Finanz-AG duerfen Zeitraeume verwalten." }, { status: 403 }); + return NextResponse.json({ error: "Nur Vorstand oder Finanz-AG dürfen Zeiträume verwalten." }, { status: 403 }); } const body = await request.json().catch(() => null); diff --git a/src/app/api/users/[id]/password/route.ts b/src/app/api/users/[id]/password/route.ts index fad5beb..4b0cdec 100644 --- a/src/app/api/users/[id]/password/route.ts +++ b/src/app/api/users/[id]/password/route.ts @@ -25,7 +25,7 @@ export async function POST(request: Request, { params }: Context) { } if (!canManageUsers(viewer.role)) { - return NextResponse.json({ error: "Nur Vorstand oder Finanz-AG duerfen Passwoerter neu setzen." }, { status: 403 }); + return NextResponse.json({ error: "Nur Vorstand oder Finanz-AG dürfen Passwörter neu setzen." }, { status: 403 }); } const body = await request.json().catch(() => null); diff --git a/src/app/api/users/[id]/route.ts b/src/app/api/users/[id]/route.ts index aa1c86b..3d6471c 100644 --- a/src/app/api/users/[id]/route.ts +++ b/src/app/api/users/[id]/route.ts @@ -124,11 +124,11 @@ export async function DELETE(_: Request, { params }: Context) { } if (!canManageUsers(viewer.role)) { - return NextResponse.json({ error: "Nur Vorstand oder Finanz-AG duerfen Nutzer loeschen." }, { status: 403 }); + return NextResponse.json({ error: "Nur Vorstand oder Finanz-AG dürfen Nutzer löschen." }, { status: 403 }); } if (viewer.id === params.id) { - return NextResponse.json({ error: "Du kannst dein eigenes Konto hier nicht loeschen." }, { status: 400 }); + return NextResponse.json({ error: "Du kannst dein eigenes Konto hier nicht löschen." }, { status: 400 }); } const user = await prisma.user.findUnique({ @@ -149,7 +149,7 @@ export async function DELETE(_: Request, { params }: Context) { if (user._count.approvals > 0 || user._count.createdExpenses > 0) { return NextResponse.json( - { error: "Nutzer mit Freigaben oder Ausgaben koennen nicht geloescht werden." }, + { error: "Nutzer mit Freigaben oder Ausgaben können nicht gelöscht werden." }, { status: 400 } ); } diff --git a/src/app/api/working-groups/[id]/route.ts b/src/app/api/working-groups/[id]/route.ts index 84d2ad9..d7f6638 100644 --- a/src/app/api/working-groups/[id]/route.ts +++ b/src/app/api/working-groups/[id]/route.ts @@ -25,7 +25,7 @@ export async function PATCH(request: Request, { params }: Context) { } if (!canManageBudgets(viewer.role)) { - return NextResponse.json({ error: "Nur Vorstand oder Finanz-AG duerfen AGs bearbeiten." }, { status: 403 }); + return NextResponse.json({ error: "Nur Vorstand oder Finanz-AG dürfen AGs bearbeiten." }, { status: 403 }); } const body = await request.json().catch(() => null); @@ -95,7 +95,7 @@ export async function DELETE(_: Request, { params }: Context) { } if (!canManageBudgets(viewer.role)) { - return NextResponse.json({ error: "Nur Vorstand oder Finanz-AG duerfen AGs loeschen." }, { status: 403 }); + return NextResponse.json({ error: "Nur Vorstand oder Finanz-AG dürfen AGs löschen." }, { status: 403 }); } const workingGroup = await prisma.workingGroup.findUnique({ diff --git a/src/app/api/working-groups/route.ts b/src/app/api/working-groups/route.ts index 4242837..7300ffd 100644 --- a/src/app/api/working-groups/route.ts +++ b/src/app/api/working-groups/route.ts @@ -19,7 +19,7 @@ export async function POST(request: Request) { } if (!canManageBudgets(viewer.role)) { - return NextResponse.json({ error: "Nur Vorstand oder Finanz-AG duerfen AGs verwalten." }, { status: 403 }); + return NextResponse.json({ error: "Nur Vorstand oder Finanz-AG dürfen AGs verwalten." }, { status: 403 }); } const body = await request.json().catch(() => null); diff --git a/src/components/dashboard/budget-column.tsx b/src/components/dashboard/budget-column.tsx index 56f9163..38c3571 100644 --- a/src/components/dashboard/budget-column.tsx +++ b/src/components/dashboard/budget-column.tsx @@ -275,7 +275,7 @@ export function BudgetColumn({ fullWidth /> - AGs lassen sich nur loeschen, wenn keine Mitglieder, Budgets oder Ausgaben mehr daran haengen. + AGs lassen sich nur löschen, wenn keine Mitglieder, Budgets oder Ausgaben mehr daran hängen. @@ -472,7 +472,7 @@ export function BudgetColumn({ sx={{ ...wrappingChipSx, width: "fit-content" }} /> - {`Unter ${formatCurrency(approvalThreshold)} werden sofort freigegeben. Groessere Ausgaben bleiben blass, bis alle drei Signaturen vorliegen.`} + {`Unter ${formatCurrency(approvalThreshold)} werden sofort freigegeben. Größere Ausgaben bleiben blass, bis alle drei Signaturen vorliegen.`} diff --git a/src/components/dashboard/dashboard-shell.tsx b/src/components/dashboard/dashboard-shell.tsx index 884ca5d..de81702 100644 --- a/src/components/dashboard/dashboard-shell.tsx +++ b/src/components/dashboard/dashboard-shell.tsx @@ -529,7 +529,7 @@ export function DashboardShell({ if (!budgetForm.workingGroupId) { setMessage({ type: "error", - text: "Bitte zuerst eine AG auswaehlen oder neu anlegen." + text: "Bitte zuerst eine AG auswählen oder neu anlegen." }); return; } @@ -679,7 +679,7 @@ export function DashboardShell({ method: "DELETE" }) ); - }, `Zeitraum ${periodName} wurde geloescht.`); + }, `Zeitraum ${periodName} wurde gelöscht.`); } async function handleSetCurrentPeriod() { @@ -732,7 +732,7 @@ export function DashboardShell({ agId: current.agId === groupId ? nextGroup?.id ?? "" : current.agId, budgetId: current.agId === groupId ? nextGroup?.budgets[0]?.id ?? "" : current.budgetId })); - }, `AG ${groupName} wurde geloescht.`); + }, `AG ${groupName} wurde gelöscht.`); } async function handleCreateUser(event: FormEvent) { @@ -773,7 +773,7 @@ export function DashboardShell({ }; }, ({ createdUsername, createdPassword }) => - `Nutzer wurde angelegt. Startpasswort fuer ${createdUsername}: ${createdPassword}` + `Nutzer wurde angelegt. Startpasswort für ${createdUsername}: ${createdPassword}` ); } @@ -983,7 +983,7 @@ export function DashboardShell({ Zeitraum wechseln - {"Nur Vorstand und Finanz-AG koennen die aktuelle Uebersicht global umstellen."} + {"Nur Vorstand und Finanz-AG können die aktuelle Übersicht global umstellen."} setSelectedCurrentPeriodId(event.target.value)} fullWidth @@ -1018,7 +1018,7 @@ export function DashboardShell({ onClick={handleSetCurrentPeriod} sx={{ minWidth: 0, minHeight: 56, px: 2 }} > - {"Uebersicht setzen"} + {"Übersicht setzen"} {selectedPeriodForManagement?.isCurrent - ? "Der aktuell aktive Zeitraum kann nicht geloescht werden." - : "Leere, nicht aktive Zeitraeume lassen sich hier wieder entfernen."} + ? "Der aktuell aktive Zeitraum kann nicht gelöscht werden." + : "Leere, nicht aktive Zeiträume lassen sich hier wieder entfernen."} @@ -1087,7 +1087,7 @@ export function DashboardShell({ setPeriodForm((current) => ({ @@ -1096,9 +1096,9 @@ export function DashboardShell({ })) } fullWidth - helperText={"Optional kopiert die vorhandenen Budgettoepfe direkt in den neuen Zeitraum."} + helperText={"Optional kopiert die vorhandenen Budgettöpfe direkt in den neuen Zeitraum."} > - Ohne Budgetuebernahme + Ohne Budgetübernahme {accountingPeriods.map((period) => ( {period.name} @@ -1261,7 +1261,7 @@ export function DashboardShell({ AG anlegen - {"Lege Arbeitsgruppen separat an. Bearbeiten oder loeschen geht danach direkt in der Uebersicht per Stift."} + {"Lege Arbeitsgruppen separat an. Bearbeiten oder löschen geht danach direkt in der Übersicht per Stift."} @@ -1330,8 +1330,8 @@ export function DashboardShell({ {selectedBudgetWorkingGroup - ? `Neue Budgettoepfe landen in ${selectedBudgetWorkingGroup.name}.` - : "Waehle zuerst eine bestehende AG aus."} + ? `Neue Budgettöpfe landen in ${selectedBudgetWorkingGroup.name}.` + : "Wähle zuerst eine bestehende AG aus."} setUserForm((current) => ({ ...current, username: event.target.value }))} required @@ -1452,7 +1452,7 @@ export function DashboardShell({ } required fullWidth - helperText={"Dieses Passwort wird nach dem Anlegen oben als Bestaetigung angezeigt."} + helperText={"Dieses Passwort wird nach dem Anlegen oben als Bestätigung angezeigt."} /> @@ -1695,7 +1695,7 @@ export function DashboardShell({ ? "Lege zuerst eine AG an." : draft.role === "MEMBER" ? "AG-Mitglieder brauchen eine feste AG-Zuordnung." - : "Optional: Auch Vorstand und Finanz-AG koennen einer AG zugeordnet werden." + : "Optional: Auch Vorstand und Finanz-AG können einer AG zugeordnet werden." } > {draft.role !== "MEMBER" ? Ohne AG : null} @@ -1754,7 +1754,7 @@ export function DashboardShell({ } fullWidth helperText={ - "Nur neu gesetzte Passwoerter sind sichtbar. Das alte Passwort bleibt absichtlich verborgen." + "Nur neu gesetzte Passwörter sind sichtbar. Das alte Passwort bleibt absichtlich verborgen." } /> @@ -1907,16 +1907,16 @@ export function DashboardShell({ const overviewContent = ( - {isCompactLayout && visibleGroups.length > 1 ? ( + {visibleGroups.length > 1 ? ( - {"AG ausw\u00e4hlen"} + AG auswählen - {"Mobil zeigen wir jeweils eine AG auf einmal, damit die Budgetkarten sauber lesbar bleiben."} + Wähle die AG, die gerade in der Übersicht angezeigt werden soll. ) : null} - - {(isCompactLayout ? (mobileSelectedGroup ? [mobileSelectedGroup] : []) : visibleGroups).map((group) => ( + + {(mobileSelectedGroup ? [mobileSelectedGroup] : []).map((group) => ( ))}