F3 schema-per-tenant: frontend usa tenantDb() pra tabelas tenant
- useTenantDb composable + lib/supabase/tenantClient (tenantDb/tenantSchemaName)
- tenantStore: getters activeTenantSlug/activeTenantSchema; my_tenants() RPC
passa a devolver slug+nome (migration 07)
- codemod scripts/codemod-tenant-db.py: supabase.from('<84 tabelas + 6 views
tenant>') -> tenantDb().from(...) em 139 arquivos (777 chamadas), remove
.eq('tenant_id') das cadeias tenant (173)
- passada manual (4 agentes): remove tenant_id de payloads insert/upsert/update,
selects, .or/.is de defaults; onConflict ajustado pros uniques sem tenant_id
(singletons usam 'singleton'); realtime de tabelas tenant aponta pro schema
do tenant ativo; repos dropam tenant_id defensivamente de payloads externos
- agendaSelects: tenant_id fora do AGENDA_EVENT_SELECT (quebraria PostgREST)
- zero embeds cross-schema (todos FK embeds sao tenant->tenant ou global->global)
- build de producao passa; 67 .js checados
Pendente (fora do escopo F3, sao cross-tenant/anon -> F4/F6):
- AgendadorPublicoPage (anon, resolve tenant por link_slug)
- Saas{Feriados,NotificationTemplates,DocumentTemplates,Whatsapp}Page
(gerenciam defaults do sistema / views cross-tenant)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@ import { computed, onMounted, onBeforeUnmount, provide, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useConversationDrawerStore } from '@/stores/conversationDrawerStore';
|
||||
|
||||
const router = useRouter();
|
||||
@@ -56,10 +57,9 @@ async function openConversationDrawer(threadKey) {
|
||||
if (!threadKey) return;
|
||||
try {
|
||||
const tenantId = tenantStore.activeTenantId;
|
||||
const { data, error } = await supabase
|
||||
.from('conversation_threads')
|
||||
const { data, error } = await tenantDb().from('conversation_threads')
|
||||
.select('*')
|
||||
.eq('tenant_id', tenantId)
|
||||
|
||||
.eq('thread_key', threadKey)
|
||||
.maybeSingle();
|
||||
if (error) throw error;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
@@ -120,7 +121,7 @@ async function loadBloqueios() {
|
||||
if (!ownerId.value) return;
|
||||
loadingB.value = true;
|
||||
try {
|
||||
const { data, error } = await supabase.from('agenda_bloqueios').select('*').eq('owner_id', ownerId.value).gte('data_inicio', `${ano.value}-01-01`).lte('data_inicio', `${ano.value}-12-31`).order('data_inicio');
|
||||
const { data, error } = await tenantDb().from('agenda_bloqueios').select('*').eq('owner_id', ownerId.value).gte('data_inicio', `${ano.value}-01-01`).lte('data_inicio', `${ano.value}-12-31`).order('data_inicio');
|
||||
if (error) throw error;
|
||||
bloqueios.value = data || [];
|
||||
} catch (e) {
|
||||
@@ -241,11 +242,11 @@ async function salvarBloqueio() {
|
||||
};
|
||||
|
||||
if (dlgMode.value === 'edit') {
|
||||
const { error } = await supabase.from('agenda_bloqueios').update(payload).eq('id', form.value.id);
|
||||
const { error } = await tenantDb().from('agenda_bloqueios').update(payload).eq('id', form.value.id);
|
||||
if (error) throw error;
|
||||
toast.add({ severity: 'success', summary: 'Salvo', detail: 'Bloqueio atualizado.', life: 1800 });
|
||||
} else {
|
||||
const { error } = await supabase.from('agenda_bloqueios').insert(payload);
|
||||
const { error } = await tenantDb().from('agenda_bloqueios').insert(payload);
|
||||
if (error) throw error;
|
||||
toast.add({ severity: 'success', summary: 'Criado', detail: 'Bloqueio adicionado.', life: 1800 });
|
||||
}
|
||||
@@ -270,7 +271,7 @@ function excluirBloqueio(id) {
|
||||
acceptClass: 'p-button-danger',
|
||||
accept: async () => {
|
||||
try {
|
||||
const { error } = await supabase.from('agenda_bloqueios').delete().eq('id', id);
|
||||
const { error } = await tenantDb().from('agenda_bloqueios').delete().eq('id', id);
|
||||
if (error) throw error;
|
||||
bloqueios.value = bloqueios.value.filter((b) => b.id !== id);
|
||||
toast.add({ severity: 'success', summary: 'Removido', life: 1500 });
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
-->
|
||||
<script setup>
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { computed, nextTick, onMounted, ref, watch } from 'vue';
|
||||
|
||||
@@ -381,11 +382,11 @@ async function getActiveTenantId(uid) {
|
||||
}
|
||||
|
||||
async function seedConfigIfMissing(uid) {
|
||||
const { data: existing } = await supabase.from('agenda_configuracoes').select('owner_id').eq('owner_id', uid).maybeSingle();
|
||||
const { data: existing } = await tenantDb().from('agenda_configuracoes').select('owner_id').eq('owner_id', uid).maybeSingle();
|
||||
if (existing) return; // já existe, não toca
|
||||
|
||||
const tenantId = await getActiveTenantId(uid);
|
||||
const { error } = await supabase.from('agenda_configuracoes').insert({ owner_id: uid, tenant_id: tenantId, session_duration_min: 40, session_break_min: 10 });
|
||||
const { error } = await tenantDb().from('agenda_configuracoes').insert({ owner_id: uid, session_duration_min: 40, session_break_min: 10 });
|
||||
if (error) throw error;
|
||||
}
|
||||
|
||||
@@ -397,7 +398,7 @@ async function loadConfig() {
|
||||
|
||||
await seedConfigIfMissing(uid);
|
||||
|
||||
const { data, error } = await supabase.from('agenda_configuracoes').select('*').eq('owner_id', uid).order('created_at', { ascending: false }).limit(1).maybeSingle();
|
||||
const { data, error } = await tenantDb().from('agenda_configuracoes').select('*').eq('owner_id', uid).order('created_at', { ascending: false }).limit(1).maybeSingle();
|
||||
if (error) throw error;
|
||||
if (data) {
|
||||
cfg.value = { ...cfg.value, ...data };
|
||||
@@ -409,7 +410,7 @@ async function loadRegras() {
|
||||
const uid = ownerId.value || (await getOwnerId());
|
||||
ownerId.value = uid;
|
||||
|
||||
const { data, error } = await supabase.from('agenda_regras_semanais').select('*').eq('owner_id', uid).order('dia_semana').order('hora_inicio');
|
||||
const { data, error } = await tenantDb().from('agenda_regras_semanais').select('*').eq('owner_id', uid).order('dia_semana').order('hora_inicio');
|
||||
if (error) throw error;
|
||||
|
||||
const rows = (data || []).map((r) => ({
|
||||
@@ -427,7 +428,7 @@ async function loadOnlineSlots() {
|
||||
ownerId.value = uid;
|
||||
resetOnlineSlots();
|
||||
|
||||
const { data, error } = await supabase.from('agenda_online_slots').select('weekday,time,enabled').eq('owner_id', uid);
|
||||
const { data, error } = await tenantDb().from('agenda_online_slots').select('weekday,time,enabled').eq('owner_id', uid);
|
||||
if (error) {
|
||||
console.warn('[CFG] loadOnlineSlots:', error);
|
||||
return;
|
||||
@@ -492,12 +493,10 @@ async function saveJornada() {
|
||||
cfg.value.pausas_semanais = pausasToSave;
|
||||
|
||||
const igualTodos = jornadaIgualTodos.value !== false;
|
||||
const { data: cfgSaved, error: cfgErr } = await supabase
|
||||
.from('agenda_configuracoes')
|
||||
const { data: cfgSaved, error: cfgErr } = await tenantDb().from('agenda_configuracoes')
|
||||
.upsert(
|
||||
{
|
||||
owner_id: uid,
|
||||
tenant_id: tenantId,
|
||||
pausas_semanais: pausasToSave,
|
||||
jornada_igual_todos: igualTodos,
|
||||
timezone: cfg.value.timezone || 'America/Sao_Paulo',
|
||||
@@ -513,13 +512,13 @@ async function saveJornada() {
|
||||
const rows = selectedDays.value.map((d) => {
|
||||
const isWeekend = d.value === 6 || d.value === 0;
|
||||
const t = jornadaIgualTodos.value === false || isWeekend ? jornadaPorDia.value[d.value] || { inicio: jornadaStart.value, fim: jornadaEnd.value } : { inicio: jornadaStart.value, fim: jornadaEnd.value };
|
||||
return { owner_id: uid, tenant_id: tenantId, dia_semana: d.value, hora_inicio: normalizeTime(t.inicio), hora_fim: normalizeTime(t.fim), modalidade: 'ambos', ativo: true };
|
||||
return { owner_id: uid, dia_semana: d.value, hora_inicio: normalizeTime(t.inicio), hora_fim: normalizeTime(t.fim), modalidade: 'ambos', ativo: true };
|
||||
});
|
||||
|
||||
const { error: delErr } = await supabase.from('agenda_regras_semanais').delete().eq('owner_id', uid);
|
||||
const { error: delErr } = await tenantDb().from('agenda_regras_semanais').delete().eq('owner_id', uid);
|
||||
if (delErr) throw delErr;
|
||||
if (rows.length) {
|
||||
const { error: insErr } = await supabase.from('agenda_regras_semanais').insert(rows);
|
||||
const { error: insErr } = await tenantDb().from('agenda_regras_semanais').insert(rows);
|
||||
if (insErr) throw insErr;
|
||||
}
|
||||
|
||||
@@ -527,7 +526,7 @@ async function saveJornada() {
|
||||
const activeDays = new Set(selectedDays.value.map((d) => d.value));
|
||||
const orphanDays = [0, 1, 2, 3, 4, 5, 6].filter((d) => !activeDays.has(d));
|
||||
if (orphanDays.length) {
|
||||
const { error: orphanErr } = await supabase.from('agenda_online_slots').delete().eq('owner_id', uid).in('weekday', orphanDays);
|
||||
const { error: orphanErr } = await tenantDb().from('agenda_online_slots').delete().eq('owner_id', uid).in('weekday', orphanDays);
|
||||
if (orphanErr) console.warn('[CFG] limpeza órfãos:', orphanErr);
|
||||
else for (const d of orphanDays) _setDay(d, new Set());
|
||||
}
|
||||
@@ -563,7 +562,7 @@ async function saveRitmo() {
|
||||
const uid = ownerId.value || (await getOwnerId());
|
||||
ownerId.value = uid;
|
||||
|
||||
const { error } = await supabase.from('agenda_configuracoes').upsert(
|
||||
const { error } = await tenantDb().from('agenda_configuracoes').upsert(
|
||||
{
|
||||
owner_id: uid,
|
||||
session_duration_min: dur,
|
||||
@@ -589,22 +588,22 @@ async function saveOnline() {
|
||||
const tenantId = await getActiveTenantId(uid);
|
||||
|
||||
// salvar flag online_ativo
|
||||
const { error: cfgErr } = await supabase.from('agenda_configuracoes').upsert({ owner_id: uid, online_ativo: cfg.value.online_ativo }, { onConflict: 'owner_id' });
|
||||
const { error: cfgErr } = await tenantDb().from('agenda_configuracoes').upsert({ owner_id: uid, online_ativo: cfg.value.online_ativo }, { onConflict: 'owner_id' });
|
||||
if (cfgErr) throw cfgErr;
|
||||
|
||||
// salvar slots
|
||||
const { error: delErr } = await supabase.from('agenda_online_slots').delete().eq('owner_id', uid);
|
||||
const { error: delErr } = await tenantDb().from('agenda_online_slots').delete().eq('owner_id', uid);
|
||||
if (delErr) throw delErr;
|
||||
|
||||
if (cfg.value.online_ativo) {
|
||||
const rows = [];
|
||||
for (const d of selectedDays.value) {
|
||||
for (const hhmm of onlineSlotsByDay.value[d.value] || new Set()) {
|
||||
if (isValidHHMM(hhmm)) rows.push({ owner_id: uid, tenant_id: tenantId, weekday: Number(d.value), time: normalizeTime(hhmm), enabled: true });
|
||||
if (isValidHHMM(hhmm)) rows.push({ owner_id: uid, weekday: Number(d.value), time: normalizeTime(hhmm), enabled: true });
|
||||
}
|
||||
}
|
||||
if (rows.length) {
|
||||
const { error: insErr } = await supabase.from('agenda_online_slots').insert(rows);
|
||||
const { error: insErr } = await tenantDb().from('agenda_online_slots').insert(rows);
|
||||
if (insErr) throw insErr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { RouterLink, useRoute } from 'vue-router';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { useEntitlementsStore } from '@/stores/entitlementsStore';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
@@ -78,7 +79,7 @@ async function onFileSelected(event, field) {
|
||||
// Persiste imediatamente no banco sem fechar o accordion
|
||||
const uid = ownerId.value;
|
||||
const tenantId = await getActiveTenantId(uid);
|
||||
await supabase.from('agendador_configuracoes').upsert({ owner_id: uid, tenant_id: tenantId, ...buildPayload('identidade'), updated_at: new Date().toISOString() }, { onConflict: 'owner_id' });
|
||||
await tenantDb().from('agendador_configuracoes').upsert({ owner_id: uid, ...buildPayload('identidade'), updated_at: new Date().toISOString() }, { onConflict: 'owner_id' });
|
||||
toast.add({ severity: 'success', summary: 'Imagem salva', life: 2000 });
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -283,7 +284,7 @@ const pixChaveEfetiva = computed(() => cfg.value.pix_chave || paymentSettings.va
|
||||
|
||||
async function loadPaymentSettings(uid) {
|
||||
try {
|
||||
const { data } = await supabase.from('payment_settings').select('pix_ativo, pix_chave, pix_tipo, deposito_ativo, dinheiro_ativo, cartao_ativo, convenio_ativo').eq('owner_id', uid).maybeSingle();
|
||||
const { data } = await tenantDb().from('payment_settings').select('pix_ativo, pix_chave, pix_tipo, deposito_ativo, dinheiro_ativo, cartao_ativo, convenio_ativo').eq('owner_id', uid).maybeSingle();
|
||||
paymentSettings.value = data || {};
|
||||
} catch {
|
||||
paymentSettings.value = {};
|
||||
@@ -360,7 +361,7 @@ async function load() {
|
||||
const uid = await getOwnerId();
|
||||
ownerId.value = uid;
|
||||
|
||||
const [{ data, error }] = await Promise.all([supabase.from('agendador_configuracoes').select('*').eq('owner_id', uid).maybeSingle(), loadPaymentSettings(uid)]);
|
||||
const [{ data, error }] = await Promise.all([tenantDb().from('agendador_configuracoes').select('*').eq('owner_id', uid).maybeSingle(), loadPaymentSettings(uid)]);
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
@@ -399,7 +400,7 @@ async function toggleAtivo() {
|
||||
try {
|
||||
const tenantId = await getActiveTenantId(uid);
|
||||
|
||||
await supabase.from('agendador_configuracoes').upsert({ owner_id: uid, tenant_id: tenantId, ativo: novoAtivo, updated_at: new Date().toISOString() }, { onConflict: 'owner_id' });
|
||||
await tenantDb().from('agendador_configuracoes').upsert({ owner_id: uid, ativo: novoAtivo, updated_at: new Date().toISOString() }, { onConflict: 'owner_id' });
|
||||
|
||||
toast.add({
|
||||
severity: novoAtivo ? 'success' : 'info',
|
||||
@@ -421,7 +422,7 @@ async function saveCard(cardKey) {
|
||||
const tenantId = await getActiveTenantId(uid);
|
||||
const payload = buildPayload(cardKey);
|
||||
|
||||
await supabase.from('agendador_configuracoes').upsert({ owner_id: uid, tenant_id: tenantId, ...payload, updated_at: new Date().toISOString() }, { onConflict: 'owner_id' });
|
||||
await tenantDb().from('agendador_configuracoes').upsert({ owner_id: uid, ...payload, updated_at: new Date().toISOString() }, { onConflict: 'owner_id' });
|
||||
|
||||
toast.add({ severity: 'success', summary: 'Salvo', life: 2500 });
|
||||
expandedCard.value = new Set();
|
||||
|
||||
@@ -19,6 +19,7 @@ import { ref, computed, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
|
||||
const router = useRouter();
|
||||
@@ -136,11 +137,11 @@ async function loadUser() {
|
||||
async function loadWhatsApp() {
|
||||
if (!tenantId.value) return;
|
||||
|
||||
let { data } = await supabase.from('notification_channels').select('credentials, connection_status').eq('tenant_id', tenantId.value).eq('channel', 'whatsapp').is('deleted_at', null).maybeSingle();
|
||||
let { data } = await tenantDb().from('notification_channels').select('credentials, connection_status').eq('channel', 'whatsapp').is('deleted_at', null).maybeSingle();
|
||||
|
||||
// Fallback owner_id
|
||||
if (!data && userId.value && userId.value !== tenantId.value) {
|
||||
const fb = await supabase.from('notification_channels').select('credentials, connection_status').eq('owner_id', userId.value).eq('channel', 'whatsapp').is('deleted_at', null).maybeSingle();
|
||||
const fb = await tenantDb().from('notification_channels').select('credentials, connection_status').eq('owner_id', userId.value).eq('channel', 'whatsapp').is('deleted_at', null).maybeSingle();
|
||||
data = fb.data;
|
||||
}
|
||||
|
||||
@@ -184,7 +185,7 @@ async function loadSms() {
|
||||
async function loadEmail() {
|
||||
if (!tenantId.value) return;
|
||||
|
||||
const { count } = await supabase.from('email_templates_tenant').select('id', { count: 'exact', head: true }).eq('tenant_id', tenantId.value);
|
||||
const { count } = await tenantDb().from('email_templates_tenant').select('id', { count: 'exact', head: true });
|
||||
|
||||
email.value.templatesCount = count || 0;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useConfirm } from 'primevue/useconfirm';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
const toast = useToast();
|
||||
const confirm = useConfirm();
|
||||
const tenantStore = useTenantStore();
|
||||
@@ -53,10 +54,9 @@ async function loadConfig() {
|
||||
if (!tenantId.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('conversation_bots')
|
||||
const { data, error } = await tenantDb().from('conversation_bots')
|
||||
.select('*')
|
||||
.eq('tenant_id', tenantId.value)
|
||||
|
||||
.maybeSingle();
|
||||
if (error) throw error;
|
||||
if (data) {
|
||||
@@ -139,7 +139,6 @@ async function saveConfig() {
|
||||
saving.value = true;
|
||||
try {
|
||||
const payload = {
|
||||
tenant_id: tenantId.value,
|
||||
enabled: !!config.value.enabled,
|
||||
greeting_message: String(config.value.greeting_message || '').trim().slice(0, 1000),
|
||||
closing_message: String(config.value.closing_message || '').trim().slice(0, 1000),
|
||||
@@ -155,9 +154,8 @@ async function saveConfig() {
|
||||
idle_timeout_minutes: Math.max(5, Math.min(1440, Number(config.value.idle_timeout_minutes) || 30)),
|
||||
respect_optout: !!config.value.respect_optout
|
||||
};
|
||||
const { error } = await supabase
|
||||
.from('conversation_bots')
|
||||
.upsert(payload, { onConflict: 'tenant_id' });
|
||||
const { error } = await tenantDb().from('conversation_bots')
|
||||
.upsert(payload, { onConflict: 'singleton' });
|
||||
if (error) throw error;
|
||||
toast.add({ severity: 'success', summary: 'Configuração salva', life: 2500 });
|
||||
loadSessions();
|
||||
@@ -173,10 +171,9 @@ async function loadSessions() {
|
||||
sessionsLoading.value = true;
|
||||
try {
|
||||
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 3600 * 1000).toISOString();
|
||||
const { data, error } = await supabase
|
||||
.from('conversation_bot_sessions')
|
||||
const { data, error } = await tenantDb().from('conversation_bot_sessions')
|
||||
.select('id, thread_key, contact_number, current_step, collected_data, status, started_at, completed_at, abandoned_at')
|
||||
.eq('tenant_id', tenantId.value)
|
||||
|
||||
.gte('started_at', sevenDaysAgo)
|
||||
.order('started_at', { ascending: false })
|
||||
.limit(30);
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useToast } from 'primevue/usetoast';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
const toast = useToast();
|
||||
const tenantStore = useTenantStore();
|
||||
|
||||
@@ -67,10 +68,9 @@ async function loadRule() {
|
||||
if (!tenantId.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('conversation_sla_rules')
|
||||
const { data, error } = await tenantDb().from('conversation_sla_rules')
|
||||
.select('*')
|
||||
.eq('tenant_id', tenantId.value)
|
||||
|
||||
.maybeSingle();
|
||||
if (error) throw error;
|
||||
if (data) {
|
||||
@@ -128,7 +128,6 @@ async function saveRule() {
|
||||
saving.value = true;
|
||||
try {
|
||||
const payload = {
|
||||
tenant_id: tenantId.value,
|
||||
enabled: !!rule.value.enabled,
|
||||
threshold_minutes: Math.round(Number(rule.value.threshold_minutes) || 60),
|
||||
respect_business_hours: !!rule.value.respect_business_hours,
|
||||
@@ -138,9 +137,8 @@ async function saveRule() {
|
||||
alert_scope: rule.value.alert_scope,
|
||||
notify_admin_on_breach: !!rule.value.notify_admin_on_breach
|
||||
};
|
||||
const { error } = await supabase
|
||||
.from('conversation_sla_rules')
|
||||
.upsert(payload, { onConflict: 'tenant_id' });
|
||||
const { error } = await tenantDb().from('conversation_sla_rules')
|
||||
.upsert(payload, { onConflict: 'singleton' });
|
||||
if (error) throw error;
|
||||
toast.add({ severity: 'success', summary: 'Configuração salva', life: 2500 });
|
||||
loadBreaches();
|
||||
@@ -156,10 +154,9 @@ async function loadBreaches() {
|
||||
breachesLoading.value = true;
|
||||
try {
|
||||
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 3600 * 1000).toISOString();
|
||||
const { data, error } = await supabase
|
||||
.from('conversation_sla_breaches')
|
||||
const { data, error } = await tenantDb().from('conversation_sla_breaches')
|
||||
.select('id, thread_key, assigned_to, last_inbound_at, threshold_minutes_at_breach, breached_at, resolved_at, notification_count')
|
||||
.eq('tenant_id', tenantId.value)
|
||||
|
||||
.gte('breached_at', sevenDaysAgo)
|
||||
.order('breached_at', { ascending: false })
|
||||
.limit(30);
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ref, computed, onMounted, reactive, watch } from 'vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { useConversationTags } from '@/composables/useConversationTags';
|
||||
|
||||
@@ -28,10 +29,9 @@ async function loadUsage() {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('conversation_thread_tags')
|
||||
const { data, error } = await tenantDb().from('conversation_thread_tags')
|
||||
.select('tag_id')
|
||||
.eq('tenant_id', tenantId);
|
||||
;
|
||||
if (error) throw error;
|
||||
const counts = {};
|
||||
for (const row of data || []) {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { usePatientDiscounts } from '@/features/agenda/composables/usePatientDiscounts';
|
||||
@@ -177,7 +178,7 @@ onMounted(async () => {
|
||||
ownerId.value = uid;
|
||||
tenantId.value = tenantStore.activeTenantId || null;
|
||||
|
||||
const [, { data: pData }] = await Promise.all([load(uid), supabase.from('patients').select('id, nome_completo').eq('owner_id', uid).eq('status', 'Ativo').order('nome_completo', { ascending: true })]);
|
||||
const [, { data: pData }] = await Promise.all([load(uid), tenantDb().from('patients').select('id, nome_completo').eq('owner_id', uid).eq('status', 'Ativo').order('nome_completo', { ascending: true })]);
|
||||
|
||||
patients.value = pData || [];
|
||||
} catch (e) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import { useToast } from 'primevue/usetoast';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import JoditEmailEditor from '@/components/ui/JoditEmailEditor.vue';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { renderEmail, renderTemplate, generateLayoutSection } from '@/lib/email/emailTemplateService';
|
||||
import { MOCK_DATA, TEMPLATE_DOMAINS } from '@/lib/email/emailTemplateConstants';
|
||||
|
||||
@@ -46,7 +47,7 @@ async function loadUser() {
|
||||
} = await supabase.auth.getUser();
|
||||
if (!user) return;
|
||||
tenantId.value = user.id;
|
||||
const { data } = await supabase.from('company_profiles').select('logo_url').eq('tenant_id', user.id).maybeSingle();
|
||||
const { data } = await tenantDb().from('company_profiles').select('logo_url').maybeSingle();
|
||||
profileLogoUrl.value = data?.logo_url || null;
|
||||
}
|
||||
|
||||
@@ -60,7 +61,7 @@ function defaultSection() {
|
||||
|
||||
async function loadLayoutConfig() {
|
||||
if (!tenantId.value) return;
|
||||
const { data } = await supabase.from('email_layout_config').select('*').eq('tenant_id', tenantId.value).maybeSingle();
|
||||
const { data } = await tenantDb().from('email_layout_config').select('*').maybeSingle();
|
||||
if (data) {
|
||||
layoutConfigId.value = data.id;
|
||||
layoutConfig.value.header = { ...defaultSection(), ...(data.header_config || {}) };
|
||||
@@ -80,7 +81,7 @@ async function load() {
|
||||
try {
|
||||
const [{ data: gData, error: gErr }, { data: oData, error: oErr }] = await Promise.all([
|
||||
supabase.from('email_templates_global').select('*').eq('is_active', true).order('domain').order('key'),
|
||||
supabase.from('email_templates_tenant').select('*').eq('tenant_id', tenantId.value).is('owner_id', null)
|
||||
tenantDb().from('email_templates_tenant').select('*').is('owner_id', null)
|
||||
]);
|
||||
if (gErr) throw gErr;
|
||||
if (oErr) throw oErr;
|
||||
@@ -165,10 +166,10 @@ async function saveLayout() {
|
||||
footer_config: layoutForm.value.footer
|
||||
};
|
||||
if (layoutConfigId.value) {
|
||||
const { error } = await supabase.from('email_layout_config').update(payload).eq('id', layoutConfigId.value);
|
||||
const { error } = await tenantDb().from('email_layout_config').update(payload).eq('id', layoutConfigId.value);
|
||||
if (error) throw error;
|
||||
} else {
|
||||
const { data, error } = await supabase.from('email_layout_config').insert(payload).select('id').single();
|
||||
const { data, error } = await tenantDb().from('email_layout_config').insert(payload).select('id').single();
|
||||
if (error) throw error;
|
||||
layoutConfigId.value = data.id;
|
||||
}
|
||||
@@ -241,11 +242,11 @@ async function save() {
|
||||
synced_version: form.value.synced_version
|
||||
};
|
||||
if (dlg.value.mode === 'create') {
|
||||
const { error } = await supabase.from('email_templates_tenant').insert(payload);
|
||||
const { error } = await tenantDb().from('email_templates_tenant').insert(payload);
|
||||
if (error) throw error;
|
||||
toast.add({ severity: 'success', summary: 'Personalização salva', life: 3000 });
|
||||
} else {
|
||||
const { error } = await supabase.from('email_templates_tenant').update(payload).eq('id', overrideMap.value[form.value.template_key].id);
|
||||
const { error } = await tenantDb().from('email_templates_tenant').update(payload).eq('id', overrideMap.value[form.value.template_key].id);
|
||||
if (error) throw error;
|
||||
toast.add({ severity: 'success', summary: 'Personalização salva', life: 3000 });
|
||||
}
|
||||
@@ -266,7 +267,7 @@ function confirmRevert(row) {
|
||||
acceptClass: 'p-button-danger',
|
||||
accept: async () => {
|
||||
try {
|
||||
const { error } = await supabase.from('email_templates_tenant').delete().eq('id', row.override.id);
|
||||
const { error } = await tenantDb().from('email_templates_tenant').delete().eq('id', row.override.id);
|
||||
if (error) throw error;
|
||||
toast.add({ severity: 'success', summary: 'Revertido para o padrão', life: 3000 });
|
||||
await load();
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useToast } from 'primevue/usetoast';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
const toast = useToast();
|
||||
const confirm = useConfirm();
|
||||
|
||||
@@ -200,7 +201,7 @@ async function load() {
|
||||
} = await supabase.auth.getUser();
|
||||
if (!user) return;
|
||||
tenantId.value = user.id;
|
||||
const { data } = await supabase.from('company_profiles').select('*').eq('tenant_id', user.id).maybeSingle();
|
||||
const { data } = await tenantDb().from('company_profiles').select('*').maybeSingle();
|
||||
if (data) {
|
||||
recordId.value = data.id;
|
||||
Object.keys(form.value).forEach((k) => {
|
||||
@@ -227,7 +228,6 @@ async function save() {
|
||||
if (logoFile.value) form.value.logo_url = await uploadLogo();
|
||||
|
||||
const payload = {
|
||||
tenant_id: tenantId.value,
|
||||
nome_fantasia: form.value.nome_fantasia || null,
|
||||
razao_social: form.value.razao_social || null,
|
||||
tipo_empresa: form.value.tipo_empresa || null,
|
||||
@@ -249,10 +249,10 @@ async function save() {
|
||||
};
|
||||
|
||||
if (recordId.value) {
|
||||
const { error } = await supabase.from('company_profiles').update(payload).eq('id', recordId.value);
|
||||
const { error } = await tenantDb().from('company_profiles').update(payload).eq('id', recordId.value);
|
||||
if (error) throw error;
|
||||
} else {
|
||||
const { data, error } = await supabase.from('company_profiles').insert(payload).select('id').single();
|
||||
const { data, error } = await tenantDb().from('company_profiles').insert(payload).select('id').single();
|
||||
if (error) throw error;
|
||||
recordId.value = data.id;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
|
||||
@@ -121,7 +122,7 @@ async function load() {
|
||||
if (!uid) return;
|
||||
ownerId.value = uid;
|
||||
|
||||
const { data } = await supabase.from('payment_settings').select('*').eq('owner_id', uid).maybeSingle();
|
||||
const { data } = await tenantDb().from('payment_settings').select('*').eq('owner_id', uid).maybeSingle();
|
||||
|
||||
if (data) {
|
||||
cfg.value = { ...DEFAULT, ...data };
|
||||
@@ -137,8 +138,7 @@ async function saveCard(cardKey) {
|
||||
savingCard.value = cardKey;
|
||||
|
||||
const payload = {
|
||||
owner_id: ownerId.value,
|
||||
tenant_id: tenantStore.activeTenantId || null
|
||||
owner_id: ownerId.value
|
||||
};
|
||||
|
||||
if (cardKey === 'pix') {
|
||||
@@ -175,7 +175,7 @@ async function saveCard(cardKey) {
|
||||
}
|
||||
|
||||
try {
|
||||
const { error } = await supabase.from('payment_settings').upsert(payload, { onConflict: 'owner_id' });
|
||||
const { error } = await tenantDb().from('payment_settings').upsert(payload, { onConflict: 'owner_id' });
|
||||
|
||||
if (error) throw error;
|
||||
toast.add({ severity: 'success', summary: 'Salvo', detail: 'Configurações de pagamento atualizadas.', life: 2500 });
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useServices } from '@/features/agenda/composables/useServices';
|
||||
@@ -143,7 +144,7 @@ onMounted(async () => {
|
||||
ownerId.value = uid;
|
||||
tenantId.value = tenantStore.activeTenantId || null;
|
||||
|
||||
const { data: cfg } = await supabase.from('agenda_configuracoes').select('slot_mode').eq('owner_id', uid).maybeSingle();
|
||||
const { data: cfg } = await tenantDb().from('agenda_configuracoes').select('slot_mode').eq('owner_id', uid).maybeSingle();
|
||||
|
||||
slotMode.value = cfg?.slot_mode ?? 'fixed';
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useToast } from 'primevue/usetoast';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
|
||||
const toast = useToast();
|
||||
@@ -106,11 +107,9 @@ async function loadTemplates() {
|
||||
if (!tenantId.value) return;
|
||||
templatesLoading.value = true;
|
||||
try {
|
||||
// 1. Busca templates globais do SaaS (tenant_id IS NULL)
|
||||
const { data: globals, error: gErr } = await supabase
|
||||
.from('notification_templates')
|
||||
// 1. Busca templates default semeados no schema do tenant
|
||||
const { data: globals, error: gErr } = await tenantDb().from('notification_templates')
|
||||
.select('*')
|
||||
.is('tenant_id', null)
|
||||
.eq('channel', 'sms')
|
||||
.eq('is_default', true)
|
||||
.eq('is_active', true)
|
||||
@@ -120,7 +119,7 @@ async function loadTemplates() {
|
||||
if (gErr) throw gErr;
|
||||
|
||||
// 2. Busca customizações do tenant
|
||||
const { data: customs, error: cErr } = await supabase.from('notification_templates').select('*').eq('tenant_id', tenantId.value).eq('channel', 'sms').is('deleted_at', null);
|
||||
const { data: customs, error: cErr } = await tenantDb().from('notification_templates').select('*').eq('channel', 'sms').is('deleted_at', null);
|
||||
if (cErr) throw cErr;
|
||||
|
||||
const customMap = {};
|
||||
@@ -177,21 +176,19 @@ async function saveTemplate(tpl) {
|
||||
templateSaving.value[tpl.key] = true;
|
||||
try {
|
||||
if (tpl.id) {
|
||||
const { error } = await supabase.from('notification_templates').update({ body_text: tpl.body_text }).eq('id', tpl.id);
|
||||
const { error } = await tenantDb().from('notification_templates').update({ body_text: tpl.body_text }).eq('id', tpl.id);
|
||||
if (error) throw error;
|
||||
} else {
|
||||
const { data: existing } = await supabase.from('notification_templates').select('id').eq('tenant_id', tenantId.value).eq('key', tpl.key).is('deleted_at', null).maybeSingle();
|
||||
const { data: existing } = await tenantDb().from('notification_templates').select('id').eq('key', tpl.key).is('deleted_at', null).maybeSingle();
|
||||
|
||||
if (existing?.id) {
|
||||
const { error } = await supabase.from('notification_templates').update({ body_text: tpl.body_text, is_active: true }).eq('id', existing.id);
|
||||
const { error } = await tenantDb().from('notification_templates').update({ body_text: tpl.body_text, is_active: true }).eq('id', existing.id);
|
||||
if (error) throw error;
|
||||
tpl.id = existing.id;
|
||||
} else {
|
||||
const { data, error } = await supabase
|
||||
.from('notification_templates')
|
||||
const { data, error } = await tenantDb().from('notification_templates')
|
||||
.insert({
|
||||
owner_id: userId.value,
|
||||
tenant_id: tenantId.value,
|
||||
channel: 'sms',
|
||||
key: tpl.key,
|
||||
domain: tpl.domain,
|
||||
@@ -271,10 +268,9 @@ async function loadLogs() {
|
||||
if (!tenantId.value) return;
|
||||
logsLoading.value = true;
|
||||
|
||||
const { data } = await supabase
|
||||
.from('notification_logs')
|
||||
const { data } = await tenantDb().from('notification_logs')
|
||||
.select('id, template_key, recipient_address, status, failure_reason, sent_at, failed_at, created_at')
|
||||
.eq('tenant_id', tenantId.value)
|
||||
|
||||
.eq('channel', 'sms')
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(10);
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useRouter, useRoute } from 'vue-router';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
|
||||
const router = useRouter();
|
||||
@@ -54,20 +55,18 @@ async function loadChannel() {
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const { data: active } = await supabase
|
||||
.from('notification_channels')
|
||||
const { data: active } = await tenantDb().from('notification_channels')
|
||||
.select('id, provider, is_active, connection_status, twilio_phone_number, credentials, updated_at')
|
||||
.eq('tenant_id', tenantId)
|
||||
|
||||
.eq('channel', 'whatsapp')
|
||||
.is('deleted_at', null)
|
||||
.maybeSingle();
|
||||
activeChannel.value = active || null;
|
||||
|
||||
// Busca soft-deleted (pra oferecer reativação por provider)
|
||||
const { data: deletedList } = await supabase
|
||||
.from('notification_channels')
|
||||
const { data: deletedList } = await tenantDb().from('notification_channels')
|
||||
.select('id, provider, credentials, created_at')
|
||||
.eq('tenant_id', tenantId)
|
||||
|
||||
.eq('channel', 'whatsapp')
|
||||
.not('deleted_at', 'is', null)
|
||||
.order('created_at', { ascending: false });
|
||||
@@ -185,8 +184,7 @@ async function deactivateCurrent() {
|
||||
switching.value = true;
|
||||
try {
|
||||
// 1ª tentativa: UPDATE direto (funciona se owner_id = user atual)
|
||||
const { error } = await supabase
|
||||
.from('notification_channels')
|
||||
const { error } = await tenantDb().from('notification_channels')
|
||||
.update({ is_active: false, deleted_at: new Date().toISOString() })
|
||||
.eq('id', activeChannel.value.id);
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useRouter, useRoute } from 'vue-router';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
|
||||
const router = useRouter();
|
||||
@@ -96,10 +97,9 @@ async function loadCredentials() {
|
||||
softDeletedRecord.value = null;
|
||||
|
||||
// 1) Tentar canal ativo por tenant_id (evolution_api)
|
||||
let { data, error } = await supabase
|
||||
.from('notification_channels')
|
||||
let { data, error } = await tenantDb().from('notification_channels')
|
||||
.select('*')
|
||||
.eq('tenant_id', tenantId.value)
|
||||
|
||||
.eq('channel', 'whatsapp')
|
||||
.eq('provider', 'evolution_api')
|
||||
.is('deleted_at', null)
|
||||
@@ -107,8 +107,7 @@ async function loadCredentials() {
|
||||
|
||||
// Fallback 1: buscar por owner_id (cenário legado ou tenant solo)
|
||||
if (!data && userId.value && userId.value !== tenantId.value) {
|
||||
const fallback = await supabase
|
||||
.from('notification_channels')
|
||||
const fallback = await tenantDb().from('notification_channels')
|
||||
.select('*')
|
||||
.eq('owner_id', userId.value)
|
||||
.eq('channel', 'whatsapp')
|
||||
@@ -136,10 +135,9 @@ async function loadCredentials() {
|
||||
}
|
||||
|
||||
// 2) Não tem ativo — verifica soft-deleted pra oferecer reativar
|
||||
const { data: deleted } = await supabase
|
||||
.from('notification_channels')
|
||||
const { data: deleted } = await tenantDb().from('notification_channels')
|
||||
.select('*')
|
||||
.eq('tenant_id', tenantId.value)
|
||||
|
||||
.eq('channel', 'whatsapp')
|
||||
.eq('provider', 'evolution_api')
|
||||
.not('deleted_at', 'is', null)
|
||||
@@ -201,8 +199,7 @@ async function checkConnectionStatus() {
|
||||
if (channelRecord.value?.id) {
|
||||
const dbStatus = rawState === 'open' ? 'connected' : rawState === 'connecting' ? 'connecting' : 'disconnected';
|
||||
if (channelRecord.value.connection_status !== dbStatus) {
|
||||
await supabase
|
||||
.from('notification_channels')
|
||||
await tenantDb().from('notification_channels')
|
||||
.update({ connection_status: dbStatus, last_health_check: new Date().toISOString() })
|
||||
.eq('id', channelRecord.value.id);
|
||||
channelRecord.value.connection_status = dbStatus;
|
||||
@@ -254,8 +251,7 @@ async function saveHeartbeatConfig() {
|
||||
heartbeat_alerts_enabled: !!heartbeatConfig.value.alerts_enabled,
|
||||
heartbeat_reconnect_enabled: !!heartbeatConfig.value.reconnect_enabled
|
||||
};
|
||||
const { error } = await supabase
|
||||
.from('notification_channels')
|
||||
const { error } = await tenantDb().from('notification_channels')
|
||||
.update({ metadata: newMeta })
|
||||
.eq('id', channelRecord.value.id);
|
||||
if (error) throw error;
|
||||
@@ -274,8 +270,7 @@ async function loadIncidents() {
|
||||
incidentsLoading.value = true;
|
||||
try {
|
||||
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 3600 * 1000).toISOString();
|
||||
const { data, error } = await supabase
|
||||
.from('whatsapp_connection_incidents')
|
||||
const { data, error } = await tenantDb().from('whatsapp_connection_incidents')
|
||||
.select('id, kind, last_state, started_at, resolved_at, duration_seconds, notified_at')
|
||||
.eq('channel_id', channelRecord.value.id)
|
||||
.gte('started_at', sevenDaysAgo)
|
||||
@@ -503,11 +498,9 @@ async function loadTemplates() {
|
||||
if (!tenantId.value) return;
|
||||
templatesLoading.value = true;
|
||||
try {
|
||||
// 1. Busca templates globais do SaaS (tenant_id IS NULL)
|
||||
const { data: globals, error: gErr } = await supabase
|
||||
.from('notification_templates')
|
||||
// 1. Busca templates default semeados no schema do tenant
|
||||
const { data: globals, error: gErr } = await tenantDb().from('notification_templates')
|
||||
.select('*')
|
||||
.is('tenant_id', null)
|
||||
.eq('channel', 'whatsapp')
|
||||
.eq('is_default', true)
|
||||
.eq('is_active', true)
|
||||
@@ -517,7 +510,7 @@ async function loadTemplates() {
|
||||
if (gErr) throw gErr;
|
||||
|
||||
// 2. Busca customizações do tenant
|
||||
const { data: customs, error: cErr } = await supabase.from('notification_templates').select('*').eq('tenant_id', tenantId.value).eq('channel', 'whatsapp').is('deleted_at', null);
|
||||
const { data: customs, error: cErr } = await tenantDb().from('notification_templates').select('*').eq('channel', 'whatsapp').is('deleted_at', null);
|
||||
if (cErr) throw cErr;
|
||||
|
||||
const customMap = {};
|
||||
@@ -576,24 +569,22 @@ async function saveTemplate(tpl) {
|
||||
try {
|
||||
if (tpl.id) {
|
||||
// Atualizar existente
|
||||
const { error } = await supabase.from('notification_templates').update({ body_text: tpl.body_text }).eq('id', tpl.id);
|
||||
const { error } = await tenantDb().from('notification_templates').update({ body_text: tpl.body_text }).eq('id', tpl.id);
|
||||
if (error) throw error;
|
||||
} else {
|
||||
// Verificar se já existe um registro ativo para esta key
|
||||
const { data: existing } = await supabase.from('notification_templates').select('id').eq('tenant_id', tenantId.value).eq('key', tpl.key).is('deleted_at', null).maybeSingle();
|
||||
const { data: existing } = await tenantDb().from('notification_templates').select('id').eq('key', tpl.key).is('deleted_at', null).maybeSingle();
|
||||
|
||||
if (existing?.id) {
|
||||
// Já existe (criado por outra sessão) — atualizar
|
||||
const { error } = await supabase.from('notification_templates').update({ body_text: tpl.body_text, is_active: true }).eq('id', existing.id);
|
||||
const { error } = await tenantDb().from('notification_templates').update({ body_text: tpl.body_text, is_active: true }).eq('id', existing.id);
|
||||
if (error) throw error;
|
||||
tpl.id = existing.id;
|
||||
} else {
|
||||
// Inserir novo
|
||||
const { data, error } = await supabase
|
||||
.from('notification_templates')
|
||||
const { data, error } = await tenantDb().from('notification_templates')
|
||||
.insert({
|
||||
owner_id: userId.value,
|
||||
tenant_id: tenantId.value,
|
||||
channel: 'whatsapp',
|
||||
key: tpl.key,
|
||||
domain: tpl.domain,
|
||||
@@ -722,7 +713,7 @@ async function loadLogs() {
|
||||
if (!tenantId.value) return;
|
||||
logsLoading.value = true;
|
||||
try {
|
||||
let query = supabase.from('notification_logs').select('*', { count: 'exact' }).eq('tenant_id', tenantId.value).eq('channel', 'whatsapp').order('created_at', { ascending: false });
|
||||
let query = tenantDb().from('notification_logs').select('*', { count: 'exact' }).eq('channel', 'whatsapp').order('created_at', { ascending: false });
|
||||
|
||||
if (logsFilter.value !== 'todos') {
|
||||
query = query.eq('status', logsFilter.value);
|
||||
|
||||
@@ -19,6 +19,7 @@ import { ref, onMounted, nextTick } from 'vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { useConversationDrawerStore } from '@/stores/conversationDrawerStore';
|
||||
|
||||
@@ -77,10 +78,8 @@ async function loadTemplates() {
|
||||
if (!tenantId.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const { data: globals, error: gErr } = await supabase
|
||||
.from('notification_templates')
|
||||
const { data: globals, error: gErr } = await tenantDb().from('notification_templates')
|
||||
.select('*')
|
||||
.is('tenant_id', null)
|
||||
.eq('channel', 'whatsapp')
|
||||
.eq('is_default', true)
|
||||
.eq('is_active', true)
|
||||
@@ -89,10 +88,9 @@ async function loadTemplates() {
|
||||
.order('event_type');
|
||||
if (gErr) throw gErr;
|
||||
|
||||
const { data: customs, error: cErr } = await supabase
|
||||
.from('notification_templates')
|
||||
const { data: customs, error: cErr } = await tenantDb().from('notification_templates')
|
||||
.select('*')
|
||||
.eq('tenant_id', tenantId.value)
|
||||
|
||||
.eq('channel', 'whatsapp')
|
||||
.is('deleted_at', null);
|
||||
if (cErr) throw cErr;
|
||||
@@ -161,21 +159,19 @@ async function saveTemplate(tpl) {
|
||||
saving.value[tpl.key] = true;
|
||||
try {
|
||||
if (tpl.id) {
|
||||
const { error } = await supabase.from('notification_templates').update({ body_text: body }).eq('id', tpl.id);
|
||||
const { error } = await tenantDb().from('notification_templates').update({ body_text: body }).eq('id', tpl.id);
|
||||
if (error) throw error;
|
||||
} else {
|
||||
const { data: existing } = await supabase.from('notification_templates').select('id').eq('tenant_id', tenantId.value).eq('key', tpl.key).is('deleted_at', null).maybeSingle();
|
||||
const { data: existing } = await tenantDb().from('notification_templates').select('id').eq('key', tpl.key).is('deleted_at', null).maybeSingle();
|
||||
|
||||
if (existing?.id) {
|
||||
const { error } = await supabase.from('notification_templates').update({ body_text: body, is_active: true }).eq('id', existing.id);
|
||||
const { error } = await tenantDb().from('notification_templates').update({ body_text: body, is_active: true }).eq('id', existing.id);
|
||||
if (error) throw error;
|
||||
tpl.id = existing.id;
|
||||
} else {
|
||||
const { data, error } = await supabase
|
||||
.from('notification_templates')
|
||||
const { data, error } = await tenantDb().from('notification_templates')
|
||||
.insert({
|
||||
owner_id: userId.value,
|
||||
tenant_id: tenantId.value,
|
||||
channel: 'whatsapp',
|
||||
key: tpl.key,
|
||||
domain: tpl.domain,
|
||||
@@ -215,7 +211,7 @@ function confirmRevert(tpl) {
|
||||
if (reverting.value[tpl.key]) return;
|
||||
reverting.value[tpl.key] = true;
|
||||
try {
|
||||
const { error } = await supabase.from('notification_templates').update({ deleted_at: new Date().toISOString() }).eq('id', tpl.id);
|
||||
const { error } = await tenantDb().from('notification_templates').update({ deleted_at: new Date().toISOString() }).eq('id', tpl.id);
|
||||
if (error) throw error;
|
||||
tpl.id = null;
|
||||
tpl.is_custom = false;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* trocar a área central pelo FullCalendar com tema glass.
|
||||
*/
|
||||
import { ref, computed, onMounted, onBeforeUnmount, watch, inject } from 'vue';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useRouter } from 'vue-router';
|
||||
import FullCalendar from '@fullcalendar/vue3';
|
||||
import timeGridPlugin from '@fullcalendar/timegrid';
|
||||
@@ -705,8 +706,7 @@ const historicoCardRef = ref(null);
|
||||
async function onHistoricoOpen({ id }) {
|
||||
if (!id) return;
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data, error } = await tenantDb().from('agenda_eventos')
|
||||
.select('*, patients!agenda_eventos_patient_id_fkey(nome_completo, status, avatar_url)')
|
||||
.eq('id', id)
|
||||
.maybeSingle();
|
||||
@@ -1096,8 +1096,7 @@ async function ensurePacienteCarregado(id) {
|
||||
if (!id) return;
|
||||
if (pacientesIndex.value.has(id)) return;
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('patients')
|
||||
const { data, error } = await tenantDb().from('patients')
|
||||
.select('id, nome_completo, avatar_url, status, last_attended_at, created_at')
|
||||
.eq('id', id)
|
||||
.maybeSingle();
|
||||
|
||||
@@ -20,6 +20,7 @@ import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
|
||||
import MelissaConfigList from './MelissaConfigList.vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import PausasChipsEditor from '@/components/agenda/PausasChipsEditor.vue';
|
||||
// DatePicker/Select/Skeleton/Tag/ToggleSwitch: auto via PrimeVueResolver
|
||||
@@ -355,16 +356,14 @@ async function getActiveTenantId(uid) {
|
||||
}
|
||||
|
||||
async function seedConfigIfMissing(uid) {
|
||||
const { data: existing } = await supabase
|
||||
.from('agenda_configuracoes')
|
||||
const { data: existing } = await tenantDb().from('agenda_configuracoes')
|
||||
.select('owner_id')
|
||||
.eq('owner_id', uid)
|
||||
.maybeSingle();
|
||||
if (existing) return;
|
||||
const tenantId = await getActiveTenantId(uid);
|
||||
const { error } = await supabase
|
||||
.from('agenda_configuracoes')
|
||||
.insert({ owner_id: uid, tenant_id: tenantId, session_duration_min: 50, session_break_min: 10 });
|
||||
const { error } = await tenantDb().from('agenda_configuracoes')
|
||||
.insert({ owner_id: uid, session_duration_min: 50, session_break_min: 10 });
|
||||
if (error) throw error;
|
||||
}
|
||||
|
||||
@@ -374,8 +373,7 @@ async function loadConfig() {
|
||||
ownerId.value = uid;
|
||||
cfg.value.owner_id = uid;
|
||||
await seedConfigIfMissing(uid);
|
||||
const { data, error } = await supabase
|
||||
.from('agenda_configuracoes')
|
||||
const { data, error } = await tenantDb().from('agenda_configuracoes')
|
||||
.select('*')
|
||||
.eq('owner_id', uid)
|
||||
.order('created_at', { ascending: false })
|
||||
@@ -391,8 +389,7 @@ async function loadConfig() {
|
||||
async function loadRegras() {
|
||||
const uid = ownerId.value || (await getOwnerId());
|
||||
ownerId.value = uid;
|
||||
const { data, error } = await supabase
|
||||
.from('agenda_regras_semanais')
|
||||
const { data, error } = await tenantDb().from('agenda_regras_semanais')
|
||||
.select('*')
|
||||
.eq('owner_id', uid)
|
||||
.order('dia_semana')
|
||||
@@ -411,8 +408,7 @@ async function loadOnlineSlots() {
|
||||
const uid = ownerId.value || (await getOwnerId());
|
||||
ownerId.value = uid;
|
||||
resetOnlineSlots();
|
||||
const { data, error } = await supabase
|
||||
.from('agenda_online_slots')
|
||||
const { data, error } = await tenantDb().from('agenda_online_slots')
|
||||
.select('weekday,time,enabled')
|
||||
.eq('owner_id', uid);
|
||||
if (error) return;
|
||||
@@ -475,12 +471,10 @@ async function saveJornada() {
|
||||
cfg.value.pausas_semanais = pausasToSave;
|
||||
|
||||
const igualTodos = jornadaIgualTodos.value !== false;
|
||||
const { error: cfgErr } = await supabase
|
||||
.from('agenda_configuracoes')
|
||||
const { error: cfgErr } = await tenantDb().from('agenda_configuracoes')
|
||||
.upsert(
|
||||
{
|
||||
owner_id: uid,
|
||||
tenant_id: tenantId,
|
||||
pausas_semanais: pausasToSave,
|
||||
jornada_igual_todos: igualTodos,
|
||||
timezone: cfg.value.timezone || 'America/Sao_Paulo',
|
||||
@@ -499,7 +493,6 @@ async function saveJornada() {
|
||||
: { inicio: jornadaStart.value, fim: jornadaEnd.value };
|
||||
return {
|
||||
owner_id: uid,
|
||||
tenant_id: tenantId,
|
||||
dia_semana: d.value,
|
||||
hora_inicio: normalizeTime(t.inicio),
|
||||
hora_fim: normalizeTime(t.fim),
|
||||
@@ -508,10 +501,10 @@ async function saveJornada() {
|
||||
};
|
||||
});
|
||||
|
||||
const { error: delErr } = await supabase.from('agenda_regras_semanais').delete().eq('owner_id', uid);
|
||||
const { error: delErr } = await tenantDb().from('agenda_regras_semanais').delete().eq('owner_id', uid);
|
||||
if (delErr) throw delErr;
|
||||
if (rows.length) {
|
||||
const { error: insErr } = await supabase.from('agenda_regras_semanais').insert(rows);
|
||||
const { error: insErr } = await tenantDb().from('agenda_regras_semanais').insert(rows);
|
||||
if (insErr) throw insErr;
|
||||
}
|
||||
|
||||
@@ -519,7 +512,7 @@ async function saveJornada() {
|
||||
const activeDays = new Set(selectedDays.value.map((d) => d.value));
|
||||
const orphanDays = [0, 1, 2, 3, 4, 5, 6].filter((d) => !activeDays.has(d));
|
||||
if (orphanDays.length) {
|
||||
await supabase.from('agenda_online_slots').delete().eq('owner_id', uid).in('weekday', orphanDays);
|
||||
await tenantDb().from('agenda_online_slots').delete().eq('owner_id', uid).in('weekday', orphanDays);
|
||||
for (const d of orphanDays) _setDay(d, new Set());
|
||||
}
|
||||
|
||||
@@ -549,8 +542,7 @@ async function saveRitmo() {
|
||||
try {
|
||||
const uid = ownerId.value || (await getOwnerId());
|
||||
ownerId.value = uid;
|
||||
const { error } = await supabase
|
||||
.from('agenda_configuracoes')
|
||||
const { error } = await tenantDb().from('agenda_configuracoes')
|
||||
.upsert(
|
||||
{ owner_id: uid, session_duration_min: dur, session_break_min: gap },
|
||||
{ onConflict: 'owner_id' }
|
||||
@@ -571,12 +563,11 @@ async function saveOnline() {
|
||||
ownerId.value = uid;
|
||||
const tenantId = await getActiveTenantId(uid);
|
||||
|
||||
const { error: cfgErr } = await supabase
|
||||
.from('agenda_configuracoes')
|
||||
const { error: cfgErr } = await tenantDb().from('agenda_configuracoes')
|
||||
.upsert({ owner_id: uid, online_ativo: cfg.value.online_ativo }, { onConflict: 'owner_id' });
|
||||
if (cfgErr) throw cfgErr;
|
||||
|
||||
const { error: delErr } = await supabase.from('agenda_online_slots').delete().eq('owner_id', uid);
|
||||
const { error: delErr } = await tenantDb().from('agenda_online_slots').delete().eq('owner_id', uid);
|
||||
if (delErr) throw delErr;
|
||||
|
||||
if (cfg.value.online_ativo) {
|
||||
@@ -586,7 +577,6 @@ async function saveOnline() {
|
||||
if (isValidHHMM(hhmm)) {
|
||||
rows.push({
|
||||
owner_id: uid,
|
||||
tenant_id: tenantId,
|
||||
weekday: Number(d.value),
|
||||
time: normalizeTime(hhmm),
|
||||
enabled: true
|
||||
@@ -595,7 +585,7 @@ async function saveOnline() {
|
||||
}
|
||||
}
|
||||
if (rows.length) {
|
||||
const { error: insErr } = await supabase.from('agenda_online_slots').insert(rows);
|
||||
const { error: insErr } = await tenantDb().from('agenda_online_slots').insert(rows);
|
||||
if (insErr) throw insErr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import JoditTextEditor from '@/components/ui/JoditTextEditor.vue';
|
||||
import AgendadorPreview from '@/components/agendador/AgendadorPreview.vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { useEntitlementsStore } from '@/stores/entitlementsStore';
|
||||
// InputText/Select/SelectButton/Textarea/RadioButton/Checkbox/ColorPicker/
|
||||
@@ -325,8 +326,7 @@ async function getActiveTenantId(uid) {
|
||||
// ── Load ───────────────────────────────────────────────────
|
||||
async function loadPaymentSettings(uid) {
|
||||
try {
|
||||
const { data } = await supabase
|
||||
.from('payment_settings')
|
||||
const { data } = await tenantDb().from('payment_settings')
|
||||
.select('pix_ativo, pix_chave, pix_tipo, deposito_ativo, dinheiro_ativo, cartao_ativo, convenio_ativo')
|
||||
.eq('owner_id', uid)
|
||||
.maybeSingle();
|
||||
@@ -343,7 +343,7 @@ async function load() {
|
||||
ownerId.value = uid;
|
||||
|
||||
const [{ data, error }] = await Promise.all([
|
||||
supabase.from('agendador_configuracoes').select('*').eq('owner_id', uid).maybeSingle(),
|
||||
tenantDb().from('agendador_configuracoes').select('*').eq('owner_id', uid).maybeSingle(),
|
||||
loadPaymentSettings(uid)
|
||||
]);
|
||||
if (error) throw error;
|
||||
@@ -382,10 +382,9 @@ async function toggleAtivo() {
|
||||
cfg.value.ativo = novoAtivo;
|
||||
try {
|
||||
const tenantId = await getActiveTenantId(uid);
|
||||
const { error } = await supabase
|
||||
.from('agendador_configuracoes')
|
||||
const { error } = await tenantDb().from('agendador_configuracoes')
|
||||
.upsert(
|
||||
{ owner_id: uid, tenant_id: tenantId, ativo: novoAtivo, updated_at: new Date().toISOString() },
|
||||
{ owner_id: uid, ativo: novoAtivo, updated_at: new Date().toISOString() },
|
||||
{ onConflict: 'owner_id' }
|
||||
);
|
||||
if (error) throw error;
|
||||
@@ -474,10 +473,9 @@ async function saveCard(cardKey) {
|
||||
const uid = ownerId.value;
|
||||
const tenantId = await getActiveTenantId(uid);
|
||||
const payload = buildPayload(cardKey);
|
||||
const { error } = await supabase
|
||||
.from('agendador_configuracoes')
|
||||
const { error } = await tenantDb().from('agendador_configuracoes')
|
||||
.upsert(
|
||||
{ owner_id: uid, tenant_id: tenantId, ...payload, updated_at: new Date().toISOString() },
|
||||
{ owner_id: uid, ...payload, updated_at: new Date().toISOString() },
|
||||
{ onConflict: 'owner_id' }
|
||||
);
|
||||
if (error) throw error;
|
||||
@@ -518,10 +516,9 @@ async function onFileSelected(event, field) {
|
||||
if (field === 'fundo') cfg.value.imagem_fundo_url = url;
|
||||
const uid = ownerId.value;
|
||||
const tenantId = await getActiveTenantId(uid);
|
||||
await supabase
|
||||
.from('agendador_configuracoes')
|
||||
await tenantDb().from('agendador_configuracoes')
|
||||
.upsert(
|
||||
{ owner_id: uid, tenant_id: tenantId, ...buildPayload('identidade'), updated_at: new Date().toISOString() },
|
||||
{ owner_id: uid, ...buildPayload('identidade'), updated_at: new Date().toISOString() },
|
||||
{ onConflict: 'owner_id' }
|
||||
);
|
||||
toast.add({ severity: 'success', summary: 'Imagem salva', life: 2000 });
|
||||
|
||||
@@ -21,6 +21,7 @@ import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
|
||||
import AgendaEventDialog from '@/features/agenda/components/AgendaEventDialog.vue';
|
||||
@@ -221,14 +222,12 @@ async function fetchSolicitacoes() {
|
||||
if (!ownerId.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
let q = supabase
|
||||
.from('agendador_solicitacoes')
|
||||
.select('id, owner_id, tenant_id, paciente_nome, paciente_sobrenome, paciente_email, paciente_celular, paciente_cpf, tipo, modalidade, data_solicitada, hora_solicitada, reservado_ate, motivo, como_conheceu, status, recusado_motivo, autorizado_em, created_at')
|
||||
let q = tenantDb().from('agendador_solicitacoes')
|
||||
.select('id, owner_id, paciente_nome, paciente_sobrenome, paciente_email, paciente_celular, paciente_cpf, tipo, modalidade, data_solicitada, hora_solicitada, reservado_ate, motivo, como_conheceu, status, recusado_motivo, autorizado_em, created_at')
|
||||
.order('data_solicitada', { ascending: false })
|
||||
.order('hora_solicitada', { ascending: true });
|
||||
|
||||
if (isClinic.value) q = q.eq('tenant_id', tenantId.value);
|
||||
else q = q.eq('owner_id', ownerId.value);
|
||||
if (!isClinic.value) q = q.eq('owner_id', ownerId.value);
|
||||
|
||||
const { data, error } = await q;
|
||||
if (error) throw error;
|
||||
@@ -294,8 +293,7 @@ async function autorizar() {
|
||||
if (!item) return;
|
||||
dlg.value.saving = true;
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('agendador_solicitacoes')
|
||||
const { error } = await tenantDb().from('agendador_solicitacoes')
|
||||
.update({ status: 'autorizado', autorizado_em: new Date().toISOString() })
|
||||
.eq('id', item.id);
|
||||
if (error) throw error;
|
||||
@@ -325,8 +323,7 @@ async function recusar() {
|
||||
dlg.value.saving = true;
|
||||
try {
|
||||
const motivo = String(dlg.value.recusa_note || '').trim() || null;
|
||||
const { error } = await supabase
|
||||
.from('agendador_solicitacoes')
|
||||
const { error } = await tenantDb().from('agendador_solicitacoes')
|
||||
.update({ status: 'recusado', recusado_motivo: motivo })
|
||||
.eq('id', item.id);
|
||||
if (error) throw error;
|
||||
@@ -362,9 +359,8 @@ function isUuid(v) {
|
||||
async function encontrarOuCriarPaciente(s) {
|
||||
const email = s.paciente_email?.toLowerCase().trim();
|
||||
if (email) {
|
||||
const { data: found } = await supabase
|
||||
.from('patients').select('id')
|
||||
.eq('tenant_id', tenantId.value)
|
||||
const { data: found } = await tenantDb().from('patients').select('id')
|
||||
|
||||
.ilike('email_principal', email)
|
||||
.maybeSingle();
|
||||
if (found?.id) return found.id;
|
||||
@@ -378,10 +374,8 @@ async function encontrarOuCriarPaciente(s) {
|
||||
if (memberErr || !memberData?.id) throw new Error('Membro ativo não encontrado para criação do paciente.');
|
||||
const scope = isClinic.value ? 'clinic' : 'therapist';
|
||||
const nome = [s.paciente_nome, s.paciente_sobrenome].filter(Boolean).join(' ');
|
||||
const { data: novo, error: criErr } = await supabase
|
||||
.from('patients')
|
||||
const { data: novo, error: criErr } = await tenantDb().from('patients')
|
||||
.insert({
|
||||
tenant_id: tenantId.value,
|
||||
responsible_member_id: memberData.id,
|
||||
owner_id: ownerId.value,
|
||||
nome_completo: nome,
|
||||
@@ -438,19 +432,17 @@ async function onEventSaved(arg) {
|
||||
const raw = isWrapped ? arg.payload : arg;
|
||||
const normalized = { ...raw };
|
||||
if (!normalized.owner_id) normalized.owner_id = ownerId.value;
|
||||
normalized.tenant_id = tenantId.value;
|
||||
normalized.tipo = 'sessao';
|
||||
if (!normalized.status) normalized.status = 'agendado';
|
||||
if (!String(normalized.titulo || '').trim()) normalized.titulo = 'Sessão';
|
||||
if (!normalized.visibility_scope) normalized.visibility_scope = 'public';
|
||||
if (!isUuid(normalized.paciente_id)) normalized.paciente_id = null;
|
||||
if (normalized.determined_commitment_id && !isUuid(normalized.determined_commitment_id)) normalized.determined_commitment_id = null;
|
||||
const dbFields = ['tenant_id', 'owner_id', 'terapeuta_id', 'patient_id', 'tipo', 'status', 'titulo', 'observacoes', 'inicio_em', 'fim_em', 'visibility_scope', 'determined_commitment_id', 'titulo_custom', 'extra_fields', 'modalidade'];
|
||||
const dbFields = ['owner_id', 'terapeuta_id', 'patient_id', 'tipo', 'status', 'titulo', 'observacoes', 'inicio_em', 'fim_em', 'visibility_scope', 'determined_commitment_id', 'titulo_custom', 'extra_fields', 'modalidade'];
|
||||
const dbPayload = {};
|
||||
for (const k of dbFields) if (normalized[k] !== undefined) dbPayload[k] = normalized[k];
|
||||
await createEvento(dbPayload);
|
||||
const { error } = await supabase
|
||||
.from('agendador_solicitacoes')
|
||||
const { error } = await tenantDb().from('agendador_solicitacoes')
|
||||
.update({ status: 'convertido' })
|
||||
.eq('id', target.id);
|
||||
if (error) throw error;
|
||||
|
||||
@@ -19,6 +19,7 @@ import MelissaConfigList from './MelissaConfigList.vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { useFeriados } from '@/composables/useFeriados';
|
||||
// DatePicker/Tag/Skeleton/Dialog: auto via PrimeVueResolver
|
||||
@@ -154,8 +155,7 @@ async function loadBloqueios() {
|
||||
if (!ownerId.value) return;
|
||||
loadingB.value = true;
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('agenda_bloqueios')
|
||||
const { data, error } = await tenantDb().from('agenda_bloqueios')
|
||||
.select('*')
|
||||
.eq('owner_id', ownerId.value)
|
||||
.gte('data_inicio', `${ano.value}-01-01`)
|
||||
@@ -288,14 +288,13 @@ async function salvarBloqueio() {
|
||||
origem: 'manual'
|
||||
};
|
||||
if (dlgMode.value === 'edit') {
|
||||
const { error } = await supabase
|
||||
.from('agenda_bloqueios')
|
||||
const { error } = await tenantDb().from('agenda_bloqueios')
|
||||
.update(payload)
|
||||
.eq('id', form.value.id);
|
||||
if (error) throw error;
|
||||
toast.add({ severity: 'success', summary: 'Salvo', detail: 'Bloqueio atualizado.', life: 1800 });
|
||||
} else {
|
||||
const { error } = await supabase.from('agenda_bloqueios').insert(payload);
|
||||
const { error } = await tenantDb().from('agenda_bloqueios').insert(payload);
|
||||
if (error) throw error;
|
||||
toast.add({ severity: 'success', summary: 'Criado', detail: 'Bloqueio adicionado.', life: 1800 });
|
||||
}
|
||||
@@ -319,8 +318,7 @@ function excluirBloqueio(id) {
|
||||
acceptClass: 'p-button-danger',
|
||||
accept: async () => {
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('agenda_bloqueios')
|
||||
const { error } = await tenantDb().from('agenda_bloqueios')
|
||||
.delete()
|
||||
.eq('id', id);
|
||||
if (error) throw error;
|
||||
|
||||
@@ -16,6 +16,7 @@ import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
// Fase 2 (Graphify hotspot): convertToPatient duplicado em 2 pages — extração pro repository.
|
||||
import { createPatient, markIntakeConverted } from '@/features/patients/services/patientsRepository';
|
||||
@@ -257,8 +258,7 @@ const pagedItems = computed(() => {
|
||||
async function fetchIntakes() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('patient_intake_requests').select('*')
|
||||
const { data, error } = await tenantDb().from('patient_intake_requests').select('*')
|
||||
.order('created_at', { ascending: false });
|
||||
if (error) throw error;
|
||||
const weight = (s) => (s === 'new' ? 0 : s === 'converted' ? 1 : s === 'rejected' ? 2 : 9);
|
||||
@@ -346,7 +346,7 @@ async function markRejected() {
|
||||
dlg.value.saving = true;
|
||||
try {
|
||||
const reason = String(dlg.value.reject_note || '').trim() || null;
|
||||
const { error } = await supabase.from('patient_intake_requests')
|
||||
const { error } = await tenantDb().from('patient_intake_requests')
|
||||
.update({ status: 'rejected', rejected_reason: reason, updated_at: new Date().toISOString() })
|
||||
.eq('id', item.id);
|
||||
if (error) throw error;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import DeterminedCommitmentDialog from '@/features/agenda/components/DeterminedCommitmentDialog.vue';
|
||||
// DataTable/Column/Paginator/ToggleSwitch/Dialog: auto-importados via PrimeVueResolver.
|
||||
@@ -161,10 +162,9 @@ async function fetchAll() {
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const { data: cData, error: cErr } = await supabase
|
||||
.from('determined_commitments')
|
||||
.select('id, tenant_id, is_native, native_key, is_locked, active, name, description, bg_color, text_color, created_at, updated_at')
|
||||
.eq('tenant_id', tenantId)
|
||||
const { data: cData, error: cErr } = await tenantDb().from('determined_commitments')
|
||||
.select('id, is_native, native_key, is_locked, active, name, description, bg_color, text_color, created_at, updated_at')
|
||||
|
||||
.order('is_native', { ascending: false })
|
||||
.order('created_at', { ascending: false });
|
||||
if (cErr) throw cErr;
|
||||
@@ -172,10 +172,9 @@ async function fetchAll() {
|
||||
const ids = (cData || []).map((x) => x.id);
|
||||
let fieldsByCommitmentId = {};
|
||||
if (ids.length > 0) {
|
||||
const { data: fData, error: fErr } = await supabase
|
||||
.from('determined_commitment_fields')
|
||||
.select('id, tenant_id, commitment_id, key, label, field_type, required, sort_order')
|
||||
.eq('tenant_id', tenantId)
|
||||
const { data: fData, error: fErr } = await tenantDb().from('determined_commitment_fields')
|
||||
.select('id, commitment_id, key, label, field_type, required, sort_order')
|
||||
|
||||
.in('commitment_id', ids)
|
||||
.order('sort_order', { ascending: true });
|
||||
if (fErr) throw fErr;
|
||||
@@ -189,8 +188,7 @@ async function fetchAll() {
|
||||
}, {});
|
||||
}
|
||||
|
||||
const { data: lData, error: lErr } = await supabase
|
||||
.from('commitment_time_logs').select('commitment_id, minutes').eq('tenant_id', tenantId);
|
||||
const { data: lData, error: lErr } = await tenantDb().from('commitment_time_logs').select('commitment_id, minutes');
|
||||
if (lErr) throw lErr;
|
||||
const totals = {};
|
||||
for (const row of lData || []) {
|
||||
@@ -253,7 +251,7 @@ async function onToggleActive(c) {
|
||||
if (!tenantId) return;
|
||||
saving.value = true;
|
||||
try {
|
||||
const { error } = await supabase.from('determined_commitments').update({ active: !!c.active }).eq('tenant_id', tenantId).eq('id', c.id);
|
||||
const { error } = await tenantDb().from('determined_commitments').update({ active: !!c.active }).eq('id', c.id);
|
||||
if (error) throw error;
|
||||
toast.add({ severity: 'success', summary: 'Atualizado', detail: `"${c.name}" ${c.active ? 'ativo' : 'inativo'}.`, life: 2200 });
|
||||
} catch (e) {
|
||||
@@ -270,10 +268,8 @@ async function onSave(payload) {
|
||||
saving.value = true;
|
||||
try {
|
||||
if (dlgMode.value === 'create') {
|
||||
const { data: newC, error: cErr } = await supabase
|
||||
.from('determined_commitments')
|
||||
const { data: newC, error: cErr } = await tenantDb().from('determined_commitments')
|
||||
.insert({
|
||||
tenant_id: tenantId,
|
||||
is_native: false,
|
||||
native_key: null,
|
||||
is_locked: false,
|
||||
@@ -288,9 +284,8 @@ async function onSave(payload) {
|
||||
|
||||
const fields = Array.isArray(payload.fields) ? payload.fields : [];
|
||||
if (fields.length > 0) {
|
||||
const { error: fErr } = await supabase.from('determined_commitment_fields').insert(
|
||||
const { error: fErr } = await tenantDb().from('determined_commitment_fields').insert(
|
||||
fields.map((f, idx) => ({
|
||||
tenant_id: tenantId,
|
||||
commitment_id: newC.id,
|
||||
key: f.key,
|
||||
label: f.label,
|
||||
@@ -304,7 +299,7 @@ async function onSave(payload) {
|
||||
toast.add({ severity: 'success', summary: 'Criado', detail: 'Compromisso criado.', life: 2200 });
|
||||
} else if (editing.value?.id) {
|
||||
const cid = editing.value.id;
|
||||
const { error: uErr } = await supabase.from('determined_commitments')
|
||||
const { error: uErr } = await tenantDb().from('determined_commitments')
|
||||
.update({
|
||||
active: !!payload.active,
|
||||
name: payload.name,
|
||||
@@ -312,17 +307,16 @@ async function onSave(payload) {
|
||||
bg_color: payload.bg_color || null,
|
||||
text_color: payload.text_color || null
|
||||
})
|
||||
.eq('tenant_id', tenantId).eq('id', cid);
|
||||
.eq('id', cid);
|
||||
if (uErr) throw uErr;
|
||||
|
||||
const { error: dErr } = await supabase.from('determined_commitment_fields')
|
||||
.delete().eq('tenant_id', tenantId).eq('commitment_id', cid);
|
||||
const { error: dErr } = await tenantDb().from('determined_commitment_fields')
|
||||
.delete().eq('commitment_id', cid);
|
||||
if (dErr) throw dErr;
|
||||
const fields = Array.isArray(payload.fields) ? payload.fields : [];
|
||||
if (fields.length > 0) {
|
||||
const { error: fErr } = await supabase.from('determined_commitment_fields').insert(
|
||||
const { error: fErr } = await tenantDb().from('determined_commitment_fields').insert(
|
||||
fields.map((f, idx) => ({
|
||||
tenant_id: tenantId,
|
||||
commitment_id: cid,
|
||||
key: f.key,
|
||||
label: f.label,
|
||||
@@ -360,14 +354,14 @@ async function onDelete(c) {
|
||||
if (!tenantId) return;
|
||||
saving.value = true;
|
||||
try {
|
||||
const { error: fErr } = await supabase.from('determined_commitment_fields')
|
||||
.delete().eq('tenant_id', tenantId).eq('commitment_id', c.id);
|
||||
const { error: fErr } = await tenantDb().from('determined_commitment_fields')
|
||||
.delete().eq('commitment_id', c.id);
|
||||
if (fErr) throw fErr;
|
||||
const { error: lErr } = await supabase.from('commitment_time_logs')
|
||||
.delete().eq('tenant_id', tenantId).eq('commitment_id', c.id);
|
||||
const { error: lErr } = await tenantDb().from('commitment_time_logs')
|
||||
.delete().eq('commitment_id', c.id);
|
||||
if (lErr) throw lErr;
|
||||
const { data: delRows, error: dErr } = await supabase.from('determined_commitments')
|
||||
.delete().eq('tenant_id', tenantId).eq('id', c.id).eq('is_native', false).select('id');
|
||||
const { data: delRows, error: dErr } = await tenantDb().from('determined_commitments')
|
||||
.delete().eq('id', c.id).eq('is_native', false).select('id');
|
||||
if (dErr) throw dErr;
|
||||
if (!delRows?.length) throw new Error('DELETE bloqueado por RLS.');
|
||||
toast.add({ severity: 'success', summary: 'Excluído', detail: 'Compromisso removido.', life: 2200 });
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
// Chart/DataTable/Column/Skeleton/Tag: auto via PrimeVueResolver
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
@@ -77,8 +78,7 @@ async function loadSummary(uid) {
|
||||
totalRecebido.value = Number(s?.total_receitas ?? 0);
|
||||
totalDespesas.value = Number(s?.total_despesas ?? 0);
|
||||
|
||||
const { data: pendRows } = await supabase
|
||||
.from('financial_records')
|
||||
const { data: pendRows } = await tenantDb().from('financial_records')
|
||||
.select('status, final_amount')
|
||||
.eq('owner_id', uid)
|
||||
.is('deleted_at', null)
|
||||
@@ -149,8 +149,7 @@ async function loadCashflow() {
|
||||
cashflowLoading.value = true;
|
||||
cashflowError.value = false;
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('v_cashflow_projection')
|
||||
const { data, error } = await tenantDb().from('v_cashflow_projection')
|
||||
.select('mes_label, receitas_projetadas, despesas_projetadas, saldo_projetado, count_registros')
|
||||
.order('mes', { ascending: true });
|
||||
if (error) throw error;
|
||||
|
||||
@@ -16,6 +16,7 @@ import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { useFinancialRecords } from '@/composables/useFinancialRecords';
|
||||
// DataTable/Column/Dialog/Tag/Select/InputNumber/DatePicker/Textarea/Button: auto via PrimeVueResolver
|
||||
@@ -63,10 +64,9 @@ const patients = ref([]);
|
||||
async function loadPatients() {
|
||||
const tenantId = tenantStore.activeTenantId;
|
||||
if (!tenantId) return;
|
||||
const { data } = await supabase
|
||||
.from('patients')
|
||||
const { data } = await tenantDb().from('patients')
|
||||
.select('id, nome_completo, identification_color')
|
||||
.eq('tenant_id', tenantId)
|
||||
|
||||
.order('nome_completo');
|
||||
patients.value = data ?? [];
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { ref, reactive, computed, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import PatientCadastroDialog from '@/components/ui/PatientCadastroDialog.vue';
|
||||
// DataTable/Column/Paginator/Dialog/InputText/Button: auto via PrimeVueResolver
|
||||
@@ -153,11 +154,11 @@ async function load() {
|
||||
try {
|
||||
const tenantId = await getTenantId();
|
||||
const [{ data: gData, error: gErr }, { data: vData }] = await Promise.all([
|
||||
supabase.from('patient_groups')
|
||||
.select('id, owner_id, tenant_id, nome, cor, is_system, is_active, created_at')
|
||||
.eq('tenant_id', tenantId)
|
||||
tenantDb().from('patient_groups')
|
||||
.select('id, owner_id, nome, cor, is_system, is_active, created_at')
|
||||
|
||||
.order('nome', { ascending: true }),
|
||||
supabase.from('patient_group_patient').select('patient_group_id')
|
||||
tenantDb().from('patient_group_patient').select('patient_group_id')
|
||||
]);
|
||||
if (gErr) throw gErr;
|
||||
|
||||
@@ -235,14 +236,14 @@ async function salvar() {
|
||||
const tenantId = await getTenantId();
|
||||
const cor = dlgForm.value.cor.startsWith('#') ? dlgForm.value.cor : '#' + dlgForm.value.cor;
|
||||
if (dlgMode.value === 'create') {
|
||||
const { error } = await supabase.from('patient_groups').insert({
|
||||
owner_id: ownerId, tenant_id: tenantId,
|
||||
const { error } = await tenantDb().from('patient_groups').insert({
|
||||
owner_id: ownerId,
|
||||
nome, cor, is_system: false, is_active: true
|
||||
});
|
||||
if (error) throw error;
|
||||
toast.add({ severity: 'success', summary: 'Grupo criado', life: 2200 });
|
||||
} else {
|
||||
const { error } = await supabase.from('patient_groups')
|
||||
const { error } = await tenantDb().from('patient_groups')
|
||||
.update({ nome, cor })
|
||||
.eq('id', dlgForm.value.id);
|
||||
if (error) throw error;
|
||||
@@ -273,8 +274,8 @@ function confirmarExcluir(row) {
|
||||
async function excluir(row) {
|
||||
saving.value = true;
|
||||
try {
|
||||
await supabase.from('patient_group_patient').delete().eq('patient_group_id', row.id);
|
||||
const { error } = await supabase.from('patient_groups').delete().eq('id', row.id);
|
||||
await tenantDb().from('patient_group_patient').delete().eq('patient_group_id', row.id);
|
||||
const { error } = await tenantDb().from('patient_groups').delete().eq('id', row.id);
|
||||
if (error) throw error;
|
||||
toast.add({ severity: 'success', summary: 'Grupo excluído', life: 2200 });
|
||||
await load();
|
||||
@@ -339,8 +340,7 @@ async function openGroupPatientsModal(groupRow) {
|
||||
patientsDialog.items = [];
|
||||
patientsDialog.search = '';
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('patient_group_patient')
|
||||
const { data, error } = await tenantDb().from('patient_group_patient')
|
||||
.select('patient_id, patient:patients(id, nome_completo, email_principal, telefone, avatar_url)')
|
||||
.eq('patient_group_id', groupRow.id);
|
||||
if (error) throw error;
|
||||
|
||||
@@ -91,6 +91,7 @@ import { useMelissaWhatsapp } from './composables/useMelissaWhatsapp';
|
||||
import { useMelissaAgenda, MELISSA_AGENDA_KEY } from './composables/useMelissaAgenda';
|
||||
import { useMelissaDockPins } from './composables/useMelissaDockPins';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useAgendaFinanceiro } from '@/composables/useAgendaFinanceiro';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { useConversationDrawerStore } from '@/stores/conversationDrawerStore';
|
||||
@@ -887,8 +888,7 @@ async function onTrocarMetodoAntecipacao() {
|
||||
}
|
||||
// Busca método atual do record paid pra pré-selecionar no dialog
|
||||
try {
|
||||
const { data: paidRec } = await supabase
|
||||
.from('financial_records')
|
||||
const { data: paidRec } = await tenantDb().from('financial_records')
|
||||
.select('payment_method')
|
||||
.eq('agenda_evento_id', ev.id)
|
||||
.eq('status', 'paid')
|
||||
@@ -916,8 +916,7 @@ async function confirmAnteciparPagamento() {
|
||||
if (anteciparMode.value === 'update') {
|
||||
const settlement = anteciparMethod.value;
|
||||
const today = new Date().toISOString();
|
||||
const { data: paidRec, error: fetchErr } = await supabase
|
||||
.from('financial_records')
|
||||
const { data: paidRec, error: fetchErr } = await tenantDb().from('financial_records')
|
||||
.select('id, payment_method, notes')
|
||||
.eq('agenda_evento_id', ev.id)
|
||||
.eq('status', 'paid')
|
||||
@@ -938,8 +937,7 @@ async function confirmAnteciparPagamento() {
|
||||
notes: newNotes,
|
||||
updated_at: today
|
||||
};
|
||||
const { error: upErr } = await supabase
|
||||
.from('financial_records')
|
||||
const { error: upErr } = await tenantDb().from('financial_records')
|
||||
.update(patch)
|
||||
.eq('id', paidRec.id);
|
||||
if (upErr) throw upErr;
|
||||
@@ -978,8 +976,7 @@ async function confirmAnteciparPagamento() {
|
||||
const rDate = ev.recurrence_date || ev.original_date || (ev.inicio_em ? String(ev.inicio_em).slice(0, 10) : null);
|
||||
if (!rid || !rDate) throw new Error('Não foi possível identificar a regra de recorrência.');
|
||||
// Confere se já não foi materializada
|
||||
const { data: existing } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data: existing } = await tenantDb().from('agenda_eventos')
|
||||
.select('id')
|
||||
.eq('recurrence_id', rid)
|
||||
.eq('recurrence_date', rDate)
|
||||
@@ -987,11 +984,9 @@ async function confirmAnteciparPagamento() {
|
||||
if (existing?.id) {
|
||||
eventoId = existing.id;
|
||||
} else {
|
||||
const { data: created, error: cErr } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data: created, error: cErr } = await tenantDb().from('agenda_eventos')
|
||||
.insert({
|
||||
owner_id: ownerId,
|
||||
tenant_id: tenantId,
|
||||
recurrence_id: rid,
|
||||
recurrence_date: rDate,
|
||||
tipo: 'sessao',
|
||||
@@ -1017,8 +1012,7 @@ async function confirmAnteciparPagamento() {
|
||||
// (record vira cancelled) e user re-antecipa. Sem o filtro, o handler
|
||||
// reusava o record cancelled atualizando pra paid, mantendo notes
|
||||
// da revogação no audit trail (confuso).
|
||||
const { data: existRec } = await supabase
|
||||
.from('financial_records')
|
||||
const { data: existRec } = await tenantDb().from('financial_records')
|
||||
.select('id, status')
|
||||
.eq('agenda_evento_id', eventoId)
|
||||
.is('deleted_at', null)
|
||||
@@ -1043,8 +1037,7 @@ async function confirmAnteciparPagamento() {
|
||||
p_due_date: dueIso
|
||||
});
|
||||
if (rpcErr) throw rpcErr;
|
||||
const { data: newRec } = await supabase
|
||||
.from('financial_records')
|
||||
const { data: newRec } = await tenantDb().from('financial_records')
|
||||
.select('id')
|
||||
.eq('agenda_evento_id', eventoId)
|
||||
.order('created_at', { ascending: false })
|
||||
@@ -1064,7 +1057,7 @@ async function confirmAnteciparPagamento() {
|
||||
patch.paid_at = new Date().toISOString();
|
||||
patch.payment_method = settlement;
|
||||
}
|
||||
await supabase.from('financial_records').update(patch).eq('id', recordId);
|
||||
await tenantDb().from('financial_records').update(patch).eq('id', recordId);
|
||||
}
|
||||
|
||||
const methodLabel = anteciparMethodOptions.find((o) => o.value === settlement)?.label || settlement;
|
||||
@@ -1117,8 +1110,7 @@ async function onRevogarAntecipacao() {
|
||||
eventoBusy.value = true;
|
||||
try {
|
||||
// Acha o paid record vinculado
|
||||
const { data: paidRec, error: fetchErr } = await supabase
|
||||
.from('financial_records')
|
||||
const { data: paidRec, error: fetchErr } = await tenantDb().from('financial_records')
|
||||
.select('id, notes, payment_method, final_amount, amount')
|
||||
.eq('agenda_evento_id', ev.id)
|
||||
.eq('status', 'paid')
|
||||
@@ -1134,8 +1126,7 @@ async function onRevogarAntecipacao() {
|
||||
const reason = `Antecipação revogada em ${today}`;
|
||||
const noteEntry = `[${today}] ${reason}`;
|
||||
const noteText = paidRec.notes ? `${paidRec.notes}\n${noteEntry}` : noteEntry;
|
||||
const { error: cancelErr } = await supabase
|
||||
.from('financial_records')
|
||||
const { error: cancelErr } = await tenantDb().from('financial_records')
|
||||
.update({
|
||||
status: 'cancelled',
|
||||
notes: noteText,
|
||||
@@ -1180,14 +1171,12 @@ async function onVerLancamentos() {
|
||||
if (isVirtual && ev.recurrence_id) {
|
||||
// Pega records de QUALQUER sibling materializada (cobre o caso
|
||||
// pacote upfront onde só a 1ª tem record do pacote inteiro).
|
||||
const { data: siblings } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data: siblings } = await tenantDb().from('agenda_eventos')
|
||||
.select('id')
|
||||
.eq('recurrence_id', ev.recurrence_id);
|
||||
const ids = (siblings || []).map((s) => s.id);
|
||||
if (ids.length) {
|
||||
const { data, error } = await supabase
|
||||
.from('financial_records')
|
||||
const { data, error } = await tenantDb().from('financial_records')
|
||||
.select('id, description, amount, final_amount, status, due_date, paid_at, payment_method, created_at')
|
||||
.in('agenda_evento_id', ids)
|
||||
.is('deleted_at', null)
|
||||
@@ -1196,8 +1185,7 @@ async function onVerLancamentos() {
|
||||
records = data || [];
|
||||
}
|
||||
} else if (!isVirtual) {
|
||||
const { data, error } = await supabase
|
||||
.from('financial_records')
|
||||
const { data, error } = await tenantDb().from('financial_records')
|
||||
.select('id, description, amount, final_amount, status, due_date, paid_at, payment_method, created_at')
|
||||
.eq('agenda_evento_id', ev.id)
|
||||
.is('deleted_at', null)
|
||||
@@ -1274,9 +1262,8 @@ async function onDeleteEvento() {
|
||||
// pra (recurrence_id, original_date) — edição anterior,
|
||||
// conflito de criação, cancel duplicado — sobrescreve em
|
||||
// vez de quebrar com unique violation.
|
||||
const { error } = await supabase.from('recurrence_exceptions').upsert({
|
||||
const { error } = await tenantDb().from('recurrence_exceptions').upsert({
|
||||
recurrence_id: recId,
|
||||
tenant_id: tenantId,
|
||||
original_date: origDate,
|
||||
type: 'cancel_session',
|
||||
reason: 'Cancelado pelo terapeuta antes de qualquer interação'
|
||||
@@ -1300,8 +1287,7 @@ async function onDeleteEvento() {
|
||||
let recordsCount = 0;
|
||||
let hasPaidRecord = false;
|
||||
try {
|
||||
const { data } = await supabase
|
||||
.from('financial_records')
|
||||
const { data } = await tenantDb().from('financial_records')
|
||||
.select('id, status')
|
||||
.eq('agenda_evento_id', ev.id)
|
||||
.is('deleted_at', null);
|
||||
@@ -1341,7 +1327,7 @@ async function onDeleteEvento() {
|
||||
try {
|
||||
// 1) Remove cobranças vinculadas (não-pagas)
|
||||
if (recordsCount > 0) {
|
||||
const { error: recErr } = await supabase.from('financial_records').delete().eq('agenda_evento_id', ev.id);
|
||||
const { error: recErr } = await tenantDb().from('financial_records').delete().eq('agenda_evento_id', ev.id);
|
||||
if (recErr) throw recErr;
|
||||
}
|
||||
if (isMaterializedOccurrence) {
|
||||
@@ -1351,9 +1337,8 @@ async function onDeleteEvento() {
|
||||
if (origDate) {
|
||||
// upsert pra ser idempotente: se já existe exception
|
||||
// pra (recurrence_id, original_date), sobrescreve.
|
||||
const { error: exErr } = await supabase.from('recurrence_exceptions').upsert({
|
||||
const { error: exErr } = await tenantDb().from('recurrence_exceptions').upsert({
|
||||
recurrence_id: ev.recurrence_id || ev.serie_id,
|
||||
tenant_id: tenantId,
|
||||
original_date: origDate,
|
||||
type: 'cancel_session',
|
||||
reason: 'Cancelado pelo terapeuta'
|
||||
@@ -1361,7 +1346,7 @@ async function onDeleteEvento() {
|
||||
if (exErr) console.warn('[Excluir] exception upsert falhou:', exErr?.message);
|
||||
}
|
||||
}
|
||||
const { error } = await supabase.from('agenda_eventos').delete().eq('id', ev.id);
|
||||
const { error } = await tenantDb().from('agenda_eventos').delete().eq('id', ev.id);
|
||||
if (error) throw error;
|
||||
const detail = recordsCount > 0 ? `Sessão e ${recordsCount} cobrança(s) removida(s).` : 'Sessão removida.';
|
||||
toast.add({ severity: 'success', summary: isMaterializedOccurrence ? 'Ocorrência cancelada' : 'Excluída', detail, life: 3000 });
|
||||
@@ -1397,15 +1382,13 @@ async function onDeleteSeries() {
|
||||
let recordsPending = 0;
|
||||
let hasPaid = false;
|
||||
try {
|
||||
const { data: evts } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data: evts } = await tenantDb().from('agenda_eventos')
|
||||
.select('id')
|
||||
.eq('recurrence_id', ruleId);
|
||||
materializedCount = (evts || []).length;
|
||||
const evtIds = (evts || []).map((e) => e.id);
|
||||
if (evtIds.length) {
|
||||
const { data: recs } = await supabase
|
||||
.from('financial_records')
|
||||
const { data: recs } = await tenantDb().from('financial_records')
|
||||
.select('id, status')
|
||||
.in('agenda_evento_id', evtIds)
|
||||
.is('deleted_at', null);
|
||||
@@ -1446,29 +1429,25 @@ async function onDeleteSeries() {
|
||||
try {
|
||||
// 1) Apaga financial_records pendentes vinculados a eventos
|
||||
// materializados desta série
|
||||
const { data: evts2 } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data: evts2 } = await tenantDb().from('agenda_eventos')
|
||||
.select('id')
|
||||
.eq('recurrence_id', ruleId);
|
||||
const evtIds = (evts2 || []).map((e) => e.id);
|
||||
if (evtIds.length) {
|
||||
const { error: recErr } = await supabase
|
||||
.from('financial_records')
|
||||
const { error: recErr } = await tenantDb().from('financial_records')
|
||||
.delete()
|
||||
.in('agenda_evento_id', evtIds);
|
||||
if (recErr) throw recErr;
|
||||
}
|
||||
// 2) Apaga eventos materializados
|
||||
if (evtIds.length) {
|
||||
const { error: evErr } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { error: evErr } = await tenantDb().from('agenda_eventos')
|
||||
.delete()
|
||||
.in('id', evtIds);
|
||||
if (evErr) throw evErr;
|
||||
}
|
||||
// 3) Apaga a regra (CASCADE: exceptions + rule_services)
|
||||
const { error: ruleErr } = await supabase
|
||||
.from('recurrence_rules')
|
||||
const { error: ruleErr } = await tenantDb().from('recurrence_rules')
|
||||
.delete()
|
||||
.eq('id', ruleId);
|
||||
if (ruleErr) throw ruleErr;
|
||||
@@ -1571,11 +1550,9 @@ async function onUsarSessao(payload = null) {
|
||||
// "Título" aparece indevidamente porque o dialog acha que não
|
||||
// é sessão).
|
||||
const raw = ev._raw || {};
|
||||
const { data: created, error: matErr } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data: created, error: matErr } = await tenantDb().from('agenda_eventos')
|
||||
.insert({
|
||||
owner_id: ownerId,
|
||||
tenant_id: tenantId,
|
||||
patient_id: ev.patient_id || ev.paciente_id,
|
||||
recurrence_id: ev.recurrence_id,
|
||||
recurrence_date: recurrenceDate,
|
||||
@@ -1606,8 +1583,7 @@ async function onUsarSessao(payload = null) {
|
||||
patch.determined_commitment_id = commitmentId;
|
||||
} else {
|
||||
// Busca da rule (fonte autoritativa) se ev não tem
|
||||
const { data: rule } = await supabase
|
||||
.from('recurrence_rules')
|
||||
const { data: rule } = await tenantDb().from('recurrence_rules')
|
||||
.select('determined_commitment_id')
|
||||
.eq('id', ev.recurrence_id)
|
||||
.maybeSingle();
|
||||
@@ -1615,8 +1591,7 @@ async function onUsarSessao(payload = null) {
|
||||
patch.determined_commitment_id = rule.determined_commitment_id;
|
||||
}
|
||||
}
|
||||
const { error: upErr } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { error: upErr } = await tenantDb().from('agenda_eventos')
|
||||
.update(patch)
|
||||
.eq('id', eventoId);
|
||||
if (upErr) throw upErr;
|
||||
@@ -1639,8 +1614,7 @@ async function onUsarSessao(payload = null) {
|
||||
if (newUsed >= contract.totalSessions) {
|
||||
patchContract.status = 'completed';
|
||||
}
|
||||
const { error: ctErr } = await supabase
|
||||
.from('billing_contracts')
|
||||
const { error: ctErr } = await tenantDb().from('billing_contracts')
|
||||
.update(patchContract)
|
||||
.eq('id', contract.id);
|
||||
if (ctErr) throw ctErr;
|
||||
@@ -1690,8 +1664,7 @@ async function onRevogarSessao(payload = null) {
|
||||
eventoBusy.value = true;
|
||||
try {
|
||||
// Acha record vinculado a essa sessão e ao contrato
|
||||
const { data: recs } = await supabase
|
||||
.from('financial_records')
|
||||
const { data: recs } = await tenantDb().from('financial_records')
|
||||
.select('id, status')
|
||||
.eq('agenda_evento_id', ev.id)
|
||||
.neq('status', 'cancelled')
|
||||
@@ -1709,8 +1682,7 @@ async function onRevogarSessao(payload = null) {
|
||||
|
||||
// 1) Cancela record (se ainda pending)
|
||||
if (activeRec) {
|
||||
const { error: cancelErr } = await supabase
|
||||
.from('financial_records')
|
||||
const { error: cancelErr } = await tenantDb().from('financial_records')
|
||||
.update({ status: 'cancelled', updated_at: new Date().toISOString() })
|
||||
.eq('id', activeRec.id);
|
||||
if (cancelErr) throw cancelErr;
|
||||
@@ -1723,8 +1695,7 @@ async function onRevogarSessao(payload = null) {
|
||||
if ((contract.sessionsUsed || 0) >= contract.totalSessions) {
|
||||
patchContract.status = 'active';
|
||||
}
|
||||
const { error: ctErr } = await supabase
|
||||
.from('billing_contracts')
|
||||
const { error: ctErr } = await tenantDb().from('billing_contracts')
|
||||
.update(patchContract)
|
||||
.eq('id', contract.id);
|
||||
if (ctErr) throw ctErr;
|
||||
@@ -1736,8 +1707,7 @@ async function onRevogarSessao(payload = null) {
|
||||
const raw = ev._raw || {};
|
||||
const patch = { status: 'agendado' };
|
||||
if (!raw.determined_commitment_id && ev.recurrence_id) {
|
||||
const { data: rule } = await supabase
|
||||
.from('recurrence_rules')
|
||||
const { data: rule } = await tenantDb().from('recurrence_rules')
|
||||
.select('determined_commitment_id')
|
||||
.eq('id', ev.recurrence_id)
|
||||
.maybeSingle();
|
||||
@@ -1745,8 +1715,7 @@ async function onRevogarSessao(payload = null) {
|
||||
patch.determined_commitment_id = rule.determined_commitment_id;
|
||||
}
|
||||
}
|
||||
const { error: upErr } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { error: upErr } = await tenantDb().from('agenda_eventos')
|
||||
.update(patch)
|
||||
.eq('id', ev.id);
|
||||
if (upErr) throw upErr;
|
||||
@@ -1893,8 +1862,7 @@ async function onEditEvento() {
|
||||
}
|
||||
eventoBusy.value = true;
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data, error } = await tenantDb().from('agenda_eventos')
|
||||
.select('*')
|
||||
.eq('id', ev.id)
|
||||
.maybeSingle();
|
||||
@@ -2122,10 +2090,9 @@ async function onCronometroSessionEnd({ pacienteId, elapsedSec, stoppedAt }) {
|
||||
const tomorrowStart = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 0, 0, 0).toISOString();
|
||||
|
||||
try {
|
||||
const { data: sessao, error } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data: sessao, error } = await tenantDb().from('agenda_eventos')
|
||||
.select('id, extra_fields, inicio_em')
|
||||
.eq('tenant_id', tenantId)
|
||||
|
||||
.eq('patient_id', pacienteId)
|
||||
.eq('tipo', 'sessao')
|
||||
.gte('inicio_em', todayStart)
|
||||
@@ -2148,8 +2115,7 @@ async function onCronometroSessionEnd({ pacienteId, elapsedSec, stoppedAt }) {
|
||||
cronometro_duracao_seg: Math.round(elapsedSec),
|
||||
cronometro_parado_em: stoppedAt
|
||||
};
|
||||
const { error: upErr } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { error: upErr } = await tenantDb().from('agenda_eventos')
|
||||
.update({ extra_fields: newExtra })
|
||||
.eq('id', sessao.id);
|
||||
if (upErr) throw upErr;
|
||||
|
||||
@@ -17,6 +17,7 @@ import { ref, reactive, computed, onMounted, onBeforeUnmount, nextTick } from 'v
|
||||
import MelissaConfigList from './MelissaConfigList.vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
// InputText/Select/Textarea/InputMask/Skeleton/Tag: auto via PrimeVueResolver
|
||||
|
||||
@@ -354,10 +355,9 @@ async function loadNegocio() {
|
||||
|
||||
tenantId.value = member.tenant_id;
|
||||
|
||||
const { data: co, error: coErr } = await supabase
|
||||
.from('company_profiles')
|
||||
const { data: co, error: coErr } = await tenantDb().from('company_profiles')
|
||||
.select('*')
|
||||
.eq('tenant_id', tenantId.value)
|
||||
|
||||
.maybeSingle();
|
||||
if (coErr) throw coErr;
|
||||
|
||||
@@ -432,7 +432,6 @@ async function saveAll() {
|
||||
}
|
||||
|
||||
const payload = {
|
||||
tenant_id: tenantId.value,
|
||||
nome_fantasia: String(form.nome_fantasia || '').trim() || null,
|
||||
razao_social: String(form.razao_social || '').trim() || null,
|
||||
tipo_empresa: String(form.tipo_empresa || '').trim() || null,
|
||||
@@ -454,9 +453,8 @@ async function saveAll() {
|
||||
updated_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
const { error } = await supabase
|
||||
.from('company_profiles')
|
||||
.upsert(payload, { onConflict: 'tenant_id' });
|
||||
const { error } = await tenantDb().from('company_profiles')
|
||||
.upsert(payload, { onConflict: 'singleton' });
|
||||
if (error) throw error;
|
||||
|
||||
clearLogoFile();
|
||||
|
||||
@@ -21,6 +21,7 @@ import { useConfirm } from 'primevue/useconfirm';
|
||||
import { formatDistanceToNow, format } from 'date-fns';
|
||||
import { ptBR } from 'date-fns/locale';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useNotificationStore } from '@/stores/notificationStore';
|
||||
import { useConversationDrawerStore } from '@/stores/conversationDrawerStore';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
@@ -146,8 +147,7 @@ async function load() {
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
const { data, error } = await supabase
|
||||
.from('notifications')
|
||||
const { data, error } = await tenantDb().from('notifications')
|
||||
.select('*')
|
||||
.eq('owner_id', ownerId.value)
|
||||
.order('created_at', { ascending: false })
|
||||
@@ -164,7 +164,7 @@ async function load() {
|
||||
// ── Actions: lida/não lida/arquivar/remover ────────────
|
||||
async function markRead(id) {
|
||||
const now = new Date().toISOString();
|
||||
const { error } = await supabase.from('notifications').update({ read_at: now }).eq('id', id);
|
||||
const { error } = await tenantDb().from('notifications').update({ read_at: now }).eq('id', id);
|
||||
if (error) {
|
||||
toast.add({ severity: 'error', summary: 'Erro', detail: error.message, life: 3500 });
|
||||
return;
|
||||
@@ -176,7 +176,7 @@ async function markRead(id) {
|
||||
}
|
||||
|
||||
async function markUnread(id) {
|
||||
const { error } = await supabase.from('notifications').update({ read_at: null }).eq('id', id);
|
||||
const { error } = await tenantDb().from('notifications').update({ read_at: null }).eq('id', id);
|
||||
if (error) {
|
||||
toast.add({ severity: 'error', summary: 'Erro', detail: error.message, life: 3500 });
|
||||
return;
|
||||
@@ -188,7 +188,7 @@ async function markUnread(id) {
|
||||
}
|
||||
|
||||
async function archive(id) {
|
||||
const { error } = await supabase.from('notifications').update({ archived: true }).eq('id', id);
|
||||
const { error } = await tenantDb().from('notifications').update({ archived: true }).eq('id', id);
|
||||
if (error) {
|
||||
toast.add({ severity: 'error', summary: 'Erro', detail: error.message, life: 3500 });
|
||||
return;
|
||||
@@ -199,7 +199,7 @@ async function archive(id) {
|
||||
}
|
||||
|
||||
async function unarchive(id) {
|
||||
const { error } = await supabase.from('notifications').update({ archived: false }).eq('id', id);
|
||||
const { error } = await tenantDb().from('notifications').update({ archived: false }).eq('id', id);
|
||||
if (error) {
|
||||
toast.add({ severity: 'error', summary: 'Erro', detail: error.message, life: 3500 });
|
||||
return;
|
||||
@@ -224,7 +224,7 @@ function confirmRemove(id) {
|
||||
}
|
||||
|
||||
async function remove(id) {
|
||||
const { error } = await supabase.from('notifications').delete().eq('id', id);
|
||||
const { error } = await tenantDb().from('notifications').delete().eq('id', id);
|
||||
if (error) {
|
||||
toast.add({ severity: 'error', summary: 'Erro', detail: error.message, life: 3500 });
|
||||
return;
|
||||
@@ -238,7 +238,7 @@ async function markAllRead() {
|
||||
const unreadIds = items.value.filter((n) => !n.read_at && !n.archived).map((n) => n.id);
|
||||
if (!unreadIds.length) return;
|
||||
const now = new Date().toISOString();
|
||||
const { error } = await supabase.from('notifications').update({ read_at: now }).in('id', unreadIds);
|
||||
const { error } = await tenantDb().from('notifications').update({ read_at: now }).in('id', unreadIds);
|
||||
if (error) {
|
||||
toast.add({ severity: 'error', summary: 'Erro', detail: error.message, life: 3500 });
|
||||
return;
|
||||
|
||||
@@ -31,6 +31,7 @@ import { useConfirm } from 'primevue/useconfirm';
|
||||
import { useMelissaPacientes } from './composables/useMelissaPacientes';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useConversationDrawerStore } from '@/stores/conversationDrawerStore';
|
||||
import {
|
||||
listGroups as repoListGroups,
|
||||
@@ -234,8 +235,8 @@ async function persistGroup() {
|
||||
createGroupError.value = '';
|
||||
try {
|
||||
const { ownerId, tenantId } = await _ownerAndTenant();
|
||||
const { error } = await supabase.from('patient_groups').insert({
|
||||
owner_id: ownerId, tenant_id: tenantId,
|
||||
const { error } = await tenantDb().from('patient_groups').insert({
|
||||
owner_id: ownerId,
|
||||
nome: name, cor: color,
|
||||
is_system: false, is_active: true
|
||||
});
|
||||
@@ -260,8 +261,8 @@ async function persistTag() {
|
||||
createTagError.value = '';
|
||||
try {
|
||||
const { ownerId, tenantId } = await _ownerAndTenant();
|
||||
const { error } = await supabase.from('patient_tags').insert({
|
||||
owner_id: ownerId, tenant_id: tenantId,
|
||||
const { error } = await tenantDb().from('patient_tags').insert({
|
||||
owner_id: ownerId,
|
||||
nome: name, cor: color
|
||||
});
|
||||
if (error) throw error;
|
||||
@@ -659,10 +660,9 @@ async function verSessoes(p) {
|
||||
sessoesLoading.value = true;
|
||||
try {
|
||||
const tenantId = tenantStore.activeTenantId || tenantStore.tenantId;
|
||||
const { data, error } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data, error } = await tenantDb().from('agenda_eventos')
|
||||
.select('id, inicio_em, fim_em, tipo, status, titulo, modalidade')
|
||||
.eq('tenant_id', tenantId)
|
||||
|
||||
.eq('patient_id', p.id)
|
||||
.order('inicio_em', { ascending: false })
|
||||
.limit(50);
|
||||
|
||||
@@ -17,6 +17,7 @@ import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
|
||||
import MelissaConfigList from './MelissaConfigList.vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
// InputText/Select/Textarea/ToggleSwitch/Tag/Skeleton: auto via PrimeVueResolver
|
||||
|
||||
@@ -124,8 +125,7 @@ async function load() {
|
||||
const uid = u?.user?.id || null;
|
||||
if (!uid) return;
|
||||
ownerId.value = uid;
|
||||
const { data } = await supabase
|
||||
.from('payment_settings')
|
||||
const { data } = await tenantDb().from('payment_settings')
|
||||
.select('*')
|
||||
.eq('owner_id', uid)
|
||||
.maybeSingle();
|
||||
@@ -140,8 +140,7 @@ async function saveCard(cardKey) {
|
||||
if (!ownerId.value) return;
|
||||
savingCard.value = cardKey;
|
||||
const payload = {
|
||||
owner_id: ownerId.value,
|
||||
tenant_id: tenantStore.activeTenantId || null
|
||||
owner_id: ownerId.value
|
||||
};
|
||||
if (cardKey === 'pix') {
|
||||
Object.assign(payload, {
|
||||
@@ -176,8 +175,7 @@ async function saveCard(cardKey) {
|
||||
payload.observacoes_pagamento = cfg.value.observacoes_pagamento?.trim() ?? '';
|
||||
}
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('payment_settings')
|
||||
const { error } = await tenantDb().from('payment_settings')
|
||||
.upsert(payload, { onConflict: 'owner_id' });
|
||||
if (error) throw error;
|
||||
toast.add({ severity: 'success', summary: 'Salvo', detail: 'Atualizado.', life: 2200 });
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
// Dialog/SelectButton/Button/Tag/ProgressBar/Avatar/Select: auto-import via PrimeVueResolver
|
||||
|
||||
@@ -99,7 +100,7 @@ async function load() {
|
||||
if (!userId.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
let q = supabase.from('recurrence_rules').select('*').order('start_date', { ascending: false }).eq('owner_id', userId.value);
|
||||
let q = tenantDb().from('recurrence_rules').select('*').order('start_date', { ascending: false }).eq('owner_id', userId.value);
|
||||
if (filterStatus.value !== 'all') q = q.eq('status', filterStatus.value);
|
||||
|
||||
const { data: rData, error: rErr } = await q;
|
||||
@@ -109,7 +110,7 @@ async function load() {
|
||||
const patientIds = [...new Set(rawRules.map((r) => r.patient_id).filter(Boolean))];
|
||||
const patientMap = {};
|
||||
if (patientIds.length) {
|
||||
const { data: pts } = await supabase.from('patients')
|
||||
const { data: pts } = await tenantDb().from('patients')
|
||||
.select('id, nome_completo, avatar_url').in('id', patientIds);
|
||||
for (const p of pts || []) patientMap[p.id] = p;
|
||||
}
|
||||
@@ -128,8 +129,8 @@ async function load() {
|
||||
|
||||
async function reloadSessions(ruleIds) {
|
||||
const [exRes, sessRes] = await Promise.all([
|
||||
supabase.from('recurrence_exceptions').select('*').in('recurrence_id', ruleIds).order('original_date'),
|
||||
supabase.from('agenda_eventos')
|
||||
tenantDb().from('recurrence_exceptions').select('*').in('recurrence_id', ruleIds).order('original_date'),
|
||||
tenantDb().from('agenda_eventos')
|
||||
.select('id, recurrence_id, recurrence_date, status, inicio_em, fim_em')
|
||||
.in('recurrence_id', ruleIds).order('inicio_em')
|
||||
]);
|
||||
@@ -298,18 +299,17 @@ const PILL_CLASS = {
|
||||
async function onPillStatusChange(rule, s, newStatus) {
|
||||
try {
|
||||
if (s.real_id) {
|
||||
await supabase.from('agenda_eventos').update({ status: newStatus }).eq('id', s.real_id);
|
||||
await tenantDb().from('agenda_eventos').update({ status: newStatus }).eq('id', s.real_id);
|
||||
} else {
|
||||
const { data: ex } = await supabase.from('agenda_eventos')
|
||||
const { data: ex } = await tenantDb().from('agenda_eventos')
|
||||
.select('id').eq('recurrence_id', rule.id).eq('recurrence_date', s.date).maybeSingle();
|
||||
if (ex?.id) {
|
||||
await supabase.from('agenda_eventos').update({ status: newStatus }).eq('id', ex.id);
|
||||
await tenantDb().from('agenda_eventos').update({ status: newStatus }).eq('id', ex.id);
|
||||
} else {
|
||||
await supabase.from('agenda_eventos').insert({
|
||||
await tenantDb().from('agenda_eventos').insert({
|
||||
recurrence_id: rule.id,
|
||||
recurrence_date: s.date,
|
||||
owner_id: rule.owner_id,
|
||||
tenant_id: rule.tenant_id,
|
||||
tipo: 'sessao',
|
||||
status: newStatus,
|
||||
inicio_em: s.date + 'T' + (rule.start_time || '00:00') + ':00',
|
||||
@@ -332,7 +332,7 @@ async function onCancelRule(rule) {
|
||||
const name = rule._patient?.nome_completo || 'paciente';
|
||||
if (!confirm(`Encerrar a série de "${name}"?\n\nSessões futuras deixarão de ser geradas. Sessões passadas já registradas são mantidas.`)) return;
|
||||
try {
|
||||
await supabase.from('recurrence_rules').update({ status: 'cancelado', updated_at: new Date().toISOString() }).eq('id', rule.id);
|
||||
await tenantDb().from('recurrence_rules').update({ status: 'cancelado', updated_at: new Date().toISOString() }).eq('id', rule.id);
|
||||
toast.add({ severity: 'success', summary: 'Série encerrada', life: 2000 });
|
||||
await load();
|
||||
} catch (e) {
|
||||
@@ -342,7 +342,7 @@ async function onCancelRule(rule) {
|
||||
|
||||
async function onReactivateRule(rule) {
|
||||
try {
|
||||
await supabase.from('recurrence_rules').update({ status: 'ativo', updated_at: new Date().toISOString() }).eq('id', rule.id);
|
||||
await tenantDb().from('recurrence_rules').update({ status: 'ativo', updated_at: new Date().toISOString() }).eq('id', rule.id);
|
||||
toast.add({ severity: 'success', summary: 'Série reativada', life: 2000 });
|
||||
await load();
|
||||
} catch (e) {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { exportSessionsToPDF, exportSessionsToXLSX, exportSessionsToCSV } from '@/services/reportExport.service';
|
||||
// Chart/DataTable/Column/Tag/Skeleton: auto via PrimeVueResolver
|
||||
@@ -86,10 +87,9 @@ async function loadSessions() {
|
||||
sessions.value = [];
|
||||
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data, error } = await tenantDb().from('agenda_eventos')
|
||||
.select('id, inicio_em, fim_em, status, modalidade, tipo, titulo, titulo_custom, patient_id, patients(nome_completo)')
|
||||
.eq('tenant_id', tenantId)
|
||||
|
||||
.eq('owner_id', uid)
|
||||
.gte('inicio_em', start.toISOString())
|
||||
.lte('inicio_em', end.toISOString())
|
||||
|
||||
@@ -20,6 +20,7 @@ import { ref, reactive, computed, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import PatientCadastroDialog from '@/components/ui/PatientCadastroDialog.vue';
|
||||
// DataTable/Column/Paginator/Dialog/InputText/Button: auto via PrimeVueResolver
|
||||
|
||||
@@ -166,19 +167,18 @@ async function load() {
|
||||
const ownerId = await getOwnerId();
|
||||
// Tenta a view agregada primeiro (já traz pacientes_count); se falhar
|
||||
// (RLS, view não existir), cai pra patient_tags + count manual.
|
||||
const v = await supabase.from('v_tag_patient_counts').select('*').eq('owner_id', ownerId).order('nome', { ascending: true });
|
||||
const v = await tenantDb().from('v_tag_patient_counts').select('*').eq('owner_id', ownerId).order('nome', { ascending: true });
|
||||
if (!v.error) {
|
||||
tags.value = (v.data || []).map(normalize);
|
||||
return;
|
||||
}
|
||||
const t = await supabase.from('patient_tags').select('*').eq('owner_id', ownerId);
|
||||
const t = await tenantDb().from('patient_tags').select('*').eq('owner_id', ownerId);
|
||||
if (t.error) throw t.error;
|
||||
// Sem a view, busca o count via patient_patient_tag agrupado por tag_id.
|
||||
const ids = (t.data || []).map((x) => x.id);
|
||||
let counts = new Map();
|
||||
if (ids.length > 0) {
|
||||
const { data: vData } = await supabase
|
||||
.from('patient_patient_tag').select('tag_id').in('tag_id', ids);
|
||||
const { data: vData } = await tenantDb().from('patient_patient_tag').select('tag_id').in('tag_id', ids);
|
||||
for (const r of vData || []) {
|
||||
counts.set(r.tag_id, (counts.get(r.tag_id) || 0) + 1);
|
||||
}
|
||||
@@ -249,13 +249,13 @@ async function salvar() {
|
||||
const cor = dlgForm.value.cor.startsWith('#') ? dlgForm.value.cor : '#' + dlgForm.value.cor;
|
||||
if (dlgMode.value === 'create') {
|
||||
const tenantId = await getActiveTenantId(ownerId);
|
||||
const { error } = await supabase.from('patient_tags').insert({
|
||||
owner_id: ownerId, tenant_id: tenantId, nome, cor
|
||||
const { error } = await tenantDb().from('patient_tags').insert({
|
||||
owner_id: ownerId, nome, cor
|
||||
});
|
||||
if (error) throw error;
|
||||
toast.add({ severity: 'success', summary: 'Tag criada', life: 2200 });
|
||||
} else {
|
||||
const { error } = await supabase.from('patient_tags')
|
||||
const { error } = await tenantDb().from('patient_tags')
|
||||
.update({ nome, cor, updated_at: new Date().toISOString() })
|
||||
.eq('id', dlgForm.value.id).eq('owner_id', ownerId);
|
||||
if (error) throw error;
|
||||
@@ -287,8 +287,8 @@ async function excluir(row) {
|
||||
saving.value = true;
|
||||
try {
|
||||
const ownerId = await getOwnerId();
|
||||
await supabase.from('patient_patient_tag').delete().eq('tag_id', row.id);
|
||||
const { error } = await supabase.from('patient_tags').delete().eq('id', row.id).eq('owner_id', ownerId);
|
||||
await tenantDb().from('patient_patient_tag').delete().eq('tag_id', row.id);
|
||||
const { error } = await tenantDb().from('patient_tags').delete().eq('id', row.id).eq('owner_id', ownerId);
|
||||
if (error) throw error;
|
||||
toast.add({ severity: 'success', summary: 'Tag excluída', life: 2200 });
|
||||
await load();
|
||||
@@ -351,8 +351,7 @@ async function openTagPatientsModal(tagRow) {
|
||||
patientsDialog.items = [];
|
||||
patientsDialog.search = '';
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('patient_patient_tag')
|
||||
const { data, error } = await tenantDb().from('patient_patient_tag')
|
||||
.select('tag_id, patient:patients(id, nome_completo, email_principal, telefone, avatar_url)')
|
||||
.eq('tag_id', tagRow.id);
|
||||
if (error) throw error;
|
||||
|
||||
@@ -28,6 +28,7 @@ import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
|
||||
import { useAgendaSettings } from '@/features/agenda/composables/useAgendaSettings';
|
||||
@@ -92,7 +93,6 @@ function normalizeForMelissa(r, paymentStateMap = null, paymentAmountMap = null,
|
||||
status: r.status || (isOccurrence ? 'agendado' : ''),
|
||||
titulo: r.titulo || r.titulo_custom || '',
|
||||
owner_id: r.owner_id ?? null,
|
||||
tenant_id: r.tenant_id ?? null,
|
||||
terapeuta_id: r.terapeuta_id ?? null,
|
||||
billing_contract_id: r.billing_contract_id ?? null,
|
||||
patient_id: r.patient_id ?? r.paciente_id ?? null,
|
||||
@@ -212,7 +212,6 @@ export function useMelissaAgenda() {
|
||||
})
|
||||
.map((i) => ({
|
||||
id: i.id,
|
||||
tenant_id: i.tenant_id ?? null,
|
||||
created_by: i.created_by ?? null,
|
||||
name: String(i.name || '').trim() || 'Sem nome',
|
||||
description: i.description || '',
|
||||
@@ -447,8 +446,7 @@ export function useMelissaAgenda() {
|
||||
// paymentState='pending' (badge $ residual). Tratamos cancelled
|
||||
// como "sem cobrança ativa" → cai pro default 'none'.
|
||||
if (realIds.length) {
|
||||
const { data: recs } = await supabase
|
||||
.from('financial_records')
|
||||
const { data: recs } = await tenantDb().from('financial_records')
|
||||
.select('agenda_evento_id, paid_at, status, amount, final_amount')
|
||||
.in('agenda_evento_id', realIds)
|
||||
.neq('status', 'cancelled');
|
||||
@@ -502,8 +500,7 @@ export function useMelissaAgenda() {
|
||||
// 2) Acha patient_id direto das rules (fonte autoritativa,
|
||||
// funciona até quando rule não tem nenhum evento
|
||||
// materializado — caso pacote saldo recém-criado).
|
||||
const { data: rulesData } = await supabase
|
||||
.from('recurrence_rules')
|
||||
const { data: rulesData } = await tenantDb().from('recurrence_rules')
|
||||
.select('id, patient_id')
|
||||
.in('id', [...ruleIdsInView]);
|
||||
const rulePatientFromRule = new Map();
|
||||
@@ -513,15 +510,13 @@ export function useMelissaAgenda() {
|
||||
|
||||
// 3) Acha eventos (em qualquer semana) das rules em view +
|
||||
// seus records paid/pending pra detectar estado.
|
||||
const { data: allRuleEvents } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data: allRuleEvents } = await tenantDb().from('agenda_eventos')
|
||||
.select('id, recurrence_id, patient_id')
|
||||
.in('recurrence_id', [...ruleIdsInView]);
|
||||
const ruleEventIds = (allRuleEvents || []).map((e) => e.id);
|
||||
let ruleRecords = [];
|
||||
if (ruleEventIds.length) {
|
||||
const { data: rr } = await supabase
|
||||
.from('financial_records')
|
||||
const { data: rr } = await tenantDb().from('financial_records')
|
||||
.select('agenda_evento_id, paid_at, status')
|
||||
.in('agenda_evento_id', ruleEventIds)
|
||||
.neq('status', 'cancelled');
|
||||
@@ -553,8 +548,7 @@ export function useMelissaAgenda() {
|
||||
const allPatientIds = [...new Set(rulePatientFromRule.values())];
|
||||
let activePackages = [];
|
||||
if (allPatientIds.length) {
|
||||
const { data: contracts } = await supabase
|
||||
.from('billing_contracts')
|
||||
const { data: contracts } = await tenantDb().from('billing_contracts')
|
||||
.select('id, patient_id, charging_style, status, type, total_sessions, sessions_used, package_price')
|
||||
.in('patient_id', allPatientIds);
|
||||
activePackages = (contracts || []).filter((c) => c.type === 'package' && c.status === 'active');
|
||||
@@ -590,8 +584,7 @@ export function useMelissaAgenda() {
|
||||
.filter(([, pid]) => upfrontPatients.has(pid))
|
||||
.map(([rid]) => rid);
|
||||
if (ruleIdsToPropagate.length) {
|
||||
const { data: siblings } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data: siblings } = await tenantDb().from('agenda_eventos')
|
||||
.select('id, recurrence_id')
|
||||
.in('recurrence_id', ruleIdsToPropagate);
|
||||
// Reusa contractByPatient da query unificada acima
|
||||
@@ -1245,8 +1238,7 @@ function _buildHandlers(deps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { data: existing, error: exErr } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data: existing, error: exErr } = await tenantDb().from('agenda_eventos')
|
||||
.select('id')
|
||||
.eq('recurrence_id', rid)
|
||||
.eq('recurrence_date', rDate)
|
||||
@@ -1259,7 +1251,6 @@ function _buildHandlers(deps) {
|
||||
} else {
|
||||
const created = await create({
|
||||
owner_id: ownerId.value,
|
||||
tenant_id: clinicTenantId.value,
|
||||
recurrence_id: rid,
|
||||
recurrence_date: rDate,
|
||||
tipo: 'sessao',
|
||||
@@ -1382,8 +1373,7 @@ function _buildOnDialogSave(deps) {
|
||||
if (!normalized.owner_id && ownerId.value) normalized.owner_id = ownerId.value;
|
||||
|
||||
const clinicId = clinicTenantId.value;
|
||||
if (!clinicId) throw new Error('tenant_id da clínica não encontrado no tenantStore.');
|
||||
normalized.tenant_id = clinicId;
|
||||
if (!clinicId) throw new Error('tenant da clínica não encontrado no tenantStore.');
|
||||
|
||||
if (!normalized.visibility_scope) normalized.visibility_scope = 'public';
|
||||
|
||||
@@ -1423,7 +1413,6 @@ function _buildOnDialogSave(deps) {
|
||||
}
|
||||
|
||||
const rule = {
|
||||
tenant_id: clinicId,
|
||||
owner_id: normalized.owner_id,
|
||||
therapist_id: normalized.terapeuta_id ?? null,
|
||||
patient_id: normalized.paciente_id ?? null,
|
||||
@@ -1459,7 +1448,6 @@ function _buildOnDialogSave(deps) {
|
||||
if (recorrencia?.conflitos?.length && createdRule?.id) {
|
||||
const exceptions = recorrencia.conflitos.map((c) => ({
|
||||
recurrence_id: createdRule.id,
|
||||
tenant_id: clinicId,
|
||||
original_date: c.date,
|
||||
type:
|
||||
c.conflict.type === 'feriado' ? 'holiday_block'
|
||||
@@ -1468,7 +1456,7 @@ function _buildOnDialogSave(deps) {
|
||||
: 'cancel_session',
|
||||
reason: c.conflict.label
|
||||
}));
|
||||
const { error: exErr } = await supabase.from('recurrence_exceptions').insert(exceptions);
|
||||
const { error: exErr } = await tenantDb().from('recurrence_exceptions').insert(exceptions);
|
||||
if (exErr) console.warn('[useMelissaAgenda] exceptions insert', exErr);
|
||||
}
|
||||
|
||||
@@ -1521,7 +1509,6 @@ function _buildOnDialogSave(deps) {
|
||||
const endDt = new Date(startDt.getTime() + durMin * 60 * 1000);
|
||||
await create({
|
||||
owner_id: createdRule.owner_id,
|
||||
tenant_id: clinicId,
|
||||
terapeuta_id: createdRule.therapist_id ?? null,
|
||||
recurrence_id: createdRule.id,
|
||||
recurrence_date: firstISO,
|
||||
@@ -1564,7 +1551,6 @@ function _buildOnDialogSave(deps) {
|
||||
if (originalDate) {
|
||||
await upsertException({
|
||||
recurrence_id: recurrenceId,
|
||||
tenant_id: clinicId,
|
||||
original_date: originalDate,
|
||||
type: 'reschedule_session',
|
||||
new_date: normalized.inicio_em?.slice(0, 10),
|
||||
@@ -1579,7 +1565,6 @@ function _buildOnDialogSave(deps) {
|
||||
} else if (originalDate) {
|
||||
await upsertException({
|
||||
recurrence_id: recurrenceId,
|
||||
tenant_id: clinicId,
|
||||
original_date: originalDate,
|
||||
type: 'reschedule_session',
|
||||
new_date: normalized.inicio_em?.slice(0, 10),
|
||||
@@ -1591,8 +1576,7 @@ function _buildOnDialogSave(deps) {
|
||||
extra_fields: normalized.extra_fields ?? null
|
||||
});
|
||||
if (arg.onSaved) {
|
||||
const { data: existing } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data: existing } = await tenantDb().from('agenda_eventos')
|
||||
.select('id')
|
||||
.eq('recurrence_id', recurrenceId)
|
||||
.eq('recurrence_date', originalDate)
|
||||
@@ -1602,7 +1586,6 @@ function _buildOnDialogSave(deps) {
|
||||
} else {
|
||||
const mat = await create({
|
||||
owner_id: normalized.owner_id,
|
||||
tenant_id: clinicId,
|
||||
recurrence_id: recurrenceId,
|
||||
recurrence_date: originalDate,
|
||||
tipo: normalized.tipo,
|
||||
@@ -1686,8 +1669,7 @@ function _buildOnDialogSave(deps) {
|
||||
insurance_plan_service_id: normalized.insurance_plan_service_id ?? null
|
||||
});
|
||||
|
||||
await supabase
|
||||
.from('agenda_eventos')
|
||||
await tenantDb().from('agenda_eventos')
|
||||
.update({
|
||||
modalidade: normalized.modalidade ?? 'presencial',
|
||||
titulo_custom: normalized.titulo_custom ?? null,
|
||||
@@ -1733,8 +1715,7 @@ function _buildOnDialogSave(deps) {
|
||||
insurance_plan_service_id: normalized.insurance_plan_service_id ?? null
|
||||
});
|
||||
|
||||
await supabase
|
||||
.from('agenda_eventos')
|
||||
await tenantDb().from('agenda_eventos')
|
||||
.update({
|
||||
modalidade: normalized.modalidade ?? 'presencial',
|
||||
titulo_custom: normalized.titulo_custom ?? null,
|
||||
@@ -1820,8 +1801,7 @@ function _buildOnDialogSave(deps) {
|
||||
: (arg?.paymentMethod || 'link');
|
||||
const paidNow = !isConvenio && arg?.markPaidNow === true && method !== 'link';
|
||||
try {
|
||||
const { data: recRow } = await supabase
|
||||
.from('financial_records')
|
||||
const { data: recRow } = await tenantDb().from('financial_records')
|
||||
.select('id')
|
||||
.eq('agenda_evento_id', createdEventId)
|
||||
.order('created_at', { ascending: false })
|
||||
@@ -1836,7 +1816,7 @@ function _buildOnDialogSave(deps) {
|
||||
patch.status = 'paid';
|
||||
patch.paid_at = new Date().toISOString();
|
||||
}
|
||||
const { error: upErr } = await supabase.from('financial_records').update(patch).eq('id', recRow.id);
|
||||
const { error: upErr } = await tenantDb().from('financial_records').update(patch).eq('id', recRow.id);
|
||||
if (upErr) throw upErr;
|
||||
}
|
||||
} catch (e2) {
|
||||
@@ -1893,8 +1873,7 @@ function _buildOnDialogSave(deps) {
|
||||
let detail = 'Já existe um compromisso nesse horário. Verifique a agenda e escolha outro horário.';
|
||||
try {
|
||||
if (normalized?.inicio_em && normalized?.fim_em && normalized?.owner_id) {
|
||||
const { data: conflicting } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data: conflicting } = await tenantDb().from('agenda_eventos')
|
||||
.select('titulo, inicio_em, fim_em')
|
||||
.eq('owner_id', normalized.owner_id)
|
||||
.lt('inicio_em', normalized.fim_em)
|
||||
@@ -1961,7 +1940,6 @@ function _buildOnDialogDelete(deps) {
|
||||
if (originalDate && recurrenceId) {
|
||||
await upsertException({
|
||||
recurrence_id: recurrenceId,
|
||||
tenant_id: clinicTenantId.value,
|
||||
original_date: originalDate,
|
||||
type: 'cancel_session'
|
||||
});
|
||||
@@ -1990,8 +1968,7 @@ function _buildOnDialogDelete(deps) {
|
||||
|
||||
if (isVirtual) {
|
||||
const rDate = row.original_date || row.inicio_em?.slice(0, 10);
|
||||
const existing = await supabase
|
||||
.from('agenda_eventos')
|
||||
const existing = await tenantDb().from('agenda_eventos')
|
||||
.select('id')
|
||||
.eq('recurrence_id', recurrenceId)
|
||||
.eq('recurrence_date', rDate)
|
||||
@@ -2002,7 +1979,6 @@ function _buildOnDialogDelete(deps) {
|
||||
} else {
|
||||
await create({
|
||||
owner_id: ownerId.value,
|
||||
tenant_id: clinicTenantId.value,
|
||||
tipo: row.tipo || 'sessao',
|
||||
status: row.status || 'agendado',
|
||||
inicio_em: row.inicio_em,
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
*/
|
||||
import { ref } from 'vue';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
|
||||
const STATUS_LABEL = {
|
||||
@@ -159,8 +160,7 @@ export function useMelissaAgendaHistorico(opts = {}) {
|
||||
const patientIds = [...new Set(list.map(extractPatientId).filter(Boolean))];
|
||||
const patientMap = new Map();
|
||||
if (patientIds.length) {
|
||||
const { data: pats } = await supabase
|
||||
.from('patients')
|
||||
const { data: pats } = await tenantDb().from('patients')
|
||||
.select('id, nome_completo')
|
||||
.in('id', patientIds);
|
||||
for (const p of pats || []) patientMap.set(p.id, p.nome_completo);
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
*/
|
||||
import { ref, watch, onMounted, computed } from 'vue';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { useMelissaCacheStore, MELISSA_CACHE_TTL } from '@/stores/melissaCacheStore';
|
||||
import { useRecurrence } from '@/features/agenda/composables/useRecurrence';
|
||||
@@ -101,8 +102,7 @@ async function _fetchRange(start, end) {
|
||||
|
||||
// Recurrence_id/date inclusos no select pra mergeWithStoredSessions dedupar
|
||||
// ocorrências já materializadas (sessões reais ganham precedência sobre virtuais).
|
||||
const { data, error } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data, error } = await tenantDb().from('agenda_eventos')
|
||||
.select('id, tipo, status, titulo, inicio_em, fim_em, modalidade, observacoes, patient_id, price, billed, recurrence_id, recurrence_date, patients!agenda_eventos_patient_id_fkey(nome_completo, status)')
|
||||
.eq('owner_id', userId)
|
||||
.is('mirror_of_event_id', null)
|
||||
@@ -259,16 +259,14 @@ export async function searchEventosByText(termo) {
|
||||
|
||||
try {
|
||||
const [byPatient, byTitle] = await Promise.all([
|
||||
supabase
|
||||
.from('agenda_eventos')
|
||||
tenantDb().from('agenda_eventos')
|
||||
.select(SELECT.replace('patients!agenda_eventos_patient_id_fkey', 'patients!inner!agenda_eventos_patient_id_fkey'))
|
||||
.eq('owner_id', userId)
|
||||
.is('mirror_of_event_id', null)
|
||||
.filter('patients.nome_completo', 'imatch', pattern)
|
||||
.order('inicio_em', { ascending: false })
|
||||
.limit(20),
|
||||
supabase
|
||||
.from('agenda_eventos')
|
||||
tenantDb().from('agenda_eventos')
|
||||
.select(SELECT)
|
||||
.eq('owner_id', userId)
|
||||
.is('mirror_of_event_id', null)
|
||||
@@ -323,8 +321,7 @@ export function useMelissaTodasSessoesPaciente() {
|
||||
const tid = tenantStore.activeTenantId || tenantStore.tenantId || null;
|
||||
if (!userId || !tid) { eventos.value = []; return; }
|
||||
|
||||
const { data, error: err } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data, error: err } = await tenantDb().from('agenda_eventos')
|
||||
.select('id, tipo, status, titulo, inicio_em, fim_em, modalidade, observacoes, patient_id, price, billed, recurrence_id, recurrence_date, patients!agenda_eventos_patient_id_fkey(nome_completo, status)')
|
||||
.eq('owner_id', userId)
|
||||
.eq('patient_id', patientId)
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { useMelissaCacheStore, MELISSA_CACHE_TTL } from '@/stores/melissaCacheStore';
|
||||
|
||||
@@ -62,11 +63,10 @@ export function useMelissaPacientes(opts = {}) {
|
||||
}
|
||||
|
||||
async function _doFetch(userId, tid, cacheKey) {
|
||||
const { data, error: err } = await supabase
|
||||
.from('patients')
|
||||
const { data, error: err } = await tenantDb().from('patients')
|
||||
.select('id, nome_completo, email_principal, telefone, status, avatar_url, last_attended_at, created_at, data_nascimento')
|
||||
.eq('owner_id', userId)
|
||||
.eq('tenant_id', tid)
|
||||
|
||||
.order('nome_completo', { ascending: true })
|
||||
.limit(1000);
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
*/
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
|
||||
const SEARCH_MAX_LEN = 100;
|
||||
@@ -106,14 +107,13 @@ export function useMelissaPacientesAside(opts) {
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
let q = supabase
|
||||
.from('patients')
|
||||
let q = tenantDb().from('patients')
|
||||
.select(
|
||||
'id, nome_completo, email_principal, telefone, status, avatar_url, last_attended_at, created_at, data_nascimento',
|
||||
{ count: 'exact' }
|
||||
)
|
||||
.eq('owner_id', userId)
|
||||
.eq('tenant_id', tid)
|
||||
|
||||
// Mesmo critério do useMelissaPacientes original (onlyActive=true).
|
||||
// DB tem valores variados ('ativo'/'Ativo'/'active'); aceita os 3.
|
||||
.in('status', ['ativo', 'Ativo', 'active'])
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
|
||||
const EMPTY = { count: 0, ultimaMsg: '', ultimoNome: '', ultimaEm: null };
|
||||
@@ -36,10 +37,9 @@ export function useMelissaWhatsapp() {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
const { data, error: err } = await supabase
|
||||
.from('conversation_threads')
|
||||
const { data, error: err } = await tenantDb().from('conversation_threads')
|
||||
.select('patient_name, contact_number, unread_count, last_message_body, last_message_at, last_message_direction')
|
||||
.eq('tenant_id', tid)
|
||||
|
||||
.eq('channel', 'whatsapp')
|
||||
.gt('unread_count', 0)
|
||||
.order('last_message_at', { ascending: false })
|
||||
|
||||
Reference in New Issue
Block a user