first commit
This commit is contained in:
251
src/components/ComponentCadastroRapido.vue
Normal file
251
src/components/ComponentCadastroRapido.vue
Normal file
@@ -0,0 +1,251 @@
|
||||
<template>
|
||||
<Dialog
|
||||
v-model:visible="isOpen"
|
||||
modal
|
||||
:closable="!saving"
|
||||
:dismissableMask="!saving"
|
||||
:style="{ width: '34rem', maxWidth: '92vw' }"
|
||||
@hide="onHide"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="text-xl font-semibold">{{ title }}</div>
|
||||
<div class="text-sm text-surface-500">
|
||||
Crie um paciente rapidamente (nome, e-mail e telefone obrigatórios).
|
||||
</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">
|
||||
<label class="text-sm font-medium" for="cr-nome">Nome *</label>
|
||||
<InputText
|
||||
id="cr-nome"
|
||||
v-model.trim="form.nome_completo"
|
||||
:disabled="saving"
|
||||
autocomplete="off"
|
||||
autofocus
|
||||
@keydown.enter.prevent="submit"
|
||||
/>
|
||||
<small v-if="touched && !form.nome_completo" class="text-red-500">
|
||||
Informe o nome.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-sm font-medium" for="cr-email">E-mail *</label>
|
||||
<InputText
|
||||
id="cr-email"
|
||||
v-model.trim="form.email_principal"
|
||||
:disabled="saving"
|
||||
inputmode="email"
|
||||
autocomplete="off"
|
||||
@keydown.enter.prevent="submit"
|
||||
/>
|
||||
<small v-if="touched && !form.email_principal" class="text-red-500">
|
||||
Informe o e-mail.
|
||||
</small>
|
||||
<small v-if="touched && form.email_principal && !isValidEmail(form.email_principal)" class="text-red-500">
|
||||
E-mail inválido.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-sm font-medium" for="cr-telefone">Telefone *</label>
|
||||
<InputMask
|
||||
id="cr-telefone"
|
||||
v-model="form.telefone"
|
||||
:disabled="saving"
|
||||
mask="(99) 99999-9999"
|
||||
placeholder="(16) 99999-9999"
|
||||
@keydown.enter.prevent="submit"
|
||||
/>
|
||||
<small v-if="touched && !form.telefone" class="text-red-500">
|
||||
Informe o telefone.
|
||||
</small>
|
||||
<small v-else-if="touched && form.telefone && !isValidPhone(form.telefone)" class="text-red-500">
|
||||
Telefone inválido.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
label="Cancelar"
|
||||
severity="secondary"
|
||||
text
|
||||
:disabled="saving"
|
||||
@click="close"
|
||||
/>
|
||||
<Button
|
||||
label="Salvar"
|
||||
:loading="saving"
|
||||
:disabled="saving"
|
||||
@click="submit"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
|
||||
import InputMask from 'primevue/inputmask'
|
||||
import Message from 'primevue/message'
|
||||
|
||||
import { supabase } from '@/lib/supabase/client'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Boolean, default: false },
|
||||
title: { type: String, default: 'Cadastro rápido' },
|
||||
|
||||
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' },
|
||||
|
||||
// ✅ NÃO coloque status aqui por padrão (evita violar patients_status_check)
|
||||
extraPayload: { type: Object, default: () => ({}) },
|
||||
|
||||
closeOnCreated: { type: Boolean, default: true },
|
||||
resetOnOpen: { type: Boolean, default: true }
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'created'])
|
||||
const toast = useToast()
|
||||
|
||||
const saving = ref(false)
|
||||
const touched = ref(false)
|
||||
const errorMsg = ref('')
|
||||
|
||||
const form = reactive({
|
||||
nome_completo: '',
|
||||
email_principal: '',
|
||||
telefone: ''
|
||||
})
|
||||
|
||||
const isOpen = computed({
|
||||
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 = ''
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
function reset () {
|
||||
form.nome_completo = ''
|
||||
form.email_principal = ''
|
||||
form.telefone = ''
|
||||
}
|
||||
|
||||
function close () {
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
function onHide () {}
|
||||
|
||||
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 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 submit () {
|
||||
touched.value = true
|
||||
errorMsg.value = ''
|
||||
|
||||
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
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
const ownerId = await getOwnerId()
|
||||
|
||||
const payload = {
|
||||
[props.ownerField]: ownerId,
|
||||
[props.nameField]: nome,
|
||||
[props.emailField]: email.toLowerCase(),
|
||||
[props.phoneField]: normalizePhoneDigits(tel),
|
||||
...props.extraPayload
|
||||
}
|
||||
|
||||
// remove undefined
|
||||
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()
|
||||
} 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>
|
||||
Reference in New Issue
Block a user