Fix sticky tasks toolbar 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-20 11:37:20 +01:00
parent a916bf8c4d
commit 3c2ebdbc0e
2 changed files with 259 additions and 256 deletions

View File

@@ -555,6 +555,7 @@ html.guest-theme.dark {
position: sticky;
top: calc(env(safe-area-inset-top, 0px) + 76px);
z-index: 45;
width: 100%;
}
@media (prefers-reduced-motion: reduce) {

View File

@@ -618,219 +618,7 @@ export default function MobileEventTasksPage() {
}
}
return (
<MobileShell
activeTab="tasks"
title={t('events.tasks.title', 'Photo tasks & checklists')}
onBack={back}
headerActions={
<XStack space="$2">
<HeaderActionButton onPress={() => load()} ariaLabel={t('common.refresh', 'Refresh')}>
<RefreshCcw size={18} color={text} />
</HeaderActionButton>
</XStack>
}
>
{error ? (
<MobileCard>
<Text fontSize={13} fontWeight="600" color={danger}>
{error}
</Text>
<CTAButton
label={t('common.retry', 'Retry')}
tone="ghost"
fullWidth={false}
onPress={() => load()}
/>
</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 tasks for this event')}
</Text>
<PillBadge tone={tasksEnabled ? 'success' : 'warning'}>
{tasksEnabled
? t('events.tasks.toggle.active', 'ACTIVE')
: t('events.tasks.toggle.inactive', 'INACTIVE')}
</PillBadge>
</XStack>
{showTaskDetails ? (
<Text fontSize="$xs" color={muted}>
{t(
'events.tasks.toggle.description',
'Give guests optional photo tasks and prompts.'
)}
</Text>
) : null}
</YStack>
<XStack alignItems="center" space="$2">
<Pressable
onPress={() => setShowTaskDetails((prev) => !prev)}
aria-label={t(
'events.tasks.toggle.description',
'Give guests optional photo tasks 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>
<Switch
size="$4"
checked={tasksEnabled}
onCheckedChange={handleTasksToggle}
aria-label={t('events.tasks.toggle.switchLabel', 'Photo tasks enabled')}
disabled={!canManageTasks || tasksToggleBusy}
>
<Switch.Thumb />
</Switch>
</XStack>
</XStack>
{showTaskDetails ? (
<Text fontSize="$xs" color={muted}>
{tasksEnabled
? t('events.tasks.toggle.onLabel', 'Guests see photo tasks')
: t('events.tasks.toggle.offLabel', 'Guest app shows photos only')}
</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}
</MobileCard>
) : null}
{!loading ? (
<YStack space="$2">
<Card
borderRadius={22}
borderWidth={2}
borderColor={border}
backgroundColor={surface}
padding="$3"
>
<YStack space="$2.5">
<XStack alignItems="center" justifyContent="space-between">
<XStack
alignItems="center"
paddingHorizontal="$3"
paddingVertical="$1.5"
borderRadius={999}
borderWidth={1}
borderColor={border}
backgroundColor={surfaceMuted}
>
<Text fontSize="$xs" fontWeight="800" color={text}>
{t('events.tasks.quickNav', 'Quick jump')}
</Text>
</XStack>
</XStack>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
<ToggleGroup
type="single"
value={quickNavSelection}
onValueChange={(next) => {
const key = next as TaskSectionKey | '';
if (!key) {
return;
}
setQuickNavSelection(key);
handleQuickNav(key);
}}
>
<XStack space="$2" paddingVertical="$1">
{sectionCounts.map((section) => (
<QuickNavChip
key={section.key}
value={section.key}
label={t(`events.tasks.sections.${section.key}`, section.key)}
count={section.count}
onPress={() => {
setQuickNavSelection(section.key);
handleQuickNav(section.key);
}}
isActive={quickNavSelection === section.key}
/>
))}
</XStack>
</ToggleGroup>
</ScrollView>
</YStack>
</Card>
<YStack className="admin-sticky-toolbar" width="100%">
<Card
borderRadius={20}
borderWidth={2}
borderColor={stickyBorder}
backgroundColor={stickySurface}
padding="$3"
shadowColor={stickyShadow}
shadowOpacity={0.16}
shadowRadius={16}
shadowOffset={{ width: 0, height: 10 }}
>
<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>
</YStack>
) : null}
{loading ? (
<YStack space="$2">
{Array.from({ length: 4 }).map((_, idx) => (
<SkeletonCard key={`tsk-${idx}`} height={70} />
))}
</YStack>
) : assignedTasks.length === 0 ? (
const taskPanel = assignedTasks.length === 0 ? (
<YStack space="$2">
<MobileCard space="$2">
<Text fontSize={13} fontWeight="700" color={text}>
@@ -1076,6 +864,220 @@ export default function MobileEventTasksPage() {
</YGroup>
)}
</YStack>
);
return (
<MobileShell
activeTab="tasks"
title={t('events.tasks.title', 'Photo tasks & checklists')}
onBack={back}
headerActions={
<XStack space="$2">
<HeaderActionButton onPress={() => load()} ariaLabel={t('common.refresh', 'Refresh')}>
<RefreshCcw size={18} color={text} />
</HeaderActionButton>
</XStack>
}
>
{error ? (
<MobileCard>
<Text fontSize={13} fontWeight="600" color={danger}>
{error}
</Text>
<CTAButton
label={t('common.retry', 'Retry')}
tone="ghost"
fullWidth={false}
onPress={() => load()}
/>
</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 tasks for this event')}
</Text>
<PillBadge tone={tasksEnabled ? 'success' : 'warning'}>
{tasksEnabled
? t('events.tasks.toggle.active', 'ACTIVE')
: t('events.tasks.toggle.inactive', 'INACTIVE')}
</PillBadge>
</XStack>
{showTaskDetails ? (
<Text fontSize="$xs" color={muted}>
{t(
'events.tasks.toggle.description',
'Give guests optional photo tasks and prompts.'
)}
</Text>
) : null}
</YStack>
<XStack alignItems="center" space="$2">
<Pressable
onPress={() => setShowTaskDetails((prev) => !prev)}
aria-label={t(
'events.tasks.toggle.description',
'Give guests optional photo tasks 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>
<Switch
size="$4"
checked={tasksEnabled}
onCheckedChange={handleTasksToggle}
aria-label={t('events.tasks.toggle.switchLabel', 'Photo tasks enabled')}
disabled={!canManageTasks || tasksToggleBusy}
>
<Switch.Thumb />
</Switch>
</XStack>
</XStack>
{showTaskDetails ? (
<Text fontSize="$xs" color={muted}>
{tasksEnabled
? t('events.tasks.toggle.onLabel', 'Guests see photo tasks')
: t('events.tasks.toggle.offLabel', 'Guest app shows photos only')}
</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}
</MobileCard>
) : null}
{loading ? (
<YStack space="$2">
{Array.from({ length: 4 }).map((_, idx) => (
<SkeletonCard key={`tsk-${idx}`} height={70} />
))}
</YStack>
) : (
<YStack space="$2">
<Card
borderRadius={22}
borderWidth={2}
borderColor={border}
backgroundColor={surface}
padding="$3"
>
<YStack space="$2.5">
<XStack alignItems="center" justifyContent="space-between">
<XStack
alignItems="center"
paddingHorizontal="$3"
paddingVertical="$1.5"
borderRadius={999}
borderWidth={1}
borderColor={border}
backgroundColor={surfaceMuted}
>
<Text fontSize="$xs" fontWeight="800" color={text}>
{t('events.tasks.quickNav', 'Quick jump')}
</Text>
</XStack>
</XStack>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
<ToggleGroup
type="single"
value={quickNavSelection}
onValueChange={(next) => {
const key = next as TaskSectionKey | '';
if (!key) {
return;
}
setQuickNavSelection(key);
handleQuickNav(key);
}}
>
<XStack space="$2" paddingVertical="$1">
{sectionCounts.map((section) => (
<QuickNavChip
key={section.key}
value={section.key}
label={t(`events.tasks.sections.${section.key}`, section.key)}
count={section.count}
onPress={() => {
setQuickNavSelection(section.key);
handleQuickNav(section.key);
}}
isActive={quickNavSelection === section.key}
/>
))}
</XStack>
</ToggleGroup>
</ScrollView>
</YStack>
</Card>
<div 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 }}
>
<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>
</div>
{taskPanel}
</YStack>
)}
<MobileSheet