El CRM solo vivía de webhooks; ahora hay pull Clientify, cron daily-sync incluye CRM, y cada módulo de Agencia tiene sync manual. Co-authored-by: Cursor <cursoragent@cursor.com>
154 lines
4.6 KiB
TypeScript
154 lines
4.6 KiB
TypeScript
'use client';
|
|
|
|
import React, { useMemo, useState } from 'react';
|
|
import { usePathname, useRouter } from 'next/navigation';
|
|
import { Loader2, RefreshCw } from 'lucide-react';
|
|
|
|
type SyncModule =
|
|
| 'metrics'
|
|
| 'crm'
|
|
| 'seguimiento'
|
|
| 'ads'
|
|
| 'social'
|
|
| 'organico'
|
|
| 'activos'
|
|
| 'all';
|
|
|
|
function moduleFromPath(pathname: string): SyncModule | null {
|
|
if (pathname.startsWith('/dashboard/agencia')) return 'all';
|
|
if (pathname.startsWith('/dashboard/kpis')) return 'metrics';
|
|
if (pathname.startsWith('/dashboard/crm')) return 'crm';
|
|
if (pathname.startsWith('/dashboard/seguimiento')) return 'seguimiento';
|
|
if (pathname.startsWith('/dashboard/ad-tracker')) return 'ads';
|
|
if (pathname.startsWith('/dashboard/social')) return 'social';
|
|
if (pathname.startsWith('/dashboard/organico')) return 'organico';
|
|
if (pathname.startsWith('/dashboard/activos')) return 'activos';
|
|
if (pathname.startsWith('/dashboard/estrategia')) return 'metrics';
|
|
if (pathname.startsWith('/dashboard/calendario')) return 'seguimiento';
|
|
return null;
|
|
}
|
|
|
|
const LABELS: Record<SyncModule, string> = {
|
|
metrics: 'Sincronizar métricas',
|
|
crm: 'Sincronizar CRM',
|
|
seguimiento: 'Sincronizar seguimiento',
|
|
ads: 'Sincronizar Ads',
|
|
social: 'Actualizar Social',
|
|
organico: 'Sincronizar orgánico',
|
|
activos: 'Sincronizar Drive',
|
|
all: 'Sincronizar todo',
|
|
};
|
|
|
|
export default function SyncNowButton({
|
|
forceModule,
|
|
className = '',
|
|
}: {
|
|
forceModule?: SyncModule;
|
|
className?: string;
|
|
}) {
|
|
const pathname = usePathname() || '';
|
|
const router = useRouter();
|
|
const module = forceModule || moduleFromPath(pathname);
|
|
const [busy, setBusy] = useState(false);
|
|
const [msg, setMsg] = useState<string | null>(null);
|
|
const [err, setErr] = useState<string | null>(null);
|
|
|
|
const label = useMemo(
|
|
() => (module ? LABELS[module] : 'Sincronizar'),
|
|
[module]
|
|
);
|
|
|
|
if (!module) return null;
|
|
|
|
async function run() {
|
|
setBusy(true);
|
|
setMsg(null);
|
|
setErr(null);
|
|
try {
|
|
if (module === 'social') {
|
|
router.refresh();
|
|
setMsg('Social actualizado');
|
|
setBusy(false);
|
|
return;
|
|
}
|
|
const res = await fetch('/api/admin/sync', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ module, days: 14 }),
|
|
});
|
|
const data = await res.json().catch(() => ({}));
|
|
if (!res.ok) {
|
|
setErr(data.error || 'Sync falló');
|
|
setBusy(false);
|
|
return;
|
|
}
|
|
const parts: string[] = [];
|
|
if (data.crm?.results) {
|
|
const ok = (data.crm.results as { leads?: number; error?: string }[]).filter(
|
|
(r) => !r.error && (r.leads || 0) >= 0
|
|
);
|
|
const bad = (data.crm.results as { error?: string }[]).filter((r) => r.error);
|
|
parts.push(`CRM ${ok.length} ok` + (bad.length ? ` · ${bad.length} sin key` : ''));
|
|
}
|
|
if (data.ads) {
|
|
parts.push(
|
|
data.ads.ok === false
|
|
? `Ads error`
|
|
: `Ads ${data.ads.synced || 0} tenants`
|
|
);
|
|
}
|
|
if (data.organico) {
|
|
parts.push(
|
|
data.organico.ok === false ? 'Orgánico avisó' : 'Orgánico OK'
|
|
);
|
|
}
|
|
if (data.seguimiento) {
|
|
parts.push(
|
|
data.seguimiento.ok === false ? 'Seguimiento avisó' : 'Seguimiento OK'
|
|
);
|
|
}
|
|
if (data.activos) {
|
|
parts.push(
|
|
data.activos.ok === false
|
|
? 'Drive avisó'
|
|
: `Drive ${data.activos.linked ?? data.activos.matched ?? 0}`
|
|
);
|
|
}
|
|
setMsg(parts.join(' · ') || 'Listo');
|
|
router.refresh();
|
|
} catch {
|
|
setErr('Error de red');
|
|
}
|
|
setBusy(false);
|
|
}
|
|
|
|
return (
|
|
<div className={`flex flex-col items-end gap-0.5 ${className}`}>
|
|
<button
|
|
type="button"
|
|
disabled={busy}
|
|
onClick={() => void run()}
|
|
className="inline-flex items-center gap-1.5 text-xs font-semibold px-3 py-1.5 rounded-lg border border-gray-200 bg-white text-gray-800 hover:border-brand-400 hover:text-brand-700 disabled:opacity-50"
|
|
title="Actualiza datos de esta pestaña desde las APIs (agencia)"
|
|
>
|
|
{busy ? (
|
|
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
|
) : (
|
|
<RefreshCw className="w-3.5 h-3.5" />
|
|
)}
|
|
{busy ? 'Sincronizando…' : label}
|
|
</button>
|
|
{msg ? (
|
|
<span className="text-[10px] text-emerald-700 max-w-[220px] text-right">
|
|
{msg}
|
|
</span>
|
|
) : null}
|
|
{err ? (
|
|
<span className="text-[10px] text-red-700 max-w-[220px] text-right">
|
|
{err}
|
|
</span>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|