Restructure event tasks layout
This commit is contained in:
@@ -584,6 +584,7 @@
|
|||||||
"imported": "Fotoaufgabenpaket importiert",
|
"imported": "Fotoaufgabenpaket importiert",
|
||||||
"saveTask": "Fotoaufgabe speichern",
|
"saveTask": "Fotoaufgabe speichern",
|
||||||
"add": "Hinzufügen",
|
"add": "Hinzufügen",
|
||||||
|
"assignedTitle": "Aufgabenliste",
|
||||||
"emptyHint": "Lege jetzt Fotoaufgaben an oder importiere ein Paket.",
|
"emptyHint": "Lege jetzt Fotoaufgaben an oder importiere ein Paket.",
|
||||||
"emptyTitle": "Noch keine Fotoaufgaben",
|
"emptyTitle": "Noch keine Fotoaufgaben",
|
||||||
"emptyBody": "Lege Fotoaufgaben an oder importiere ein Paket für dein Event.",
|
"emptyBody": "Lege Fotoaufgaben an oder importiere ein Paket für dein Event.",
|
||||||
|
|||||||
@@ -580,6 +580,7 @@
|
|||||||
"imported": "Photo task pack imported",
|
"imported": "Photo task pack imported",
|
||||||
"saveTask": "Save photo task",
|
"saveTask": "Save photo task",
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
|
"assignedTitle": "Task list",
|
||||||
"emptyHint": "Add photo tasks or import a pack.",
|
"emptyHint": "Add photo tasks or import a pack.",
|
||||||
"emptyTitle": "No photo tasks yet",
|
"emptyTitle": "No photo tasks yet",
|
||||||
"emptyBody": "Create photo tasks or import a pack for your event.",
|
"emptyBody": "Create photo tasks or import a pack for your event.",
|
||||||
|
|||||||
@@ -701,10 +701,15 @@ export default function MobileEventTasksPage() {
|
|||||||
</YStack>
|
</YStack>
|
||||||
) : (
|
) : (
|
||||||
<YStack space="$2">
|
<YStack space="$2">
|
||||||
<XStack alignItems="center" flexWrap="wrap" space="$2">
|
<XStack alignItems="baseline" justifyContent="space-between" flexWrap="wrap" space="$2">
|
||||||
<Text fontSize="$sm" color={muted}>
|
<XStack alignItems="baseline" space="$2" flexWrap="wrap">
|
||||||
{t('events.tasks.count', '{{count}} photo tasks', { count: filteredTasks.length })}
|
<Text fontSize="$sm" fontWeight="800" color={text}>
|
||||||
</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' ? (
|
{typeof remainingTasks === 'number' && typeof maxTasks === 'number' ? (
|
||||||
<Tag
|
<Tag
|
||||||
label={t('events.tasks.remainingIndicator', '{{count}} / {{total}} tasks remaining', {
|
label={t('events.tasks.remainingIndicator', '{{count}} / {{total}} tasks remaining', {
|
||||||
@@ -1041,81 +1046,6 @@ export default function MobileEventTasksPage() {
|
|||||||
</MobileCard>
|
</MobileCard>
|
||||||
) : null}
|
) : 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 ? (
|
{loading ? (
|
||||||
<YStack space="$2">
|
<YStack space="$2">
|
||||||
{Array.from({ length: 4 }).map((_, idx) => (
|
{Array.from({ length: 4 }).map((_, idx) => (
|
||||||
@@ -1123,79 +1053,207 @@ export default function MobileEventTasksPage() {
|
|||||||
))}
|
))}
|
||||||
</YStack>
|
</YStack>
|
||||||
) : (
|
) : (
|
||||||
<Tabs value={activeTab} onValueChange={(value) => setActiveTab(value as TaskSectionKey)}>
|
<Card
|
||||||
<Tabs.List>
|
borderRadius={24}
|
||||||
<Tabs.Tab value="assigned">{t('events.tasks.tabs.tasks', 'Tasks')}</Tabs.Tab>
|
borderWidth={2}
|
||||||
<Tabs.Tab value="library">{t('events.tasks.tabs.library', 'Task Library')}</Tabs.Tab>
|
borderColor={border}
|
||||||
<Tabs.Tab value="emotions">{t('events.tasks.tabs.emotions', 'Emotions')}</Tabs.Tab>
|
backgroundColor={surface}
|
||||||
<Tabs.Tab value="collections">{t('events.tasks.tabs.collections', 'Collections')}</Tabs.Tab>
|
padding="$3"
|
||||||
</Tabs.List>
|
>
|
||||||
|
<YStack space="$3">
|
||||||
<Tabs.Content value="assigned" paddingTop="$2">
|
<Card
|
||||||
<YStack space="$2">
|
borderRadius={18}
|
||||||
<YStack className="admin-sticky-toolbar">
|
borderWidth={1}
|
||||||
<Card
|
borderColor={border}
|
||||||
borderRadius={20}
|
backgroundColor={surfaceMuted}
|
||||||
borderWidth={2}
|
padding="$3"
|
||||||
borderColor={stickyBorder}
|
>
|
||||||
backgroundColor={stickySurface}
|
<YStack space="$2">
|
||||||
padding="$3"
|
<XStack alignItems="center" justifyContent="space-between" space="$2">
|
||||||
shadowColor={stickyShadow}
|
<YStack space="$1" flex={1}>
|
||||||
shadowOpacity={0.16}
|
<Text fontSize="$sm" fontWeight="800" color={text}>
|
||||||
shadowRadius={16}
|
{t('events.tasks.toggle.title', 'Photo task mode')}
|
||||||
shadowOffset={{ width: 0, height: 10 }}
|
</Text>
|
||||||
>
|
</YStack>
|
||||||
<XStack alignItems="center" space="$2">
|
<XStack alignItems="center" space="$2">
|
||||||
<XStack flex={1}>
|
<PillBadge tone={tasksEnabled ? 'success' : 'warning'}>
|
||||||
<MobileInput
|
{tasksEnabled
|
||||||
type="search"
|
? t('events.tasks.toggle.active', 'ACTIVE')
|
||||||
value={searchTerm}
|
: t('events.tasks.toggle.inactive', 'INACTIVE')}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
</PillBadge>
|
||||||
placeholder={t('events.tasks.search', 'Search photo tasks')}
|
<Pressable
|
||||||
compact
|
onPress={() => setShowTaskDetails((prev) => !prev)}
|
||||||
/>
|
aria-label={t(
|
||||||
</XStack>
|
'events.tasks.toggle.description',
|
||||||
<Pressable onPress={() => setShowEmotionFilterSheet(true)}>
|
'Control whether guests see mission cards and prompts.'
|
||||||
|
)}
|
||||||
|
>
|
||||||
<XStack
|
<XStack
|
||||||
|
width={30}
|
||||||
|
height={30}
|
||||||
|
borderRadius={10}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
space="$1.5"
|
justifyContent="center"
|
||||||
paddingVertical="$2"
|
|
||||||
paddingHorizontal="$3"
|
|
||||||
borderRadius={14}
|
|
||||||
borderWidth={1}
|
borderWidth={1}
|
||||||
borderColor={border}
|
borderColor={showTaskDetails ? withAlpha(primary, 0.45) : border}
|
||||||
backgroundColor={surface}
|
backgroundColor={showTaskDetails ? withAlpha(primary, 0.12) : surface}
|
||||||
>
|
>
|
||||||
<Text fontSize={11} fontWeight="700" color={text}>
|
<Info size={14} color={showTaskDetails ? primary : muted} />
|
||||||
{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>
|
</XStack>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</XStack>
|
</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>
|
</YStack>
|
||||||
{taskPanel}
|
</Card>
|
||||||
</YStack>
|
|
||||||
</Tabs.Content>
|
|
||||||
|
|
||||||
<Tabs.Content value="library" paddingTop="$2">
|
<Tabs
|
||||||
{libraryPanel}
|
value={activeTab}
|
||||||
</Tabs.Content>
|
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">
|
<Tabs.Content value="assigned" paddingTop="$2">
|
||||||
{emotionsPanel}
|
<Card borderRadius={18} borderWidth={1} borderColor={border} backgroundColor={surface} padding="$3">
|
||||||
</Tabs.Content>
|
<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">
|
<Tabs.Content value="library" paddingTop="$2">
|
||||||
{collectionsPanel}
|
<Card borderRadius={18} borderWidth={1} borderColor={border} backgroundColor={surface} padding="$3">
|
||||||
</Tabs.Content>
|
{libraryPanel}
|
||||||
</Tabs>
|
</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
|
<MobileSheet
|
||||||
|
|||||||
Reference in New Issue
Block a user