feat(ui): T5 — DynamicFilter sur RDV (worklist : recherche + chips Type)

Worklist de rendez-vous : recherche (job/client/lieu) + chips service_type (dynamiques) + presets ('filter.rdv') ;
`filteredJobs` applique le filtre APRÈS la portée `view` (à planifier/à recontacter/tous), qui reste un toggle séparé.
Client-side, réactif. Build vert, leak 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
louispaulb 2026-07-04 19:36:32 -04:00
parent 7def94199c
commit f6f4303e2e

View File

@ -14,6 +14,8 @@
<div class="row q-col-gutter-md"> <div class="row q-col-gutter-md">
<!-- Worklist --> <!-- Worklist -->
<div class="col-12 col-md-4"> <div class="col-12 col-md-4">
<DynamicFilter v-model="filterState" dense search-placeholder="Job, client, lieu" :chip-groups="chipGroups"
preset-key="filter.rdv" :filtered-count="filteredJobs.length" class="q-mb-sm" />
<q-list bordered separator class="rounded-borders"> <q-list bordered separator class="rounded-borders">
<q-item-label header> <q-item-label header>
{{ view === 'reschedule' ? 'À recontacter' : 'Jobs' }} ({{ filteredJobs.length }}) {{ view === 'reschedule' ? 'À recontacter' : 'Jobs' }} ({{ filteredJobs.length }})
@ -124,6 +126,7 @@
<script setup> <script setup>
import { ref, reactive, computed, onMounted, onBeforeUnmount } from 'vue' import { ref, reactive, computed, onMounted, onBeforeUnmount } from 'vue'
import { fmtDur } from 'src/composables/useHelpers' // durée canonique (source unique : «30m»/«1h»/«1h30») import { fmtDur } from 'src/composables/useHelpers' // durée canonique (source unique : «30m»/«1h»/«1h30»)
import DynamicFilter from 'src/components/shared/DynamicFilter.vue'
import { MapPin } from 'lucide-vue-next' // pin monochrome outline (style menu Dispatch) import { MapPin } from 'lucide-vue-next' // pin monochrome outline (style menu Dispatch)
import { useQuasar } from 'quasar' import { useQuasar } from 'quasar'
import * as roster from 'src/api/roster' import * as roster from 'src/api/roster'
@ -161,10 +164,17 @@ function wkMon (iso) { const dt = d2(iso); const off = (dt.getUTCDay() + 6) % 7;
function wkLabel (m) { const a = d2(m); const b = new Date(a); b.setUTCDate(b.getUTCDate() + 6); return 'Semaine du ' + a.getUTCDate() + ' ' + MO[a.getUTCMonth()] + ' ' + b.getUTCDate() + ' ' + MO[b.getUTCMonth()] } function wkLabel (m) { const a = d2(m); const b = new Date(a); b.setUTCDate(b.getUTCDate() + 6); return 'Semaine du ' + a.getUTCDate() + ' ' + MO[a.getUTCMonth()] + ' ' + b.getUTCDate() + ' ' + MO[b.getUTCMonth()] }
const isReschedule = computed(() => view.value === 'reschedule' || (sel.value && sel.value.booking_status === 'À reporter')) const isReschedule = computed(() => view.value === 'reschedule' || (sel.value && sel.value.booking_status === 'À reporter'))
// Filtre dynamique (client) de la worklist : recherche (job/client/lieu) + chips Type. La PORTÉE (à planifier/à recontacter/tous) reste le toggle `view`.
const filterState = ref({ search: '', chips: {} })
const serviceTypes = computed(() => [...new Set(jobs.value.map(j => (j.service_type || '').trim()).filter(Boolean))].sort().map(t => ({ value: t, label: t })))
const chipGroups = computed(() => [{ key: 'type', label: 'Type', icon: 'category', options: serviceTypes.value }])
const filteredJobs = computed(() => { const filteredJobs = computed(() => {
if (view.value === 'reschedule') return rescheduleJobs.value let base = view.value === 'reschedule' ? rescheduleJobs.value : (view.value === 'all' ? jobs.value : jobs.value.filter(j => !j.scheduled_date || j.booking_status !== 'Confirmé'))
if (view.value === 'all') return jobs.value const t = filterState.value.chips.type
return jobs.value.filter(j => !j.scheduled_date || j.booking_status !== 'Confirmé') if (t) base = base.filter(j => (j.service_type || '').trim() === t)
const q = (filterState.value.search || '').toLowerCase().trim()
if (q) base = base.filter(j => (jobTitle(j) + ' ' + jobLoc(j) + ' ' + (j.customer_name || '')).toLowerCase().includes(q))
return base
}) })
// Créneaux groupés Semaine Jour // Créneaux groupés Semaine Jour
const slotWeeks = computed(() => { const slotWeeks = computed(() => {