Files
clube67_newwhats.local/clube67/newwhats.local/frontend/hooks/useWarmerLogic.ts
T
VPS 4 Deploy Agent 2f8c04a0a7
continuous-integration/webhook Falha no deploy de clube67_newwhats.local (VPS 4)
chore(ops): restore all source files in newwhats.clube67.com
2026-05-18 03:28:29 +02:00

176 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* useWarmerLogic — toda a lógica da página /warmer (WhatsApp Warmer).
*
* Gerencia configurações de aquecimento por instância, armazenadas em
* localStorage. Quando o backend expor endpoints de warmer, este hook
* será o único arquivo a alterar.
*/
import { useState, useEffect, useCallback, useMemo } from 'react'
import { useInstanceStore, type Instance } from '../store/instanceStore'
// ─── Tipos ────────────────────────────────────────────────────────────────────
export interface WarmerConfig {
enabled: boolean
messagesPerDay: number // 1200
minIntervalSec: number // segundos entre mensagens (mínimo)
maxIntervalSec: number // segundos entre mensagens (máximo)
templates: string[] // mensagens a enviar ciclicamente
startedAt: string | null // ISO string quando foi ativado
}
export interface WarmerStats {
sentToday: number
lastSentAt: string | null
}
const DEFAULT_CONFIG: WarmerConfig = {
enabled: false,
messagesPerDay: 20,
minIntervalSec: 120,
maxIntervalSec: 600,
templates: [
'Oi, tudo bem?',
'Olá!',
'Boa tarde!',
'Tudo certo por aí?',
],
startedAt: null,
}
const STORAGE_KEY = 'newwhats:warmer'
function loadStorage(): Record<string, WarmerConfig> {
try {
const raw = localStorage.getItem(STORAGE_KEY)
return raw ? JSON.parse(raw) : {}
} catch {
return {}
}
}
function saveStorage(data: Record<string, WarmerConfig>) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(data))
} catch {}
}
// ─── Hook ─────────────────────────────────────────────────────────────────────
export function useWarmerLogic() {
const instances = useInstanceStore((s) => s.instances)
const loadInst = useInstanceStore((s) => s.loadInstances)
const [configs, setConfigs] = useState<Record<string, WarmerConfig>>({})
const [editing, setEditing] = useState<string | null>(null) // instanceId em edição
const [editDraft, setEditDraft] = useState<WarmerConfig>(DEFAULT_CONFIG)
const [toast, setToast] = useState<{ msg: string; ok: boolean } | null>(null)
// ── Bootstrap ──────────────────────────────────────────────────────────────
useEffect(() => {
loadInst()
setConfigs(loadStorage())
}, [])
// Sincroniza configs → localStorage sempre que o estado mudar.
// Evita chamar saveStorage() dentro de updaters de setState (efeito
// colateral em função pura — StrictMode executa updaters 2× em dev).
useEffect(() => {
saveStorage(configs)
}, [configs])
// ── Helpers ────────────────────────────────────────────────────────────────
const showToast = useCallback((msg: string, ok = true) => {
setToast({ msg, ok })
setTimeout(() => setToast(null), 3000)
}, [])
const configFor = useCallback(
(id: string): WarmerConfig => configs[id] ?? { ...DEFAULT_CONFIG },
[configs]
)
// ── Instâncias enriquecidas ────────────────────────────────────────────────
const enriched = useMemo(() =>
instances.map((inst) => ({
...inst,
warmer: configFor(inst.id),
})),
[instances, configFor]
)
const activeCount = useMemo(
() => enriched.filter((i) => i.warmer.enabled).length,
[enriched]
)
// ── Toggle rápido ──────────────────────────────────────────────────────────
const toggleEnabled = useCallback((instanceId: string) => {
setConfigs((prev) => {
const current = prev[instanceId] ?? { ...DEFAULT_CONFIG }
const next: WarmerConfig = {
...current,
enabled: !current.enabled,
startedAt: !current.enabled ? new Date().toISOString() : null,
}
return { ...prev, [instanceId]: next }
})
}, [])
// ── Edição ────────────────────────────────────────────────────────────────
const openEdit = useCallback((instanceId: string) => {
setEditDraft({ ...configFor(instanceId) })
setEditing(instanceId)
}, [configFor])
const closeEdit = useCallback(() => setEditing(null), [])
const saveEdit = useCallback(() => {
if (!editing) return
setConfigs((prev) => ({ ...prev, [editing]: { ...editDraft } }))
setEditing(null)
showToast('Configuração salva')
}, [editing, editDraft, showToast])
const addTemplate = useCallback((text: string) => {
if (!text.trim()) return
setEditDraft((d) => ({ ...d, templates: [...d.templates, text.trim()] }))
}, [])
const removeTemplate = useCallback((idx: number) => {
setEditDraft((d) => ({ ...d, templates: d.templates.filter((_, i) => i !== idx) }))
}, [])
const patchDraft = useCallback(<K extends keyof WarmerConfig>(key: K, value: WarmerConfig[K]) => {
setEditDraft((d) => ({ ...d, [key]: value }))
}, [])
return {
// Dados
instances: enriched,
activeCount,
totalCount: instances.length,
// Toast
toast,
// Ações de card
toggleEnabled,
// Modal de edição
editing,
editDraft,
openEdit,
closeEdit,
saveEdit,
addTemplate,
removeTemplate,
patchDraft,
}
}