Files
fotospiel-app/resources/js/admin/lib/eventTabs.ts
Codex Agent 9bde8f3f32 Neue Branding-Page und Gäste-PWA reagiert nun auf Branding-Einstellungen vom event-admin. Implemented local Google Fonts pipeline and admin UI selects for branding and invites.
- Added fonts:sync-google command (uses GOOGLE_FONTS_API_KEY, generates /public/fonts/google files, manifest, CSS, cache flush) and
    exposed manifest via new GET /api/v1/tenant/fonts endpoint with fallbacks for existing local fonts.
  - Imported generated fonts CSS, added API client + font loader hook, and wired branding page font fields to searchable selects (with
    custom override) that auto-load selected fonts.
  - Invites layout editor now offers font selection per element with runtime font loading for previews/export alignment.
  - New tests cover font sync command and font manifest API.

  Tests run: php artisan test --filter=Fonts --testsuite=Feature.
  Note: repository already has other modified files (e.g., EventPublicController, SettingsStoreRequest, guest components, etc.); left
  untouched. Run php artisan fonts:sync-google after setting the API key to populate /public/fonts/google.
2025-11-25 19:31:52 +01:00

73 lines
2.0 KiB
TypeScript

import type { TenantEvent } from '../api';
import {
ADMIN_EVENT_INVITES_PATH,
ADMIN_EVENT_PHOTOBOOTH_PATH,
ADMIN_EVENT_PHOTOS_PATH,
ADMIN_EVENT_TASKS_PATH,
ADMIN_EVENT_VIEW_PATH,
ADMIN_EVENT_RECAP_PATH,
ADMIN_EVENT_BRANDING_PATH,
} from '../constants';
export type EventTabCounts = Partial<{
photos: number;
tasks: number;
invites: number;
}>;
type Translator = (key: string, fallback: string) => string;
export function buildEventTabs(event: TenantEvent, translate: Translator, counts: EventTabCounts = {}) {
if (!event.slug) {
return [];
}
const formatBadge = (value?: number | null): number | undefined => {
if (typeof value === 'number' && Number.isFinite(value)) {
return value;
}
return undefined;
};
return [
{
key: 'overview',
label: translate('eventMenu.summary', 'Übersicht'),
href: ADMIN_EVENT_VIEW_PATH(event.slug),
},
{
key: 'photos',
label: translate('eventMenu.photos', 'Uploads'),
href: ADMIN_EVENT_PHOTOS_PATH(event.slug),
badge: formatBadge(counts.photos ?? event.photo_count ?? event.pending_photo_count ?? null),
},
{
key: 'tasks',
label: translate('eventMenu.tasks', 'Aufgaben'),
href: ADMIN_EVENT_TASKS_PATH(event.slug),
badge: formatBadge(counts.tasks ?? event.tasks_count ?? null),
},
{
key: 'invites',
label: translate('eventMenu.invites', 'Einladungen'),
href: ADMIN_EVENT_INVITES_PATH(event.slug),
badge: formatBadge(counts.invites ?? event.active_invites_count ?? event.total_invites_count ?? null),
},
{
key: 'branding',
label: translate('eventMenu.branding', 'Branding'),
href: ADMIN_EVENT_BRANDING_PATH(event.slug),
},
{
key: 'photobooth',
label: translate('eventMenu.photobooth', 'Photobooth'),
href: ADMIN_EVENT_PHOTOBOOTH_PATH(event.slug),
},
{
key: 'recap',
label: translate('eventMenu.recap', 'Nachbereitung'),
href: ADMIN_EVENT_RECAP_PATH(event.slug),
},
];
}