merge appeal and file moderation winodw

This commit is contained in:
smanylov
2026-06-04 13:55:04 +07:00
parent 03766057eb
commit d4d3e5d3b1
11 changed files with 296 additions and 142 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "no-copy-admin-panel-frontend",
"version": "0.16.0",
"version": "0.17.0",
"private": true,
"scripts": {
"dev": "next dev -p 2996",
@@ -1,10 +1,21 @@
import { useQuery } from '@tanstack/react-query';
import { fetchModerationContentList } from '@/app/actions/contentActions';
export interface AppealInfo {
additionalInfo: string | null;
adminComment: null;
appealId: string | null;
appealReason: string | null;
createdAt: string | null;
fileId: string | null;
fileName: string | null;
resolvedAt: string | null;
status: string;
}
export interface ModerationFile {
fileName: string;
moderationInfo: {
appealInfo: null | any;
appealInfo: null | AppealInfo;
hasActiveAppeal: boolean;
};
userId: number;
+56
View File
@@ -125,6 +125,62 @@
cursor: not-allowed;
}
}
.tabs-modal__header {
display: flex;
flex-direction: column;
gap: 1rem;
}
.tabs-modal__tabs {
display: flex;
gap: 0.5rem;
border-bottom: 1px solid #e2e8f0;
padding-bottom: 0.5rem;
justify-content: space-between;
}
.tabs-modal__tab {
padding: 0.5rem 1rem;
background: transparent;
border: none;
border-radius: 0.375rem;
cursor: pointer;
font-size: 0.875rem;
font-weight: 500;
color: #64748b;
transition: all 0.2s;
position: relative;
}
.tabs-modal__tab:hover:not(:disabled) {
color: #3b82f6;
background: #f1f5f9;
}
.tabs-modal__tab--active {
color: #3b82f6;
background: #eff6ff;
}
.tabs-modal__tab--disabled {
opacity: 0.5;
cursor: not-allowed;
}
.tabs-modal__tab-badge {
display: inline-block;
margin-left: 0.5rem;
padding: 0.125rem 0.375rem;
background: #e2e8f0;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: normal;
}
.tabs-modal__content {
margin-top: 1rem;
}
}
.status-badge {
+8
View File
@@ -1035,6 +1035,14 @@
}
}
.table-status {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 5px;
}
.table-item-protected {
color: #10b981;
font-size: 12px;
+7 -9
View File
@@ -7,9 +7,10 @@ import DropDownList from '@/app/components/dropDownList';
import { AppealsFile } from '@/app/hooks/react-query/useContentForAppeals';
import { changeAppealContentStatus } from '@/app/actions/contentActions';
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
import { AppealInfo } from '@/app/hooks/react-query/useContentForModeration';
interface AppealModerationModalProps {
file: AppealsFile | null;
file: AppealInfo | AppealsFile | null;
onClose: () => void;
}
@@ -100,10 +101,7 @@ export default function AppealModerationModal({ file, onClose }: AppealModeratio
if (!file) return null;
return (
<div className="file-moderation-modal">
<h2 className="file-moderation-modal__title">Управление апелляцией</h2>
<div className="file-moderation-modal__content">
<>
{/* Информация о файле */}
<div className="file-moderation-modal__info-section">
<label className="file-moderation-modal__label">Название файла</label>
@@ -129,7 +127,9 @@ export default function AppealModerationModal({ file, onClose }: AppealModeratio
<div className="file-moderation-modal__info-section">
<label className="file-moderation-modal__label">Дата создания</label>
<p className="file-moderation-modal__value">
{formatDate(file.createdAt)}, {formatDateTime(file.createdAt)}
{file.createdAt && (
`${formatDate(file.createdAt)}, ${formatDateTime(file.createdAt)}`
)}
</p>
</div>
@@ -166,7 +166,6 @@ export default function AppealModerationModal({ file, onClose }: AppealModeratio
value={getLabel(selectedStatus)}
callBack={(value: string) => setSelectedStatus(value)}
>
{/* <li value="MODERATION">На модерации</li> */}
<li value="REJECTED">Заблокировано</li>
<li value="APPROVED">Одобрено</li>
</DropDownList>
@@ -189,7 +188,6 @@ export default function AppealModerationModal({ file, onClose }: AppealModeratio
{changeStatusMutation.isPending ? 'Сохранение...' : 'Сохранить'}
</button>
</div>
</div>
</div>
</>
);
}
@@ -82,9 +82,6 @@ export default function FileModerationModal({ file, onClose }: FileModerationMod
if (!file) return null;
return (
<div className="file-moderation-modal">
<h2 className="file-moderation-modal__title">Управление файлом</h2>
<div className="file-moderation-modal__content">
{/* Информация о файле */}
<div className="file-moderation-modal__info-section">
@@ -133,6 +130,5 @@ export default function FileModerationModal({ file, onClose }: FileModerationMod
</button>
</div>
</div>
</div>
);
}
@@ -0,0 +1,62 @@
'use client';
import { useState } from 'react';
import { ModerationFile } from '@/app/hooks/react-query/useContentForModeration';
import FileModerationModal from './FileModerationModal';
import AppealModerationModal from './AppealModerationModal';
interface FileWithAppealModerationModalProps {
file: ModerationFile | null;
onClose: () => void;
}
type TabType = 'file' | 'appeal';
export default function FileWithAppealModerationModal({ file, onClose }: FileWithAppealModerationModalProps) {
const [activeTab, setActiveTab] = useState<TabType>('file');
if (!file) return null;
const hasAppeal = !!file.moderationInfo?.appealInfo;
return (
<div className="file-moderation-modal tabs-modal">
<div className="tabs-modal__header">
<h2 className="file-moderation-modal__title">
Модерация контента
</h2>
<div className="tabs-modal__tabs">
<button
className={`tabs-modal__tab ${activeTab === 'file' ? 'tabs-modal__tab--active' : ''}`}
onClick={() => setActiveTab('file')}
>
Модерация файла
</button>
<button
className={`tabs-modal__tab ${activeTab === 'appeal' ? 'tabs-modal__tab--active' : ''} ${!hasAppeal ? 'tabs-modal__tab--disabled' : ''}`}
onClick={() => hasAppeal && setActiveTab('appeal')}
disabled={!hasAppeal}
title={!hasAppeal ? 'Апелляция отсутствует' : ''}
>
Модерация апелляции
</button>
</div>
</div>
<div className="tabs-modal__content">
{activeTab === 'file' && (
<FileModerationModal file={file} onClose={onClose} />
)}
{activeTab === 'appeal' && hasAppeal && (
<AppealModerationModal
file={file.moderationInfo.appealInfo}
onClose={onClose}
/>
)}
</div>
</div>
);
}
+2 -1
View File
@@ -22,6 +22,7 @@ import { downloadFile } from '@/app/actions/contentActions';
import { useContentForAppeals, AppealsFile } from '@/app/hooks/react-query/useContentForAppeals';
import AppealModerationModal from '@/app/ui/content/AppealModerationModal';
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
import {AppealInfo} from '@/app/hooks/react-query/useContentForModeration';
const cutFileExtension = (fileName: string) => {
const lastDotIndex = fileName.lastIndexOf('.');
@@ -79,7 +80,7 @@ export default function ContentAppealsTable({ permission }: { permission: number
pageSize: 10,
});
const [openWindow, setOpenWindow] = useState<boolean>(false);
const [selectedFile, setSelectedFile] = useState<AppealsFile | null>(null);
const [selectedFile, setSelectedFile] = useState<AppealsFile | AppealInfo | null>(null);
const [isFileLoading, setIsFileLoading] = useState(false);
const t = useTranslations("Global");
@@ -1,6 +1,6 @@
'use client';
import { useState, useMemo, useEffect, ReactNode, useCallback } from 'react';
import { useState, useMemo, useEffect, useCallback } from 'react';
import {
useReactTable,
getCoreRowModel,
@@ -17,12 +17,12 @@ import DropDownList from '@/app/components/dropDownList';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import ModalWindow from '@/app/components/modalWindow';
import { toast } from 'sonner';
import { useContentForModeration, ModerationFile, ContentForModeration, ModerationStatus } from '@/app/hooks/react-query/useContentForModeration';
import FileModerationModal from './FileModerationModal';
import { useContentForModeration, ModerationFile, ModerationStatus } from '@/app/hooks/react-query/useContentForModeration';
import { FileTypeIcon } from '@/app/components/FileTypeIcon';
import { getFileType } from '@/app/lib/getFileType';
import Image from 'next/image';
import { downloadFile } from '@/app/actions/contentActions';
import FileWithAppealModerationModal from './FileWithAppealModerationModal';
const cutFileExtension = (fileName: string) => {
const lastDotIndex = fileName.lastIndexOf('.');
@@ -71,6 +71,23 @@ const StatusBadge = ({ status }: { status: string }) => {
);
};
const AppealStatusBadge = ({ status }: { status: string }) => {
const statusConfig: Record<string, { className: string; label: string }> = {
'REJECTED': { className: 'bg-red-100 text-red-800', label: 'Заблокировано' },
'MODERATION': { className: 'bg-yellow-100 text-yellow-800', label: 'На модерации' },
'APPROVED': { className: 'bg-green-100 text-green-800', label: 'Одобрено' },
'PENDING': { className: 'bg-gray-100 text-gray-800', label: 'На рассмотрении' }
};
const config = statusConfig[status] || { className: 'bg-gray-100 text-gray-800', label: status };
return (
<span className={`px-2 py-1 rounded-full text-xs font-medium ${config.className}`}>
{config.label}
</span>
);
};
export default function ContentModerationTable({ permission }: { permission: number }) {
// Состояния
const [sorting, setSorting] = useState<SortingState>([]);
@@ -127,10 +144,6 @@ export default function ContentModerationTable({ permission }: { permission: num
const queryClient = useQueryClient();
useEffect(() => {
console.log(moderationData);
}, [moderationData])
const toggleStatus = (status: ModerationStatus) => {
setSelectedStatuses(prev => {
if (prev.includes(status)) {
@@ -305,9 +318,16 @@ export default function ContentModerationTable({ permission }: { permission: num
</div>
),
cell: ({ row }) => (
<div className="text-center">
<div className="table-status">
<div>
<StatusBadge status={row.original.status} />
</div>
{row.original.moderationInfo.appealInfo?.status && (
<div>
{t('appeal')}: <AppealStatusBadge status={row.original.moderationInfo.appealInfo?.status} />
</div>
)}
</div>
),
},
];
@@ -401,7 +421,7 @@ export default function ContentModerationTable({ permission }: { permission: num
return (
<div className="tanstak-table-wrapper">
<ModalWindow state={openWindow} callBack={handleCloseModal}>
<FileModerationModal file={selectedFile} onClose={handleCloseModal} />
<FileWithAppealModerationModal file={selectedFile} onClose={handleCloseModal} />
</ModalWindow>
<h3>
Таблица модерации контента
+2 -1
View File
@@ -225,7 +225,8 @@
"create-stuff": "Create stuff",
"download": "Download",
"all": "All",
"has-appeal": "Has appeal"
"has-appeal": "Has appeal",
"appeal": "Appeal"
},
"Login-register-form": {
"and": "and",
+2 -1
View File
@@ -225,7 +225,8 @@
"create-stuff": "Создать сотрудника",
"download": "Скачать",
"all": "Все",
"has-appeal": "Есть апелляция"
"has-appeal": "Есть апелляция",
"appeal": "Апелляция"
},
"Login-register-form": {
"and": "и",