diff --git a/package-lock.json b/package-lock.json index 57e9b7b..072f28b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "react": "19.2.0", "react-dom": "19.2.0", "recharts": "^3.5.1", + "sonner": "^2.0.7", "tailwindcss": "^4.1.17", "zod": "^4.1.13", "zustand": "^5.0.9" @@ -7119,6 +7120,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sonner": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", diff --git a/package.json b/package.json index b861e09..12ddfe5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.9.0", + "version": "0.10.0", "private": true, "scripts": { "dev": "next dev -p 2999", @@ -22,6 +22,7 @@ "react": "19.2.0", "react-dom": "19.2.0", "recharts": "^3.5.1", + "sonner": "^2.0.7", "tailwindcss": "^4.1.17", "zod": "^4.1.13", "zustand": "^5.0.9" diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index fc35eea..d684eaf 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -3,6 +3,7 @@ import type { Metadata } from "next"; import { NextIntlClientProvider, hasLocale } from 'next-intl'; import { notFound } from 'next/navigation'; import { routing } from '@/i18n/routing'; +import { ToastProvider } from '@/app/providers/ToastProvider'; import "../styles/globals.css"; import "../styles/global-styles.scss"; @@ -33,6 +34,7 @@ export default async function RootLayout({ {children} + diff --git a/src/app/components/tanstakTable.tsx b/src/app/components/tanstakTable.tsx index e483206..6bd3770 100644 --- a/src/app/components/tanstakTable.tsx +++ b/src/app/components/tanstakTable.tsx @@ -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 @@ -69,23 +84,18 @@ const formatDateTime = (timestamp: number) => { export default function TanstakFilesTable() { const { - data: userFilesData, + data: tableData, isLoading, isError, error, - refetch - } = useQuery({ + } = useQuery({ queryKey: ['userFilesData'], - queryFn: () => { - return getUserFilesData(); - } - }); + queryFn: getUserFilesData, - const [tableData, setTableData] = useState([]); + 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([]); @@ -343,7 +354,7 @@ export default function TanstakFilesTable() {
diff --git a/src/app/components/upload-section-file.tsx b/src/app/components/upload-section-file.tsx index 8c132b2..3ba8a67 100644 --- a/src/app/components/upload-section-file.tsx +++ b/src/app/components/upload-section-file.tsx @@ -5,6 +5,7 @@ import { IconShieldAdd } from '@/app/ui/icons/icons'; import { fileUpload, cancelUpload, chunkUpload, checkChunkStatus } from '@/app/actions/fileUpload'; import { useTranslations } from 'next-intl'; import { useNavigationBlocker } from '@/app/hooks/useNavigationBlocker'; +import { useQueryClient } from '@tanstack/react-query'; interface SelectedFile { file: File; preview: string | undefined; @@ -38,7 +39,7 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile const [uploadId, setUploadId] = useState(null); const [isFileUploaded, setIsFileUploaded] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); - + const queryClient = useQueryClient(); const isCancelledRef = useRef(false); const t = useTranslations('Global'); @@ -92,7 +93,7 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile default: break; - } + }; const acceptString = useMemo(() => { if (!allowedExtensions || !Array.isArray(allowedExtensions)) { @@ -292,6 +293,7 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile const chunkStatus = await checkChunkStatus(response.upload_id); if (chunkStatus.message_body.missing_chunks === 0) { setIsFileUploaded(true); + await queryClient.invalidateQueries({ queryKey: ['userFilesData'] }); } else { throw new Error('Not all chunks were uploaded'); } diff --git a/src/app/providers/ToastProvider.tsx b/src/app/providers/ToastProvider.tsx new file mode 100644 index 0000000..5ab5041 --- /dev/null +++ b/src/app/providers/ToastProvider.tsx @@ -0,0 +1,17 @@ +'use client'; + +import { Toaster } from 'sonner'; + +export function ToastProvider() { + return ( + + ); +} \ No newline at end of file diff --git a/src/app/stores/useStoreWithDevtools.ts b/src/app/stores/useStoreWithDevtools.ts new file mode 100644 index 0000000..d900c73 --- /dev/null +++ b/src/app/stores/useStoreWithDevtools.ts @@ -0,0 +1,23 @@ +import { create } from 'zustand' +import { devtools } from 'zustand/middleware' + +interface Store { + value: any + setValue: (value: any) => void +} + +export const useStoreWithDevtools = create()( + devtools( + (set) => ({ + value: 0, + setValue: (value) => set({ value }), + }), + { + name: 'DevStore', // имя для DevTools + } + ) +) + +/* пример */ +/* const setText = useStoreWithDevtools(s => s.setValue); */ +/* const text = useStoreWithDevtools(s => s.value); */ \ No newline at end of file diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 344f6ec..27ae535 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -148,7 +148,9 @@ "yes": "yes", "no": "no", "you-sure-you-want-to-delete": "Are you sure you want to delete?", - "reports-and-analytics": "Reports and analytics" + "reports-and-analytics": "Reports and analytics", + "file-has-been-deleted": "The file has been deleted", + "error": "Error" }, "Login-register-form": { "and": "and", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 32f726e..a144fc6 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -148,7 +148,9 @@ "yes": "да", "no": "нет", "you-sure-you-want-to-delete": "Уверены, что хотите удалить?", - "reports-and-analytics": "Отчёты и аналитика" + "reports-and-analytics": "Отчёты и аналитика", + "file-has-been-deleted": "Файл удален", + "error": "Ошбка" }, "Login-register-form": { "and": "и",