Ver código fonte

ajustes botoes, configurações, login e avaliacoes

leonardo 2 meses atrás
pai
commit
9f3a2e1e2e

+ 2 - 2
dist/index.html

@@ -6,8 +6,8 @@
     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
     <link rel="icon" type="image/x-icon" href="/favicon.ico?v=2" />
     <title>ORÁCULO</title>
-    <script type="module" crossorigin src="/assets/index-W1StSfp_.js"></script>
-    <link rel="stylesheet" crossorigin href="/assets/index-pxjNFwKZ.css">
+    <script type="module" crossorigin src="/assets/index-DDHY7Q2I.js"></script>
+    <link rel="stylesheet" crossorigin href="/assets/index-d-JmXxBm.css">
   </head>
   <body class="bg-background">
     <noscript>

+ 18 - 26
src/views/atendimentos/AvaliacoesView.vue

@@ -3,6 +3,7 @@ import { computed, nextTick, onBeforeUnmount, onMounted, ref } from "vue";
 import { useRoute, useRouter } from "vue-router";
 import LayoutSistema from "../../layout/LayoutSistema.vue";
 import BaseBadge from "../../components/base/BaseBadge.vue";
+import BaseButton from "../../components/base/BaseButton.vue";
 import {
   avaliarAtendimentosPendentes,
   estatisticasAvaliacoes,
@@ -191,14 +192,9 @@ onBeforeUnmount(() => {
                 </p>
               </div>
             </div>
-            <button
-              type="button"
-              class="rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-white shadow-sm hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60"
-              :disabled="avaliandoLote"
-              @click="avaliarPendentes"
-            >
+            <BaseButton :loading="avaliandoLote" @click="avaliarPendentes">
               {{ avaliandoLote ? "Avaliando..." : `Avaliar pendentes${stats?.pendentes ? ` (${stats.pendentes})` : ""}` }}
-            </button>
+            </BaseButton>
           </div>
 
           <div v-if="progresso" class="mt-5">
@@ -282,14 +278,14 @@ onBeforeUnmount(() => {
                 v-model="filtroBusca"
                 type="search"
                 placeholder="Protocolo, cliente, atendente ou data (dd/mm/aaaa)"
-                class="w-full rounded-lg border border-gray-300 bg-transparent py-1.5 pl-9 pr-8 text-sm text-gray-700 placeholder:text-gray-400 dark:border-gray-600 dark:text-gray-200 dark:placeholder:text-gray-500"
+                class="w-full rounded-lg border border-gray-200 bg-background py-1.5 pl-9 pr-8 text-sm text-gray-700 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-primary/30 dark:border-gray-700 dark:text-gray-200 dark:placeholder:text-gray-500"
                 @input="onBuscaInput"
                 @keyup.enter="onBuscaEnter"
               />
               <button
                 v-if="filtroBusca"
                 type="button"
-                class="absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300"
+                class="absolute right-2 top-1/2 -translate-y-1/2 rounded bg-transparent p-0.5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300"
                 title="Limpar busca"
                 @click="filtroBusca = ''; onBuscaEnter();"
               >
@@ -300,7 +296,7 @@ onBeforeUnmount(() => {
             </div>
             <select
               v-model="filtroResolvido"
-              class="rounded-lg border border-gray-300 bg-transparent px-3 py-1.5 text-sm text-gray-700 dark:border-gray-600 dark:bg-transparent dark:text-gray-200 [&>option]:dark:bg-gray-800"
+              class="rounded-lg border border-gray-200 bg-background px-3 py-1.5 text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-primary/30 dark:border-gray-700 dark:text-gray-200 [&>option]:dark:bg-gray-800"
               @change="aplicarFiltros"
             >
               <option value="">Resolução: todas</option>
@@ -311,7 +307,7 @@ onBeforeUnmount(() => {
             </select>
             <select
               v-model="filtroSentimento"
-              class="rounded-lg border border-gray-300 bg-transparent px-3 py-1.5 text-sm text-gray-700 dark:border-gray-600 dark:bg-transparent dark:text-gray-200 [&>option]:dark:bg-gray-800"
+              class="rounded-lg border border-gray-200 bg-background px-3 py-1.5 text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-primary/30 dark:border-gray-700 dark:text-gray-200 [&>option]:dark:bg-gray-800"
               @change="aplicarFiltros"
             >
               <option value="">Sentimento: todos</option>
@@ -334,7 +330,7 @@ onBeforeUnmount(() => {
             <li v-for="a in avaliacoes" :id="`avaliacao-${a.AtendimentoId}`" :key="a.AtendimentoId">
               <button
                 type="button"
-                class="flex w-full flex-wrap items-center gap-2 px-4 py-3 text-left hover:bg-black/[0.03] dark:hover:bg-white/[0.04]"
+                class="flex w-full flex-wrap items-center gap-2 bg-transparent px-4 py-3 text-left hover:bg-black/[0.03] dark:hover:bg-white/[0.04]"
                 @click="alternarExpandido(a.AtendimentoId)"
               >
                 <div class="min-w-0 flex-1">
@@ -378,16 +374,12 @@ onBeforeUnmount(() => {
                   <template v-else>agendada, mas o atendente não informou o horário</template>
                 </div>
                 <div class="flex flex-wrap items-center justify-between gap-3">
-                  <button
-                    type="button"
-                    class="inline-flex items-center gap-1.5 rounded-lg border border-gray-300 px-3 py-1.5 text-xs font-semibold text-gray-700 hover:bg-black/[0.03] dark:border-gray-600 dark:text-gray-200 dark:hover:bg-white/[0.04]"
-                    @click="verConversa(a.AtendimentoId)"
-                  >
+                  <BaseButton variant="secondary" size="sm" @click="verConversa(a.AtendimentoId)">
                     Ver conversa completa
                     <svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                       <path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
                     </svg>
-                  </button>
+                  </BaseButton>
                   <div class="text-xs text-gray-400 dark:text-gray-500">
                     Avaliado por {{ a.Modelo }} em {{ formatData(a.UpdatedAt) }} · {{ a.TotalMensagens }} mensagens
                   </div>
@@ -400,23 +392,23 @@ onBeforeUnmount(() => {
             v-if="totalPaginas > 1"
             class="flex items-center justify-between border-t border-gray-200 px-4 py-3 text-sm dark:border-gray-700"
           >
-            <button
-              type="button"
-              class="rounded-lg border border-gray-300 px-3 py-1.5 text-gray-700 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-600 dark:text-gray-200"
+            <BaseButton
+              variant="secondary"
+              size="sm"
               :disabled="page <= 1 || carregando"
               @click="irParaPagina(page - 1)"
             >
               Anterior
-            </button>
+            </BaseButton>
             <span class="text-gray-500 dark:text-gray-400">Página {{ page }} de {{ totalPaginas }}</span>
-            <button
-              type="button"
-              class="rounded-lg border border-gray-300 px-3 py-1.5 text-gray-700 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-600 dark:text-gray-200"
+            <BaseButton
+              variant="secondary"
+              size="sm"
               :disabled="page >= totalPaginas || carregando"
               @click="irParaPagina(page + 1)"
             >
               Próxima
-            </button>
+            </BaseButton>
           </div>
         </section>
       </div>

+ 3 - 18
src/views/auth/LoginView.vue

@@ -3,6 +3,7 @@ import { computed, onMounted, onUnmounted, ref, watch } from "vue";
 import { useRoute, useRouter } from "vue-router";
 import BotaoAlterarTema from "../../components/BotaoAlterarTema.vue";
 import ModalEsqueciSenha from "../../components/ModalEsqueciSenha.vue";
+import BaseButton from "../../components/base/BaseButton.vue";
 import { useAuth } from "../../composables/useAuth.js";
 
 const router = useRouter();
@@ -238,25 +239,9 @@ async function onSubmit() {
             </div>
           </Transition>
 
-          <button
-            type="submit"
-            class="mt-2 inline-flex items-center justify-center gap-2 rounded-xl border border-primary bg-primary px-4 py-2.5 text-sm font-semibold text-primary-foreground transition-colors hover:border-[var(--primary-600)] hover:bg-[var(--primary-600)] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed"
-            :disabled="!canSubmit || loading"
-          >
-            <svg
-              v-if="loading"
-              aria-hidden="true"
-              class="h-4 w-4 motion-safe:animate-spin"
-              viewBox="0 0 24 24"
-              fill="none"
-              stroke="currentColor"
-              stroke-width="3"
-            >
-              <circle cx="12" cy="12" r="9" class="opacity-25" />
-              <path stroke-linecap="round" d="M21 12a9 9 0 0 0-9-9" />
-            </svg>
+          <BaseButton type="submit" size="lg" class="mt-2" :loading="loading" :disabled="!canSubmit || loading">
             {{ loading ? "Entrando..." : "Entrar" }}
-          </button>
+          </BaseButton>
         </form>
       </div>
     </main>

+ 11 - 27
src/views/configuracoes/ConfiguracoesView.vue

@@ -1,12 +1,11 @@
 <script setup>
 import { computed, ref } from "vue";
-import { useRouter } from "vue-router";
 import LayoutSistema from "../../layout/LayoutSistema.vue";
 import BotaoAlterarTema from "../../components/BotaoAlterarTema.vue";
+import BaseButton from "../../components/base/BaseButton.vue";
 import { useAuth } from "../../composables/useAuth.js";
 import { sincronizarAtendimentos, sincronizarTodosAtendimentos } from "../../api/atendimentos.js";
 
-const router = useRouter();
 const { user } = useAuth();
 const isAdmin = computed(() => String(user.value?.Nivel) === "3");
 
@@ -43,10 +42,6 @@ async function importarTudo() {
     importandoTudo.value = false;
   }
 }
-
-function irParaUsuarios() {
-  router.push({ name: "usuarios" });
-}
 </script>
 
 <template>
@@ -90,10 +85,9 @@ function irParaUsuarios() {
             Usuários & Acesso
           </div>
 
-          <button
-            type="button"
-            class="w-full rounded-2xl border border-gray-200 !bg-none bg-background/70 p-5 text-left shadow-sm dark:border-gray-700 dark:bg-background/20"
-            @click="irParaUsuarios"
+          <RouterLink
+            :to="{ name: 'usuarios' }"
+            class="block w-full rounded-2xl border border-gray-200 bg-background/70 p-5 text-left shadow-sm transition-colors hover:bg-black/[0.02] dark:border-gray-700 dark:bg-background/20 dark:hover:bg-white/[0.03]"
           >
             <div class="flex items-center justify-between gap-4">
               <div class="flex items-center gap-3">
@@ -112,13 +106,13 @@ function irParaUsuarios() {
                   <div class="text-xs text-gray-500 dark:text-gray-400">Cadastre e gerencie usuários, perfis e níveis de acesso.</div>
                 </div>
               </div>
-              <span class="inline-flex items-center justify-center rounded-md !p-2 !bg-none !bg-transparent !border-0 !text-current hover:!bg-black/10 dark:hover:!bg-white/10 motion-safe:transition-colors">
-                <svg class="h-5 w-5 shrink-0 text-gray-400 dark:text-gray-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
+              <span class="inline-flex items-center justify-center rounded-md p-2 text-gray-400 dark:text-gray-500">
+                <svg class="h-5 w-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
                   <path stroke-linecap="round" stroke-linejoin="round" d="m9 6 6 6-6 6" />
                 </svg>
               </span>
             </div>
-          </button>
+          </RouterLink>
         </section>
 
         <section v-if="isAdmin">
@@ -142,22 +136,12 @@ function irParaUsuarios() {
                 </div>
               </div>
               <div class="flex shrink-0 items-center gap-2">
-                <button
-                  type="button"
-                  class="rounded-lg border border-gray-300 px-4 py-2 text-sm font-semibold text-gray-700 shadow-sm hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-60 dark:border-gray-600 dark:text-gray-200 dark:hover:bg-white/5"
-                  :disabled="syncOcupado"
-                  @click="importarTudo"
-                >
+                <BaseButton variant="secondary" :loading="importandoTudo" :disabled="syncOcupado" @click="importarTudo">
                   {{ importandoTudo ? "Importando..." : "Importar tudo" }}
-                </button>
-                <button
-                  type="button"
-                  class="rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-white shadow-sm hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60"
-                  :disabled="syncOcupado"
-                  @click="sincronizarAgora"
-                >
+                </BaseButton>
+                <BaseButton :loading="sincronizando" :disabled="syncOcupado" @click="sincronizarAgora">
                   {{ sincronizando ? "Sincronizando..." : "Sincronizar" }}
-                </button>
+                </BaseButton>
               </div>
             </div>
 

+ 27 - 8
src/views/conversas/ConversasPesquisarView.vue

@@ -2,6 +2,7 @@
 import { computed, ref } from "vue";
 import { useRouter } from "vue-router";
 import LayoutSistema from "../../layout/LayoutSistema.vue";
+import BaseButton from "../../components/base/BaseButton.vue";
 import { useChat } from "../../composables/useChat.js";
 
 const router = useRouter();
@@ -33,12 +34,24 @@ function abrirConversa(id) {
       <h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Pesquisar conversas</h2>
 
       <div class="mt-3 grid grid-cols-1 gap-2 min-[520px]:grid-cols-[1fr_120px]">
-        <input
-          v-model="draftQuery"
-          placeholder="Buscar por título..."
-          @keydown.enter.prevent="buscar"
-        />
-        <button type="button" :disabled="!draftQuery.trim()" @click="buscar">Buscar</button>
+        <div class="relative">
+          <svg
+            class="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400 dark:text-gray-500"
+            viewBox="0 0 24 24"
+            fill="none"
+            stroke="currentColor"
+            stroke-width="2"
+          >
+            <path stroke-linecap="round" stroke-linejoin="round" d="m21 21-4.35-4.35M17 10.5a6.5 6.5 0 1 1-13 0 6.5 6.5 0 0 1 13 0Z" />
+          </svg>
+          <input
+            v-model="draftQuery"
+            placeholder="Buscar por título..."
+            class="pl-9"
+            @keydown.enter.prevent="buscar"
+          />
+        </div>
+        <BaseButton :disabled="!draftQuery.trim()" @click="buscar">Buscar</BaseButton>
       </div>
 
       <div class="mt-2.5 flex items-center justify-between gap-3">
@@ -70,9 +83,15 @@ function abrirConversa(id) {
 
         <div
           v-if="query.trim() && !results.length"
-          class="py-6 text-center text-sm text-gray-400 dark:text-gray-500"
+          class="flex flex-col items-center gap-3 py-8 text-center"
         >
-          Nenhuma conversa encontrada.
+          <div class="flex h-14 w-14 items-center justify-center rounded-2xl border border-dashed border-gray-300 dark:border-gray-600">
+            <svg class="h-6 w-6 text-gray-400 dark:text-gray-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
+              <path stroke-linecap="round" stroke-linejoin="round" d="m21 21-4.35-4.35M17 10.5a6.5 6.5 0 1 1-13 0 6.5 6.5 0 0 1 13 0Z" />
+            </svg>
+          </div>
+          <div class="text-sm font-medium text-gray-500 dark:text-gray-400">Nenhuma conversa encontrada</div>
+          <div class="text-xs text-gray-400 dark:text-gray-500">Tente buscar por outro título.</div>
         </div>
       </div>
     </section>