MelissaConversas: a11y + perf tagsForThread + DRY (channelMeta + KANBAN_COLUMNS shared)

A11y no parent:
- aria-label em botoes icon-only do header (Recarregar dinamico, Buscar
  compact, Close); tooltip vira title que SR ignora
- aria-hidden=true em icones decorativos (header title, search input,
  subheader info-circle, kanban col head, empty state, button icons)
- aria-busy reativo no mw-col__body durante loading
- aria-label dinamico no count do kanban ("3 conversas em Urgente")
- aria-expanded + aria-controls no menu mobile button
- aria-label no input de busca
- role=note no subheader explicativo
- :inert="(drawerOpen && isMobile) || null" no <section class="mw-page">
  — focus trap real: drawer aberto torna conteudo de fundo inerte
  (boolean attr via || null pra Vue 3.4 serializar correto)

A11y no Sidebar:
- aria-hidden=true em todos icones decorativos restantes (filter title
  icons, list/bell/user/user-minus, channel icons, filter-slash, etc)

Perf — tagsForThread cacheado:
- Antes era chamado in-template (2x por card, recriava array a cada
  render). Agora tagsByThreadKey computed Map: lookup O(1) por card,
  recompute so quando threadTagsMap ou tagById muda. EMPTY_TAGS frozen
  evita criar arrays novos pra threads sem tags.

DRY — channelMeta + KANBAN_COLUMNS shared:
- src/utils/channelMeta.js (novo): CHANNEL_OPTIONS frozen + channelIcon
  + channelLabel. Antes channelIcon estava em 3 lugares (parent, Sidebar,
  Card); CHANNEL_OPTIONS em 2 (parent, Sidebar). Agora 1.
- useConversations.js: exporta KANBAN_COLUMNS frozen (metadata canonica:
  key + label + icon + color). Antes parent+Sidebar tinham copias locais
  de 8 linhas cada + composable tinha KANBAN_ORDER separado. Agora
  KANBAN_ORDER deriva de KANBAN_COLUMNS.

Drift eliminado: 3 fontes -> 1 pra channelIcon, 2 -> 1 pra
CHANNEL_OPTIONS, 2 -> 1 pra KANBAN_COLUMNS (KANBAN_ORDER ainda interno
ao composable mas derivado).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-05-07 16:37:46 -03:00
parent 250e946084
commit cc7841bd1f
5 changed files with 116 additions and 69 deletions
+11 -1
View File
@@ -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 '';