Update dashboard KPIs for live show and auto-approval
This commit is contained in:
@@ -36,6 +36,10 @@
|
||||
"lowCredits": "Mehr Kontingent buchen empfohlen"
|
||||
}
|
||||
},
|
||||
"kpis": {
|
||||
"liveShowApproved": "Live-Show freigegeben",
|
||||
"likesTotal": "Likes gesamt"
|
||||
},
|
||||
"liveNow": {
|
||||
"title": "Während des Events",
|
||||
"description": "Direkter Zugriff, solange {{count}} Event(s) live sind.",
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
"lowCredits": "Add bundle soon"
|
||||
}
|
||||
},
|
||||
"kpis": {
|
||||
"liveShowApproved": "Live Show approved",
|
||||
"likesTotal": "Likes total"
|
||||
},
|
||||
"liveNow": {
|
||||
"title": "During the event",
|
||||
"description": "Quick actions while {{count}} event(s) are live.",
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { AlertCircle, Bell, CalendarDays, Camera, CheckCircle2, ChevronRight, Circle, Download, Image as ImageIcon, Layout, ListTodo, Megaphone, QrCode, Settings, ShieldCheck, Sparkles, TrendingUp, Tv, Users } from 'lucide-react';
|
||||
import { AlertCircle, Bell, CalendarDays, Camera, CheckCircle2, ChevronRight, Circle, Download, Heart, Image as ImageIcon, Layout, ListTodo, Megaphone, QrCode, Settings, ShieldCheck, Sparkles, TrendingUp, Tv, Users } from 'lucide-react';
|
||||
import { Button } from '@tamagui/button';
|
||||
import { Card } from '@tamagui/card';
|
||||
import { YGroup } from '@tamagui/group';
|
||||
@@ -19,7 +19,7 @@ import toast from 'react-hot-toast';
|
||||
import { MobileShell } from './components/MobileShell';
|
||||
import { ADMIN_EVENTS_PATH, adminPath } from '../constants';
|
||||
import { useEventContext } from '../context/EventContext';
|
||||
import { getEventStats, EventStats, TenantEvent, getEventPhotos, TenantPhoto, updateEvent } from '../api';
|
||||
import { getEventStats, EventStats, TenantEvent, getEventPhotos, TenantPhoto, updateEvent, getLiveShowQueue } from '../api';
|
||||
import { formatEventDate } from '../lib/events';
|
||||
import { useAuth } from '../auth/context';
|
||||
import { ADMIN_ACTION_COLORS, useAdminTheme } from './theme';
|
||||
@@ -164,6 +164,19 @@ export default function MobileDashboardPage() {
|
||||
},
|
||||
});
|
||||
|
||||
const { data: liveShowApprovedCount = 0 } = useQuery({
|
||||
queryKey: ['mobile', 'dashboard', 'live-show-approved', activeEvent?.slug],
|
||||
enabled: Boolean(activeEvent?.slug),
|
||||
queryFn: async () => {
|
||||
if (!activeEvent?.slug) return 0;
|
||||
const result = await getLiveShowQueue(activeEvent.slug, { liveStatus: 'approved', perPage: 1 });
|
||||
if (result.photos.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
return result.meta.total ?? result.photos.length;
|
||||
},
|
||||
});
|
||||
|
||||
const locale = i18n.language?.startsWith('en') ? 'en-GB' : 'de-DE';
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -232,7 +245,7 @@ export default function MobileDashboardPage() {
|
||||
|
||||
{/* 2. PULSE STRIP */}
|
||||
<Separator backgroundColor={theme.border} opacity={0.6} />
|
||||
<PulseStrip event={activeEvent} stats={stats} />
|
||||
<PulseStrip event={activeEvent} stats={stats} liveShowApprovedCount={liveShowApprovedCount} />
|
||||
</YStack>
|
||||
</DashboardCard>
|
||||
|
||||
@@ -559,15 +572,17 @@ function LifecycleHero({
|
||||
);
|
||||
}
|
||||
|
||||
function PulseStrip({ event, stats }: any) {
|
||||
function PulseStrip({ event, stats, liveShowApprovedCount }: any) {
|
||||
const theme = useAdminTheme();
|
||||
const { t } = useTranslation('management');
|
||||
const { t } = useTranslation(['management', 'dashboard']);
|
||||
const uploadCount = stats?.uploads_total ?? event?.photo_count ?? 0;
|
||||
const guestCount = event?.active_invites_count ?? event?.total_invites_count ?? 0;
|
||||
const pendingCount = stats?.pending_photos ?? event?.pending_photo_count ?? 0;
|
||||
const likesTotal = stats?.likes_total ?? stats?.likes ?? 0;
|
||||
const showPending = (event?.settings?.guest_upload_visibility ?? 'review') !== 'immediate';
|
||||
const approvedCount = typeof liveShowApprovedCount === 'number' ? liveShowApprovedCount : 0;
|
||||
|
||||
return (
|
||||
<KpiStrip items={[
|
||||
const items = [
|
||||
{
|
||||
icon: ImageIcon,
|
||||
value: uploadCount,
|
||||
@@ -578,17 +593,31 @@ function PulseStrip({ event, stats }: any) {
|
||||
icon: Users,
|
||||
value: guestCount,
|
||||
label: t('management:events.list.stats.guests', 'Guests'),
|
||||
tone: 'neutral',
|
||||
tone: 'neutral' as const,
|
||||
},
|
||||
{
|
||||
icon: Tv,
|
||||
value: approvedCount,
|
||||
label: t('dashboard:kpis.liveShowApproved', 'Live Show approved'),
|
||||
color: ADMIN_ACTION_COLORS.liveShow,
|
||||
},
|
||||
showPending
|
||||
? {
|
||||
icon: ShieldCheck,
|
||||
value: pendingCount,
|
||||
label: t('management:photos.filters.pending', 'Pending'),
|
||||
note: pendingCount > 0 ? t('management:common.actionNeeded', 'Review') : undefined,
|
||||
color: pendingCount > 0 ? '#8B5CF6' : theme.textMuted,
|
||||
}
|
||||
: {
|
||||
icon: Heart,
|
||||
value: likesTotal,
|
||||
label: t('dashboard:kpis.likesTotal', 'Likes total'),
|
||||
color: ADMIN_ACTION_COLORS.images,
|
||||
},
|
||||
]} />
|
||||
);
|
||||
];
|
||||
|
||||
return <KpiStrip items={items} />;
|
||||
}
|
||||
|
||||
function UnifiedToolGrid({ event, navigate, permissions, isMember, isCompleted }: any) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { render, screen, waitFor, within } from '@testing-library/react';
|
||||
import { ADMIN_EVENTS_PATH } from '../../constants';
|
||||
|
||||
const fixtures = vi.hoisted(() => ({
|
||||
@@ -148,7 +148,7 @@ vi.mock('../components/Primitives', () => ({
|
||||
MobileCard: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
CTAButton: ({ label }: { label: string }) => <button type="button">{label}</button>,
|
||||
KpiStrip: ({ items }: { items: Array<{ label: string; value: string | number }> }) => (
|
||||
<div>
|
||||
<div data-testid="kpi-strip">
|
||||
{items.map((item) => (
|
||||
<span key={item.label}>{item.label}</span>
|
||||
))}
|
||||
@@ -324,6 +324,7 @@ describe('MobileDashboardPage', () => {
|
||||
fixtures.activePackage.remaining_events = 3;
|
||||
fixtures.event.tasks_count = 4;
|
||||
fixtures.event.engagement_mode = undefined;
|
||||
fixtures.event.settings = { location: 'Berlin' };
|
||||
navigateMock.mockClear();
|
||||
window.sessionStorage.clear();
|
||||
});
|
||||
@@ -368,9 +369,23 @@ describe('MobileDashboardPage', () => {
|
||||
it('shows the activity pulse strip', () => {
|
||||
render(<MobileDashboardPage />);
|
||||
|
||||
expect(screen.getAllByText('Photos').length).toBeGreaterThan(0);
|
||||
expect(screen.getAllByText('Guests').length).toBeGreaterThan(0);
|
||||
expect(screen.getAllByText('Pending').length).toBeGreaterThan(0);
|
||||
const strip = screen.getByTestId('kpi-strip');
|
||||
|
||||
expect(within(strip).getAllByText('Photos').length).toBeGreaterThan(0);
|
||||
expect(within(strip).getAllByText('Guests').length).toBeGreaterThan(0);
|
||||
expect(within(strip).getAllByText('Pending').length).toBeGreaterThan(0);
|
||||
expect(within(strip).getAllByText('Live Show approved').length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('replaces pending with likes when auto-approval is enabled', () => {
|
||||
fixtures.event.settings = { location: 'Berlin', guest_upload_visibility: 'immediate' };
|
||||
|
||||
render(<MobileDashboardPage />);
|
||||
|
||||
const strip = screen.getByTestId('kpi-strip');
|
||||
|
||||
expect(within(strip).queryByText('Pending')).not.toBeInTheDocument();
|
||||
expect(within(strip).getAllByText('Likes total').length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('shows shortcut sections for members', () => {
|
||||
|
||||
Reference in New Issue
Block a user