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>
47 lines
2.2 KiB
Vue
47 lines
2.2 KiB
Vue
<template>
|
|
<q-page class="cfp-page">
|
|
<div class="cfp-bar">
|
|
<q-btn flat dense icon="arrow_back" label="Boîte" no-caps color="primary" @click="$router.push('/communications')" />
|
|
</div>
|
|
<ConversationPanel inline />
|
|
</q-page>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { onMounted, watch } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import ConversationPanel from 'src/components/shared/ConversationPanel.vue'
|
|
import { useConversations } from 'src/composables/useConversations'
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const { discussions, activeDiscussion, fetchList, openDiscussion, openConversation } = useConversations()
|
|
|
|
async function openByToken (token) {
|
|
if (!token) return
|
|
const find = () => (discussions.value || []).find(d => d.token === token || (d.conversations || []).some(c => c.token === token))
|
|
let disc = find()
|
|
if (!disc) { await fetchList(); disc = find() }
|
|
if (disc) { openDiscussion(disc); return } // ouvre le fil (active la vue thread plein écran)
|
|
await openConversation(token) // repli : ouverture directe par jeton (pose aussi activeDiscussion)
|
|
if (!activeDiscussion.value) router.push('/communications') // introuvable/supprimée → retour à la boîte, pas de loader infini
|
|
}
|
|
|
|
onMounted(() => openByToken(route.params.token))
|
|
watch(() => route.params.token, t => openByToken(t))
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* Plein écran : la page occupe la hauteur du viewport (moins l'en-tête) → le fil défile à l'intérieur, le bloc de réponse reste ancré en bas */
|
|
.cfp-page { padding: 0; height: calc(100dvh - 51px); display: flex; flex-direction: column; overflow: hidden; }
|
|
.cfp-bar { padding: 4px 8px; border-bottom: 1px solid var(--ops-border, #e2e8f0); background: #fff; flex: 0 0 auto; }
|
|
.cfp-page :deep(.conv-fullpage) { flex: 1 1 auto; min-height: 0; }
|
|
|
|
/* Mobile : ne pas CLIPPER. La hauteur fixe + overflow:hidden cachait le bouton « Envoyer » quand
|
|
le composeur était long (grosse signature). On laisse la PAGE défiler verticalement ; l'éditeur
|
|
est borné (max-height 40vh) et défile en interne → le bouton reste toujours atteignable. */
|
|
@media (max-width: 700px) {
|
|
.cfp-page { height: auto; min-height: calc(100dvh - 50px); overflow: visible; }
|
|
}
|
|
</style>
|