47 lines
3.8 KiB
TypeScript
47 lines
3.8 KiB
TypeScript
import { router, usePage } from '@inertiajs/react';
|
|
import { useEffect, useRef, useState } from 'react';
|
|
// Shared feedback for existing Inertia search consumers; form state is never reset.
|
|
const collections = ['results','records','resources','partnerships','opportunities','interests','investors','leads','articles','services','items','entries','documents','requests','batches','users','assignments','zones','matches','businesses'];
|
|
function count(props: Record<string, unknown>): number | null {
|
|
if (props.demands && props.capacities) return Number((props.demands as {total:number}).total) + Number((props.capacities as {total:number}).total);
|
|
if (props.plan && Array.isArray((props.plan as {rows:unknown[]}).rows)) return (props.plan as {rows:unknown[]}).rows.length;
|
|
if (typeof props.total === 'number') return props.total;
|
|
if (typeof props.match === 'boolean' && Array.isArray(props.articles)) return props.match ? props.articles.length : 0;
|
|
for (const key of collections) {
|
|
const value = props[key] as {total?:number; meta?:{total?:number}; data?:unknown[]} | unknown[] | undefined;
|
|
if (!value) continue;
|
|
if (Array.isArray(value)) return value.length;
|
|
if (typeof value.total === 'number') return value.total;
|
|
if (typeof value.meta?.total === 'number') return value.meta.total;
|
|
if (Array.isArray(value.data)) return value.data.length;
|
|
}
|
|
return null;
|
|
}
|
|
export default function SearchFeedback() {
|
|
const page = usePage();
|
|
const [state, setState] = useState<'idle'|'loading'|'error'>('idle');
|
|
const latest = useRef<{url: string; data: Record<string, unknown>} | null>(null);
|
|
const active = useRef(false);
|
|
const searchable = page.props.filters !== undefined || ['portal/legacy','desk/catalogue','investment/zones','partnerships/catalogue','education/index','management/territory-geometry-import'].includes(page.component);
|
|
const total = searchable ? count(page.props) : null;
|
|
useEffect(() => {
|
|
const off = [router.on('before', event => {
|
|
const visit = event.detail.visit;
|
|
if (visit.method !== 'get') { active.current = false; return; }
|
|
const data = visit.data as Record<string, unknown>;
|
|
const url = visit.url;
|
|
const isSearch = total !== null || ['q','query','search','filter','status','sector'].some(key => key in data || url.searchParams.has(key));
|
|
active.current = isSearch;
|
|
if (isSearch) { latest.current = {url: url.toString(), data}; setState('loading'); }
|
|
}), router.on('success', () => { if (active.current) { active.current = false; setState('idle'); } }),
|
|
router.on('error', () => { if (active.current) setState('error'); }),
|
|
router.on('httpException', event => { if (active.current) { event.preventDefault(); setState('error'); } }),
|
|
router.on('networkError', event => { if (active.current) { event.preventDefault(); setState('error'); } })];
|
|
return () => off.forEach(remove => remove());
|
|
}, [total]);
|
|
if (total === null && state === 'idle') return null;
|
|
return <aside data-search-feedback role={state === 'error' ? 'alert' : 'status'} aria-live="polite" aria-atomic="true" className="sticky top-0 z-50 border-b bg-white p-3 text-center text-slate-950 shadow">
|
|
{state === 'loading' ? 'A pesquisar…' : state === 'error' ? <><span>Não foi possível concluir a pesquisa. Os resultados anteriores mantêm-se abaixo. </span><button type="button" className="underline" onClick={() => { const request = latest.current; if (request) router.get(request.url, request.data as never, {preserveState:true, preserveScroll:true}); }}>Tentar novamente</button></> : total === 0 ? 'Não foram encontrados resultados para esta pesquisa' : `${total} resultados encontrados`}
|
|
</aside>;
|
|
}
|