From d7521e6ad1e0e9fd2b68d11582ebe1e61c1f0b8a Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 27 May 2026 14:20:00 +0700 Subject: [PATCH] add appeal table --- src/app/[locale]/pages/content/page.tsx | 4 + src/app/actions/contentActions.ts | 41 ++ .../hooks/react-query/useContentForAppeals.ts | 43 ++ .../react-query/useContentForModeration.ts | 2 +- src/app/styles/file-moderation-modal.scss | 35 +- src/app/ui/content/AppealModerationModal.tsx | 181 +++++++ src/app/ui/content/content-appeals-table.tsx | 495 ++++++++++++++++++ .../ui/content/content-moderation-table.tsx | 7 +- 8 files changed, 802 insertions(+), 6 deletions(-) create mode 100644 src/app/hooks/react-query/useContentForAppeals.ts create mode 100644 src/app/ui/content/AppealModerationModal.tsx create mode 100644 src/app/ui/content/content-appeals-table.tsx diff --git a/src/app/[locale]/pages/content/page.tsx b/src/app/[locale]/pages/content/page.tsx index 9e3b5a8..856bd87 100644 --- a/src/app/[locale]/pages/content/page.tsx +++ b/src/app/[locale]/pages/content/page.tsx @@ -1,4 +1,5 @@ import ContentModerationTable from '@/app/ui/content/content-moderation-table'; +import ContentAppealsTable from '@/app/ui/content/content-appeals-table'; export default function Page() { return ( @@ -9,6 +10,9 @@ export default function Page() {
+
+ +
) } \ No newline at end of file diff --git a/src/app/actions/contentActions.ts b/src/app/actions/contentActions.ts index 18e1fd8..592bc34 100644 --- a/src/app/actions/contentActions.ts +++ b/src/app/actions/contentActions.ts @@ -105,4 +105,45 @@ export async function downloadFile(fileId: string, fileName: string) { contentType: response.headers.get('content-type') || 'application/octet-stream', fileName: fileName } +} + +export async function fetchAppealsContentList(page?: number, size?: number, sortBy?: string, sortDirection?: 'asc' | 'desc' | string) { + const token = await getSessionData('token'); + + try { + const response = await fetch(`${API_BASE_URL}/api/admin/info`, { + method: 'POST', + body: JSON.stringify({ + version: 1, + msg_id: 40001, + message_body: { + action: 'all_appeals', + page: page, + page_size: size, +/* sort_by: sortDirection || 'asc', + sort_order: sortBy || '', */ + } + }), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': `Bearer ${token}` + } + }); + + if (response.ok) { + const parsed = await response.json(); + + if (parsed.message_code === 0) { + return parsed.message_body.message_body; + } else { + return null; + } + + } else { + throw new Error(`${response.status}`); + } + } catch (error) { + return null; + } } \ No newline at end of file diff --git a/src/app/hooks/react-query/useContentForAppeals.ts b/src/app/hooks/react-query/useContentForAppeals.ts new file mode 100644 index 0000000..3711766 --- /dev/null +++ b/src/app/hooks/react-query/useContentForAppeals.ts @@ -0,0 +1,43 @@ +import { useQuery } from '@tanstack/react-query'; +import { fetchAppealsContentList } from '@/app/actions/contentActions'; + +export interface AppealsFile { + additionalInfo: string; + adminComment: string; + appealId: string; + appealReason: string; + createdAt: string; + fileId: string; + resolvedAt: string; + status: 'BLOCKED' | 'APPROVED' | string; +} + +export interface ContentForAppeals { + appeals: AppealsFile[]; + totalPages: number; + currentPage: number; + pageSize: number; + totalCount: number; +} + +export const useContentForAppeals = ( + page?: number, + size?: number, + sortBy?: string, + sortDirection?: 'asc' | 'desc' | string +) => { + return useQuery({ + queryKey: ['contentForAppeals', page, size, sortBy, sortDirection], + queryFn: () => { + return fetchAppealsContentList(page, size, sortBy, sortDirection); + }, + select: (data: ContentForAppeals | null) => { + if (!data) { + return null; + } + return data; + }, + refetchInterval: 30000, + placeholderData: (previousData) => previousData // для оптимистичных обновлений + }); +}; \ No newline at end of file diff --git a/src/app/hooks/react-query/useContentForModeration.ts b/src/app/hooks/react-query/useContentForModeration.ts index 91d2ed9..415b17f 100644 --- a/src/app/hooks/react-query/useContentForModeration.ts +++ b/src/app/hooks/react-query/useContentForModeration.ts @@ -37,7 +37,7 @@ export const useContentForModeration = ( if (!data) { return null; } - return data; // теперь возвращаем весь объект с пагинацией + return data; }, refetchInterval: 30000, placeholderData: (previousData) => previousData // для оптимистичных обновлений diff --git a/src/app/styles/file-moderation-modal.scss b/src/app/styles/file-moderation-modal.scss index 2e91676..0a1b1fc 100644 --- a/src/app/styles/file-moderation-modal.scss +++ b/src/app/styles/file-moderation-modal.scss @@ -18,7 +18,8 @@ &__info-section, &__status-section, - &__status-change-section { + &__status-change-section, + &__comment-section { border-bottom: 1px solid #e5e7eb; padding-bottom: 0.75rem; } @@ -92,6 +93,38 @@ background-color: #93c5fd; } } + + &__comment-label { + margin-bottom: 0.5rem; + } + + &__comment-textarea { + width: 100%; + padding: 0.5rem; + border: 1px solid #e5e7eb; + border-radius: 0.25rem; + font-size: 0.875rem; + font-family: inherit; + color: #111827; + background-color: #ffffff; + resize: vertical; + transition: all 0.2s; + + &:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1); + } + + &::placeholder { + color: #9ca3af; + } + + &:disabled { + background-color: #f3f4f6; + cursor: not-allowed; + } + } } .status-badge { diff --git a/src/app/ui/content/AppealModerationModal.tsx b/src/app/ui/content/AppealModerationModal.tsx new file mode 100644 index 0000000..6be4e07 --- /dev/null +++ b/src/app/ui/content/AppealModerationModal.tsx @@ -0,0 +1,181 @@ +'use client'; + +import { useState } from 'react'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { toast } from 'sonner'; +import DropDownList from '@/app/components/dropDownList'; +/* import { changeAppealStatus } from '@/app/actions/contentActions'; */ +import { AppealsFile } from '@/app/hooks/react-query/useContentForAppeals'; + +interface AppealModerationModalProps { + file: AppealsFile | null; + onClose: () => void; +} + +const getLabel = (status: string) => { + switch (status) { + case 'BLOCKED': + return 'Заблокировано'; + case 'MODERATION': + return 'На модерации'; + case 'APPROVED': + return 'Одобрено'; + default: + return status; + } +}; + +const StatusBadge = ({ status }: { status: string }) => { + const getBadgeClass = () => { + switch (status) { + case 'BLOCKED': + return 'status-badge--blocked'; + case 'MODERATION': + return 'status-badge--moderation'; + case 'APPROVED': + return 'status-badge--approved'; + default: + return 'status-badge--default'; + } + }; + return ( + + {getLabel(status)} + + ); +}; + +export default function AppealModerationModal({ file, onClose }: AppealModerationModalProps) { + const [selectedStatus, setSelectedStatus] = useState(file?.status || 'MODERATION'); + const [adminComment, setAdminComment] = useState(file?.adminComment || ''); + const queryClient = useQueryClient(); + +/* const changeStatusMutation = useMutation({ + mutationFn: async ({ appealId, status, comment }: { appealId: string; status: string; comment: string }) => { + return await changeAppealStatus(appealId, status, comment); + }, + onSuccess: (data) => { + if (data) { + toast.success('Статус апелляции успешно изменен'); + queryClient.invalidateQueries({ queryKey: ['contentForAppeals'] }); + onClose(); + } else { + toast.error('Не удалось изменить статус апелляции'); + } + }, + onError: () => { + toast.error('Произошла ошибка при изменении статуса'); + }, + }); */ + + const handleStatusChange = () => { + if (!file?.appealId) { + toast.error('ID апелляции не найден'); + return; + } + +/* changeStatusMutation.mutate({ + appealId: file.appealId, + status: selectedStatus, + comment: adminComment, + }); */ + }; + + if (!file) return null; + + return ( +
+

Управление апелляцией

+ +
+ {/* Информация о файле */} +
+ + {/* @ts-ignore */} +

{file?.fileName ? file?.fileName : '---'}

+
+ +
+ + {/* @ts-ignore */} +

{file?.userId ? file?.userId : '---'}

+
+ +
+ +

{file.appealReason}

+
+ +
+ +

{file.additionalInfo || '—'}

+
+ +
+ +

+ {new Date(file.createdAt).toLocaleString('ru-RU')} +

+
+ + {file.resolvedAt && ( +
+ +

+ {new Date(file.resolvedAt).toLocaleString('ru-RU')} +

+
+ )} + +
+ + +
+ + {/* Комментарий администратора */} +
+ +