melissa/paciente-docs: wire-up preview actions + Editar abre dialog em modo edicao

DocumentPreviewDialog emitia @download/@edit/@share/@sign/@delete que
o MelissaPatientDocuments nao ouvia — os 5 botoes da sidebar do preview
caiam no vazio. Adicionado wire-up roteando pros mesmos handlers do
card (onDownload, onEdit, onShare, onSign, onDelete). Share/sign/delete
fecham o preview antes de abrir o proprio dialog pra UX limpa; download
mantem preview aberto (acao instantanea).

DocumentGenerateDialog ganha prop editing-doc-id. Quando setado:
- Busca template_id + dados_preenchidos via loadGeneratedFromDocId
- Pre-seleciona template, popula vars (sobrescreve auto-loaded vars
  com dados_preenchidos pra preservar customizacao anterior)
- Pula direto pra step 'edit'
- Save vira UPDATE in-place (preserva documents.id e audit trail)
- Header muda pra "Editar documento" + icone pi-pencil amber
- Botao final vira "Substituir documento"
- Doc sem registro generated (legado): toast info + flow normal de
  select template; ao salvar, cria o registro generated linkado.

MelissaPatientDocuments:
- onEdit substituido (era shortcut pra onPreview): abre generate dialog
  com editing-doc-id setado.
- Novo ref editingDoc dedicado (separado do selectedDoc que serve
  preview/share/sign/delete) pra evitar vazar "edit state" pro botao
  "Gerar" do header quando user so abre preview e fecha.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-05-22 13:42:39 -03:00
parent 342defecde
commit 89bf181742
2 changed files with 87 additions and 17 deletions
@@ -13,12 +13,19 @@ import { ref, watch, computed } from 'vue'
import { useToast } from 'primevue/usetoast' import { useToast } from 'primevue/usetoast'
import { useDocumentGenerate } from '../composables/useDocumentGenerate' import { useDocumentGenerate } from '../composables/useDocumentGenerate'
import { useDocumentTemplates } from '../composables/useDocumentTemplates' import { useDocumentTemplates } from '../composables/useDocumentTemplates'
import { loadGeneratedFromDocId } from '@/services/DocumentGenerate.service'
const props = defineProps({ const props = defineProps({
visible: { type: Boolean, default: false }, visible: { type: Boolean, default: false },
patientId: { type: String, default: null }, patientId: { type: String, default: null },
patientName: { type: String, default: '' }, patientName: { type: String, default: '' },
agendaEventoId: { type: String, default: null } agendaEventoId: { type: String, default: null },
// Modo edicao: ID de um documents.id existente. Quando setado, o dialog
// busca o template_id + dados_preenchidos do document_generated vinculado,
// pre-seleciona o template e popula as variaveis. Save vira UPDATE
// in-place (preserva documents.id e audit). Doc sem registro generated
// (uploaded direto) cai no flow normal de "select template".
editingDocId: { type: String, default: null }
}) })
const emit = defineEmits(['update:visible', 'generated']) const emit = defineEmits(['update:visible', 'generated'])
@@ -52,13 +59,48 @@ const {
// ── Reset ao abrir ────────────────────────────────────────── // ── Reset ao abrir ──────────────────────────────────────────
watch(() => props.visible, async (v) => { watch(() => props.visible, async (v) => {
if (v) { if (!v) return;
step.value = 'select' step.value = 'select'
reset() reset()
await Promise.all([ await Promise.all([
fetchTemplates(), fetchTemplates(),
props.patientId ? loadVariables(props.patientId, props.agendaEventoId) : Promise.resolve() props.patientId ? loadVariables(props.patientId, props.agendaEventoId) : Promise.resolve()
]) ])
// Modo edicao: tenta carregar o registro do generated, pre-seleciona
// template e popula vars com dados_preenchidos (sobrescreve auto-vars
// — preserva customizacao anterior do user). Se nao houver linkage
// (doc uploaded direto), continua no flow normal de "select template".
if (props.editingDocId) {
const gen = await loadGeneratedFromDocId(props.editingDocId)
if (gen?.template_id) {
try {
await selectTemplate(gen.template_id)
// Merge: dados_preenchidos override auto-loaded variables.
// Mantemos as vars que o user nao tinha customizado da vez
// anterior (pra caso o template tenha vars novas adicionadas
// depois) — pegamos as keys auto + sobrescreve com generated.
const saved = gen.dados_preenchidos || {}
Object.entries(saved).forEach(([k, val]) => {
setVariable(k, val == null ? '' : String(val))
})
step.value = 'edit'
} catch (e) {
toast.add({
severity: 'warn',
summary: 'Template original não encontrado',
detail: 'Selecione um template para regenerar o documento.',
life: 3500
})
}
} else {
toast.add({
severity: 'info',
summary: 'Documento legado',
detail: 'Sem dados de edição. Selecione um template para regenerar.',
life: 3500
})
}
} }
}) })
@@ -109,8 +151,15 @@ function onVarChange(key, val) {
async function onGenerate() { async function onGenerate() {
try { try {
const result = await generateAndSave(props.patientId) const result = await generateAndSave(props.patientId, props.editingDocId || null)
toast.add({ severity: 'success', summary: 'Documento salvo', detail: 'Disponível nos documentos do paciente.', life: 3000 }) toast.add({
severity: 'success',
summary: props.editingDocId ? 'Documento atualizado' : 'Documento salvo',
detail: props.editingDocId
? 'PDF substituído com os novos valores.'
: 'Disponível nos documentos do paciente.',
life: 3000
})
emit('generated', result) emit('generated', result)
close() close()
} catch (e) { } catch (e) {
@@ -153,10 +202,10 @@ function close() {
<template #header> <template #header>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<span class="flex items-center justify-center w-8 h-8 rounded-lg bg-green-500/10"> <span class="flex items-center justify-center w-8 h-8 rounded-lg bg-green-500/10">
<i class="pi pi-file-pdf text-green-600" /> <i :class="editingDocId ? 'pi pi-pencil text-amber-600' : 'pi pi-file-pdf text-green-600'" />
</span> </span>
<div> <div>
<div class="text-base font-semibold">Gerar documento</div> <div class="text-base font-semibold">{{ editingDocId ? 'Editar documento' : 'Gerar documento' }}</div>
<div class="text-xs text-[var(--text-color-secondary)]"> <div class="text-xs text-[var(--text-color-secondary)]">
<template v-if="step === 'select'">Selecione um template</template> <template v-if="step === 'select'">Selecione um template</template>
<template v-else-if="step === 'edit'">{{ selectedTemplate?.nome_template }} {{ patientName }}</template> <template v-else-if="step === 'edit'">{{ selectedTemplate?.nome_template }} {{ patientName }}</template>
@@ -299,7 +348,7 @@ function close() {
/> />
<Button <Button
v-if="step === 'preview'" v-if="step === 'preview'"
label="Salvar documento" :label="editingDocId ? 'Substituir documento' : 'Salvar documento'"
icon="pi pi-check" icon="pi pi-check"
@click="onGenerate" @click="onGenerate"
:loading="generating" :loading="generating"
+25 -4
View File
@@ -57,6 +57,10 @@ const signatureDlg = ref(false);
const shareDlg = ref(false); const shareDlg = ref(false);
const selectedDoc = ref(null); const selectedDoc = ref(null);
const previewUrl = ref(''); const previewUrl = ref('');
// Ref dedicado pro modo edicao do generate dialog. Separado do selectedDoc
// (que tambem alimenta preview/share/sign/delete) pra evitar vazar "edit
// state" pro "Gerar" do header quando o user so abre preview e fecha.
const editingDoc = ref(null);
// ── Tipo selecionado (filtro pela sidebar) ──────────────── // ── Tipo selecionado (filtro pela sidebar) ────────────────
// null = todos os tipos // null = todos os tipos
@@ -123,9 +127,15 @@ async function onPreview(doc) {
function onDownload(doc) { download(doc); } function onDownload(doc) { download(doc); }
function onEdit(doc) { function onEdit(doc) {
selectedDoc.value = doc; // Abre o DocumentGenerateDialog em modo edicao (editingDocId passado).
// Reusa preview dialog em modo "ver" — edit completo só via DocumentsListPage // Dialog busca template + dados_preenchidos do document_generated e
onPreview(doc); // pre-popula tudo, pulando direto pra step 'edit'. Save substitui o PDF
// in-place no Storage e atualiza documents (preserva id + audit trail).
// Docs uploaded direto (sem registro generated) caem no flow normal de
// "select template" com um toast info.
editingDoc.value = doc;
previewDlg.value = false; // fecha preview se estiver aberto
generateDlg.value = true;
} }
function onDelete(doc) { function onDelete(doc) {
@@ -380,13 +390,24 @@ onBeforeUnmount(() => {
:doc="selectedDoc" :doc="selectedDoc"
:preview-url="previewUrl" :preview-url="previewUrl"
@updated="fetchDocuments" @updated="fetchDocuments"
@download="onDownload"
@edit="onEdit"
@share="(d) => { previewDlg = false; onShare(d); }"
@sign="(d) => { previewDlg = false; onSign(d); }"
@delete="(d) => { previewDlg = false; onDelete(d); }"
/> />
<!-- editing-doc-id vem do ref editingDoc dedicado so e setado
via onEdit (botao Editar). "Gerar" no header usa generateDlg=true
com editingDoc=null, abrindo limpo. Limpa editingDoc no
fechamento pra nao vazar pro proximo Gerar. -->
<DocumentGenerateDialog <DocumentGenerateDialog
v-if="patientId" v-if="patientId"
v-model:visible="generateDlg" :visible="generateDlg"
:patient-id="patientId" :patient-id="patientId"
:patient-name="patientName" :patient-name="patientName"
:editing-doc-id="editingDoc?.id || null"
@generated="onGenerated" @generated="onGenerated"
@update:visible="(v) => { generateDlg = v; if (!v) editingDoc = null; }"
/> />
<DocumentSignatureDialog <DocumentSignatureDialog
:visible="signatureDlg" :visible="signatureDlg"