+ Menu Hover no Layout Rail, Twilio, Sms, Email, Templates, LNovo Layout Configurações
This commit is contained in:
@@ -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,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user