Fix sticky tasks toolbar layout
This commit is contained in:
@@ -618,6 +618,254 @@ export default function MobileEventTasksPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const taskPanel = assignedTasks.length === 0 ? (
|
||||
<YStack space="$2">
|
||||
<MobileCard space="$2">
|
||||
<Text fontSize={13} fontWeight="700" color={text}>
|
||||
{t('events.tasks.emptyTitle', 'No photo tasks yet')}
|
||||
</Text>
|
||||
<Text fontSize={12} color={muted}>
|
||||
{t('events.tasks.emptyBody', 'Create photo tasks or import a pack for your event.')}
|
||||
</Text>
|
||||
<XStack space="$2">
|
||||
<CTAButton
|
||||
label={t('events.tasks.emptyActionTask', 'Add photo task')}
|
||||
onPress={() => setShowTaskSheet(true)}
|
||||
fullWidth={false}
|
||||
/>
|
||||
<CTAButton
|
||||
label={t('events.tasks.emptyActionPack', 'Import photo task pack')}
|
||||
tone="ghost"
|
||||
onPress={() => setShowCollectionSheet(true)}
|
||||
fullWidth={false}
|
||||
/>
|
||||
</XStack>
|
||||
</MobileCard>
|
||||
<YGroup {...({ borderWidth: 1, borderColor: border, borderRadius: "$4", overflow: "hidden" } as any)}>
|
||||
<YGroup.Item>
|
||||
<ListItem
|
||||
hoverTheme
|
||||
pressTheme
|
||||
onPress={() => setShowTaskSheet(true)}
|
||||
title={
|
||||
<XStack alignItems="center" space="$2">
|
||||
<YStack
|
||||
width={28}
|
||||
height={28}
|
||||
borderRadius={14}
|
||||
backgroundColor={primary}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Plus size={14} color={surface} />
|
||||
</YStack>
|
||||
<Text fontSize={12.5} fontWeight="700" color={text}>
|
||||
{t('events.tasks.addTask', 'Fotoaufgabe hinzufügen')}
|
||||
</Text>
|
||||
</XStack>
|
||||
}
|
||||
subTitle={
|
||||
<Text fontSize={11.5} color={muted}>
|
||||
{t('events.tasks.addTaskHint', 'Erstelle eine neue Fotoaufgabe für dieses Event.')}
|
||||
</Text>
|
||||
}
|
||||
paddingVertical="$2"
|
||||
paddingHorizontal="$3"
|
||||
iconAfter={<ChevronRight size={16} color={muted} />}
|
||||
/>
|
||||
</YGroup.Item>
|
||||
<YGroup.Item>
|
||||
<ListItem
|
||||
hoverTheme
|
||||
pressTheme
|
||||
onPress={() => setShowCollectionSheet(true)}
|
||||
title={
|
||||
<XStack alignItems="center" space="$2">
|
||||
<YStack
|
||||
width={28}
|
||||
height={28}
|
||||
borderRadius={14}
|
||||
backgroundColor={primary}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Plus size={14} color={surface} />
|
||||
</YStack>
|
||||
<Text fontSize={12.5} fontWeight="700" color={text}>
|
||||
{t('events.tasks.import', 'Fotoaufgabenpaket importieren')}
|
||||
</Text>
|
||||
</XStack>
|
||||
}
|
||||
subTitle={
|
||||
<Text fontSize={11.5} color={muted}>
|
||||
{t('events.tasks.importHint', 'Nutze vordefinierte Pakete für deinen Event-Typ.')}
|
||||
</Text>
|
||||
}
|
||||
paddingVertical="$2"
|
||||
paddingHorizontal="$3"
|
||||
iconAfter={<ChevronRight size={16} color={muted} />}
|
||||
/>
|
||||
</YGroup.Item>
|
||||
</YGroup>
|
||||
</YStack>
|
||||
) : (
|
||||
<YStack space="$2">
|
||||
<YStack ref={assignedRef} />
|
||||
<Text fontSize="$sm" color={muted}>
|
||||
{t('events.tasks.count', '{{count}} photo tasks', { count: filteredTasks.length })}
|
||||
</Text>
|
||||
{selectionMode ? (
|
||||
<MobileCard padding="$2.5" space="$2">
|
||||
<Text fontSize={12} fontWeight="700" color={text}>
|
||||
{t('events.tasks.selectionCount', '{{count}} ausgewählt', { count: selectedTaskIds.size })}
|
||||
</Text>
|
||||
<XStack space="$2">
|
||||
<CTAButton
|
||||
label={t('events.tasks.bulkRemove', 'Auswahl löschen')}
|
||||
tone="danger"
|
||||
fullWidth={false}
|
||||
disabled={selectedTaskIds.size === 0 || bulkDeleteBusy}
|
||||
onPress={() => setBulkDeleteOpen(true)}
|
||||
/>
|
||||
<CTAButton
|
||||
label={t('events.tasks.bulkCancel', 'Auswahl beenden')}
|
||||
tone="ghost"
|
||||
fullWidth={false}
|
||||
onPress={() => clearSelection()}
|
||||
/>
|
||||
</XStack>
|
||||
</MobileCard>
|
||||
) : null}
|
||||
<YGroup {...({ borderWidth: 1, borderColor: border, borderRadius: "$4", overflow: "hidden" } as any)}>
|
||||
{filteredTasks.map((task, idx) => (
|
||||
<YGroup.Item key={task.id}>
|
||||
<ListItem
|
||||
hoverTheme
|
||||
pressTheme
|
||||
onPress={() => handleTaskPress(task)}
|
||||
onPointerDown={() => startLongPress(task.id)}
|
||||
onPointerUp={cancelLongPress}
|
||||
onPointerLeave={cancelLongPress}
|
||||
onPointerCancel={cancelLongPress}
|
||||
title={
|
||||
<XStack alignItems="center" space="$2">
|
||||
{selectionMode ? (
|
||||
<Checkbox
|
||||
size="$3"
|
||||
checked={selectedTaskIds.has(task.id)}
|
||||
onCheckedChange={() => toggleSelectedTask(task.id)}
|
||||
onPress={(event: any) => event?.stopPropagation?.()}
|
||||
aria-label={t('events.tasks.select', 'Select photo task')}
|
||||
>
|
||||
<Checkbox.Indicator>
|
||||
<Check size={14} color={text} />
|
||||
</Checkbox.Indicator>
|
||||
</Checkbox>
|
||||
) : null}
|
||||
<Text fontSize={12.5} fontWeight="600" color={text}>
|
||||
{task.title}
|
||||
</Text>
|
||||
</XStack>
|
||||
}
|
||||
subTitle={
|
||||
task.description ? (
|
||||
<Text fontSize={11.5} fontWeight="400" color={subtle}>
|
||||
{task.description}
|
||||
</Text>
|
||||
) : null
|
||||
}
|
||||
iconAfter={
|
||||
selectionMode ? null : (
|
||||
<XStack space="$2" alignItems="center">
|
||||
{task.emotion ? (
|
||||
<Tag label={task.emotion.name ?? ''} color={task.emotion.color ?? text} />
|
||||
) : null}
|
||||
<Button
|
||||
size="$2"
|
||||
circular
|
||||
backgroundColor={dangerBg}
|
||||
borderWidth={1}
|
||||
borderColor={`${danger}33`}
|
||||
icon={<Trash2 size={14} color={dangerText} />}
|
||||
aria-label={t('events.tasks.remove', 'Remove photo task')}
|
||||
disabled={busyId === task.id}
|
||||
onPress={(event: any) => {
|
||||
event?.stopPropagation?.();
|
||||
setDeleteCandidate(task);
|
||||
}}
|
||||
/>
|
||||
<ChevronRight size={14} color={subtle} />
|
||||
</XStack>
|
||||
)
|
||||
}
|
||||
paddingVertical="$2"
|
||||
paddingHorizontal="$3"
|
||||
/>
|
||||
</YGroup.Item>
|
||||
))}
|
||||
</YGroup>
|
||||
<XStack justifyContent="space-between" alignItems="center" marginTop="$2">
|
||||
<YStack ref={libraryRef} />
|
||||
<Text fontSize={12.5} fontWeight="600" color={text}>
|
||||
{t('events.tasks.library', 'Weitere Fotoaufgaben')}
|
||||
</Text>
|
||||
<Pressable onPress={() => setShowCollectionSheet(true)}>
|
||||
<Text fontSize={12} fontWeight="600" color={primary}>
|
||||
{t('events.tasks.import', 'Import photo task pack')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</XStack>
|
||||
<Pressable onPress={() => setExpandedLibrary((prev) => !prev)}>
|
||||
<Text fontSize={12} fontWeight="600" color={primary}>
|
||||
{expandedLibrary ? t('events.tasks.hideLibrary', 'Hide library') : t('events.tasks.viewAllLibrary', 'View all')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
{library.length === 0 ? (
|
||||
<Text fontSize={12} fontWeight="500" color={subtle}>
|
||||
{t('events.tasks.libraryEmpty', 'Keine weiteren Fotoaufgaben verfügbar.')}
|
||||
</Text>
|
||||
) : (
|
||||
<YGroup {...({ borderWidth: 1, borderColor: border, borderRadius: "$4", overflow: "hidden" } as any)}>
|
||||
{(expandedLibrary ? library : library.slice(0, 6)).map((task, idx, arr) => (
|
||||
<YGroup.Item key={`lib-${task.id}`}>
|
||||
<ListItem
|
||||
hoverTheme
|
||||
pressTheme
|
||||
title={
|
||||
<Text fontSize={12.5} fontWeight="600" color={text}>
|
||||
{task.title}
|
||||
</Text>
|
||||
}
|
||||
subTitle={
|
||||
task.description ? (
|
||||
<Text fontSize={11.5} fontWeight="400" color={subtle}>
|
||||
{task.description}
|
||||
</Text>
|
||||
) : null
|
||||
}
|
||||
iconAfter={
|
||||
<XStack space="$1.5" alignItems="center">
|
||||
<Pressable onPress={() => quickAssign(task.id)}>
|
||||
<XStack alignItems="center" space="$1">
|
||||
<Plus size={14} color={primary} />
|
||||
<Text fontSize={12} fontWeight="600" color={primary}>
|
||||
{assigningId === task.id ? t('common.processing', '...') : t('events.tasks.add', 'Add')}
|
||||
</Text>
|
||||
</XStack>
|
||||
</Pressable>
|
||||
<ChevronRight size={14} color={subtle} />
|
||||
</XStack>
|
||||
}
|
||||
paddingVertical="$2"
|
||||
paddingHorizontal="$3"
|
||||
/>
|
||||
</YGroup.Item>
|
||||
))}
|
||||
</YGroup>
|
||||
)}
|
||||
</YStack>
|
||||
);
|
||||
|
||||
return (
|
||||
<MobileShell
|
||||
activeTab="tasks"
|
||||
@@ -715,7 +963,13 @@ export default function MobileEventTasksPage() {
|
||||
</MobileCard>
|
||||
) : null}
|
||||
|
||||
{!loading ? (
|
||||
{loading ? (
|
||||
<YStack space="$2">
|
||||
{Array.from({ length: 4 }).map((_, idx) => (
|
||||
<SkeletonCard key={`tsk-${idx}`} height={70} />
|
||||
))}
|
||||
</YStack>
|
||||
) : (
|
||||
<YStack space="$2">
|
||||
<Card
|
||||
borderRadius={22}
|
||||
@@ -774,7 +1028,7 @@ export default function MobileEventTasksPage() {
|
||||
</YStack>
|
||||
</Card>
|
||||
|
||||
<YStack className="admin-sticky-toolbar" width="100%">
|
||||
<div className="admin-sticky-toolbar">
|
||||
<Card
|
||||
borderRadius={20}
|
||||
borderWidth={2}
|
||||
@@ -820,261 +1074,9 @@ export default function MobileEventTasksPage() {
|
||||
</Pressable>
|
||||
</XStack>
|
||||
</Card>
|
||||
</YStack>
|
||||
</YStack>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<YStack space="$2">
|
||||
{Array.from({ length: 4 }).map((_, idx) => (
|
||||
<SkeletonCard key={`tsk-${idx}`} height={70} />
|
||||
))}
|
||||
</YStack>
|
||||
) : assignedTasks.length === 0 ? (
|
||||
<YStack space="$2">
|
||||
<MobileCard space="$2">
|
||||
<Text fontSize={13} fontWeight="700" color={text}>
|
||||
{t('events.tasks.emptyTitle', 'No photo tasks yet')}
|
||||
</Text>
|
||||
<Text fontSize={12} color={muted}>
|
||||
{t('events.tasks.emptyBody', 'Create photo tasks or import a pack for your event.')}
|
||||
</Text>
|
||||
<XStack space="$2">
|
||||
<CTAButton
|
||||
label={t('events.tasks.emptyActionTask', 'Add photo task')}
|
||||
onPress={() => setShowTaskSheet(true)}
|
||||
fullWidth={false}
|
||||
/>
|
||||
<CTAButton
|
||||
label={t('events.tasks.emptyActionPack', 'Import photo task pack')}
|
||||
tone="ghost"
|
||||
onPress={() => setShowCollectionSheet(true)}
|
||||
fullWidth={false}
|
||||
/>
|
||||
</XStack>
|
||||
</MobileCard>
|
||||
<YGroup {...({ borderWidth: 1, borderColor: border, borderRadius: "$4", overflow: "hidden" } as any)}>
|
||||
<YGroup.Item>
|
||||
<ListItem
|
||||
hoverTheme
|
||||
pressTheme
|
||||
onPress={() => setShowTaskSheet(true)}
|
||||
title={
|
||||
<XStack alignItems="center" space="$2">
|
||||
<YStack
|
||||
width={28}
|
||||
height={28}
|
||||
borderRadius={14}
|
||||
backgroundColor={primary}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Plus size={14} color={surface} />
|
||||
</YStack>
|
||||
<Text fontSize={12.5} fontWeight="700" color={text}>
|
||||
{t('events.tasks.addTask', 'Fotoaufgabe hinzufügen')}
|
||||
</Text>
|
||||
</XStack>
|
||||
}
|
||||
subTitle={
|
||||
<Text fontSize={11.5} color={muted}>
|
||||
{t('events.tasks.addTaskHint', 'Erstelle eine neue Fotoaufgabe für dieses Event.')}
|
||||
</Text>
|
||||
}
|
||||
paddingVertical="$2"
|
||||
paddingHorizontal="$3"
|
||||
iconAfter={<ChevronRight size={16} color={muted} />}
|
||||
/>
|
||||
</YGroup.Item>
|
||||
<YGroup.Item>
|
||||
<ListItem
|
||||
hoverTheme
|
||||
pressTheme
|
||||
onPress={() => setShowCollectionSheet(true)}
|
||||
title={
|
||||
<XStack alignItems="center" space="$2">
|
||||
<YStack
|
||||
width={28}
|
||||
height={28}
|
||||
borderRadius={14}
|
||||
backgroundColor={primary}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Plus size={14} color={surface} />
|
||||
</YStack>
|
||||
<Text fontSize={12.5} fontWeight="700" color={text}>
|
||||
{t('events.tasks.import', 'Fotoaufgabenpaket importieren')}
|
||||
</Text>
|
||||
</XStack>
|
||||
}
|
||||
subTitle={
|
||||
<Text fontSize={11.5} color={muted}>
|
||||
{t('events.tasks.importHint', 'Nutze vordefinierte Pakete für deinen Event-Typ.')}
|
||||
</Text>
|
||||
}
|
||||
paddingVertical="$2"
|
||||
paddingHorizontal="$3"
|
||||
iconAfter={<ChevronRight size={16} color={muted} />}
|
||||
/>
|
||||
</YGroup.Item>
|
||||
</YGroup>
|
||||
</YStack>
|
||||
) : (
|
||||
<YStack space="$2">
|
||||
<YStack ref={assignedRef} />
|
||||
<Text fontSize="$sm" color={muted}>
|
||||
{t('events.tasks.count', '{{count}} photo tasks', { count: filteredTasks.length })}
|
||||
</Text>
|
||||
{selectionMode ? (
|
||||
<MobileCard padding="$2.5" space="$2">
|
||||
<Text fontSize={12} fontWeight="700" color={text}>
|
||||
{t('events.tasks.selectionCount', '{{count}} ausgewählt', { count: selectedTaskIds.size })}
|
||||
</Text>
|
||||
<XStack space="$2">
|
||||
<CTAButton
|
||||
label={t('events.tasks.bulkRemove', 'Auswahl löschen')}
|
||||
tone="danger"
|
||||
fullWidth={false}
|
||||
disabled={selectedTaskIds.size === 0 || bulkDeleteBusy}
|
||||
onPress={() => setBulkDeleteOpen(true)}
|
||||
/>
|
||||
<CTAButton
|
||||
label={t('events.tasks.bulkCancel', 'Auswahl beenden')}
|
||||
tone="ghost"
|
||||
fullWidth={false}
|
||||
onPress={() => clearSelection()}
|
||||
/>
|
||||
</XStack>
|
||||
</MobileCard>
|
||||
) : null}
|
||||
<YGroup {...({ borderWidth: 1, borderColor: border, borderRadius: "$4", overflow: "hidden" } as any)}>
|
||||
{filteredTasks.map((task, idx) => (
|
||||
<YGroup.Item key={task.id}>
|
||||
<ListItem
|
||||
hoverTheme
|
||||
pressTheme
|
||||
onPress={() => handleTaskPress(task)}
|
||||
onPointerDown={() => startLongPress(task.id)}
|
||||
onPointerUp={cancelLongPress}
|
||||
onPointerLeave={cancelLongPress}
|
||||
onPointerCancel={cancelLongPress}
|
||||
title={
|
||||
<XStack alignItems="center" space="$2">
|
||||
{selectionMode ? (
|
||||
<Checkbox
|
||||
size="$3"
|
||||
checked={selectedTaskIds.has(task.id)}
|
||||
onCheckedChange={() => toggleSelectedTask(task.id)}
|
||||
onPress={(event: any) => event?.stopPropagation?.()}
|
||||
aria-label={t('events.tasks.select', 'Select photo task')}
|
||||
>
|
||||
<Checkbox.Indicator>
|
||||
<Check size={14} color={text} />
|
||||
</Checkbox.Indicator>
|
||||
</Checkbox>
|
||||
) : null}
|
||||
<Text fontSize={12.5} fontWeight="600" color={text}>
|
||||
{task.title}
|
||||
</Text>
|
||||
</XStack>
|
||||
}
|
||||
subTitle={
|
||||
task.description ? (
|
||||
<Text fontSize={11.5} fontWeight="400" color={subtle}>
|
||||
{task.description}
|
||||
</Text>
|
||||
) : null
|
||||
}
|
||||
iconAfter={
|
||||
selectionMode ? null : (
|
||||
<XStack space="$2" alignItems="center">
|
||||
{task.emotion ? (
|
||||
<Tag label={task.emotion.name ?? ''} color={task.emotion.color ?? text} />
|
||||
) : null}
|
||||
<Button
|
||||
size="$2"
|
||||
circular
|
||||
backgroundColor={dangerBg}
|
||||
borderWidth={1}
|
||||
borderColor={`${danger}33`}
|
||||
icon={<Trash2 size={14} color={dangerText} />}
|
||||
aria-label={t('events.tasks.remove', 'Remove photo task')}
|
||||
disabled={busyId === task.id}
|
||||
onPress={(event: any) => {
|
||||
event?.stopPropagation?.();
|
||||
setDeleteCandidate(task);
|
||||
}}
|
||||
/>
|
||||
<ChevronRight size={14} color={subtle} />
|
||||
</XStack>
|
||||
)
|
||||
}
|
||||
paddingVertical="$2"
|
||||
paddingHorizontal="$3"
|
||||
/>
|
||||
</YGroup.Item>
|
||||
))}
|
||||
</YGroup>
|
||||
<XStack justifyContent="space-between" alignItems="center" marginTop="$2">
|
||||
<YStack ref={libraryRef} />
|
||||
<Text fontSize={12.5} fontWeight="600" color={text}>
|
||||
{t('events.tasks.library', 'Weitere Fotoaufgaben')}
|
||||
</Text>
|
||||
<Pressable onPress={() => setShowCollectionSheet(true)}>
|
||||
<Text fontSize={12} fontWeight="600" color={primary}>
|
||||
{t('events.tasks.import', 'Import photo task pack')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</XStack>
|
||||
<Pressable onPress={() => setExpandedLibrary((prev) => !prev)}>
|
||||
<Text fontSize={12} fontWeight="600" color={primary}>
|
||||
{expandedLibrary ? t('events.tasks.hideLibrary', 'Hide library') : t('events.tasks.viewAllLibrary', 'View all')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
{library.length === 0 ? (
|
||||
<Text fontSize={12} fontWeight="500" color={subtle}>
|
||||
{t('events.tasks.libraryEmpty', 'Keine weiteren Fotoaufgaben verfügbar.')}
|
||||
</Text>
|
||||
) : (
|
||||
<YGroup {...({ borderWidth: 1, borderColor: border, borderRadius: "$4", overflow: "hidden" } as any)}>
|
||||
{(expandedLibrary ? library : library.slice(0, 6)).map((task, idx, arr) => (
|
||||
<YGroup.Item key={`lib-${task.id}`}>
|
||||
<ListItem
|
||||
hoverTheme
|
||||
pressTheme
|
||||
title={
|
||||
<Text fontSize={12.5} fontWeight="600" color={text}>
|
||||
{task.title}
|
||||
</Text>
|
||||
}
|
||||
subTitle={
|
||||
task.description ? (
|
||||
<Text fontSize={11.5} fontWeight="400" color={subtle}>
|
||||
{task.description}
|
||||
</Text>
|
||||
) : null
|
||||
}
|
||||
iconAfter={
|
||||
<XStack space="$1.5" alignItems="center">
|
||||
<Pressable onPress={() => quickAssign(task.id)}>
|
||||
<XStack alignItems="center" space="$1">
|
||||
<Plus size={14} color={primary} />
|
||||
<Text fontSize={12} fontWeight="600" color={primary}>
|
||||
{assigningId === task.id ? t('common.processing', '...') : t('events.tasks.add', 'Add')}
|
||||
</Text>
|
||||
</XStack>
|
||||
</Pressable>
|
||||
<ChevronRight size={14} color={subtle} />
|
||||
</XStack>
|
||||
}
|
||||
paddingVertical="$2"
|
||||
paddingHorizontal="$3"
|
||||
/>
|
||||
</YGroup.Item>
|
||||
))}
|
||||
</YGroup>
|
||||
)}
|
||||
{taskPanel}
|
||||
</YStack>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user