Files
scoreodonto.com/frontend/views/newwhats/hooks/useSessionsLogic.ts
T
VPS 4 Builder 3042ddca38 feat(newwhats): frontend do plugin (Inbox/Sessions/Secretária) em tela cheia
- /wa-inbox, /wa-sessions e /wa-secretaria rodam sem a sidebar do scoreodonto
  (isStandaloneView); cada tela tem seu "Voltar" (Secretária ganhou botão na ThinNav).
- SessionsView: banner "Você já possui acesso ao WhatsApp" quando há sessão ativa.
- avatares: usa a URL do CDN (contactAvatarUrl/instance.avatar) direto; Avatar
  exibe sem depender de version; self-heal no onError re-busca a foto atual.
- deps: framer-motion, immer. Dev override com HMR (docker-compose.dev.yml).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 20:54:24 +02:00

186 lines
6.2 KiB
TypeScript

/**
* useSessionsLogic — toda a lógica da página /sessions. Portado do motor.
* Adaptações no satélite: imports apontam p/ os shims; o bootstrap chama
* socketService.connect() sem depender de localStorage['token'] (o shim lê o
* token do scoreodonto internamente).
*/
import { useState, useEffect, useCallback } from 'react'
import { useInstanceStore, type Instance } from '../store/instanceStore'
import { socketService } from '../services/socketService'
interface QrModalState {
instanceId: string
instanceName: string
qrBase64: string | null
}
interface Toast {
msg: string
ok: boolean
}
export function useSessionsLogic() {
const storeInstances = useInstanceStore((s) => s.instances)
const storeLoading = useInstanceStore((s) => s.loading)
const loadInstances = useInstanceStore((s) => s.loadInstances)
const createInstance = useInstanceStore((s) => s.createInstance)
const connectInst = useInstanceStore((s) => s.connectInstance)
const disconnectInst = useInstanceStore((s) => s.disconnectInstance)
const deleteInst = useInstanceStore((s) => s.deleteInstance)
const patchInstance = useInstanceStore((s) => s.patchInstance)
const setQr = useInstanceStore((s) => s.setQr)
const clearQr = useInstanceStore((s) => s.clearQr)
const qrByInstance = useInstanceStore((s) => s.qrByInstance)
const initListeners = useInstanceStore((s) => s.initSocketListeners)
const [actionLoading, setActionLoading] = useState<string | null>(null)
const [addOpen, setAddOpen] = useState(false)
const [newName, setNewName] = useState('')
const [creating, setCreating] = useState(false)
const [qrModal, setQrModal] = useState<QrModalState | null>(null)
const [deleteTarget, setDeleteTarget] = useState<Instance | null>(null)
const [deleting, setDeleting] = useState(false)
const [toast, setToast] = useState<Toast | null>(null)
useEffect(() => {
socketService.connect() // shim lê o token do scoreodonto internamente
initListeners()
loadInstances()
}, [])
useEffect(() => {
const onStatus = ({ instanceId, status, qrBase64 }: { instanceId: string; status: Instance['status']; qrBase64?: string }) => {
patchInstance(instanceId, { status })
if (qrBase64) {
setQr(instanceId, qrBase64)
setQrModal((prev) => (prev?.instanceId === instanceId ? { ...prev, qrBase64 } : prev))
}
if (status === 'CONNECTED') {
clearQr(instanceId)
setQrModal((prev) => (prev?.instanceId === instanceId ? null : prev))
showToast('WhatsApp conectado!')
}
}
const onQr = ({ instanceId, qrBase64 }: { instanceId: string; qrBase64: string }) => {
setQr(instanceId, qrBase64)
patchInstance(instanceId, { status: 'QR_PENDING' })
setQrModal((prev) => (prev?.instanceId === instanceId ? { ...prev, qrBase64 } : prev))
}
socketService.on('instance:status', onStatus)
socketService.on('qr', onQr)
return () => {
socketService.off('instance:status', onStatus)
socketService.off('qr', onQr)
}
}, [])
const showToast = (msg: string, ok = true) => {
setToast({ msg, ok })
setTimeout(() => setToast(null), 3000)
}
const openAddModal = useCallback(() => setAddOpen(true), [])
const closeAddModal = useCallback(() => { setAddOpen(false); setNewName('') }, [])
const handleCreate = useCallback(async () => {
const name = newName.trim()
if (!name) return
setCreating(true)
try {
const created = await createInstance(name)
closeAddModal()
if (created?.id) {
socketService.joinInstance(created.id)
await connectInst(created.id)
setQrModal({ instanceId: created.id, instanceName: name, qrBase64: null })
}
} catch (err: any) {
showToast(err.response?.data?.error ?? 'Erro ao criar instância', false)
} finally {
setCreating(false)
}
}, [newName, createInstance, connectInst, closeAddModal])
const handleConnect = useCallback(async (inst: Instance) => {
setActionLoading(`connect-${inst.id}`)
try {
socketService.joinInstance(inst.id)
await connectInst(inst.id)
setQrModal({ instanceId: inst.id, instanceName: inst.name, qrBase64: null })
} catch (err: any) {
showToast(err.response?.data?.error ?? 'Erro ao conectar', false)
} finally {
setActionLoading(null)
}
}, [connectInst])
const handleDisconnect = useCallback(async (inst: Instance) => {
setActionLoading(`disconnect-${inst.id}`)
try {
await disconnectInst(inst.id)
showToast('Instância desconectada')
} catch (err: any) {
showToast(err.response?.data?.error ?? 'Erro ao desconectar', false)
} finally {
setActionLoading(null)
}
}, [disconnectInst])
const handleShowQr = useCallback(async (inst: Instance) => {
socketService.joinInstance(inst.id)
const cached = qrByInstance[inst.id]
if (cached) {
setQrModal({ instanceId: inst.id, instanceName: inst.name, qrBase64: cached })
return
}
setQrModal({ instanceId: inst.id, instanceName: inst.name, qrBase64: null })
}, [qrByInstance])
const handleDelete = useCallback(async () => {
if (!deleteTarget) return
setDeleting(true)
try {
await deleteInst(deleteTarget.id)
setDeleteTarget(null)
showToast('Instância removida')
} catch (err: any) {
showToast(err.response?.data?.error ?? 'Erro ao deletar', false)
} finally {
setDeleting(false)
}
}, [deleteTarget, deleteInst])
const handleNewQr = useCallback(async (instanceId: string) => {
socketService.joinInstance(instanceId)
await connectInst(instanceId)
}, [connectInst])
return {
instances: storeInstances,
loading: storeLoading,
actionLoading,
toast,
addOpen,
newName,
creating,
setNewName,
openAddModal,
closeAddModal,
handleCreate,
qrModal,
closeQrModal: () => setQrModal(null),
handleNewQr,
deleteTarget,
deleting,
setDeleteTarget,
closeDeleteModal: () => setDeleteTarget(null),
handleDelete,
handleConnect,
handleDisconnect,
handleShowQr,
refreshInstances: loadInstances,
}
}