feat(agencia): kpis y cartera arriba en portafolio y subpagina panel de control conexiones

This commit is contained in:
Marcos Fargas
2026-10-17 20:53:38 +02:00
parent e35e7bac26
commit 15c8b56b25
3 changed files with 90 additions and 29 deletions

View File

@@ -0,0 +1,43 @@
import React from 'react';
import Link from 'next/link';
import { ArrowLeft } from 'lucide-react';
import AgencyPortfolioBoard from '@/components/AgencyPortfolioBoard';
import { createClient } from '@/lib/supabase/server';
import { redirect } from 'next/navigation';
import { PageHeader } from '@/components/ui';
import { isAgencyRole, userRole } from '@/lib/agency';
export const revalidate = 0;
export default async function AgencyConexionesPage() {
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) redirect('/login');
if (!isAgencyRole(userRole(user))) {
redirect('/dashboard/kpis');
}
return (
<div className="space-y-6">
<div>
<Link
href="/dashboard/agencia"
className="inline-flex items-center gap-1.5 text-xs text-mist-500 hover:text-ink font-medium mb-3 transition-colors"
>
<ArrowLeft className="w-3.5 h-3.5" />
Volver a Portafolio Agencia
</Link>
<PageHeader
eyebrow="Agencia"
title="Panel de control · clientes y conexiones"
description="Gestión integral de conexiones por cliente (Meta Ads, Google Drive, Superhuman, Clientify), alertas operativas y salud de integraciones."
/>
</div>
<AgencyPortfolioBoard />
</div>
);
}

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { Building2, DollarSign, Users, TrendingUp } from 'lucide-react';
import Link from 'next/link';
import { Building2, DollarSign, Users, TrendingUp, Plug, ArrowRight } from 'lucide-react';
import ClientManagement from '@/components/ClientManagement';
import AgencyPortfolioBoard from '@/components/AgencyPortfolioBoard';
import { createClient } from '@/lib/supabase/server';
import { redirect } from 'next/navigation';
import { PageHeader } from '@/components/ui';
@@ -47,7 +47,7 @@ export default async function AgencyPage() {
fetch(`${supabaseUrl}/rest/v1/tenants?select=*&order=created_at.asc`, { headers, cache: 'no-store' }),
fetch(`${supabaseUrl}/rest/v1/ad_daily?select=spend`, { headers, cache: 'no-store' }),
fetch(`${supabaseUrl}/rest/v1/leads?select=id`, { headers, cache: 'no-store' }),
fetch(`${supabaseUrl}/rest/v1/deals?select=amount&stage=eq.won`, { headers, cache: 'no-store' })
fetch(`${supabaseUrl}/rest/v1/deals?select=amount&stage=eq.won`, { headers, cache: 'no-store' }),
]);
if (tenantsRes.ok) tenants = await tenantsRes.json();
@@ -72,58 +72,66 @@ export default async function AgencyPage() {
return (
<div className="space-y-8">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<PageHeader
eyebrow="Agencia"
title="Portafolio"
description="Clientes, conexiones (Meta, Google, Drive, Clientify…) y seguimiento operativo."
title="Portafolio Agencia"
description="Visión general de clientes activos, métricas globales de inversión y rendimiento de la agencia."
/>
<Link
href="/dashboard/agencia/conexiones"
className="inline-flex items-center gap-2 self-start sm:self-auto px-4 py-2.5 rounded-xl border border-gray-200 bg-white text-gray-800 font-semibold text-xs shadow-xs hover:border-brand-500 hover:text-brand-600 transition-colors shrink-0"
>
<Plug className="w-4 h-4 text-brand-500" />
<span>Panel de control · clientes y conexiones</span>
<ArrowRight className="w-3.5 h-3.5 text-gray-400" />
</Link>
</div>
<AgencyPortfolioBoard />
{/* Global Agency KPI Cards */}
{/* 1. Parte más alta: Global Agency KPI Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-white border border-gray-200 rounded-xl p-5">
<div className="bg-white border border-gray-200 rounded-xl p-5 shadow-xs">
<div className="flex items-center justify-between mb-3">
<span className="text-xs text-gray-600 font-semibold">Clientes / Tenants</span>
<Building2 className="w-4 h-4 text-brand-400" />
<span className="text-xs text-gray-600 font-semibold">Clients / Tenants</span>
<Building2 className="w-4 h-4 text-brand-500" />
</div>
<p className="text-2xl font-extrabold text-gray-900">{tenants.length}</p>
<p className="text-[11px] text-emerald-400 mt-1">100% aislados vía RLS</p>
<p className="text-[11px] text-emerald-600 mt-1">100% aislados vía RLS</p>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-5">
<div className="bg-white border border-gray-200 rounded-xl p-5 shadow-xs">
<div className="flex items-center justify-between mb-3">
<span className="text-xs text-gray-600 font-semibold">Inversión Gestionada</span>
<DollarSign className="w-4 h-4 text-blue-400" />
<span className="text-xs text-gray-600 font-semibold">Inversión gestionada</span>
<DollarSign className="w-4 h-4 text-blue-500" />
</div>
<p className="text-2xl font-extrabold text-gray-900">
{totalSpend.toLocaleString('es-ES', { minimumFractionDigits: 2 })} €
</p>
<p className="text-[11px] text-gray-400 mt-1">Vía Windsor.ai pipeline</p>
<p className="text-[11px] text-gray-500 mt-1">Vía Windsor.ai pipeline</p>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-5">
<div className="bg-white border border-gray-200 rounded-xl p-5 shadow-xs">
<div className="flex items-center justify-between mb-3">
<span className="text-xs text-gray-600 font-semibold">Leads Captados</span>
<Users className="w-4 h-4 text-purple-400" />
<span className="text-xs text-gray-600 font-semibold">Leads captados</span>
<Users className="w-4 h-4 text-purple-500" />
</div>
<p className="text-2xl font-extrabold text-gray-900">{totalLeads}</p>
<p className="text-[11px] text-gray-400 mt-1">{totalWonDeals} cerrados ganados</p>
<p className="text-[11px] text-gray-500 mt-1">{totalWonDeals} cerrados ganados</p>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-5">
<div className="bg-white border border-gray-200 rounded-xl p-5 shadow-xs">
<div className="flex items-center justify-between mb-3">
<span className="text-xs text-gray-600 font-semibold">ROAS Blended Agencia</span>
<TrendingUp className="w-4 h-4 text-emerald-400" />
<span className="text-xs text-gray-600 font-semibold">ROAS blended agencia</span>
<TrendingUp className="w-4 h-4 text-emerald-500" />
</div>
<p className="text-2xl font-extrabold text-gray-900">{blendedRoas}x</p>
<p className="text-[11px] text-emerald-400 mt-1">
<p className="text-[11px] text-emerald-600 mt-1">
{totalRevenue.toLocaleString('es-ES', { minimumFractionDigits: 2 })} € generados
</p>
</div>
</div>
{/* Interactive Tenants Management with Onboarding Form */}
{/* 2. Debajo: Módulo Cartera de Clientes Activos */}
<ClientManagement initialTenants={tenants} />
</div>
);

View File

@@ -23,6 +23,7 @@ import {
Sparkles,
Images,
History,
Plug,
} from 'lucide-react';
import SidebarTenantSwitcher from '@/components/SidebarTenantSwitcher';
import type { DashboardModuleId } from '@/lib/tenant-modules';
@@ -137,7 +138,7 @@ function shouldShowDateHeader(pathname: string | null): boolean {
function isActive(pathname: string, href: string): boolean {
if (pathname === href) return true;
if (href !== '/dashboard' && pathname.startsWith(href + '/')) return true;
if (href !== '/dashboard' && href !== '/dashboard/agencia' && pathname.startsWith(href + '/')) return true;
return false;
}
@@ -288,6 +289,15 @@ export default function DashboardShell({
}}
onNavigate={close}
/>
<NavLink
item={{
href: '/dashboard/agencia/conexiones',
label: 'Panel de control · clientes y conexiones',
icon: Plug,
iconClass: 'text-cyan-500',
}}
onNavigate={close}
/>
<NavLink
item={{
href: '/dashboard/historial',