From 9eb1a45b096ebe59963d3d98af4f55937d9c7e02 Mon Sep 17 00:00:00 2001 From: admbusiness Date: Sat, 30 May 2026 11:05:00 -0300 Subject: [PATCH 3/6] refactor(ui): rework 3 underused layouts from UX audit (round 3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 3 telas que o usuário identificou com aproveitamento de espaço ruim, conteúdo quebrado ou estados vazios pobres. Refatoração visual sem mexer em lógica. 1) SelectChannel.vue — grid de canais 2x2 fixo em 720px deixava metade da tela vazia em desktop. Trocado para grid responsivo: - auto-fit minmax(260px, 1fr) por padrão, 4 colunas em ≥1200px, 1 coluna ≤640px - cards ganharam min-height:220px + height:100% para alinhar a base - footer e grid ocupam 100% do container (eliminado max-width:720px) 2) Step2Content/AiMode.vue — coluna direita do briefing era só um ícone sparkles + texto "Preencha o briefing e gere variações". 60% da viewport desperdiçada quando o estado mais frequente é "ainda não gerou nada". Substituído por dois blocos lado a lado (md+ breakpoint): - Preview SMS ao vivo em mockup de dispositivo (atualiza conforme briefing.product/audience/benefit/cta vão sendo preenchidos), com contador 0/160 quando válido - "Como a IA gera suas mensagens": 3 passos numerados + dica destacada incentivando briefing específico 3) Funnels Editor — 3 problemas: a) FunnelToolbar.vue usava emoji 📨⏱🔀🏷👤🤖 como ícones (viola no-emoji-icons) + background hardcoded #dcfce7 verde-claro de light-mode num app dark (texto preto sumia). Refeito com: - ícones tabler (ti-message/clock/arrows-split/tag/user-check/robot) - background semi-transparente da cor do bloco (rgba) - layout vertical icon + label + hover/focus states acessíveis b) MiniMap do VueFlow sem props → renderizava com tamanho/cores default conflitando com o dark theme. Adicionados width=160, height=100, maskColor, nodeColor + position bottom-right; Controls movido para bottom-left pra não competir; Background ganhou pattern-color escuro c) NodePropertiesPanel.vue: empty state era texto genérico "Clique num bloco para editar" sem contexto. Refeito com ícone + título + descrição + 4 passos numerados explicando o fluxo (arrastar → conectar → configurar → ativar) Validação: - vite build passa (17.29s) - Screenshots FIX-select-channel-v2.jpeg, FIX-create-step1-v2.jpeg, FIX-funnels-editor-v2.jpeg confirmam visualmente os 3 refactors. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../campaigns/steps/Step2Content/AiMode.vue | 212 +++++++++++++++++- .../src/components/funnels/FunnelToolbar.vue | 103 ++++++++- .../funnels/NodePropertiesPanel.vue | 93 +++++++- .../src/pages/campaigns/SelectChannel.vue | 20 +- .../frontend/src/pages/funnels/Editor.vue | 16 +- 5 files changed, 415 insertions(+), 29 deletions(-) diff --git a/new_saas/frontend/src/components/campaigns/steps/Step2Content/AiMode.vue b/new_saas/frontend/src/components/campaigns/steps/Step2Content/AiMode.vue index c335d716..4f9e283f 100644 --- a/new_saas/frontend/src/components/campaigns/steps/Step2Content/AiMode.vue +++ b/new_saas/frontend/src/components/campaigns/steps/Step2Content/AiMode.vue @@ -103,14 +103,57 @@
- -
-
-
- + +
+ +
+
+
+
+ + Pré-visualização — {{ channelLabel }} +
+
+

{{ previewMessage }}

+
+ {{ previewCharCount }}/160 +
+
+
+
+ + +
+

+ Como a IA gera suas mensagens +

+
    +
  1. + 1 +
    + Você descreve + produto, público, benefício e chamada para ação +
    +
  2. +
  3. + 2 +
    + IA cria N versões + com tons diferentes, otimizadas para o canal +
    +
  4. +
  5. + 3 +
    + Você escolhe a melhor + e dispara — sem perder tempo escrevendo do zero +
    +
  6. +
+
+ + Quanto mais específico o briefing, melhor o resultado. Quebra o público em "mães 30-45 anos" em vez de "geral".
-

Preencha o briefing e gere variações

-

A IA criará mensagens personalizadas para sua campanha.

@@ -239,6 +282,31 @@ const briefingValid = computed(() => !!brief.value.product && !!brief.value.audience && !!brief.value.benefit && !!brief.value.cta ) +const channelLabel = computed(() => { + const map: Record = { sms: 'SMS', voice: 'Torpedo de Voz', email: 'Email', whatsapp: 'WhatsApp' } + return map[props.channel] ?? props.channel.toUpperCase() +}) + +// Pré-visualização ao vivo da mensagem. Usa o briefing já preenchido para mostrar +// uma versão rascunho — quando vazio, mostra um exemplo placeholder. +const previewMessage = computed(() => { + const b = brief.value + if (!b.product && !b.audience && !b.benefit && !b.cta) { + return 'Olá! Aproveite [benefício] em [produto]. [chamada para ação] → [link]' + } + const parts: string[] = [] + if (b.audience) parts.push(`Olá! Para ${b.audience.toLowerCase()}:`) + else parts.push('Olá!') + if (b.product && b.benefit) parts.push(`${b.product} com ${b.benefit.toLowerCase()}.`) + else if (b.product) parts.push(`${b.product}.`) + else if (b.benefit) parts.push(`${b.benefit}.`) + if (b.cta) parts.push(b.cta + '.') + if (b.link) parts.push(b.link) + return parts.join(' ') +}) + +const previewCharCount = computed(() => briefingValid.value ? previewMessage.value.length : 0) + function handleGenerate() { if (!brief.value.link?.trim()) { showNoLinkWarning.value = true @@ -351,4 +419,134 @@ onMounted(() => { if (props.campaignId) loadSessions() }) background: #0064ff; color: #fff; } + +/* ─── Empty state preview (Step 1 Briefing direita) ─── */ +.ai-empty { + display: grid; + gap: 1.25rem; +} +@media (min-width: 992px) { + .ai-empty { grid-template-columns: minmax(280px, 360px) 1fr; } +} + +.ai-empty__preview { + display: flex; + justify-content: center; +} +.ai-empty__device { + position: relative; + width: 100%; + max-width: 320px; + background: var(--bc-gray); + border-radius: 24px; + padding: 18px 14px 16px; + border: 1px solid var(--bc-outline); +} +.ai-empty__device-notch { + position: absolute; + top: 8px; + left: 50%; + transform: translateX(-50%); + width: 60px; + height: 5px; + border-radius: 9999px; + background: rgba(255,255,255,0.08); +} +.ai-empty__bubble-header { + display: flex; + align-items: center; + gap: 0.4rem; + font-size: 0.7rem; + color: var(--bc-text-muted); + text-transform: uppercase; + letter-spacing: 0.06em; + margin: 0.5rem 0 0.75rem; + font-weight: 600; +} +.ai-empty__bubble { + background: var(--bc-primary-subtle); + color: var(--bc-text); + border-radius: 16px 16px 16px 4px; + padding: 0.85rem 1rem; + font-size: 0.88rem; + line-height: 1.55; + position: relative; +} +.ai-empty__bubble.is-placeholder { + color: var(--bc-text-muted); + font-style: italic; + background: var(--bc-gray-soft); +} +.ai-empty__chars { + margin-top: 0.5rem; + font-size: 0.7rem; + font-family: 'JetBrains Mono', monospace; + color: var(--bc-text-muted); + text-align: right; +} + +.ai-empty__tips { + background: var(--bc-gray); + border-radius: 14px; + padding: 1.25rem; +} +.ai-empty__tips-title { + font-family: 'Manrope', sans-serif; + font-size: 0.95rem; + font-weight: 700; + margin: 0 0 1rem; + color: var(--bc-text); +} +.ai-empty__steps { + list-style: none; + padding: 0; + margin: 0 0 1rem; + display: flex; + flex-direction: column; + gap: 0.85rem; +} +.ai-empty__steps li { + display: flex; + align-items: flex-start; + gap: 0.75rem; +} +.ai-empty__step-num { + flex-shrink: 0; + width: 26px; + height: 26px; + border-radius: 50%; + background: var(--bc-primary-subtle); + color: var(--bc-primary); + font-weight: 700; + font-size: 0.78rem; + display: flex; + align-items: center; + justify-content: center; +} +.ai-empty__steps li > div { + display: flex; + flex-direction: column; + gap: 0.15rem; + font-size: 0.85rem; + line-height: 1.4; +} +.ai-empty__steps strong { color: var(--bc-text); font-weight: 600; } + +.ai-empty__note { + display: flex; + align-items: flex-start; + gap: 0.5rem; + padding: 0.65rem 0.85rem; + background: rgba(0, 100, 255, 0.06); + border-left: 2px solid var(--bc-primary); + border-radius: 6px; + font-size: 0.78rem; + color: var(--bc-text-muted); + line-height: 1.45; +} +.ai-empty__note i { + color: var(--bc-primary); + margin-top: 0.15rem; + flex-shrink: 0; +} diff --git a/new_saas/frontend/src/components/funnels/FunnelToolbar.vue b/new_saas/frontend/src/components/funnels/FunnelToolbar.vue index 8b293cb5..f4465349 100644 --- a/new_saas/frontend/src/components/funnels/FunnelToolbar.vue +++ b/new_saas/frontend/src/components/funnels/FunnelToolbar.vue @@ -1,23 +1,36 @@ + + diff --git a/new_saas/frontend/src/components/funnels/NodePropertiesPanel.vue b/new_saas/frontend/src/components/funnels/NodePropertiesPanel.vue index db7123ef..7887a3fa 100644 --- a/new_saas/frontend/src/components/funnels/NodePropertiesPanel.vue +++ b/new_saas/frontend/src/components/funnels/NodePropertiesPanel.vue @@ -93,8 +93,32 @@
-
- Clique num bloco para editar +
+
+ +
+

Selecione um bloco

+

+ Clique em qualquer bloco no canvas para editar suas propriedades aqui. +

+
    +
  1. + 1 + Arraste blocos da barra lateral esquerda +
  2. +
  3. + 2 + Conecte-os puxando das bordas +
  4. +
  5. + 3 + Configure as propriedades de cada bloco +
  6. +
  7. + 4 + Defina os gatilhos no rodapé e ative +
  8. +
@@ -134,3 +158,68 @@ function emitLabelUpdate() { if (props.node) emit('update:label', props.node.id, label.value) } + + diff --git a/new_saas/frontend/src/pages/campaigns/SelectChannel.vue b/new_saas/frontend/src/pages/campaigns/SelectChannel.vue index 2ca9e0a3..393bbb0c 100644 --- a/new_saas/frontend/src/pages/campaigns/SelectChannel.vue +++ b/new_saas/frontend/src/pages/campaigns/SelectChannel.vue @@ -221,12 +221,18 @@ onMounted(loadChannels) color: var(--bc-text-muted); } -/* ─── 2x2 Grid ─── */ +/* ─── Responsive channel grid (até 4 colunas em desktop) ─── */ .sc-grid { display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 16px; - max-width: 720px; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: 1rem; + width: 100%; +} +/* Em telas >= 1200px com 4 canais, força 4 colunas equilibradas */ +@media (min-width: 1200px) { + .sc-grid { + grid-template-columns: repeat(4, 1fr); + } } @media (max-width: 640px) { .sc-grid { grid-template-columns: 1fr; } @@ -237,12 +243,14 @@ onMounted(loadChannels) position: relative; background: var(--bc-gray); border-radius: var(--bc-radius-lg); - padding: 28px 24px; + padding: 28px 20px; display: flex; flex-direction: column; align-items: center; text-align: center; transition: var(--bc-transition); + min-height: 220px; + height: 100%; } /* Enabled hover */ @@ -378,7 +386,7 @@ onMounted(loadChannels) align-items: center; justify-content: space-between; margin-top: 2rem; - max-width: 720px; + width: 100%; } .sc-footer__info { font-size: 0.8rem; diff --git a/new_saas/frontend/src/pages/funnels/Editor.vue b/new_saas/frontend/src/pages/funnels/Editor.vue index b162006c..a157dff3 100644 --- a/new_saas/frontend/src/pages/funnels/Editor.vue +++ b/new_saas/frontend/src/pages/funnels/Editor.vue @@ -47,9 +47,19 @@ @node-click="onNodeClick" @pane-click="selectedNode = null" > - - - + + +
-- 2.48.1.windows.1