Clean seller metadata display names
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 40s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 40s
This commit is contained in:
@@ -42,6 +42,7 @@ const CUSTOMER_IDENTITY_CTE = `
|
|||||||
)
|
)
|
||||||
`;
|
`;
|
||||||
const CUSTOMER_KEY_SQL = 'customer_key';
|
const CUSTOMER_KEY_SQL = 'customer_key';
|
||||||
|
const TRAILING_SELLER_ID_SQL_PATTERN = '[[:space:]]*#([0-9]+)[[:space:]]*$';
|
||||||
|
|
||||||
const getClientTokenSecret = () => (
|
const getClientTokenSecret = () => (
|
||||||
process.env.CLIENT_TOKEN_SECRET ||
|
process.env.CLIENT_TOKEN_SECRET ||
|
||||||
@@ -196,6 +197,19 @@ const normalizeSellerFilter = (value) => {
|
|||||||
return { type: 'any', value: normalizedValue };
|
return { type: 'any', value: normalizedValue };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const normalizeSellerOption = (row) => {
|
||||||
|
const rawId = String(row.id || '').trim();
|
||||||
|
const rawName = String(row.name || '').trim();
|
||||||
|
const idFromName = rawName.match(/#(\d+)\s*$/)?.[1] || '';
|
||||||
|
const id = rawId || idFromName;
|
||||||
|
const name = rawName.replace(/#\d+\s*$/, '').trim();
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: name || id
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const appendOrderMetadataFilters = (params, filters, range = {}) => {
|
const appendOrderMetadataFilters = (params, filters, range = {}) => {
|
||||||
const marketplace = normalizeTextFilter(range.marketplace);
|
const marketplace = normalizeTextFilter(range.marketplace);
|
||||||
const salesChannel = normalizeTextFilter(range.canal_venda || range.canalVenda);
|
const salesChannel = normalizeTextFilter(range.canal_venda || range.canalVenda);
|
||||||
@@ -703,8 +717,11 @@ const getClientFilterOptions = async () => {
|
|||||||
pool.query(`
|
pool.query(`
|
||||||
WITH seller_options AS (
|
WITH seller_options AS (
|
||||||
SELECT
|
SELECT
|
||||||
NULLIF(TRIM(id_vendedor), '') as id,
|
COALESCE(
|
||||||
NULLIF(TRIM(nome_vendedor), '') as name
|
NULLIF(TRIM(id_vendedor), ''),
|
||||||
|
substring(NULLIF(TRIM(nome_vendedor), '') from '${TRAILING_SELLER_ID_SQL_PATTERN}')
|
||||||
|
) as id,
|
||||||
|
NULLIF(TRIM(regexp_replace(COALESCE(nome_vendedor, ''), '${TRAILING_SELLER_ID_SQL_PATTERN}', '')), '') as name
|
||||||
FROM orders
|
FROM orders
|
||||||
WHERE (
|
WHERE (
|
||||||
NULLIF(TRIM(id_vendedor), '') IS NOT NULL
|
NULLIF(TRIM(id_vendedor), '') IS NOT NULL
|
||||||
@@ -720,8 +737,7 @@ const getClientFilterOptions = async () => {
|
|||||||
const sellerOptionsByValue = new Map();
|
const sellerOptionsByValue = new Map();
|
||||||
|
|
||||||
sellerResult.rows.forEach(row => {
|
sellerResult.rows.forEach(row => {
|
||||||
const id = row.id || '';
|
const { id, name } = normalizeSellerOption(row);
|
||||||
const name = row.name || '';
|
|
||||||
const value = id ? `id:${id}` : `name:${name}`;
|
const value = id ? `id:${id}` : `name:${name}`;
|
||||||
if (!value || sellerOptionsByValue.has(value)) return;
|
if (!value || sellerOptionsByValue.has(value)) return;
|
||||||
|
|
||||||
|
|||||||
@@ -641,6 +641,7 @@ test('getClientFilterOptions returns all distinct order metadata options', async
|
|||||||
rows: [
|
rows: [
|
||||||
{ id: 'VEN-1', name: 'Maria' },
|
{ id: 'VEN-1', name: 'Maria' },
|
||||||
{ id: '', name: 'Sem ID' },
|
{ id: '', name: 'Sem ID' },
|
||||||
|
{ id: '', name: 'KEDMA DA SILVA #977226210' },
|
||||||
{ id: 'VEN-1', name: 'Maria' }
|
{ id: 'VEN-1', name: 'Maria' }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@@ -657,12 +658,15 @@ test('getClientFilterOptions returns all distinct order metadata options', async
|
|||||||
});
|
});
|
||||||
assert.match(calls[0].sql, /WHERE NULLIF\(TRIM\(marketplace\), ''\) IS NOT NULL/);
|
assert.match(calls[0].sql, /WHERE NULLIF\(TRIM\(marketplace\), ''\) IS NOT NULL/);
|
||||||
assert.match(calls[2].sql, /WITH seller_options AS/);
|
assert.match(calls[2].sql, /WITH seller_options AS/);
|
||||||
|
assert.match(calls[2].sql, /substring\(NULLIF\(TRIM\(nome_vendedor\), ''\) from/);
|
||||||
|
assert.match(calls[2].sql, /regexp_replace\(COALESCE\(nome_vendedor, ''\)/);
|
||||||
assert.match(calls[2].sql, /NULLIF\(TRIM\(nome_vendedor\), ''\) IS NOT NULL/);
|
assert.match(calls[2].sql, /NULLIF\(TRIM\(nome_vendedor\), ''\) IS NOT NULL/);
|
||||||
assert.deepEqual(options.marketplaces, ['Mercado Livre', 'Shopee']);
|
assert.deepEqual(options.marketplaces, ['Mercado Livre', 'Shopee']);
|
||||||
assert.deepEqual(options.salesChannels, ['Online']);
|
assert.deepEqual(options.salesChannels, ['Online']);
|
||||||
assert.deepEqual(options.sellers, [
|
assert.deepEqual(options.sellers, [
|
||||||
{ value: 'id:VEN-1', id: 'VEN-1', name: 'Maria' },
|
{ value: 'id:VEN-1', id: 'VEN-1', name: 'Maria' },
|
||||||
{ value: 'name:Sem ID', id: '', name: 'Sem ID' }
|
{ value: 'name:Sem ID', id: '', name: 'Sem ID' },
|
||||||
|
{ value: 'id:977226210', id: '977226210', name: 'KEDMA DA SILVA' }
|
||||||
]);
|
]);
|
||||||
} finally {
|
} finally {
|
||||||
pool.query = originalQuery;
|
pool.query = originalQuery;
|
||||||
|
|||||||
26
src/displayFormatters.ts
Normal file
26
src/displayFormatters.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
const SMALL_WORDS = new Set(['da', 'de', 'do', 'das', 'dos', 'e']);
|
||||||
|
|
||||||
|
export const removeTrailingSellerId = (value: string) => {
|
||||||
|
return value.replace(/\s*#\d+\s*$/, '').trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatDisplayName = (value: string) => {
|
||||||
|
const normalized = String(value || '').replace(/\s+/g, ' ').trim();
|
||||||
|
if (!normalized) return '';
|
||||||
|
|
||||||
|
const withoutSellerId = removeTrailingSellerId(normalized);
|
||||||
|
const withoutNumericPrefix = withoutSellerId.replace(/^\[\d+\]\s*/, '').trim();
|
||||||
|
const isUppercaseName = /[A-ZÁÀÂÃÉÈÊÍÏÓÔÕÖÚÇÑ]/.test(withoutNumericPrefix) &&
|
||||||
|
withoutNumericPrefix === withoutNumericPrefix.toUpperCase();
|
||||||
|
|
||||||
|
if (!isUppercaseName) return withoutNumericPrefix;
|
||||||
|
|
||||||
|
return withoutNumericPrefix
|
||||||
|
.toLocaleLowerCase('pt-BR')
|
||||||
|
.split(' ')
|
||||||
|
.map((word, index) => {
|
||||||
|
if (index > 0 && SMALL_WORDS.has(word)) return word;
|
||||||
|
return word.charAt(0).toLocaleUpperCase('pt-BR') + word.slice(1);
|
||||||
|
})
|
||||||
|
.join(' ');
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContaine
|
|||||||
import DateRangePicker from '../components/DateRangePicker';
|
import DateRangePicker from '../components/DateRangePicker';
|
||||||
import type { ClientDetailsAnalytics, DateRange, OrderData } from '../types';
|
import type { ClientDetailsAnalytics, DateRange, OrderData } from '../types';
|
||||||
import { fetchClientDetailsAnalytics } from '../dataService';
|
import { fetchClientDetailsAnalytics } from '../dataService';
|
||||||
|
import { formatDisplayName, removeTrailingSellerId } from '../displayFormatters';
|
||||||
|
|
||||||
type CustomTooltipProps = {
|
type CustomTooltipProps = {
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
@@ -27,14 +28,11 @@ const CustomTooltip = ({ active, payload, label }: CustomTooltipProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getOrderMetadata = (order: OrderData) => {
|
const getOrderMetadata = (order: OrderData) => {
|
||||||
const seller = [
|
const sellerName = formatDisplayName(removeTrailingSellerId(order.nome_vendedor || ''));
|
||||||
order.nome_vendedor,
|
|
||||||
order.id_vendedor ? `#${order.id_vendedor}` : ''
|
|
||||||
].filter(Boolean).join(' ');
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
order.cliente_nome_fantasia ? `Fantasia: ${order.cliente_nome_fantasia}` : '',
|
order.cliente_nome_fantasia ? `Fantasia: ${formatDisplayName(order.cliente_nome_fantasia)}` : '',
|
||||||
seller ? `Vendedor: ${seller}` : '',
|
sellerName ? `Vendedor: ${sellerName}` : '',
|
||||||
order.marketplace ? `Marketplace: ${order.marketplace}` : '',
|
order.marketplace ? `Marketplace: ${order.marketplace}` : '',
|
||||||
order.canal_venda ? `Canal: ${order.canal_venda}` : '',
|
order.canal_venda ? `Canal: ${order.canal_venda}` : '',
|
||||||
order.numero_ecommerce ? `E-commerce: ${order.numero_ecommerce}` : ''
|
order.numero_ecommerce ? `E-commerce: ${order.numero_ecommerce}` : ''
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { ClientAnalyticsItem, ClientFilterOptions, ClientMetadataFilters, D
|
|||||||
import { fetchClientAnalytics, fetchClientFilterOptions, fetchRfmAnalytics, getCachedClientAnalytics, getCachedClientFilterOptions, getCachedRfmAnalytics } from '../dataService';
|
import { fetchClientAnalytics, fetchClientFilterOptions, fetchRfmAnalytics, getCachedClientAnalytics, getCachedClientFilterOptions, getCachedRfmAnalytics } from '../dataService';
|
||||||
import { endOfLocalDay, formatDateParam, parseLocalDateInput, rangeForDay, rangeForLastDays, rangeForPreviousDay, startOfLocalDay } from '../dateRanges';
|
import { endOfLocalDay, formatDateParam, parseLocalDateInput, rangeForDay, rangeForLastDays, rangeForPreviousDay, startOfLocalDay } from '../dateRanges';
|
||||||
import type { ClientSortOption, ClientSummary } from '../analytics/clients';
|
import type { ClientSortOption, ClientSummary } from '../analytics/clients';
|
||||||
|
import { formatDisplayName, removeTrailingSellerId } from '../displayFormatters';
|
||||||
|
|
||||||
const clientTypeStyles: Record<string, string> = {
|
const clientTypeStyles: Record<string, string> = {
|
||||||
'Sem análise': 'border-zinc-600/30 bg-zinc-600/15 text-zinc-300',
|
'Sem análise': 'border-zinc-600/30 bg-zinc-600/15 text-zinc-300',
|
||||||
@@ -318,8 +319,7 @@ const Clients = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getSellerOptionLabel = (seller: ClientFilterOptions['sellers'][number]) => {
|
const getSellerOptionLabel = (seller: ClientFilterOptions['sellers'][number]) => {
|
||||||
if (seller.id && seller.name && seller.id !== seller.name) return `${seller.name} (${seller.id})`;
|
return formatDisplayName(removeTrailingSellerId(seller.name || seller.id));
|
||||||
return seller.name || seller.id;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const marketplaceOptions = useMemo(() => {
|
const marketplaceOptions = useMemo(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user