import { Feather } from "@expo/vector-icons"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useLocalSearchParams, useRouter } from "expo-router"; import React, { useEffect, useMemo, useRef, useState } from "react"; import { Pressable, ScrollView, StyleSheet, Text, View } from "react-native"; import { ApiError } from "../../src/api"; import { useApp } from "../../src/app-context"; import { ScreenShell } from "../../src/components/ScreenShell"; import { notificationApi, notificationKeys, uploadDraftApi } from "../../src/notification-api"; import { formatNotificationPrice, typeMap } from "../../src/notification-presenter"; import { downloadAndOpen, pickFiles, type NativeFile } from "../../src/native-files"; import { publicApi } from "../../src/services"; import { colors, radii, spacing } from "../../src/theme"; import type { NotificationButton, UploadDraft } from "../../src/types"; import { Button, ErrorNotice, Loading, styles } from "../../src/ui"; export default function NotificationDetailScreen() { const { id = "" } = useLocalSearchParams<{ id: string }>(); const { authStatus } = useApp(); const authenticated = authStatus === "authenticated"; const router = useRouter(); const client = useQueryClient(); const readSent = useRef(false); const [error, setError] = useState(); const detail = useQuery({ queryKey: notificationKeys.detail(id), queryFn: () => notificationApi.detail(id), enabled: authenticated && Boolean(id), retry: (count, reason) => !(reason instanceof ApiError && reason.status === 404) && count < 1, }); const catalog = useQuery({ queryKey: notificationKeys.catalog, queryFn: notificationApi.catalog, staleTime: Infinity, }); const config = useQuery({ queryKey: ["public-config"], queryFn: publicApi.config }); const byCode = useMemo(() => typeMap(catalog.data ?? []), [catalog.data]); const type = detail.data ? byCode.get(detail.data.notification_type) : undefined; const canUpload = Boolean(detail.data?.details?.send_documents); const drafts = useQuery({ queryKey: ["uploads", "notification", id], queryFn: () => uploadDraftApi.list(id), enabled: authenticated && Boolean(id) && canUpload, }); const pending = drafts.data ?? detail.data?.details?.pending_documents ?? []; const closed = detail.data?.lifecycle_status === "closed"; useEffect(() => { if (!detail.data || readSent.current || detail.data.is_read !== false) return; readSent.current = true; void notificationApi.read(id).then((state) => { client.setQueryData(notificationKeys.detail(id), { ...detail.data, ...state }); client.setQueryData(notificationKeys.counter, { unread_count: state.unread_count }); }).catch(setError); }, [client, detail.data, id]); const pressButton = useMutation({ mutationFn: (button: NotificationButton) => notificationApi.button(id, button.code), onSuccess: async (state) => { client.setQueryData(notificationKeys.counter, { unread_count: state.unread_count }); await client.invalidateQueries({ queryKey: ["notifications"] }); router.replace("/notifications"); }, onError: setError, }); const removeDraft = useMutation({ mutationFn: uploadDraftApi.remove, onSuccess: () => client.invalidateQueries({ queryKey: ["uploads", "notification", id] }), onError: setError, }); const chooseFile = async () => { try { const files = await pickFiles({ multiple: true, mimeTypes: config.data?.attachments.allowed_mime_types, }); if (files.length) await uploadFiles(files); } catch (reason) { setError(reason); } }; const uploadFiles = async (files: NativeFile[]) => { const limits = config.data?.attachments; const maxBytes = (limits?.max_size_mb ?? 5) * 1024 * 1024; const allowed = limits?.allowed_mime_types ?? []; if (pending.length + files.length > 10) { setError(new Error("К одному уведомлению можно приложить не более 10 файлов.")); return; } const invalid = files.find((file) => file.size > maxBytes || (allowed.length > 0 && !allowed.includes(file.mimeType))); if (invalid) { setError(new Error(`Файл «${invalid.name}» имеет недопустимый тип или размер.`)); return; } setError(undefined); try { for (const file of files) await uploadDraftApi.upload(id, file); await drafts.refetch(); await detail.refetch(); } catch (reason) { setError(reason); } }; const download = async (documentId: string) => { setError(undefined); try { const result = await notificationApi.documentUrl(id, documentId); const title = detail.data?.details?.documents?.find((item) => item.document_id === documentId)?.title; await downloadAndOpen(result.download_url, title ?? "document"); await Promise.all([detail.refetch(), client.invalidateQueries({ queryKey: ["notifications"] })]); } catch (reason) { setError(reason); } }; if (!authenticated) { return ( router.replace("/notifications")} /> Для просмотра уведомления требуется авторизация.