From 10c69457ba6697ea8a26b8c6af3149bf897ab787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Wed, 17 Jun 2026 12:13:48 -0300 Subject: [PATCH] Rollback analytics to ed1f129b07 --- backend/db.js | 7 +- backend/services/analyticsService.js | 85 +++++++-------- backend/sql/orderDateSql.js | 44 -------- backend/test/analyticsService.test.js | 142 ++++---------------------- src/dataService.ts | 18 ++-- src/pages/Clients.tsx | 15 ++- src/pages/Rfm.tsx | 4 +- src/types.ts | 2 - 8 files changed, 78 insertions(+), 239 deletions(-) delete mode 100644 backend/sql/orderDateSql.js diff --git a/backend/db.js b/backend/db.js index 7f07290..3d7d62b 100644 --- a/backend/db.js +++ b/backend/db.js @@ -1,6 +1,5 @@ const { Pool } = require('pg'); const { DATABASE_URL } = require('./config'); -const { ORDER_DATE_SQL } = require('./sql/orderDateSql'); const pool = new Pool({ connectionString: DATABASE_URL @@ -37,7 +36,11 @@ const initDB = async () => { await pool.query(` UPDATE orders - SET data_pedido_date = ${ORDER_DATE_SQL} + SET data_pedido_date = CASE + WHEN data_pedido ~ '^\\d{4}[-/]\\d{1,2}[-/]\\d{1,2}' THEN to_date(replace(left(data_pedido, 10), '/', '-'), 'YYYY-MM-DD') + WHEN data_pedido ~ '^\\d{1,2}[-/]\\d{1,2}[-/]\\d{4}' THEN to_date(replace(left(data_pedido, 10), '/', '-'), 'DD-MM-YYYY') + ELSE NULL + END WHERE data_pedido_date IS NULL AND data_pedido IS NOT NULL AND data_pedido != ''; diff --git a/backend/services/analyticsService.js b/backend/services/analyticsService.js index 6627420..097dace 100644 --- a/backend/services/analyticsService.js +++ b/backend/services/analyticsService.js @@ -1,5 +1,4 @@ const { pool } = require('../db'); -const { ORDER_DATE_SQL } = require('../sql/orderDateSql'); const SIZE_SUFFIX_SQL_PATTERN = '\\s+-\\s+(?:(?:PP|P|M|G|GG|XG|XGG|EG|EGG|EXG|U|UNICO|ÚNICO|\\d{2})(?:/(?:PP|P|M|G|GG|XG|XGG|EG|EGG|EXG|U|UNICO|ÚNICO|\\d{2}))*)$'; const PRODUCT_NAME_SQL = ` @@ -8,7 +7,6 @@ const PRODUCT_NAME_SQL = ` ELSE NULLIF(TRIM(regexp_replace(split_part(COALESCE(produto_descricao, 'Unknown'), ' TAMANHO', 1), '${SIZE_SUFFIX_SQL_PATTERN}', '', 'i')), '') END `; -const CUSTOMER_KEY_SQL = "COALESCE(NULLIF(cliente_fone, ''), 'name:' || COALESCE(NULLIF(cliente_nome, ''), 'Cliente Desconhecido'))"; const normalizeDateParam = (value) => { if (!value) return null; @@ -35,18 +33,18 @@ const normalizeDateParam = (value) => { const buildDateFilter = ({ start, end } = {}) => { const params = []; - const filters = [`${ORDER_DATE_SQL} IS NOT NULL`]; + const filters = ['data_pedido_date IS NOT NULL']; const normalizedStart = normalizeDateParam(start); const normalizedEnd = normalizeDateParam(end); if (normalizedStart) { params.push(normalizedStart); - filters.push(`${ORDER_DATE_SQL} >= $${params.length}::date`); + filters.push(`data_pedido_date >= $${params.length}::date`); } if (normalizedEnd) { params.push(normalizedEnd); - filters.push(`${ORDER_DATE_SQL} <= $${params.length}::date`); + filters.push(`data_pedido_date <= $${params.length}::date`); } return { @@ -213,8 +211,8 @@ const getProductAnalytics = async (range = {}) => { COALESCE(SUM(quantidade), 0) as quantity_sold, COALESCE(SUM(quantidade * valor_unitario), 0) as revenue, COUNT(*)::int as order_line_count, - MIN(${ORDER_DATE_SQL}) as first_sale_date, - MAX(${ORDER_DATE_SQL}) as last_sale_date + MIN(data_pedido_date) as first_sale_date, + MAX(data_pedido_date) as last_sale_date FROM orders ${whereClause} GROUP BY name @@ -237,21 +235,19 @@ const getClientAnalytics = async (range = {}) => { const { params, whereClause } = buildDateFilter(range); const result = await pool.query(` SELECT - ${CUSTOMER_KEY_SQL} as customer_key, - MAX(COALESCE(NULLIF(cliente_nome, ''), 'Cliente Desconhecido')) as name, + COALESCE(NULLIF(cliente_nome, ''), 'Cliente Desconhecido') as name, MAX(NULLIF(cliente_fone, '')) as phone, COALESCE(SUM(quantidade), 0) as quantity_purchased, COALESCE(SUM(quantidade * valor_unitario), 0) as total_spent, COUNT(DISTINCT COALESCE(NULLIF(pedido_id, ''), data_pedido || '_' || valor_pedido::text))::int as order_count, - MAX(${ORDER_DATE_SQL}) as last_purchase_date + MAX(data_pedido_date) as last_purchase_date FROM orders ${whereClause} - GROUP BY customer_key + GROUP BY COALESCE(NULLIF(cliente_nome, ''), 'Cliente Desconhecido') ORDER BY total_spent DESC; `, params); return result.rows.map(row => ({ - customerKey: row.customer_key, name: row.name, phone: row.phone || '', quantityPurchased: toNumber(row.quantity_purchased), @@ -265,69 +261,62 @@ const getRfmAnalytics = async (range = {}) => { const { params, whereClause } = buildDateFilter(range); const normalizedStart = normalizeDateParam(range.start); const normalizedEnd = normalizeDateParam(range.end); - const periodRecencyReferenceDate = normalizedEnd ? `$${params.length}::date` : 'CURRENT_DATE'; - const recencyReferenceDate = normalizedEnd ? '$1::date' : 'CURRENT_DATE'; - const historyParams = normalizedEnd ? [normalizedEnd] : []; - const usePeriodAsHistory = !normalizedStart || normalizedStart <= '2000-01-01'; + const tagReference = getPreviousDate(normalizedStart) || normalizedEnd; + const recencyReferenceDate = tagReference ? '$1::date' : 'CURRENT_DATE'; + const historyParams = tagReference ? [tagReference] : []; - const periodQuery = pool.query(` + const [periodResult, historyResult] = await Promise.all([ + pool.query(` SELECT - ${CUSTOMER_KEY_SQL} as customer_key, MAX(cliente_nome) as name, - MAX(NULLIF(cliente_fone, '')) as phone, + cliente_fone as phone, COALESCE(SUM(quantidade * valor_unitario), 0) as monetary, COUNT(DISTINCT COALESCE(NULLIF(pedido_id, ''), data_pedido || '_' || valor_pedido::text))::int as frequency, COALESCE(SUM(quantidade), 0) as quantity_purchased, - MAX(${ORDER_DATE_SQL}) as last_purchase_date, - GREATEST((${periodRecencyReferenceDate} - MAX(${ORDER_DATE_SQL}))::int, 0) as recency_days + MAX(data_pedido_date) as last_purchase_date FROM orders ${whereClause} - GROUP BY customer_key + AND cliente_fone IS NOT NULL + AND cliente_fone != '' + GROUP BY cliente_fone ORDER BY monetary DESC; - `, params); - - const historyQuery = usePeriodAsHistory ? null : pool.query(` + `, params), + pool.query(` SELECT - ${CUSTOMER_KEY_SQL} as customer_key, MAX(cliente_nome) as name, - MAX(NULLIF(cliente_fone, '')) as phone, + cliente_fone as phone, COALESCE(SUM(quantidade * valor_unitario), 0) as monetary, COUNT(DISTINCT COALESCE(NULLIF(pedido_id, ''), data_pedido || '_' || valor_pedido::text))::int as frequency, COALESCE(SUM(quantidade), 0) as quantity_purchased, - MAX(${ORDER_DATE_SQL}) as last_purchase_date, - GREATEST((${recencyReferenceDate} - MAX(${ORDER_DATE_SQL}))::int, 0) as recency_days + MAX(data_pedido_date) as last_purchase_date, + GREATEST((${recencyReferenceDate} - MAX(data_pedido_date))::int, 0) as recency_days FROM orders - WHERE ${ORDER_DATE_SQL} IS NOT NULL - AND ${ORDER_DATE_SQL} <= ${recencyReferenceDate} - GROUP BY customer_key; - `, historyParams); + WHERE data_pedido_date IS NOT NULL + AND data_pedido_date <= ${recencyReferenceDate} + AND cliente_fone IS NOT NULL + AND cliente_fone != '' + GROUP BY cliente_fone; + `, historyParams) + ]); - const [periodResult, historyResult] = historyQuery - ? await Promise.all([periodQuery, historyQuery]) - : [await periodQuery, null]; - - const historyRows = historyResult ? historyResult.rows : periodResult.rows; - - const historyClients = buildRfmClients(historyRows.map(row => ({ - customerKey: row.customer_key, + const historyClients = buildRfmClients(historyResult.rows.map(row => ({ name: row.name, - phone: row.phone || '', + phone: row.phone, monetary: toNumber(row.monetary), frequency: toNumber(row.frequency), quantityPurchased: toNumber(row.quantity_purchased), lastPurchaseDate: row.last_purchase_date, recencyDays: toNumber(row.recency_days) }))); - const tagsByCustomerKey = new Map(historyClients.map(client => [client.customerKey, client])); + const tagsByPhone = new Map(historyClients.map(client => [client.phone, client])); const clients = periodResult.rows.map(row => { - const taggedClient = tagsByCustomerKey.get(row.customer_key); + const taggedClient = tagsByPhone.get(row.phone); if (!taggedClient) { const newCustomerSegment = getRfmSegment(3, 1); return { - customerKey: row.customer_key, name: row.name, - phone: row.phone || '', + phone: row.phone, monetary: toNumber(row.monetary), frequency: toNumber(row.frequency), quantityPurchased: toNumber(row.quantity_purchased), @@ -345,9 +334,8 @@ const getRfmAnalytics = async (range = {}) => { return { ...taggedClient, - customerKey: row.customer_key, name: row.name, - phone: row.phone || '', + phone: row.phone, monetary: toNumber(row.monetary), frequency: toNumber(row.frequency), quantityPurchased: toNumber(row.quantity_purchased), @@ -384,6 +372,5 @@ module.exports = { getDashboardAnalytics, getProductAnalytics, normalizeDateParam, - ORDER_DATE_SQL, scoreTertile }; diff --git a/backend/sql/orderDateSql.js b/backend/sql/orderDateSql.js deleted file mode 100644 index a48122b..0000000 --- a/backend/sql/orderDateSql.js +++ /dev/null @@ -1,44 +0,0 @@ -const ISO_ORDER_YEAR_SQL = "substring(data_pedido from '^(\\d{4})[-/]')::int"; -const ISO_ORDER_MONTH_SQL = "substring(data_pedido from '^\\d{4}[-/](\\d{1,2})[-/]')::int"; -const ISO_ORDER_DAY_SQL = "substring(data_pedido from '^\\d{4}[-/]\\d{1,2}[-/](\\d{1,2})')::int"; -const BR_ORDER_DAY_SQL = "substring(data_pedido from '^(\\d{1,2})[-/]')::int"; -const BR_ORDER_MONTH_SQL = "substring(data_pedido from '^\\d{1,2}[-/](\\d{1,2})[-/]')::int"; -const BR_ORDER_YEAR_SQL = "substring(data_pedido from '^\\d{1,2}[-/]\\d{1,2}[-/](\\d{4})')::int"; - -const buildSafeDateSql = ({ yearSql, monthSql, daySql }) => { - const candidateDateSql = `(make_date(${yearSql}, ${monthSql}, 1) + ((${daySql} - 1) * INTERVAL '1 day'))::date`; - - return ` - CASE - WHEN ${yearSql} BETWEEN 1 AND 9999 - AND ${monthSql} BETWEEN 1 AND 12 - AND ${daySql} BETWEEN 1 AND 31 - AND EXTRACT(MONTH FROM ${candidateDateSql})::int = ${monthSql} - THEN ${candidateDateSql} - ELSE NULL - END - `; -}; - -const ORDER_DATE_SQL = ` - COALESCE( - data_pedido_date, - CASE - WHEN data_pedido ~ '^\\d{4}[-/]\\d{1,2}[-/]\\d{1,2}' THEN ${buildSafeDateSql({ - yearSql: ISO_ORDER_YEAR_SQL, - monthSql: ISO_ORDER_MONTH_SQL, - daySql: ISO_ORDER_DAY_SQL - })} - WHEN data_pedido ~ '^\\d{1,2}[-/]\\d{1,2}[-/]\\d{4}' THEN ${buildSafeDateSql({ - yearSql: BR_ORDER_YEAR_SQL, - monthSql: BR_ORDER_MONTH_SQL, - daySql: BR_ORDER_DAY_SQL - })} - ELSE NULL - END - ) -`; - -module.exports = { - ORDER_DATE_SQL -}; diff --git a/backend/test/analyticsService.test.js b/backend/test/analyticsService.test.js index ddec12a..a878e8c 100644 --- a/backend/test/analyticsService.test.js +++ b/backend/test/analyticsService.test.js @@ -9,26 +9,10 @@ const { getRfmAnalytics, getRfmSegment, normalizeDateParam, - ORDER_DATE_SQL, scoreTertile } = require('../services/analyticsService'); const { pool } = require('../db'); -const compactSql = (sql) => sql.replace(/\s+/g, ' ').trim(); -const expectedDateFilter = ({ startPlaceholder, endPlaceholder } = {}) => { - const filters = [`${ORDER_DATE_SQL} IS NOT NULL`]; - - if (startPlaceholder) { - filters.push(`${ORDER_DATE_SQL} >= ${startPlaceholder}::date`); - } - - if (endPlaceholder) { - filters.push(`${ORDER_DATE_SQL} <= ${endPlaceholder}::date`); - } - - return `WHERE ${filters.join(' AND ')}`; -}; - test('normalizeDateParam accepts strict ISO dates', () => { assert.equal(normalizeDateParam('2026-05-28'), '2026-05-28'); }); @@ -44,8 +28,8 @@ test('buildDateFilter builds bounded date predicates', () => { assert.deepEqual(filter.params, ['2026-05-01', '2026-05-28']); assert.equal( - compactSql(filter.whereClause), - compactSql(expectedDateFilter({ startPlaceholder: '$1', endPlaceholder: '$2' })) + filter.whereClause, + 'WHERE data_pedido_date IS NOT NULL AND data_pedido_date >= $1::date AND data_pedido_date <= $2::date' ); }); @@ -54,8 +38,8 @@ test('buildDateFilter builds Hoje as an inclusive single-day date predicate', () assert.deepEqual(filter.params, ['2026-06-15', '2026-06-15']); assert.equal( - compactSql(filter.whereClause), - compactSql(expectedDateFilter({ startPlaceholder: '$1', endPlaceholder: '$2' })) + filter.whereClause, + 'WHERE data_pedido_date IS NOT NULL AND data_pedido_date >= $1::date AND data_pedido_date <= $2::date' ); }); @@ -64,8 +48,8 @@ test('buildDateFilter builds Ontem as an inclusive single-day date predicate', ( assert.deepEqual(filter.params, ['2026-06-14', '2026-06-14']); assert.equal( - compactSql(filter.whereClause), - compactSql(expectedDateFilter({ startPlaceholder: '$1', endPlaceholder: '$2' })) + filter.whereClause, + 'WHERE data_pedido_date IS NOT NULL AND data_pedido_date >= $1::date AND data_pedido_date <= $2::date' ); }); @@ -74,8 +58,8 @@ test('buildDateFilter builds Ultimos 7 dias as an inclusive calendar range', () assert.deepEqual(filter.params, ['2026-06-09', '2026-06-15']); assert.equal( - compactSql(filter.whereClause), - compactSql(expectedDateFilter({ startPlaceholder: '$1', endPlaceholder: '$2' })) + filter.whereClause, + 'WHERE data_pedido_date IS NOT NULL AND data_pedido_date >= $1::date AND data_pedido_date <= $2::date' ); }); @@ -84,8 +68,8 @@ test('buildDateFilter builds custom single-day ranges inclusively', () => { assert.deepEqual(filter.params, ['2026-06-10', '2026-06-10']); assert.equal( - compactSql(filter.whereClause), - compactSql(expectedDateFilter({ startPlaceholder: '$1', endPlaceholder: '$2' })) + filter.whereClause, + 'WHERE data_pedido_date IS NOT NULL AND data_pedido_date >= $1::date AND data_pedido_date <= $2::date' ); }); @@ -94,20 +78,11 @@ test('buildDateFilter ignores invalid bounds', () => { assert.deepEqual(filter.params, ['2026-05-28']); assert.equal( - compactSql(filter.whereClause), - compactSql(expectedDateFilter({ endPlaceholder: '$1' })) + filter.whereClause, + 'WHERE data_pedido_date IS NOT NULL AND data_pedido_date <= $1::date' ); }); -test('buildDateFilter falls back to parsing the stored display date when normalized dates are missing', () => { - const filter = buildDateFilter({ start: '2026-05-01', end: '2026-05-28' }); - const whereClause = compactSql(filter.whereClause); - - assert.match(whereClause, /COALESCE\( data_pedido_date,/); - assert.match(whereClause, /data_pedido ~ '\^\\d\{4\}/); - assert.match(whereClause, /data_pedido ~ '\^\\d\{1,2\}/); -}); - test('getPreviousDate returns the calendar day before an ISO date', () => { assert.equal(getPreviousDate('2026-06-15'), '2026-06-14'); assert.equal(getPreviousDate('2026-03-01'), '2026-02-28'); @@ -216,30 +191,28 @@ test('buildRfmClients scores segments from RFM history when period totals are sm assert.equal(yesterdayBuyer.monetaryScore, 3); }); -test('getRfmAnalytics classifies period buyers by history through the selected range end', async () => { +test('getRfmAnalytics groups period buyers by their RFM tag before the selected period', async () => { const originalQuery = pool.query; const calls = []; pool.query = async (sql, params) => { calls.push({ sql, params }); - const isHistoryQuery = params.length === 1; - const referenceDate = params[0]; + const isHistoryQuery = sql.includes('recency_days'); + const endDate = params[0]; if (isHistoryQuery) { return { rows: [ { - customer_key: '1', name: 'Cliente Ontem', phone: '1', monetary: 5000, frequency: 20, quantity_purchased: 20, last_purchase_date: '2026-06-14', - recency_days: referenceDate === '2026-06-14' ? 0 : 1 + recency_days: endDate === '2026-06-13' ? 0 : 1 }, { - customer_key: '2', name: 'Cliente Antigo', phone: '2', monetary: 50, @@ -249,7 +222,6 @@ test('getRfmAnalytics classifies period buyers by history through the selected r recency_days: 164 }, { - customer_key: '3', name: 'Cliente Medio', phone: '3', monetary: 100, @@ -257,16 +229,6 @@ test('getRfmAnalytics classifies period buyers by history through the selected r quantity_purchased: 2, last_purchase_date: '2026-03-01', recency_days: 105 - }, - { - customer_key: 'name:Cliente Sem Fone', - name: 'Cliente Sem Fone', - phone: null, - monetary: 1000, - frequency: 10, - quantity_purchased: 10, - last_purchase_date: '2026-06-14', - recency_days: 1 } ] }; @@ -275,7 +237,6 @@ test('getRfmAnalytics classifies period buyers by history through the selected r return { rows: [ { - customer_key: '1', name: 'Cliente Ontem', phone: '1', monetary: 100, @@ -284,22 +245,12 @@ test('getRfmAnalytics classifies period buyers by history through the selected r last_purchase_date: '2026-06-14' }, { - customer_key: '4', name: 'Cliente Novo no Periodo', phone: '4', monetary: 25, frequency: 1, quantity_purchased: 1, last_purchase_date: '2026-06-14' - }, - { - customer_key: 'name:Cliente Sem Fone', - name: 'Cliente Sem Fone', - phone: null, - monetary: 30, - frequency: 1, - quantity_purchased: 1, - last_purchase_date: '2026-06-14' } ] }; @@ -310,69 +261,18 @@ test('getRfmAnalytics classifies period buyers by history through the selected r const yesterday = await getRfmAnalytics({ start: '2026-06-14', end: '2026-06-14' }); assert.deepEqual(calls[0].params, ['2026-06-09', '2026-06-15']); - assert.match(compactSql(calls[1].sql), /\(\$1::date - MAX\( COALESCE\( data_pedido_date,/); - assert.match(compactSql(calls[1].sql), /COALESCE\( data_pedido_date,.* <= \$1::date/); - assert.deepEqual(calls[1].params, ['2026-06-15']); + assert.match(calls[1].sql, /\(\$1::date - MAX\(data_pedido_date\)\)::int/); + assert.match(calls[1].sql, /data_pedido_date <= \$1::date/); + assert.deepEqual(calls[1].params, ['2026-06-08']); assert.deepEqual(calls[2].params, ['2026-06-14', '2026-06-14']); - assert.deepEqual(calls[3].params, ['2026-06-14']); + assert.deepEqual(calls[3].params, ['2026-06-13']); assert.equal(sevenDays.clients[0].segmentKey, 'champions'); assert.equal(yesterday.clients[0].segmentKey, 'champions'); assert.equal(yesterday.clients[0].frequency, 1); assert.equal(yesterday.clients[0].monetary, 100); - assert.equal(yesterday.clients.length, 3); + assert.equal(yesterday.clients.length, 2); assert.ok(!yesterday.clients.some(client => client.phone === '2')); assert.ok(yesterday.clients.some(client => client.phone === '4' && client.segmentKey === 'new_customers')); - assert.ok(yesterday.clients.some(client => ( - client.customerKey === 'name:Cliente Sem Fone' && - client.phone === '' && - client.segmentKey === 'champions' && - client.monetary === 30 - ))); - } finally { - pool.query = originalQuery; - } -}); - -test('getRfmAnalytics reuses period rows as RFV history for all-period ranges', async () => { - const originalQuery = pool.query; - const calls = []; - - pool.query = async (sql, params) => { - calls.push({ sql, params }); - return { - rows: [ - { - customer_key: '1', - name: 'Cliente Frequente', - phone: '1', - monetary: 5000, - frequency: 20, - quantity_purchased: 20, - last_purchase_date: '2026-06-14', - recency_days: 1 - }, - { - customer_key: '2', - name: 'Cliente Antigo', - phone: '2', - monetary: 50, - frequency: 1, - quantity_purchased: 1, - last_purchase_date: '2026-01-01', - recency_days: 165 - } - ] - }; - }; - - try { - const result = await getRfmAnalytics({ start: '2000-01-01', end: '2026-06-15' }); - - assert.equal(calls.length, 1); - assert.match(compactSql(calls[0].sql), /\(\$2::date - MAX\( COALESCE\( data_pedido_date,/); - assert.deepEqual(calls[0].params, ['2000-01-01', '2026-06-15']); - assert.equal(result.clients.length, 2); - assert.equal(result.segments.reduce((total, segment) => total + segment.count, 0), 2); } finally { pool.query = originalQuery; } diff --git a/src/dataService.ts b/src/dataService.ts index 1903508..3d72298 100644 --- a/src/dataService.ts +++ b/src/dataService.ts @@ -109,16 +109,6 @@ const authFetch = async (path: string, options: RequestInit = {}): Promise => { - const params = new URLSearchParams({ - start: formatDateParam(dateRange.start), - end: formatDateParam(dateRange.end) - }); - const response = await authFetch(`/analytics/clients?${params.toString()}`); - if (!response.ok) return []; - return await response.json(); -}; - export const fetchDashboardAnalytics = async (dateRange: DateRange): Promise => { try { const params = new URLSearchParams({ @@ -151,7 +141,13 @@ export const fetchRfmAnalytics = async (dateRange: DateRange): Promise => { try { - return await fetchClientAnalyticsForRange(dateRange); + const params = new URLSearchParams({ + start: formatDateParam(dateRange.start), + end: formatDateParam(dateRange.end) + }); + const response = await authFetch(`/analytics/clients?${params.toString()}`); + if (!response.ok) return []; + return await response.json(); } catch (error) { console.error('Fetch client analytics failed', error); return []; diff --git a/src/pages/Clients.tsx b/src/pages/Clients.tsx index 11f2b3d..d1219c2 100644 --- a/src/pages/Clients.tsx +++ b/src/pages/Clients.tsx @@ -7,7 +7,7 @@ import DateRangePicker from '../components/DateRangePicker'; import type { ClientSortOption, ClientSummary } from '../analytics/clients'; const clientTypeStyles: Record = { - 'Sem análise': 'border-zinc-500/30 bg-zinc-500/15 text-zinc-300', + 'Sem análise': 'border-zinc-600/30 bg-zinc-600/15 text-zinc-300', 'Campeão': 'border-emerald-500/30 bg-emerald-500/15 text-emerald-300', 'Potencial Leal': 'border-sky-500/30 bg-sky-500/15 text-sky-300', 'Novo Cliente': 'border-cyan-500/30 bg-cyan-500/15 text-cyan-300', @@ -83,13 +83,12 @@ const Clients = () => { let isMounted = true; const loadClients = async () => { - const clientsData = await fetchClientAnalytics(dateRange); + const [clientsData, rfmData] = await Promise.all([ + fetchClientAnalytics(dateRange), + fetchRfmAnalytics(dateRange) + ]); if (isMounted) { setClientAnalytics(clientsData); - } - - const rfmData = await fetchRfmAnalytics(dateRange); - if (isMounted) { setRfmAnalytics(rfmData); } }; @@ -103,9 +102,9 @@ const Clients = () => { const allClientsData = useMemo(() => { const normalizedSearch = searchTerm.trim().toLowerCase(); - const rfmByCustomerKey = new Map((rfmAnalytics?.clients || []).map(client => [client.customerKey, client])); + const rfmByPhone = new Map((rfmAnalytics?.clients || []).map(client => [client.phone, client])); const clients = clientAnalytics.map((client): ClientSummary => { - const rfmClient = rfmByCustomerKey.get(client.customerKey); + const rfmClient = client.phone ? rfmByPhone.get(client.phone) : undefined; return { name: client.name, phone: client.phone, diff --git a/src/pages/Rfm.tsx b/src/pages/Rfm.tsx index f506b9a..51810e1 100644 --- a/src/pages/Rfm.tsx +++ b/src/pages/Rfm.tsx @@ -292,7 +292,7 @@ const Rfm = () => {

Clientes no Período

{clients.length}

-

Segmento RFV calculado até o fim do período

+

Agrupados pela tag RFV anterior

Receita no Período

@@ -312,7 +312,7 @@ const Rfm = () => {

Matriz RFV

-

Compradores do período agrupados pelo RFV histórico até a data final.

+

Compradores do período agrupados pela tag RFV anterior ao período.

Menor prioridade diff --git a/src/types.ts b/src/types.ts index 3abbb59..be2e367 100644 --- a/src/types.ts +++ b/src/types.ts @@ -64,7 +64,6 @@ export interface DashboardAnalytics { } export interface ClientAnalyticsItem { - customerKey: string; name: string; phone: string; quantityPurchased: number; @@ -74,7 +73,6 @@ export interface ClientAnalyticsItem { } export interface RfmClient { - customerKey: string; name: string; phone: string; monetary: number;