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:
@@ -0,0 +1,207 @@
|
||||
<!--
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Criado e desenvolvido por Leonardo Nohama
|
||||
|
|
||||
| Arquivo: src/features/documents/components/DocumentTemplateEditor.vue
|
||||
| Editor de template: edicao HTML, insercao de variaveis, preview ao vivo.
|
||||
|--------------------------------------------------------------------------
|
||||
-->
|
||||
<script setup>
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { useDocumentTemplates } from '../composables/useDocumentTemplates'
|
||||
import JoditEmailEditor from '@/components/ui/JoditEmailEditor.vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Object, default: () => ({}) },
|
||||
mode: { type: String, default: 'create' } // create | edit
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'save', 'cancel'])
|
||||
|
||||
const { TIPOS_TEMPLATE, TEMPLATE_VARIABLES, variablesGrouped, previewHtml } = useDocumentTemplates()
|
||||
|
||||
const activeTab = ref('editor') // editor | preview
|
||||
|
||||
// ── Form reativo synced com modelValue ──────────────────────
|
||||
|
||||
const form = ref({ ...defaultForm(), ...props.modelValue })
|
||||
|
||||
function defaultForm() {
|
||||
return {
|
||||
nome_template: '',
|
||||
tipo: 'outro',
|
||||
descricao: '',
|
||||
corpo_html: '',
|
||||
cabecalho_html: '',
|
||||
rodape_html: '',
|
||||
variaveis: [],
|
||||
logo_url: ''
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, (val) => {
|
||||
form.value = { ...defaultForm(), ...val }
|
||||
}, { deep: true })
|
||||
|
||||
watch(form, (val) => {
|
||||
emit('update:modelValue', { ...val })
|
||||
}, { deep: true })
|
||||
|
||||
// ── Preview ─────────────────────────────────────────────────
|
||||
|
||||
const renderedPreview = computed(() => previewHtml(form.value.corpo_html))
|
||||
const renderedCabecalho = computed(() => previewHtml(form.value.cabecalho_html || ''))
|
||||
const renderedRodape = computed(() => previewHtml(form.value.rodape_html || ''))
|
||||
|
||||
// ── Inserir variavel no corpo ───────────────────────────────
|
||||
|
||||
const cursorField = ref('corpo_html') // qual campo esta ativo
|
||||
const editorCabecalho = ref(null)
|
||||
const editorCorpo = ref(null)
|
||||
const editorRodape = ref(null)
|
||||
|
||||
function insertVariable(varKey) {
|
||||
const tag = `{{${varKey}}}`
|
||||
const editorMap = {
|
||||
cabecalho_html: editorCabecalho,
|
||||
corpo_html: editorCorpo,
|
||||
rodape_html: editorRodape
|
||||
}
|
||||
const editorRef = editorMap[cursorField.value]
|
||||
if (editorRef?.value?.insertHTML) {
|
||||
editorRef.value.insertHTML(tag)
|
||||
} else {
|
||||
form.value[cursorField.value] = (form.value[cursorField.value] || '') + tag
|
||||
}
|
||||
|
||||
// Adiciona a variavel na lista se nao estiver
|
||||
if (!form.value.variaveis.includes(varKey)) {
|
||||
form.value.variaveis = [...form.value.variaveis, varKey]
|
||||
}
|
||||
}
|
||||
|
||||
// ── Save ────────────────────────────────────────────────────
|
||||
|
||||
function onSave() {
|
||||
emit('save', { ...form.value })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<!-- Header: nome e tipo -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-[1fr_200px] gap-3">
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="text-xs font-medium text-[var(--text-color-secondary)]">Nome do template</label>
|
||||
<InputText v-model="form.nome_template" placeholder="Ex: Declaração de comparecimento" class="w-full" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="text-xs font-medium text-[var(--text-color-secondary)]">Tipo</label>
|
||||
<Select
|
||||
v-model="form.tipo"
|
||||
:options="TIPOS_TEMPLATE"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="text-xs font-medium text-[var(--text-color-secondary)]">Descrição</label>
|
||||
<InputText v-model="form.descricao" placeholder="Breve descrição do template" class="w-full" />
|
||||
</div>
|
||||
|
||||
<!-- Tabs: Editor / Preview -->
|
||||
<div class="flex items-center gap-1 border-b border-[var(--surface-border)]">
|
||||
<button
|
||||
class="px-3 py-2 text-sm font-medium border-b-2 transition-colors"
|
||||
:class="activeTab === 'editor' ? 'border-primary text-primary' : 'border-transparent text-[var(--text-color-secondary)] hover:text-[var(--text-color)]'"
|
||||
@click="activeTab = 'editor'"
|
||||
>
|
||||
Editor
|
||||
</button>
|
||||
<button
|
||||
class="px-3 py-2 text-sm font-medium border-b-2 transition-colors"
|
||||
:class="activeTab === 'preview' ? 'border-primary text-primary' : 'border-transparent text-[var(--text-color-secondary)] hover:text-[var(--text-color)]'"
|
||||
@click="activeTab = 'preview'"
|
||||
>
|
||||
Preview
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Editor -->
|
||||
<div v-show="activeTab === 'editor'" class="flex flex-col lg:flex-row gap-4">
|
||||
<!-- Campos HTML -->
|
||||
<div class="flex-1 flex flex-col gap-3">
|
||||
<div class="flex flex-col gap-1" @focusin="cursorField = 'cabecalho_html'">
|
||||
<label class="text-xs font-medium text-[var(--text-color-secondary)]">Cabeçalho</label>
|
||||
<JoditEmailEditor ref="editorCabecalho" v-model="form.cabecalho_html" :minHeight="120" layoutButtons :logoUrl="form.logo_url" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1" @focusin="cursorField = 'corpo_html'">
|
||||
<label class="text-xs font-medium text-[var(--text-color-secondary)]">Corpo do documento</label>
|
||||
<JoditEmailEditor ref="editorCorpo" v-model="form.corpo_html" :minHeight="350" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1" @focusin="cursorField = 'rodape_html'">
|
||||
<label class="text-xs font-medium text-[var(--text-color-secondary)]">Rodapé</label>
|
||||
<JoditEmailEditor ref="editorRodape" v-model="form.rodape_html" :minHeight="120" layoutButtons :logoUrl="form.logo_url" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="text-xs font-medium text-[var(--text-color-secondary)]">URL do logo (opcional)</label>
|
||||
<InputText v-model="form.logo_url" placeholder="https://..." class="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Painel de variaveis -->
|
||||
<div class="w-full lg:w-[220px] flex-shrink-0">
|
||||
<div class="sticky top-0">
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[var(--text-color-secondary)] mb-2">
|
||||
Variáveis
|
||||
</div>
|
||||
<div class="text-[0.65rem] text-[var(--text-color-secondary)] mb-3">
|
||||
Clique para inserir no campo ativo
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 max-h-[500px] overflow-y-auto pr-1">
|
||||
<div v-for="(vars, grupo) in variablesGrouped" :key="grupo">
|
||||
<div class="text-[0.65rem] font-semibold uppercase tracking-wider text-[var(--text-color-secondary)] mb-1">{{ grupo }}</div>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<button
|
||||
v-for="v in vars"
|
||||
:key="v.key"
|
||||
class="text-left text-xs px-2 py-1 rounded hover:bg-primary/10 hover:text-primary transition-colors truncate"
|
||||
:title="v.key"
|
||||
@click="insertVariable(v.key)"
|
||||
>
|
||||
<span class="font-mono text-[0.65rem] opacity-60">{{</span>
|
||||
{{ v.label }}
|
||||
<span class="font-mono text-[0.65rem] opacity-60">}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Preview -->
|
||||
<div v-show="activeTab === 'preview'" class="border border-[var(--surface-border)] rounded-lg bg-white overflow-hidden">
|
||||
<div class="p-6 text-black" style="font-family: 'Segoe UI', Arial, sans-serif; font-size: 12pt; line-height: 1.6;">
|
||||
<div v-if="form.cabecalho_html" class="text-center mb-4 pb-3 border-b border-gray-300" v-html="renderedCabecalho" />
|
||||
<div class="min-h-[300px]" v-html="renderedPreview" />
|
||||
<div v-if="form.rodape_html" class="mt-8 pt-3 border-t border-gray-300 text-center text-[10pt] text-gray-500" v-html="renderedRodape" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Acoes -->
|
||||
<div class="flex items-center justify-end gap-2 pt-2">
|
||||
<Button label="Cancelar" text @click="emit('cancel')" />
|
||||
<Button :label="mode === 'create' ? 'Criar template' : 'Salvar'" icon="pi pi-check" @click="onSave" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user