Agenda, Agendador, Configurações
This commit is contained in:
@@ -0,0 +1,716 @@
|
||||
<!-- src/components/AjudaDrawer.vue -->
|
||||
<!-- Painel de ajuda lateral — home com sessão/docs/faq + navegação interna + votação -->
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useAjuda } from '@/composables/useAjuda'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const {
|
||||
sessionDocs, sessionFaq,
|
||||
allDocs, allDocsLoading, allDocsHasMore,
|
||||
globalFaq, globalFaqLoading,
|
||||
currentDoc, isHome, isNavigating, navStack,
|
||||
drawerOpen, loading,
|
||||
meusVotos,
|
||||
closeDrawer, navigateToDoc, navBack,
|
||||
votar, loadMoreDocs,
|
||||
} = useAjuda()
|
||||
|
||||
// ── FAQ accordion state ───────────────────────────────────────
|
||||
const faqAbertos = ref({})
|
||||
function toggleFaq (id) { faqAbertos.value[id] = !faqAbertos.value[id] }
|
||||
|
||||
// ── Docs do conteúdo atual (quando navegando) ─────────────────
|
||||
const docAtual = computed(() => currentDoc.value?.docs?.[0] || null)
|
||||
const faqDoDocAtual = computed(() => currentDoc.value?.faqItens || [])
|
||||
const relacionados = computed(() => currentDoc.value?.relatedDocs || [])
|
||||
|
||||
// ── Label do caminho atual para o cabeçalho da sessão ────────
|
||||
const sessionLabel = computed(() => {
|
||||
// Usa o último segmento da rota como label legível
|
||||
const segs = route.path.split('/').filter(Boolean)
|
||||
if (!segs.length) return 'esta página'
|
||||
const last = segs[segs.length - 1]
|
||||
return last.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase())
|
||||
})
|
||||
|
||||
// ── Votação ───────────────────────────────────────────────────
|
||||
const votandoId = ref(null)
|
||||
|
||||
async function handleVotar (docId, util) {
|
||||
votandoId.value = docId
|
||||
await votar(docId, util)
|
||||
votandoId.value = null
|
||||
}
|
||||
|
||||
function meuVoto (docId) {
|
||||
return meusVotos.value[docId] ?? null
|
||||
}
|
||||
|
||||
// ── Navegação ─────────────────────────────────────────────────
|
||||
function abrirDoc (doc) {
|
||||
faqAbertos.value = {}
|
||||
navigateToDoc(doc.id, doc.titulo)
|
||||
}
|
||||
|
||||
function voltar () {
|
||||
faqAbertos.value = {}
|
||||
navBack()
|
||||
}
|
||||
|
||||
function fechar () {
|
||||
faqAbertos.value = {}
|
||||
closeDrawer()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition name="ajuda-slide">
|
||||
<div v-if="drawerOpen" class="ajuda-panel">
|
||||
|
||||
<!-- ── Cabeçalho ─────────────────────────────────────── -->
|
||||
<div class="ajuda-header">
|
||||
<div class="flex items-center gap-2 min-w-0 flex-1">
|
||||
<button v-if="isNavigating" class="nav-btn" @click="voltar" title="Voltar">
|
||||
<i class="pi pi-arrow-left text-xs" />
|
||||
</button>
|
||||
<i v-else class="pi pi-question-circle text-blue-500 text-lg shrink-0" />
|
||||
<div class="flex flex-col min-w-0">
|
||||
<span class="font-semibold text-base leading-tight truncate">
|
||||
{{ isNavigating ? (docAtual?.titulo || 'Ajuda') : 'Central de Ajuda' }}
|
||||
</span>
|
||||
<span v-if="isNavigating && navStack.length > 0" class="breadcrumb">
|
||||
<i class="pi pi-chevron-left text-[9px] mr-0.5" />
|
||||
{{ navStack[navStack.length - 1].label || 'Voltar' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="close-btn" @click="fechar">
|
||||
<i class="pi pi-times text-sm" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ── Corpo ─────────────────────────────────────────── -->
|
||||
<div class="ajuda-body">
|
||||
|
||||
<!-- Loading global -->
|
||||
<div v-if="loading" class="flex justify-center py-10">
|
||||
<i class="pi pi-spinner pi-spin text-xl opacity-40" />
|
||||
</div>
|
||||
|
||||
<!-- ════ VISUALIZAÇÃO DE DOC (navegação interna) ════ -->
|
||||
<template v-else-if="!isHome">
|
||||
|
||||
<div class="doc-view">
|
||||
|
||||
<!-- Conteúdo -->
|
||||
<div v-if="docAtual?.conteudo" class="doc-conteudo ql-content" v-html="docAtual.conteudo" />
|
||||
|
||||
<!-- Mídias -->
|
||||
<template v-if="docAtual?.medias?.length">
|
||||
<div v-for="(m, idx) in docAtual.medias.filter(m => m.url)" :key="idx" class="mt-3">
|
||||
<img v-if="m.tipo === 'imagem'" :src="m.url" :alt="docAtual.titulo" class="doc-img" />
|
||||
<div v-else-if="m.tipo === 'video'" class="doc-video-wrap">
|
||||
<iframe :src="m.url" frameborder="0" allowfullscreen class="doc-iframe" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- FAQ do doc -->
|
||||
<div v-if="faqDoDocAtual.length" class="faq-section">
|
||||
<div class="section-label">
|
||||
<i class="pi pi-comments text-[10px] mr-1" />Perguntas frequentes
|
||||
</div>
|
||||
<div class="faq-list">
|
||||
<div
|
||||
v-for="item in faqDoDocAtual" :key="item.id"
|
||||
class="faq-item" :class="{ 'faq-item--open': faqAbertos[item.id] }"
|
||||
>
|
||||
<button class="faq-pergunta" @click="toggleFaq(item.id)">
|
||||
<span>{{ item.pergunta }}</span>
|
||||
<i class="pi shrink-0 text-xs opacity-50 transition-transform duration-200"
|
||||
:class="faqAbertos[item.id] ? 'pi-chevron-up' : 'pi-chevron-down'" />
|
||||
</button>
|
||||
<Transition name="expand">
|
||||
<div v-if="faqAbertos[item.id] && item.resposta"
|
||||
class="faq-resposta ql-content" v-html="item.resposta" />
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Docs relacionados -->
|
||||
<div v-if="relacionados.length" class="rel-section">
|
||||
<div class="section-label"><i class="pi pi-link text-[10px] mr-1" />Veja também</div>
|
||||
<div class="flex flex-col gap-1 mt-2">
|
||||
<button v-for="rel in relacionados" :key="rel.id" class="rel-btn" @click="abrirDoc(rel)">
|
||||
<i class="pi pi-arrow-right text-xs shrink-0" />
|
||||
<span>{{ rel.titulo }}</span>
|
||||
<i class="pi pi-chevron-right text-[10px] opacity-30 ml-auto shrink-0" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Votação -->
|
||||
<div v-if="docAtual" class="voto-section">
|
||||
<div class="voto-label">Este documento foi útil?</div>
|
||||
<div class="voto-btns">
|
||||
<button
|
||||
class="voto-btn"
|
||||
:class="{
|
||||
'voto-btn--ativo-sim': meuVoto(docAtual.id) === true,
|
||||
'voto-btn--loading': votandoId === docAtual.id
|
||||
}"
|
||||
:disabled="votandoId === docAtual.id"
|
||||
@click="handleVotar(docAtual.id, true)"
|
||||
>
|
||||
<i class="pi pi-thumbs-up" />
|
||||
<span>Sim</span>
|
||||
<span v-if="docAtual.votos_util" class="voto-count">{{ docAtual.votos_util }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="voto-btn"
|
||||
:class="{
|
||||
'voto-btn--ativo-nao': meuVoto(docAtual.id) === false,
|
||||
'voto-btn--loading': votandoId === docAtual.id
|
||||
}"
|
||||
:disabled="votandoId === docAtual.id"
|
||||
@click="handleVotar(docAtual.id, false)"
|
||||
>
|
||||
<i class="pi pi-thumbs-down" />
|
||||
<span>Não</span>
|
||||
<span v-if="docAtual.votos_nao_util" class="voto-count">{{ docAtual.votos_nao_util }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- ════ HOME ════ -->
|
||||
<template v-else>
|
||||
|
||||
<!-- Ícone de boas-vindas -->
|
||||
<div class="home-welcome">
|
||||
<div class="home-icon">
|
||||
<i class="pi pi-book text-2xl" />
|
||||
</div>
|
||||
<p class="home-welcome-text">Como podemos ajudar?</p>
|
||||
</div>
|
||||
|
||||
<!-- Seção: Documentos desta sessão -->
|
||||
<section class="home-section">
|
||||
<div class="section-title">
|
||||
<i class="pi pi-map-marker text-[11px] mr-1.5 opacity-60" />
|
||||
Documentos desta página
|
||||
</div>
|
||||
|
||||
<div v-if="!sessionDocs.length" class="empty-msg">
|
||||
<i class="pi pi-info-circle mr-1 opacity-40" />
|
||||
Ainda não há documentos para esta página.
|
||||
</div>
|
||||
|
||||
<div v-else class="doc-list">
|
||||
<button
|
||||
v-for="doc in sessionDocs" :key="doc.id"
|
||||
class="doc-card"
|
||||
@click="abrirDoc(doc)"
|
||||
>
|
||||
<div class="doc-card-icon">
|
||||
<i class="pi pi-file-edit text-xs" />
|
||||
</div>
|
||||
<div class="doc-card-body">
|
||||
<span class="doc-card-titulo">{{ doc.titulo }}</span>
|
||||
<span v-if="doc.categoria" class="doc-card-cat">{{ doc.categoria }}</span>
|
||||
</div>
|
||||
<i class="pi pi-chevron-right text-[11px] opacity-30 shrink-0" />
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Seção: Outros documentos -->
|
||||
<section class="home-section">
|
||||
<div class="section-title">
|
||||
<i class="pi pi-book text-[11px] mr-1.5 opacity-60" />
|
||||
Outros documentos
|
||||
</div>
|
||||
|
||||
<div v-if="allDocsLoading && !allDocs.length" class="flex justify-center py-4">
|
||||
<i class="pi pi-spinner pi-spin opacity-30" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="!allDocs.length" class="empty-msg">
|
||||
<i class="pi pi-info-circle mr-1 opacity-40" />
|
||||
Nenhum outro documento disponível.
|
||||
</div>
|
||||
|
||||
<div v-else class="doc-list">
|
||||
<button
|
||||
v-for="doc in allDocs" :key="doc.id"
|
||||
class="doc-card"
|
||||
@click="abrirDoc(doc)"
|
||||
>
|
||||
<div class="doc-card-icon">
|
||||
<i class="pi pi-file text-xs" />
|
||||
</div>
|
||||
<div class="doc-card-body">
|
||||
<span class="doc-card-titulo">{{ doc.titulo }}</span>
|
||||
<span v-if="doc.categoria" class="doc-card-cat">{{ doc.categoria }}</span>
|
||||
</div>
|
||||
<i class="pi pi-chevron-right text-[11px] opacity-30 shrink-0" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Carregar mais -->
|
||||
<button
|
||||
v-if="allDocsHasMore"
|
||||
class="load-more-btn"
|
||||
:disabled="allDocsLoading"
|
||||
@click="loadMoreDocs"
|
||||
>
|
||||
<i v-if="allDocsLoading" class="pi pi-spinner pi-spin mr-1" />
|
||||
<i v-else class="pi pi-chevron-down mr-1" />
|
||||
Carregar mais
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<!-- Seção: Perguntas frequentes -->
|
||||
<section class="home-section">
|
||||
<div class="section-title">
|
||||
<i class="pi pi-comments text-[11px] mr-1.5 opacity-60" />
|
||||
Perguntas frequentes
|
||||
</div>
|
||||
|
||||
<!-- FAQ da sessão atual -->
|
||||
<template v-if="sessionFaq.length">
|
||||
<div class="faq-subsection-label">Desta página</div>
|
||||
<div class="faq-list">
|
||||
<div
|
||||
v-for="item in sessionFaq" :key="item.id"
|
||||
class="faq-item" :class="{ 'faq-item--open': faqAbertos[item.id] }"
|
||||
>
|
||||
<button class="faq-pergunta" @click="toggleFaq(item.id)">
|
||||
<span>{{ item.pergunta }}</span>
|
||||
<i class="pi shrink-0 text-xs opacity-50 transition-transform duration-200"
|
||||
:class="faqAbertos[item.id] ? 'pi-chevron-up' : 'pi-chevron-down'" />
|
||||
</button>
|
||||
<Transition name="expand">
|
||||
<div v-if="faqAbertos[item.id] && item.resposta"
|
||||
class="faq-resposta ql-content" v-html="item.resposta" />
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- FAQ global (outros docs) -->
|
||||
<div v-if="globalFaqLoading && !globalFaq.length" class="flex justify-center py-3">
|
||||
<i class="pi pi-spinner pi-spin opacity-30 text-sm" />
|
||||
</div>
|
||||
|
||||
<template v-if="globalFaq.length">
|
||||
<div v-if="sessionFaq.length" class="faq-subsection-label mt-3">Outros tópicos</div>
|
||||
<div class="faq-list">
|
||||
<div
|
||||
v-for="item in globalFaq" :key="item.id"
|
||||
class="faq-item" :class="{ 'faq-item--open': faqAbertos[item.id] }"
|
||||
>
|
||||
<button class="faq-pergunta" @click="toggleFaq(item.id)">
|
||||
<div class="flex flex-col gap-0.5 min-w-0 text-left">
|
||||
<span>{{ item.pergunta }}</span>
|
||||
<span v-if="item._docTitulo" class="faq-doc-label">{{ item._docTitulo }}</span>
|
||||
</div>
|
||||
<i class="pi shrink-0 text-xs opacity-50 transition-transform duration-200"
|
||||
:class="faqAbertos[item.id] ? 'pi-chevron-up' : 'pi-chevron-down'" />
|
||||
</button>
|
||||
<Transition name="expand">
|
||||
<div v-if="faqAbertos[item.id] && item.resposta"
|
||||
class="faq-resposta ql-content" v-html="item.resposta" />
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="!sessionFaq.length && !globalFaq.length && !globalFaqLoading" class="empty-msg">
|
||||
<i class="pi pi-info-circle mr-1 opacity-40" />
|
||||
Ainda não há perguntas cadastradas.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* ── Painel ──────────────────────────────────────────────────── */
|
||||
.ajuda-panel {
|
||||
position: fixed;
|
||||
top: 0; right: 0;
|
||||
width: 400px;
|
||||
height: 100vh;
|
||||
z-index: 998;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--surface-card);
|
||||
border-left: 1px solid var(--surface-border);
|
||||
box-shadow: -4px 0 16px rgba(0,0,0,0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ajuda-slide-enter-active,
|
||||
.ajuda-slide-leave-active { transition: transform 0.3s ease; }
|
||||
.ajuda-slide-enter-from,
|
||||
.ajuda-slide-leave-to { transform: translateX(100%); }
|
||||
|
||||
/* ── Cabeçalho ──────────────────────────────────────────────── */
|
||||
.ajuda-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1rem 1.25rem;
|
||||
border-bottom: 1px solid var(--surface-border);
|
||||
flex-shrink: 0;
|
||||
background: var(--surface-card);
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.nav-btn, .close-btn {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
width: 28px; height: 28px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--surface-border);
|
||||
background: var(--surface-ground);
|
||||
color: var(--text-color-secondary);
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.close-btn { border: none; background: transparent; }
|
||||
.nav-btn:hover, .close-btn:hover { background: var(--surface-hover); }
|
||||
|
||||
.breadcrumb {
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-color-secondary);
|
||||
opacity: 0.65;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
/* ── Corpo ───────────────────────────────────────────────────── */
|
||||
.ajuda-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ── Home ────────────────────────────────────────────────────── */
|
||||
.home-welcome {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 1.75rem 1.25rem 1.25rem;
|
||||
gap: 0.625rem;
|
||||
}
|
||||
.home-icon {
|
||||
width: 56px; height: 56px;
|
||||
border-radius: 16px;
|
||||
background: color-mix(in srgb, var(--primary-color) 12%, transparent);
|
||||
color: var(--primary-color);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.home-welcome-text {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-color-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ── Seções ──────────────────────────────────────────────────── */
|
||||
.home-section {
|
||||
padding: 0 1.25rem 1.25rem;
|
||||
border-top: 1px solid var(--surface-border);
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.07em;
|
||||
color: var(--text-color-secondary);
|
||||
opacity: 0.7;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 0.625rem;
|
||||
}
|
||||
|
||||
.empty-msg {
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-color-secondary);
|
||||
opacity: 0.6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.375rem 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ── Cards de documento ──────────────────────────────────────── */
|
||||
.doc-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.doc-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
padding: 0.5rem 0.625rem;
|
||||
border-radius: 0.625rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.doc-card:hover { background: var(--surface-hover); }
|
||||
|
||||
.doc-card-icon {
|
||||
width: 28px; height: 28px;
|
||||
border-radius: 7px;
|
||||
background: color-mix(in srgb, var(--primary-color) 10%, transparent);
|
||||
color: var(--primary-color);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.doc-card-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.doc-card-titulo {
|
||||
font-size: 0.82rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.doc-card-cat {
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-color-secondary);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Carregar mais */
|
||||
.load-more-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.78rem;
|
||||
color: var(--primary-color);
|
||||
background: transparent;
|
||||
border: 1px dashed color-mix(in srgb, var(--primary-color) 30%, transparent);
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.load-more-btn:hover:not(:disabled) { background: color-mix(in srgb, var(--primary-color) 6%, transparent); }
|
||||
.load-more-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
/* ── Visualização de doc ─────────────────────────────────────── */
|
||||
.doc-view {
|
||||
padding: 1.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.doc-conteudo {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-color-secondary);
|
||||
line-height: 1.65;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* Quill styles compartilhados */
|
||||
.doc-conteudo.ql-content :deep(p),
|
||||
.faq-resposta.ql-content :deep(p) { margin: 0 0 0.5rem; }
|
||||
.doc-conteudo.ql-content :deep(p:last-child),
|
||||
.faq-resposta.ql-content :deep(p:last-child) { margin-bottom: 0; }
|
||||
.doc-conteudo.ql-content :deep(strong),
|
||||
.faq-resposta.ql-content :deep(strong) { font-weight: 600; color: var(--text-color); }
|
||||
.doc-conteudo.ql-content :deep(em),
|
||||
.faq-resposta.ql-content :deep(em) { font-style: italic; }
|
||||
.doc-conteudo.ql-content :deep(h1),
|
||||
.faq-resposta.ql-content :deep(h1) { font-size: 1.1rem; font-weight: 700; color: var(--text-color); margin: 0.75rem 0 0.35rem; }
|
||||
.doc-conteudo.ql-content :deep(h2),
|
||||
.faq-resposta.ql-content :deep(h2) { font-size: 1rem; font-weight: 700; color: var(--text-color); margin: 0.65rem 0 0.3rem; }
|
||||
.doc-conteudo.ql-content :deep(h3),
|
||||
.faq-resposta.ql-content :deep(h3) { font-size: 0.9rem; font-weight: 600; color: var(--text-color); margin: 0.5rem 0 0.25rem; }
|
||||
.doc-conteudo.ql-content :deep(ul),
|
||||
.faq-resposta.ql-content :deep(ul),
|
||||
.doc-conteudo.ql-content :deep(ol),
|
||||
.faq-resposta.ql-content :deep(ol) { padding-left: 1.25rem; margin: 0.4rem 0; }
|
||||
.doc-conteudo.ql-content :deep(li),
|
||||
.faq-resposta.ql-content :deep(li) { margin-bottom: 0.2rem; }
|
||||
.doc-conteudo.ql-content :deep(a),
|
||||
.faq-resposta.ql-content :deep(a) { color: var(--primary-color); text-decoration: underline; }
|
||||
.doc-conteudo.ql-content :deep(blockquote),
|
||||
.faq-resposta.ql-content :deep(blockquote) {
|
||||
border-left: 3px solid var(--surface-border);
|
||||
margin: 0.5rem 0; padding: 0.25rem 0.75rem;
|
||||
color: var(--text-color-secondary); font-style: italic;
|
||||
}
|
||||
.doc-conteudo.ql-content :deep(pre),
|
||||
.faq-resposta.ql-content :deep(pre) {
|
||||
background: var(--surface-ground);
|
||||
border-radius: 0.5rem; padding: 0.5rem 0.75rem;
|
||||
font-size: 0.8rem; overflow-x: auto; white-space: pre-wrap; word-break: break-all;
|
||||
}
|
||||
|
||||
/* ── Mídias ──────────────────────────────────────────────────── */
|
||||
.doc-img {
|
||||
width: 100%; max-width: 100%; height: auto;
|
||||
border-radius: 0.75rem; border: 1px solid var(--surface-border); display: block;
|
||||
}
|
||||
.doc-video-wrap {
|
||||
position: relative; width: 100%; padding-top: 56.25%;
|
||||
border-radius: 0.75rem; overflow: hidden; border: 1px solid var(--surface-border);
|
||||
}
|
||||
.doc-iframe { position: absolute; inset: 0; width: 100%; height: 100%; }
|
||||
|
||||
/* ── FAQ accordion ───────────────────────────────────────────── */
|
||||
.faq-section {
|
||||
border-top: 1px solid var(--surface-border);
|
||||
padding-top: 0.75rem;
|
||||
}
|
||||
.section-label {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.07em;
|
||||
color: var(--text-color-secondary);
|
||||
opacity: 0.65;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.faq-subsection-label {
|
||||
font-size: 0.68rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--text-color-secondary);
|
||||
opacity: 0.5;
|
||||
margin-bottom: 0.375rem;
|
||||
}
|
||||
.faq-list { display: flex; flex-direction: column; gap: 0.25rem; }
|
||||
.faq-item {
|
||||
border: 1px solid var(--surface-border);
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
background: var(--surface-ground);
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
.faq-item--open { border-color: color-mix(in srgb, var(--primary-color) 30%, transparent); }
|
||||
.faq-pergunta {
|
||||
width: 100%;
|
||||
display: flex; align-items: flex-start; justify-content: space-between;
|
||||
gap: 0.5rem; padding: 0.5rem 0.625rem;
|
||||
font-size: 0.8rem; font-weight: 500; color: var(--text-color);
|
||||
background: transparent; border: none; cursor: pointer; text-align: left;
|
||||
transition: background 0.15s; line-height: 1.4;
|
||||
}
|
||||
.faq-pergunta:hover { background: var(--surface-hover); }
|
||||
.faq-resposta {
|
||||
padding: 0 0.625rem 0.5rem;
|
||||
font-size: 0.8rem; color: var(--text-color-secondary);
|
||||
line-height: 1.6; word-break: break-word;
|
||||
}
|
||||
.faq-doc-label {
|
||||
font-size: 0.68rem;
|
||||
color: var(--text-color-secondary);
|
||||
opacity: 0.55;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* ── Relacionados ────────────────────────────────────────────── */
|
||||
.rel-section { border-top: 1px solid var(--surface-border); padding-top: 0.75rem; }
|
||||
.rel-btn {
|
||||
display: flex; align-items: center; gap: 0.5rem;
|
||||
padding: 0.5rem 0.625rem; border-radius: 0.5rem;
|
||||
font-size: 0.875rem; text-align: left;
|
||||
color: var(--primary-color); background: transparent;
|
||||
border: none; cursor: pointer; transition: background 0.15s; width: 100%;
|
||||
}
|
||||
.rel-btn span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
|
||||
.rel-btn:hover { background: var(--surface-hover); }
|
||||
|
||||
/* ── Votação ─────────────────────────────────────────────────── */
|
||||
.voto-section {
|
||||
border-top: 1px solid var(--surface-border);
|
||||
padding-top: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
}
|
||||
.voto-label {
|
||||
font-size: 0.78rem;
|
||||
color: var(--text-color-secondary);
|
||||
opacity: 0.75;
|
||||
}
|
||||
.voto-btns { display: flex; gap: 0.5rem; }
|
||||
.voto-btn {
|
||||
display: flex; align-items: center; gap: 0.375rem;
|
||||
padding: 0.375rem 0.875rem;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.8rem; font-weight: 500;
|
||||
border: 1px solid var(--surface-border);
|
||||
background: var(--surface-ground);
|
||||
color: var(--text-color-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.voto-btn:hover:not(:disabled) {
|
||||
border-color: var(--primary-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.voto-btn--ativo-sim {
|
||||
background: color-mix(in srgb, #22c55e 12%, transparent);
|
||||
border-color: #22c55e;
|
||||
color: #16a34a;
|
||||
}
|
||||
.voto-btn--ativo-nao {
|
||||
background: color-mix(in srgb, #ef4444 10%, transparent);
|
||||
border-color: #ef4444;
|
||||
color: #dc2626;
|
||||
}
|
||||
.voto-btn--loading { opacity: 0.5; cursor: not-allowed; }
|
||||
.voto-count {
|
||||
font-size: 0.72rem;
|
||||
opacity: 0.65;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* ── Expand transition ───────────────────────────────────────── */
|
||||
.expand-enter-active,
|
||||
.expand-leave-active {
|
||||
transition: opacity 0.2s ease, max-height 0.25s ease;
|
||||
max-height: 600px; overflow: hidden;
|
||||
}
|
||||
.expand-enter-from,
|
||||
.expand-leave-to { opacity: 0; max-height: 0; }
|
||||
</style>
|
||||
@@ -1,156 +0,0 @@
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
header: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
code: null,
|
||||
recent: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
free: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
containerClass: null,
|
||||
previewStyle: null
|
||||
});
|
||||
|
||||
const BlockView = reactive({
|
||||
PREVIEW: 0,
|
||||
CODE: 1
|
||||
});
|
||||
const blockView = ref(0);
|
||||
const codeCopied = ref(false);
|
||||
const codeCopyLoading = ref(false);
|
||||
|
||||
function activateView(event, blockViewValue) {
|
||||
blockView.value = blockViewValue;
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
async function copyCode(event) {
|
||||
if (codeCopied.value || codeCopyLoading.value) return;
|
||||
|
||||
codeCopyLoading.value = true;
|
||||
event.preventDefault();
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(props.code);
|
||||
codeCopyLoading.value = false;
|
||||
codeCopied.value = true;
|
||||
setTimeout(() => {
|
||||
codeCopied.value = false;
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
console.error('Clipboard write failed:', err);
|
||||
codeCopyLoading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-16 overflow-hidden">
|
||||
<div class="flex flex-col lg:flex-row justify-between py-4 gap-4 lg:gap-2 px-0!">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-xl">{{ header }}</span>
|
||||
<span v-if="free" class="flex items-center justify-center px-1.5 py-1 w-fit bg-emerald-500 text-emerald-100 dark:bg-emerald-400 dark:text-emerald-800 rounded-md leading-none! text-xs md:text-sm">Free</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Preview/Code Toggle -->
|
||||
<div class="inline-flex border border-surface-200 dark:border-surface-700 rounded-2xl overflow-hidden min-h-10">
|
||||
<button
|
||||
:class="[
|
||||
'min-w-28 flex items-center gap-1 justify-center px-4 py-2 rounded-2xl transition-all duration-200 font-medium cursor-pointer ',
|
||||
blockView === BlockView.CODE ? 'bg-primary text-primary-contrast ' : 'text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-0'
|
||||
]"
|
||||
@click="activateView($event, BlockView.CODE)"
|
||||
>
|
||||
<span>Code</span>
|
||||
</button>
|
||||
<button
|
||||
:class="[
|
||||
'min-w-28 flex items-center gap-1 justify-center px-4 py-2 rounded-2xl transition-all duration-200 font-medium cursor-pointer',
|
||||
blockView === BlockView.PREVIEW ? 'bg-primary text-primary-contrast ' : 'text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-0'
|
||||
]"
|
||||
@click="activateView($event, BlockView.PREVIEW)"
|
||||
>
|
||||
<span>Preview</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Separator -->
|
||||
<div class="h-6 w-px bg-surface-200 dark:bg-surface-700 hidden lg:block"></div>
|
||||
|
||||
<!-- Animated Copy Button -->
|
||||
<div class="flex items-center gap-2 grow lg:grow-0 justify-end lg:justify-start">
|
||||
<button
|
||||
@click="copyCode($event)"
|
||||
:disabled="codeCopyLoading"
|
||||
class="relative w-10 h-10 border border-surface-200 dark:border-surface-700 rounded-full hover:bg-surface-100 dark:hover:bg-surface-800 transition-all focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-surface-0 dark:focus-visible:ring-offset-surface-900 cursor-pointer disabled:cursor-wait"
|
||||
>
|
||||
<!-- Loading Spinner -->
|
||||
<span :class="['absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transition-all duration-300 leading-none', codeCopyLoading ? 'opacity-100 scale-100 z-10' : 'opacity-0 scale-50 -z-[2]']">
|
||||
<i class="pi pi-spinner animate-spin text-surface-700 dark:text-surface-300" style="font-size: 1.25rem"></i>
|
||||
</span>
|
||||
|
||||
<!-- Checkmark Icon -->
|
||||
<span :class="['absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transition-all duration-300 leading-none', codeCopied && !codeCopyLoading ? 'opacity-100 scale-100 z-10' : 'opacity-0 scale-50 -z-[2]']">
|
||||
<svg class="w-5 h-5 fill-green-600 dark:fill-green-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<g id="check">
|
||||
<path d="M9,18.25A.74.74,0,0,1,8.47,18l-5-5A.75.75,0,1,1,4.53,12L9,16.44,19.47,6A.75.75,0,0,1,20.53,7l-11,11A.74.74,0,0,1,9,18.25Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<!-- Copy Icon -->
|
||||
<span :class="['absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transition-all duration-300 leading-none', !codeCopied && !codeCopyLoading ? 'opacity-100 scale-100 z-10' : 'opacity-0 scale-50 -z-[2]']">
|
||||
<svg class="w-5 h-5 fill-surface-700 dark:fill-surface-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<g id="clone">
|
||||
<path
|
||||
d="M14,16.75H6A2.75,2.75,0,0,1,3.25,14V6A2.75,2.75,0,0,1,6,3.25h8A2.75,2.75,0,0,1,16.75,6v8A2.75,2.75,0,0,1,14,16.75Zm-8-12A1.25,1.25,0,0,0,4.75,6v8A1.25,1.25,0,0,0,6,15.25h8A1.25,1.25,0,0,0,15.25,14V6A1.25,1.25,0,0,0,14,4.75Z"
|
||||
></path>
|
||||
<path d="M18,20.75H10A2.75,2.75,0,0,1,7.25,18V16h1.5v2A1.25,1.25,0,0,0,10,19.25h8A1.25,1.25,0,0,0,19.25,18V10A1.25,1.25,0,0,0,18,8.75H16V7.25h2A2.75,2.75,0,0,1,20.75,10v8A2.75,2.75,0,0,1,18,20.75Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-0 border border-surface-200 dark:border-surface-700 rounded-xl overflow-hidden">
|
||||
<div :class="containerClass" :style="previewStyle" v-if="blockView == BlockView.PREVIEW">
|
||||
<slot />
|
||||
</div>
|
||||
<div v-if="blockView === BlockView.CODE">
|
||||
<pre class="app-code"><code>{{code}}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
pre {
|
||||
border: 0 none !important;
|
||||
border-radius: 0 !important;
|
||||
.app-code {
|
||||
background: var(--p-surface-900) !important;
|
||||
margin: 0 !important;
|
||||
border: 0 none !important;
|
||||
&:before,
|
||||
&:after {
|
||||
display: none !important;
|
||||
}
|
||||
code {
|
||||
color: var(--p-surface-50);
|
||||
padding: 1rem;
|
||||
line-height: 1.5;
|
||||
display: block;
|
||||
font-family: monaco, Consolas, monospace;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,21 +0,0 @@
|
||||
<script setup>
|
||||
import AppConfigurator from '@/layout/AppConfigurator.vue';
|
||||
import { useLayout } from '@/layout/composables/layout';
|
||||
|
||||
const { toggleDarkMode, isDarkTheme } = useLayout();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="fixed flex gap-4 top-8 right-8">
|
||||
<Button type="button" @click="toggleDarkMode" rounded :icon="isDarkTheme ? 'pi pi-moon' : 'pi pi-sun'" severity="secondary" />
|
||||
<div class="relative">
|
||||
<Button
|
||||
icon="pi pi-palette"
|
||||
v-styleclass="{ selector: '@next', enterFromClass: 'hidden', enterActiveClass: 'animate-scalein', leaveToClass: 'hidden', leaveActiveClass: 'animate-fadeout', hideOnOutsideClick: true }"
|
||||
type="button"
|
||||
rounded
|
||||
/>
|
||||
<AppConfigurator />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -221,7 +221,7 @@ function saveCustom() {
|
||||
<div class="flex gap-3">
|
||||
<div class="flex-1 flex flex-col gap-1">
|
||||
<label class="text-xs text-[var(--text-color-secondary)]">Início</label>
|
||||
<DatePicker v-model="form.inicio" showIcon fluid iconDisplay="input" timeOnly hourFormat="24">
|
||||
<DatePicker v-model="form.inicio" showIcon fluid iconDisplay="input" timeOnly hourFormat="24" :stepMinute="15" :manualInput="false">
|
||||
<template #inputicon="slotProps">
|
||||
<i class="pi pi-clock" @click="slotProps.clickCallback" />
|
||||
</template>
|
||||
@@ -229,7 +229,7 @@ function saveCustom() {
|
||||
</div>
|
||||
<div class="flex-1 flex flex-col gap-1">
|
||||
<label class="text-xs text-[var(--text-color-secondary)]">Fim</label>
|
||||
<DatePicker v-model="form.fim" showIcon fluid iconDisplay="input" timeOnly hourFormat="24">
|
||||
<DatePicker v-model="form.fim" showIcon fluid iconDisplay="input" timeOnly hourFormat="24" :stepMinute="15" :manualInput="false">
|
||||
<template #inputicon="slotProps">
|
||||
<i class="pi pi-clock" @click="slotProps.clickCallback" />
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user