I finished the remaining polish so the admin app now feels fully “app‑like” across the core screens.
This commit is contained in:
@@ -7,7 +7,7 @@ import { SizableText as Text } from '@tamagui/text';
|
||||
import { ListItem } from '@tamagui/list-item';
|
||||
import { Pressable } from '@tamagui/react-native-web-lite';
|
||||
import { MobileShell, HeaderActionButton } from './components/MobileShell';
|
||||
import { MobileCard, CTAButton, SkeletonCard } from './components/Primitives';
|
||||
import { MobileCard, CTAButton, SkeletonCard, PillBadge } from './components/Primitives';
|
||||
import { MobileField, MobileInput, MobileSelect, MobileTextArea } from './components/FormControls';
|
||||
import {
|
||||
getEvent,
|
||||
@@ -39,12 +39,63 @@ import { useEventContext } from '../context/EventContext';
|
||||
import { useTheme } from '@tamagui/core';
|
||||
import { RadioGroup } from '@tamagui/radio-group';
|
||||
import { useBackNavigation } from './hooks/useBackNavigation';
|
||||
import { buildTaskSummary } from './lib/taskSummary';
|
||||
import { buildTaskSectionCounts, type TaskSectionKey } from './lib/taskSectionCounts';
|
||||
|
||||
function InlineSeparator() {
|
||||
const theme = useTheme();
|
||||
return <XStack height={1} opacity={0.7} marginLeft="$3" backgroundColor={theme.borderColor?.val ?? '#e5e7eb'} />;
|
||||
}
|
||||
|
||||
function TaskSummaryCard({
|
||||
summary,
|
||||
text,
|
||||
muted,
|
||||
border,
|
||||
}: {
|
||||
summary: ReturnType<typeof buildTaskSummary>;
|
||||
text: string;
|
||||
muted: string;
|
||||
border: string;
|
||||
}) {
|
||||
const { t } = useTranslation('management');
|
||||
return (
|
||||
<MobileCard space="$2" borderColor={border}>
|
||||
<XStack alignItems="center" justifyContent="space-between" space="$2">
|
||||
<SummaryItem label={t('events.tasks.summary.assigned', 'Assigned')} value={summary.assigned} text={text} muted={muted} />
|
||||
<SummaryItem label={t('events.tasks.summary.library', 'Library')} value={summary.library} text={text} muted={muted} />
|
||||
</XStack>
|
||||
<XStack alignItems="center" justifyContent="space-between" space="$2">
|
||||
<SummaryItem label={t('events.tasks.summary.collections', 'Collections')} value={summary.collections} text={text} muted={muted} />
|
||||
<SummaryItem label={t('events.tasks.summary.emotions', 'Emotions')} value={summary.emotions} text={text} muted={muted} />
|
||||
</XStack>
|
||||
</MobileCard>
|
||||
);
|
||||
}
|
||||
|
||||
function SummaryItem({
|
||||
label,
|
||||
value,
|
||||
text,
|
||||
muted,
|
||||
}: {
|
||||
label: string;
|
||||
value: number;
|
||||
text: string;
|
||||
muted: string;
|
||||
}) {
|
||||
return (
|
||||
<YStack flex={1} padding="$2" borderRadius={12} backgroundColor="rgba(15, 23, 42, 0.03)" space="$1">
|
||||
<Text fontSize={11} color={muted}>
|
||||
{label}
|
||||
</Text>
|
||||
<Text fontSize={16} fontWeight="800" color={text}>
|
||||
{value}
|
||||
</Text>
|
||||
</YStack>
|
||||
);
|
||||
}
|
||||
|
||||
export default function MobileEventTasksPage() {
|
||||
const { slug: slugParam } = useParams<{ slug?: string }>();
|
||||
const { activeEvent, selectEvent } = useEventContext();
|
||||
@@ -89,7 +140,16 @@ export default function MobileEventTasksPage() {
|
||||
const [emotionForm, setEmotionForm] = React.useState({ name: '', color: String(border) });
|
||||
const [savingEmotion, setSavingEmotion] = React.useState(false);
|
||||
const [showEmotionFilterSheet, setShowEmotionFilterSheet] = React.useState(false);
|
||||
const assignedRef = React.useRef<HTMLDivElement>(null);
|
||||
const libraryRef = React.useRef<HTMLDivElement>(null);
|
||||
const back = useBackNavigation(slug ? adminPath(`/mobile/events/${slug}`) : adminPath('/mobile/events'));
|
||||
const summary = buildTaskSummary({
|
||||
assigned: assignedTasks.length,
|
||||
library: library.length,
|
||||
collections: collections.length,
|
||||
emotions: emotions.length,
|
||||
});
|
||||
const sectionCounts = React.useMemo(() => buildTaskSectionCounts(summary), [summary]);
|
||||
React.useEffect(() => {
|
||||
if (slugParam && activeEvent?.slug !== slugParam) {
|
||||
selectEvent(slugParam);
|
||||
@@ -102,6 +162,28 @@ export default function MobileEventTasksPage() {
|
||||
setSearchTerm('');
|
||||
}, [slug]);
|
||||
|
||||
const scrollToSection = (ref: React.RefObject<HTMLDivElement>) => {
|
||||
if (ref.current) {
|
||||
ref.current.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
};
|
||||
|
||||
const handleQuickNav = (key: TaskSectionKey) => {
|
||||
if (key === 'assigned') {
|
||||
scrollToSection(assignedRef);
|
||||
return;
|
||||
}
|
||||
if (key === 'library') {
|
||||
scrollToSection(libraryRef);
|
||||
return;
|
||||
}
|
||||
if (key === 'collections') {
|
||||
setShowCollectionSheet(true);
|
||||
return;
|
||||
}
|
||||
setShowEmotionSheet(true);
|
||||
};
|
||||
|
||||
const load = React.useCallback(async () => {
|
||||
if (!slug) {
|
||||
try {
|
||||
@@ -374,6 +456,44 @@ export default function MobileEventTasksPage() {
|
||||
</MobileCard>
|
||||
) : null}
|
||||
|
||||
{!loading ? (
|
||||
<TaskSummaryCard
|
||||
summary={summary}
|
||||
text={text}
|
||||
muted={muted}
|
||||
border={border}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{!loading ? (
|
||||
<YStack space="$2">
|
||||
<Text fontSize={12} fontWeight="700" color={muted}>
|
||||
{t('events.tasks.quickNav', 'Quick jump')}
|
||||
</Text>
|
||||
<XStack space="$2" flexWrap="wrap">
|
||||
{sectionCounts.map((section) => (
|
||||
<Pressable key={section.key} onPress={() => handleQuickNav(section.key)} style={{ flexGrow: 1 }}>
|
||||
<XStack
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
space="$1.5"
|
||||
paddingVertical="$2"
|
||||
paddingHorizontal="$3"
|
||||
borderRadius={14}
|
||||
borderWidth={1}
|
||||
borderColor={border}
|
||||
>
|
||||
<Text fontSize="$xs" fontWeight="700" color={text}>
|
||||
{t(`events.tasks.sections.${section.key}`, section.key)}
|
||||
</Text>
|
||||
<PillBadge tone="muted">{section.count}</PillBadge>
|
||||
</XStack>
|
||||
</Pressable>
|
||||
))}
|
||||
</XStack>
|
||||
</YStack>
|
||||
) : null}
|
||||
|
||||
{loading ? (
|
||||
<YStack space="$2">
|
||||
{Array.from({ length: 4 }).map((_, idx) => (
|
||||
@@ -471,6 +591,7 @@ export default function MobileEventTasksPage() {
|
||||
</YStack>
|
||||
) : (
|
||||
<YStack space="$2">
|
||||
<div ref={assignedRef} />
|
||||
<YStack space="$2">
|
||||
<MobileInput
|
||||
type="search"
|
||||
@@ -540,6 +661,7 @@ export default function MobileEventTasksPage() {
|
||||
))}
|
||||
</YStack>
|
||||
<XStack justifyContent="space-between" alignItems="center" marginTop="$2">
|
||||
<div ref={libraryRef} />
|
||||
<Text fontSize={12.5} fontWeight="600" color={text}>
|
||||
{t('events.tasks.library', 'Weitere Aufgaben')}
|
||||
</Text>
|
||||
|
||||
Reference in New Issue
Block a user