From 96cfb3d125725d069f034c27e6dc22d48f754eed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Tue, 17 Mar 2026 12:45:15 -0300 Subject: [PATCH] refactor: remove mock data and finalize n8n data schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Removed all hardcoded MOCK_ATTENDANCES, USERS, and TENANTS generators from constants.ts since the system is now production-ready. - Renamed 'summary' to 'title' in the database and across all frontend components for clarity. - Added 'full_summary' to the attendances schema to explicitly store the large, detailed AI analysis texts from n8n. - Updated the 'Resumo da Interação' UI to render the 'full_summary' without adding any artificial filler text. - Localized all dates and times across the dashboard to Brazilian formatting (pt-BR). --- backend/index.js | 37 +++++++-- components/Layout.tsx | 6 +- constants.ts | 161 +------------------------------------ pages/ApiKeys.tsx | 2 +- pages/AttendanceDetail.tsx | 18 +++-- pages/UserDetail.tsx | 12 ++- types.ts | 5 +- 7 files changed, 55 insertions(+), 186 deletions(-) diff --git a/backend/index.js b/backend/index.js index 2c9b5e0..631ba3a 100644 --- a/backend/index.js +++ b/backend/index.js @@ -796,7 +796,7 @@ apiRouter.get('/search', async (req, res) => { } // 4. Search Attendances - let attendancesQ = 'SELECT a.id, a.summary, a.created_at, u.name as user_name FROM attendances a JOIN users u ON a.user_id = u.id WHERE a.summary LIKE ?'; + let attendancesQ = 'SELECT a.id, a.title, a.created_at, u.name as user_name FROM attendances a JOIN users u ON a.user_id = u.id WHERE a.title LIKE ?'; const attendancesParams = [queryStr]; if (req.user.role === 'super_admin') { @@ -959,7 +959,8 @@ apiRouter.post('/integration/attendances', requireRole(['admin']), async (req, r user_id, origin, funnel_stage, - summary, + title, + full_summary, score, first_response_time_min, handling_time_min, @@ -970,8 +971,8 @@ apiRouter.post('/integration/attendances', requireRole(['admin']), async (req, r improvement_points } = req.body; - if (!user_id || !origin || !funnel_stage || !summary) { - return res.status(400).json({ error: 'Campos obrigatórios ausentes: user_id, origin, funnel_stage, summary' }); + if (!user_id || !origin || !funnel_stage || !title) { + return res.status(400).json({ error: 'Campos obrigatórios ausentes: user_id, origin, funnel_stage, title' }); } try { @@ -982,16 +983,17 @@ apiRouter.post('/integration/attendances', requireRole(['admin']), async (req, r const attId = `att_${crypto.randomUUID().split('-')[0]}`; await pool.query( `INSERT INTO attendances ( - id, tenant_id, user_id, summary, score, + id, tenant_id, user_id, title, full_summary, score, first_response_time_min, handling_time_min, funnel_stage, origin, product_requested, product_sold, converted, attention_points, improvement_points - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ attId, req.user.tenant_id, user_id, - summary, + title, + full_summary || null, score || 0, first_response_time_min || 0, handling_time_min || 0, @@ -1300,6 +1302,27 @@ const provisionSuperAdmin = async (retries = 10, delay = 10000) => { console.log('Schema update note (funnel_stage):', err.message); } + // Add full_summary column for detailed AI analysis + try { + await connection.query("ALTER TABLE attendances ADD COLUMN full_summary TEXT DEFAULT NULL"); + } catch (err) { + if (err.code !== 'ER_DUP_FIELDNAME') console.log('Schema update note (full_summary):', err.message); + } + + // Rename summary to title + try { + await connection.query("ALTER TABLE attendances RENAME COLUMN summary TO title"); + } catch (err) { + if (err.code !== 'ER_BAD_FIELD_ERROR' && err.code !== 'ER_DUP_FIELDNAME') { + // If RENAME COLUMN fails (older mysql), try CHANGE + try { + await connection.query("ALTER TABLE attendances CHANGE COLUMN summary title TEXT"); + } catch (e) { + console.log('Schema update note (summary to title):', e.message); + } + } + } + // Create funnels table await connection.query(` CREATE TABLE IF NOT EXISTS funnels ( diff --git a/components/Layout.tsx b/components/Layout.tsx index 54f7460..d13cc01 100644 --- a/components/Layout.tsx +++ b/components/Layout.tsx @@ -443,10 +443,10 @@ export const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => KPI
-
{a.summary}
+
{a.title}
{a.user_name} - {new Date(a.created_at).toLocaleDateString()} + {new Date(a.created_at).toLocaleDateString('pt-BR')}
@@ -541,7 +541,7 @@ export const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {n.type === 'success' ? 'SUCESSO' : n.type === 'warning' ? 'AVISO' : n.type === 'error' ? 'ERRO' : 'INFO'} - {new Date(n.created_at).toLocaleDateString()} + {new Date(n.created_at).toLocaleDateString('pt-BR')}
{n.title}
diff --git a/constants.ts b/constants.ts index fa96868..401eb99 100644 --- a/constants.ts +++ b/constants.ts @@ -1,164 +1,5 @@ -import { Attendance, FunnelStage, Tenant, User } from './types'; - -export const TENANTS: Tenant[] = [ - { - id: 'tenant_123', - name: 'Fasto Corp', - slug: 'fasto', - admin_email: 'admin@fasto.com', - status: 'active', - user_count: 12, - attendance_count: 1450, - created_at: '2023-01-15T10:00:00Z' - }, - { - id: 'tenant_456', - name: 'Acme Inc', - slug: 'acme-inc', - admin_email: 'contact@acme.com', - status: 'trial', - user_count: 5, - attendance_count: 320, - created_at: '2023-06-20T14:30:00Z' - }, - { - id: 'tenant_789', - name: 'Globex Utils', - slug: 'globex', - admin_email: 'sysadmin@globex.com', - status: 'inactive', - user_count: 2, - attendance_count: 45, - created_at: '2022-11-05T09:15:00Z' - }, - { - id: 'tenant_101', - name: 'Soylent Green', - slug: 'soylent', - admin_email: 'admin@soylent.com', - status: 'active', - user_count: 25, - attendance_count: 5600, - created_at: '2023-02-10T11:20:00Z' - }, -]; - -export const USERS: User[] = [ - { - id: 'sa1', - tenant_id: 'system', - name: 'Super Administrator', - email: 'root@system.com', - role: 'super_admin', - team_id: '', - avatar_url: 'https://ui-avatars.com/api/?name=Super+Admin&background=0f172a&color=fff', - bio: 'Administrador do Sistema Global', - status: 'active' - }, - { - id: 'u1', - tenant_id: 'tenant_123', - name: 'Lidya Chan', - email: 'lidya@fasto.com', - role: 'manager', - team_id: 'sales_1', - avatar_url: 'https://picsum.photos/id/1011/200/200', - bio: 'Gerente de Vendas com mais de 10 anos de experiência em SaaS. Apaixonada por construção de equipes e crescimento de receita.', - status: 'active' - }, - { - id: 'u2', - tenant_id: 'tenant_123', - name: 'Alex Noer', - email: 'alex@fasto.com', - role: 'agent', - team_id: 'sales_1', - avatar_url: 'https://picsum.photos/id/1012/200/200', - bio: 'Melhor desempenho no Q3. Focado em clientes corporativos e relacionamentos de longo prazo.', - status: 'active' - }, - { - id: 'u3', - tenant_id: 'tenant_123', - name: 'Angela Moss', - email: 'angela@fasto.com', - role: 'agent', - team_id: 'sales_1', - avatar_url: 'https://picsum.photos/id/1013/200/200', - status: 'inactive' - }, - { - id: 'u4', - tenant_id: 'tenant_123', - name: 'Brian Samuel', - email: 'brian@fasto.com', - role: 'agent', - team_id: 'sales_2', - avatar_url: 'https://picsum.photos/id/1014/200/200', - status: 'active' - }, - { - id: 'u5', - tenant_id: 'tenant_123', - name: 'Benny Chagur', - email: 'benny@fasto.com', - role: 'agent', - team_id: 'sales_2', - avatar_url: 'https://picsum.photos/id/1025/200/200', - status: 'active' - }, -]; - -const generateMockAttendances = (count: number): Attendance[] => { - const origins = ['WhatsApp', 'Instagram', 'Website', 'LinkedIn', 'Indicação'] as const; - const stages = Object.values(FunnelStage); - const products = ['Plano Premium', 'Plano Básico', 'Suíte Enterprise', 'Consultoria']; - - return Array.from({ length: count }).map((_, i) => { - const user = USERS.slice(1)[Math.floor(Math.random() * (USERS.length - 1))]; // Skip super admin - const rand = Math.random(); - // Weighted stages for realism - let stage = FunnelStage.IDENTIFICATION; - let isConverted = false; - - if (rand > 0.85) { - stage = FunnelStage.WON; - isConverted = true; - } else if (rand > 0.7) { - stage = FunnelStage.LOST; - } else if (rand > 0.5) { - stage = FunnelStage.NEGOTIATION; - } else if (rand > 0.2) { - stage = FunnelStage.IDENTIFICATION; - } else { - stage = FunnelStage.NO_CONTACT; - } - - // Force won/lost logic consistency - if (stage === FunnelStage.WON) isConverted = true; - - return { - id: `att_${i}`, - tenant_id: 'tenant_123', - user_id: user.id, - created_at: new Date(Date.now() - Math.floor(Math.random() * 60 * 24 * 60 * 60 * 1000)).toISOString(), - summary: "Cliente perguntou sobre detalhes do produto e níveis de preços.", - attention_points: Math.random() > 0.8 ? ["Resposta demorada", "Verificar tom de voz"] : [], - improvement_points: ["Sugerir plano anual", "Fazer follow-up mais cedo"], - score: Math.floor(Math.random() * (100 - 50) + 50), - first_response_time_min: Math.floor(Math.random() * 120), - handling_time_min: Math.floor(Math.random() * 45), - funnel_stage: stage, - origin: origins[Math.floor(Math.random() * origins.length)], - product_requested: products[Math.floor(Math.random() * products.length)], - product_sold: isConverted ? products[Math.floor(Math.random() * products.length)] : undefined, - converted: isConverted, - }; - }); -}; - -export const MOCK_ATTENDANCES = generateMockAttendances(300); +import { FunnelStage } from './types'; // Visual Constants export const COLORS = { diff --git a/pages/ApiKeys.tsx b/pages/ApiKeys.tsx index 711f30a..62e91e8 100644 --- a/pages/ApiKeys.tsx +++ b/pages/ApiKeys.tsx @@ -169,7 +169,7 @@ export const ApiKeys: React.FC = () => { {key.masked_key} - {key.last_used_at ? new Date(key.last_used_at).toLocaleString() : 'Nunca'} + {key.last_used_at ? new Date(key.last_used_at).toLocaleString('pt-BR') : 'Nunca'}