diff --git a/public/images/no-image.png b/public/images/no-image.png new file mode 100644 index 0000000..942404a Binary files /dev/null and b/public/images/no-image.png differ diff --git a/src/app/actions/contentActions.ts b/src/app/actions/contentActions.ts index 55604cc..a8015ef 100644 --- a/src/app/actions/contentActions.ts +++ b/src/app/actions/contentActions.ts @@ -5,16 +5,6 @@ import { API_BASE_URL } from '@/app/actions/definitions'; export async function fetchModerationContentList(page?: number, size?: number, sortBy?: string, sortDirection?: 'asc' | 'desc' | string) { const token = await getSessionData('token'); - console.log('fetchModerationContentList'); -/* console.log( - { - action: 'files_for_moderation', - page: page, - page_size: size, - sort_by: sortBy || '', - sort_direction: sortDirection || 'asc', - } - ) */ try { const response = await fetch(`${API_BASE_URL}/api/admin/info`, { @@ -26,11 +16,8 @@ export async function fetchModerationContentList(page?: number, size?: number, s action: 'files_for_moderation', page: page, page_size: size, -/* sort_by: sortBy || '', - sort_order: sortDirection || 'asc', */ - /* full_name: nameQuery, */ - /* fileName: '', - userId: '' */ + sort_by: sortBy || '', + sort_order: sortDirection || 'asc', } }), headers: { @@ -42,7 +29,6 @@ export async function fetchModerationContentList(page?: number, size?: number, s if (response.ok) { const parsed = await response.json(); - console.log(parsed); if (parsed.message_code === 0) { return parsed.message_body.message_body; @@ -60,10 +46,9 @@ export async function fetchModerationContentList(page?: number, size?: number, s export async function changeModerationContentStatus(fileId?: string, fileStatus?: string) { const token = await getSessionData('token'); - console.log(`changeModerationContentStatus - ${fileId} - ${fileStatus}`); try { - const response = await fetch(`${API_BASE_URL}/api/admin/info`, { + const response = await fetch(`${API_BASE_URL}/api/admin/control`, { method: 'POST', body: JSON.stringify({ version: 1, diff --git a/src/app/components/FileTypeIcon.tsx b/src/app/components/FileTypeIcon.tsx new file mode 100644 index 0000000..3b57350 --- /dev/null +++ b/src/app/components/FileTypeIcon.tsx @@ -0,0 +1,22 @@ +import { IconImageFile, IconVideoFile, IconAudioFile, IconDocument } from '@/app/ui/icons/icons'; + +export const FileTypeIcon = ({ type }: { type: string }) => { + switch (type) { + case 'image': + return + + ; + case 'video': + return + + ; + case 'audio': + return + + ; + default: + return + + ; + } +}; \ 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 d4d5077..91d2ed9 100644 --- a/src/app/hooks/react-query/useContentForModeration.ts +++ b/src/app/hooks/react-query/useContentForModeration.ts @@ -10,6 +10,8 @@ export interface ModerationFile { userId: number; fileId: string; status: 'BLOCKED' | 'MODERATION' | string; + image: string; + downloadUrl: string; } export interface ContentForModeration { diff --git a/src/app/lib/getFileType.ts b/src/app/lib/getFileType.ts new file mode 100644 index 0000000..450badd --- /dev/null +++ b/src/app/lib/getFileType.ts @@ -0,0 +1,34 @@ +export const getFileType = (fileName: string): 'image' | 'video' | 'audio' | 'document' | 'unknown' => { + const lastDotIndex = fileName.lastIndexOf('.'); + + if (lastDotIndex <= 0) { + return 'unknown'; + } + + const extension = fileName.substring(lastDotIndex + 1).toLowerCase(); + + const allowedExtensions = { + images: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg', 'ico'], + videos: ['mp4', 'webm', 'ogg', 'mov', 'avi', 'mkv', 'flv', 'wmv'], + audios: ['mp3', 'wav', 'ogg', 'flac', 'aac', 'm4a', 'wma'], + documents: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'rtf', 'csv', 'md'] + }; + + if (allowedExtensions.images.includes(extension)) { + return 'image'; + } + + if (allowedExtensions.videos.includes(extension)) { + return 'video'; + } + + if (allowedExtensions.audios.includes(extension)) { + return 'audio'; + } + + if (allowedExtensions.documents.includes(extension)) { + return 'document'; + } + + return 'unknown'; +} \ No newline at end of file diff --git a/src/app/styles/global-styles.scss b/src/app/styles/global-styles.scss index 6e8f9e1..87f4b79 100644 --- a/src/app/styles/global-styles.scss +++ b/src/app/styles/global-styles.scss @@ -356,7 +356,7 @@ flex-wrap: wrap; gap: 12px; -/* &-actions { + /* &-actions { display: flex; gap: 12px; flex-wrap: wrap; @@ -1199,6 +1199,22 @@ } } } + + .color-image { + color: v.$color-image; + } + + .color-video { + color: v.$color-video; + } + + .color-audio { + color: v.$color-audio; + } + + .color-document { + color: v.$color-document; + } } .empty-state { diff --git a/src/app/ui/content/FileModerationModal.tsx b/src/app/ui/content/FileModerationModal.tsx index d03f16a..7e1c791 100644 --- a/src/app/ui/content/FileModerationModal.tsx +++ b/src/app/ui/content/FileModerationModal.tsx @@ -12,6 +12,19 @@ interface FileModerationModalProps { onClose: () => void; } +const getLabel = (status: string) => { + switch (status) { + case 'BLOCKED': + return 'Заблокировано'; + case 'MODERATION': + return 'На модерации'; + case 'ACTIVE': + return 'Одобрено'; + default: + return status; + } +}; + const StatusBadge = ({ status }: { status: string }) => { const getBadgeClass = () => { switch (status) { @@ -19,29 +32,15 @@ const StatusBadge = ({ status }: { status: string }) => { return 'status-badge--blocked'; case 'MODERATION': return 'status-badge--moderation'; - case 'APPROVED': + case 'ACTIVE': return 'status-badge--approved'; default: return 'status-badge--default'; } }; - - const getLabel = () => { - switch (status) { - case 'BLOCKED': - return 'Заблокировано'; - case 'MODERATION': - return 'На модерации'; - case 'APPROVED': - return 'Одобрено'; - default: - return status; - } - }; - return ( - {getLabel()} + {getLabel(status)} ); }; @@ -107,11 +106,12 @@ export default function FileModerationModal({ file, onClose }: FileModerationMod
setSelectedStatus(value)} >
  • На модерации
  • Заблокировано
  • +
  • Одобрено
  • diff --git a/src/app/ui/content/content-moderation-table.tsx b/src/app/ui/content/content-moderation-table.tsx index d448359..c430e1f 100644 --- a/src/app/ui/content/content-moderation-table.tsx +++ b/src/app/ui/content/content-moderation-table.tsx @@ -11,7 +11,7 @@ import { SortingState, ColumnFiltersState, } from '@tanstack/react-table'; -import { IconEye, IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter } from '@/app/ui/icons/icons'; +import { IconEye, IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter, IconFileDownload } from '@/app/ui/icons/icons'; import { useTranslations } from 'next-intl'; import DropDownList from '@/app/components/dropDownList'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; @@ -19,6 +19,15 @@ import ModalWindow from '@/app/components/modalWindow'; import { toast } from 'sonner'; import { useContentForModeration, ModerationFile, ContentForModeration } from '@/app/hooks/react-query/useContentForModeration'; import FileModerationModal from './FileModerationModal'; +import { FileTypeIcon } from '@/app/components/FileTypeIcon'; +import { getFileType } from '@/app/lib/getFileType'; +import Image from 'next/image'; + +const cutFileExtension = (fileName: string) => { + const lastDotIndex = fileName.lastIndexOf('.'); + const extension = fileName.substring(lastDotIndex + 1); + return extension; +} const cutFileName = (fileName: string) => { const MAX_FILE_NAME_LENGTH = 30; @@ -69,8 +78,8 @@ export default function ContentModerationTable({ permission }: { permission: num pageSize: 10, }); const [openWindow, setOpenWindow] = useState(false); - const [openWindowChildren, setOpenWindowChildren] = useState(null); const [selectedFile, setSelectedFile] = useState(null); + const [isFileLoading, setIsFileLoading] = useState(false); const t = useTranslations("Global"); @@ -82,7 +91,7 @@ export default function ContentModerationTable({ permission }: { permission: num const sort = sorting[0]; const sortByMap: Record = { - 'fileName': 'fileName', + 'fileName': 'originalFileName', 'userId': 'userId', 'status': 'status', 'createdAt': 'createdAt', @@ -124,6 +133,36 @@ export default function ContentModerationTable({ permission }: { permission: num setSelectedFile(null); }, []); + const handleDownload = async (file: any) => { + setIsFileLoading(true); + console.log(file.downloadUrl); + + try { + const response = await fetch(file.downloadUrl); + + if (!response.ok) { + throw new Error('Download failed'); + } + + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = file._original?.fileName || file.fileName || `file-${file.id}`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + + toast.success(`${t('file-is-downloading')} - ${file.fileName}`); + } catch (error) { + console.error('Download error:', error); + toast.error(t('failed-to-download-file')); + } finally { + setIsFileLoading(false); + } + } + const columns = useMemo[]>(() => { const allColumns: ColumnDef[] = [ { @@ -147,9 +186,32 @@ export default function ContentModerationTable({ permission }: { permission: num ), cell: ({ row }) => ( -
    -
    - {cutFileName(row.original.fileName)} +
    + {getFileType(row.original.fileName) === 'image' && row.original?.image.length !== 0 ? ( + + {row.original?.fileName} { + const target = e.target as HTMLImageElement; + target.src = '/images/no-image.png'; + }} + /> + + ) : ( + + )} +
    +
    + + {cutFileName(row.original.fileName)} + +
    ), @@ -207,7 +269,7 @@ export default function ContentModerationTable({ permission }: { permission: num
    ), }, - { + /* { accessorKey: 'moderationInfo.hasActiveAppeal', header: ({ column }) => (
    @@ -236,7 +298,7 @@ export default function ContentModerationTable({ permission }: { permission: num )}
    ), - } + } */ ]; if (permission === 3) { @@ -253,6 +315,15 @@ export default function ContentModerationTable({ permission }: { permission: num > + +
    ), diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index f02b0ac..142c588 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -222,7 +222,8 @@ "delete-template-warning": "Are you sure you want to delete this template?", "save": "Save", "delete": "Delete", - "create-stuff": "Create stuff" + "create-stuff": "Create stuff", + "download": "Download" }, "Login-register-form": { "and": "and", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index db4004c..36d1bd2 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -222,7 +222,8 @@ "delete-template-warning": "Вы уверены, что хотите удалить шаблон?", "save": "Сохранить", "delete": "Удалить", - "create-stuff": "Создать сотрудника" + "create-stuff": "Создать сотрудника", + "download": "Скачать" }, "Login-register-form": { "and": "и",