add status filter for content table
This commit is contained in:
@@ -3,7 +3,16 @@
|
|||||||
import { getSessionData } from '@/app/actions/session';
|
import { getSessionData } from '@/app/actions/session';
|
||||||
import { API_BASE_URL } from '@/app/actions/definitions';
|
import { API_BASE_URL } from '@/app/actions/definitions';
|
||||||
|
|
||||||
export async function fetchModerationContentList(page?: number, size?: number, sortBy?: string, sortDirection?: 'asc' | 'desc' | string) {
|
type ModerationStatus = 'BLOCKED' | 'ACTIVE' | 'MODERATION';
|
||||||
|
|
||||||
|
export async function fetchModerationContentList(
|
||||||
|
page?: number,
|
||||||
|
size?: number,
|
||||||
|
sortBy?: string,
|
||||||
|
sortDirection?: 'asc' | 'desc' | string,
|
||||||
|
statuses?: ModerationStatus[],
|
||||||
|
isAppeal?: boolean
|
||||||
|
) {
|
||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -18,6 +27,8 @@ export async function fetchModerationContentList(page?: number, size?: number, s
|
|||||||
page_size: size,
|
page_size: size,
|
||||||
sort_by: sortBy || '',
|
sort_by: sortBy || '',
|
||||||
sort_order: sortDirection || 'asc',
|
sort_order: sortDirection || 'asc',
|
||||||
|
statuses: statuses,
|
||||||
|
is_appeal: isAppeal,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -22,16 +22,20 @@ export interface ContentForModeration {
|
|||||||
totalCount: number;
|
totalCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ModerationStatus = 'BLOCKED' | 'ACTIVE' | 'MODERATION';
|
||||||
|
|
||||||
export const useContentForModeration = (
|
export const useContentForModeration = (
|
||||||
page?: number,
|
page?: number,
|
||||||
size?: number,
|
size?: number,
|
||||||
sortBy?: string,
|
sortBy?: string,
|
||||||
sortDirection?: 'asc' | 'desc' | string
|
sortDirection?: 'asc' | 'desc' | string,
|
||||||
|
statuses?: ModerationStatus[],
|
||||||
|
isAppeal?: boolean
|
||||||
) => {
|
) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['contentForModeration', page, size, sortBy, sortDirection],
|
queryKey: ['contentForModeration', page, size, sortBy, sortDirection, statuses, isAppeal],
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
return fetchModerationContentList(page, size, sortBy, sortDirection);
|
return fetchModerationContentList(page, size, sortBy, sortDirection, statuses, isAppeal);
|
||||||
},
|
},
|
||||||
select: (data: ContentForModeration | null) => {
|
select: (data: ContentForModeration | null) => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
|||||||
@@ -771,6 +771,7 @@
|
|||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.table-filtres-item {
|
.table-filtres-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -806,6 +807,87 @@
|
|||||||
box-shadow: 0 0 0 2px #6365f187;
|
box-shadow: 0 0 0 2px #6365f187;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-filters {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.status-btn {
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BLOCK статус
|
||||||
|
&--block {
|
||||||
|
background-color: white;
|
||||||
|
color: #dc2626;
|
||||||
|
border-color: #fca5a5;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #fef2f2;
|
||||||
|
border-color: #f87171;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #dc2626;
|
||||||
|
color: white;
|
||||||
|
border-color: #b91c1c;
|
||||||
|
box-shadow: 0 2px 4px rgba(220, 38, 38, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ACTIVE статус
|
||||||
|
&--active {
|
||||||
|
background-color: white;
|
||||||
|
color: #16a34a;
|
||||||
|
border-color: #86efac;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #f0fdf4;
|
||||||
|
border-color: #4ade80;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #16a34a;
|
||||||
|
color: white;
|
||||||
|
border-color: #15803d;
|
||||||
|
box-shadow: 0 2px 4px rgba(22, 163, 74, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MODERATION статус
|
||||||
|
&--moderation {
|
||||||
|
background-color: white;
|
||||||
|
color: #ca8a04;
|
||||||
|
border-color: #fde047;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #fefce8;
|
||||||
|
border-color: #facc15;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #ca8a04;
|
||||||
|
color: white;
|
||||||
|
border-color: #a16207;
|
||||||
|
box-shadow: 0 2px 4px rgba(202, 138, 4, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,10 +116,6 @@ export default function ContentAppealsTable({ permission }: { permission: number
|
|||||||
getSortParams.sortDirection
|
getSortParams.sortDirection
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log('Appeals data:', appealsData);
|
|
||||||
}, [appealsData]);
|
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const handleViewFile = useCallback((file: AppealsFile) => {
|
const handleViewFile = useCallback((file: AppealsFile) => {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import DropDownList from '@/app/components/dropDownList';
|
|||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import ModalWindow from '@/app/components/modalWindow';
|
import ModalWindow from '@/app/components/modalWindow';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useContentForModeration, ModerationFile, ContentForModeration } from '@/app/hooks/react-query/useContentForModeration';
|
import { useContentForModeration, ModerationFile, ContentForModeration, ModerationStatus } from '@/app/hooks/react-query/useContentForModeration';
|
||||||
import FileModerationModal from './FileModerationModal';
|
import FileModerationModal from './FileModerationModal';
|
||||||
import { FileTypeIcon } from '@/app/components/FileTypeIcon';
|
import { FileTypeIcon } from '@/app/components/FileTypeIcon';
|
||||||
import { getFileType } from '@/app/lib/getFileType';
|
import { getFileType } from '@/app/lib/getFileType';
|
||||||
@@ -59,6 +59,7 @@ const StatusBadge = ({ status }: { status: string }) => {
|
|||||||
'BLOCKED': { className: 'bg-red-100 text-red-800', label: 'Заблокировано' },
|
'BLOCKED': { 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: 'Одобрено' },
|
||||||
|
'ACTIVE': { className: 'bg-green-100 text-green-800', label: 'ACTIVE' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = statusConfig[status] || { className: 'bg-gray-100 text-gray-800', label: status };
|
const config = statusConfig[status] || { className: 'bg-gray-100 text-gray-800', label: status };
|
||||||
@@ -82,6 +83,10 @@ export default function ContentModerationTable({ permission }: { permission: num
|
|||||||
const [selectedFile, setSelectedFile] = useState<ModerationFile | null>(null);
|
const [selectedFile, setSelectedFile] = useState<ModerationFile | null>(null);
|
||||||
const [isFileLoading, setIsFileLoading] = useState(false);
|
const [isFileLoading, setIsFileLoading] = useState(false);
|
||||||
|
|
||||||
|
// НОВЫЕ СОСТОЯНИЯ ДЛЯ ФИЛЬТРОВ
|
||||||
|
const [selectedStatuses, setSelectedStatuses] = useState<ModerationStatus[]>([]);
|
||||||
|
const [isAppeal, setIsAppeal] = useState<boolean | undefined>(undefined);
|
||||||
|
|
||||||
const t = useTranslations("Global");
|
const t = useTranslations("Global");
|
||||||
|
|
||||||
// Получаем параметры сортировки для API
|
// Получаем параметры сортировки для API
|
||||||
@@ -104,6 +109,7 @@ export default function ContentModerationTable({ permission }: { permission: num
|
|||||||
};
|
};
|
||||||
}, [sorting]);
|
}, [sorting]);
|
||||||
|
|
||||||
|
// Используем хук с фильтрами
|
||||||
const {
|
const {
|
||||||
data: moderationData,
|
data: moderationData,
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -114,11 +120,45 @@ export default function ContentModerationTable({ permission }: { permission: num
|
|||||||
pagination.pageIndex,
|
pagination.pageIndex,
|
||||||
pagination.pageSize,
|
pagination.pageSize,
|
||||||
getSortParams.sortBy,
|
getSortParams.sortBy,
|
||||||
getSortParams.sortDirection
|
getSortParams.sortDirection,
|
||||||
|
selectedStatuses.length > 0 ? selectedStatuses : undefined,
|
||||||
|
isAppeal
|
||||||
);
|
);
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(moderationData);
|
||||||
|
}, [moderationData])
|
||||||
|
|
||||||
|
const toggleStatus = (status: ModerationStatus) => {
|
||||||
|
setSelectedStatuses(prev => {
|
||||||
|
if (prev.includes(status)) {
|
||||||
|
return prev.filter(s => s !== status);
|
||||||
|
} else {
|
||||||
|
return [...prev, status];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setPagination(prev => ({ ...prev, pageIndex: 0 }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearAllStatuses = () => {
|
||||||
|
setSelectedStatuses([]);
|
||||||
|
setPagination(prev => ({ ...prev, pageIndex: 0 }));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Обработчик изменения isAppeal
|
||||||
|
const handleAppealChange = (value: string) => {
|
||||||
|
if (value === 'all') {
|
||||||
|
setIsAppeal(undefined);
|
||||||
|
} else if (value === 'hasAppeal') {
|
||||||
|
setIsAppeal(true);
|
||||||
|
} else if (value === 'noAppeal') {
|
||||||
|
setIsAppeal(false);
|
||||||
|
}
|
||||||
|
setPagination(prev => ({ ...prev, pageIndex: 0 }));
|
||||||
|
};
|
||||||
|
|
||||||
const handleViewFile = useCallback((file: ModerationFile) => {
|
const handleViewFile = useCallback((file: ModerationFile) => {
|
||||||
console.log('View file:', file);
|
console.log('View file:', file);
|
||||||
setSelectedFile(file);
|
setSelectedFile(file);
|
||||||
@@ -270,36 +310,6 @@ export default function ContentModerationTable({ permission }: { permission: num
|
|||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
/* {
|
|
||||||
accessorKey: 'moderationInfo.hasActiveAppeal',
|
|
||||||
header: ({ column }) => (
|
|
||||||
<div className="column">
|
|
||||||
<span>
|
|
||||||
Апелляция
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
|
||||||
className="sort-button"
|
|
||||||
>
|
|
||||||
{column.getIsSorted() === 'asc' ?
|
|
||||||
<span><IconArrowUp /></span>
|
|
||||||
: column.getIsSorted() === 'desc' ?
|
|
||||||
<span><IconArrowDown /></span>
|
|
||||||
: <span><IconFilter /></span>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="text-center">
|
|
||||||
{row.original.moderationInfo.hasActiveAppeal ? (
|
|
||||||
<span className="text-blue-600">Активна</span>
|
|
||||||
) : (
|
|
||||||
<span className="text-gray-400">Нет</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
} */
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if (permission === 3) {
|
if (permission === 3) {
|
||||||
@@ -393,12 +403,52 @@ export default function ContentModerationTable({ permission }: { permission: num
|
|||||||
<ModalWindow state={openWindow} callBack={handleCloseModal}>
|
<ModalWindow state={openWindow} callBack={handleCloseModal}>
|
||||||
<FileModerationModal file={selectedFile} onClose={handleCloseModal} />
|
<FileModerationModal file={selectedFile} onClose={handleCloseModal} />
|
||||||
</ModalWindow>
|
</ModalWindow>
|
||||||
|
|
||||||
{/* Фильтры */}
|
|
||||||
<div className="tanstak-table-filtres">
|
|
||||||
<h3>
|
<h3>
|
||||||
Таблица модерации контента
|
Таблица модерации контента
|
||||||
</h3>
|
</h3>
|
||||||
|
{/* Фильтры */}
|
||||||
|
<div className="tanstak-table-filtres">
|
||||||
|
{/* Блок фильтрации по статусам */}
|
||||||
|
<div className="">
|
||||||
|
<div className="table-filtres-label">Статусы:</div>
|
||||||
|
<div className="status-filters">
|
||||||
|
<button
|
||||||
|
onClick={() => toggleStatus('BLOCKED')}
|
||||||
|
className={`status-btn status-btn--block ${selectedStatuses.includes('BLOCKED') ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
BLOCKED
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => toggleStatus('ACTIVE')}
|
||||||
|
className={`status-btn status-btn--active ${selectedStatuses.includes('ACTIVE') ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
ACTIVE
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => toggleStatus('MODERATION')}
|
||||||
|
className={`status-btn status-btn--moderation ${selectedStatuses.includes('MODERATION') ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
MODERATION
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Блок фильтрации по апелляции */}
|
||||||
|
<div className="table-filtres-item appeal">
|
||||||
|
<div className="table-filtres-label">Апелляция:</div>
|
||||||
|
<DropDownList
|
||||||
|
value={isAppeal === undefined ? t('all') : t('has-appeal')}
|
||||||
|
callBack={handleAppealChange}
|
||||||
|
>
|
||||||
|
<li value="all">
|
||||||
|
{t('all')}
|
||||||
|
</li>
|
||||||
|
<li value="hasAppeal">
|
||||||
|
{t('has-appeal')}
|
||||||
|
</li>
|
||||||
|
{/* <li value="noAppeal">Без апелляции</li> */}
|
||||||
|
</DropDownList>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="table-filtres-item">
|
<div className="table-filtres-item">
|
||||||
<div className="table-filtres-label">{t('items-per-page')}:</div>
|
<div className="table-filtres-label">{t('items-per-page')}:</div>
|
||||||
|
|||||||
@@ -223,7 +223,9 @@
|
|||||||
"save": "Save",
|
"save": "Save",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"create-stuff": "Create stuff",
|
"create-stuff": "Create stuff",
|
||||||
"download": "Download"
|
"download": "Download",
|
||||||
|
"all": "All",
|
||||||
|
"has-appeal": "Has appeal"
|
||||||
},
|
},
|
||||||
"Login-register-form": {
|
"Login-register-form": {
|
||||||
"and": "and",
|
"and": "and",
|
||||||
|
|||||||
@@ -223,7 +223,9 @@
|
|||||||
"save": "Сохранить",
|
"save": "Сохранить",
|
||||||
"delete": "Удалить",
|
"delete": "Удалить",
|
||||||
"create-stuff": "Создать сотрудника",
|
"create-stuff": "Создать сотрудника",
|
||||||
"download": "Скачать"
|
"download": "Скачать",
|
||||||
|
"all": "Все",
|
||||||
|
"has-appeal": "Есть апелляция"
|
||||||
},
|
},
|
||||||
"Login-register-form": {
|
"Login-register-form": {
|
||||||
"and": "и",
|
"and": "и",
|
||||||
|
|||||||
Reference in New Issue
Block a user