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-26 19:32:57 +07:00
|
|
|
import { getFileType } from '@/app/lib/getFileType';
|
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
|
|
|
|
2026-02-26 12:53:27 +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
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-26 19:32:57 +07:00
|
|
|
export function FileSearchPanel(
|
|
|
|
|
{ fileId, ref, allowedExtensions, fileType }
|
|
|
|
|
:
|
|
|
|
|
{ fileId: string | null, ref: any, allowedExtensions: AllowedExtensions, fileType: string | null }
|
|
|
|
|
) {
|
2026-02-26 12:53:27 +07:00
|
|
|
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);
|
2026-03-03 11:57:18 +07:00
|
|
|
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);
|
2026-03-03 11:57:18 +07:00
|
|
|
setGlobalSearchErrorMessage(null);
|
2026-01-28 12:12:23 +07:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
let result = await searchUserFiles(fileId);
|
|
|
|
|
|
2026-03-02 14:23:36 +07:00
|
|
|
if (result.content.length) {
|
2026-01-28 12:12:23 +07:00
|
|
|
setSearchedUserFiles(result.content);
|
2026-03-02 14:23:36 +07:00
|
|
|
if (fileType === 'image') {
|
|
|
|
|
setShowGlobalSearch(true);
|
|
|
|
|
handlerSearchGlobalFile(fileId, page);
|
|
|
|
|
}
|
2026-01-28 16:30:17 +07:00
|
|
|
} else {
|
|
|
|
|
setSearchedUserFilesShowNull(true);
|
2026-01-28 12:12:23 +07:00
|
|
|
}
|
|
|
|
|
} catch (error) {
|
2026-02-12 18:02:25 +07:00
|
|
|
setErrorMessage('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> => {
|
2026-03-03 11:57:18 +07:00
|
|
|
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);
|
2026-03-03 11:57:18 +07:00
|
|
|
|
|
|
|
|
if (result.error) {
|
|
|
|
|
throw result.error;
|
|
|
|
|
}
|
2026-01-28 12:12:23 +07:00
|
|
|
if (result.images.length) {
|
|
|
|
|
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-03 11:57:18 +07:00
|
|
|
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-02-23 13:41:07 +07:00
|
|
|
{t('search-error')}
|
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>
|
|
|
|
|
|
2026-03-02 14:23:36 +07:00
|
|
|
{/* <div className="search-counter" id="search-counter">
|
2026-02-09 16:44:34 +07:00
|
|
|
<div className="counter-info">
|
|
|
|
|
<div className="counter-icon" id="counter-icon"></div>
|
|
|
|
|
<div className="counter-text">
|
2026-02-23 13:41:07 +07:00
|
|
|
<div className="counter-label">{t('global-searches-today')}</div>
|
|
|
|
|
<div className="counter-value" id="counter-value">0 {t('out-of')} 10</div>
|
2026-02-09 16:44:34 +07:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="progress-bar">
|
|
|
|
|
<div
|
|
|
|
|
className="progress-fill"
|
|
|
|
|
id="progress-fill"
|
|
|
|
|
style={{ width: '0%' }}
|
|
|
|
|
>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-02-27 12:25:08 +07:00
|
|
|
</div> */}
|
2026-02-09 16:44:34 +07:00
|
|
|
|
|
|
|
|
<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>
|
2026-03-03 11:57:18 +07:00
|
|
|
</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')}
|
2026-03-03 11:57:18 +07:00
|
|
|
</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
|
|
|
}
|