Добавлены уведомления

This commit is contained in:
mi
2026-07-27 17:36:53 +03:00
parent a072005164
commit 958fba5f3e
149 changed files with 6371 additions and 110 deletions
@@ -0,0 +1,107 @@
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 { 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 { Button, 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 ? (
<View style={local.gate}>
<View style={local.bell}>
<Feather name="bell" size={34} color={colors.primaryForeground} />
</View>
<Text style={styles.title}>Уведомления доступны после входа</Text>
<Text style={[styles.text, local.centerText]}>
Авторизуйтесь, чтобы видеть важные напоминания, документы и статусы услуг.
</Text>
<Button
title="Авторизоваться"
onPress={() => router.replace({ pathname: "/", params: { authorize: "1" } })}
/>
</View>
) : (
<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" },
gate: { flex: 1, justifyContent: "center", alignItems: "center", gap: spacing.md, padding: spacing.xl },
bell: { width: 68, height: 68, borderRadius: radii.full, alignItems: "center", justifyContent: "center", backgroundColor: colors.primary },
centerText: { textAlign: "center" },
content: { padding: spacing.lg, gap: spacing.md, paddingBottom: 40 },
empty: { alignItems: "center", gap: spacing.sm, paddingVertical: 48 },
});