Preficicação, Convenio, Ajustes Agenda, Configurações Excessões
This commit is contained in:
@@ -31,7 +31,8 @@
|
||||
<!-- Controles desktop (≥1200px) -->
|
||||
<div class="hidden xl:flex items-center gap-2 shrink-0">
|
||||
<Button icon="pi pi-refresh" severity="secondary" outlined class="h-9 w-9 rounded-full" :loading="loading" title="Atualizar" @click="fetchAll" />
|
||||
<SplitButton label="Cadastrar" icon="pi pi-user-plus" :model="createMenu" class="rounded-full" @click="goCreateFull" />
|
||||
<Button icon="pi pi-percentage" severity="secondary" outlined class="h-9 w-9 rounded-full" title="Descontos por Paciente" @click="router.push('/configuracoes/descontos')" />
|
||||
<SplitButton label="Novo" icon="pi pi-user-plus" :model="createMenu" class="rounded-full" @click="goCreateFull" />
|
||||
</div>
|
||||
|
||||
<!-- Menu mobile (<1200px) -->
|
||||
@@ -413,7 +414,23 @@
|
||||
<Avatar v-if="data.avatar_url" :image="data.avatar_url" shape="square" size="large" />
|
||||
<Avatar v-else :label="initials(data.nome_completo)" shape="square" size="large" />
|
||||
<div class="min-w-0">
|
||||
<div class="font-medium truncate">{{ data.nome_completo }}</div>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="font-medium truncate">{{ data.nome_completo }}</span>
|
||||
<Tag
|
||||
v-if="discountMap[data.id]"
|
||||
:value="fmtDiscount(discountMap[data.id])"
|
||||
severity="success"
|
||||
class="shrink-0"
|
||||
style="font-size: 0.7rem; padding: 1px 6px;"
|
||||
/>
|
||||
<Tag
|
||||
v-if="insuranceMap[data.id]"
|
||||
:value="insuranceMap[data.id]"
|
||||
severity="info"
|
||||
class="shrink-0"
|
||||
style="font-size: 0.7rem; padding: 1px 6px;"
|
||||
/>
|
||||
</div>
|
||||
<small class="text-color-secondary">ID: {{ shortId(data.id) }}</small>
|
||||
</div>
|
||||
</div>
|
||||
@@ -509,7 +526,23 @@
|
||||
<Avatar v-if="pat.avatar_url" :image="pat.avatar_url" shape="square" size="large" />
|
||||
<Avatar v-else :label="initials(pat.nome_completo)" shape="square" size="large" />
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="font-semibold truncate">{{ pat.nome_completo }}</div>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="font-semibold truncate">{{ pat.nome_completo }}</span>
|
||||
<Tag
|
||||
v-if="discountMap[pat.id]"
|
||||
:value="fmtDiscount(discountMap[pat.id])"
|
||||
severity="success"
|
||||
class="shrink-0"
|
||||
style="font-size: 0.7rem; padding: 1px 6px;"
|
||||
/>
|
||||
<Tag
|
||||
v-if="insuranceMap[pat.id]"
|
||||
:value="insuranceMap[pat.id]"
|
||||
severity="info"
|
||||
class="shrink-0"
|
||||
style="font-size: 0.7rem; padding: 1px 6px;"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-xs text-color-secondary">{{ fmtPhoneBR(pat.telefone) }} · {{ pat.email_principal || '—' }}</div>
|
||||
</div>
|
||||
<Tag :value="pat.status" :severity="pat.status === 'Ativo' ? 'success' : 'danger'" />
|
||||
@@ -632,10 +665,15 @@
|
||||
|
||||
<div v-else class="sess-list">
|
||||
<div v-for="ev in sessoesLista" :key="ev.id" class="sess-item">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex items-center gap-3 flex-wrap">
|
||||
<Tag :value="ev.status || 'agendado'" :severity="statusSessaoSev(ev.status)" />
|
||||
<span class="font-semibold text-sm">{{ fmtDataSessao(ev.inicio_em) }}</span>
|
||||
<Tag v-if="ev.modalidade" :value="ev.modalidade" severity="secondary" class="ml-auto" />
|
||||
<span v-if="ev.insurance_plans?.name" class="text-xs text-color-secondary flex items-center gap-1">
|
||||
<i class="pi pi-id-card opacity-60" />{{ ev.insurance_plans.name }}
|
||||
<span v-if="ev.insurance_guide_number" class="opacity-70">· Guia: {{ ev.insurance_guide_number }}</span>
|
||||
<span v-if="ev.insurance_value" class="opacity-70">· {{ fmtBRL(ev.insurance_value) }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="ev.titulo" class="text-xs text-color-secondary mt-1">{{ ev.titulo }}</div>
|
||||
</div>
|
||||
@@ -655,6 +693,14 @@ import Popover from 'primevue/popover'
|
||||
import Menu from 'primevue/menu'
|
||||
import ProgressSpinner from 'primevue/progressspinner'
|
||||
|
||||
// ── Descontos por paciente ────────────────────────────────────────
|
||||
// Map de patient_id → { discount_pct, discount_flat }
|
||||
const discountMap = ref({})
|
||||
|
||||
// ── Convênio por paciente ─────────────────────────────────────────
|
||||
// Map de patient_id → nome do convênio mais recente
|
||||
const insuranceMap = ref({})
|
||||
|
||||
|
||||
import PatientProntuario from '@/features/patients/prontuario/PatientProntuario.vue'
|
||||
import ComponentCadastroRapido from '@/components/ComponentCadastroRapido.vue'
|
||||
@@ -686,7 +732,7 @@ async function abrirSessoes (pat) {
|
||||
const [evts, recs] = await Promise.all([
|
||||
supabase
|
||||
.from('agenda_eventos')
|
||||
.select('id, titulo, tipo, status, inicio_em, fim_em, modalidade')
|
||||
.select('id, titulo, tipo, status, inicio_em, fim_em, modalidade, insurance_guide_number, insurance_value, insurance_plans(name)')
|
||||
.eq('patient_id', pat.id)
|
||||
.order('inicio_em', { ascending: false })
|
||||
.limit(100),
|
||||
@@ -746,6 +792,8 @@ const patMobileMenuItems = [
|
||||
{ label: 'Cadastro Rápido', icon: 'pi pi-bolt', command: () => openQuickCreate() },
|
||||
{ label: 'Cadastro Completo', icon: 'pi pi-file-edit', command: () => goCreateFull() },
|
||||
{ separator: true },
|
||||
{ label: 'Descontos por Paciente', icon: 'pi pi-percentage', command: () => router.push('/configuracoes/descontos') },
|
||||
{ separator: true },
|
||||
{ label: 'Atualizar', icon: 'pi pi-refresh', command: () => fetchAll() }
|
||||
]
|
||||
|
||||
@@ -870,6 +918,19 @@ onMounted(async () => {
|
||||
|
||||
onBeforeUnmount(() => { _observer?.disconnect() })
|
||||
|
||||
function fmtBRL (v) {
|
||||
if (v == null || v === '') return '—'
|
||||
return Number(v).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' })
|
||||
}
|
||||
|
||||
function fmtDiscount (d) {
|
||||
if (!d) return null
|
||||
const parts = []
|
||||
if (Number(d.discount_pct) > 0) parts.push(`${Number(d.discount_pct)}%`)
|
||||
if (Number(d.discount_flat) > 0) parts.push(fmtBRL(d.discount_flat))
|
||||
return parts.length ? parts.join(' + ') : null
|
||||
}
|
||||
|
||||
function fmtPhoneBR(v) {
|
||||
const d = String(v ?? '').replace(/\D/g, '')
|
||||
if (!d) return '—'
|
||||
@@ -1144,6 +1205,41 @@ async function fetchAll() {
|
||||
const base = await listPatients()
|
||||
patients.value = base
|
||||
|
||||
// Carrega descontos ativos de todos os pacientes de uma vez
|
||||
discountMap.value = {}
|
||||
if (uid.value) {
|
||||
const now = new Date().toISOString()
|
||||
const { data: discRows } = await supabase
|
||||
.from('patient_discounts')
|
||||
.select('patient_id, discount_pct, discount_flat')
|
||||
.eq('owner_id', uid.value)
|
||||
.eq('active', true)
|
||||
.or(`active_to.is.null,active_to.gte.${now}`)
|
||||
if (discRows) {
|
||||
discountMap.value = Object.fromEntries(
|
||||
discRows.map(d => [d.patient_id, d])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Carrega convênio mais recente por paciente
|
||||
insuranceMap.value = {}
|
||||
if (uid.value) {
|
||||
const { data: insRows } = await supabase
|
||||
.from('agenda_eventos')
|
||||
.select('patient_id, insurance_plan_id, insurance_plans(name)')
|
||||
.eq('owner_id', uid.value)
|
||||
.not('insurance_plan_id', 'is', null)
|
||||
.order('inicio_em', { ascending: false })
|
||||
if (insRows) {
|
||||
for (const row of insRows) {
|
||||
if (!insuranceMap.value[row.patient_id]) {
|
||||
insuranceMap.value[row.patient_id] = row.insurance_plans?.name ?? null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
groups.value = await listGroups()
|
||||
console.log('[PatientsListPage] groups loaded:', groups.value)
|
||||
|
||||
@@ -1459,4 +1555,4 @@ function updateKpis() {
|
||||
background: var(--surface-ground);
|
||||
font-size: .85rem;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
Reference in New Issue
Block a user