Replace KPI/tenant wording in admin UI and help

This commit is contained in:
Codex Agent
2026-01-23 10:55:24 +01:00
parent 564e1dbcf4
commit 335087b20f
24 changed files with 346 additions and 75 deletions

View File

@@ -685,7 +685,7 @@ export default function MobileBrandingPage() {
</XStack>
<Text fontSize="$xs" color={muted}>
{form.useDefaultBranding
? t('events.branding.usingDefault', 'Tenant-Branding aktiv')
? t('events.branding.usingDefault', 'Account-Branding aktiv')
: t('events.branding.usingCustom', 'Event-Branding aktiv')}
</Text>
</MobileCard>

View File

@@ -197,7 +197,7 @@ export function DataExportsPanel({
{t('dataExports.request.title', 'Export request')}
</Text>
<Text fontSize="$xs" color={muted}>
{t('dataExports.request.hint', 'Export tenant data or a specific event archive.')}
{t('dataExports.request.hint', 'Export account data or a specific event archive.')}
</Text>
<YStack space="$2">
{!isRecap ? (
@@ -211,7 +211,7 @@ export function DataExportsPanel({
compact
style={{ minWidth: 140, maxWidth: 180 }}
>
<option value="tenant">{t('dataExports.scopes.tenant', 'Tenant')}</option>
<option value="tenant">{t('dataExports.scopes.tenant', 'Account')}</option>
<option value="event">{t('dataExports.scopes.event', 'Event')}</option>
</MobileSelect>
</XStack>
@@ -281,7 +281,7 @@ export function DataExportsPanel({
{t('dataExports.history.title', 'Recent exports')}
</Text>
<Text fontSize="$xs" color={muted}>
{t('dataExports.history.hint', 'Latest 10 exports for your tenant and events.')}
{t('dataExports.history.hint', 'Latest 10 exports for your account and events.')}
</Text>
{loading ? (
<YStack space="$2">
@@ -301,7 +301,7 @@ export function DataExportsPanel({
<Text fontSize="$sm" fontWeight="700" color={textStrong}>
{entry.scope === 'event'
? t('dataExports.scopes.event', 'Event export')
: t('dataExports.scopes.tenant', 'Tenant export')}
: t('dataExports.scopes.tenant', 'Account export')}
</Text>
{entry.event ? (
<Text fontSize="$xs" color={muted}>

View File

@@ -221,7 +221,7 @@ export default function MobileSettingsPage() {
{user?.name ?? user?.email ?? t('settings.session.unknown', 'Benutzer')}
</Text>
{user?.tenant_id ? (
<PillBadge tone="muted">{t('mobileSettings.tenantBadge', 'Tenant #{{id}}', { id: user.tenant_id })}</PillBadge>
<PillBadge tone="muted">{t('mobileSettings.tenantBadge', 'Account #{{id}}', { id: user.tenant_id })}</PillBadge>
) : null}
<XStack space="$2">
<CTAButton label={t('settings.profile.actions.openProfile', 'Profil bearbeiten')} onPress={() => navigate(ADMIN_PROFILE_ACCOUNT_PATH)} />

View File

@@ -0,0 +1,119 @@
import React from 'react';
import { describe, expect, it, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
const navigateMock = vi.fn();
const tMock = (
_key: string,
fallback?: string | Record<string, unknown>,
options?: Record<string, unknown>,
) => {
let value = typeof fallback === 'string' ? fallback : _key;
if (options) {
Object.entries(options).forEach(([key, val]) => {
value = value.replaceAll(`{{${key}}}`, String(val));
});
}
return value;
};
vi.mock('react-router-dom', () => ({
useNavigate: () => navigateMock,
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: tMock }),
initReactI18next: {
type: '3rdParty',
init: () => undefined,
},
}));
vi.mock('../hooks/useBackNavigation', () => ({
useBackNavigation: () => vi.fn(),
}));
vi.mock('../theme', () => ({
useAdminTheme: () => ({
textStrong: '#111827',
text: '#111827',
muted: '#6b7280',
border: '#e5e7eb',
danger: '#b91c1c',
}),
}));
vi.mock('../components/MobileShell', () => ({
MobileShell: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
HeaderActionButton: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
}));
vi.mock('../components/Primitives', () => ({
MobileCard: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
CTAButton: ({ label, onPress }: { label: string; onPress?: () => void }) => (
<button type="button" onClick={onPress}>
{label}
</button>
),
PillBadge: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
SkeletonCard: () => <div>Loading...</div>,
}));
vi.mock('../components/FormControls', () => ({
MobileSelect: ({ children, ...props }: { children: React.ReactNode }) => (
<select {...props}>{children}</select>
),
}));
vi.mock('@tamagui/stacks', () => ({
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
XStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
}));
vi.mock('@tamagui/text', () => ({
SizableText: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
}));
vi.mock('@tamagui/switch', () => ({
Switch: Object.assign(
({ children }: { children: React.ReactNode }) => <div>{children}</div>,
{ Thumb: () => <div /> },
),
}));
vi.mock('@tamagui/react-native-web-lite', () => ({
Pressable: ({
children,
onPress,
...rest
}: {
children: React.ReactNode;
onPress?: () => void;
[key: string]: unknown;
}) => (
<button type="button" onClick={onPress} {...rest}>
{children}
</button>
),
FlatList: ({ data = [], renderItem }: { data?: unknown[]; renderItem?: (info: any) => React.ReactNode }) => (
<div>{renderItem ? data.map((item, index) => renderItem({ item, index })) : null}</div>
),
}));
vi.mock('../../api', () => ({
listTenantDataExports: vi.fn().mockResolvedValue([]),
getEvents: vi.fn().mockResolvedValue([]),
requestTenantDataExport: vi.fn(),
downloadTenantDataExport: vi.fn(),
}));
import MobileDataExportsPage from '../DataExportsPage';
describe('MobileDataExportsPage', () => {
it('renders account scope label', async () => {
render(<MobileDataExportsPage />);
expect(await screen.findByText('Account')).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,152 @@
import React from 'react';
import { describe, expect, it, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
const navigateMock = vi.fn();
const logoutMock = vi.fn();
const tMock = (
_key: string,
fallback?: string | Record<string, unknown>,
options?: Record<string, unknown>,
) => {
let value = typeof fallback === 'string' ? fallback : _key;
if (options) {
Object.entries(options).forEach(([key, val]) => {
value = value.replaceAll(`{{${key}}}`, String(val));
});
}
return value;
};
vi.mock('react-router-dom', () => ({
useNavigate: () => navigateMock,
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: tMock }),
initReactI18next: {
type: '3rdParty',
init: () => undefined,
},
}));
vi.mock('../../auth/context', () => ({
useAuth: () => ({ user: { name: 'Test User', tenant_id: 123 }, logout: logoutMock }),
}));
vi.mock('../../api', () => ({
getNotificationPreferences: vi.fn().mockResolvedValue({ defaults: {}, preferences: {} }),
updateNotificationPreferences: vi.fn(),
}));
vi.mock('../components/MobileShell', () => ({
MobileShell: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
}));
vi.mock('../components/Primitives', () => ({
MobileCard: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
CTAButton: ({ label, onPress }: { label: string; onPress?: () => void }) => (
<button type="button" onClick={onPress}>
{label}
</button>
),
PillBadge: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
}));
vi.mock('../components/MobileInstallBanner', () => ({
MobileInstallBanner: () => null,
}));
vi.mock('../hooks/useAdminPushSubscription', () => ({
useAdminPushSubscription: () => ({ supported: false, permission: 'default', subscribed: false }),
}));
vi.mock('../hooks/useDevicePermissions', () => ({
useDevicePermissions: () => ({
loading: false,
notifications: 'granted',
camera: 'granted',
storage: 'available',
requestPersistentStorage: vi.fn().mockResolvedValue(false),
refresh: vi.fn(),
}),
}));
vi.mock('../hooks/useInstallPrompt', () => ({
useInstallPrompt: () => ({
isInstalled: false,
isStandalone: false,
canInstall: false,
isIos: false,
promptInstall: vi.fn(),
}),
}));
vi.mock('../hooks/useBackNavigation', () => ({
useBackNavigation: () => vi.fn(),
}));
vi.mock('../hooks/useOnlineStatus', () => ({
useOnlineStatus: () => true,
}));
vi.mock('../lib/installBanner', () => ({
getInstallBannerDismissed: () => false,
setInstallBannerDismissed: () => undefined,
shouldShowInstallBanner: () => false,
}));
vi.mock('../lib/mobileTour', () => ({
setTourSeen: () => undefined,
}));
vi.mock('../theme', () => ({
useAdminTheme: () => ({
text: '#111827',
muted: '#6b7280',
border: '#e5e7eb',
danger: '#b91c1c',
}),
}));
vi.mock('@tamagui/stacks', () => ({
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
XStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
}));
vi.mock('@tamagui/text', () => ({
SizableText: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
}));
vi.mock('@tamagui/group', () => ({
YGroup: Object.assign(({ children }: { children: React.ReactNode }) => <div>{children}</div>, {
Item: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
}),
}));
vi.mock('@tamagui/list-item', () => ({
ListItem: ({ title, subTitle }: { title?: React.ReactNode; subTitle?: React.ReactNode }) => (
<div>
{title}
{subTitle}
</div>
),
}));
vi.mock('@tamagui/switch', () => ({
Switch: Object.assign(
({ children }: { children: React.ReactNode }) => <div>{children}</div>,
{ Thumb: () => <div /> },
),
}));
import MobileSettingsPage from '../SettingsPage';
describe('MobileSettingsPage', () => {
it('renders account badge for the current user', async () => {
render(<MobileSettingsPage />);
expect(await screen.findByText('Account #123')).toBeInTheDocument();
});
});