102 lines
3.7 KiB
TypeScript
102 lines
3.7 KiB
TypeScript
import { useEffect, useRef, useState, type SyntheticEvent } from 'react'
|
|
import '../ui/Modal.scss'
|
|
import { type PagedSearchResponse, type ServiceItem, type SearchRequest, type SyncRequest } from '../../types/search'
|
|
import { syncVectorData, search } from '../../api/searches'
|
|
import type { ServiceType } from '../../types/connection'
|
|
import { uiText } from '../../text/uiText'
|
|
|
|
interface Props {
|
|
serviceType: ServiceType
|
|
label: string
|
|
appUrl: string
|
|
username: string
|
|
onClose: () => void
|
|
}
|
|
|
|
export function SearchModal({ serviceType, label, appUrl, username, onClose }: Readonly<Props>) {
|
|
const [query, setQuery] = useState('')
|
|
const [results, setResults] = useState<PagedSearchResponse<ServiceItem> | null>(null)
|
|
|
|
const [loading, setLoading] = useState(false)
|
|
const [searchError, setSearchError] = useState<string | null>(null)
|
|
const [syncError, setSyncError] = useState<string | null>(null)
|
|
//TODO: implement aiSearch
|
|
const [aiSearch, setAiSearch] = useState(false)
|
|
const firstInputRef = useRef<HTMLInputElement>(null)
|
|
const dialogRef = useRef<HTMLDialogElement>(null)
|
|
|
|
useEffect(() => {
|
|
const dialog = dialogRef.current
|
|
if (!dialog) return
|
|
|
|
dialog.showModal()
|
|
firstInputRef.current?.focus()
|
|
|
|
const handleCancel = (e: Event) => {
|
|
e.preventDefault()
|
|
onClose()
|
|
}
|
|
dialog.addEventListener('cancel', handleCancel)
|
|
return () => dialog.removeEventListener('cancel', handleCancel)
|
|
}, [onClose])
|
|
|
|
const handleSubmit = async (e: SyntheticEvent<HTMLFormElement>) => {
|
|
e.preventDefault()
|
|
setSearchError(null)
|
|
setLoading(true)
|
|
try {
|
|
const req: SearchRequest = { appUrl, serviceType, username, query, aiSearch}
|
|
const res = await search(req)
|
|
setResults(res)
|
|
} catch (err) {
|
|
setSearchError(err instanceof Error ? err.message : uiText.search.searchFailed)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const handleSync = async () => {
|
|
setSyncError(null)
|
|
setLoading(true)
|
|
try {
|
|
const req: SyncRequest = { appUrl, serviceType, username}
|
|
await syncVectorData(req)
|
|
} catch (err) {
|
|
setSyncError(err instanceof Error ? err.message: uiText.search.syncFailed)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<dialog className='modal' ref={dialogRef}>
|
|
<div className='modal__header'>
|
|
<h2 className='modal__title' id='modal-title'>{uiText.search.title(label)}</h2>
|
|
<button className='modal__close' onClick={onClose} aria-label={uiText.modal.closeAriaLabel}>{uiText.modal.closeSymbol}</button>
|
|
</div>
|
|
<form className='modal__form' onSubmit={handleSubmit}>
|
|
<div className='modal__field'>
|
|
<input id='search' className='modal__input'
|
|
value={query} onChange={e => setQuery(e.target.value)} />
|
|
</div>
|
|
{searchError && <p className='modal__error'>{searchError}</p>}
|
|
{syncError && <p className='modal__error'>{syncError}</p>}
|
|
<div className='modal__actions'>
|
|
<button className='modal__other' title={uiText.search.syncTooltip(label)} type='button' disabled={loading} onClick={handleSync}>{uiText.search.refreshDataButton}</button>
|
|
<button className='modal__submit' type='submit' disabled={loading}>{uiText.search.searchButton}</button>
|
|
</div>
|
|
</form>
|
|
{results && (
|
|
<div className='modal__results'>
|
|
<p className='modal__results-count'>{results.totalElements}</p>
|
|
{results.content.map((item) => (<div key={item.id} className='modal__result-item'>
|
|
<p className='"modal__result-title'>{item.title}</p>
|
|
{item.description && <p className='modal__result-desc'>{item.description}</p>}
|
|
|
|
</div>))}
|
|
</div>
|
|
)}
|
|
</dialog>
|
|
)
|
|
}
|