add appeals change status

This commit is contained in:
smanylov
2026-05-27 18:59:37 +07:00
parent c626b697e2
commit 05e04886c3
5 changed files with 108 additions and 34 deletions
+17 -6
View File
@@ -1,18 +1,29 @@
'use client'
import ContentModerationTable from '@/app/ui/content/content-moderation-table'; import ContentModerationTable from '@/app/ui/content/content-moderation-table';
import ContentAppealsTable from '@/app/ui/content/content-appeals-table'; import ContentAppealsTable from '@/app/ui/content/content-appeals-table';
import { useEmployeInfo } from '@/app/hooks/react-query/useEmployeInfo';
export default function Page() { export default function Page() {
const { getPermissionLevel } = useEmployeInfo();
const contentModerationPermissionLevel = getPermissionLevel('content_moderation');
return ( return (
<div className="admin-content"> <div className="admin-content">
<div className="content-header"> <div className="content-header">
<h3>Управление контентом</h3> <h3>Управление контентом</h3>
</div> </div>
<div className="content-body"> {getPermissionLevel('content_moderation') > 0 && (
<ContentModerationTable permission={3} /> <>
</div> <div className="content-body">
<div className="content-body"> <ContentModerationTable permission={contentModerationPermissionLevel} />
<ContentAppealsTable permission={3} /> </div>
</div> <div className="content-body">
<ContentAppealsTable permission={contentModerationPermissionLevel} />
</div>
</>
)}
</div> </div>
) )
} }
+48
View File
@@ -86,13 +86,20 @@ export async function changeModerationContentStatus(fileId?: string, fileStatus?
export async function downloadFile(fileId: string, fileName: string) { export async function downloadFile(fileId: string, fileName: string) {
const token = await getSessionData('token'); const token = await getSessionData('token');
console.log('downloadFile');
/* ${API_DASHBOARD_URL} */
/* ${API_BASE_URL} */
const response = await fetch(`${API_DASHBOARD_URL}/api/v1/files/download/${fileId}`, { const response = await fetch(`${API_DASHBOARD_URL}/api/v1/files/download/${fileId}`, {
headers: { headers: {
'Authorization': `Bearer ${token}`, 'Authorization': `Bearer ${token}`,
}, },
}) })
/* console.log(response); */
const text = await response.text();
console.log(text);
if (!response.ok) { if (!response.ok) {
throw 'failed-to-download-file'; throw 'failed-to-download-file';
} }
@@ -140,6 +147,47 @@ export async function fetchAppealsContentList(page?: number, size?: number, sort
return null; return null;
} }
} else {
throw new Error(`${response.status}`);
}
} catch (error) {
return null;
}
}
export async function changeAppealContentStatus(appealId?: string, approve?: boolean, comment?: string) {
const token = await getSessionData('token');
try {
const response = await fetch(`${API_BASE_URL}/api/admin/control`, {
method: 'POST',
body: JSON.stringify({
version: 1,
msg_id: 40002,
message_body: {
action: 'review_appeal',
appeal_id: appealId,
approve: approve,
comment: comment
}
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
const parsed = await response.json();
console.log(parsed);
if (parsed.message_code === 0) {
return parsed.message_body.message_body;
} else {
return null;
}
} else { } else {
throw new Error(`${response.status}`); throw new Error(`${response.status}`);
} }
@@ -9,7 +9,8 @@ export interface AppealsFile {
createdAt: string; createdAt: string;
fileId: string; fileId: string;
resolvedAt: string; resolvedAt: string;
status: 'BLOCKED' | 'APPROVED' | string; fileName: string;
status: 'REJECTED' | 'APPROVED' | string;
} }
export interface ContentForAppeals { export interface ContentForAppeals {
+33 -20
View File
@@ -4,8 +4,8 @@ import { useState } from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner'; import { toast } from 'sonner';
import DropDownList from '@/app/components/dropDownList'; import DropDownList from '@/app/components/dropDownList';
/* import { changeAppealStatus } from '@/app/actions/contentActions'; */
import { AppealsFile } from '@/app/hooks/react-query/useContentForAppeals'; import { AppealsFile } from '@/app/hooks/react-query/useContentForAppeals';
import { changeAppealContentStatus } from '@/app/actions/contentActions';
interface AppealModerationModalProps { interface AppealModerationModalProps {
file: AppealsFile | null; file: AppealsFile | null;
@@ -14,7 +14,7 @@ interface AppealModerationModalProps {
const getLabel = (status: string) => { const getLabel = (status: string) => {
switch (status) { switch (status) {
case 'BLOCKED': case 'REJECTED':
return 'Заблокировано'; return 'Заблокировано';
case 'MODERATION': case 'MODERATION':
return 'На модерации'; return 'На модерации';
@@ -25,10 +25,23 @@ const getLabel = (status: string) => {
} }
}; };
const getStatusValue = (label: string) => {
switch (label) {
case 'REJECTED':
return 'REJECTED';
case 'MODERATION':
return 'MODERATION';
case 'APPROVED':
return 'APPROVED';
default:
return 'MODERATION';
}
};
const StatusBadge = ({ status }: { status: string }) => { const StatusBadge = ({ status }: { status: string }) => {
const getBadgeClass = () => { const getBadgeClass = () => {
switch (status) { switch (status) {
case 'BLOCKED': case 'REJECTED':
return 'status-badge--blocked'; return 'status-badge--blocked';
case 'MODERATION': case 'MODERATION':
return 'status-badge--moderation'; return 'status-badge--moderation';
@@ -46,16 +59,16 @@ const StatusBadge = ({ status }: { status: string }) => {
}; };
export default function AppealModerationModal({ file, onClose }: AppealModerationModalProps) { export default function AppealModerationModal({ file, onClose }: AppealModerationModalProps) {
const [selectedStatus, setSelectedStatus] = useState<string>(file?.status || 'MODERATION'); const [selectedStatus, setSelectedStatus] = useState<string>(getLabel(file?.status || 'MODERATION'));
const [adminComment, setAdminComment] = useState<string>(file?.adminComment || ''); const [adminComment, setAdminComment] = useState<string>(file?.adminComment || '');
const queryClient = useQueryClient(); const queryClient = useQueryClient();
/* const changeStatusMutation = useMutation({ const changeStatusMutation = useMutation({
mutationFn: async ({ appealId, status, comment }: { appealId: string; status: string; comment: string }) => { mutationFn: async ({ appealId, approve, comment }: { appealId: string; approve: boolean; comment: string }) => {
return await changeAppealStatus(appealId, status, comment); return await changeAppealContentStatus(appealId, approve, comment);
}, },
onSuccess: (data) => { onSuccess: (data) => {
if (data) { if (data !== null && data !== undefined) {
toast.success('Статус апелляции успешно изменен'); toast.success('Статус апелляции успешно изменен');
queryClient.invalidateQueries({ queryKey: ['contentForAppeals'] }); queryClient.invalidateQueries({ queryKey: ['contentForAppeals'] });
onClose(); onClose();
@@ -66,19 +79,21 @@ export default function AppealModerationModal({ file, onClose }: AppealModeratio
onError: () => { onError: () => {
toast.error('Произошла ошибка при изменении статуса'); toast.error('Произошла ошибка при изменении статуса');
}, },
}); */ });
const handleStatusChange = () => { const handleStatusChange = () => {
if (!file?.appealId) { if (!file?.appealId) {
toast.error('ID апелляции не найден'); toast.error('ID апелляции не найден');
return; return;
} }
const statusValue = getStatusValue(selectedStatus);
const approve = statusValue === 'APPROVED';
/* changeStatusMutation.mutate({ changeStatusMutation.mutate({
appealId: file.appealId, appealId: file.appealId,
status: selectedStatus, approve: approve,
comment: adminComment, comment: adminComment,
}); */ });
}; };
if (!file) return null; if (!file) return null;
@@ -91,13 +106,12 @@ export default function AppealModerationModal({ file, onClose }: AppealModeratio
{/* Информация о файле */} {/* Информация о файле */}
<div className="file-moderation-modal__info-section"> <div className="file-moderation-modal__info-section">
<label className="file-moderation-modal__label">Название файла</label> <label className="file-moderation-modal__label">Название файла</label>
{/* @ts-ignore */}
<p className="file-moderation-modal__value">{file?.fileName ? file?.fileName : '---'}</p> <p className="file-moderation-modal__value">{file?.fileName ? file?.fileName : '---'}</p>
</div> </div>
<div className="file-moderation-modal__info-section"> <div className="file-moderation-modal__info-section">
<label className="file-moderation-modal__label">User ID</label> <label className="file-moderation-modal__label">User ID</label>
{/* @ts-ignore */} {/* @ts-ignore сейчас нету айди юзера возможно нужно будет удалить или добавить*/}
<p className="file-moderation-modal__value">{file?.userId ? file?.userId : '---'}</p> <p className="file-moderation-modal__value">{file?.userId ? file?.userId : '---'}</p>
</div> </div>
@@ -151,8 +165,8 @@ export default function AppealModerationModal({ file, onClose }: AppealModeratio
value={getLabel(selectedStatus)} value={getLabel(selectedStatus)}
callBack={(value: string) => setSelectedStatus(value)} callBack={(value: string) => setSelectedStatus(value)}
> >
<li value="MODERATION">На модерации</li> {/* <li value="MODERATION">На модерации</li> */}
<li value="BLOCKED">Заблокировано</li> <li value="REJECTED">Заблокировано</li>
<li value="APPROVED">Одобрено</li> <li value="APPROVED">Одобрено</li>
</DropDownList> </DropDownList>
</div> </div>
@@ -162,17 +176,16 @@ export default function AppealModerationModal({ file, onClose }: AppealModeratio
<button <button
onClick={onClose} onClick={onClose}
className="btn btn-secondary" className="btn btn-secondary"
/* disabled={changeStatusMutation.isPending} */ disabled={changeStatusMutation.isPending}
> >
Отмена Отмена
</button> </button>
<button <button
onClick={handleStatusChange} onClick={handleStatusChange}
className="btn btn-primary" className="btn btn-primary"
/* disabled={changeStatusMutation.isPending || selectedStatus === file.status} */ disabled={changeStatusMutation.isPending || selectedStatus === getLabel(file.status)}
> >
{/* {changeStatusMutation.isPending ? 'Сохранение...' : 'Сохранить'} */} {changeStatusMutation.isPending ? 'Сохранение...' : 'Сохранить'}
Сохранить
</button> </button>
</div> </div>
</div> </div>
+8 -7
View File
@@ -54,7 +54,7 @@ const cutFileName = (fileName: string) => {
const StatusBadge = ({ status }: { status: string }) => { const StatusBadge = ({ status }: { status: string }) => {
const statusConfig: Record<string, { className: string; label: string }> = { const statusConfig: Record<string, { className: string; label: string }> = {
'BLOCKED': { className: 'bg-red-100 text-red-800', label: 'Заблокировано' }, 'REJECTED': { className: 'bg-red-100 text-red-800', label: 'Заблокировано' },
'MODERATION': { className: 'bg-yellow-100 text-yellow-800', label: 'На модерации' }, 'MODERATION': { className: 'bg-yellow-100 text-yellow-800', label: 'На модерации' },
'APPROVED': { className: 'bg-green-100 text-green-800', label: 'Одобрено' }, 'APPROVED': { className: 'bg-green-100 text-green-800', label: 'Одобрено' },
}; };
@@ -168,11 +168,11 @@ export default function ContentAppealsTable({ permission }: { permission: number
const columns = useMemo<ColumnDef<AppealsFile>[]>(() => { const columns = useMemo<ColumnDef<AppealsFile>[]>(() => {
const allColumns: ColumnDef<AppealsFile>[] = [ const allColumns: ColumnDef<AppealsFile>[] = [
{ {
accessorKey: 'fileId', accessorKey: 'fileName',
header: ({ column }) => ( header: ({ column }) => (
<div className="column start"> <div className="column start">
<span> <span>
fileId fileName
</span> </span>
<button <button
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')} onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
@@ -189,11 +189,12 @@ export default function ContentAppealsTable({ permission }: { permission: number
), ),
cell: ({ row }) => ( cell: ({ row }) => (
<div className="flex items-center space-x-2 table-item table-item-file-name"> <div className="flex items-center space-x-2 table-item table-item-file-name">
<FileTypeIcon type={row.original.fileId} /> <FileTypeIcon type={row.original.fileName} />
<div> <div>
<div className="font-medium w-full truncate" title={row.original.fileId}> <div className="font-medium w-full truncate" title={row.original.fileName}>
<span className="font-semibold"> <span className="font-semibold">
{cutFileName(row.original.fileId)} {/* {cutFileName(row.original.fileName)} */}
{row.original.fileName}
</span> </span>
</div> </div>
</div> </div>
@@ -419,7 +420,7 @@ export default function ContentAppealsTable({ permission }: { permission: number
<span className="pagination-info-pages"> <span className="pagination-info-pages">
{t('page')}{' '} {t('page')}{' '}
<strong> <strong>
{(appealsData?.currentPage ?? 0)} {t('out-of')} {appealsData?.totalPages ?? 1} {(appealsData?.currentPage ?? 0) + 1} {t('out-of')} {appealsData?.totalPages ?? 1}
</strong> </strong>
</span> </span>
<span className="pagination-info-files"> <span className="pagination-info-files">