Refresh event overview list UI
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-01-20 13:21:39 +01:00
parent f88aa40315
commit 9d8f01d294
2 changed files with 120 additions and 104 deletions

View File

@@ -1,13 +1,14 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { CalendarDays, MapPin, Plus, Search, Camera, Users, Sparkles } from 'lucide-react';
import { CalendarDays, MapPin, Plus, Search, Camera, Users, Sparkles, ChevronRight } from 'lucide-react';
import { Card } from '@tamagui/card';
import { YStack, XStack } from '@tamagui/stacks';
import { SizableText as Text } from '@tamagui/text';
import { Pressable } from '@tamagui/react-native-web-lite';
import { ScrollView } from '@tamagui/scroll-view';
import { ToggleGroup } from '@tamagui/toggle-group';
import { Separator } from '@tamagui/separator';
import { ListItem } from '@tamagui/list-item';
import { YGroup } from '@tamagui/group';
import { useTranslation } from 'react-i18next';
import { MobileShell, HeaderActionButton } from './components/MobileShell';
import { PillBadge, CTAButton, FloatingActionButton, SkeletonCard } from './components/Primitives';
@@ -351,7 +352,27 @@ function EventsList({
</YStack>
</Card>
) : (
filteredEvents.map((event) => {
<Card
borderRadius={22}
borderWidth={2}
borderColor={border}
backgroundColor={surface}
padding="$2"
shadowColor={shadow}
shadowOpacity={0.14}
shadowRadius={14}
shadowOffset={{ width: 0, height: 8 }}
>
<YGroup
{...({
borderRadius: 18,
borderWidth: 1,
borderColor: border,
overflow: 'hidden',
backgroundColor: surface,
} as any)}
>
{filteredEvents.map((event) => {
const statusKey = resolveEventStatusKey(event);
const statusLabel =
statusKey === 'draft'
@@ -361,132 +382,104 @@ function EventsList({
: t('events.list.filters.upcoming');
const statusTone = statusKey === 'draft' ? 'warning' : statusKey === 'past' ? 'muted' : 'success';
return (
<EventRow
key={event.id}
<YGroup.Item key={event.id}>
<ListItem
hoverTheme
pressTheme
paddingVertical="$3"
paddingHorizontal="$3"
backgroundColor={surface}
onPress={() => onOpen(event.slug)}
title={
<EventListItem
event={event}
text={text}
muted={muted}
subtle={subtle}
border={border}
primary={primary}
surface={surface}
shadow={shadow}
statusLabel={statusLabel}
statusTone={statusTone}
onOpen={onOpen}
onEdit={onEdit}
/>
}
iconAfter={
<XStack alignItems="center" space="$1.5">
<Text fontSize="$xs" color={primary} fontWeight="700">
{t('events.list.actions.open')}
</Text>
<ChevronRight size={16} color={primary} />
</XStack>
}
/>
</YGroup.Item>
);
})
})}
</YGroup>
</Card>
)}
</YStack>
);
}
function EventRow({
function EventListItem({
event,
text,
muted,
subtle,
border,
primary,
surface,
shadow,
statusLabel,
statusTone,
onOpen,
onEdit,
}: {
event: TenantEvent;
text: string;
muted: string;
subtle: string;
border: string;
primary: string;
surface: string;
shadow: string;
statusLabel: string;
statusTone: 'success' | 'warning' | 'muted';
onOpen: (slug: string) => void;
onEdit?: (slug: string) => void;
}) {
const { t, i18n } = useTranslation('management');
const locale = i18n.language;
const stats = buildEventListStats(event);
return (
<Card
borderRadius={22}
borderWidth={2}
borderColor={border}
backgroundColor={surface}
padding="$3"
shadowColor={shadow}
shadowOpacity={0.14}
shadowRadius={14}
shadowOffset={{ width: 0, height: 8 }}
>
<YStack space="$2.5">
<XStack justifyContent="space-between" alignItems="flex-start" space="$2">
<YStack space="$1">
<YStack space="$1.5">
<XStack alignItems="center" justifyContent="space-between" space="$2">
<Text fontSize="$md" fontWeight="800" color={text}>
{renderName(event.name, t)}
</Text>
<XStack alignItems="center" space="$2">
<CalendarDays size={14} color={subtle} />
<Text fontSize="$sm" color={muted}>
{formatDate(event.event_date, t, locale)}
</Text>
</XStack>
<XStack alignItems="center" space="$2">
<MapPin size={14} color={subtle} />
<Text fontSize="$sm" color={muted}>
{resolveLocation(event, t)}
</Text>
</XStack>
<XStack alignItems="center" space="$1.5">
<PillBadge tone={statusTone}>{statusLabel}</PillBadge>
</YStack>
{onEdit ? (
<Pressable onPress={() => onEdit(event.slug)}>
<Text fontSize="$xl" color={muted}>
˅
<Text fontSize="$xs" color={primary} fontWeight="700">
{t('events.list.actions.settings', 'Settings')}
</Text>
</Pressable>
) : null}
</XStack>
<XStack alignItems="center" space="$2" flexWrap="wrap">
<EventStatChip
icon={Camera}
label={t('events.list.stats.photos')}
value={stats.photos}
muted={subtle}
/>
<EventStatChip
icon={Users}
label={t('events.list.stats.guests')}
value={stats.guests}
muted={subtle}
/>
<EventStatChip
icon={Sparkles}
label={t('events.list.stats.tasks')}
value={stats.tasks}
muted={subtle}
/>
</XStack>
<Separator borderColor={border} />
<Pressable onPress={() => onOpen(event.slug)}>
<XStack alignItems="center" justifyContent="flex-start" space="$2">
<Plus size={16} color={primary} />
<Text fontSize="$sm" color={primary} fontWeight="700">
{t('events.list.actions.open')}
<XStack alignItems="center" space="$2" flexWrap="wrap">
<XStack alignItems="center" space="$1.5">
<CalendarDays size={12} color={subtle} />
<Text fontSize="$xs" color={muted}>
{formatDate(event.event_date, t, locale)}
</Text>
</XStack>
</Pressable>
<XStack alignItems="center" space="$1.5">
<MapPin size={12} color={subtle} />
<Text fontSize="$xs" color={muted}>
{resolveLocation(event, t)}
</Text>
</XStack>
</XStack>
<XStack alignItems="center" space="$2" flexWrap="wrap">
<EventStatChip icon={Camera} label={t('events.list.stats.photos')} value={stats.photos} muted={subtle} />
<EventStatChip icon={Users} label={t('events.list.stats.guests')} value={stats.guests} muted={subtle} />
<EventStatChip icon={Sparkles} label={t('events.list.stats.tasks')} value={stats.tasks} muted={subtle} />
</XStack>
</YStack>
</Card>
);
}

View File

@@ -167,6 +167,29 @@ vi.mock('@tamagui/separator', () => ({
Separator: () => <div />,
}));
vi.mock('@tamagui/list-item', () => ({
ListItem: ({
title,
iconAfter,
onPress,
}: {
title?: React.ReactNode;
iconAfter?: React.ReactNode;
onPress?: () => void;
}) => (
<div onClick={onPress}>
{title}
{iconAfter}
</div>
),
}));
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/stacks', () => ({
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
XStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,