Refresh event overview list UI
This commit is contained in:
@@ -1,13 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
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 { Card } from '@tamagui/card';
|
||||||
import { YStack, XStack } from '@tamagui/stacks';
|
import { YStack, XStack } from '@tamagui/stacks';
|
||||||
import { SizableText as Text } from '@tamagui/text';
|
import { SizableText as Text } from '@tamagui/text';
|
||||||
import { Pressable } from '@tamagui/react-native-web-lite';
|
import { Pressable } from '@tamagui/react-native-web-lite';
|
||||||
import { ScrollView } from '@tamagui/scroll-view';
|
import { ScrollView } from '@tamagui/scroll-view';
|
||||||
import { ToggleGroup } from '@tamagui/toggle-group';
|
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 { useTranslation } from 'react-i18next';
|
||||||
import { MobileShell, HeaderActionButton } from './components/MobileShell';
|
import { MobileShell, HeaderActionButton } from './components/MobileShell';
|
||||||
import { PillBadge, CTAButton, FloatingActionButton, SkeletonCard } from './components/Primitives';
|
import { PillBadge, CTAButton, FloatingActionButton, SkeletonCard } from './components/Primitives';
|
||||||
@@ -351,7 +352,27 @@ function EventsList({
|
|||||||
</YStack>
|
</YStack>
|
||||||
</Card>
|
</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 statusKey = resolveEventStatusKey(event);
|
||||||
const statusLabel =
|
const statusLabel =
|
||||||
statusKey === 'draft'
|
statusKey === 'draft'
|
||||||
@@ -361,132 +382,104 @@ function EventsList({
|
|||||||
: t('events.list.filters.upcoming');
|
: t('events.list.filters.upcoming');
|
||||||
const statusTone = statusKey === 'draft' ? 'warning' : statusKey === 'past' ? 'muted' : 'success';
|
const statusTone = statusKey === 'draft' ? 'warning' : statusKey === 'past' ? 'muted' : 'success';
|
||||||
return (
|
return (
|
||||||
<EventRow
|
<YGroup.Item key={event.id}>
|
||||||
key={event.id}
|
<ListItem
|
||||||
|
hoverTheme
|
||||||
|
pressTheme
|
||||||
|
paddingVertical="$3"
|
||||||
|
paddingHorizontal="$3"
|
||||||
|
backgroundColor={surface}
|
||||||
|
onPress={() => onOpen(event.slug)}
|
||||||
|
title={
|
||||||
|
<EventListItem
|
||||||
event={event}
|
event={event}
|
||||||
text={text}
|
text={text}
|
||||||
muted={muted}
|
muted={muted}
|
||||||
subtle={subtle}
|
subtle={subtle}
|
||||||
border={border}
|
|
||||||
primary={primary}
|
primary={primary}
|
||||||
surface={surface}
|
|
||||||
shadow={shadow}
|
|
||||||
statusLabel={statusLabel}
|
statusLabel={statusLabel}
|
||||||
statusTone={statusTone}
|
statusTone={statusTone}
|
||||||
onOpen={onOpen}
|
|
||||||
onEdit={onEdit}
|
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>
|
</YStack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function EventRow({
|
function EventListItem({
|
||||||
event,
|
event,
|
||||||
text,
|
text,
|
||||||
muted,
|
muted,
|
||||||
subtle,
|
subtle,
|
||||||
border,
|
|
||||||
primary,
|
primary,
|
||||||
surface,
|
|
||||||
shadow,
|
|
||||||
statusLabel,
|
statusLabel,
|
||||||
statusTone,
|
statusTone,
|
||||||
onOpen,
|
|
||||||
onEdit,
|
onEdit,
|
||||||
}: {
|
}: {
|
||||||
event: TenantEvent;
|
event: TenantEvent;
|
||||||
text: string;
|
text: string;
|
||||||
muted: string;
|
muted: string;
|
||||||
subtle: string;
|
subtle: string;
|
||||||
border: string;
|
|
||||||
primary: string;
|
primary: string;
|
||||||
surface: string;
|
|
||||||
shadow: string;
|
|
||||||
statusLabel: string;
|
statusLabel: string;
|
||||||
statusTone: 'success' | 'warning' | 'muted';
|
statusTone: 'success' | 'warning' | 'muted';
|
||||||
onOpen: (slug: string) => void;
|
|
||||||
onEdit?: (slug: string) => void;
|
onEdit?: (slug: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const { t, i18n } = useTranslation('management');
|
const { t, i18n } = useTranslation('management');
|
||||||
const locale = i18n.language;
|
const locale = i18n.language;
|
||||||
const stats = buildEventListStats(event);
|
const stats = buildEventListStats(event);
|
||||||
return (
|
return (
|
||||||
<Card
|
<YStack space="$1.5">
|
||||||
borderRadius={22}
|
<XStack alignItems="center" justifyContent="space-between" space="$2">
|
||||||
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">
|
|
||||||
<Text fontSize="$md" fontWeight="800" color={text}>
|
<Text fontSize="$md" fontWeight="800" color={text}>
|
||||||
{renderName(event.name, t)}
|
{renderName(event.name, t)}
|
||||||
</Text>
|
</Text>
|
||||||
<XStack alignItems="center" space="$2">
|
<XStack alignItems="center" space="$1.5">
|
||||||
<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>
|
|
||||||
<PillBadge tone={statusTone}>{statusLabel}</PillBadge>
|
<PillBadge tone={statusTone}>{statusLabel}</PillBadge>
|
||||||
</YStack>
|
|
||||||
{onEdit ? (
|
{onEdit ? (
|
||||||
<Pressable onPress={() => onEdit(event.slug)}>
|
<Pressable onPress={() => onEdit(event.slug)}>
|
||||||
<Text fontSize="$xl" color={muted}>
|
<Text fontSize="$xs" color={primary} fontWeight="700">
|
||||||
˅
|
{t('events.list.actions.settings', 'Settings')}
|
||||||
</Text>
|
</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
) : null}
|
) : null}
|
||||||
</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>
|
</XStack>
|
||||||
|
<XStack alignItems="center" space="$2" flexWrap="wrap">
|
||||||
<Separator borderColor={border} />
|
<XStack alignItems="center" space="$1.5">
|
||||||
|
<CalendarDays size={12} color={subtle} />
|
||||||
<Pressable onPress={() => onOpen(event.slug)}>
|
<Text fontSize="$xs" color={muted}>
|
||||||
<XStack alignItems="center" justifyContent="flex-start" space="$2">
|
{formatDate(event.event_date, t, locale)}
|
||||||
<Plus size={16} color={primary} />
|
|
||||||
<Text fontSize="$sm" color={primary} fontWeight="700">
|
|
||||||
{t('events.list.actions.open')}
|
|
||||||
</Text>
|
</Text>
|
||||||
</XStack>
|
</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>
|
</YStack>
|
||||||
</Card>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -167,6 +167,29 @@ vi.mock('@tamagui/separator', () => ({
|
|||||||
Separator: () => <div />,
|
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', () => ({
|
vi.mock('@tamagui/stacks', () => ({
|
||||||
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||||
XStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
XStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||||
|
|||||||
Reference in New Issue
Block a user