Restructure event tasks layout
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-22 20:50:38 +01:00
parent db5fea9f2a
commit 32644eb41e
3 changed files with 200 additions and 140 deletions

View File

@@ -584,6 +584,7 @@
"imported": "Fotoaufgabenpaket importiert",
"saveTask": "Fotoaufgabe speichern",
"add": "Hinzufügen",
"assignedTitle": "Aufgabenliste",
"emptyHint": "Lege jetzt Fotoaufgaben an oder importiere ein Paket.",
"emptyTitle": "Noch keine Fotoaufgaben",
"emptyBody": "Lege Fotoaufgaben an oder importiere ein Paket für dein Event.",

View File

@@ -580,6 +580,7 @@
"imported": "Photo task pack imported",
"saveTask": "Save photo task",
"add": "Add",
"assignedTitle": "Task list",
"emptyHint": "Add photo tasks or import a pack.",
"emptyTitle": "No photo tasks yet",
"emptyBody": "Create photo tasks or import a pack for your event.",

View File

@@ -701,10 +701,15 @@ export default function MobileEventTasksPage() {
</YStack>
) : (
<YStack space="$2">
<XStack alignItems="center" flexWrap="wrap" space="$2">
<Text fontSize="$sm" color={muted}>
{t('events.tasks.count', '{{count}} photo tasks', { count: filteredTasks.length })}
</Text>
<XStack alignItems="baseline" justifyContent="space-between" flexWrap="wrap" space="$2">
<XStack alignItems="baseline" space="$2" flexWrap="wrap">
<Text fontSize="$sm" fontWeight="800" color={text}>
{t('events.tasks.assignedTitle', 'Task list')}
</Text>
<Text fontSize="$xs" color={muted}>
{t('events.tasks.count', '{{count}} photo tasks', { count: filteredTasks.length })}
</Text>
</XStack>
{typeof remainingTasks === 'number' && typeof maxTasks === 'number' ? (
<Tag
label={t('events.tasks.remainingIndicator', '{{count}} / {{total}} tasks remaining', {
@@ -1041,81 +1046,6 @@ export default function MobileEventTasksPage() {
</MobileCard>
) : null}
{!loading ? (
<MobileCard space="$2" padding="$3">
<XStack alignItems="center" justifyContent="space-between" space="$2" flexWrap="wrap">
<YStack space="$1" flex={1} minWidth={180}>
<XStack alignItems="center" space="$2" flexWrap="wrap">
<Text fontSize="$sm" fontWeight="800" color={text}>
{t('events.tasks.toggle.title', 'Photo task mode')}
</Text>
<PillBadge tone={tasksEnabled ? 'success' : 'warning'}>
{tasksEnabled
? t('events.tasks.toggle.active', 'ACTIVE')
: t('events.tasks.toggle.inactive', 'INACTIVE')}
</PillBadge>
</XStack>
</YStack>
<XStack alignItems="center" space="$2">
<Pressable
onPress={() => setShowTaskDetails((prev) => !prev)}
aria-label={t(
'events.tasks.toggle.description',
'Control whether guests see mission cards and prompts.'
)}
>
<XStack
width={32}
height={32}
borderRadius={10}
alignItems="center"
justifyContent="center"
borderWidth={1}
borderColor={showTaskDetails ? withAlpha(primary, 0.45) : border}
backgroundColor={showTaskDetails ? withAlpha(primary, 0.12) : surfaceMuted}
>
<Info size={14} color={showTaskDetails ? primary : muted} />
</XStack>
</Pressable>
</XStack>
</XStack>
{showTaskDetails ? (
<YStack space="$2">
<Text fontSize="$xs" color={muted}>
{t(
'events.tasks.toggle.description',
'Control whether guests see mission cards and prompts.'
)}
</Text>
<XStack alignItems="center" justifyContent="space-between" space="$2">
<Text fontSize="$sm" fontWeight="700" color={text}>
{t('events.tasks.toggle.switchLabel', 'Photo tasks for guests')}
</Text>
<Switch
size="$4"
checked={tasksEnabled}
onCheckedChange={handleTasksToggle}
aria-label={t('events.tasks.toggle.switchLabel', 'Photo tasks for guests')}
disabled={!canManageTasks || tasksToggleBusy}
>
<Switch.Thumb />
</Switch>
</XStack>
<Text fontSize="$xs" color={muted}>
{tasksEnabled
? t('events.tasks.toggle.onLabel', 'Mission cards active')
: t('events.tasks.toggle.offLabel', 'Photo feed only')}
</Text>
</YStack>
) : null}
{isMember && !canManageTasks ? (
<Text fontSize="$xs" color={muted}>
{t('events.tasks.toggle.permissionHint', 'You do not have permission to change photo tasks.')}
</Text>
) : null}
</MobileCard>
) : null}
{loading ? (
<YStack space="$2">
{Array.from({ length: 4 }).map((_, idx) => (
@@ -1123,79 +1053,207 @@ export default function MobileEventTasksPage() {
))}
</YStack>
) : (
<Tabs value={activeTab} onValueChange={(value) => setActiveTab(value as TaskSectionKey)}>
<Tabs.List>
<Tabs.Tab value="assigned">{t('events.tasks.tabs.tasks', 'Tasks')}</Tabs.Tab>
<Tabs.Tab value="library">{t('events.tasks.tabs.library', 'Task Library')}</Tabs.Tab>
<Tabs.Tab value="emotions">{t('events.tasks.tabs.emotions', 'Emotions')}</Tabs.Tab>
<Tabs.Tab value="collections">{t('events.tasks.tabs.collections', 'Collections')}</Tabs.Tab>
</Tabs.List>
<Tabs.Content value="assigned" paddingTop="$2">
<YStack space="$2">
<YStack className="admin-sticky-toolbar">
<Card
borderRadius={20}
borderWidth={2}
borderColor={stickyBorder}
backgroundColor={stickySurface}
padding="$3"
shadowColor={stickyShadow}
shadowOpacity={0.16}
shadowRadius={16}
shadowOffset={{ width: 0, height: 10 }}
>
<Card
borderRadius={24}
borderWidth={2}
borderColor={border}
backgroundColor={surface}
padding="$3"
>
<YStack space="$3">
<Card
borderRadius={18}
borderWidth={1}
borderColor={border}
backgroundColor={surfaceMuted}
padding="$3"
>
<YStack space="$2">
<XStack alignItems="center" justifyContent="space-between" space="$2">
<YStack space="$1" flex={1}>
<Text fontSize="$sm" fontWeight="800" color={text}>
{t('events.tasks.toggle.title', 'Photo task mode')}
</Text>
</YStack>
<XStack alignItems="center" space="$2">
<XStack flex={1}>
<MobileInput
type="search"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder={t('events.tasks.search', 'Search photo tasks')}
compact
/>
</XStack>
<Pressable onPress={() => setShowEmotionFilterSheet(true)}>
<PillBadge tone={tasksEnabled ? 'success' : 'warning'}>
{tasksEnabled
? t('events.tasks.toggle.active', 'ACTIVE')
: t('events.tasks.toggle.inactive', 'INACTIVE')}
</PillBadge>
<Pressable
onPress={() => setShowTaskDetails((prev) => !prev)}
aria-label={t(
'events.tasks.toggle.description',
'Control whether guests see mission cards and prompts.'
)}
>
<XStack
width={30}
height={30}
borderRadius={10}
alignItems="center"
space="$1.5"
paddingVertical="$2"
paddingHorizontal="$3"
borderRadius={14}
justifyContent="center"
borderWidth={1}
borderColor={border}
backgroundColor={surface}
borderColor={showTaskDetails ? withAlpha(primary, 0.45) : border}
backgroundColor={showTaskDetails ? withAlpha(primary, 0.12) : surface}
>
<Text fontSize={11} fontWeight="700" color={text}>
{t('events.tasks.emotionFilterShort', 'Emotion')}
</Text>
<Text fontSize={11} color={muted}>
{emotionFilter
? emotions.find((e) => String(e.id) === emotionFilter)?.name ?? t('events.tasks.customEmotion', 'Custom emotion')
: t('events.tasks.allEmotions', 'All')}
</Text>
<ChevronDown size={14} color={muted} />
<Info size={14} color={showTaskDetails ? primary : muted} />
</XStack>
</Pressable>
</XStack>
</Card>
</XStack>
<XStack alignItems="center" justifyContent="space-between" space="$2">
<Text fontSize="$sm" fontWeight="700" color={text}>
{t('events.tasks.toggle.switchLabel', 'Photo tasks for guests')}
</Text>
<Switch
size="$4"
checked={tasksEnabled}
onCheckedChange={handleTasksToggle}
aria-label={t('events.tasks.toggle.switchLabel', 'Photo tasks for guests')}
disabled={!canManageTasks || tasksToggleBusy}
>
<Switch.Thumb />
</Switch>
</XStack>
<Text fontSize="$xs" color={muted}>
{tasksEnabled
? t('events.tasks.toggle.onLabel', 'Mission cards active')
: t('events.tasks.toggle.offLabel', 'Photo feed only')}
</Text>
{showTaskDetails ? (
<Text fontSize="$xs" color={muted}>
{t('events.tasks.toggle.description', 'Control whether guests see mission cards and prompts.')}
</Text>
) : null}
{isMember && !canManageTasks ? (
<Text fontSize="$xs" color={muted}>
{t('events.tasks.toggle.permissionHint', 'You do not have permission to change photo tasks.')}
</Text>
) : null}
</YStack>
{taskPanel}
</YStack>
</Tabs.Content>
</Card>
<Tabs.Content value="library" paddingTop="$2">
{libraryPanel}
</Tabs.Content>
<Tabs
value={activeTab}
onValueChange={(value) => setActiveTab(value as TaskSectionKey)}
flexDirection="column"
alignItems="stretch"
width="100%"
>
<Tabs.List
borderRadius={16}
borderWidth={1}
borderColor={border}
backgroundColor={surfaceMuted}
overflow="hidden"
gap="$0"
>
{[
{ value: 'assigned', label: t('events.tasks.tabs.tasks', 'Tasks') },
{ value: 'library', label: t('events.tasks.tabs.library', 'Task Library') },
{ value: 'emotions', label: t('events.tasks.tabs.emotions', 'Emotions') },
{ value: 'collections', label: t('events.tasks.tabs.collections', 'Collections') },
].map((tab, index, arr) => {
const isActive = activeTab === tab.value;
return (
<Tabs.Tab
key={tab.value}
value={tab.value}
flex={1}
unstyled
paddingVertical="$2.5"
alignItems="center"
justifyContent="center"
backgroundColor={isActive ? primary : 'transparent'}
borderRightWidth={index === arr.length - 1 ? 0 : 1}
borderRightColor={border}
pressStyle={{ backgroundColor: isActive ? primary : surface }}
>
<Text fontSize="$sm" fontWeight={isActive ? '700' : '500'} color={isActive ? 'white' : text}>
{tab.label}
</Text>
</Tabs.Tab>
);
})}
</Tabs.List>
<Tabs.Content value="emotions" paddingTop="$2">
{emotionsPanel}
</Tabs.Content>
<Tabs.Content value="assigned" paddingTop="$2">
<Card borderRadius={18} borderWidth={1} borderColor={border} backgroundColor={surface} padding="$3">
<YStack space="$2">
<YStack className="admin-sticky-toolbar">
<Card
borderRadius={16}
borderWidth={1}
borderColor={stickyBorder}
backgroundColor={stickySurface}
padding="$2.5"
shadowColor={stickyShadow}
shadowOpacity={0.12}
shadowRadius={12}
shadowOffset={{ width: 0, height: 8 }}
>
<XStack alignItems="center" space="$2">
<XStack flex={1}>
<MobileInput
type="search"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder={t('events.tasks.search', 'Search photo tasks')}
compact
/>
</XStack>
<Pressable onPress={() => setShowEmotionFilterSheet(true)}>
<XStack
alignItems="center"
space="$1.5"
paddingVertical="$2"
paddingHorizontal="$3"
borderRadius={14}
borderWidth={1}
borderColor={border}
backgroundColor={surface}
>
<Text fontSize={11} fontWeight="700" color={text}>
{t('events.tasks.emotionFilterShort', 'Emotion')}
</Text>
<Text fontSize={11} color={muted}>
{emotionFilter
? emotions.find((e) => String(e.id) === emotionFilter)?.name ?? t('events.tasks.customEmotion', 'Custom emotion')
: t('events.tasks.allEmotions', 'All')}
</Text>
<ChevronDown size={14} color={muted} />
</XStack>
</Pressable>
</XStack>
</Card>
</YStack>
{taskPanel}
</YStack>
</Card>
</Tabs.Content>
<Tabs.Content value="collections" paddingTop="$2">
{collectionsPanel}
</Tabs.Content>
</Tabs>
<Tabs.Content value="library" paddingTop="$2">
<Card borderRadius={18} borderWidth={1} borderColor={border} backgroundColor={surface} padding="$3">
{libraryPanel}
</Card>
</Tabs.Content>
<Tabs.Content value="emotions" paddingTop="$2">
<Card borderRadius={18} borderWidth={1} borderColor={border} backgroundColor={surface} padding="$3">
{emotionsPanel}
</Card>
</Tabs.Content>
<Tabs.Content value="collections" paddingTop="$2">
<Card borderRadius={18} borderWidth={1} borderColor={border} backgroundColor={surface} padding="$3">
{collectionsPanel}
</Card>
</Tabs.Content>
</Tabs>
</YStack>
</Card>
)}
<MobileSheet