fix(web): Oberflaechentexte ohne Mandanten-Begriff, Organisationsauswahl im Marktplatz nur bei mehreren
Tessera CI/CD / Lint & Type Check (push) Successful in 57s
Tessera CI/CD / Tests (push) Successful in 1m51s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 19s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m23s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-03 02:58:50 +02:00
parent 2053dbac28
commit 3562b154f0
7 changed files with 47 additions and 31 deletions
@@ -13,7 +13,7 @@ const messages: Record<string, Record<string, unknown>> = {
title: 'Freigaben-Matrix', title: 'Freigaben-Matrix',
searchPlaceholder: 'Module oder Gruppen durchsuchen...', searchPlaceholder: 'Module oder Gruppen durchsuchen...',
emptyModules: emptyModules:
'Es sind noch keine Module für diesen Mandanten aktiviert. Aktiviere zuerst ein Modul unter Module.', 'Es sind noch keine Module aktiviert. Aktiviere zuerst ein Modul unter Module.',
emptyModulesLink: 'Zu Module', emptyModulesLink: 'Zu Module',
adminNote: adminNote:
'ADMIN und SUPER_ADMIN haben immer Zugriff auf alle aktiven Module — diese Matrix betrifft nur die Rolle USER.', 'ADMIN und SUPER_ADMIN haben immer Zugriff auf alle aktiven Module — diese Matrix betrifft nur die Rolle USER.',
@@ -153,7 +153,7 @@ describe('AdminModuleGrantsPage (Permission-Matrix)', () => {
render(<AdminModuleGrantsPage />); render(<AdminModuleGrantsPage />);
await waitFor(() => { await waitFor(() => {
expect(screen.getByText(/keine Module für diesen Mandanten aktiviert/)).toBeInTheDocument(); expect(screen.getByText(/noch keine Module aktiviert/)).toBeInTheDocument();
}); });
const link = screen.getByText('Zu Module'); const link = screen.getByText('Zu Module');
expect(link.closest('a')).toHaveAttribute('href', '/admin/modules'); expect(link.closest('a')).toHaveAttribute('href', '/admin/modules');
@@ -34,7 +34,8 @@ export function TenantContextSelector() {
.finally(() => setLoading(false)); .finally(() => setLoading(false));
}, [isSuperAdmin]); }, [isSuperAdmin]);
if (!isSuperAdmin) return null; // Nur bei mehreren Organisationen sinnvoll; im Ein-Firmen-Betrieb ausgeblendet.
if (!isSuperAdmin || (!loading && tenants.length <= 1)) return null;
return ( return (
<div className="flex flex-wrap items-center gap-3 rounded-lg bg-card px-4 py-3 shadow-sm dark:border dark:border-border"> <div className="flex flex-wrap items-center gap-3 rounded-lg bg-card px-4 py-3 shadow-sm dark:border dark:border-border">
@@ -25,7 +25,7 @@ vi.mock('next-intl', () => ({
errorLoad: 'Module konnten nicht geladen werden.', errorLoad: 'Module konnten nicht geladen werden.',
toastActivated: 'Modul erfolgreich aktiviert', toastActivated: 'Modul erfolgreich aktiviert',
accessDenied: 'Zugriff verweigert', accessDenied: 'Zugriff verweigert',
tenantContextLabel: 'Mandanten-Kontext', tenantContextLabel: 'Organisation',
}; };
let result = translations[key] ?? key; let result = translations[key] ?? key;
if (params) { if (params) {
@@ -27,7 +27,7 @@ vi.mock('next-intl', () => ({
toastActivated: 'Modul erfolgreich aktiviert', toastActivated: 'Modul erfolgreich aktiviert',
toastDeactivated: 'Modul erfolgreich deaktiviert', toastDeactivated: 'Modul erfolgreich deaktiviert',
accessDenied: 'Zugriff verweigert', accessDenied: 'Zugriff verweigert',
tenantContextLabel: 'Mandanten-Kontext', tenantContextLabel: 'Organisation',
deactivateDialogTitle: 'Modul deaktivieren', deactivateDialogTitle: 'Modul deaktivieren',
deactivateDialogBody: 'Moechten Sie {moduleName} fuer diesen Mandanten deaktivieren?', deactivateDialogBody: 'Moechten Sie {moduleName} fuer diesen Mandanten deaktivieren?',
deactivateDialogConfirm: 'Deaktivieren', deactivateDialogConfirm: 'Deaktivieren',
@@ -118,6 +118,21 @@ describe('TenantContextSelector', () => {
expect(screen.getByText('Tenant Beta')).toBeInTheDocument(); expect(screen.getByText('Tenant Beta')).toBeInTheDocument();
}); });
it('blendet die Auswahl bei nur einer Organisation aus', async () => {
mockAuthStore.mockImplementation(
(selector: (state: { user: { id: string; username: string; displayName: string; role: string; tenantId: string } }) => unknown) =>
selector({
user: { id: 'u1', username: 'superadmin', displayName: 'Super', role: 'SUPER_ADMIN', tenantId: 't1' },
}),
);
vi.stubGlobal(
'fetch',
vi.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve([mockTenants[0]]) })),
);
const { container } = render(<TenantContextSelector />);
await waitFor(() => expect(container.innerHTML).toBe(''));
});
it('renders nothing for ADMIN and does not load the tenant list', () => { it('renders nothing for ADMIN and does not load the tenant list', () => {
mockAuthStore.mockImplementation( mockAuthStore.mockImplementation(
(selector: (state: { user: { id: string; username: string; displayName: string; role: string; tenantId: string } }) => unknown) => (selector: (state: { user: { id: string; username: string; displayName: string; role: string; tenantId: string } }) => unknown) =>
@@ -120,7 +120,7 @@ describe('WelcomeMailTemplateEditor', () => {
]) { ]) {
expect(within(box).getByRole('button', { name: `${token} einfügen` })).toBeTruthy(); expect(within(box).getByRole('button', { name: `${token} einfügen` })).toBeTruthy();
} }
expect(within(box).getByText('Name Ihres Mandanten')).toBeTruthy(); expect(within(box).getByText('Name Ihrer Organisation')).toBeTruthy();
expect(within(box).getByText('max.mustermann@example.com')).toBeTruthy(); expect(within(box).getByText('max.mustermann@example.com')).toBeTruthy();
}); });
+13 -13
View File
@@ -555,7 +555,7 @@
"viaGroupsColumn": "Über Gruppe(n)", "viaGroupsColumn": "Über Gruppe(n)",
"directColumn": "Direkt", "directColumn": "Direkt",
"noInheritance": "–", "noInheritance": "–",
"noActiveModules": "Für diesen Mandanten sind keine Module aktiviert.", "noActiveModules": "Es sind keine Module aktiviert.",
"directCheckboxLabel": "{module} direkt für {user} {granted, select, true {freigeben} other {entziehen}}", "directCheckboxLabel": "{module} direkt für {user} {granted, select, true {freigeben} other {entziehen}}",
"directLevelLabel": "Stufe der direkten Freigabe von {module} für {user}", "directLevelLabel": "Stufe der direkten Freigabe von {module} für {user}",
"manageMarker": "Verwalten", "manageMarker": "Verwalten",
@@ -751,7 +751,7 @@
}, },
"welcomeMail": { "welcomeMail": {
"title": "Willkommensmail", "title": "Willkommensmail",
"description": "Hier legen Sie fest, mit welchen Worten Tessera neue Benutzer begrüßt. Die Vorlage gilt für Ihren Mandanten und wird bei jeder Willkommensmail aus der Benutzerverwaltung verwendet.", "description": "Hier legen Sie fest, mit welchen Worten Tessera neue Benutzer begrüßt. Die Vorlage gilt für alle Benutzer und wird bei jeder Willkommensmail aus der Benutzerverwaltung verwendet.",
"loading": "Vorlage wird geladen …", "loading": "Vorlage wird geladen …",
"fixed": { "fixed": {
"title": "Was Sie anpassen können – und was immer drin bleibt", "title": "Was Sie anpassen können – und was immer drin bleibt",
@@ -832,7 +832,7 @@
"example": "https://tessera.example.com" "example": "https://tessera.example.com"
}, },
"firma": { "firma": {
"meaning": "Name Ihres Mandanten", "meaning": "Name Ihrer Organisation",
"example": "Beispiel GmbH" "example": "Beispiel GmbH"
} }
} }
@@ -846,13 +846,13 @@
"frameTitle": "Vorschau der Willkommensmail", "frameTitle": "Vorschau der Willkommensmail",
"error": "Die Vorschau konnte nicht erstellt werden.", "error": "Die Vorschau konnte nicht erstellt werden.",
"blocked": "Die Vorschau wird aktualisiert, sobald alle Felder in Ordnung sind.", "blocked": "Die Vorschau wird aktualisiert, sobald alle Felder in Ordnung sind.",
"sampleHint": "Die Vorschau verwendet Beispielwerte (Max Mustermann) und den Namen Ihres Mandanten. Der Link „Passwort festlegen“ ist in der Vorschau nur ein Beispiel." "sampleHint": "Die Vorschau verwendet Beispielwerte (Max Mustermann) und den Namen Ihrer Organisation. Der Link „Passwort festlegen“ ist in der Vorschau nur ein Beispiel."
} }
} }
}, },
"adminModules": { "adminModules": {
"title": "Modulverwaltung", "title": "Modulverwaltung",
"description": "Module für Ihren Mandanten aktivieren oder deaktivieren", "description": "Module aktivieren oder deaktivieren",
"activate": "Aktivieren", "activate": "Aktivieren",
"deactivate": "Deaktivieren", "deactivate": "Deaktivieren",
"activated": "Aktiviert", "activated": "Aktiviert",
@@ -868,7 +868,7 @@
"grants": { "grants": {
"title": "Freigaben-Matrix", "title": "Freigaben-Matrix",
"searchPlaceholder": "Module oder Gruppen durchsuchen...", "searchPlaceholder": "Module oder Gruppen durchsuchen...",
"emptyModules": "Es sind noch keine Module für diesen Mandanten aktiviert. Aktiviere zuerst ein Modul unter Module.", "emptyModules": "Es sind noch keine Module aktiviert. Aktiviere zuerst ein Modul unter Module.",
"emptyModulesLink": "Zu Module", "emptyModulesLink": "Zu Module",
"levelExplanation": "„Benutzen“: Das Modul öffnen und damit arbeiten. „Verwalten“: zusätzlich die Einstellungen dieses Moduls ändern. Freigaben vergeben und Module aktivieren dürfen weiterhin nur Administratoren. Hat jemand über mehrere Wege Zugriff, gilt die höhere Stufe.", "levelExplanation": "„Benutzen“: Das Modul öffnen und damit arbeiten. „Verwalten“: zusätzlich die Einstellungen dieses Moduls ändern. Freigaben vergeben und Module aktivieren dürfen weiterhin nur Administratoren. Hat jemand über mehrere Wege Zugriff, gilt die höhere Stufe.",
"adminNote": "Administratoren haben immer Zugriff auf alle aktiven Module und dürfen deren Einstellungen ändern – diese Matrix betrifft nur Benutzer ohne Administratorrechte.", "adminNote": "Administratoren haben immer Zugriff auf alle aktiven Module und dürfen deren Einstellungen ändern – diese Matrix betrifft nur Benutzer ohne Administratorrechte.",
@@ -964,7 +964,7 @@
"notFoundDescription": "Das angeforderte Modul existiert nicht oder ist nicht aktiviert.", "notFoundDescription": "Das angeforderte Modul existiert nicht oder ist nicht aktiviert.",
"backToCategory": "Zurück zur Kategorie", "backToCategory": "Zurück zur Kategorie",
"allModules": "Module", "allModules": "Module",
"allModulesDescription": "Verfügbare Module für Ihren Mandanten", "allModulesDescription": "Verfügbare Module",
"noActiveModules": "Keine Module aktiviert. Bitten Sie Ihren Administrator, Module zu aktivieren.", "noActiveModules": "Keine Module aktiviert. Bitten Sie Ihren Administrator, Module zu aktivieren.",
"accessDenied": { "accessDenied": {
"title": "Kein Zugriff auf dieses Modul", "title": "Kein Zugriff auf dieses Modul",
@@ -1194,7 +1194,7 @@
}, },
"marketplace": { "marketplace": {
"title": "Marktplatz", "title": "Marktplatz",
"subtitle": "Entdecken und aktivieren Sie Module für Ihren Mandanten", "subtitle": "Entdecken und aktivieren Sie Module",
"activate": "Modul aktivieren", "activate": "Modul aktivieren",
"deactivate": "Deaktivieren", "deactivate": "Deaktivieren",
"searchPlaceholder": "Module suchen...", "searchPlaceholder": "Module suchen...",
@@ -1212,13 +1212,13 @@
"toastActivateError": "Aktivierung fehlgeschlagen. Bitte versuchen Sie es erneut.", "toastActivateError": "Aktivierung fehlgeschlagen. Bitte versuchen Sie es erneut.",
"toastDeactivateError": "Deaktivierung fehlgeschlagen. Bitte versuchen Sie es erneut.", "toastDeactivateError": "Deaktivierung fehlgeschlagen. Bitte versuchen Sie es erneut.",
"deactivateDialogTitle": "Modul deaktivieren", "deactivateDialogTitle": "Modul deaktivieren",
"deactivateDialogBody": "Möchten Sie {moduleName} für diesen Mandanten deaktivieren? Das Modul wird aus der Seitenleiste entfernt.", "deactivateDialogBody": "Möchten Sie {moduleName} deaktivieren? Das Modul wird aus der Seitenleiste entfernt.",
"deactivateDialogConfirm": "Deaktivieren", "deactivateDialogConfirm": "Deaktivieren",
"deactivateDialogCancel": "Abbrechen", "deactivateDialogCancel": "Abbrechen",
"tenantContextLabel": "Mandanten-Kontext", "tenantContextLabel": "Organisation",
"backToMarketplace": "Zurück zum Marktplatz", "backToMarketplace": "Zurück zum Marktplatz",
"detailVersion": "Version {version}", "detailVersion": "Version {version}",
"detailStatusActive": "Aktiviert für diesen Mandanten", "detailStatusActive": "Aktiviert",
"detailStatusInactive": "Nicht aktiviert", "detailStatusInactive": "Nicht aktiviert",
"accessDenied": "Zugriff verweigert", "accessDenied": "Zugriff verweigert",
"statusNoAccess": "Kein Zugriff", "statusNoAccess": "Kein Zugriff",
@@ -1528,7 +1528,7 @@
"settings": { "settings": {
"title": "Ausschreibungs-Radar — Einstellungen", "title": "Ausschreibungs-Radar — Einstellungen",
"rssSectionTitle": "RSS-Feeds", "rssSectionTitle": "RSS-Feeds",
"rssSectionBody": "Öffentliche, plattformweite RSS-Quellen (z. B. service.bund.de oder eine subreport-elvis-Kommunalfeed) — gilt für alle Mandanten gleich, nicht pro Mandant konfigurierbar.", "rssSectionBody": "Öffentliche, plattformweite RSS-Quellen (z. B. service.bund.de oder eine subreport-elvis-Kommunalfeed) — gilt für alle gleich.",
"rssSectionUserNote": "Persönliche Feeds legen Sie auf „Meine Quellen\" an.", "rssSectionUserNote": "Persönliche Feeds legen Sie auf „Meine Quellen\" an.",
"accessDeniedText": "Diese Seite verwaltet plattformweite Einstellungen und ist Administratoren vorbehalten.", "accessDeniedText": "Diese Seite verwaltet plattformweite Einstellungen und ist Administratoren vorbehalten.",
"digestIntervalLabel": "Digest-Intervall", "digestIntervalLabel": "Digest-Intervall",
@@ -1611,7 +1611,7 @@
}, },
"mySources": { "mySources": {
"title": "Meine Quellen", "title": "Meine Quellen",
"intro": "Ausschreibungen, die aus Ihrem Postfach hereinkommen, erscheinen danach in der Trefferliste aller Kolleginnen und Kollegen Ihres Mandanten — es gibt keine getrennte Sichtbarkeit je Postfach.", "intro": "Ausschreibungen, die aus Ihrem Postfach hereinkommen, erscheinen danach in der Trefferliste aller Kolleginnen und Kollegen — es gibt keine getrennte Sichtbarkeit je Postfach.",
"mailboxSectionTitle": "Mein Postfach", "mailboxSectionTitle": "Mein Postfach",
"feedsSectionTitle": "Meine Feeds", "feedsSectionTitle": "Meine Feeds",
"notificationSectionTitle": "Benachrichtigung", "notificationSectionTitle": "Benachrichtigung",
+12 -12
View File
@@ -555,7 +555,7 @@
"viaGroupsColumn": "Via Group(s)", "viaGroupsColumn": "Via Group(s)",
"directColumn": "Direct", "directColumn": "Direct",
"noInheritance": "–", "noInheritance": "–",
"noActiveModules": "No modules are activated for this tenant.", "noActiveModules": "No modules are activated.",
"directCheckboxLabel": "{granted, select, true {Grant} other {Revoke}} {module} directly for {user}", "directCheckboxLabel": "{granted, select, true {Grant} other {Revoke}} {module} directly for {user}",
"directLevelLabel": "Level of the direct grant of {module} for {user}", "directLevelLabel": "Level of the direct grant of {module} for {user}",
"manageMarker": "Manage", "manageMarker": "Manage",
@@ -751,7 +751,7 @@
}, },
"welcomeMail": { "welcomeMail": {
"title": "Welcome email", "title": "Welcome email",
"description": "Choose the words Tessera uses to greet new users. The template applies to your tenant and is used for every welcome email sent from user management.", "description": "Choose the words Tessera uses to greet new users. The template applies to all users and is used for every welcome email sent from user management.",
"loading": "Loading template …", "loading": "Loading template …",
"fixed": { "fixed": {
"title": "What you can change – and what always stays", "title": "What you can change – and what always stays",
@@ -832,7 +832,7 @@
"example": "https://tessera.example.com" "example": "https://tessera.example.com"
}, },
"firma": { "firma": {
"meaning": "Name of your tenant", "meaning": "Name of your organization",
"example": "Example Ltd" "example": "Example Ltd"
} }
} }
@@ -846,13 +846,13 @@
"frameTitle": "Welcome email preview", "frameTitle": "Welcome email preview",
"error": "The preview could not be created.", "error": "The preview could not be created.",
"blocked": "The preview updates as soon as all fields are valid.", "blocked": "The preview updates as soon as all fields are valid.",
"sampleHint": "The preview uses sample values (Max Mustermann) and the name of your tenant. The “Set password” link is only an example in the preview." "sampleHint": "The preview uses sample values (Max Mustermann) and the name of your organization. The “Set password” link is only an example in the preview."
} }
} }
}, },
"adminModules": { "adminModules": {
"title": "Module Management", "title": "Module Management",
"description": "Activate or deactivate modules for your tenant", "description": "Activate or deactivate modules",
"activate": "Activate", "activate": "Activate",
"deactivate": "Deactivate", "deactivate": "Deactivate",
"activated": "Activated", "activated": "Activated",
@@ -868,7 +868,7 @@
"grants": { "grants": {
"title": "Grants Matrix", "title": "Grants Matrix",
"searchPlaceholder": "Search modules or groups...", "searchPlaceholder": "Search modules or groups...",
"emptyModules": "No modules are activated for this tenant yet. Activate a module under Modules first.", "emptyModules": "No modules are activated yet. Activate a module under Modules first.",
"emptyModulesLink": "Go to Modules", "emptyModulesLink": "Go to Modules",
"levelExplanation": "“Use”: open the module and work with it. “Manage”: additionally change the settings of this module. Only administrators may grant access and activate modules. If someone has access through several routes, the higher level applies.", "levelExplanation": "“Use”: open the module and work with it. “Manage”: additionally change the settings of this module. Only administrators may grant access and activate modules. If someone has access through several routes, the higher level applies.",
"adminNote": "Administrators always have access to all active modules and may change their settings — this matrix only affects users without administrator rights.", "adminNote": "Administrators always have access to all active modules and may change their settings — this matrix only affects users without administrator rights.",
@@ -964,7 +964,7 @@
"notFoundDescription": "The requested module does not exist or is not activated.", "notFoundDescription": "The requested module does not exist or is not activated.",
"backToCategory": "Back to category", "backToCategory": "Back to category",
"allModules": "Modules", "allModules": "Modules",
"allModulesDescription": "Available modules for your tenant", "allModulesDescription": "Available modules",
"noActiveModules": "No modules activated. Ask your administrator to enable modules.", "noActiveModules": "No modules activated. Ask your administrator to enable modules.",
"accessDenied": { "accessDenied": {
"title": "No Access to This Module", "title": "No Access to This Module",
@@ -1194,7 +1194,7 @@
}, },
"marketplace": { "marketplace": {
"title": "Marketplace", "title": "Marketplace",
"subtitle": "Discover and activate modules for your tenant", "subtitle": "Discover and activate modules",
"activate": "Activate Module", "activate": "Activate Module",
"deactivate": "Deactivate", "deactivate": "Deactivate",
"searchPlaceholder": "Search modules...", "searchPlaceholder": "Search modules...",
@@ -1212,13 +1212,13 @@
"toastActivateError": "Activation failed. Please try again.", "toastActivateError": "Activation failed. Please try again.",
"toastDeactivateError": "Deactivation failed. Please try again.", "toastDeactivateError": "Deactivation failed. Please try again.",
"deactivateDialogTitle": "Deactivate Module", "deactivateDialogTitle": "Deactivate Module",
"deactivateDialogBody": "Do you want to deactivate {moduleName} for this tenant? The module will be removed from the sidebar.", "deactivateDialogBody": "Do you want to deactivate {moduleName}? The module will be removed from the sidebar.",
"deactivateDialogConfirm": "Deactivate", "deactivateDialogConfirm": "Deactivate",
"deactivateDialogCancel": "Cancel", "deactivateDialogCancel": "Cancel",
"tenantContextLabel": "Tenant Context", "tenantContextLabel": "Organization",
"backToMarketplace": "Back to Marketplace", "backToMarketplace": "Back to Marketplace",
"detailVersion": "Version {version}", "detailVersion": "Version {version}",
"detailStatusActive": "Activated for this tenant", "detailStatusActive": "Activated",
"detailStatusInactive": "Not activated", "detailStatusInactive": "Not activated",
"accessDenied": "Access denied", "accessDenied": "Access denied",
"statusNoAccess": "No Access", "statusNoAccess": "No Access",
@@ -1528,7 +1528,7 @@
"settings": { "settings": {
"title": "Tender Radar — Settings", "title": "Tender Radar — Settings",
"rssSectionTitle": "RSS Feeds", "rssSectionTitle": "RSS Feeds",
"rssSectionBody": "Public, platform-wide RSS sources (e.g. service.bund.de or a subreport-elvis municipal feed) — applies equally to all tenants, not configurable per tenant.", "rssSectionBody": "Public, platform-wide RSS sources (e.g. service.bund.de or a subreport-elvis municipal feed) — applies to everyone equally.",
"rssSectionUserNote": "Add personal feeds under \"My sources\".", "rssSectionUserNote": "Add personal feeds under \"My sources\".",
"accessDeniedText": "This page manages platform-wide settings and is reserved for administrators.", "accessDeniedText": "This page manages platform-wide settings and is reserved for administrators.",
"digestIntervalLabel": "Digest Interval", "digestIntervalLabel": "Digest Interval",