die AG-Übersicht bekommt die gleiche Dropdown-Auswahl wie mobil statt Horizontal-Scroll, und ich räume die Umlaut-Geschichte sauber auf, damit wir wieder normales UTF-8 im Code haben statt dieser ASCII-Ausweichmanöver.
All checks were successful
CI / Build (push) Successful in 1m20s
CI / Deploy (push) Successful in 1m4s

This commit is contained in:
Jan
2026-04-13 13:22:34 +02:00
parent 859442a3c1
commit 700e677c45
15 changed files with 64 additions and 70 deletions

View File

@@ -181,7 +181,7 @@ async function main() {
await prisma.expense.create({ await prisma.expense.create({
data: { data: {
title: "Muster: Kabelbinder", title: "Muster: Kabelbinder",
description: "Beispiel fuer einen automatisch freigegebenen Infrastrukturposten.", description: "Beispiel für einen automatisch freigegebenen Infrastrukturposten.",
amount: 24.5, amount: 24.5,
creatorId: technikUser.id, creatorId: technikUser.id,
agId: technik.id, agId: technik.id,
@@ -206,7 +206,7 @@ async function main() {
await prisma.expense.create({ await prisma.expense.create({
data: { data: {
title: "Muster: Vereinssoftware Abo", title: "Muster: Vereinssoftware Abo",
description: "Monatlich wiederkehrendes Beispielabo fuer die Uebersicht.", description: "Monatlich wiederkehrendes Beispielabo für die Übersicht.",
amount: 19, amount: 19,
creatorId: financeUser.id, creatorId: financeUser.id,
agId: technik.id, agId: technik.id,

View File

@@ -99,7 +99,7 @@ export async function DELETE(_: Request, { params }: Context) {
} }
if (!canManageBudgets(viewer.role)) { 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({ const budget = await prisma.budget.findUnique({

View File

@@ -120,7 +120,7 @@ export async function POST(request: Request, { params }: Context) {
entityType: "expense", entityType: "expense",
entityId: expense.id, entityId: expense.id,
entityLabel: expense.title, entityLabel: expense.title,
summary: `${parsed.data.approvalType} fuer ${expense.title} wurde gesetzt.`, summary: `${parsed.data.approvalType} für ${expense.title} wurde gesetzt.`,
metadata: { metadata: {
approvalType: parsed.data.approvalType, approvalType: parsed.data.approvalType,
approvalThreshold, approvalThreshold,

View File

@@ -35,7 +35,7 @@ export async function DELETE(_: Request, { params }: Context) {
if (!isAdminDelete && !isOwnPendingExpense) { if (!isAdminDelete && !isOwnPendingExpense) {
return NextResponse.json( return NextResponse.json(
{ error: "Du darfst nur eigene ungepruefte Ausgaben loeschen." }, { error: "Du darfst nur eigene ungeprüfte Ausgaben löschen." },
{ status: 403 } { status: 403 }
); );
} }

View File

@@ -33,7 +33,7 @@ export async function POST(request: Request) {
const parsed = expenseSchema.safeParse(body); const parsed = expenseSchema.safeParse(body);
if (!parsed.success) { 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)) { if (!canCreateExpenseForGroup(viewer.role, viewer.workingGroupId, parsed.data.agId)) {

View File

@@ -58,14 +58,14 @@ export async function POST(request: Request) {
const uploadedFile = formData?.get("file"); const uploadedFile = formData?.get("file");
if (!(uploadedFile instanceof 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 content = await uploadedFile.text();
const rows = parseCsv(content); const rows = parseCsv(content);
if (rows.length < 2) { 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]; const headers = rows[0];
@@ -81,7 +81,7 @@ export async function POST(request: Request) {
if (userRows.some((entry) => !entry.passwordHash)) { if (userRows.some((entry) => !entry.passwordHash)) {
return NextResponse.json( 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 } { status: 400 }
); );
} }
@@ -119,7 +119,7 @@ export async function POST(request: Request) {
const endsAt = toDate(row.periodEndsAt); const endsAt = toDate(row.periodEndsAt);
if (!startsAt || !endsAt) { 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({ await tx.accountingPeriod.create({
@@ -169,7 +169,7 @@ export async function POST(request: Request) {
const totalBudget = toNumber(row.totalBudget); const totalBudget = toNumber(row.totalBudget);
if (totalBudget === null) { 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({ await tx.budget.create({
@@ -189,7 +189,7 @@ export async function POST(request: Request) {
const amount = toNumber(row.amount); const amount = toNumber(row.amount);
if (amount === null) { 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({ await tx.expense.create({
@@ -216,7 +216,7 @@ export async function POST(request: Request) {
const timestamp = toDate(row.createdAt); const timestamp = toDate(row.createdAt);
if (!timestamp) { 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({ await tx.approval.create({

View File

@@ -20,7 +20,7 @@ export async function DELETE(_: Request, { params }: Context) {
} }
if (!canManageBudgets(viewer.role)) { 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({ const period = await prisma.accountingPeriod.findUnique({
@@ -40,12 +40,12 @@ export async function DELETE(_: Request, { params }: Context) {
} }
if (period.isCurrent) { 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) { if (period._count.budgets > 0 || period._count.expenses > 0) {
return NextResponse.json( 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 } { status: 400 }
); );
} }
@@ -60,7 +60,7 @@ export async function DELETE(_: Request, { params }: Context) {
entityType: "period", entityType: "period",
entityId: period.id, entityId: period.id,
entityLabel: period.name, entityLabel: period.name,
summary: `Zeitraum ${period.name} wurde geloescht.`, summary: `Zeitraum ${period.name} wurde gelöscht.`,
metadata: { metadata: {
rollback: { rollback: {
kind: "period.delete", kind: "period.delete",

View File

@@ -18,14 +18,14 @@ export async function PATCH(request: Request) {
} }
if (!canManageBudgets(viewer.role)) { 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 body = await request.json().catch(() => null);
const parsed = currentPeriodSchema.safeParse(body); const parsed = currentPeriodSchema.safeParse(body);
if (!parsed.success) { 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({ const period = await prisma.accountingPeriod.findUnique({

View File

@@ -22,7 +22,7 @@ export async function POST(request: Request) {
} }
if (!canManageBudgets(viewer.role)) { 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); const body = await request.json().catch(() => null);

View File

@@ -25,7 +25,7 @@ export async function POST(request: Request, { params }: Context) {
} }
if (!canManageUsers(viewer.role)) { 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); const body = await request.json().catch(() => null);

View File

@@ -124,11 +124,11 @@ export async function DELETE(_: Request, { params }: Context) {
} }
if (!canManageUsers(viewer.role)) { 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) { 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({ 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) { if (user._count.approvals > 0 || user._count.createdExpenses > 0) {
return NextResponse.json( 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 } { status: 400 }
); );
} }

View File

@@ -25,7 +25,7 @@ export async function PATCH(request: Request, { params }: Context) {
} }
if (!canManageBudgets(viewer.role)) { 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); const body = await request.json().catch(() => null);
@@ -95,7 +95,7 @@ export async function DELETE(_: Request, { params }: Context) {
} }
if (!canManageBudgets(viewer.role)) { 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({ const workingGroup = await prisma.workingGroup.findUnique({

View File

@@ -19,7 +19,7 @@ export async function POST(request: Request) {
} }
if (!canManageBudgets(viewer.role)) { 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); const body = await request.json().catch(() => null);

View File

@@ -275,7 +275,7 @@ export function BudgetColumn({
fullWidth fullWidth
/> />
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
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.
</Typography> </Typography>
<Stack direction="row" gap={1} useFlexGap flexWrap="wrap"> <Stack direction="row" gap={1} useFlexGap flexWrap="wrap">
<Button type="submit" variant="contained" disabled={busy}> <Button type="submit" variant="contained" disabled={busy}>
@@ -299,7 +299,7 @@ export function BudgetColumn({
startIcon={<DeleteOutlineRoundedIcon />} startIcon={<DeleteOutlineRoundedIcon />}
disabled={busy} disabled={busy}
onClick={async () => { onClick={async () => {
if (!window.confirm(`AG "${group.name}" wirklich loeschen?`)) { if (!window.confirm(`AG "${group.name}" wirklich löschen?`)) {
return; return;
} }
@@ -307,7 +307,7 @@ export function BudgetColumn({
setIsEditingGroup(false); setIsEditingGroup(false);
}} }}
> >
AG loeschen AG löschen
</Button> </Button>
</Stack> </Stack>
</Stack> </Stack>
@@ -472,7 +472,7 @@ export function BudgetColumn({
sx={{ ...wrappingChipSx, width: "fit-content" }} sx={{ ...wrappingChipSx, width: "fit-content" }}
/> />
<Typography color="text.secondary"> <Typography color="text.secondary">
{`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.`}
</Typography> </Typography>
</Stack> </Stack>
</Stack> </Stack>

View File

@@ -529,7 +529,7 @@ export function DashboardShell({
if (!budgetForm.workingGroupId) { if (!budgetForm.workingGroupId) {
setMessage({ setMessage({
type: "error", type: "error",
text: "Bitte zuerst eine AG auswaehlen oder neu anlegen." text: "Bitte zuerst eine AG auswählen oder neu anlegen."
}); });
return; return;
} }
@@ -679,7 +679,7 @@ export function DashboardShell({
method: "DELETE" method: "DELETE"
}) })
); );
}, `Zeitraum ${periodName} wurde geloescht.`); }, `Zeitraum ${periodName} wurde gelöscht.`);
} }
async function handleSetCurrentPeriod() { async function handleSetCurrentPeriod() {
@@ -732,7 +732,7 @@ export function DashboardShell({
agId: current.agId === groupId ? nextGroup?.id ?? "" : current.agId, agId: current.agId === groupId ? nextGroup?.id ?? "" : current.agId,
budgetId: current.agId === groupId ? nextGroup?.budgets[0]?.id ?? "" : current.budgetId budgetId: current.agId === groupId ? nextGroup?.budgets[0]?.id ?? "" : current.budgetId
})); }));
}, `AG ${groupName} wurde geloescht.`); }, `AG ${groupName} wurde gelöscht.`);
} }
async function handleCreateUser(event: FormEvent<HTMLFormElement>) { async function handleCreateUser(event: FormEvent<HTMLFormElement>) {
@@ -773,7 +773,7 @@ export function DashboardShell({
}; };
}, },
({ createdUsername, createdPassword }) => ({ 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 Zeitraum wechseln
</Typography> </Typography>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
{"Nur Vorstand und Finanz-AG koennen die aktuelle Uebersicht global umstellen."} {"Nur Vorstand und Finanz-AG können die aktuelle Übersicht global umstellen."}
</Typography> </Typography>
</Box> </Box>
<Box <Box
@@ -999,7 +999,7 @@ export function DashboardShell({
> >
<TextField <TextField
select select
label={"Aktuelle Uebersicht"} label={"Aktuelle Übersicht"}
value={selectedCurrentPeriodId} value={selectedCurrentPeriodId}
onChange={(event) => setSelectedCurrentPeriodId(event.target.value)} onChange={(event) => setSelectedCurrentPeriodId(event.target.value)}
fullWidth fullWidth
@@ -1018,7 +1018,7 @@ export function DashboardShell({
onClick={handleSetCurrentPeriod} onClick={handleSetCurrentPeriod}
sx={{ minWidth: 0, minHeight: 56, px: 2 }} sx={{ minWidth: 0, minHeight: 56, px: 2 }}
> >
{"Uebersicht setzen"} {"Übersicht setzen"}
</Button> </Button>
<Button <Button
type="button" type="button"
@@ -1031,7 +1031,7 @@ export function DashboardShell({
return; return;
} }
if (!window.confirm(`Zeitraum "${selectedPeriodForManagement.name}" wirklich loeschen?`)) { if (!window.confirm(`Zeitraum "${selectedPeriodForManagement.name}" wirklich löschen?`)) {
return; return;
} }
@@ -1044,13 +1044,13 @@ export function DashboardShell({
whiteSpace: "normal" whiteSpace: "normal"
}} }}
> >
{"Zeitraum loeschen"} {"Zeitraum löschen"}
</Button> </Button>
</Box> </Box>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
{selectedPeriodForManagement?.isCurrent {selectedPeriodForManagement?.isCurrent
? "Der aktuell aktive Zeitraum kann nicht geloescht werden." ? "Der aktuell aktive Zeitraum kann nicht gelöscht werden."
: "Leere, nicht aktive Zeitraeume lassen sich hier wieder entfernen."} : "Leere, nicht aktive Zeiträume lassen sich hier wieder entfernen."}
</Typography> </Typography>
<Box component="form" onSubmit={handleCreatePeriod} sx={nestedPanelSx}> <Box component="form" onSubmit={handleCreatePeriod} sx={nestedPanelSx}>
@@ -1087,7 +1087,7 @@ export function DashboardShell({
</Stack> </Stack>
<TextField <TextField
select select
label={"Budgets uebernehmen"} label={"Budgets übernehmen"}
value={periodForm.copyBudgetsFromPeriodId} value={periodForm.copyBudgetsFromPeriodId}
onChange={(event) => onChange={(event) =>
setPeriodForm((current) => ({ setPeriodForm((current) => ({
@@ -1096,9 +1096,9 @@ export function DashboardShell({
})) }))
} }
fullWidth fullWidth
helperText={"Optional kopiert die vorhandenen Budgettoepfe direkt in den neuen Zeitraum."} helperText={"Optional kopiert die vorhandenen Budgettöpfe direkt in den neuen Zeitraum."}
> >
<MenuItem value="">Ohne Budgetuebernahme</MenuItem> <MenuItem value="">Ohne Budgetübernahme</MenuItem>
{accountingPeriods.map((period) => ( {accountingPeriods.map((period) => (
<MenuItem key={period.id} value={period.id}> <MenuItem key={period.id} value={period.id}>
{period.name} {period.name}
@@ -1261,7 +1261,7 @@ export function DashboardShell({
AG anlegen AG anlegen
</Typography> </Typography>
<Typography color="text.secondary"> <Typography color="text.secondary">
{"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."}
</Typography> </Typography>
</Box> </Box>
<Box component="form" onSubmit={handleCreateWorkingGroup}> <Box component="form" onSubmit={handleCreateWorkingGroup}>
@@ -1330,8 +1330,8 @@ export function DashboardShell({
</TextField> </TextField>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
{selectedBudgetWorkingGroup {selectedBudgetWorkingGroup
? `Neue Budgettoepfe landen in ${selectedBudgetWorkingGroup.name}.` ? `Neue Budgettöpfe landen in ${selectedBudgetWorkingGroup.name}.`
: "Waehle zuerst eine bestehende AG aus."} : "Wähle zuerst eine bestehende AG aus."}
</Typography> </Typography>
<TextField <TextField
label="Budget-Name" label="Budget-Name"
@@ -1438,7 +1438,7 @@ export function DashboardShell({
<Stack spacing={2}> <Stack spacing={2}>
<TextField <TextField
label="Login-Name" label="Login-Name"
helperText={"Damit melden sich Nutzer spaeter an und so werden sie auch angezeigt."} helperText={"Damit melden sich Nutzer später an und so werden sie auch angezeigt."}
value={userForm.username} value={userForm.username}
onChange={(event) => setUserForm((current) => ({ ...current, username: event.target.value }))} onChange={(event) => setUserForm((current) => ({ ...current, username: event.target.value }))}
required required
@@ -1452,7 +1452,7 @@ export function DashboardShell({
} }
required required
fullWidth fullWidth
helperText={"Dieses Passwort wird nach dem Anlegen oben als Bestaetigung angezeigt."} helperText={"Dieses Passwort wird nach dem Anlegen oben als Bestätigung angezeigt."}
/> />
<Button <Button
type="button" type="button"
@@ -1501,7 +1501,7 @@ export function DashboardShell({
? "Lege zuerst eine AG an." ? "Lege zuerst eine AG an."
: userForm.role === "MEMBER" : userForm.role === "MEMBER"
? "AG-Mitglieder brauchen eine feste AG-Zuordnung." ? "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."
} }
> >
{userForm.role !== "MEMBER" ? <MenuItem value="">Ohne AG</MenuItem> : null} {userForm.role !== "MEMBER" ? <MenuItem value="">Ohne AG</MenuItem> : null}
@@ -1518,7 +1518,7 @@ export function DashboardShell({
...current, ...current,
approvalPermissions: toggleApprovalPermission(current.approvalPermissions, approvalType) approvalPermissions: toggleApprovalPermission(current.approvalPermissions, approvalType)
})), })),
"Lege fest, fuer welche Freigabeschritte dieses Konto zeichnen darf." "Lege fest, für welche Freigabeschritte dieses Konto zeichnen darf."
)} )}
<Button type="submit" variant="outlined" disabled={busy}> <Button type="submit" variant="outlined" disabled={busy}>
Nutzer speichern Nutzer speichern
@@ -1566,7 +1566,7 @@ export function DashboardShell({
Nutzer verwalten Nutzer verwalten
</Typography> </Typography>
<Typography color="text.secondary"> <Typography color="text.secondary">
{"Bestehende Passwoerter bleiben sicher gehasht. Hier kannst du Rolle, AG-Zuordnung, Freigaberollen und Passwoerter pflegen."} {"Bestehende Passwörter bleiben sicher gehasht. Hier kannst du Rolle, AG-Zuordnung, Freigaberollen und Passwörter pflegen."}
</Typography> </Typography>
</Box> </Box>
<Stack spacing={1.4}> <Stack spacing={1.4}>
@@ -1631,14 +1631,14 @@ export function DashboardShell({
startIcon={<DeleteOutlineRoundedIcon />} startIcon={<DeleteOutlineRoundedIcon />}
disabled={busy || !canDelete} disabled={busy || !canDelete}
onClick={async () => { onClick={async () => {
if (!window.confirm(`Nutzer "${user.username}" wirklich loeschen?`)) { if (!window.confirm(`Nutzer "${user.username}" wirklich löschen?`)) {
return; return;
} }
await handleDeleteUser(user.id); await handleDeleteUser(user.id);
}} }}
> >
{"Loeschen"} {"Löschen"}
</Button> </Button>
</Stack> </Stack>
</Stack> </Stack>
@@ -1695,7 +1695,7 @@ export function DashboardShell({
? "Lege zuerst eine AG an." ? "Lege zuerst eine AG an."
: draft.role === "MEMBER" : draft.role === "MEMBER"
? "AG-Mitglieder brauchen eine feste AG-Zuordnung." ? "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" ? <MenuItem value="">Ohne AG</MenuItem> : null} {draft.role !== "MEMBER" ? <MenuItem value="">Ohne AG</MenuItem> : null}
@@ -1754,7 +1754,7 @@ export function DashboardShell({
} }
fullWidth fullWidth
helperText={ 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."
} }
/> />
<Stack direction="row" gap={1} useFlexGap flexWrap="wrap"> <Stack direction="row" gap={1} useFlexGap flexWrap="wrap">
@@ -1907,16 +1907,16 @@ export function DashboardShell({
const overviewContent = ( const overviewContent = (
<Stack spacing={2.5}> <Stack spacing={2.5}>
{isCompactLayout && visibleGroups.length > 1 ? ( {visibleGroups.length > 1 ? (
<Card> <Card>
<CardContent sx={{ p: 2.5 }}> <CardContent sx={{ p: 2.5 }}>
<Stack spacing={1.5}> <Stack spacing={1.5}>
<Box> <Box>
<Typography variant="h3" sx={{ fontSize: "1.15rem" }}> <Typography variant="h3" sx={{ fontSize: "1.15rem" }}>
{"AG ausw\u00e4hlen"} AG auswählen
</Typography> </Typography>
<Typography color="text.secondary"> <Typography color="text.secondary">
{"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.
</Typography> </Typography>
</Box> </Box>
<TextField <TextField
@@ -1937,15 +1937,8 @@ export function DashboardShell({
</Card> </Card>
) : null} ) : null}
<Stack <Stack direction="column" gap={2}>
direction={isCompactLayout ? "column" : "row"} {(mobileSelectedGroup ? [mobileSelectedGroup] : []).map((group) => (
gap={2}
sx={{
overflowX: isCompactLayout ? "visible" : "auto",
pb: isCompactLayout ? 0 : 2
}}
>
{(isCompactLayout ? (mobileSelectedGroup ? [mobileSelectedGroup] : []) : visibleGroups).map((group) => (
<BudgetColumn <BudgetColumn
key={group.id} key={group.id}
group={group} group={group}
@@ -1958,7 +1951,8 @@ export function DashboardShell({
onSaveWorkingGroup={handleSaveWorkingGroup} onSaveWorkingGroup={handleSaveWorkingGroup}
onDeleteWorkingGroup={handleDeleteWorkingGroup} onDeleteWorkingGroup={handleDeleteWorkingGroup}
onSaveBudget={handleSaveBudget} onSaveBudget={handleSaveBudget}
onDeleteBudget={handleDeleteBudget} onDeleteExpense={handleDeleteExpense} onDeleteBudget={handleDeleteBudget}
onDeleteExpense={handleDeleteExpense}
/> />
))} ))}
</Stack> </Stack>