Compare commits
3
Commits
84b73b912f
...
d419c1c2cb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d419c1c2cb | ||
|
|
48e03b4237 | ||
|
|
2fad519b7e |
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "no-copy-frontend",
|
||||
"version": "0.105.0",
|
||||
"version": "0.107.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 2999",
|
||||
|
||||
@@ -4,65 +4,124 @@ import Link from 'next/link';
|
||||
import PdfViewer from '@/app/components/PdfViewer';
|
||||
import { DownloadButton } from '@/app/components/document-viewer/DownloadButton';
|
||||
import { CopyLinkButton } from '@/app/components/document-viewer/CopyLinkButton';
|
||||
import { fetchDocumentInfo } from '@/app/actions/trackingActions';
|
||||
import { LoginUserBage } from '@/app/components/document-viewer/LoginUserBage';
|
||||
|
||||
interface PageProps {
|
||||
searchParams: Promise<{ docid: string; title?: string }>;
|
||||
}
|
||||
|
||||
interface DocumentInfo {
|
||||
checksCount?: number;
|
||||
checksum?: string;
|
||||
createdAt?: string;
|
||||
downloadUrl?: string;
|
||||
existsOnDisk?: boolean;
|
||||
fileExtension?: string;
|
||||
fileFormat?: string | null;
|
||||
fileName?: string;
|
||||
filePath?: string;
|
||||
fileSize?: number;
|
||||
fileUploadDate?: string;
|
||||
formattedSize?: string;
|
||||
id: string;
|
||||
mimeType?: string;
|
||||
monitoring?: string;
|
||||
originalFileName?: string;
|
||||
ownerCompany?: string | null;
|
||||
ownerEmail?: string;
|
||||
ownerName?: string;
|
||||
permissions?: {
|
||||
DOWNLOAD?: boolean;
|
||||
};
|
||||
protectStatus?: string;
|
||||
protectedFilePath?: string;
|
||||
status?: string;
|
||||
storedFileName?: string;
|
||||
supportId?: number;
|
||||
thumbnailFileUrl?: string;
|
||||
updatedAt?: string;
|
||||
uploadSessionId?: string;
|
||||
userId?: number;
|
||||
}
|
||||
|
||||
export default async function Page({ searchParams }: PageProps) {
|
||||
const { docid, title } = await searchParams;
|
||||
|
||||
let response: DocumentInfo | null = null;
|
||||
let error: string | null = null;
|
||||
|
||||
try {
|
||||
response = await fetchDocumentInfo(docid);
|
||||
} catch (err) {
|
||||
error = 'Не удалось загрузить информацию о документе';
|
||||
}
|
||||
|
||||
const documentTitle = title || 'FileName';
|
||||
const ownerName = "Name Name Name";
|
||||
const companyName = "companyName";
|
||||
|
||||
if (error || !response) {
|
||||
return (
|
||||
<div className="watch-doc-wrapper">
|
||||
<div className="watch-doc-page">
|
||||
<header className="watch-doc-header">
|
||||
<Link href="#" className="header-logo">NoCopy</Link>
|
||||
<LoginUserBage />
|
||||
</header>
|
||||
<div className="error-container">
|
||||
<h2>Ошибка загрузки документа</h2>
|
||||
<p>{error || 'Документ не найден'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="watch-doc-wrapper">
|
||||
<div className="watch-doc-page">
|
||||
<header className="watch-doc-header">
|
||||
<Link href="#" className="header-logo">NoCopy</Link>
|
||||
<Link href="#" className="header-user">Name</Link>
|
||||
<LoginUserBage />
|
||||
</header>
|
||||
|
||||
<div className="watch-doc-status">
|
||||
<div className="st-title">Защита подтверждена</div>
|
||||
<div className="st-sub">
|
||||
Документ <strong>{documentTitle}</strong> зарегистрирован и защищён платформой NoCopy.
|
||||
Документ <strong>{response?.fileName ? response?.fileName : '#'}</strong> зарегистрирован и защищён платформой NoCopy.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="watch-doc-grid">
|
||||
<div>
|
||||
<div
|
||||
className="flex flex-col justify-between"
|
||||
>
|
||||
<div className="watch-doc-card">
|
||||
<div className="card-head">Правообладатель</div>
|
||||
<div className="card-body">
|
||||
<div className="owner-row">
|
||||
<div className="owner-av">Н</div>
|
||||
<div className="owner-av">
|
||||
{response?.fileName ? response?.fileName[0] : ''}
|
||||
</div>
|
||||
<div>
|
||||
<div className="owner-name">{ownerName}</div>
|
||||
{/* <div className="owner-meta">{companyName}</div> */}
|
||||
<div className="owner-meta">Зарегистрирован в NoCopy</div>
|
||||
{response?.ownerName && (
|
||||
<div className="owner-name">{response?.ownerName}</div>
|
||||
)}
|
||||
{response?.ownerCompany && (
|
||||
<div className="owner-name">{response?.ownerCompany}</div>
|
||||
)}
|
||||
{response?.ownerName && (
|
||||
<div className="owner-meta">Зарегистрирован в NoCopy</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div className="watch-doc-card">
|
||||
<div className="card-head">Скачать документ</div>
|
||||
<div className="card-body">
|
||||
<DownloadButton />
|
||||
<div className="download-description">
|
||||
Вы вошли как <strong>{ownerName}</strong>.
|
||||
<br />
|
||||
Скачивание будет зарегистрировано.
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className="watch-doc-actions">
|
||||
<DownloadButton />
|
||||
{response?.permissions?.DOWNLOAD && (
|
||||
<DownloadButton />
|
||||
)}
|
||||
<CopyLinkButton />
|
||||
<Link href="#" className="btn btn-out" target="_blank">О NoCopy</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -81,8 +140,8 @@ export default async function Page({ searchParams }: PageProps) {
|
||||
|
||||
<div className="watch-doc-footer">
|
||||
<Link href="#">NoCopy</Link>
|
||||
<Link href="#">Условия</Link>
|
||||
<Link href="#">Конфиденциальность</Link>
|
||||
<Link href="/terms-of-use">Условия</Link>
|
||||
<Link href="/privacy-policy">Конфиденциальность</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,86 +5,163 @@ import { API_BASE_URL } from '@/app/actions/definitions';
|
||||
import { headers } from 'next/headers';
|
||||
|
||||
export async function requestFileWithTracking(fileId: string) {
|
||||
const token = await getSessionData('token');
|
||||
const headersList = await headers();
|
||||
const forwardedFor = headersList.get('x-forwarded-for');
|
||||
const realIp = headersList.get('x-real-ip');
|
||||
const ip = forwardedFor?.split(',')[0] || realIp || 'unknown';
|
||||
const userAgent = headersList.get('user-agent') || 'unknown';
|
||||
const token = await getSessionData('token');
|
||||
const headersList = await headers();
|
||||
const forwardedFor = headersList.get('x-forwarded-for');
|
||||
const realIp = headersList.get('x-real-ip');
|
||||
const ip = forwardedFor?.split(',')[0] || realIp || 'unknown';
|
||||
const userAgent = headersList.get('user-agent') || 'unknown';
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/file/link`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
file_id: fileId,
|
||||
token: token,
|
||||
ip: ip,
|
||||
user_agent: userAgent
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/file/link`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
file_id: fileId,
|
||||
token: token,
|
||||
ip: ip,
|
||||
user_agent: userAgent
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (response.ok) {
|
||||
const contentType = response.headers.get('content-type');
|
||||
|
||||
if (contentType?.includes('application/json')) {
|
||||
const data = await response.json();
|
||||
return data.message_body;
|
||||
} else {
|
||||
const blob = await response.blob();
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
if (contentType?.includes('application/json')) {
|
||||
const data = await response.json();
|
||||
return data.message_body;
|
||||
} else {
|
||||
const blob = await response.blob();
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function requestFileAsBuffer(fileId: string): Promise<ArrayBuffer | null> {
|
||||
const token = await getSessionData('token');
|
||||
const headersList = await headers();
|
||||
const forwardedFor = headersList.get('x-forwarded-for');
|
||||
const realIp = headersList.get('x-real-ip');
|
||||
const ip = forwardedFor?.split(',')[0] || realIp || 'unknown';
|
||||
const userAgent = headersList.get('user-agent') || 'unknown';
|
||||
const token = await getSessionData('token');
|
||||
const headersList = await headers();
|
||||
const forwardedFor = headersList.get('x-forwarded-for');
|
||||
const realIp = headersList.get('x-real-ip');
|
||||
const ip = forwardedFor?.split(',')[0] || realIp || 'unknown';
|
||||
const userAgent = headersList.get('user-agent') || 'unknown';
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/file/link`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
file_id: fileId,
|
||||
token: token,
|
||||
ip: ip,
|
||||
user_agent: userAgent
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/file/link`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
file_id: fileId,
|
||||
token: token,
|
||||
ip: ip,
|
||||
user_agent: userAgent
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`${response.status}`);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(`${response.status}`);
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (contentType?.includes('application/json')) {
|
||||
const data = await response.json();
|
||||
const fileUrl = data.message_body;
|
||||
const fileResponse = await fetch(fileUrl);
|
||||
if (!fileResponse.ok) {
|
||||
throw new Error(`Failed to fetch file: ${fileResponse.status}`);
|
||||
}
|
||||
|
||||
return await fileResponse.arrayBuffer();
|
||||
}
|
||||
else {
|
||||
return await response.arrayBuffer();
|
||||
}
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (contentType?.includes('application/json')) {
|
||||
const data = await response.json();
|
||||
const fileUrl = data.message_body;
|
||||
const fileResponse = await fetch(fileUrl);
|
||||
if (!fileResponse.ok) {
|
||||
throw new Error(`Failed to fetch file: ${fileResponse.status}`);
|
||||
}
|
||||
|
||||
return await fileResponse.arrayBuffer();
|
||||
}
|
||||
else {
|
||||
return await response.arrayBuffer();
|
||||
}
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchDocumentInfo(fileId: string) {
|
||||
const token = await getSessionData('token');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
version: 1,
|
||||
msg_id: 20005,
|
||||
message_body: {
|
||||
file_id: fileId,
|
||||
token: token,
|
||||
action: 'file_info'
|
||||
}
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const parsed = await response.json();
|
||||
|
||||
if (parsed.message_code === 0) {
|
||||
return parsed.message_body;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new Error(`${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function filePermisionChange(fileId: string, permissions: boolean) {
|
||||
const token = await getSessionData('token');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
version: 1,
|
||||
msg_id: 20005,
|
||||
message_body: {
|
||||
file_id: fileId,
|
||||
token: token,
|
||||
action: 'change_permission',
|
||||
permissions: {
|
||||
DOWNLOAD: permissions
|
||||
}
|
||||
}
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const parsed = await response.json();
|
||||
|
||||
if (parsed.message_code === 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new Error(`${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useUserProfile } from '@/app/hooks/react-query/useUserDataInfo';
|
||||
|
||||
export function LoginUserBage() {
|
||||
const { data: userData, isLoading, isError, error } = useUserProfile();
|
||||
|
||||
if (!userData?.fullName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Link href="#" className="header-user">
|
||||
{userData?.fullName}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import { useDebouncedCallback } from 'use-debounce';
|
||||
import Link from 'next/link';
|
||||
import { SelectedFilesAction } from '@/app/components/tanstak-table/SelectedFilesActions';
|
||||
import Image from 'next/image';
|
||||
import { filePermisionChange } from '@/app/actions/trackingActions';
|
||||
|
||||
export type FileItem = {
|
||||
id: string;
|
||||
@@ -45,7 +46,10 @@ export type FileItem = {
|
||||
protectStatus: string;
|
||||
_original?: ApiFile;
|
||||
supportId: number;
|
||||
thumbnailFileUrl: string
|
||||
thumbnailFileUrl: string;
|
||||
permissions: {
|
||||
DOWNLOAD: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
type ApiFile = {
|
||||
@@ -61,6 +65,9 @@ type ApiFile = {
|
||||
supportId: number;
|
||||
fileName: string;
|
||||
thumbnailFileUrl: string;
|
||||
permissions: {
|
||||
DOWNLOAD: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
type ApiResponse = {
|
||||
@@ -190,7 +197,10 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType
|
||||
protectStatus: item.protectStatus,
|
||||
supportId: item.supportId,
|
||||
_original: item,
|
||||
thumbnailFileUrl: item.thumbnailFileUrl
|
||||
thumbnailFileUrl: item.thumbnailFileUrl,
|
||||
permissions: {
|
||||
DOWNLOAD: item.permissions.DOWNLOAD,
|
||||
}
|
||||
}));
|
||||
|
||||
return {
|
||||
@@ -553,6 +563,33 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'downloadPermision',
|
||||
header: ({ column }) => (
|
||||
<div className="column">
|
||||
<span>
|
||||
Можно скачать по ссылке
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<div className="text-center font-semibold table-item">
|
||||
<button
|
||||
onClick={() => changeDownloadPermition(row.original.id, row.original.permissions.DOWNLOAD)}
|
||||
disabled={isFileLoading}
|
||||
className={`toggle-switch ${row.original.permissions?.DOWNLOAD ? 'active' : 'inactive'}`}
|
||||
title={'change-permission'}
|
||||
>
|
||||
<span className="toggle-slider"></span>
|
||||
<span className="toggle-label">
|
||||
{row.original.permissions?.DOWNLOAD ? 'Да' : 'Нет'}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: () => {
|
||||
@@ -564,15 +601,8 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType
|
||||
},
|
||||
cell: ({ row }) => (
|
||||
<div className="actions">
|
||||
<div className="actions-group">
|
||||
<Link
|
||||
href={`/pages/file/${row.original.id}`}
|
||||
className="bg-violet-500 hover:bg-violet-600"
|
||||
title={t('view')}
|
||||
>
|
||||
<IconEye />
|
||||
</Link>
|
||||
{(showFileLink && row.original.fileType === 'document') && (
|
||||
{(showFileLink && row.original.fileType === 'document') ? (
|
||||
<div className="actions-group">
|
||||
<Link
|
||||
href={`/watch-doc?docid=${row.original.id}`}
|
||||
className="bg-indigo-500 hover:bg-indigo-600"
|
||||
@@ -581,30 +611,41 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType
|
||||
>
|
||||
<IconLink />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<div className="actions-group">
|
||||
{row.original.protectStatus === 'PROTECTED' && (
|
||||
<button
|
||||
onClick={() => handleDownload(row.original)}
|
||||
disabled={isFileLoading}
|
||||
className="table-action-download"
|
||||
title={t('download')}
|
||||
>
|
||||
<IconFileDownload />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => {
|
||||
handleView(row.original)
|
||||
}}
|
||||
className="table-action-view"
|
||||
title={t('make-check')}
|
||||
>
|
||||
<IconInfo />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<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 className="actions-group">
|
||||
{row.original.protectStatus === 'PROTECTED' && (
|
||||
<button
|
||||
onClick={() => handleDownload(row.original)}
|
||||
disabled={isFileLoading}
|
||||
className="table-action-download"
|
||||
title={t('download')}
|
||||
>
|
||||
<IconFileDownload />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => {
|
||||
handleView(row.original)
|
||||
}}
|
||||
className="table-action-view"
|
||||
title={t('make-check')}
|
||||
>
|
||||
<IconInfo />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
enableSorting: false,
|
||||
@@ -659,6 +700,16 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType
|
||||
}
|
||||
}
|
||||
|
||||
const changeDownloadPermition = async (fileId: string, permission: boolean) => {
|
||||
const response = await filePermisionChange(fileId, !permission);
|
||||
if (response) {
|
||||
queryClient.invalidateQueries({ queryKey: ['userFilesData'] });
|
||||
toast.success('Разрешение на скачивание изменено.');
|
||||
} else {
|
||||
toast.warning('Не удалось изменить разрешение на скачивание.');
|
||||
}
|
||||
}
|
||||
|
||||
// Фильтрация по типу файла и дате
|
||||
const filteredData = useMemo(() => {
|
||||
let result = tableData;
|
||||
@@ -735,6 +786,10 @@ export default function TanstakFilesTable({ fileType, showFileLink }: { fileType
|
||||
state: {
|
||||
sorting,
|
||||
pagination,
|
||||
columnVisibility: {
|
||||
monitoring: showFileLink ? false : true,
|
||||
downloadPermision: showFileLink ? true : false,
|
||||
}
|
||||
},
|
||||
pageCount: totalPages,
|
||||
manualPagination: true,
|
||||
|
||||
@@ -1027,6 +1027,67 @@
|
||||
background-color: #155dfc;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 60px;
|
||||
height: 28px;
|
||||
padding: 0 6px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
background-color: #615fff;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.toggle-switch.active {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.toggle-switch.inactive {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.toggle-switch .toggle-slider {
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 28px;
|
||||
background-color: white;
|
||||
border: 1px solid #615fff;
|
||||
border-radius: 8px;
|
||||
transition: transform 0.3s ease;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.toggle-switch.active .toggle-slider {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.toggle-switch.inactive .toggle-slider {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.toggle-switch .toggle-label {
|
||||
z-index: 1;
|
||||
color: v.$text-p;
|
||||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
||||
width: 30px;
|
||||
height: 26px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.toggle-switch:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.table-item-checkbox {
|
||||
|
||||
@@ -164,6 +164,10 @@
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.error-container {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
&-page {
|
||||
@@ -247,9 +251,9 @@
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 14px;
|
||||
margin-bottom: 14px;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow);
|
||||
height: 100%;
|
||||
|
||||
.card-head {
|
||||
padding: 14px 20px;
|
||||
|
||||
Reference in New Issue
Block a user