feat(ui): T5 — DynamicFilter sur Tickets (recherche + chips statut/type/priorité + presets)

Remplace search + 3 q-select par <DynamicFilter> ('filter.tickets') ; chipGroups computed (types dynamiques) ;
statut défaut = not_closed préservé ; mappe l'état → refs existantes (search/statusFilter/typeFilter/priorityFilter) → resetAndLoad débouncé.
La PORTÉE « Mes tickets / Mes départements / Tous » reste un toggle séparé (logique de scope/pagination distincte). Compte redondant retiré.
Vérifié préview : 3 groupes, « Non fermés » actif par défaut, chip Urgent 542→3, toggle portée conservé. Build vert, leak 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
louispaulb 2026-07-04 19:26:57 -04:00
parent 3d52ae32c4
commit 4d940eefc4

View File

@ -2,30 +2,12 @@
<q-page padding> <q-page padding>
<!-- Filters row --> <!-- Filters row -->
<div class="row q-col-gutter-sm q-mb-md items-end"> <div class="row q-col-gutter-sm q-mb-md items-end">
<div class="col-12 col-md-3"> <div class="col-12 col-md">
<div class="filter-label">Recherche</div> <DynamicFilter v-model="filterState" search-placeholder="Sujet, client, #ticket"
<q-input v-model="search" dense outlined placeholder="Sujet, client, #ticket..." class="ops-search" :chip-groups="chipGroups" preset-key="filter.tickets" :filtered-count="total" @change="onFilterChange" />
@keyup.enter="loadTickets" clearable @clear="loadTickets">
<template #prepend><q-icon name="search" /></template>
</q-input>
</div> </div>
<div class="col-6 col-md-2"> <div class="col-6 col-md-2">
<div class="filter-label">Statut</div> <div class="filter-label">Portée</div>
<q-select v-model="statusFilter" dense outlined emit-value map-options
:options="statusOptions" @update:model-value="resetAndLoad" />
</div>
<div class="col-6 col-md-2">
<div class="filter-label">Type / Departement</div>
<q-select v-model="typeFilter" dense outlined emit-value map-options clearable
:options="issueTypes" @update:model-value="resetAndLoad" placeholder="Tous" />
</div>
<div class="col-6 col-md-2">
<div class="filter-label">Priorite</div>
<q-select v-model="priorityFilter" dense outlined emit-value map-options clearable
:options="priorityOptions" @update:model-value="resetAndLoad" placeholder="Toutes" />
</div>
<div class="col-6 col-md-2">
<div class="filter-label">Mes tickets</div>
<q-btn-toggle v-model="ownerFilter" no-caps dense unelevated <q-btn-toggle v-model="ownerFilter" no-caps dense unelevated
toggle-color="primary" color="grey-3" text-color="grey-8" toggle-color="primary" color="grey-3" text-color="grey-8"
:options="[ :options="[
@ -36,9 +18,6 @@
@update:model-value="resetAndLoad" @update:model-value="resetAndLoad"
/> />
</div> </div>
<div class="col-auto">
<div class="text-caption text-grey-6 q-mt-sm">{{ total.toLocaleString() }} tickets</div>
</div>
<div class="col-auto"> <div class="col-auto">
<FlowQuickButton flat dense icon="account_tree" label="Flows tickets" <FlowQuickButton flat dense icon="account_tree" label="Flows tickets"
tooltip="Configurer les automatisations pour les tickets" tooltip="Configurer les automatisations pour les tickets"
@ -185,6 +164,7 @@ import { formatDate } from 'src/composables/useFormatters'
import { ticketStatusClass as statusClass, priorityClass } from 'src/composables/useStatusClasses' import { ticketStatusClass as statusClass, priorityClass } from 'src/composables/useStatusClasses'
import { useDetailModal } from 'src/composables/useDetailModal' import { useDetailModal } from 'src/composables/useDetailModal'
import { statusOptions, priorityOptions, columns, buildFilters, getSortField } from 'src/config/ticket-config' import { statusOptions, priorityOptions, columns, buildFilters, getSortField } from 'src/config/ticket-config'
import DynamicFilter from 'src/components/shared/DynamicFilter.vue'
import { useSla, SLA_BADGE } from 'src/composables/useSla' import { useSla, SLA_BADGE } from 'src/composables/useSla'
import { useAuthStore } from 'src/stores/auth' import { useAuthStore } from 'src/stores/auth'
import { HUB_URL } from 'src/config/hub' // pour résoudre « mes files » (queue-members) issue_types de mes départements import { HUB_URL } from 'src/config/hub' // pour résoudre « mes files » (queue-members) issue_types de mes départements
@ -206,6 +186,22 @@ const statusFilter = ref('not_closed') // défaut = NON FERMÉS → évite les v
const typeFilter = ref(null) const typeFilter = ref(null)
const priorityFilter = ref(null) const priorityFilter = ref(null)
const ownerFilter = ref('mine') // défaut « façon F » : on atterrit sur MES tickets assignés (priorisés), bascule « Tous » dispo const ownerFilter = ref('mine') // défaut « façon F » : on atterrit sur MES tickets assignés (priorisés), bascule « Tous » dispo
// DynamicFilter (recherche + chips statut/type/priorité + presets) mappe vers les refs existantes qui alimentent buildFilters.
const filterState = ref({ search: '', chips: { statut: statusFilter.value } }) // statut défaut = not_closed
const chipGroups = computed(() => [
{ key: 'statut', label: 'Statut', icon: 'label', options: statusOptions.filter(o => o.value !== 'all') },
{ key: 'type', label: 'Type', icon: 'category', options: issueTypes.value },
{ key: 'priorite', label: 'Priorité', icon: 'priority_high', options: priorityOptions },
])
let filterTimer = null
function onFilterChange (s) {
search.value = s.search || ''
statusFilter.value = s.chips.statut || 'all'
typeFilter.value = s.chips.type || null
priorityFilter.value = s.chips.priorite || null
clearTimeout(filterTimer)
filterTimer = setTimeout(() => resetAndLoad(), 300)
}
const tickets = ref([]) const tickets = ref([])
// Tri « façon F » : dans les vues scopées (Mes tickets), RANGER par priorité (UrgentBas) côté client // Tri « façon F » : dans les vues scopées (Mes tickets), RANGER par priorité (UrgentBas) côté client
// ERPNext+Postgres ne range pas une priorité Select dans l'ORDER BY, et l'ensemble scopé tient sur 1 page. // ERPNext+Postgres ne range pas une priorité Select dans l'ORDER BY, et l'ensemble scopé tient sur 1 page.