Files
fotospiel-app/resources/js/admin/mobile/components/SetupChecklist.tsx
Codex Agent 6318aec3cb
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled
Replace checklist badge with check icon
2026-01-22 21:26:24 +01:00

150 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.success : theme.primary}
animation="bouncy"
/>
</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>
);
}