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:
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* channelMeta — fonte unica de canais de comunicacao (WhatsApp/SMS/E-mail).
|
||||
* --------------------------------------------------------------------
|
||||
* Antes, os 3 consumidores (MelissaConversas parent, Sidebar, Card)
|
||||
* tinham copias locais de CHANNEL_OPTIONS + channelIcon. Drift risk
|
||||
* pequeno mas existia — adicionar 'telegram' exigia mexer em N lugares.
|
||||
* Aqui centraliza.
|
||||
*
|
||||
* NAO eh um composable Vue (no useXxx) porque sao constantes puras
|
||||
* e funcoes sem reactivity — arquivo utility eh suficiente.
|
||||
*/
|
||||
|
||||
export const CHANNEL_OPTIONS = Object.freeze([
|
||||
Object.freeze({ label: 'Todos', value: null }),
|
||||
Object.freeze({ label: 'WhatsApp', value: 'whatsapp' }),
|
||||
Object.freeze({ label: 'SMS', value: 'sms' }),
|
||||
Object.freeze({ label: 'E-mail', value: 'email' })
|
||||
]);
|
||||
|
||||
const ICON_MAP = Object.freeze({
|
||||
whatsapp: 'pi-whatsapp',
|
||||
sms: 'pi-comment',
|
||||
email: 'pi-envelope'
|
||||
});
|
||||
|
||||
export function channelIcon(channel) {
|
||||
return ICON_MAP[channel] || 'pi-comment';
|
||||
}
|
||||
|
||||
export function channelLabel(channel) {
|
||||
if (!channel) return 'Todos';
|
||||
const opt = CHANNEL_OPTIONS.find((o) => o.value === channel);
|
||||
return opt?.label || channel;
|
||||
}
|
||||
Reference in New Issue
Block a user