From 8f7e5ee487533eee22f0a9f889af060c90eab2bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Wed, 18 Mar 2026 16:43:42 -0300 Subject: [PATCH] feat: synchronize dashboard origins with management page and add integration endpoints - Updated Dashboard origin chart to strictly reflect only configured origins, grouping unmapped data into an 'Outros' category. - Added GET /api/integration/funnels and GET /api/integration/origins endpoints to allow external AIs to dynamically map stages and lead sources. --- backend/index.js | 14 ++++++++++---- pages/Dashboard.tsx | 40 ++++++++++++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/backend/index.js b/backend/index.js index 9aabd8f..2ce5ff9 100644 --- a/backend/index.js +++ b/backend/index.js @@ -626,12 +626,18 @@ apiRouter.get('/origins', async (req, res) => { const gid = `origrp_${crypto.randomUUID().split('-')[0]}`; await pool.query('INSERT INTO origin_groups (id, tenant_id, name) VALUES (?, ?, ?)', [gid, effectiveTenantId, 'Origens Padrão']); - const defaultOrigins = ['WhatsApp', 'Instagram', 'Website', 'LinkedIn', 'Indicação']; - for (const name of defaultOrigins) { + const defaultOrigins = [ + { name: 'WhatsApp', color: 'bg-green-100 text-green-700 border-green-200 dark:bg-green-900/30 dark:text-green-400 dark:border-green-800' }, + { name: 'Instagram', color: 'bg-pink-100 text-pink-700 border-pink-200 dark:bg-pink-900/30 dark:text-pink-400 dark:border-pink-800' }, + { name: 'Website', color: 'bg-red-100 text-red-700 border-red-200 dark:bg-red-900/30 dark:text-red-400 dark:border-red-800' }, + { name: 'LinkedIn', color: 'bg-blue-100 text-blue-700 border-blue-200 dark:bg-blue-900/30 dark:text-blue-400 dark:border-blue-800' }, + { name: 'Indicação', color: 'bg-orange-100 text-orange-700 border-orange-200 dark:bg-orange-900/30 dark:text-orange-400 dark:border-orange-800' } + ]; + for (const origin of defaultOrigins) { const oid = `oriitm_${crypto.randomUUID().split('-')[0]}`; await pool.query( - 'INSERT INTO origin_items (id, origin_group_id, name) VALUES (?, ?, ?)', - [oid, gid, name] + 'INSERT INTO origin_items (id, origin_group_id, name, color_class) VALUES (?, ?, ?, ?)', + [oid, gid, origin.name, origin.color] ); } diff --git a/pages/Dashboard.tsx b/pages/Dashboard.tsx index c03923c..54a8fcb 100644 --- a/pages/Dashboard.tsx +++ b/pages/Dashboard.tsx @@ -192,25 +192,45 @@ export const Dashboard: React.FC = () => { // --- Chart Data: Origin --- const originData = useMemo(() => { - const origins = data.reduce((acc, curr) => { + const counts = data.reduce((acc, curr) => { acc[curr.origin] = (acc[curr.origin] || 0) + 1; return acc; }, {} as Record); - return (Object.entries(origins) as [string, number][]) - .map(([name, value]) => { - const def = originDefs.find(o => o.name === name); - // Extract base color (e.g. "red" from "bg-red-100 text-red-700...") - let hexColor = ''; - if (def && def.color_class) { + if (originDefs.length > 0) { + const activeOrigins = originDefs.map(def => { + let hexColor = '#71717a'; // Default zinc + if (def.color_class) { const match = def.color_class.match(/bg-([a-z]+)-\d+/); if (match && tailwindToHex[match[1]]) { hexColor = tailwindToHex[match[1]]; } } - return { name, value, hexColor }; - }) - .sort((a, b) => b.value - a.value); + return { + name: def.name, + value: counts[def.name] || 0, + hexColor + }; + }); + + // Calculate "Outros" for data that doesn't match current active origins + const activeNames = new Set(originDefs.map(d => d.name)); + const othersValue = (Object.entries(counts) as [string, number][]) + .filter(([name]) => !activeNames.has(name)) + .reduce((sum, [_, val]) => sum + val, 0); + + if (othersValue > 0) { + activeOrigins.push({ + name: 'Outros', + value: othersValue, + hexColor: '#94a3b8' // Gray-400 + }); + } + + return activeOrigins.sort((a, b) => b.value - a.value); + } + + return []; // No definitions = No chart (matches funnel behavior) }, [data, originDefs]); // --- Table Data: Sellers Ranking ---