update files-state, add toast, add zustand
This commit is contained in:
@@ -14,23 +14,38 @@ import {
|
||||
import { IconImageFile, IconVideoFile, IconAudioFile, IconEye, IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter, IconFileOpen, IconShieldExclamation, IconDelete } from '@/app/ui/icons/icons';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import DropDownList from '@/app/components/dropDownList';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { getUserFilesData, removeUserFile } from '@/app/actions/fileEntity';
|
||||
import ModalWindow from '@/app/components/modalWindow';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
// Типы данных
|
||||
type FileType = 'image' | 'video' | 'audio';
|
||||
|
||||
type FileItem = {
|
||||
id: string;
|
||||
fileName: string;
|
||||
fileType: FileType;
|
||||
fileType: string;
|
||||
violations?: number | undefined;
|
||||
checks?: number | undefined;
|
||||
lastCheck?: number; // timestamp в миллисекундах
|
||||
lastCheck?: number;
|
||||
_original?: ApiFile;
|
||||
};
|
||||
|
||||
type ApiFile = {
|
||||
id: string;
|
||||
originalFileName: string;
|
||||
mimeType: string;
|
||||
violations: number;
|
||||
checks: number;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
type ApiResponse = {
|
||||
files?: ApiFile[];
|
||||
};
|
||||
|
||||
// Иконки для типов файлов
|
||||
const FileTypeIcon = ({ type }: { type: FileType }) => {
|
||||
const FileTypeIcon = ({ type }: { type: string }) => {
|
||||
switch (type) {
|
||||
case 'image':
|
||||
return <span className="text-[#f08c00]">
|
||||
@@ -69,23 +84,18 @@ const formatDateTime = (timestamp: number) => {
|
||||
|
||||
export default function TanstakFilesTable() {
|
||||
const {
|
||||
data: userFilesData,
|
||||
data: tableData,
|
||||
isLoading,
|
||||
isError,
|
||||
error,
|
||||
refetch
|
||||
} = useQuery({
|
||||
} = useQuery<ApiResponse, Error, FileItem[], ['userFilesData']>({
|
||||
queryKey: ['userFilesData'],
|
||||
queryFn: () => {
|
||||
return getUserFilesData();
|
||||
}
|
||||
});
|
||||
queryFn: getUserFilesData,
|
||||
|
||||
const [tableData, setTableData] = useState<FileItem[]>([]);
|
||||
select: (data: ApiResponse): FileItem[] => {
|
||||
if (!data?.files) return [];
|
||||
|
||||
useEffect(() => {
|
||||
if (userFilesData?.files) {
|
||||
const formattedData = userFilesData.files.map((item: any) => {
|
||||
return data.files.map((item: ApiFile) => {
|
||||
const [datePart, timePart] = item.updatedAt.split(' ');
|
||||
const [day, month, year] = datePart.split('-').map(Number);
|
||||
const [hours, minutes, seconds] = timePart.split(':').map(Number);
|
||||
@@ -97,13 +107,14 @@ export default function TanstakFilesTable() {
|
||||
fileType: item.mimeType.toLocaleLowerCase(),
|
||||
violations: item.violations,
|
||||
checks: item.checks,
|
||||
lastCheck: newDate
|
||||
lastCheck: newDate,
|
||||
_original: item
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
setTableData(formattedData);
|
||||
}
|
||||
}, [userFilesData]);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// Состояния
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
@@ -343,7 +354,7 @@ export default function TanstakFilesTable() {
|
||||
<IconShieldExclamation />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleRemove(row.original)}
|
||||
onClick={() => handleOpenWindowForRemove(row.original)}
|
||||
className="px-3 py-1 bg-red-700 text-white rounded hover:bg-red-800 text-sm"
|
||||
>
|
||||
<IconDelete />
|
||||
@@ -370,7 +381,7 @@ export default function TanstakFilesTable() {
|
||||
console.log(`Щиток: ${file.fileName}`);
|
||||
};
|
||||
|
||||
const handleRemove = async (file: FileItem) => {
|
||||
const handleOpenWindowForRemove = async (file: FileItem) => {
|
||||
|
||||
setOpenWindowChildren(() => {
|
||||
return (
|
||||
@@ -381,18 +392,12 @@ export default function TanstakFilesTable() {
|
||||
<div className="mt-2 mb-8 text-center">{file.fileName}</div>
|
||||
<div className="flex justify-center gap-4">
|
||||
<button className="btn-primary btn-modal"
|
||||
onClick={async () => {
|
||||
const response = await removeUserFile(file.id);
|
||||
if (response === file.id) {
|
||||
setTableData(
|
||||
prev => prev.filter(item => item.id !== file.id)
|
||||
)
|
||||
setOpenWindow(false);
|
||||
setOpenWindowChildren(null);
|
||||
}
|
||||
onClick={() => {
|
||||
deleteMutation.mutate(file.id)
|
||||
}}
|
||||
disabled={deleteMutation.isPending}
|
||||
>
|
||||
{t('yes')}
|
||||
{deleteMutation.isPending ? '...' : t('yes')}
|
||||
</button>
|
||||
<button className="btn-primary btn-modal"
|
||||
onClick={() => {
|
||||
@@ -409,9 +414,41 @@ export default function TanstakFilesTable() {
|
||||
setOpenWindow(true);
|
||||
};
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: removeUserFile,
|
||||
|
||||
onMutate: async (fileId: string) => {
|
||||
await queryClient.cancelQueries({ queryKey: ['userFilesData'] });
|
||||
|
||||
queryClient.setQueryData<ApiResponse>(['userFilesData'], (old) => {
|
||||
if (!old?.files) return old;
|
||||
return {
|
||||
...old,
|
||||
files: old.files.filter(file => file.id !== fileId)
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
onError: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['userFilesData'] });
|
||||
toast.error(t('error'));
|
||||
},
|
||||
|
||||
onSuccess: (response, fileId) => {
|
||||
if (response === fileId) {
|
||||
setOpenWindow(false);
|
||||
setOpenWindowChildren(null);
|
||||
toast.success(t('file-has-been-deleted'));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Фильтрация по типу файла и дате
|
||||
const filteredData = useMemo(() => {
|
||||
let result = tableData;
|
||||
if (!result) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Фильтр по типу файла
|
||||
if (typeFilter !== 'all') {
|
||||
@@ -503,21 +540,6 @@ export default function TanstakFilesTable() {
|
||||
<div className="p-6 mx-auto tanstak-table">
|
||||
<ModalWindow children={openWindowChildren} state={openWindow} callBack={setOpenWindow}></ModalWindow>
|
||||
{/* Фильтры */}
|
||||
<button
|
||||
className="mb-2 btn-primary rounded disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
onClick={() => refetch()}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<span className="flex items-center gap-2">
|
||||
refreshing...
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex items-center gap-2">
|
||||
refresh
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 pb-4 pt-0 px-0">
|
||||
<div className="flex flex-col md:flex-row gap-4 w-full md:w-auto">
|
||||
<div className="flex flex-col w-50">
|
||||
|
||||
Reference in New Issue
Block a user