iachat/progresso/como-criar-pagina-settings.md

5.0 KiB

Como Criar uma Nova Página de Settings no Chatwoot

Data: 2026-02-22

Objetivo

Criar uma nova tela na área de Configurações do dashboard do Chatwoot (ex: /settings/captain/units).

Contexto

O Chatwoot usa Vue 3 com Composition API (<script setup>). Telas de settings antigas usavam Options API e woot-button, mas o padrão atual usa componentes do dashboard/components-next. Usar o padrão antigo causa tela em branco (crash silencioso).


Passo a Passo

1. Criar o arquivo Vue da página

Local padrão: app/javascript/dashboard/routes/dashboard/settings/<area>/<NomeDaPagina>.vue

Template mínimo funcional:

<script setup>
import { ref, computed, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { useStore, useMapGetter } from 'dashboard/composables/store';
import { useAlert } from 'dashboard/composables';
import { useI18n } from 'vue-i18n';
import SettingsLayout from '../SettingsLayout.vue';
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
import Button from 'dashboard/components-next/button/Button.vue';
import Dialog from 'dashboard/components-next/dialog/Dialog.vue';

const { t } = useI18n();
const router = useRouter();
const store = useStore();

// Exemplo: getter de uma store Vuex namespaced
const records = useMapGetter('minhaStore/getRecords');
const uiFlags = useMapGetter('minhaStore/getUIFlags');

const deleteDialogRef = ref(null);
const itemToDelete = ref(null);

onMounted(async () => {
  await store.dispatch('minhaStore/get');
});
</script>

<template>
  <SettingsLayout
    :is-loading="uiFlags.isFetching"
    :loading-message="t('MINHA_SECAO.LOADING')"
  >
    <template #header>
      <BaseSettingsHeader
        :title="t('MINHA_SECAO.TITLE')"
        :description="t('MINHA_SECAO.DESC')"
      >
        <template #actions>
          <Button
            :label="t('MINHA_SECAO.ADD')"
            icon="i-lucide-plus"
            @click="goToNew"
          />
        </template>
      </BaseSettingsHeader>
    </template>

    <template #body>
      <div class="flex flex-col px-6 pb-8">
        <!-- conteúdo aqui -->

        <!-- Para modal de confirmação de exclusão -->
        <Dialog
          ref="deleteDialogRef"
          type="alert"
          :title="t('MINHA_SECAO.DELETE.TITLE')"
          :description="t('MINHA_SECAO.DELETE.MESSAGE')"
          :confirm-button-label="t('MINHA_SECAO.DELETE.YES')"
          @confirm="confirmDelete"
        />
      </div>
    </template>
  </SettingsLayout>
</template>

⚠️ CRÍTICO: O Dialog de confirmação deve ficar dentro do slot #body, nunca fora do <SettingsLayout>. Dois root elements causam tela branca no Vue 3.


2. Registrar a rota

Arquivo: app/javascript/dashboard/routes/dashboard/settings/<area>/<area>.routes.js

import MinhaNovaPage from './MinhaNovaPage.vue';

// Dentro de children:
{
  path: 'minha-rota',
  name: 'minha_rota_name',
  component: MinhaNovaPage,
  meta: { permissions: ['administrator'] },
},

3. Adicionar item no Sidebar

Arquivo: app/javascript/dashboard/components-next/sidebar/Sidebar.vue

Dentro do grupo correto (ex: Captain), adicione:

{
  name: 'Minha Página',
  label: t('SIDEBAR.MINHA_CHAVE'),
  activeOn: ['minha_rota_name'],
  to: accountScopedRoute('minha_rota_name'),
},

4. Adicionar traduções

Sidebar keyapp/javascript/dashboard/i18n/locale/pt_BR/settings.json:

"SIDEBAR": {
  "MINHA_CHAVE": "Minha Página"
}

Textos da página → Criar ou editar o arquivo específico da feature em i18n/locale/pt_BR/:

{
  "MINHA_SECAO": {
    "TITLE": "...",
    "DESC": "...",
    "ADD": "Adicionar",
    "DELETE": {
      "TITLE": "Confirmar exclusão",
      "MESSAGE": "Tem certeza?",
      "YES": "Excluir"
    }
  }
}

5. Criar ou registrar a Vuex Store (se necessário)

Arquivo: app/javascript/dashboard/store/modules/minhaStore.js

export const state = {
  records: [],
  uiFlags: { isFetching: false, isDeleting: false },
};

export const getters = {
  getRecords: $state => $state.records,
  getUIFlags: $state => $state.uiFlags,
};

// ... actions e mutations

export default {
  namespaced: true,
  state, getters, actions, mutations,
};

Registrar em app/javascript/dashboard/store/index.js:

import minhaStore from './modules/minhaStore';
// dentro de modules:
minhaStore,

Componentes Disponíveis (components-next)

Componente Import
Button dashboard/components-next/button/Button.vue
Dialog dashboard/components-next/dialog/Dialog.vue
Input dashboard/components-next/input/Input.vue
TextArea dashboard/components-next/textarea/TextArea.vue
Icon dashboard/components-next/icon/Icon.vue

Ícones usam o prefixo i-lucide-* (ex: i-lucide-plus, i-lucide-trash-2, i-lucide-pencil).


Exemplo real

Ver app/javascript/dashboard/routes/dashboard/settings/captain/units/Index.vue — tela de Unidades Pix implementada com sucesso seguindo este padrão.