Files
no-copy-frontend/src/app/ui/search/file-search-panel.tsx
T

279 lines
7.8 KiB
TypeScript
Raw Normal View History

2026-01-28 12:12:23 +07:00
import { SearchedUserFilesList } from '@/app/ui/search/searched-user-files-list';
import { SearchedGlobalFilesList } from '@/app/ui/search/searched-global-files-list';
import { useCallback, useState, useImperativeHandle } from 'react';
2026-02-05 21:01:19 +07:00
import { searchUserFiles, searchGlobalFiles } from '@/app/actions/searchActions';
import { useQueryClient } from '@tanstack/react-query';
2026-02-10 15:40:18 +07:00
import { Pagination } from '@/app/components/Pagination';
2026-02-23 13:41:07 +07:00
import { useTranslations } from 'next-intl';
2026-02-23 14:44:22 +07:00
import { AllowedExtensions } from '@/app/ui/search/section-search-file';
2026-02-10 15:40:18 +07:00
export interface SearchItem {
url: string,
pageTitle: string,
height: number,
width: number,
host: string,
pageUrl: string
}
2026-01-28 12:12:23 +07:00
export interface FileInfo {
fileId: string,
fileSize: number,
hammingDistance: number,
originalFileName: string,
ownerId: number,
similarityLevel: string,
status: string,
supportId: number,
uploadDate: string,
url: string
}
export function FileSearchPanel(
{ fileId, ref, allowedExtensions, fileType }
:
{ fileId: string | null, ref: any, allowedExtensions: AllowedExtensions, fileType: string | null }
) {
const [searchedUserFiles, setSearchedUserFiles] = useState<FileInfo[]>([]);
2026-01-28 16:30:17 +07:00
const [searchedUserFilesShowNull, setSearchedUserFilesShowNull] = useState<boolean>(false);
2026-02-10 15:40:18 +07:00
const [searchedGlobalFiles, setSearchedGlobalFiles] = useState<SearchItem[]>([]);
2026-01-28 16:30:17 +07:00
const [searchedGlobalFilesShowNull, setSearchedGlobalFilesShowNull] = useState<boolean>(false);
2026-01-30 14:54:52 +07:00
const [loading, setLoading] = useState<boolean>(false);
2026-02-10 17:29:36 +07:00
const [paginationLoading, setPaginationLoading] = useState<boolean>(false);
2026-02-09 16:44:34 +07:00
const [showGlobalSearch, setShowGlobalSearch] = useState<boolean>(false);
2026-02-05 21:01:19 +07:00
const queryClient = useQueryClient();
2026-02-12 18:02:25 +07:00
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [globalSearchErrorMessage, setGlobalSearchErrorMessage] = useState<string | null>(null);
2026-02-23 13:41:07 +07:00
const t = useTranslations('Global');
2026-01-28 12:12:23 +07:00
2026-02-10 15:40:18 +07:00
const [searchTotalPages, setSearchTotalPages] = useState<number>(0);
const [searchCurrentPages, setSearchCurrentPages] = useState<number>(1);
2026-01-28 12:12:23 +07:00
useImperativeHandle(ref, () => ({
2026-01-28 16:30:17 +07:00
clearList: () => {
2026-01-28 12:12:23 +07:00
setSearchedUserFiles([]);
2026-01-28 16:30:17 +07:00
setSearchedGlobalFiles([]);
setSearchedUserFilesShowNull(false);
setSearchedGlobalFilesShowNull(false);
2026-02-09 16:44:34 +07:00
setShowGlobalSearch(false);
2026-02-10 17:29:36 +07:00
setSearchCurrentPages(1);
setSearchTotalPages(0);
2026-01-28 12:12:23 +07:00
}
}));
2026-02-10 15:40:18 +07:00
const handlerSearchUserFile = useCallback(async (fileId: string, page: number): Promise<void> => {
2026-02-12 18:02:25 +07:00
setErrorMessage(null);
setGlobalSearchErrorMessage(null);
2026-01-28 12:12:23 +07:00
try {
let result = await searchUserFiles(fileId);
2026-03-03 13:25:26 +07:00
if (result.errorMessage) {
throw result.errorMessage
}
if (result?.content?.length) {
2026-01-28 12:12:23 +07:00
setSearchedUserFiles(result.content);
2026-03-20 21:37:51 +07:00
const hasOwned = result?.content.some((item: { owner: boolean }) => {
if (item.owner) {
return true;
}
return false;
});
if (hasOwned) {
if (fileType === 'image') {
setShowGlobalSearch(true);
handlerSearchGlobalFile(fileId, page);
}
}
} else {
setSearchedUserFilesShowNull(true);
2026-03-02 14:23:36 +07:00
if (fileType === 'image') {
setShowGlobalSearch(true);
handlerSearchGlobalFile(fileId, page);
}
2026-01-28 12:12:23 +07:00
}
} catch (error) {
2026-03-03 13:25:26 +07:00
if (error === 'file-for-search-unsupported') {
setErrorMessage(error);
} else {
setErrorMessage('search-error');
}
2026-01-28 12:12:23 +07:00
}
}, [fileId])
2026-02-10 17:29:36 +07:00
const handlerSearchGlobalFile = useCallback(async (fileId: string, page: number, actionFromPagination?: boolean): Promise<void> => {
setGlobalSearchErrorMessage(null);
2026-02-10 17:29:36 +07:00
if (!actionFromPagination) {
setLoading(true);
setSearchedGlobalFilesShowNull(false);
setSearchedGlobalFiles([]);
} else {
setPaginationLoading(true);
}
2026-01-28 12:12:23 +07:00
try {
2026-02-10 15:40:18 +07:00
let result = await searchGlobalFiles(fileId, page);
if (result.error) {
throw result.error;
}
2026-03-26 11:56:36 +07:00
if (result.totalResults) {
2026-01-28 12:12:23 +07:00
setSearchedGlobalFiles(result.images);
2026-02-10 15:40:18 +07:00
setSearchTotalPages(result.totalPages);
setSearchCurrentPages(page);
2026-01-28 16:30:17 +07:00
} else {
setSearchedGlobalFilesShowNull(true);
2026-01-28 12:12:23 +07:00
}
} catch (error) {
2026-03-26 11:56:36 +07:00
if (error) {
setGlobalSearchErrorMessage(error as string);
} else {
setGlobalSearchErrorMessage('error');
}
2026-01-30 14:54:52 +07:00
} finally {
2026-02-05 21:01:19 +07:00
await queryClient.invalidateQueries({ queryKey: ['userData'] });
await queryClient.invalidateQueries({ queryKey: ['userSearchData'] });
2026-01-30 14:54:52 +07:00
setLoading(false);
2026-02-10 17:29:36 +07:00
setPaginationLoading(false);
2026-01-28 12:12:23 +07:00
}
}, [fileId])
return (
<>
2026-02-23 13:41:07 +07:00
<div className="mb-4">
2026-01-28 12:12:23 +07:00
<button
className="btn btn-primary btn-search"
2026-02-23 13:41:07 +07:00
disabled={!fileId}
2026-01-28 12:12:23 +07:00
onClick={() => {
if (fileId) {
2026-02-10 15:40:18 +07:00
handlerSearchUserFile(fileId, 1)
2026-01-28 12:12:23 +07:00
}
}}
>
2026-02-23 13:41:07 +07:00
{t('start-search')}
2026-01-28 12:12:23 +07:00
</button>
2026-02-12 18:02:25 +07:00
{errorMessage && (
<div className='mt-2 text-red-600 text-sm'>
2026-03-03 13:25:26 +07:00
{t(errorMessage)}
2026-02-12 18:02:25 +07:00
</div>
)}
2026-01-28 12:12:23 +07:00
</div>
{(searchedUserFiles.length !== 0) && (
2026-02-23 14:44:22 +07:00
<SearchedUserFilesList list={searchedUserFiles} allowedExtensions={allowedExtensions} />
2026-01-28 16:30:17 +07:00
)}
2026-01-28 12:12:23 +07:00
2026-01-28 16:30:17 +07:00
{searchedUserFilesShowNull && (
2026-02-23 13:41:07 +07:00
<div className="user-file-search-results">
2026-01-28 16:30:17 +07:00
<div className="results-header">
2026-02-23 13:41:07 +07:00
<div className="results-title">{t('search-results-title')}</div>
2026-01-28 16:30:17 +07:00
<div className="results-count">
2026-02-23 13:41:07 +07:00
{t('files-found', { count: searchedUserFiles.length })}
2026-01-28 16:30:17 +07:00
</div>
</div>
2026-02-23 13:41:07 +07:00
<div className="empty-results">
<h3>{t('no-similar-files-title')}</h3>
<p>{t('no-similar-files-description')}</p>
2026-01-28 16:30:17 +07:00
<div className="empty-results-hint">
2026-02-23 13:41:07 +07:00
<strong>{t('hint-title')}</strong> {t('hint-text')}
2026-01-28 16:30:17 +07:00
</div>
</div>
</div>
2026-01-28 12:12:23 +07:00
)}
2026-02-09 16:44:34 +07:00
{showGlobalSearch && (
2026-02-23 13:41:07 +07:00
<div className="global-search-section">
2026-02-09 16:44:34 +07:00
<div className="global-search-header">
<div className="global-search-title">
2026-02-23 13:41:07 +07:00
{t('global-search-title')}
2026-02-09 16:44:34 +07:00
</div>
<div className="">
2026-02-23 13:41:07 +07:00
{t('global-search-description')}
2026-02-09 16:44:34 +07:00
</div>
<div className="global-search-badge">
2026-02-23 13:41:07 +07:00
{t('global-search-badge')}
2026-02-09 16:44:34 +07:00
</div>
2026-01-28 12:12:23 +07:00
</div>
2026-02-09 16:44:34 +07:00
<div className="global-search-content">
<div className="global-search-info">
<div className="global-search-info-title">
2026-02-23 13:41:07 +07:00
{t('global-search-info-title')}
2026-02-09 16:44:34 +07:00
</div>
<div className="global-search-info-text">
2026-02-23 13:41:07 +07:00
{t('global-search-info-text')}
2026-02-09 16:44:34 +07:00
</div>
</div>
<div className={`global-loading ${loading ? 'show' : ''}`} id="global-loading">
<div className="global-spinner"></div>
<div className="global-loading-text" id="global-loading-text">
2026-02-23 13:41:07 +07:00
{t('searching-internet')}
2026-02-09 16:44:34 +07:00
</div>
</div>
<div className={`global-result ${searchedGlobalFiles.length ? 'show' : ''}`} id="global-result">
<div className="global-result-card">
<div className="global-result-header">
2026-02-23 13:41:07 +07:00
<span>{t('found-on-internet')}</span>
2026-02-10 17:29:36 +07:00
{paginationLoading && (
<div className="loading-animation">
<span className="global-spinner"></span>
</div>
)}
2026-02-09 16:44:34 +07:00
</div>
<div className="global-result-content">
<SearchedGlobalFilesList list={searchedGlobalFiles} />
</div>
</div>
</div>
2026-02-23 13:41:07 +07:00
{(searchedGlobalFilesShowNull && !loading) && (
2026-02-09 16:44:34 +07:00
<div className="global-no-result" id="global-no-result">
2026-02-23 13:41:07 +07:00
<h4>{t('no-similar-global-title')}</h4>
<p className="global-no-result-text">
{t('no-similar-global-description')}
</p>
</div>
)}
{globalSearchErrorMessage && (
<>
<div className="global-error">
<strong>{t('search-error')}</strong>
</div>
<button
2026-02-23 13:41:07 +07:00
onClick={() => {
if (fileId) {
handlerSearchGlobalFile(fileId, 1);
}
}}
className="btn btn-primary btn-search"
>
{t('search-again')}
</button>
</>
2026-02-09 16:44:34 +07:00
)}
2026-02-23 13:41:07 +07:00
<Pagination
totalPages={searchTotalPages}
currentPage={searchCurrentPages}
fileId={fileId}
callBack={handlerSearchGlobalFile}
/>
2026-01-28 12:12:23 +07:00
</div>
</div>
2026-02-09 16:44:34 +07:00
)}
2026-01-28 12:12:23 +07:00
</>
2026-02-23 13:41:07 +07:00
);
2026-01-28 12:12:23 +07:00
}