carousel, agenda arquivados, agenda cor, agenda arquivados, grupos pacientes, pacientes arquivados - desativados, sessoes verificadas, ajuste notificações, Prontuario, Agenda Animation, Menu Profile, bagdes Profile, Offline

This commit is contained in:
Leonardo
2026-03-18 09:26:09 -03:00
parent 66f67cd40f
commit d6d2fe29d1
55 changed files with 3655 additions and 1512 deletions
+85
View File
@@ -0,0 +1,85 @@
// src/composables/useMenuBadges.js
// Singleton — contadores para badges do menu (agenda hoje, cadastros e agendamentos recebidos)
import { ref } from 'vue'
import { supabase } from '@/lib/supabase/client'
import { useTenantStore } from '@/stores/tenantStore'
// ─── estado compartilhado ──────────────────────────────────
const agendaHoje = ref(0)
const cadastrosRecebidos = ref(0)
const agendamentosRecebidos = ref(0)
let _timer = null
let _started = false
async function _refresh () {
try {
const tenantStore = useTenantStore()
const role = tenantStore.role
const tenantId = tenantStore.activeTenantId || tenantStore.tenantId || tenantStore.tenant?.id || null
const { data: authData } = await supabase.auth.getUser()
const ownerId = authData?.user?.id
if (!ownerId) return
const isClinic = role === 'clinic_admin' || role === 'tenant_admin' || role === 'clinic'
const now = new Date()
const y = now.getFullYear(), mo = now.getMonth(), d = now.getDate()
const startDay = new Date(y, mo, d).toISOString()
const endDay = new Date(y, mo, d + 1).toISOString()
// 1. Agenda hoje
{
let q = supabase
.from('agenda_eventos')
.select('id', { count: 'exact', head: true })
.gte('inicio_em', startDay)
.lt('inicio_em', endDay)
if (isClinic && tenantId) q = q.eq('tenant_id', tenantId)
else q = q.eq('owner_id', ownerId)
const { count } = await q
agendaHoje.value = count || 0
}
// 2. Cadastros recebidos (status = 'new') — RLS filtra pelo owner
{
const { count } = await supabase
.from('patient_intake_requests')
.select('id', { count: 'exact', head: true })
.eq('status', 'new')
cadastrosRecebidos.value = count || 0
}
// 3. Agendamentos recebidos (status = 'pendente')
{
let q = supabase
.from('agendador_solicitacoes')
.select('id', { count: 'exact', head: true })
.eq('status', 'pendente')
if (isClinic && tenantId) q = q.eq('tenant_id', tenantId)
else q = q.eq('owner_id', ownerId)
const { count } = await q
agendamentosRecebidos.value = count || 0
}
} catch {
// badge falhar não deve quebrar a navegação
}
}
// ─── API pública ───────────────────────────────────────────
export function useMenuBadges () {
if (!_started) {
_started = true
_refresh()
_timer = setInterval(_refresh, 5 * 60 * 1000) // atualiza a cada 5 min
}
return {
agendaHoje,
cadastrosRecebidos,
agendamentosRecebidos,
refresh: _refresh
}
}
+74
View File
@@ -0,0 +1,74 @@
// src/composables/usePatientLifecycle.js
import { supabase } from '@/lib/supabase/client'
export function usePatientLifecycle () {
async function canDelete (patientId) {
const { data, error } = await supabase.rpc('can_delete_patient', { p_patient_id: patientId })
if (error) return false
return !!data
}
async function deletePatient (patientId) {
const { data, error } = await supabase.rpc('safe_delete_patient', { p_patient_id: patientId })
if (error) return { ok: false, error: 'rpc_error', message: error.message }
return data // { ok, error?, message? }
}
async function checkActiveSchedule (patientId) {
const now = new Date().toISOString()
const [evts, recs] = await Promise.all([
supabase
.from('agenda_eventos')
.select('id', { count: 'exact', head: true })
.eq('patient_id', patientId)
.eq('status', 'agendado')
.gt('inicio_em', now),
supabase
.from('recurrence_rules')
.select('id', { count: 'exact', head: true })
.eq('patient_id', patientId)
.eq('status', 'ativo')
])
return {
hasFutureSessions: (evts.count ?? 0) > 0,
hasActiveRecurrence: (recs.count ?? 0) > 0
}
}
async function deactivatePatient (patientId) {
const { error } = await supabase
.from('patients')
.update({ status: 'Inativo', updated_at: new Date().toISOString() })
.eq('id', patientId)
return error ? { ok: false, error } : { ok: true }
}
async function archivePatient (patientId) {
const { error } = await supabase
.from('patients')
.update({ status: 'Arquivado', updated_at: new Date().toISOString() })
.eq('id', patientId)
return error ? { ok: false, error } : { ok: true }
}
async function reactivatePatient (patientId) {
const { error } = await supabase
.from('patients')
.update({ status: 'Ativo', updated_at: new Date().toISOString() })
.eq('id', patientId)
return error ? { ok: false, error } : { ok: true }
}
return { canDelete, deletePatient, checkActiveSchedule, deactivatePatient, archivePatient, reactivatePatient }
}
// ─── Helper puro — não precisa de instância do composable ───────────────────
export function getPatientAgendaPermissions (status) {
return {
canCreateSession: !['Inativo', 'Arquivado'].includes(status),
canReschedule: !['Inativo'].includes(status),
canEditPastSession: !['Arquivado'].includes(status),
canCreateRecurrence: !['Inativo', 'Arquivado'].includes(status),
}
}