Documentos Pacientes, Template Documentos Pacientes Saas, Documentos prontuários, Documentos Externos, Visualização Externa, Permissão de Visualização, Render Otimização

This commit is contained in:
Leonardo
2026-03-30 14:08:19 -03:00
parent 0658e2e9bf
commit d088a89fb7
112 changed files with 115867 additions and 5266 deletions
@@ -15,18 +15,18 @@
|--------------------------------------------------------------------------
-->
<script setup>
import { computed, ref, watch, onMounted, nextTick } from 'vue';
import { supabase } from '@/lib/supabase/client';
import { useTenantStore } from '@/stores/tenantStore';
import { computed, nextTick, onMounted, ref, watch } from 'vue';
import { useToast } from 'primevue/usetoast';
import DatePicker from 'primevue/datepicker';
import { useToast } from 'primevue/usetoast';
import PausasChipsEditor from '@/components/agenda/PausasChipsEditor.vue';
import FullCalendar from '@fullcalendar/vue3';
import timeGridPlugin from '@fullcalendar/timegrid';
import ptBrLocale from '@fullcalendar/core/locales/pt-br';
import timeGridPlugin from '@fullcalendar/timegrid';
import FullCalendar from '@fullcalendar/vue3';
const toast = useToast();
const tenantStore = useTenantStore();
@@ -1385,7 +1385,7 @@ const jornadaEndDate = computed({
<div class="anim-child [--delay:120ms] xl:w-[42%] xl:top-4 xl:self-start">
<div class="rounded-[6px] border border-[var(--surface-border)] bg-[var(--surface-card)] overflow-hidden shadow-sm agenda-altura">
<!-- Header do preview -->
<div class="sticky top-0 z-10 bg-white">
<div class="sticky top-0 z-10">
<div class="flex items-center justify-between px-4 py-3 border-b border-[var(--surface-border)]">
<div class="font-semibold text-sm">Preview da agenda</div>
<div class="flex gap-1">
@@ -19,7 +19,7 @@ import { ref, computed, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { useToast } from 'primevue/usetoast';
import { useConfirm } from 'primevue/useconfirm';
import Editor from 'primevue/editor';
import JoditEmailEditor from '@/components/ui/JoditEmailEditor.vue';
import { supabase } from '@/lib/supabase/client';
import { renderEmail, renderTemplate, generateLayoutSection } from '@/lib/email/emailTemplateService';
import { MOCK_DATA, TEMPLATE_DOMAINS } from '@/lib/email/emailTemplateConstants';
@@ -135,19 +135,6 @@ const DOMAIN_SEVERITY = { session: 'info', intake: 'warning', billing: 'success'
// ── Dialog layout (header/footer global) ──────────────────────
const layoutDlg = ref({ open: false, saving: false });
const layoutForm = ref({ header: defaultSection(), footer: defaultSection() });
const headerEditorRef = ref(null);
const footerEditorRef = ref(null);
const LAYOUT_OPTIONS = [
{ value: 'logo-left', label: 'Logo à esquerda' },
{ value: 'logo-right', label: 'Logo à direita' },
{ value: 'logo-center', label: 'Logo centralizada' }
];
const TEXT_OPTIONS = [
{ value: 'text-left', label: 'Texto à esquerda' },
{ value: 'text-center', label: 'Texto centralizado' },
{ value: 'text-right', label: 'Texto à direita' }
];
function openLayoutDlg() {
layoutForm.value = {
@@ -157,10 +144,6 @@ function openLayoutDlg() {
layoutDlg.value = { open: true, saving: false };
}
function selectLayout(which, type) {
layoutForm.value[which].layout = type;
}
const headerLayoutPreview = computed(() => generateLayoutSection(layoutForm.value.header, profileLogoUrl.value, true));
const footerLayoutPreview = computed(() => generateLayoutSection(layoutForm.value.footer, profileLogoUrl.value, false));
@@ -206,7 +189,6 @@ function openEdit(row) {
subject: ov?.subject ?? row.subject,
body_html: ov?.body_html ?? row.body_html,
body_text: ov?.body_text ?? '',
enabled: ov?.enabled ?? true,
synced_version: row.version,
variables: row.variables || {}
};
@@ -225,15 +207,11 @@ const formVariables = computed(() => {
function insertVar(varName) {
const snippet = `{{${varName}}}`;
const quill = editorRef.value?.quill;
if (!quill) {
if (editorRef.value?.insertHTML) {
editorRef.value.insertHTML(snippet);
} else {
form.value.body_html = (form.value.body_html || '') + snippet;
return;
}
const range = quill.getSelection(true);
const index = range ? range.index : quill.getLength() - 1;
quill.insertText(index, snippet, 'user');
quill.setSelection(index + snippet.length, 0);
}
async function save() {
@@ -251,7 +229,7 @@ async function save() {
subject: form.value.use_custom_subject ? form.value.subject : null,
body_html: form.value.use_custom_body ? form.value.body_html : null,
body_text: form.value.use_custom_body && form.value.body_text ? form.value.body_text : null,
enabled: form.value.enabled,
enabled: true,
synced_version: form.value.synced_version
};
if (dlg.value.mode === 'create') {
@@ -323,9 +301,19 @@ onMounted(async () => {
<template>
<div class="flex flex-col gap-4">
<!-- Filtro -->
<div class="flex gap-2 flex-wrap">
<!-- Filtro + Layout global -->
<div class="flex items-center gap-2 flex-wrap">
<Button v-for="opt in DOMAIN_OPTIONS" :key="String(opt.value)" :label="opt.label" size="small" :severity="filterDomain === opt.value ? 'primary' : 'secondary'" :outlined="filterDomain !== opt.value" @click="filterDomain = opt.value" />
<div class="ml-auto">
<Button
label="Layout global"
icon="pi pi-palette"
size="small"
:severity="layoutActive ? 'success' : 'secondary'"
:outlined="!layoutActive"
@click="openLayoutDlg"
/>
</div>
</div>
<!-- SKELETON -->
@@ -394,88 +382,21 @@ onMounted(async () => {
</div>
<div v-if="layoutForm.header.enabled" class="px-4 py-4 flex flex-col gap-4">
<!-- Cards de layout -->
<div class="flex flex-col gap-2">
<p class="text-xs font-semibold">Com logo</p>
<div class="flex gap-2">
<button v-for="opt in LAYOUT_OPTIONS" :key="opt.value" class="layout-card" :class="{ 'layout-card--active': layoutForm.header.layout === opt.value }" @click="selectLayout('header', opt.value)">
<div class="layout-card__thumb">
<template v-if="opt.value === 'logo-left'">
<div class="lc-logo" />
<div class="lc-spacer" />
<div class="lc-lines">
<div class="lc-line" />
<div class="lc-line short" />
</div>
</template>
<template v-else-if="opt.value === 'logo-right'">
<div class="lc-lines">
<div class="lc-line" />
<div class="lc-line short" />
</div>
<div class="lc-spacer" />
<div class="lc-logo" />
</template>
<template v-else>
<div class="lc-center">
<div class="lc-logo" />
<div class="lc-line" style="width: 70%; margin-top: 5px" />
</div>
</template>
</div>
<span class="layout-card__label">{{ opt.label }}</span>
</button>
</div>
<p class="text-xs font-semibold mt-1"> texto</p>
<div class="flex gap-2">
<button v-for="opt in TEXT_OPTIONS" :key="opt.value" class="layout-card" :class="{ 'layout-card--active': layoutForm.header.layout === opt.value }" @click="selectLayout('header', opt.value)">
<div class="layout-card__thumb">
<template v-if="opt.value === 'text-left'">
<div class="lc-lines">
<div class="lc-line" />
<div class="lc-line short" />
</div>
</template>
<template v-else-if="opt.value === 'text-center'">
<div class="lc-lines lc-lines--center">
<div class="lc-line" style="width: 85%" />
<div class="lc-line short" style="width: 55%; align-self: center" />
</div>
</template>
<template v-else>
<div class="lc-lines lc-lines--right">
<div class="lc-line" />
<div class="lc-line short" style="align-self: flex-end" />
</div>
</template>
</div>
<span class="layout-card__label">{{ opt.label }}</span>
</button>
</div>
</div>
<!-- Editor de texto -->
<div class="flex flex-col gap-1.5">
<label class="text-xs font-semibold">Texto</label>
<Editor ref="headerEditorRef" v-model="layoutForm.header.content" editor-style="min-height:100px;font-size:0.85rem;">
<template #toolbar>
<span class="ql-formats">
<button class="ql-bold" type="button" />
<button class="ql-italic" type="button" />
<button class="ql-underline" type="button" />
</span>
<span class="ql-formats">
<select class="ql-color" />
</span>
</template>
</Editor>
</div>
<!-- Editor Jodit use os botões " Logo Esq./Dir./Centro" na toolbar para inserir layouts prontos -->
<JoditEmailEditor
v-model="layoutForm.header.content"
:min-height="160"
:layout-buttons="true"
:logo-url="profileLogoUrl"
/>
<!-- Preview -->
<div v-if="headerLayoutPreview" class="rounded border border-[var(--surface-border)] bg-white p-3 text-sm text-gray-800">
<span class="text-[0.65rem] text-gray-400 uppercase tracking-widest block mb-2">Preview</span>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-html="headerLayoutPreview" />
<div v-if="headerLayoutPreview" class="rounded-lg border border-(--surface-border) bg-(--surface-ground) p-3">
<span class="text-[0.65rem] text-(--text-color-secondary) uppercase tracking-widest block mb-2">Preview</span>
<div class="rounded border border-(--surface-border) bg-white p-3 text-sm text-gray-800 shadow-sm">
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-html="headerLayoutPreview" />
</div>
</div>
</div>
</div>
@@ -494,88 +415,21 @@ onMounted(async () => {
</div>
<div v-if="layoutForm.footer.enabled" class="px-4 py-4 flex flex-col gap-4">
<!-- Cards de layout -->
<div class="flex flex-col gap-2">
<p class="text-xs font-semibold">Com logo</p>
<div class="flex gap-2">
<button v-for="opt in LAYOUT_OPTIONS" :key="opt.value" class="layout-card" :class="{ 'layout-card--active': layoutForm.footer.layout === opt.value }" @click="selectLayout('footer', opt.value)">
<div class="layout-card__thumb">
<template v-if="opt.value === 'logo-left'">
<div class="lc-logo" />
<div class="lc-spacer" />
<div class="lc-lines">
<div class="lc-line" />
<div class="lc-line short" />
</div>
</template>
<template v-else-if="opt.value === 'logo-right'">
<div class="lc-lines">
<div class="lc-line" />
<div class="lc-line short" />
</div>
<div class="lc-spacer" />
<div class="lc-logo" />
</template>
<template v-else>
<div class="lc-center">
<div class="lc-logo" />
<div class="lc-line" style="width: 70%; margin-top: 5px" />
</div>
</template>
</div>
<span class="layout-card__label">{{ opt.label }}</span>
</button>
</div>
<p class="text-xs font-semibold mt-1"> texto</p>
<div class="flex gap-2">
<button v-for="opt in TEXT_OPTIONS" :key="opt.value" class="layout-card" :class="{ 'layout-card--active': layoutForm.footer.layout === opt.value }" @click="selectLayout('footer', opt.value)">
<div class="layout-card__thumb">
<template v-if="opt.value === 'text-left'">
<div class="lc-lines">
<div class="lc-line" />
<div class="lc-line short" />
</div>
</template>
<template v-else-if="opt.value === 'text-center'">
<div class="lc-lines lc-lines--center">
<div class="lc-line" style="width: 85%" />
<div class="lc-line short" style="width: 55%; align-self: center" />
</div>
</template>
<template v-else>
<div class="lc-lines lc-lines--right">
<div class="lc-line" />
<div class="lc-line short" style="align-self: flex-end" />
</div>
</template>
</div>
<span class="layout-card__label">{{ opt.label }}</span>
</button>
</div>
</div>
<!-- Editor de texto -->
<div class="flex flex-col gap-1.5">
<label class="text-xs font-semibold">Texto</label>
<Editor ref="footerEditorRef" v-model="layoutForm.footer.content" editor-style="min-height:100px;font-size:0.85rem;">
<template #toolbar>
<span class="ql-formats">
<button class="ql-bold" type="button" />
<button class="ql-italic" type="button" />
<button class="ql-underline" type="button" />
</span>
<span class="ql-formats">
<select class="ql-color" />
</span>
</template>
</Editor>
</div>
<!-- Editor Jodit use os botões " Logo Esq./Dir./Centro" na toolbar para inserir layouts prontos -->
<JoditEmailEditor
v-model="layoutForm.footer.content"
:min-height="160"
:layout-buttons="true"
:logo-url="profileLogoUrl"
/>
<!-- Preview -->
<div v-if="footerLayoutPreview" class="rounded border border-[var(--surface-border)] bg-white p-3 text-sm text-gray-800">
<span class="text-[0.65rem] text-gray-400 uppercase tracking-widest block mb-2">Preview</span>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-html="footerLayoutPreview" />
<div v-if="footerLayoutPreview" class="rounded-lg border border-(--surface-border) bg-(--surface-ground) p-3">
<span class="text-[0.65rem] text-(--text-color-secondary) uppercase tracking-widest block mb-2">Preview</span>
<div class="rounded border border-(--surface-border) bg-white p-3 text-sm text-gray-800 shadow-sm">
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-html="footerLayoutPreview" />
</div>
</div>
</div>
</div>
@@ -616,35 +470,7 @@ onMounted(async () => {
</div>
<div class="px-4 py-3 flex flex-col gap-3">
<template v-if="form.use_custom_body">
<Editor ref="editorRef" v-model="form.body_html" editor-style="min-height:200px;font-size:0.85rem;">
<template #toolbar>
<span class="ql-formats">
<select class="ql-header">
<option value="1">Título</option>
<option value="2">Subtítulo</option>
<option selected>Normal</option>
</select>
</span>
<span class="ql-formats">
<button class="ql-bold" type="button" />
<button class="ql-italic" type="button" />
<button class="ql-underline" type="button" />
</span>
<span class="ql-formats">
<select class="ql-align" />
<select class="ql-color" />
<select class="ql-background" />
</span>
<span class="ql-formats">
<button class="ql-list" value="ordered" type="button" />
<button class="ql-list" value="bullet" type="button" />
</span>
<span class="ql-formats">
<button class="ql-link" type="button" />
<button class="ql-clean" type="button" />
</span>
</template>
</Editor>
<JoditEmailEditor ref="editorRef" v-model="form.body_html" :min-height="220" />
<div class="flex flex-col gap-1.5">
<span class="text-xs text-[var(--text-color-secondary)]">Inserir variável no cursor:</span>
<div class="flex flex-wrap gap-1.5">
@@ -660,11 +486,6 @@ onMounted(async () => {
</div>
</div>
<!-- Override ativo -->
<div class="flex items-center gap-2">
<ToggleSwitch v-model="form.enabled" inputId="sw-enabled" />
<label for="sw-enabled" class="text-sm cursor-pointer select-none">Override ativo</label>
</div>
</div>
<template #footer>
@@ -699,104 +520,4 @@ onMounted(async () => {
</template>
<style scoped>
/* ── Layout cards ───────────────────────────────────────── */
.layout-card {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 10px 8px;
border: 1.5px solid var(--surface-border);
border-radius: 8px;
background: var(--surface-card);
cursor: pointer;
transition:
border-color 0.15s,
box-shadow 0.15s,
background 0.15s;
}
.layout-card:hover {
border-color: color-mix(in srgb, var(--primary-color, #6366f1) 50%, transparent);
background: color-mix(in srgb, var(--primary-color, #6366f1) 4%, var(--surface-card));
}
.layout-card--active {
border-color: var(--primary-color, #6366f1);
background: color-mix(in srgb, var(--primary-color, #6366f1) 8%, var(--surface-card));
box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color, #6366f1) 15%, transparent);
}
.layout-card__thumb {
display: flex;
align-items: center;
gap: 5px;
width: 100%;
height: 38px;
border: 1px solid #e5e7eb;
border-radius: 5px;
padding: 6px;
background: #f9fafb;
}
.layout-card__label {
font-size: 0.72rem;
font-weight: 600;
color: var(--text-color-secondary);
text-align: center;
line-height: 1.3;
}
.layout-card--active .layout-card__label {
color: var(--primary-color, #6366f1);
}
/* Elementos internos dos cards */
.lc-logo {
width: 22px;
height: 22px;
border-radius: 3px;
flex-shrink: 0;
background: color-mix(in srgb, var(--primary-color, #6366f1) 35%, #e5e7eb);
border: 1px solid color-mix(in srgb, var(--primary-color, #6366f1) 20%, transparent);
}
.lc-spacer {
flex: 1;
min-width: 4px;
}
.lc-lines {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
}
.lc-lines--center {
align-items: center;
}
.lc-lines--right {
align-items: flex-end;
}
.lc-line {
height: 3px;
background: #d1d5db;
border-radius: 2px;
}
.lc-line.short {
width: 60%;
}
.lc-center {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
gap: 2px;
}
.lc-center .lc-logo {
width: 18px;
height: 18px;
}
.lc-center .lc-line {
width: 100%;
}
/* Esconde botão de imagem do Quill em todos os editores desta página */
:deep(.ql-image) {
display: none !important;
}
</style>
@@ -378,6 +378,42 @@ function confirmRestoreTemplate(tpl) {
});
}
// ══════════════════════════════════════════════════════════════
// ABA 2 — Emojis rápidos para o guia de formatação
// ══════════════════════════════════════════════════════════════
const QUICK_EMOJIS = [
{ char: '📅', label: 'Calendário' },
{ char: '⏰', label: 'Relógio / Lembrete' },
{ char: '✅', label: 'Confirmado' },
{ char: '❌', label: 'Cancelado' },
{ char: '🔔', label: 'Notificação' },
{ char: '💬', label: 'Mensagem' },
{ char: '💙', label: 'Cuidado / Saúde' },
{ char: '🌿', label: 'Bem-estar' },
{ char: '🤝', label: 'Parceria / Encontro' },
{ char: '📋', label: 'Formulário / Triagem' },
{ char: '💰', label: 'Financeiro' },
{ char: '🔗', label: 'Link' },
{ char: '📍', label: 'Local' },
{ char: '📞', label: 'Telefone' },
{ char: '🏥', label: 'Clínica' },
{ char: '🧠', label: 'Psicologia' },
{ char: '👤', label: 'Paciente' },
{ char: '🌟', label: 'Destaque' },
{ char: '⚠️', label: 'Atenção' },
{ char: '➡️', label: 'Seta / Próximo passo' },
{ char: '🗓️', label: 'Agenda' },
{ char: '🕐', label: 'Hora' },
{ char: '📩', label: 'Recebido' },
{ char: '🔄', label: 'Reagendamento' }
];
function copyEmoji(char) {
navigator.clipboard?.writeText(char).catch(() => {});
toast.add({ severity: 'info', summary: `${char} copiado!`, life: 1500 });
}
// ══════════════════════════════════════════════════════════════
// ABA 3 — Logs de envio
// ══════════════════════════════════════════════════════════════
@@ -533,47 +569,152 @@ onBeforeUnmount(() => {
<!-- ABA 2 Templates -->
<TabPanel :value="1">
<div class="flex flex-col gap-3 pt-3">
<!-- Skeleton loading -->
<template v-if="templatesLoading">
<div v-for="n in 4" :key="n" class="border border-[var(--surface-border)] rounded-xl bg-[var(--surface-card)] p-4">
<div class="flex items-center gap-2 mb-3">
<Skeleton width="5rem" height="1.4rem" border-radius="999px" />
<Skeleton width="10rem" height="1rem" />
<div class="flex gap-4 pt-3 items-start">
<!-- Coluna esquerda: cards de templates (65%) -->
<div class="flex flex-col gap-3 min-w-0" style="flex: 0 0 65%;">
<!-- Skeleton loading -->
<template v-if="templatesLoading">
<div v-for="n in 4" :key="n" class="border border-[var(--surface-border)] rounded-xl bg-[var(--surface-card)] p-4">
<div class="flex items-center gap-2 mb-3">
<Skeleton width="5rem" height="1.4rem" border-radius="999px" />
<Skeleton width="10rem" height="1rem" />
</div>
<Skeleton width="100%" height="5rem" class="mb-2" />
<div class="flex gap-1">
<Skeleton v-for="i in 3" :key="i" width="6rem" height="1.6rem" border-radius="999px" />
</div>
</div>
<Skeleton width="100%" height="5rem" class="mb-2" />
<div class="flex gap-1">
<Skeleton v-for="i in 3" :key="i" width="6rem" height="1.6rem" border-radius="999px" />
</template>
<!-- Cards de templates -->
<div v-else v-for="tpl in templates" :key="tpl.key" class="border border-[var(--surface-border)] rounded-xl bg-[var(--surface-card)] p-4 flex flex-col gap-3">
<!-- Header do card -->
<div class="flex items-center gap-2 flex-wrap">
<span class="font-semibold text-sm">{{ tpl.label }}</span>
<Tag :value="tpl.type" :severity="tpl.type_severity" class="text-[0.65rem]" />
<Tag v-if="tpl.is_custom" value="Personalizado" severity="success" class="text-[0.65rem]" />
</div>
</div>
</template>
<!-- Cards de templates -->
<div v-else v-for="tpl in templates" :key="tpl.key" class="border border-[var(--surface-border)] rounded-xl bg-[var(--surface-card)] p-4 flex flex-col gap-3">
<!-- Header do card -->
<div class="flex items-center gap-2 flex-wrap">
<span class="font-semibold text-sm">{{ tpl.label }}</span>
<Tag :value="tpl.type" :severity="tpl.type_severity" class="text-[0.65rem]" />
<Tag v-if="tpl.is_custom" value="Personalizado" severity="success" class="text-[0.65rem]" />
</div>
<!-- Textarea editável -->
<Textarea :ref="(el) => setTextareaRef(tpl.key, el)" v-model="tpl.body_text" rows="5" auto-resize class="w-full text-sm font-mono" />
<!-- Textarea editável -->
<Textarea :ref="(el) => setTextareaRef(tpl.key, el)" v-model="tpl.body_text" rows="5" auto-resize class="w-full text-sm font-mono" />
<!-- Variáveis clicáveis -->
<div class="flex flex-col gap-1.5">
<span class="text-xs text-[var(--text-color-secondary)]">Inserir variável no cursor:</span>
<div class="flex flex-wrap gap-1.5">
<Button v-for="v in tpl.variables" :key="v" :label="`{{${v}}}`" size="small" severity="secondary" outlined class="font-mono !text-[0.68rem] !py-1 !px-2" @click="insertVariable(tpl.key, v)" />
<!-- Variáveis clicáveis -->
<div class="flex flex-col gap-1.5">
<span class="text-xs text-[var(--text-color-secondary)]">Inserir variável no cursor:</span>
<div class="flex flex-wrap gap-1.5">
<Button v-for="v in tpl.variables" :key="v" :label="`{{${v}}}`" size="small" severity="secondary" outlined class="font-mono !text-[0.68rem] !py-1 !px-2" @click="insertVariable(tpl.key, v)" />
</div>
</div>
</div>
<!-- Ações -->
<div class="flex items-center gap-2 justify-end">
<Button v-if="isTemplateModified(tpl)" label="Restaurar original" icon="pi pi-undo" size="small" severity="secondary" outlined @click="confirmRestoreTemplate(tpl)" />
<Button label="Salvar" icon="pi pi-check" size="small" :loading="templateSaving[tpl.key]" :disabled="templateSaving[tpl.key]" @click="saveTemplate(tpl)" />
<!-- Ações -->
<div class="flex items-center gap-2 justify-end">
<Button v-if="isTemplateModified(tpl)" label="Restaurar original" icon="pi pi-undo" size="small" severity="secondary" outlined @click="confirmRestoreTemplate(tpl)" />
<Button label="Salvar" icon="pi pi-check" size="small" :loading="templateSaving[tpl.key]" :disabled="templateSaving[tpl.key]" @click="saveTemplate(tpl)" />
</div>
</div>
</div>
<!-- Coluna direita: guia de formatação (35%) -->
<div class="flex flex-col gap-3 sticky top-4" style="flex: 0 0 35%;">
<div class="border border-[var(--surface-border)] rounded-xl bg-[var(--surface-card)] p-4 flex flex-col gap-4">
<div class="flex items-center gap-2">
<i class="pi pi-book text-[var(--primary-color)]" />
<span class="font-semibold text-sm">Guia de formatação</span>
</div>
<!-- Formatação oficial -->
<div class="flex flex-col gap-2">
<div class="flex items-center gap-1.5 mb-1">
<span class="text-[0.7rem] font-semibold uppercase tracking-wider text-[var(--text-color-secondary)]">Formatação oficial</span>
<div class="flex-1 h-px bg-[var(--surface-border)]" />
</div>
<div class="flex flex-col gap-1.5">
<div class="flex items-center justify-between gap-2">
<span class="font-mono text-xs bg-[var(--surface-ground)] px-2 py-0.5 rounded text-[var(--text-color-secondary)]">*texto*</span>
<span class="text-xs font-bold">Negrito</span>
</div>
<div class="flex items-center justify-between gap-2">
<span class="font-mono text-xs bg-[var(--surface-ground)] px-2 py-0.5 rounded text-[var(--text-color-secondary)]">_texto_</span>
<span class="text-xs italic">Itálico</span>
</div>
<div class="flex items-center justify-between gap-2">
<span class="font-mono text-xs bg-[var(--surface-ground)] px-2 py-0.5 rounded text-[var(--text-color-secondary)]">~texto~</span>
<span class="text-xs line-through">Tachado</span>
</div>
<div class="flex items-center justify-between gap-2">
<span class="font-mono text-xs bg-[var(--surface-ground)] px-2 py-0.5 rounded text-[var(--text-color-secondary)]">`texto`</span>
<span class="text-xs font-mono bg-[var(--surface-ground)] px-1 rounded">Monoespaçado</span>
</div>
</div>
</div>
<!-- Efeitos extras Unicode -->
<div class="flex flex-col gap-2">
<div class="flex items-center gap-1.5 mb-1">
<span class="text-[0.7rem] font-semibold uppercase tracking-wider text-[var(--text-color-secondary)]">Efeitos extras (Unicode)</span>
<div class="flex-1 h-px bg-[var(--surface-border)]" />
</div>
<div class="flex flex-col gap-2">
<div class="flex flex-col gap-0.5">
<div class="flex items-center justify-between gap-2">
<span class="text-xs text-[var(--text-color-secondary)]">Negrito Unicode</span>
<span class="text-xs">𝙝𝙤𝙡𝙖</span>
</div>
<span class="text-[0.65rem] text-[var(--text-color-secondary)] opacity-70">Copie de sites de "font generator"</span>
</div>
<div class="flex flex-col gap-0.5">
<div class="flex items-center justify-between gap-2">
<span class="text-xs text-[var(--text-color-secondary)]">Cursiva Unicode</span>
<span class="text-xs">𝓽𝓮𝔁𝓽𝓸</span>
</div>
<span class="text-[0.65rem] text-[var(--text-color-secondary)] opacity-70">Cada letra é um caractere diferente</span>
</div>
<div class="flex flex-col gap-0.5">
<div class="flex items-center justify-between gap-2">
<span class="text-xs text-[var(--text-color-secondary)]">Small Caps</span>
<span class="text-xs">ᴛᴇxᴛᴏ</span>
</div>
<span class="text-[0.65rem] text-[var(--text-color-secondary)] opacity-70">Bom para títulos curtos</span>
</div>
<div class="flex flex-col gap-0.5">
<div class="flex items-center justify-between gap-2">
<span class="text-xs text-[var(--text-color-secondary)]">Sublinhado</span>
<span class="text-xs">t̲e̲x̲t̲o̲</span>
</div>
<span class="text-[0.65rem] text-[var(--text-color-secondary)] opacity-70">U+0332 após cada letra</span>
</div>
</div>
</div>
<!-- Emojis mais usados -->
<div class="flex flex-col gap-2">
<div class="flex items-center gap-1.5 mb-1">
<span class="text-[0.7rem] font-semibold uppercase tracking-wider text-[var(--text-color-secondary)]">Emojis mais usados</span>
<div class="flex-1 h-px bg-[var(--surface-border)]" />
</div>
<div class="flex flex-wrap gap-1">
<button
v-for="emoji in QUICK_EMOJIS"
:key="emoji.char"
v-tooltip.top="emoji.label"
class="text-base leading-none p-1 rounded hover:bg-[var(--surface-hover)] transition-colors cursor-pointer border-0 bg-transparent"
@click="copyEmoji(emoji.char)"
>{{ emoji.char }}</button>
</div>
<span class="text-[0.65rem] text-[var(--text-color-secondary)] opacity-70">Clique para copiar</span>
</div>
<!-- Dica -->
<div class="flex items-start gap-2 px-3 py-2.5 rounded-lg bg-[var(--surface-ground)] border border-[var(--surface-border)]">
<i class="pi pi-lightbulb text-amber-500 text-xs mt-0.5 shrink-0" />
<p class="text-[0.68rem] text-[var(--text-color-secondary)] m-0 leading-relaxed">
Use <strong>*negrito*</strong> para destacar horários e datas. Evite excesso de formatação mensagens simples têm maior taxa de leitura.
</p>
</div>
</div>
</div>
</div>
</TabPanel>