Add manual sync trigger to SearchModal

Wires the existing /sync endpoint to a "Refresh Data" button so
Homebox data can be re-vectorized before searching, without waiting
for it to be triggered automatically on login.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-07-20 16:32:25 +02:00
co-authored by Claude Sonnet 5
parent bb7bef18d9
commit f2ab2b31ed
4 changed files with 108 additions and 60 deletions
+4
View File
@@ -3,6 +3,7 @@ import type {
PagedSearchResponse, PagedSearchResponse,
SearchRequest, SearchRequest,
ServiceItem, ServiceItem,
SyncRequest,
} from "../types/search"; } from "../types/search";
export const search = (req: SearchRequest) => export const search = (req: SearchRequest) =>
@@ -10,3 +11,6 @@ export const search = (req: SearchRequest) =>
method: "POST", method: "POST",
body: JSON.stringify(req), body: JSON.stringify(req),
}); });
export const syncVectorData = (req: SyncRequest) =>
apiFetch<Object>("/sync", { method: "POST", body: JSON.stringify(req) });
+26 -12
View File
@@ -1,7 +1,7 @@
import { useEffect, useRef, useState, type SyntheticEvent } from 'react' import { useEffect, useRef, useState, type SyntheticEvent } from 'react'
import '../ui/Modal.scss' import '../ui/Modal.scss'
import { type PagedSearchResponse, type ServiceItem, type SearchRequest } from '../../types/search' import { type PagedSearchResponse, type ServiceItem, type SearchRequest, type SyncRequest } from '../../types/search'
import { search } from '../../api/searches' import { syncVectorData, search } from '../../api/searches'
import type { ServiceType } from '../../types/connection' import type { ServiceType } from '../../types/connection'
interface Props { interface Props {
@@ -17,9 +17,10 @@ export function SearchModal({ serviceType, label, appUrl, username, onClose }: R
const [results, setResults] = useState<PagedSearchResponse<ServiceItem> | null>(null) const [results, setResults] = useState<PagedSearchResponse<ServiceItem> | null>(null)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null) const [searchError, setSearchError] = useState<string | null>(null)
const [syncError, setSyncError] = useState<string | null>(null)
//TODO: implement aiSearch //TODO: implement aiSearch
const [aiSearch, setAiSearch] = useState(false); const [aiSearch, setAiSearch] = useState(false)
const firstInputRef = useRef<HTMLInputElement>(null) const firstInputRef = useRef<HTMLInputElement>(null)
const dialogRef = useRef<HTMLDialogElement>(null) const dialogRef = useRef<HTMLDialogElement>(null)
@@ -40,16 +41,27 @@ export function SearchModal({ serviceType, label, appUrl, username, onClose }: R
const handleSubmit = async (e: SyntheticEvent<HTMLFormElement>) => { const handleSubmit = async (e: SyntheticEvent<HTMLFormElement>) => {
e.preventDefault() e.preventDefault()
setError(null) setSearchError(null)
setLoading(true) setLoading(true)
try { try {
const req: SearchRequest = { appUrl, serviceType, username, query, aiSearch} const req: SearchRequest = { appUrl, serviceType, username, query, aiSearch}
const res = await search(req) const res = await search(req)
console.log(req)
setResults(res) setResults(res)
console.log('results', results)
} catch (err) { } catch (err) {
setError(err instanceof Error ? err.message : 'Search failed') setSearchError(err instanceof Error ? err.message : 'Search failed')
} 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: 'Refreshing data failed')
} finally { } finally {
setLoading(false) setLoading(false)
} }
@@ -66,10 +78,12 @@ export function SearchModal({ serviceType, label, appUrl, username, onClose }: R
<input id='search' className='modal__input' <input id='search' className='modal__input'
value={query} onChange={e => setQuery(e.target.value)} /> value={query} onChange={e => setQuery(e.target.value)} />
</div> </div>
{error && <p className='modal__error'>{error}</p>} {searchError && <p className='modal__error'>{searchError}</p>}
<button className='modal__submit' type='submit' disabled={loading}> {syncError && <p className='modal__error'>{syncError}</p>}
Search <div className='modal__actions'>
</button> <button className='modal__other' title={`Syncs your ${serviceType} database for vectorization`} type='button' disabled={loading} onClick={handleSync}>Refresh Data</button>
<button className='modal__submit' type='submit' disabled={loading}>Search</button>
</div>
</form> </form>
{results && ( {results && (
<div className='modal__results'> <div className='modal__results'>
+33 -9
View File
@@ -44,8 +44,13 @@
line-height: 1; line-height: 1;
padding: 4px; padding: 4px;
border-radius: 4px; border-radius: 4px;
&:hover { color: var(--text-h); } &:hover {
&:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } color: var(--text-h);
}
&:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
} }
&__form { &__form {
@@ -94,23 +99,42 @@
border: 1px solid rgba(239, 68, 68, 0.2); border: 1px solid rgba(239, 68, 68, 0.2);
} }
&__submit { &__actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 4px; margin-top: 4px;
}
&__submit,
&__other {
padding: 10px 20px; padding: 10px 20px;
font-size: 15px; font-size: 15px;
font-weight: 500; font-weight: 500;
border-radius: 6px; border-radius: 6px;
border: none; border: none;
background: var(--accent);
color: #fff;
cursor: pointer; cursor: pointer;
transition: opacity 0.2s; transition: opacity 0.2s;
align-self: flex-end;
min-width: 100px; min-width: 100px;
color: #fff;
&:hover:not(:disabled) { opacity: 0.85; } &:hover:not(:disabled) {
&:disabled { opacity: 0.6; cursor: not-allowed; } opacity: 0.85;
&:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } }
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
&:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
}
&__submit {
background: var(--accent);
} }
&--expanded { &--expanded {
+6
View File
@@ -24,3 +24,9 @@ export interface PagedSearchResponse<T> {
last: boolean; last: boolean;
sort: string; sort: string;
} }
export interface SyncRequest {
appUrl: string;
username: string;
serviceType: ServiceType;
}