update files-state, add toast, add zustand

This commit is contained in:
smanylov
2025-12-29 14:00:02 +07:00
parent fffd38a812
commit 198a01d026
9 changed files with 134 additions and 52 deletions
+69 -47
View File
@@ -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">