feat(agencia): kpis y cartera arriba en portafolio y subpagina panel de control conexiones
This commit is contained in:
43
src/app/dashboard/agencia/conexiones/page.tsx
Normal file
43
src/app/dashboard/agencia/conexiones/page.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
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 ClientManagement from '@/components/ClientManagement';
|
||||||
import AgencyPortfolioBoard from '@/components/AgencyPortfolioBoard';
|
|
||||||
import { createClient } from '@/lib/supabase/server';
|
import { createClient } from '@/lib/supabase/server';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
import { PageHeader } from '@/components/ui';
|
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/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/ad_daily?select=spend`, { headers, cache: 'no-store' }),
|
||||||
fetch(`${supabaseUrl}/rest/v1/leads?select=id`, { 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();
|
if (tenantsRes.ok) tenants = await tenantsRes.json();
|
||||||
@@ -72,58 +72,66 @@ export default async function AgencyPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
<PageHeader
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||||
eyebrow="Agencia"
|
<PageHeader
|
||||||
title="Portafolio"
|
eyebrow="Agencia"
|
||||||
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 />
|
{/* 1. Parte más alta: Global Agency KPI Cards */}
|
||||||
|
|
||||||
{/* Global Agency KPI Cards */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
<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">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<span className="text-xs text-gray-600 font-semibold">Clientes / Tenants</span>
|
<span className="text-xs text-gray-600 font-semibold">Clients / Tenants</span>
|
||||||
<Building2 className="w-4 h-4 text-brand-400" />
|
<Building2 className="w-4 h-4 text-brand-500" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-2xl font-extrabold text-gray-900">{tenants.length}</p>
|
<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>
|
||||||
|
|
||||||
<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">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<span className="text-xs text-gray-600 font-semibold">Inversión Gestionada</span>
|
<span className="text-xs text-gray-600 font-semibold">Inversión gestionada</span>
|
||||||
<DollarSign className="w-4 h-4 text-blue-400" />
|
<DollarSign className="w-4 h-4 text-blue-500" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-2xl font-extrabold text-gray-900">
|
<p className="text-2xl font-extrabold text-gray-900">
|
||||||
{totalSpend.toLocaleString('es-ES', { minimumFractionDigits: 2 })} €
|
{totalSpend.toLocaleString('es-ES', { minimumFractionDigits: 2 })} €
|
||||||
</p>
|
</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>
|
||||||
|
|
||||||
<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">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<span className="text-xs text-gray-600 font-semibold">Leads Captados</span>
|
<span className="text-xs text-gray-600 font-semibold">Leads captados</span>
|
||||||
<Users className="w-4 h-4 text-purple-400" />
|
<Users className="w-4 h-4 text-purple-500" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-2xl font-extrabold text-gray-900">{totalLeads}</p>
|
<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>
|
||||||
|
|
||||||
<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">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<span className="text-xs text-gray-600 font-semibold">ROAS Blended Agencia</span>
|
<span className="text-xs text-gray-600 font-semibold">ROAS blended agencia</span>
|
||||||
<TrendingUp className="w-4 h-4 text-emerald-400" />
|
<TrendingUp className="w-4 h-4 text-emerald-500" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-2xl font-extrabold text-gray-900">{blendedRoas}x</p>
|
<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
|
{totalRevenue.toLocaleString('es-ES', { minimumFractionDigits: 2 })} € generados
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Interactive Tenants Management with Onboarding Form */}
|
{/* 2. Debajo: Módulo Cartera de Clientes Activos */}
|
||||||
<ClientManagement initialTenants={tenants} />
|
<ClientManagement initialTenants={tenants} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
Sparkles,
|
Sparkles,
|
||||||
Images,
|
Images,
|
||||||
History,
|
History,
|
||||||
|
Plug,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import SidebarTenantSwitcher from '@/components/SidebarTenantSwitcher';
|
import SidebarTenantSwitcher from '@/components/SidebarTenantSwitcher';
|
||||||
import type { DashboardModuleId } from '@/lib/tenant-modules';
|
import type { DashboardModuleId } from '@/lib/tenant-modules';
|
||||||
@@ -137,7 +138,7 @@ function shouldShowDateHeader(pathname: string | null): boolean {
|
|||||||
|
|
||||||
function isActive(pathname: string, href: string): boolean {
|
function isActive(pathname: string, href: string): boolean {
|
||||||
if (pathname === href) return true;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,6 +289,15 @@ export default function DashboardShell({
|
|||||||
}}
|
}}
|
||||||
onNavigate={close}
|
onNavigate={close}
|
||||||
/>
|
/>
|
||||||
|
<NavLink
|
||||||
|
item={{
|
||||||
|
href: '/dashboard/agencia/conexiones',
|
||||||
|
label: 'Panel de control · clientes y conexiones',
|
||||||
|
icon: Plug,
|
||||||
|
iconClass: 'text-cyan-500',
|
||||||
|
}}
|
||||||
|
onNavigate={close}
|
||||||
|
/>
|
||||||
<NavLink
|
<NavLink
|
||||||
item={{
|
item={{
|
||||||
href: '/dashboard/historial',
|
href: '/dashboard/historial',
|
||||||
|
|||||||
Reference in New Issue
Block a user