merge appeal and file moderation winodw
This commit is contained in:
+1
-1
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 +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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
Таблица модерации контента
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -225,7 +225,8 @@
|
||||
"create-stuff": "Создать сотрудника",
|
||||
"download": "Скачать",
|
||||
"all": "Все",
|
||||
"has-appeal": "Есть апелляция"
|
||||
"has-appeal": "Есть апелляция",
|
||||
"appeal": "Апелляция"
|
||||
},
|
||||
"Login-register-form": {
|
||||
"and": "и",
|
||||
|
||||
Reference in New Issue
Block a user