gigafibre-fsm/apps/ops/src/components/flow-editor/VariablePicker.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

115 lines
4.0 KiB
Vue

<!--
VariablePicker.vue Menu de variables `{{path}}` cliquables.
Un dropdown qui liste toutes les variables disponibles pour un `applies_to`
donné (Customer / Quotation / Service Contract / Issue / Subscription).
Deux modes d'utilisation :
- Mode "insert" (défaut) : émet @insert('{{path}}') pour qu'un parent
l'ajoute à la fin du champ qu'il contrôle (ex : trigger_condition).
- Mode "copy" : copie `{{path}}` dans le presse-papier et
notifie l'utilisateur — utile quand il n'y a pas de champ cible
capturé dans le même composant (ex : dans le StepEditorModal
n'importe quel champ de payload peut en avoir besoin).
Props :
- appliesTo : 'Customer' | 'Quotation' | ...
- label : texte du bouton (défaut "+ Variable")
- mode : 'insert' | 'copy' (défaut 'insert')
Events :
- insert(text) : émet `{{path}}` à insérer (mode 'insert' uniquement)
Performance :
- La liste est computed une fois par applies_to (mémoisée par Vue).
- Les entrées sont triées par domaine puis alphabétique pour rester stable.
-->
<template>
<q-btn-dropdown flat dense no-caps size="sm" :icon="icon" :label="label"
class="variable-picker" color="primary" content-class="vp-menu">
<q-list dense class="vp-list">
<q-item-label header class="vp-header">
Variables disponibles
<span v-if="appliesTo" class="text-caption text-grey-6">
· {{ appliesTo }}
</span>
</q-item-label>
<q-item v-if="!appliesTo" class="vp-empty">
<q-item-section>
<q-item-label class="text-caption text-grey-7">
Sélectionnez « Applique à » pour voir les variables du domaine.
</q-item-label>
</q-item-section>
</q-item>
<q-item v-for="v in variables" :key="v.path" clickable v-close-popup
@click="onPick(v)" class="vp-item">
<q-item-section>
<q-item-label class="text-caption text-weight-medium">{{ v.label }}</q-item-label>
<q-item-label caption>
<code class="vp-code">{{ formatted(v.path) }}</code>
</q-item-label>
<q-item-label v-if="v.hint" caption class="text-grey-6 vp-hint">
{{ v.hint }}
</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon :name="mode === 'copy' ? 'content_copy' : 'add_circle_outline'" size="16px" color="green-5" />
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</template>
<script setup>
import { computed } from 'vue'
import { Notify, copyToClipboard } from 'quasar'
import { getVariables } from './variables'
const props = defineProps({
appliesTo: { type: String, default: null },
label: { type: String, default: '+ Variable' },
mode: { type: String, default: 'insert' }, // 'insert' | 'copy'
icon: { type: String, default: 'data_object' },
})
const emit = defineEmits(['insert'])
const variables = computed(() => getVariables(props.appliesTo))
function formatted (path) { return `{{${path}}}` }
async function onPick (v) {
const text = formatted(v.path)
if (props.mode === 'copy') {
try {
await copyToClipboard(text)
Notify.create({ type: 'positive', message: `Copié : ${text}`, timeout: 1200, position: 'top' })
} catch {
Notify.create({ type: 'negative', message: 'Copie impossible', timeout: 1500 })
}
return
}
emit('insert', text)
}
</script>
<style scoped>
.variable-picker :deep(.q-btn__content) { font-size: 0.72rem; }
.vp-list { min-width: 320px; max-width: 380px; max-height: 420px; overflow-y: auto; }
.vp-header { font-weight: 600; font-size: 0.72rem; color: #475569; }
.vp-item { padding: 6px 12px; border-bottom: 1px solid #f1f5f9; }
.vp-item:last-child { border-bottom: none; }
.vp-code {
background: #f1f5f9;
color: #1e293b;
padding: 1px 5px;
border-radius: 3px;
font-size: 0.72rem;
font-family: ui-monospace, Menlo, monospace;
}
.vp-hint { font-size: 0.7rem; margin-top: 2px; }
.vp-empty { padding: 10px 14px; }
</style>