215 lines
7.2 KiB
TypeScript
215 lines
7.2 KiB
TypeScript
/**
|
|
* useSessionsLogic — toda a lógica da página /sessions.
|
|
* A página visual (SessionsPage) é uma casca que só renderiza.
|
|
*/
|
|
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)
|
|
|
|
// ── UI state local ────────────────────────────────────────────────────────
|
|
|
|
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)
|
|
|
|
// ── Bootstrap ─────────────────────────────────────────────────────────────
|
|
|
|
useEffect(() => {
|
|
const token = localStorage.getItem('token')
|
|
if (token) socketService.connect(token)
|
|
initListeners()
|
|
loadInstances()
|
|
}, [])
|
|
|
|
// ── Socket: QR + status (sincroniza modal aberto) ─────────────────────────
|
|
|
|
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)
|
|
}
|
|
}, [])
|
|
|
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
|
|
const showToast = (msg: string, ok = true) => {
|
|
setToast({ msg, ok })
|
|
setTimeout(() => setToast(null), 3000)
|
|
}
|
|
|
|
const openAddModal = useCallback(() => setAddOpen(true), [])
|
|
const closeAddModal = useCallback(() => { setAddOpen(false); setNewName('') }, [])
|
|
|
|
// ── Actions ───────────────────────────────────────────────────────────────
|
|
|
|
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 })
|
|
// QR chegará via Socket.IO — modal já está aberto aguardando
|
|
}, [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 {
|
|
// Estado
|
|
instances: storeInstances,
|
|
loading: storeLoading,
|
|
actionLoading,
|
|
toast,
|
|
|
|
// Add modal
|
|
addOpen,
|
|
newName,
|
|
creating,
|
|
setNewName,
|
|
openAddModal,
|
|
closeAddModal,
|
|
handleCreate,
|
|
|
|
// QR modal
|
|
qrModal,
|
|
closeQrModal: () => setQrModal(null),
|
|
handleNewQr,
|
|
|
|
// Delete modal
|
|
deleteTarget,
|
|
deleting,
|
|
setDeleteTarget,
|
|
closeDeleteModal: () => setDeleteTarget(null),
|
|
handleDelete,
|
|
|
|
// Card actions
|
|
handleConnect,
|
|
handleDisconnect,
|
|
handleShowQr,
|
|
|
|
// Reload
|
|
refreshInstances: loadInstances,
|
|
}
|
|
}
|