Composable

useSearchToolbar

Composable headless per gestire lo stato filtri senza il componente UI. Stessa API di SearchToolbar (filtri base + preset + rule builder + group-by + ricerche salvate).

Panoramica

useSearchToolbar gestisce lo stato dei filtri in modo programmatico. Utile quando vuoi separare la logica di ricerca dalla UI, costruire toolbar custom o riusare la logica in più viste.

I 4 layer avanzati (preset, rule builder, group-by, ricerche salvate) sono tutti opzionali. Se non passi le opzioni rispettive, il composable ritorna comunque le proprietà ma con defaults innocui (Set vuoto, no-op handlers, ecc.) — così il consumer può usare la stessa interfaccia senza branch condizionali.

Firma

function useSearchToolbar<T extends Record<string, unknown>>(
  options: UseSearchToolbarOptions<T>
): UseSearchToolbarReturn<T>

Opzioni

interface UseSearchToolbarOptions<T extends Record<string, unknown>> {
  // Core
  fields: FieldConfig[]
  initialValues?: Partial<T>
  onApply?: (state: SearchState) => void
  onReset?: (filters: T) => void

  // Preset (opzionale)
  presets?: PresetFilterGroup[]
  initialActivePresets?: string[]

  // Rule builder (opzionale)
  ruleBuilderFields?: RuleFieldDefinition[]
  initialRules?: RuleGroup

  // Group by (opzionale)
  groupByFields?: GroupByFieldConfig[]
  initialGroupBy?: GroupByEntry[]

  // Saved searches (opzionale)
  savedSearchAdapter?: SavedSearchAdapter
}
Un solo onApply: riceve sempre lo stato completo SearchState con filters, activePresets, rules e groupByFields. Quando i layer non sono configurati i campi sono valori vuoti ([], null).

Esempio: filtri base

import { useSearchToolbar, buildPostgrestQuery } from '@pzeta/vue-ricerca'

const { filters, isValid, hasActiveFilters, activeFilterCount, apply, reset, setField } =
  useSearchToolbar({
    fields,
    initialValues: { stato: 'attivo' },
    onApply: (state) => {
      const query = buildPostgrestQuery(state.filters, fields)
      // fetch(...)
    },
  })

Esempio: setup completo

import { useSearchToolbar, buildPostgrestQueryFromState } from '@pzeta/vue-ricerca'

const {
  filters,
  rules,
  activePresets,
  groupByFields,
  savedSearches,
  searchState,
  apply,
} = useSearchToolbar({
  fields,
  presets: presetGroups,
  ruleBuilderFields: ruleFields,
  groupByFields: groupByConfig,
  savedSearchAdapter: adapter,
  onApply: (state) => {
    const query = buildPostgrestQueryFromState(state, {
      fields,
      presets: presetGroups,
      ruleBuilderFields: ruleFields,
      groupByFields: groupByConfig,
    })
    // fetch con query
  },
})

Return — stato base

ProprietàTipoDescrizione
filtersRef<T>Stato reattivo dei filtri (con merge di initialValues su buildDefaultValues(fields))
errorsComputedRef<Record<string, string>>Errori di validazione range (numberrange / daterange con from > to)
isValidComputedRef<boolean>true se nessun errore di validazione
hasActiveFiltersComputedRef<boolean>true se almeno un filtro è diverso dal default
activeFilterCountComputedRef<number>Numero di filtri attivi (range count = 2 se entrambi from/to settati)
apply()() => voidChiama onApply(searchState.value) se isValid.value
reset()() => voidResetta filtri, preset, rule builder e group-by ai valori iniziali
setField(key, value)(string, unknown) => voidImposta un singolo campo
searchStateComputedRef<SearchState>Stato combinato reattivo (filtri + preset + rules + groupBy)

Return — preset

ProprietàTipoDescrizione
activePresetsRef<Set<string>>Set degli ID preset attivi
togglePreset(id)(string) => voidAttiva/disattiva un preset (gestisce exclusive per gruppo)
clearPresets()() => voidDisattiva tutti i preset
activePresetCountComputedRef<number>Numero preset attivi

Senza opzione presets: activePresets è Ref(new Set()), togglePreset/clearPresets sono no-op.

Return — rule builder

ProprietàTipoDescrizione
rulesRef<RuleGroup>AST radice (sempre presente, anche vuoto)
addRule(groupId?)(string?) => voidAggiunge regola vuota (al gruppo radice se groupId omesso)
removeRule(id)(string) => voidRimuove regola per ID
updateRule(id, patch)(string, Partial<Rule>) => voidAggiorna parzialmente una regola
addGroup(parentId?)(string?) => voidAggiunge sotto-gruppo
removeGroup(id)(string) => voidRimuove un gruppo
setCombinator(id, c)(string, 'AND' | 'OR') => voidCambia combinatore di un gruppo
moveRuleUp(id) / moveRuleDown(id)(string) => voidRiordina regole nel gruppo padre
hasRulesComputedRef<boolean>true se almeno una regola completa
ruleCountComputedRef<number>Numero totale di regole (foglie dell'AST)
Il rule builder è sempre attivo nel composable (anche senza ruleBuilderFields), così puoi caricare regole programmaticamente da una ricerca salvata. La UI del rule builder viene renderizzata solo se passi ruleBuilderFields al SearchToolbar.

Return — group by

ProprietàTipoDescrizione
groupByFieldsRef<GroupByEntry[]>Raggruppamenti attivi in ordine di priorità
setGroupBy(fields)(GroupByEntry[]) => voidImposta i raggruppamenti (passa [] per rimuoverli)

Return — ricerche salvate

ProprietàTipoDescrizione
savedSearchesRef<SavedSearch[]>Lista ricerche salvate caricate dall'adapter
savedSearchesLoadingRef<boolean>true durante operazioni async
canUpdateSearchbooleantrue se l'adapter implementa update
canArchiveSearchbooleantrue se l'adapter implementa archive
saveSearch(name, opts?)asyncSalva la ricerca corrente con searchState.value
updateSearch(id, patch)asyncAggiorna metadati (nome, isDefault, isShared, sharedWith)
archiveSearch(id)asyncSoft delete (richiede archive sull'adapter)
loadSearch(id)(string) => voidRipristina filtri, preset, rules, groupBy da una ricerca
deleteSearch(id)asyncElimina ricerca per ID
setDefaultSearch(id)asyncImposta o rimuove (null) la ricerca default

Senza opzione savedSearchAdapter: savedSearches è Ref([]), tutti i metodi sono no-op che ritornano Promise<void>, canUpdateSearch/canArchiveSearch sono false.

Validazione range

Il composable valida automaticamente i campi daterange e numberrange. Se from > to, errors contiene un messaggio e apply() non chiama onApply (esce silenziosamente):

// filters.prezzoMin = 500, filters.prezzoMax = 100
// → errors.value = { prezzo: 'Il valore "Da" non può essere superiore al valore "A"' }
// → isValid.value = false
// → apply() esce senza chiamare onApply

Pattern: caricare una ricerca salvata

loadSearch aggiorna in cascata filters, activePresets, rules e groupByFields con i valori dello snapshot — utile per implementare un selettore "ricerche recenti":

const { savedSearches, loadSearch, apply } = useSearchToolbar({ fields, savedSearchAdapter })

function applyPreset(id: string) {
  loadSearch(id)
  apply()
}