compliance CFP #7: dialog gera share_link junto com signature
DocumentSignatureDialog (terapeuta-side) ja existia com fluxo de
add signatarios. Estendido pra:
- Checkbox "Gerar link publico para assinatura" (default ON)
- Select de validade (24h/3d/7d/30d, default 7d)
- Apos submit: alem de createSignatureRequests chama createShareLink
e exibe o URL gerado num bloco emerald com botao Copy
- Dialog fica aberto se gerou link (terapeuta copia/envia); fecha
se nao gerou
Fluxo end-to-end agora funcional: terapeuta clica "Solicitar
assinatura" no DocumentsListPage > preenche signatarios > submit
gera signature requests + share_link > copia URL > envia via WA/
email > paciente abre /shared/document/:token > assina via fluxo
publico (RPC sign_document_by_token capturando IP/UA server-side).
Fecha ROADMAP #1.2 #6/#7 — Compliance basico BR completo (#5/#6/#7/
#8/#9 todos verdes, #6 com TCLE + Telehealth + TCLE menores + termo
sigilo + LGPD + autorizacao gravacao).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
listSignatures,
|
||||
getSignatureStatus
|
||||
} from '@/services/DocumentSignatures.service'
|
||||
import { createShareLink, buildShareUrl } from '@/services/DocumentShareLinks.service'
|
||||
|
||||
const props = defineProps({
|
||||
visible: { type: Boolean, default: false },
|
||||
@@ -42,6 +43,11 @@ const TIPOS_SIGNATARIO = [
|
||||
const signatarios = ref([])
|
||||
const patientEmails = ref([])
|
||||
|
||||
// Geracao de share link p/ assinatura via portal/whatsapp
|
||||
const generateLink = ref(true)
|
||||
const linkExpiracaoHoras = ref(168) // 7 dias default
|
||||
const generatedShareUrl = ref('')
|
||||
|
||||
function addSignatario() {
|
||||
signatarios.value.push({ tipo: 'paciente', nome: '', email: '' })
|
||||
}
|
||||
@@ -81,6 +87,7 @@ function useEmail(email) {
|
||||
watch(() => props.visible, async (v) => {
|
||||
if (v && props.doc) {
|
||||
signatarios.value = []
|
||||
generatedShareUrl.value = ''
|
||||
loading.value = true
|
||||
try {
|
||||
const [sigs, status] = await Promise.all([
|
||||
@@ -99,6 +106,13 @@ watch(() => props.visible, async (v) => {
|
||||
}
|
||||
})
|
||||
|
||||
function copyShareUrl() {
|
||||
if (!generatedShareUrl.value) return
|
||||
navigator.clipboard.writeText(generatedShareUrl.value)
|
||||
.then(() => toast.add({ severity: 'success', summary: 'Link copiado', life: 1800 }))
|
||||
.catch(() => toast.add({ severity: 'warn', summary: 'Falha ao copiar', detail: 'Copie manualmente.', life: 2200 }))
|
||||
}
|
||||
|
||||
// ── Status badge ────────────────────────────────────────────
|
||||
|
||||
const statusColor = computed(() => {
|
||||
@@ -146,9 +160,39 @@ async function submit() {
|
||||
saving.value = true
|
||||
try {
|
||||
const result = await createSignatureRequests(props.doc.id, signatarios.value)
|
||||
toast.add({ severity: 'success', summary: 'Solicitação enviada', detail: `${result.length} signatário(s) adicionado(s).`, life: 3000 })
|
||||
emit('requested', result)
|
||||
emit('update:visible', false)
|
||||
|
||||
// Gera share link público quando habilitado — o paciente abre /shared/document/:token
|
||||
// e assina via fluxo público (RPC sign_document_by_token captura IP/UA server-side).
|
||||
if (generateLink.value) {
|
||||
try {
|
||||
const link = await createShareLink(props.doc.id, {
|
||||
expiracaoHoras: Number(linkExpiracaoHoras.value) || 168,
|
||||
usosMax: Math.max(signatarios.value.length * 3, 5)
|
||||
})
|
||||
generatedShareUrl.value = buildShareUrl(link.token)
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Solicitação criada',
|
||||
detail: `${result.length} signatário(s). Link de assinatura gerado.`,
|
||||
life: 3500
|
||||
})
|
||||
} catch (linkErr) {
|
||||
toast.add({
|
||||
severity: 'warn',
|
||||
summary: 'Signatários criados, mas falhou o link',
|
||||
detail: linkErr?.message || 'Tente gerar o link na ação "Compartilhar".',
|
||||
life: 4500
|
||||
})
|
||||
}
|
||||
} else {
|
||||
toast.add({ severity: 'success', summary: 'Solicitação enviada', detail: `${result.length} signatário(s) adicionado(s).`, life: 3000 })
|
||||
}
|
||||
|
||||
emit('requested', { signatures: result, shareUrl: generatedShareUrl.value })
|
||||
|
||||
// Mantém dialog aberto se gerou link — pra terapeuta copiar.
|
||||
// Fecha automaticamente se não gerou link.
|
||||
if (!generatedShareUrl.value) emit('update:visible', false)
|
||||
} catch (e) {
|
||||
toast.add({ severity: 'error', summary: 'Erro', detail: e?.message || 'Falha ao solicitar assinatura.' })
|
||||
} finally {
|
||||
@@ -263,6 +307,49 @@ function close() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toggle: gerar link público -->
|
||||
<div class="flex items-start gap-3 p-3 rounded-lg border border-blue-200 bg-blue-50/40">
|
||||
<Checkbox v-model="generateLink" :binary="true" inputId="cb-gen-link" class="mt-0.5" />
|
||||
<div class="flex-1">
|
||||
<label for="cb-gen-link" class="text-sm font-medium text-[var(--text-color)] cursor-pointer">
|
||||
Gerar link público para assinatura
|
||||
</label>
|
||||
<div class="text-xs text-[var(--text-color-secondary)] mt-0.5">
|
||||
Cria um link em <code>/shared/document/<token></code> pra enviar via WhatsApp, e-mail ou copiar. O paciente assina sem precisar logar (IP, navegador e timestamp são registrados server-side).
|
||||
</div>
|
||||
<div v-if="generateLink" class="mt-2 flex items-center gap-2">
|
||||
<label class="text-xs text-[var(--text-color-secondary)]">Validade:</label>
|
||||
<Select
|
||||
v-model="linkExpiracaoHoras"
|
||||
:options="[
|
||||
{ value: 24, label: '24 horas' },
|
||||
{ value: 72, label: '3 dias' },
|
||||
{ value: 168, label: '7 dias' },
|
||||
{ value: 720, label: '30 dias' }
|
||||
]"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
class="!text-xs w-32"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Link gerado (após submit) -->
|
||||
<div v-if="generatedShareUrl" class="p-3 rounded-lg border border-emerald-200 bg-emerald-50/40">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<i class="pi pi-link text-emerald-600" />
|
||||
<div class="text-sm font-medium text-emerald-800">Link de assinatura gerado</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<InputText :modelValue="generatedShareUrl" readonly class="w-full !text-xs" />
|
||||
<Button icon="pi pi-copy" size="small" class="!h-8 shrink-0" v-tooltip.top="'Copiar link'" @click="copyShareUrl" />
|
||||
</div>
|
||||
<div class="text-[0.7rem] text-[var(--text-color-secondary)] mt-1.5">
|
||||
Envie este link para o(a) paciente. Eles podem assinar diretamente sem precisar criar conta.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Emails cadastrados do paciente -->
|
||||
<div class="p-3 rounded-lg border border-[var(--surface-border)] bg-[var(--surface-ground)]">
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[var(--text-color-secondary)] mb-2">E-mails do paciente</div>
|
||||
|
||||
Reference in New Issue
Block a user