first commit

This commit is contained in:
Leonardo
2026-02-18 22:36:45 -03:00
parent ec6b6ef53a
commit 676042268b
122 changed files with 26354 additions and 1615 deletions

View 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>