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'}
|
|