rework of the event admin UI
This commit is contained in:
36
resources/js/admin/lib/branding.ts
Normal file
36
resources/js/admin/lib/branding.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { TenantEvent } from '../api';
|
||||
|
||||
export type BrandingPalette = {
|
||||
colors: string[];
|
||||
font?: string;
|
||||
};
|
||||
|
||||
export function extractBrandingPalette(settings: TenantEvent['settings'] | null | undefined): BrandingPalette {
|
||||
const colors: string[] = [];
|
||||
let font: string | undefined;
|
||||
|
||||
if (settings && typeof settings === 'object') {
|
||||
const brand = (settings as Record<string, unknown>).branding;
|
||||
if (brand && typeof brand === 'object') {
|
||||
const colorPalette = (brand as Record<string, unknown>).colors;
|
||||
if (colorPalette && typeof colorPalette === 'object') {
|
||||
const paletteRecord = colorPalette as Record<string, unknown>;
|
||||
for (const key of Object.keys(paletteRecord)) {
|
||||
const value = paletteRecord[key];
|
||||
if (typeof value === 'string' && value.trim().length) {
|
||||
colors.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
const fontValue = (brand as Record<string, unknown>).font_family;
|
||||
if (typeof fontValue === 'string' && fontValue.trim()) {
|
||||
font = fontValue.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
colors,
|
||||
font,
|
||||
};
|
||||
}
|
||||
29
resources/js/admin/lib/emotions.ts
Normal file
29
resources/js/admin/lib/emotions.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { TenantEmotion } from '../api';
|
||||
|
||||
export function filterEmotionsByEventType(
|
||||
emotions: TenantEmotion[],
|
||||
eventTypeId: number | null,
|
||||
): TenantEmotion[] {
|
||||
if (!Array.isArray(emotions) || emotions.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const filtered = emotions.filter((emotion) => {
|
||||
if (!emotion.is_active) {
|
||||
return false;
|
||||
}
|
||||
if (!eventTypeId) {
|
||||
return true;
|
||||
}
|
||||
if (!Array.isArray(emotion.event_types) || emotion.event_types.length === 0) {
|
||||
return true;
|
||||
}
|
||||
return emotion.event_types.some((type) => type?.id === eventTypeId);
|
||||
});
|
||||
|
||||
return filtered.sort((a, b) => {
|
||||
const left = typeof a.sort_order === 'number' ? a.sort_order : Number.MAX_SAFE_INTEGER;
|
||||
const right = typeof b.sort_order === 'number' ? b.sort_order : Number.MAX_SAFE_INTEGER;
|
||||
return left - right;
|
||||
});
|
||||
}
|
||||
60
resources/js/admin/lib/eventTabs.ts
Normal file
60
resources/js/admin/lib/eventTabs.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
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,
|
||||
} 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: 'photobooth',
|
||||
label: translate('eventMenu.photobooth', 'Photobooth'),
|
||||
href: ADMIN_EVENT_PHOTOBOOTH_PATH(event.slug),
|
||||
},
|
||||
];
|
||||
}
|
||||
82
resources/js/admin/lib/events.ts
Normal file
82
resources/js/admin/lib/events.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import type { TenantEvent } from '../api';
|
||||
|
||||
function isTranslatableName(value: unknown): value is Record<string, string> {
|
||||
return Boolean(value && typeof value === 'object');
|
||||
}
|
||||
|
||||
export function resolveEventDisplayName(event?: TenantEvent | null): string {
|
||||
if (!event) {
|
||||
return 'Event';
|
||||
}
|
||||
|
||||
const { name, slug } = event;
|
||||
|
||||
if (typeof name === 'string' && name.trim().length > 0) {
|
||||
return name;
|
||||
}
|
||||
|
||||
if (isTranslatableName(name)) {
|
||||
const match = Object.values(name).find(
|
||||
(entry) => typeof entry === 'string' && entry.trim().length > 0,
|
||||
);
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
return slug ?? 'Event';
|
||||
}
|
||||
|
||||
export function formatEventDate(value?: string | null, locale = 'de-DE'): string | null {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return new Intl.DateTimeFormat(locale, {
|
||||
day: '2-digit',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
}).format(date);
|
||||
} catch {
|
||||
return date.toISOString().slice(0, 10);
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveEngagementMode(event?: TenantEvent | null): 'tasks' | 'photo_only' | null {
|
||||
if (!event) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (event.engagement_mode) {
|
||||
return event.engagement_mode;
|
||||
}
|
||||
|
||||
if (event.settings && typeof event.settings === 'object') {
|
||||
const mode = event.settings.engagement_mode;
|
||||
if (mode === 'tasks' || mode === 'photo_only') {
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function formatEventStatusLabel(
|
||||
status: TenantEvent['status'] | null,
|
||||
t: (key: string, options?: Record<string, unknown>) => string,
|
||||
): string {
|
||||
const map: Record<string, { key: string; fallback: string }> = {
|
||||
published: { key: 'events.status.published', fallback: 'Veröffentlicht' },
|
||||
draft: { key: 'events.status.draft', fallback: 'Entwurf' },
|
||||
archived: { key: 'events.status.archived', fallback: 'Archiviert' },
|
||||
};
|
||||
|
||||
const target = map[status ?? 'draft'] ?? map.draft;
|
||||
return t(target.key, { defaultValue: target.fallback });
|
||||
}
|
||||
Reference in New Issue
Block a user