diff --git a/src/composables/useConversations.js b/src/composables/useConversations.js index 6c8d896..7148e6c 100644 --- a/src/composables/useConversations.js +++ b/src/composables/useConversations.js @@ -19,7 +19,17 @@ import { ref, computed, onUnmounted } from 'vue'; import { supabase } from '@/lib/supabase/client'; import { useTenantStore } from '@/stores/tenantStore'; -const KANBAN_ORDER = ['urgent', 'awaiting_us', 'awaiting_patient', 'resolved']; +// Metadata canonica das colunas do kanban — fonte unica consumida pelo +// SFC parent (kanban grid central) e pelo MelissaConversasSidebar +// (resumo "Por status"). Antes, parent + sidebar tinham copias locais +// de KANBAN_COLUMNS e o composable separadamente tinha KANBAN_ORDER. +export const KANBAN_COLUMNS = Object.freeze([ + Object.freeze({ key: 'urgent', label: 'Urgente', icon: 'pi pi-exclamation-triangle', color: 'red' }), + Object.freeze({ key: 'awaiting_us', label: 'Aguardando resposta', icon: 'pi pi-inbox', color: 'amber' }), + Object.freeze({ key: 'awaiting_patient', label: 'Aguardando paciente', icon: 'pi pi-hourglass', color: 'blue' }), + Object.freeze({ key: 'resolved', label: 'Resolvido', icon: 'pi pi-check', color: 'emerald' }) +]); +const KANBAN_ORDER = KANBAN_COLUMNS.map((c) => c.key); function sanitizeSearch(raw) { if (typeof raw !== 'string') return ''; diff --git a/src/layout/melissa/MelissaConversas.vue b/src/layout/melissa/MelissaConversas.vue index 350fb5b..affe5f2 100644 --- a/src/layout/melissa/MelissaConversas.vue +++ b/src/layout/melissa/MelissaConversas.vue @@ -17,7 +17,7 @@ import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'; import { supabase } from '@/lib/supabase/client'; import Popover from 'primevue/popover'; -import { useConversations } from '@/composables/useConversations'; +import { useConversations, KANBAN_COLUMNS } from '@/composables/useConversations'; import { useConversationTags } from '@/composables/useConversationTags'; import { useTenantStore } from '@/stores/tenantStore'; import { useConversationDrawerStore } from '@/stores/conversationDrawerStore'; @@ -56,9 +56,26 @@ const tagById = computed(() => { for (const t of tagsApi.allTags.value) m[t.id] = t; return m; }); + +// Cache O(1) por thread_key — antes tagsForThread era chamado in-template +// no v-for de cards, recriava array a cada render. Agora computa uma vez +// quando threadTagsMap ou tagById muda (raro), lookup O(1) por card. +const EMPTY_TAGS = Object.freeze([]); +const tagsByThreadKey = computed(() => { + const out = new Map(); + const tags = tagById.value; + for (const [key, ids] of threadTagsMap.value) { + const arr = []; + for (const id of ids) { + const t = tags[id]; + if (t) arr.push(t); + } + out.set(key, arr); + } + return out; +}); function tagsForThread(threadKey) { - const ids = threadTagsMap.value.get(threadKey) || []; - return ids.map((id) => tagById.value[id]).filter(Boolean); + return tagsByThreadKey.value.get(threadKey) || EMPTY_TAGS; } async function reloadThreadTags() { // Tags pra TODOS os threads (nao so filtered) — antes, mudar de filtro @@ -108,14 +125,8 @@ async function loadMemberNames() { // assigneeLabel migrou pra MelissaConversasCard (usa props.memberNameMap). // Constantes -const KANBAN_COLUMNS = [ - { key: 'urgent', label: 'Urgente', icon: 'pi pi-exclamation-triangle', color: 'red' }, - { key: 'awaiting_us', label: 'Aguardando resposta', icon: 'pi pi-inbox', color: 'amber' }, - { key: 'awaiting_patient', label: 'Aguardando paciente', icon: 'pi pi-hourglass', color: 'blue' }, - { key: 'resolved', label: 'Resolvido', icon: 'pi pi-check', color: 'emerald' } -]; - -// CHANNEL_OPTIONS migrou pra MelissaConversasSidebar (uso unico la). +// KANBAN_COLUMNS importado de @/composables/useConversations (fonte unica). +// CHANNEL_OPTIONS movido pra @/utils/channelMeta (compartilhado com Sidebar/Card). // Helpers fmtRelative/channelIcon/truncate/contactLabel migraram pra // MelissaConversasCard (uso unico la). Se aparecer 3o consumidor, @@ -212,18 +223,23 @@ watch(() => tenantStore.activeTenantId, async (newTid, oldTid) => { /> -
+