Analytics 7.1: tempo médio de 1ª resposta WhatsApp no dashboard

Card novo pra clínica e terapeuta com 3 métricas + sparkline:
- Tempo médio (e mediana) de 1ª resposta no período
- Taxa de SLA cumprido — % de respostas dentro do threshold configurado
- Contagem total de respostas no período
- Sparkline da evolução com indicador de tendência (melhorando/piorando)
- Ranking top 5 terapeutas (só no ClinicDashboard)

Filtro de período: 7/30/90 dias (muda granularidade do bucket:
1/7/15 dias pra sparkline com ~5-6 pontos).

Banco (migration 20260423000006):
- Helper interno _first_response_runs: identifica "runs" de inbound
  (sequências do paciente sem outbound entre) e calcula delta até a
  próxima outbound. Evita contar múltiplas mensagens repetidas do
  paciente. responder_id vem de conversation_assignments.
- first_response_stats: agregados (count, avg, median, min, max,
  sla_compliance_rate baseado em conversation_sla_rules).
- first_response_by_therapist: ranking com avg e count por assigned_to.
- first_response_evolution: série temporal com bucket alinhado a
  p_from (p_from + bucket_index * N days). Parâmetro p_bucket_days
  deixa o frontend escolher granularidade por período.

Todas SECURITY DEFINER + GRANT authenticated/service_role. Filtro
opcional por therapist_id nas funções que aplicam.

Frontend:
- useFirstResponseAnalytics composable wraps as 3 RPCs com cache
  via Promise.all paralelo. Helper formatSeconds (Ns/Xmin/Xh).
- FirstResponseCard.vue renderiza sparkline SVG nativo
  (sem lib extra), cor da taxa SLA por threshold (verde ≥80%,
  âmbar ≥50%, vermelho).
- Integrado em ClinicDashboard (visão global) e TherapistDashboard
  (filtrado por ownerId, sem ranking).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-04-23 11:51:55 -03:00
parent 36fbc02e9f
commit adf9208d2d
5 changed files with 546 additions and 0 deletions
@@ -22,6 +22,7 @@ import Menu from 'primevue/menu';
import { supabase } from '@/lib/supabase/client';
import { useTenantStore } from '@/stores/tenantStore';
import { useClinicKPIs } from '@/composables/useClinicKPIs';
import FirstResponseCard from '@/components/dashboard/FirstResponseCard.vue';
// Fase 3a — KPIs financeiros/operacionais da clínica
const kpis = useClinicKPIs();
@@ -953,6 +954,9 @@ onMounted(async () => {
<div class="dash-card__foot" @click="$router.push('/admin/agendamentos-recebidos')">Ver todas </div>
</div>
<!-- Analytics: tempo de resposta WhatsApp -->
<FirstResponseCard v-if="!loading" />
<!-- Cadastros externos -->
<div v-if="!loading" class="dash-card">
<div class="dash-card__head gap-2.5 p-2.5">