From 6bfd8a117f221627491a274d14b233751f1477aa Mon Sep 17 00:00:00 2001 From: Marcos Fargas Date: Sun, 4 Oct 2026 21:16:35 +0200 Subject: [PATCH] feat: add blue parrot static html kpis dashboard connected to windsor data --- .../kpis/kpis/Blue Parrot-kpis2.html | 535 +++++++++++++++++ src/app/dashboard/kpis/kpis/route.ts | 107 ++++ src/app/dashboard/kpis/kpis/template.ts | 536 ++++++++++++++++++ 3 files changed, 1178 insertions(+) create mode 100644 src/app/dashboard/kpis/kpis/Blue Parrot-kpis2.html create mode 100644 src/app/dashboard/kpis/kpis/route.ts create mode 100644 src/app/dashboard/kpis/kpis/template.ts diff --git a/src/app/dashboard/kpis/kpis/Blue Parrot-kpis2.html b/src/app/dashboard/kpis/kpis/Blue Parrot-kpis2.html new file mode 100644 index 0000000..76492d2 --- /dev/null +++ b/src/app/dashboard/kpis/kpis/Blue Parrot-kpis2.html @@ -0,0 +1,535 @@ + +Blue Parrot · Panel de creatividades + + + +
+
+
+
4Ventures · Panel de cliente
+

Blue Parrot School

+
+
Paquete PREMIUM: 16 publicaciones y 20 stories al mes, más 2 reels por semana.
+
+ + + + +
+
Cargando el resumen…
Aquí aparecen los indicadores del mes con su tendencia, lo que mejora y empeora, los avisos y las próximas publicaciones.
+
+ + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/src/app/dashboard/kpis/kpis/route.ts b/src/app/dashboard/kpis/kpis/route.ts new file mode 100644 index 0000000..3bbd8d1 --- /dev/null +++ b/src/app/dashboard/kpis/kpis/route.ts @@ -0,0 +1,107 @@ +import { NextResponse } from 'next/server'; +import { htmlTemplate } from './template'; +import fs from 'fs'; +import path from 'path'; +import { createClient } from '@/lib/supabase/server'; + +export async function GET() { + const supabase = await createClient(); + const tenant_id = '8bec977a-6032-4759-aa7f-b5042081f450'; + + const { data: adDaily } = await supabase + .from('ad_daily') + .select('*') + .eq('tenant_id', tenant_id) + .order('day', { ascending: true }); + + const { data: orgDaily } = await supabase + .from('organic_daily') + .select('*') + .eq('tenant_id', tenant_id) + .order('day', { ascending: true }); + + const { data: creativePipeline } = await supabase + .from('creative_pipeline') + .select('*') + .order('created_at', { ascending: true }); + + // Map to K format + const startDay = new Date(); + startDay.setFullYear(startDay.getFullYear() - 1); + const startStr = startDay.toISOString().split('T')[0]; + const endStr = new Date().toISOString().split('T')[0]; + + const daysCount = Math.round((new Date(endStr).getTime() - new Date(startStr).getTime()) / 864e5) + 1; + const K: any = { + start: startStr, + end: endStr, + n: daysCount, + src: { + meta: { m: { spend: Array(daysCount).fill(null), imp: Array(daysCount).fill(null), clk: Array(daysCount).fill(null), res: Array(daysCount).fill(null) }, from: startStr, to: endStr }, + gads: { m: { spend: Array(daysCount).fill(null), imp: Array(daysCount).fill(null), clk: Array(daysCount).fill(null), res: Array(daysCount).fill(null) }, from: startStr, to: endStr }, + gsc: { m: { clk: Array(daysCount).fill(null), imp: Array(daysCount).fill(null), posw: Array(daysCount).fill(null) }, from: startStr, to: endStr }, + ga4: { m: { ses: Array(daysCount).fill(null), usr: Array(daysCount).fill(null), new: Array(daysCount).fill(null), key: Array(daysCount).fill(null) }, from: startStr, to: endStr }, + gbp: { m: { imp: Array(daysCount).fill(null), call: Array(daysCount).fill(null), web: Array(daysCount).fill(null), dir: Array(daysCount).fill(null) }, from: startStr, to: endStr }, + ig: { m: { views: Array(daysCount).fill(null), inter: Array(daysCount).fill(null), likes: Array(daysCount).fill(null), com: Array(daysCount).fill(null), saves: Array(daysCount).fill(null), shares: Array(daysCount).fill(null), taps: Array(daysCount).fill(null) }, from: startStr, to: endStr } + } + }; + + const getIdx = (d: string) => Math.round((new Date(d).getTime() - new Date(startStr).getTime()) / 864e5); + + if (adDaily) { + for (const row of adDaily) { + if (row.day < startStr || row.day > endStr) continue; + const idx = getIdx(row.day); + const plat = row.platform === 'meta' ? 'meta' : row.platform === 'google' ? 'gads' : null; + if (!plat) continue; + K.src[plat].m.spend[idx] = row.spend; + K.src[plat].m.imp[idx] = row.impressions; + K.src[plat].m.clk[idx] = row.clicks; + K.src[plat].m.res[idx] = row.leads || 0; + } + } + + if (orgDaily) { + for (const row of orgDaily) { + if (row.day < startStr || row.day > endStr) continue; + const idx = getIdx(row.day); + K.src.ga4.m.ses[idx] = row.ga4_sessions; + K.src.ga4.m.usr[idx] = row.ga4_users; + K.src.ga4.m.new[idx] = row.ga4_new_users; + K.src.ga4.m.key[idx] = row.ga4_key_events; + K.src.gsc.m.clk[idx] = row.gsc_clicks; + K.src.gsc.m.imp[idx] = row.gsc_impressions; + K.src.gsc.m.posw[idx] = row.gsc_position_w; + K.src.gbp.m.imp[idx] = row.gbp_impressions; + K.src.gbp.m.call[idx] = row.gbp_calls; + K.src.gbp.m.web[idx] = row.gbp_website_clicks; + K.src.gbp.m.dir[idx] = row.gbp_directions; + } + } + + const kData = { seguidores: 0, posts: [], actualizado: endStr, desde: startStr, hasta: endStr }; + + // Inject data + const dataPayload = { K, KC: {}, kData, piezas: creativePipeline || [] }; + + let htmlStr = htmlTemplate.replace( + /\/\* ---------- datos ---------- \*\/[\s\S]*?\}\)\(\);/g, + `/* ---------- datos ---------- */ + window.__INJECTED_DATA__ = ${JSON.stringify(dataPayload).replace(/ +Blue Parrot · Panel de creatividades + + + +
+
+
+
4Ventures · Panel de cliente
+

Blue Parrot School

+
+
Paquete PREMIUM: 16 publicaciones y 20 stories al mes, más 2 reels por semana.
+
+ + + + +
+
Cargando el resumen…
Aquí aparecen los indicadores del mes con su tendencia, lo que mejora y empeora, los avisos y las próximas publicaciones.
+
+ + + + + + + + +
+ + + + +`;