176 lines
5.8 KiB
TypeScript
176 lines
5.8 KiB
TypeScript
/**
|
||
* 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 // 1–200
|
||
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,
|
||
}
|
||
}
|