Correcao Sidebar Classico e Rail, Correcao Layout, Ajuste de Breakpoint para Tailwind, Ajuste AppTopbar, Ajuste Menu PopOver, Recriado Paleta de Cores, Inserido algumas animações leves, Reajuste Cor items NOVOS da tabela, Drawer Ajuda Corrigido no Logout, Whatsapp, sms, email, recursos extras
This commit is contained in:
@@ -14,433 +14,351 @@
|
||||
| © 2026 — Todos os direitos reservados
|
||||
|--------------------------------------------------------------------------
|
||||
-->
|
||||
<template>
|
||||
<Dialog
|
||||
v-model:visible="isOpen"
|
||||
modal
|
||||
:draggable="false"
|
||||
:closable="!saving"
|
||||
:dismissableMask="!saving"
|
||||
:style="{ width: '34rem', maxWidth: '92vw' }"
|
||||
pt:mask:class="backdrop-blur-xs"
|
||||
@hide="onHide"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
<div class="text-xl font-semibold">{{ title }}</div>
|
||||
<div class="text-sm text-surface-500">
|
||||
Crie um paciente rapidamente.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TOPBAR ACTION -->
|
||||
<Button
|
||||
v-if="canSee('testMODE')"
|
||||
label="Gerar usuário"
|
||||
icon="pi pi-user-plus"
|
||||
severity="secondary"
|
||||
outlined
|
||||
:disabled="saving"
|
||||
@click="generateUser"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<Message v-if="errorMsg" severity="error" :closable="false">
|
||||
{{ errorMsg }}
|
||||
</Message>
|
||||
|
||||
<div class="flex flex-col gap-2 mt-2">
|
||||
<!-- Nome -->
|
||||
<FloatLabel variant="on">
|
||||
<IconField>
|
||||
<InputIcon class="pi pi-user" />
|
||||
<InputText
|
||||
id="cr-nome"
|
||||
v-model.trim="form.nome_completo"
|
||||
class="w-full"
|
||||
variant="filled"
|
||||
:disabled="saving"
|
||||
autocomplete="off"
|
||||
autofocus
|
||||
@keydown.enter.prevent="submit('only')"
|
||||
/>
|
||||
</IconField>
|
||||
<label for="cr-nome">Nome completo *</label>
|
||||
</FloatLabel>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<!-- E-mail -->
|
||||
<FloatLabel variant="on">
|
||||
<IconField>
|
||||
<InputIcon class="pi pi-envelope" />
|
||||
<InputText
|
||||
id="cr-email"
|
||||
v-model.trim="form.email_principal"
|
||||
class="w-full"
|
||||
variant="filled"
|
||||
:disabled="saving"
|
||||
inputmode="email"
|
||||
autocomplete="off"
|
||||
@keydown.enter.prevent="submit('only')"
|
||||
/>
|
||||
</IconField>
|
||||
<label for="cr-email">E-mail *</label>
|
||||
</FloatLabel>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<!-- Telefone -->
|
||||
<FloatLabel variant="on">
|
||||
<IconField>
|
||||
<InputIcon class="pi pi-phone" />
|
||||
<InputMask
|
||||
id="cr-telefone"
|
||||
v-model="form.telefone"
|
||||
mask="(99) 99999-9999"
|
||||
class="w-full"
|
||||
variant="filled"
|
||||
:disabled="saving"
|
||||
@keydown.enter.prevent="submit('only')"
|
||||
/>
|
||||
</IconField>
|
||||
<label for="cr-telefone">Telefone *</label>
|
||||
</FloatLabel>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-surface-500">
|
||||
Dica: "Gerar usuário" preenche automaticamente com dados fictícios.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
label="Cancelar"
|
||||
severity="secondary"
|
||||
text
|
||||
:disabled="saving"
|
||||
@click="close"
|
||||
/>
|
||||
<!-- Na rota de pacientes: só "Salvar" -->
|
||||
<Button
|
||||
v-if="isOnPatientsPage"
|
||||
label="Salvar"
|
||||
:loading="saving"
|
||||
:disabled="saving"
|
||||
@click="submit('only')"
|
||||
/>
|
||||
<!-- Fora da rota de pacientes: "Salvar e fechar" + "Salvar e ver pacientes" -->
|
||||
<template v-else>
|
||||
<Button
|
||||
label="Salvar e fechar"
|
||||
severity="secondary"
|
||||
outlined
|
||||
:loading="saving"
|
||||
:disabled="saving"
|
||||
@click="submit('only')"
|
||||
/>
|
||||
<Button
|
||||
label="Salvar e ver pacientes"
|
||||
:loading="saving"
|
||||
:disabled="saving"
|
||||
@click="submit('view')"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useRoleGuard } from '@/composables/useRoleGuard'
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useRoleGuard } from '@/composables/useRoleGuard';
|
||||
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
|
||||
import InputMask from 'primevue/inputmask'
|
||||
import Message from 'primevue/message'
|
||||
import InputMask from 'primevue/inputmask';
|
||||
import Message from 'primevue/message';
|
||||
|
||||
|
||||
import { supabase } from '@/lib/supabase/client'
|
||||
const { canSee } = useRoleGuard()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
const { canSee } = useRoleGuard();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const isOnPatientsPage = computed(() => {
|
||||
const p = String(route.path || '')
|
||||
return p.includes('/patients') || p.includes('/pacientes')
|
||||
})
|
||||
const p = String(route.path || '');
|
||||
return p.includes('/patients') || p.includes('/pacientes');
|
||||
});
|
||||
|
||||
/**
|
||||
* Lista "curada" de pensadores influentes na psicanálise e seu entorno.
|
||||
* Usada para geração rápida de dados fictícios.
|
||||
*/
|
||||
const PSICANALISE_PENSADORES = Object.freeze([
|
||||
{ nome: 'Sigmund Freud' },
|
||||
{ nome: 'Jacques Lacan' },
|
||||
{ nome: 'Melanie Klein' },
|
||||
{ nome: 'Donald Winnicott' },
|
||||
{ nome: 'Wilfred Bion' },
|
||||
{ nome: 'Sándor Ferenczi' },
|
||||
{ nome: 'Anna Freud' },
|
||||
{ nome: 'Karl Abraham' },
|
||||
{ nome: 'Otto Rank' },
|
||||
{ nome: 'Karen Horney' },
|
||||
{ nome: 'Erich Fromm' },
|
||||
{ nome: 'Michael Balint' },
|
||||
{ nome: 'Ronald Fairbairn' },
|
||||
{ nome: 'John Bowlby' },
|
||||
{ nome: 'André Green' },
|
||||
{ nome: 'Jean Laplanche' },
|
||||
{ nome: 'Christopher Bollas' },
|
||||
{ nome: 'Thomas Ogden' },
|
||||
{ nome: 'Jessica Benjamin' },
|
||||
{ nome: 'Joyce McDougall' },
|
||||
{ nome: 'Peter Fonagy' },
|
||||
{ nome: 'Carl Gustav Jung' },
|
||||
{ nome: 'Alfred Adler' }
|
||||
])
|
||||
{ nome: 'Sigmund Freud' },
|
||||
{ nome: 'Jacques Lacan' },
|
||||
{ nome: 'Melanie Klein' },
|
||||
{ nome: 'Donald Winnicott' },
|
||||
{ nome: 'Wilfred Bion' },
|
||||
{ nome: 'Sándor Ferenczi' },
|
||||
{ nome: 'Anna Freud' },
|
||||
{ nome: 'Karl Abraham' },
|
||||
{ nome: 'Otto Rank' },
|
||||
{ nome: 'Karen Horney' },
|
||||
{ nome: 'Erich Fromm' },
|
||||
{ nome: 'Michael Balint' },
|
||||
{ nome: 'Ronald Fairbairn' },
|
||||
{ nome: 'John Bowlby' },
|
||||
{ nome: 'André Green' },
|
||||
{ nome: 'Jean Laplanche' },
|
||||
{ nome: 'Christopher Bollas' },
|
||||
{ nome: 'Thomas Ogden' },
|
||||
{ nome: 'Jessica Benjamin' },
|
||||
{ nome: 'Joyce McDougall' },
|
||||
{ nome: 'Peter Fonagy' },
|
||||
{ nome: 'Carl Gustav Jung' },
|
||||
{ nome: 'Alfred Adler' }
|
||||
]);
|
||||
|
||||
// domínio seguro para dados fictícios
|
||||
const AUTO_EMAIL_DOMAIN = 'example.com'
|
||||
const AUTO_EMAIL_DOMAIN = 'example.com';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Boolean, default: false },
|
||||
title: { type: String, default: 'Cadastro rápido' },
|
||||
modelValue: { type: Boolean, default: false },
|
||||
title: { type: String, default: 'Cadastro rápido' },
|
||||
|
||||
tableName: { type: String, default: 'patients' },
|
||||
ownerField: { type: String, default: 'owner_id' },
|
||||
tableName: { type: String, default: 'patients' },
|
||||
ownerField: { type: String, default: 'owner_id' },
|
||||
|
||||
// defaults alinhados com seu schema
|
||||
nameField: { type: String, default: 'nome_completo' },
|
||||
emailField: { type: String, default: 'email_principal' },
|
||||
phoneField: { type: String, default: 'telefone' },
|
||||
// defaults alinhados com seu schema
|
||||
nameField: { type: String, default: 'nome_completo' },
|
||||
emailField: { type: String, default: 'email_principal' },
|
||||
phoneField: { type: String, default: 'telefone' },
|
||||
|
||||
// multi-tenant (defaults do seu schema)
|
||||
tenantField: { type: String, default: 'tenant_id' },
|
||||
responsibleMemberField: { type: String, default: 'responsible_member_id' },
|
||||
// multi-tenant (defaults do seu schema)
|
||||
tenantField: { type: String, default: 'tenant_id' },
|
||||
responsibleMemberField: { type: String, default: 'responsible_member_id' },
|
||||
|
||||
extraPayload: { type: Object, default: () => ({}) },
|
||||
extraPayload: { type: Object, default: () => ({}) },
|
||||
|
||||
closeOnCreated: { type: Boolean, default: true },
|
||||
resetOnOpen: { type: Boolean, default: true }
|
||||
})
|
||||
closeOnCreated: { type: Boolean, default: true },
|
||||
resetOnOpen: { type: Boolean, default: true }
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'created'])
|
||||
const toast = useToast()
|
||||
const emit = defineEmits(['update:modelValue', 'created']);
|
||||
const toast = useToast();
|
||||
|
||||
const saving = ref(false)
|
||||
const touched = ref(false)
|
||||
const errorMsg = ref('')
|
||||
const saving = ref(false);
|
||||
const touched = ref(false);
|
||||
const errorMsg = ref('');
|
||||
|
||||
const form = reactive({
|
||||
nome_completo: '',
|
||||
email_principal: '',
|
||||
telefone: ''
|
||||
})
|
||||
nome_completo: '',
|
||||
email_principal: '',
|
||||
telefone: ''
|
||||
});
|
||||
|
||||
const isOpen = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (v) => emit('update:modelValue', v)
|
||||
})
|
||||
get: () => props.modelValue,
|
||||
set: (v) => emit('update:modelValue', v)
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(v) => {
|
||||
if (v && props.resetOnOpen) reset()
|
||||
if (v) {
|
||||
touched.value = false
|
||||
errorMsg.value = ''
|
||||
() => props.modelValue,
|
||||
(v) => {
|
||||
if (v && props.resetOnOpen) reset();
|
||||
if (v) {
|
||||
touched.value = false;
|
||||
errorMsg.value = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
function reset () {
|
||||
form.nome_completo = ''
|
||||
form.email_principal = ''
|
||||
form.telefone = ''
|
||||
function reset() {
|
||||
form.nome_completo = '';
|
||||
form.email_principal = '';
|
||||
form.telefone = '';
|
||||
}
|
||||
|
||||
function close () {
|
||||
isOpen.value = false
|
||||
function close() {
|
||||
isOpen.value = false;
|
||||
}
|
||||
|
||||
function onHide () {}
|
||||
function onHide() {}
|
||||
|
||||
function isValidEmail (v) {
|
||||
return /.+@.+\..+/.test(String(v || '').trim())
|
||||
function isValidEmail(v) {
|
||||
return /.+@.+\..+/.test(String(v || '').trim());
|
||||
}
|
||||
|
||||
function isValidPhone (v) {
|
||||
const digits = String(v || '').replace(/\D/g, '')
|
||||
return digits.length === 10 || digits.length === 11
|
||||
function isValidPhone(v) {
|
||||
const digits = String(v || '').replace(/\D/g, '');
|
||||
return digits.length === 10 || digits.length === 11;
|
||||
}
|
||||
|
||||
function normalizePhoneDigits (v) {
|
||||
const digits = String(v || '').replace(/\D/g, '')
|
||||
return digits || null
|
||||
function normalizePhoneDigits(v) {
|
||||
const digits = String(v || '').replace(/\D/g, '');
|
||||
return digits || null;
|
||||
}
|
||||
|
||||
async function getOwnerId () {
|
||||
const { data, error } = await supabase.auth.getUser()
|
||||
if (error) throw error
|
||||
const user = data?.user
|
||||
if (!user?.id) throw new Error('Usuário não encontrado. Faça login novamente.')
|
||||
return user.id
|
||||
async function getOwnerId() {
|
||||
const { data, error } = await supabase.auth.getUser();
|
||||
if (error) throw error;
|
||||
const user = data?.user;
|
||||
if (!user?.id) throw new Error('Usuário não encontrado. Faça login novamente.');
|
||||
return user.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pega tenant_id + member_id do usuário logado.
|
||||
*/
|
||||
async function resolveTenantContextOrFail () {
|
||||
const { data: authData, error: authError } = await supabase.auth.getUser()
|
||||
if (authError) throw authError
|
||||
const uid = authData?.user?.id
|
||||
if (!uid) throw new Error('Sessão inválida.')
|
||||
async function resolveTenantContextOrFail() {
|
||||
const { data: authData, error: authError } = await supabase.auth.getUser();
|
||||
if (authError) throw authError;
|
||||
const uid = authData?.user?.id;
|
||||
if (!uid) throw new Error('Sessão inválida.');
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('tenant_members')
|
||||
.select('id, tenant_id')
|
||||
.eq('user_id', uid)
|
||||
.eq('status', 'active')
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(1)
|
||||
.single()
|
||||
const { data, error } = await supabase.from('tenant_members').select('id, tenant_id').eq('user_id', uid).eq('status', 'active').order('created_at', { ascending: false }).limit(1).single();
|
||||
|
||||
if (error) throw error
|
||||
if (!data?.tenant_id || !data?.id) throw new Error('Responsible member not found')
|
||||
if (error) throw error;
|
||||
if (!data?.tenant_id || !data?.id) throw new Error('Responsible member not found');
|
||||
|
||||
return { tenantId: data.tenant_id, memberId: data.id }
|
||||
return { tenantId: data.tenant_id, memberId: data.id };
|
||||
}
|
||||
|
||||
/* ----------------------------
|
||||
* Gerador (nome/email/telefone)
|
||||
* ---------------------------- */
|
||||
function randInt (min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min
|
||||
function randInt(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
function pick (arr) {
|
||||
return arr[randInt(0, arr.length - 1)]
|
||||
function pick(arr) {
|
||||
return arr[randInt(0, arr.length - 1)];
|
||||
}
|
||||
function slugify (s) {
|
||||
return String(s || '')
|
||||
.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '.')
|
||||
.replace(/(^\.)|(\.$)/g, '')
|
||||
function slugify(s) {
|
||||
return String(s || '')
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '.')
|
||||
.replace(/(^\.)|(\.$)/g, '');
|
||||
}
|
||||
function randomPhoneBRMasked () {
|
||||
const ddd = randInt(11, 99)
|
||||
const a = randInt(10000, 99999)
|
||||
const b = randInt(1000, 9999)
|
||||
return `(${ddd}) ${a}-${b}`
|
||||
function randomPhoneBRMasked() {
|
||||
const ddd = randInt(11, 99);
|
||||
const a = randInt(10000, 99999);
|
||||
const b = randInt(1000, 9999);
|
||||
return `(${ddd}) ${a}-${b}`;
|
||||
}
|
||||
|
||||
function generateUser () {
|
||||
if (saving.value) return
|
||||
function generateUser() {
|
||||
if (saving.value) return;
|
||||
|
||||
const p = pick(PSICANALISE_PENSADORES)
|
||||
const nome = p?.nome || 'Paciente'
|
||||
const p = pick(PSICANALISE_PENSADORES);
|
||||
const nome = p?.nome || 'Paciente';
|
||||
|
||||
const base = slugify(nome) || 'paciente'
|
||||
const suffix = randInt(10, 999)
|
||||
const email = `${base}.${suffix}@${AUTO_EMAIL_DOMAIN}`
|
||||
const base = slugify(nome) || 'paciente';
|
||||
const suffix = randInt(10, 999);
|
||||
const email = `${base}.${suffix}@${AUTO_EMAIL_DOMAIN}`;
|
||||
|
||||
form.nome_completo = nome
|
||||
form.email_principal = email
|
||||
form.telefone = randomPhoneBRMasked()
|
||||
form.nome_completo = nome;
|
||||
form.email_principal = email;
|
||||
form.telefone = randomPhoneBRMasked();
|
||||
|
||||
touched.value = true
|
||||
errorMsg.value = ''
|
||||
touched.value = true;
|
||||
errorMsg.value = '';
|
||||
|
||||
toast.add({
|
||||
severity: 'info',
|
||||
summary: 'Gerar usuário',
|
||||
detail: 'Dados fictícios preenchidos.',
|
||||
life: 2200
|
||||
})
|
||||
toast.add({
|
||||
severity: 'info',
|
||||
summary: 'Gerar usuário',
|
||||
detail: 'Dados fictícios preenchidos.',
|
||||
life: 2200
|
||||
});
|
||||
}
|
||||
|
||||
function patientsListRoute () {
|
||||
const p = String(route.path || '')
|
||||
return p.startsWith('/therapist') ? '/therapist/patients' : '/admin/pacientes'
|
||||
function patientsListRoute() {
|
||||
const p = String(route.path || '');
|
||||
return p.startsWith('/therapist') ? '/therapist/patients' : '/admin/pacientes';
|
||||
}
|
||||
|
||||
async function submit (mode = 'only') {
|
||||
touched.value = true
|
||||
errorMsg.value = ''
|
||||
async function submit(mode = 'only') {
|
||||
touched.value = true;
|
||||
errorMsg.value = '';
|
||||
|
||||
const nome = String(form.nome_completo || '').trim()
|
||||
const email = String(form.email_principal || '').trim()
|
||||
const tel = String(form.telefone || '')
|
||||
const nome = String(form.nome_completo || '').trim();
|
||||
const email = String(form.email_principal || '').trim();
|
||||
const tel = String(form.telefone || '');
|
||||
|
||||
if (!nome) return
|
||||
if (!email) return
|
||||
if (!isValidEmail(email)) return
|
||||
if (!tel) return
|
||||
if (!isValidPhone(tel)) return
|
||||
if (!nome) return;
|
||||
if (!email) return;
|
||||
if (!isValidEmail(email)) return;
|
||||
if (!tel) return;
|
||||
if (!isValidPhone(tel)) return;
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
const ownerId = await getOwnerId()
|
||||
const { tenantId, memberId } = await resolveTenantContextOrFail()
|
||||
saving.value = true;
|
||||
try {
|
||||
const ownerId = await getOwnerId();
|
||||
const { tenantId, memberId } = await resolveTenantContextOrFail();
|
||||
|
||||
// extraPayload antes; tenant/responsible forçados depois (não podem ser sobrescritos sem querer)
|
||||
const payload = {
|
||||
...props.extraPayload,
|
||||
// extraPayload antes; tenant/responsible forçados depois (não podem ser sobrescritos sem querer)
|
||||
const payload = {
|
||||
...props.extraPayload,
|
||||
|
||||
[props.ownerField]: ownerId,
|
||||
[props.tenantField]: tenantId,
|
||||
[props.responsibleMemberField]: memberId,
|
||||
[props.ownerField]: ownerId,
|
||||
[props.tenantField]: tenantId,
|
||||
[props.responsibleMemberField]: memberId,
|
||||
|
||||
[props.nameField]: nome,
|
||||
[props.emailField]: email.toLowerCase(),
|
||||
[props.phoneField]: normalizePhoneDigits(tel)
|
||||
[props.nameField]: nome,
|
||||
[props.emailField]: email.toLowerCase(),
|
||||
[props.phoneField]: normalizePhoneDigits(tel)
|
||||
};
|
||||
|
||||
Object.keys(payload).forEach((k) => {
|
||||
if (payload[k] === undefined) delete payload[k];
|
||||
});
|
||||
|
||||
const { data, error } = await supabase.from(props.tableName).insert(payload).select().single();
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Paciente criado',
|
||||
detail: nome,
|
||||
life: 2500
|
||||
});
|
||||
|
||||
emit('created', data);
|
||||
if (props.closeOnCreated) close();
|
||||
if (mode === 'view') await router.push(patientsListRoute());
|
||||
} catch (err) {
|
||||
const msg = err?.message || err?.details || 'Não foi possível criar o paciente.';
|
||||
errorMsg.value = msg;
|
||||
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Erro ao salvar',
|
||||
detail: msg,
|
||||
life: 4500
|
||||
});
|
||||
|
||||
console.error('[ComponentCadastroRapido] insert error:', err);
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
|
||||
Object.keys(payload).forEach((k) => {
|
||||
if (payload[k] === undefined) delete payload[k]
|
||||
})
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from(props.tableName)
|
||||
.insert(payload)
|
||||
.select()
|
||||
.single()
|
||||
|
||||
if (error) throw error
|
||||
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Paciente criado',
|
||||
detail: nome,
|
||||
life: 2500
|
||||
})
|
||||
|
||||
emit('created', data)
|
||||
if (props.closeOnCreated) close()
|
||||
if (mode === 'view') await router.push(patientsListRoute())
|
||||
} catch (err) {
|
||||
const msg = err?.message || err?.details || 'Não foi possível criar o paciente.'
|
||||
errorMsg.value = msg
|
||||
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Erro ao salvar',
|
||||
detail: msg,
|
||||
life: 4500
|
||||
})
|
||||
|
||||
console.error('[ComponentCadastroRapido] insert error:', err)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model:visible="isOpen" modal :draggable="false" :closable="!saving" :dismissableMask="!saving" :style="{ width: '34rem', maxWidth: '92vw' }" pt:mask:class="backdrop-blur-xs" @hide="onHide">
|
||||
<template #header>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
<div class="text-xl font-semibold">{{ title }}</div>
|
||||
<div class="text-sm text-surface-500">Crie um paciente rapidamente.</div>
|
||||
</div>
|
||||
|
||||
<!-- TOPBAR ACTION -->
|
||||
<Button v-if="canSee('testMODE')" label="Gerar usuário" icon="pi pi-user-plus" severity="secondary" outlined :disabled="saving" @click="generateUser" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<Message v-if="errorMsg" severity="error" :closable="false">
|
||||
{{ errorMsg }}
|
||||
</Message>
|
||||
|
||||
<div class="flex flex-col gap-2 mt-2">
|
||||
<!-- Nome -->
|
||||
<FloatLabel variant="on">
|
||||
<IconField>
|
||||
<InputIcon class="pi pi-user" />
|
||||
<InputText id="cr-nome" v-model.trim="form.nome_completo" class="w-full" variant="filled" :disabled="saving" autocomplete="off" autofocus @keydown.enter.prevent="submit('only')" />
|
||||
</IconField>
|
||||
<label for="cr-nome">Nome completo *</label>
|
||||
</FloatLabel>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<!-- E-mail -->
|
||||
<FloatLabel variant="on">
|
||||
<IconField>
|
||||
<InputIcon class="pi pi-envelope" />
|
||||
<InputText id="cr-email" v-model.trim="form.email_principal" class="w-full" variant="filled" :disabled="saving" inputmode="email" autocomplete="off" @keydown.enter.prevent="submit('only')" />
|
||||
</IconField>
|
||||
<label for="cr-email">E-mail *</label>
|
||||
</FloatLabel>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<!-- Telefone -->
|
||||
<FloatLabel variant="on">
|
||||
<IconField>
|
||||
<InputIcon class="pi pi-phone" />
|
||||
<InputMask id="cr-telefone" v-model="form.telefone" mask="(99) 99999-9999" class="w-full" variant="filled" :disabled="saving" @keydown.enter.prevent="submit('only')" />
|
||||
</IconField>
|
||||
<label for="cr-telefone">Telefone *</label>
|
||||
</FloatLabel>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-surface-500">Dica: "Gerar usuário" preenche automaticamente com dados fictícios.</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button label="Cancelar" severity="secondary" text :disabled="saving" @click="close" />
|
||||
<!-- Na rota de pacientes: só "Salvar" -->
|
||||
<Button v-if="isOnPatientsPage" label="Salvar" :loading="saving" :disabled="saving" @click="submit('only')" />
|
||||
<!-- Fora da rota de pacientes: "Salvar e fechar" + "Salvar e ver pacientes" -->
|
||||
<template v-else>
|
||||
<Button label="Salvar e fechar" severity="secondary" outlined :loading="saving" :disabled="saving" @click="submit('only')" />
|
||||
<Button label="Salvar e ver pacientes" :loading="saving" :disabled="saving" @click="submit('view')" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user