149 lines
5.3 KiB
TypeScript
149 lines
5.3 KiB
TypeScript
import React from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Card } from '@tamagui/card';
|
|
import { YGroup } from '@tamagui/group';
|
|
import { ListItem } from '@tamagui/list-item';
|
|
import { Progress } from '@tamagui/progress';
|
|
import { YStack, XStack } from '@tamagui/stacks';
|
|
import { SizableText as Text } from '@tamagui/text';
|
|
import { Pressable } from '@tamagui/react-native-web-lite';
|
|
import { CheckCircle2, Circle, ChevronDown, ChevronUp } from 'lucide-react';
|
|
import { Separator } from 'tamagui';
|
|
import { useAdminTheme } from '../theme';
|
|
import { ReadinessStep } from '../hooks/useEventReadiness';
|
|
import { adminPath } from '../../constants';
|
|
import { PillBadge } from './Primitives';
|
|
|
|
export function SetupChecklist({
|
|
steps,
|
|
title,
|
|
variant = 'card',
|
|
}: {
|
|
steps: ReadinessStep[];
|
|
title: string;
|
|
variant?: 'card' | 'inline';
|
|
}) {
|
|
const theme = useAdminTheme();
|
|
const { t } = useTranslation('dashboard');
|
|
const navigate = useNavigate();
|
|
const isAllComplete = steps.every(s => s.isComplete);
|
|
const [collapsed, setCollapsed] = React.useState(true);
|
|
const isInline = variant === 'inline';
|
|
|
|
const completedCount = steps.filter(s => s.isComplete).length;
|
|
const totalSteps = steps.length;
|
|
const progressValue = totalSteps > 0 ? Math.round((completedCount / totalSteps) * 100) : 0;
|
|
|
|
const content = (
|
|
<YStack>
|
|
<Pressable onPress={() => setCollapsed(!collapsed)}>
|
|
<YStack padding="$3" paddingVertical="$2.5" space="$2">
|
|
<XStack alignItems="center" justifyContent="space-between">
|
|
<XStack alignItems="center" space="$2">
|
|
<Text fontSize="$xs" fontWeight="700" color={theme.muted} textTransform="uppercase" letterSpacing={1}>
|
|
{title}
|
|
</Text>
|
|
{isAllComplete ? (
|
|
<CheckCircle2 size={14} color={theme.successText} />
|
|
) : (
|
|
<PillBadge tone="warning">{t('readiness.pending', 'Noch offen')}</PillBadge>
|
|
)}
|
|
</XStack>
|
|
|
|
<XStack alignItems="center" space="$2">
|
|
<Text fontSize="$xs" color={theme.muted} fontWeight="600">
|
|
{completedCount}/{steps.length}
|
|
</Text>
|
|
{collapsed ? <ChevronDown size={16} color={theme.muted} /> : <ChevronUp size={16} color={theme.muted} />}
|
|
</XStack>
|
|
</XStack>
|
|
|
|
<Progress
|
|
value={progressValue}
|
|
size="$1"
|
|
backgroundColor={theme.surfaceMuted}
|
|
height={6}
|
|
>
|
|
<Progress.Indicator
|
|
backgroundColor={isAllComplete ? theme.successText : theme.primary}
|
|
/>
|
|
</Progress>
|
|
</YStack>
|
|
</Pressable>
|
|
|
|
{!collapsed && (
|
|
<YStack>
|
|
<Separator backgroundColor={theme.border} opacity={0.6} />
|
|
<YGroup {...({ borderRadius: "$4", borderWidth: 1, borderColor: theme.border, overflow: "hidden" } as any)}>
|
|
{steps.map((step, index) => {
|
|
const isNext = !step.isComplete && steps.slice(0, index).every(s => s.isComplete);
|
|
|
|
return (
|
|
<YGroup.Item key={step.id}>
|
|
<ListItem
|
|
hoverTheme
|
|
pressTheme
|
|
paddingVertical="$2"
|
|
paddingHorizontal="$3"
|
|
backgroundColor={isNext ? theme.surfaceMuted : 'transparent'}
|
|
onPress={() => navigate(adminPath(step.targetPath))}
|
|
title={
|
|
<XStack alignItems="center" space="$2.5">
|
|
{step.isComplete ? (
|
|
<CheckCircle2 size={18} color={theme.successText} />
|
|
) : isNext ? (
|
|
<Circle size={18} color={theme.primary} strokeWidth={2.5} />
|
|
) : (
|
|
<Circle size={18} color={theme.border} />
|
|
)}
|
|
<Text
|
|
fontSize="$sm"
|
|
fontWeight={isNext ? '700' : '500'}
|
|
color={step.isComplete ? theme.muted : theme.textStrong}
|
|
textDecorationLine={step.isComplete ? 'line-through' : 'none'}
|
|
>
|
|
{step.label}
|
|
</Text>
|
|
</XStack>
|
|
}
|
|
subTitle={
|
|
step.description && !step.isComplete ? (
|
|
<Text fontSize="$xs" color={theme.muted}>
|
|
{step.description}
|
|
</Text>
|
|
) : undefined
|
|
}
|
|
iconAfter={isNext ? <PillBadge tone="success">Start</PillBadge> : undefined}
|
|
/>
|
|
</YGroup.Item>
|
|
);
|
|
})}
|
|
</YGroup>
|
|
</YStack>
|
|
)}
|
|
</YStack>
|
|
);
|
|
|
|
if (isInline) {
|
|
return content;
|
|
}
|
|
|
|
return (
|
|
<Card
|
|
backgroundColor={theme.surface}
|
|
borderRadius={20}
|
|
borderWidth={1}
|
|
borderColor={theme.border}
|
|
padding="$0"
|
|
overflow="hidden"
|
|
shadowColor={theme.shadow}
|
|
shadowOpacity={0.16}
|
|
shadowRadius={16}
|
|
shadowOffset={{ width: 0, height: 10 }}
|
|
>
|
|
{content}
|
|
</Card>
|
|
);
|
|
}
|