add multiselect for files tanstak-table

This commit is contained in:
smanylov
2026-03-30 12:48:31 +07:00
parent 534e18bbd4
commit 154e9cc5c1
5 changed files with 175 additions and 30 deletions
@@ -0,0 +1,48 @@
'use client'
import { useTranslations } from 'next-intl';
import { toast } from 'sonner';
export function SelectedFilesAction({ filesId, callBack }: { filesId: Set<string>, callBack: (value: Set<string>) => void }) {
const t = useTranslations('Global');
if (filesId.size === 0) {
return null;
}
function downloadHandler() {
if (filesId.size) {
const filesArray = Array.from(filesId);
const filesString = filesArray.join(', ');
toast.success(`${t('download')}: ${filesString}`)
}
}
function clearHandler() {
callBack(new Set());
}
return (
<div
className="selected-files-wrapper"
>
<button
className="btn btn-primary"
onClick={() => {
downloadHandler();
}}
>
{t('download')}
</button>
<button
className="btn btn-primary"
onClick={() => {
clearHandler();
}}
>
{t('cancel')}
</button>
</div>
)
}
@@ -12,7 +12,7 @@ import {
ColumnFiltersState, ColumnFiltersState,
PaginationState PaginationState
} from '@tanstack/react-table'; } from '@tanstack/react-table';
import { IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter, IconFileDownload, IconInfo } from '@/app/ui/icons/icons'; import { IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter, IconFileDownload, IconInfo, IconEye, IconCheck } from '@/app/ui/icons/icons';
import { useTranslations, useLocale } from 'next-intl'; import { useTranslations, useLocale } from 'next-intl';
import DropDownList from '@/app/components/DropDownList'; import DropDownList from '@/app/components/DropDownList';
import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query';
@@ -29,6 +29,8 @@ import { formatDate, formatDateTime } from '@/app/lib/formatDate';
import { useViewport } from '@/app/hooks/useViewport'; import { useViewport } from '@/app/hooks/useViewport';
import { downloadFile } from '@/app/actions/fileEntity'; import { downloadFile } from '@/app/actions/fileEntity';
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from 'use-debounce';
import Link from 'next/link';
import { SelectedFilesAction } from '@/app/components/tanstak-table/SelectedFilesActions';
export type FileItem = { export type FileItem = {
id: string; id: string;
@@ -80,9 +82,10 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
pageIndex: 0, pageIndex: 0,
pageSize: 10, pageSize: 10,
}); });
const [query, setQuery] = useState<string>(''); /* const [query, setQuery] = useState<string>(''); */
const [searchInputValue, setSearchInputValue] = useState<string>(''); const [searchInputValue, setSearchInputValue] = useState<string>('');
const [searchQuery, setSearchQuery] = useState<string>(''); const [searchQuery, setSearchQuery] = useState<string>('');
const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set());
const debouncedSetSearchQuery = useDebouncedCallback( const debouncedSetSearchQuery = useDebouncedCallback(
(value: string) => { (value: string) => {
@@ -260,9 +263,49 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
return nameWithoutExtension.substring(0, maxNameLength) + '...' /* + extension */; return nameWithoutExtension.substring(0, maxNameLength) + '...' /* + extension */;
} }
function selectHandler(fileId: string) {
setSelectedFiles((prev) => {
const newSet = new Set(prev)
if (newSet.has(fileId)) {
newSet.delete(fileId)
} else {
newSet.add(fileId)
}
return newSet;
});
}
// Определение колонок // Определение колонок
const columns = useMemo<ColumnDef<FileItem>[]>( const columns = useMemo<ColumnDef<FileItem>[]>(
() => [ () => [
{
accessorKey: 'selectd',
header: ({ column }) => (
<div className="column">
<span>
</span>
</div>
),
cell: ({ row }) => {
return (
<div className="text-center table-item table-item-id">
<button
className={`table-item-checkbox ${selectedFiles.has(row.original.id) ? 'selected' : ''}`}
onClick={() => {
selectHandler(row.original.id);
}}
>
{selectedFiles.has(row.original.id) && (
<IconCheck />
)}
</button>
</div>
)
},
},
{ {
accessorKey: 'id', accessorKey: 'id',
header: ({ column }) => ( header: ({ column }) => (
@@ -478,6 +521,13 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
cell: ({ row }) => ( cell: ({ row }) => (
<div className="actions"> <div className="actions">
<div className="actions-group"> <div className="actions-group">
<Link
href={`/pages/file/${row.original.id}`}
className="bg-violet-500 hover:bg-violet-600"
title={t('view')}
>
<IconEye />
</Link>
</div> </div>
<div className="actions-group"> <div className="actions-group">
{row.original.protectStatus === 'PROTECTED' && ( {row.original.protectStatus === 'PROTECTED' && (
@@ -507,7 +557,7 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
enableColumnFilter: false, enableColumnFilter: false,
}, },
], ],
[viewport.device] [viewport.device, selectedFiles]
); );
const handleView = async (file: FileItem) => { const handleView = async (file: FileItem) => {
@@ -613,7 +663,7 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
} }
return result; return result;
}, [query, typeFilter, dateFilter]); }, [/* query, */ typeFilter, dateFilter]);
useEffect(() => { useEffect(() => {
const currentPageRows = table.getRowModel().rows; const currentPageRows = table.getRowModel().rows;
@@ -904,6 +954,7 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
</button> </button>
</div> </div>
</div> </div>
<SelectedFilesAction filesId={selectedFiles} callBack={setSelectedFiles} />
</div > </div >
); );
} }
+64 -26
View File
@@ -321,6 +321,7 @@
border-radius: 20px; border-radius: 20px;
padding: 30px; padding: 30px;
box-shadow: 0 4px 20px v.$shadow-1; box-shadow: 0 4px 20px v.$shadow-1;
margin-bottom: 2rem;
h3 { h3 {
margin-bottom: 20px; margin-bottom: 20px;
@@ -635,7 +636,7 @@
.table-filtres-text-filter { .table-filtres-text-filter {
background-color: #fff; background-color: #fff;
border: 2px solid #e2e8f0; border: 2px solid v.$border-color-1;
border-radius: 12px; border-radius: 12px;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
@@ -694,7 +695,7 @@
padding-right: 0.5rem; padding-right: 0.5rem;
padding-top: 0.25rem; padding-top: 0.25rem;
padding-bottom: 0.25rem; padding-bottom: 0.25rem;
border: 2px solid #e2e8f0; border: 2px solid v.$border-color-1;
border-radius: 10px; border-radius: 10px;
box-shadow: 0 1px 2px #0000000d; box-shadow: 0 1px 2px #0000000d;
color: v.$p-color; color: v.$p-color;
@@ -721,7 +722,7 @@
gap: 0.25rem; gap: 0.25rem;
button { button {
border: 2px solid #e2e8f0; border: 2px solid v.$border-color-1;
padding: 0.25rem 0.75rem; padding: 0.25rem 0.75rem;
border-radius: 10px; border-radius: 10px;
color: v.$p-color; color: v.$p-color;
@@ -947,6 +948,22 @@
} }
} }
} }
.table-item-checkbox {
width: 16px;
height: 16px;
border-radius: 4px;
border: 2px solid v.$border-color-1;
cursor: pointer;
transition: all 0.3s ease-in;
display: flex;
justify-content: center;
align-items: center;
&:hover {
border: 2px solid v.$border-color-1-hover;
}
}
} }
} }
@@ -1117,7 +1134,7 @@
transition: all 0.3s ease; transition: all 0.3s ease;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
border: 2px solid #e2e8f0; border: 2px solid v.$border-color-1;
&::before { &::before {
content: ''; content: '';
@@ -1194,7 +1211,7 @@
display: flex; display: flex;
flex: 1; flex: 1;
padding: 10px; padding: 10px;
border: 2px solid #e2e8f0; border: 2px solid v.$border-color-1;
border-radius: 12px; border-radius: 12px;
font-size: 14px; font-size: 14px;
background: #f8f9fa; background: #f8f9fa;
@@ -1267,7 +1284,7 @@
.level-card { .level-card {
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
border: 2px solid #e2e8f0; border: 2px solid v.$border-color-1;
border-radius: 16px; border-radius: 16px;
padding: 18px; padding: 18px;
text-align: center; text-align: center;
@@ -1704,7 +1721,7 @@
padding: 16px; padding: 16px;
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-radius: 14px; border-radius: 14px;
border: 1px solid #e2e8f0; border: 1px solid v.$border-color-1;
transition: all 0.3s ease; transition: all 0.3s ease;
&:hover { &:hover {
@@ -1984,7 +2001,7 @@
.progress-bar-container { .progress-bar-container {
width: 100%; width: 100%;
height: 12px; height: 12px;
background: #e2e8f0; background: v.$border-color-1;
border-radius: 6px; border-radius: 6px;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
@@ -2082,7 +2099,7 @@
.country-bar { .country-bar {
width: 100%; width: 100%;
height: 5px; height: 5px;
background: #e2e8f0; background: v.$border-color-1;
border-radius: 2.5px; border-radius: 2.5px;
overflow: hidden; overflow: hidden;
} }
@@ -2201,7 +2218,7 @@
border-radius: 16px; border-radius: 16px;
overflow: hidden; overflow: hidden;
margin-bottom: 16px; margin-bottom: 16px;
background: linear-gradient(135deg, #f1f5f9, #e2e8f0); background: linear-gradient(135deg, #f1f5f9, v.$border-color-1);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -2257,7 +2274,7 @@
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding-bottom: 12px; padding-bottom: 12px;
border-bottom: 2px solid #e2e8f0; border-bottom: 2px solid v.$border-color-1;
} }
.violation-filename { .violation-filename {
@@ -2555,7 +2572,7 @@
.case-card { .case-card {
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
border: 2px solid #e2e8f0; border: 2px solid v.$border-color-1;
border-radius: 16px; border-radius: 16px;
padding: 24px; padding: 24px;
transition: all 0.3s ease; transition: all 0.3s ease;
@@ -2759,7 +2776,7 @@
border-radius: 16px; border-radius: 16px;
overflow: hidden; overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
border: 2px solid #e2e8f0; border: 2px solid v.$border-color-1;
overflow: auto; overflow: auto;
.violations-table { .violations-table {
@@ -2919,7 +2936,7 @@
width: 32px; width: 32px;
height: 32px; height: 32px;
border-radius: 8px; border-radius: 8px;
border: 2px solid #e2e8f0; border: 2px solid v.$border-color-1;
background: white; background: white;
font-size: 1rem; font-size: 1rem;
cursor: pointer; cursor: pointer;
@@ -3587,7 +3604,7 @@
.file-info-card { .file-info-card {
background: #ffffff; background: #ffffff;
border: 1px solid #e2e8f0; border: 1px solid v.$border-color-1;
border-radius: 12px; border-radius: 12px;
padding: 0; padding: 0;
overflow: hidden; overflow: hidden;
@@ -3602,7 +3619,7 @@
background: linear-gradient(0deg, #6366f1 50%, #8b5cf6 130%); background: linear-gradient(0deg, #6366f1 50%, #8b5cf6 130%);
color: white; color: white;
padding: 16px 20px; padding: 16px 20px;
border-bottom: 1px solid #e2e8f0; border-bottom: 1px solid v.$border-color-1;
h4 { h4 {
margin: 0; margin: 0;
@@ -4018,7 +4035,7 @@
margin-top: 10px; margin-top: 10px;
.pagination-button { .pagination-button {
border: 2px solid #e2e8f0; border: 2px solid v.$border-color-1;
padding: 0.25rem 0.75rem; padding: 0.25rem 0.75rem;
border-radius: 10px; border-radius: 10px;
color: v.$p-color; color: v.$p-color;
@@ -4256,7 +4273,7 @@
justify-content: space-between; justify-content: space-between;
margin-bottom: 20px; margin-bottom: 20px;
padding-bottom: 15px; padding-bottom: 15px;
border-bottom: 1px solid #e2e8f0; border-bottom: 1px solid v.$border-color-1;
} }
.info-row { .info-row {
@@ -4292,7 +4309,7 @@
background: #f8fafc; background: #f8fafc;
padding: 12px; padding: 12px;
border-radius: 8px; border-radius: 8px;
border: 1px solid #e2e8f0; border: 1px solid v.$border-color-1;
margin-top: 10px; margin-top: 10px;
word-break: break-all; word-break: break-all;
@@ -4309,7 +4326,7 @@
padding: 15px; padding: 15px;
border-radius: 12px; border-radius: 12px;
margin-top: 15px; margin-top: 15px;
border: 1px solid #e2e8f0; border: 1px solid v.$border-color-1;
text-align: left; text-align: left;
.notes-title { .notes-title {
@@ -4345,7 +4362,7 @@
padding: 15px; padding: 15px;
border-radius: 12px; border-radius: 12px;
text-align: center; text-align: center;
border: 1px solid #e2e8f0; border: 1px solid v.$border-color-1;
} }
.stat-mini-value { .stat-mini-value {
@@ -4391,7 +4408,7 @@
color: #64748b; color: #64748b;
&:hover { &:hover {
background: #e2e8f0; background: v.$border-color-1;
} }
} }
@@ -4409,7 +4426,7 @@
.source-card { .source-card {
/* background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); /* background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-radius: 12px; border-radius: 12px;
border: 1px solid #e2e8f0; */ border: 1px solid v.$border-color-1; */
background: #fff; background: #fff;
border: 1px solid #f3f4f6; border: 1px solid #f3f4f6;
@@ -4596,7 +4613,7 @@
} }
&-title { &-title {
border-bottom: 1px solid #e2e8f0; border-bottom: 1px solid v.$border-color-1;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 20px; margin-bottom: 20px;
@@ -4646,7 +4663,7 @@
} }
&-actions { &-actions {
border-left: 1px solid #e2e8f0; border-left: 1px solid v.$border-color-1;
padding-left: 15px; padding-left: 15px;
} }
@@ -4730,7 +4747,7 @@
width: 100%; width: 100%;
min-height: 80px; min-height: 80px;
padding: 12px; padding: 12px;
border: 1px solid #e2e8f0; border: 1px solid v.$border-color-1;
border-radius: 8px; border-radius: 8px;
font-size: 14px; font-size: 14px;
font-family: inherit; font-family: inherit;
@@ -5009,3 +5026,24 @@
} }
} }
} }
.selected-files-wrapper {
position: fixed;
bottom: 40px;
left: calc(50% + (var(--side-bar-width) / 2));
transform: translateX(-50%);
display: flex;
gap: 10px;
background: v.$white;
border-radius: 20px;
padding: 20px 25px;
box-shadow: 0 4px 20px v.$shadow-1;
border: 1px solid v.$b-color-1;
.btn-primary {
background: #2b7fff;
padding: 10px 16px;
}
}
+2
View File
@@ -15,6 +15,8 @@ $red: #dc2626;
$green: #00a63e; $green: #00a63e;
$p-color-disabled: #6365f18e; $p-color-disabled: #6365f18e;
$s-color-disabled: #8a5cf663; $s-color-disabled: #8a5cf663;
$border-color-1: #e2e8f0;
$border-color-1-hover: #b1b7be;
$color-image: #f08c00; $color-image: #f08c00;
$color-video: #2f9e44; $color-video: #2f9e44;
+6
View File
@@ -201,3 +201,9 @@ export function IconGraph() {
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" className="icon"><path fill="currentColor" d="m16 11.78l4.24-7.33l1.73 1l-5.23 9.05l-6.51-3.75L5.46 19H22v2H2V3h2v14.54L9.5 8z" /></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" className="icon"><path fill="currentColor" d="m16 11.78l4.24-7.33l1.73 1l-5.23 9.05l-6.51-3.75L5.46 19H22v2H2V3h2v14.54L9.5 8z" /></svg>
) )
} }
export function IconCheck() {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" className="icon"><path fill="currentColor" d="m9.55 15.15l8.475-8.475q.3-.3.7-.3t.7.3t.3.713t-.3.712l-9.175 9.2q-.3.3-.7.3t-.7-.3L4.55 13q-.3-.3-.288-.712t.313-.713t.713-.3t.712.3z" /></svg>
)
}