108 lines
4.7 KiB
TypeScript
108 lines
4.7 KiB
TypeScript
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 },
|
|
});
|