Первая версия мобильного приложения
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import { Feather } from "@expo/vector-icons";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useRouter } from "expo-router";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { Pressable, ScrollView, StyleSheet, Text, View } from "react-native";
|
||||
import { useApp } from "../../src/app-context";
|
||||
import { GuestAuthGate } from "../../src/components/GuestAuthGate";
|
||||
import { NotificationCard } from "../../src/components/NotificationCard";
|
||||
import { ScreenShell } from "../../src/components/ScreenShell";
|
||||
import { useNotificationAction } from "../../src/notification-actions";
|
||||
import { notificationApi, notificationKeys } from "../../src/notification-api";
|
||||
import { typeMap } from "../../src/notification-presenter";
|
||||
import { colors, radii, spacing } from "../../src/theme";
|
||||
import { ErrorNotice, Loading, styles } from "../../src/ui";
|
||||
|
||||
export default function NotificationCenterScreen() {
|
||||
const { authStatus } = useApp();
|
||||
const authenticated = authStatus === "authenticated";
|
||||
const router = useRouter();
|
||||
const [actionError, setActionError] = useState<unknown>();
|
||||
const catalog = useQuery({
|
||||
queryKey: notificationKeys.catalog,
|
||||
queryFn: notificationApi.catalog,
|
||||
staleTime: Infinity,
|
||||
});
|
||||
const notifications = useQuery({
|
||||
queryKey: notificationKeys.center,
|
||||
queryFn: () => notificationApi.list("center"),
|
||||
enabled: authenticated,
|
||||
});
|
||||
const byCode = useMemo(() => typeMap(catalog.data ?? []), [catalog.data]);
|
||||
const action = useNotificationAction({
|
||||
authenticated,
|
||||
onError: setActionError,
|
||||
requireAuth: () => router.replace({ pathname: "/", params: { authorize: "1" } }),
|
||||
});
|
||||
|
||||
return (
|
||||
<ScreenShell>
|
||||
<View style={local.header}>
|
||||
<Pressable accessibilityLabel="Назад" accessibilityRole="button" onPress={() => router.back()} style={local.back}>
|
||||
<Feather name="arrow-left" size={20} color={colors.foreground} />
|
||||
</Pressable>
|
||||
<Text accessibilityRole="header" style={styles.title}>Центр уведомлений</Text>
|
||||
</View>
|
||||
|
||||
{!authenticated ? (
|
||||
<GuestAuthGate
|
||||
icon="bell"
|
||||
title="Уведомления доступны после входа"
|
||||
description="Авторизуйтесь, чтобы видеть важные напоминания, документы и статусы услуг."
|
||||
/>
|
||||
) : (
|
||||
<ScrollView contentContainerStyle={local.content}>
|
||||
{(notifications.isLoading || catalog.isLoading) && <Loading />}
|
||||
{(notifications.error || catalog.error) && (
|
||||
<ErrorNotice
|
||||
error={notifications.error ?? catalog.error}
|
||||
retry={() => { void notifications.refetch(); void catalog.refetch(); }}
|
||||
/>
|
||||
)}
|
||||
{!notifications.isLoading && !notifications.error && notifications.data?.length === 0 && (
|
||||
<View style={local.empty}>
|
||||
<Feather name="check-circle" size={32} color={colors.success} />
|
||||
<Text style={styles.heading}>Новых уведомлений нет</Text>
|
||||
<Text style={styles.muted}>Здесь появятся важные сообщения и задачи.</Text>
|
||||
</View>
|
||||
)}
|
||||
{notifications.data?.map((item) => (
|
||||
<NotificationCard
|
||||
key={item.id}
|
||||
compact
|
||||
item={item}
|
||||
type={byCode.get(item.notification_type)}
|
||||
onCta={() => void action(item, byCode.get(item.notification_type))}
|
||||
/>
|
||||
))}
|
||||
{Boolean(actionError) && <ErrorNotice error={actionError} />}
|
||||
</ScrollView>
|
||||
)}
|
||||
</ScreenShell>
|
||||
);
|
||||
}
|
||||
|
||||
const local = StyleSheet.create({
|
||||
header: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: spacing.md,
|
||||
padding: spacing.lg,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.border,
|
||||
},
|
||||
back: { width: 36, height: 36, borderRadius: radii.full, alignItems: "center", justifyContent: "center" },
|
||||
content: { padding: spacing.lg, gap: spacing.md, paddingBottom: 40 },
|
||||
empty: { alignItems: "center", gap: spacing.sm, paddingVertical: 48 },
|
||||
});
|
||||
Reference in New Issue
Block a user