gigafibre-fsm/apps/ops/src/components/shared/NotificationBell.vue
louispaulb 512c4a5f1b feat(ops): dispatch auto complet + perf Boîte/rapports + fix session
Planificateur « Suggérer » : 4 stratégies (smart / meilleurs d'abord / équilibré /
juste ce qu'il faut), compétences+niveaux par tech (édition inline), niveau requis
par compétence + par job, carte des tournées (1 couleur/tech, domicile→arrêts,
sélecteur de jour), fenêtre de dispatch auj.+demain (dates sélectionnées), règle
week-end + placeholder « en attente du quart », clustering + lasso + filtre-date
sur la carte, accès rapide « À assigner » (badge).

Boîte : liste /conversations allégée (45 Mo → ~1 Mo, 17×) + messages chargés à
l'ouverture. Rapports : cache SWR sur revenue-explorer (22×). Session : keep-alive
+ timeout fetch global + authFetch durci → fin des rechargements manuels.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 08:49:35 -04:00

66 lines
4.7 KiB
Vue

<template>
<q-btn flat round dense icon="notifications" :color="dark ? 'white' : 'grey-8'">
<q-badge v-if="unread" floating color="red" rounded>{{ unread > 9 ? '9+' : unread }}</q-badge>
<q-tooltip>Notifications</q-tooltip>
<q-menu anchor="bottom right" self="top right" @show="onShow">
<div style="min-width:330px;max-width:390px">
<div class="row items-center q-px-md q-py-sm" style="border-bottom:1px solid #eef1f6">
<q-icon name="notifications" color="primary" class="q-mr-xs" />
<span class="text-weight-bold">Notifications</span>
<q-space />
<q-btn flat dense round size="sm" icon="tune" :color="showPrefs ? 'primary' : 'grey-7'" @click.stop="showPrefs = !showPrefs"><q-tooltip>Choisir mes notifications</q-tooltip></q-btn>
<q-btn v-if="notifications.length" flat dense size="sm" no-caps label="Tout lu" color="grey-7" @click="markAllRead" />
</div>
<!-- Préférences PAR UTILISATEUR : activer/désactiver chaque feed (suivent le compte) -->
<div v-if="showPrefs" class="q-px-md q-py-sm" style="border-bottom:1px solid #eef1f6;background:#fafbfc">
<div class="text-caption text-grey-6 q-mb-xs">Me notifier pour&nbsp;:</div>
<div v-for="f in FEEDS" :key="f.key" class="row items-center no-wrap q-py-xs">
<q-icon :name="f.icon" :color="f.color" size="18px" class="q-mr-sm" />
<span class="text-body2 col">{{ f.label }}</span>
<q-toggle :model-value="prefs[f.key]" @update:model-value="v => setFeed(f.key, v)" dense color="primary" />
</div>
<!-- Mes FILES (self-service) : m'abonner + (dés)activer la notif par file → pilote qui SONNE sur appel entrant + le screen-pop. -->
<div v-if="(myQueues.queues || []).length" class="q-mt-sm q-pt-sm" style="border-top:1px solid #eef1f6">
<div class="text-caption text-grey-6 q-mb-xs">Mes files (appels + conversations)&nbsp;:</div>
<div v-for="q in myQueues.queues" :key="q" class="row items-center no-wrap q-py-xs">
<q-icon name="groups" :color="myQueues.mine.includes(q) ? 'primary' : 'grey-5'" size="18px" class="q-mr-sm" />
<span class="text-body2 col" :class="{ 'text-grey-5': !myQueues.mine.includes(q) }">{{ (myQueues.labels && myQueues.labels[q]) || q }}</span>
<q-toggle :model-value="myQueues.mine.includes(q)" @update:model-value="v => setQueueSub(q, v)" dense color="primary"><q-tooltip>M'abonner à cette file</q-tooltip></q-toggle>
<q-btn flat dense round size="sm" :icon="myQueues.mine.includes(q) && (myQueues.notify[q] !== false) ? 'notifications_active' : 'notifications_off'" :color="myQueues.mine.includes(q) && (myQueues.notify[q] !== false) ? 'green-7' : 'grey-5'" :disable="!myQueues.mine.includes(q)" class="q-ml-xs" @click="setQueueNotify(q, myQueues.notify[q] === false)"><q-tooltip>{{ myQueues.notify[q] === false ? 'Activer la notif de cette file' : 'Couper la notif de cette file' }}</q-tooltip></q-btn>
</div>
</div>
<div class="text-caption text-grey-5 q-mt-xs">Préférences propres à ton compte.</div>
</div>
<q-list v-if="notifications.length" style="max-height:60vh;overflow:auto">
<q-item v-for="n in notifications" :key="n.id" clickable v-close-popup @click="go(n)">
<q-item-section avatar>
<q-icon :name="n.icon || (n.type === 'comment' ? 'rate_review' : 'star')" :color="n.low ? 'orange-8' : (n.type === 'comment' ? 'primary' : (n.type === 'rating' ? 'amber-7' : 'teal-7'))" size="22px" />
</q-item-section>
<q-item-section>
<q-item-label lines="1">{{ n.title }}</q-item-label>
<q-item-label caption lines="2">{{ n.caption }}</q-item-label>
</q-item-section>
<q-item-section side><q-item-label caption>{{ relTime(n.ts) }}</q-item-label></q-item-section>
</q-item>
</q-list>
<div v-else class="text-grey-6 text-center q-pa-lg"><q-icon name="notifications_none" size="32px" color="grey-4" /><br>Aucune notification</div>
</div>
</q-menu>
</q-btn>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { useNotifications } from 'src/composables/useNotifications'
import { relTime } from 'src/composables/useFormatters'
defineProps({ dark: Boolean })
const { notifications, unread, prefs, FEEDS, myQueues, setQueueSub, setQueueNotify, initNotifications, markAllRead, savePrefs, go } = useNotifications()
const showPrefs = ref(false)
function onShow () { markAllRead() } // ouvrir = marquer lu (pastille à zéro)
function setFeed (key, v) { prefs.value[key] = v; savePrefs() }
onMounted(initNotifications)
</script>