+ Menu Hover no Layout Rail, Twilio, Sms, Email, Templates, LNovo Layout Configurações

This commit is contained in:
Leonardo
2026-03-25 08:39:45 -03:00
parent 53a4980396
commit 3f1786c9bf
59 changed files with 2553 additions and 1106 deletions
+308
View File
@@ -0,0 +1,308 @@
/*
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Arquivo: src/stores/twilioWhatsappStore.js
| Data: 2026
|--------------------------------------------------------------------------
| Pinia store para gerenciamento de subcontas Twilio WhatsApp.
| Usado tanto pelo painel SaaS admin quanto pelo self-service do tenant.
|--------------------------------------------------------------------------
*/
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import * as service from '@/services/twilioWhatsappService'
export const useTwilioWhatsappStore = defineStore('twilioWhatsapp', () => {
// ── Estado ─────────────────────────────────────────────────────────
// Admin: lista de todos os canais
const allChannels = ref([])
const loadingChannels = ref(false)
// Tenant self-service: canal do tenant atual
const myChannel = ref(null)
const loadingMyChannel = ref(false)
// Operações em andamento
const provisioning = ref(false)
const deprovisioning = ref(false)
const suspending = ref(false)
const reactivating = ref(false)
const syncingUsage = ref(false)
const testingSend = ref(false)
// Logs de mensagens (self-service)
const messageLogs = ref([])
const loadingLogs = ref(false)
// Dados de uso (admin)
const usageReport = ref([])
const loadingUsage = ref(false)
// Números disponíveis para compra
const availableNumbers = ref([])
const loadingNumbers = ref(false)
// Erros
const lastError = ref(null)
// ── Computed ───────────────────────────────────────────────────────
const hasActiveChannel = computed(() => !!myChannel.value?.twilio_subaccount_sid && myChannel.value?.is_active)
const myChannelStatus = computed(() => {
if (!myChannel.value) return 'not_provisioned'
if (!myChannel.value.twilio_subaccount_sid) return 'not_provisioned'
if (!myChannel.value.is_active) return 'suspended'
return myChannel.value.connection_status ?? 'connected'
})
const totalMonthlyCostBrl = computed(() =>
allChannels.value.reduce((sum, ch) => sum + parseFloat(ch.current_month_cost_brl ?? 0), 0)
)
const totalMonthlyRevenueBrl = computed(() =>
allChannels.value.reduce((sum, ch) => sum + parseFloat(ch.current_month_revenue_brl ?? 0), 0)
)
const totalMonthlyMarginBrl = computed(() => totalMonthlyRevenueBrl.value - totalMonthlyCostBrl.value)
const activeCount = computed(() => allChannels.value.filter(ch => ch.is_active).length)
const suspendedCount = computed(() => allChannels.value.filter(ch => !ch.is_active && ch.twilio_subaccount_sid).length)
// ── Ações Admin ────────────────────────────────────────────────────
async function loadAllChannels() {
loadingChannels.value = true
lastError.value = null
try {
allChannels.value = await service.getAllChannels()
} catch (e) {
lastError.value = e.message
throw e
} finally {
loadingChannels.value = false
}
}
async function provision(tenantId, options = {}) {
provisioning.value = true
lastError.value = null
try {
const result = await service.provisionTenant(tenantId, options)
await loadAllChannels()
return result
} catch (e) {
lastError.value = e.message
throw e
} finally {
provisioning.value = false
}
}
async function deprovision(channelId) {
deprovisioning.value = true
lastError.value = null
try {
const result = await service.deprovisionTenant(channelId)
await loadAllChannels()
return result
} catch (e) {
lastError.value = e.message
throw e
} finally {
deprovisioning.value = false
}
}
async function suspend(channelId) {
suspending.value = true
lastError.value = null
try {
const result = await service.suspendTenant(channelId)
const ch = allChannels.value.find(c => c.channel_id === channelId)
if (ch) {
ch.is_active = false
ch.connection_status = 'disconnected'
}
return result
} catch (e) {
lastError.value = e.message
throw e
} finally {
suspending.value = false
}
}
async function reactivate(channelId) {
reactivating.value = true
lastError.value = null
try {
const result = await service.reactivateTenant(channelId)
const ch = allChannels.value.find(c => c.channel_id === channelId)
if (ch) {
ch.is_active = true
ch.connection_status = 'connected'
}
return result
} catch (e) {
lastError.value = e.message
throw e
} finally {
reactivating.value = false
}
}
async function syncUsageAll(channelId = null) {
syncingUsage.value = true
lastError.value = null
try {
const result = await service.syncUsage(channelId)
await loadAllChannels()
return result
} catch (e) {
lastError.value = e.message
throw e
} finally {
syncingUsage.value = false
}
}
async function loadUsageReport(filters = {}) {
loadingUsage.value = true
lastError.value = null
try {
usageReport.value = await service.getUsageReport(filters)
} catch (e) {
lastError.value = e.message
throw e
} finally {
loadingUsage.value = false
}
}
async function updatePricing(channelId, pricing) {
lastError.value = null
try {
const updated = await service.updatePricing(channelId, pricing)
const ch = allChannels.value.find(c => c.channel_id === channelId)
if (ch) {
ch.cost_per_message_usd = updated.cost_per_message_usd
ch.price_per_message_brl = updated.price_per_message_brl
}
return updated
} catch (e) {
lastError.value = e.message
throw e
}
}
async function loadAvailableNumbers(country = 'BR', areaCode = null) {
loadingNumbers.value = true
lastError.value = null
try {
const result = await service.searchNumbers(country, areaCode)
availableNumbers.value = result.numbers ?? []
} catch (e) {
lastError.value = e.message
throw e
} finally {
loadingNumbers.value = false
}
}
// ── Ações Tenant Self-service ──────────────────────────────────────
async function loadMyChannel(tenantId) {
loadingMyChannel.value = true
lastError.value = null
try {
myChannel.value = await service.getChannel(tenantId)
} catch (e) {
lastError.value = e.message
throw e
} finally {
loadingMyChannel.value = false
}
}
async function provisionMyChannel(tenantId, options = {}) {
provisioning.value = true
lastError.value = null
try {
const result = await service.provisionTenant(tenantId, options)
await loadMyChannel(tenantId)
return result
} catch (e) {
lastError.value = e.message
throw e
} finally {
provisioning.value = false
}
}
async function testSendMessage(channelId, toNumber, message) {
testingSend.value = true
lastError.value = null
try {
return await service.testSend(channelId, toNumber, message)
} catch (e) {
lastError.value = e.message
throw e
} finally {
testingSend.value = false
}
}
async function loadMyLogs(tenantId, limit = 50) {
loadingLogs.value = true
lastError.value = null
try {
messageLogs.value = await service.getMessageLogs(tenantId, limit)
} catch (e) {
lastError.value = e.message
throw e
} finally {
loadingLogs.value = false
}
}
// ── Reset ──────────────────────────────────────────────────────────
function $reset() {
allChannels.value = []
myChannel.value = null
messageLogs.value = []
usageReport.value = []
availableNumbers.value = []
lastError.value = null
}
return {
// Estado
allChannels, loadingChannels,
myChannel, loadingMyChannel,
provisioning, deprovisioning, suspending, reactivating,
syncingUsage, testingSend,
messageLogs, loadingLogs,
usageReport, loadingUsage,
availableNumbers, loadingNumbers,
lastError,
// Computed
hasActiveChannel, myChannelStatus,
totalMonthlyCostBrl, totalMonthlyRevenueBrl, totalMonthlyMarginBrl,
activeCount, suspendedCount,
// Admin
loadAllChannels, provision, deprovision, suspend, reactivate,
syncUsageAll, loadUsageReport, updatePricing, loadAvailableNumbers,
// Self-service
loadMyChannel, provisionMyChannel, testSendMessage, loadMyLogs,
$reset,
}
})