Clarify RFM period metrics
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m29s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m29s
This commit is contained in:
@@ -171,10 +171,11 @@ const Rfm = () => {
|
|||||||
|
|
||||||
const totals = useMemo(() => {
|
const totals = useMemo(() => {
|
||||||
const totalRevenue = clients.reduce((sum, client) => sum + client.monetary, 0);
|
const totalRevenue = clients.reduce((sum, client) => sum + client.monetary, 0);
|
||||||
|
const periodClientCount = clients.filter(client => client.frequency > 0 || client.monetary > 0).length;
|
||||||
const activeSegments = segments.filter(segment => segment.count > 0).length;
|
const activeSegments = segments.filter(segment => segment.count > 0).length;
|
||||||
const topSegment = [...segments].sort((a, b) => b.totalRevenue - a.totalRevenue)[0];
|
const topSegment = [...segments].sort((a, b) => b.totalRevenue - a.totalRevenue)[0];
|
||||||
|
|
||||||
return { totalRevenue, activeSegments, topSegment };
|
return { totalRevenue, periodClientCount, activeSegments, topSegment };
|
||||||
}, [clients, segments]);
|
}, [clients, segments]);
|
||||||
|
|
||||||
const segmentInsights = useMemo(() => {
|
const segmentInsights = useMemo(() => {
|
||||||
@@ -215,9 +216,9 @@ const Rfm = () => {
|
|||||||
Frequencia: client.frequencyScore,
|
Frequencia: client.frequencyScore,
|
||||||
Monetario: client.monetaryScore,
|
Monetario: client.monetaryScore,
|
||||||
'Dias desde ultima compra': client.recencyDays,
|
'Dias desde ultima compra': client.recencyDays,
|
||||||
Pedidos: client.frequency,
|
'Pedidos no periodo': client.frequency,
|
||||||
'Ticket medio (R$)': (client.frequency ? client.monetary / client.frequency : 0).toFixed(2).replace('.', ','),
|
'Ticket medio no periodo (R$)': (client.frequency ? client.monetary / client.frequency : 0).toFixed(2).replace('.', ','),
|
||||||
'Total gasto (R$)': client.monetary.toFixed(2).replace('.', ','),
|
'Receita no periodo (R$)': client.monetary.toFixed(2).replace('.', ','),
|
||||||
'Ultima compra': formatDate(client.lastPurchaseDate)
|
'Ultima compra': formatDate(client.lastPurchaseDate)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -252,18 +253,19 @@ const Rfm = () => {
|
|||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
<div className="bg-dark-card p-6 rounded-2xl border border-dark-border shadow-sm">
|
<div className="bg-dark-card p-6 rounded-2xl border border-dark-border shadow-sm">
|
||||||
<p className="text-dark-muted text-sm font-medium mb-1">Clientes Classificados</p>
|
<p className="text-dark-muted text-sm font-medium mb-1">Base RFM</p>
|
||||||
<h3 className="text-3xl font-bold text-dark-text">{clients.length}</h3>
|
<h3 className="text-3xl font-bold text-dark-text">{clients.length}</h3>
|
||||||
|
<p className="mt-1 text-xs font-semibold text-dark-muted">{totals.periodClientCount} com compra no período</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-dark-card p-6 rounded-2xl border border-dark-border shadow-sm">
|
<div className="bg-dark-card p-6 rounded-2xl border border-dark-border shadow-sm">
|
||||||
<p className="text-dark-muted text-sm font-medium mb-1">Receita no Período</p>
|
<p className="text-dark-muted text-sm font-medium mb-1">Receita no Período</p>
|
||||||
<h3 className="text-3xl font-bold text-dark-text">{formatCurrency(totals.totalRevenue)}</h3>
|
<h3 className="text-3xl font-bold text-dark-text">{formatCurrency(totals.totalRevenue)}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-dark-card p-6 rounded-2xl border border-dark-border shadow-sm">
|
<div className="bg-dark-card p-6 rounded-2xl border border-dark-border shadow-sm">
|
||||||
<p className="text-dark-muted text-sm font-medium mb-1">Segmentos Ativos</p>
|
<p className="text-dark-muted text-sm font-medium mb-1">Segmentos na Base</p>
|
||||||
<h3 className="text-3xl font-bold text-dark-text">{totals.activeSegments}</h3>
|
<h3 className="text-3xl font-bold text-dark-text">{totals.activeSegments}</h3>
|
||||||
<p className="mt-1 text-xs font-semibold text-dark-muted">
|
<p className="mt-1 text-xs font-semibold text-dark-muted">
|
||||||
Principal: {totals.topSegment?.label || 'N/A'}
|
Maior receita no período: {totals.topSegment?.label || 'N/A'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -273,7 +275,7 @@ const Rfm = () => {
|
|||||||
<div className="mb-4 flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
|
<div className="mb-4 flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-bold text-dark-text">Matriz RFM</h2>
|
<h2 className="text-lg font-bold text-dark-text">Matriz RFM</h2>
|
||||||
<p className="text-sm font-medium text-dark-muted">Distribuição dos clientes por recência e valor.</p>
|
<p className="text-sm font-medium text-dark-muted">Base classificada até o fim do período; valores mostram apenas o período selecionado.</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-xs font-semibold text-dark-muted">
|
<div className="flex items-center gap-2 text-xs font-semibold text-dark-muted">
|
||||||
<span>Menor prioridade</span>
|
<span>Menor prioridade</span>
|
||||||
@@ -347,7 +349,7 @@ const Rfm = () => {
|
|||||||
<p className={`text-xl font-bold leading-none ${style.text}`}>{segment?.count || 0}</p>
|
<p className={`text-xl font-bold leading-none ${style.text}`}>{segment?.count || 0}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-[10px] font-bold uppercase tracking-wider text-dark-muted">Receita</p>
|
<p className="text-[10px] font-bold uppercase tracking-wider text-dark-muted">Receita período</p>
|
||||||
<p className="truncate text-xs font-bold text-dark-text">{formatCurrency(segment?.totalRevenue || 0)}</p>
|
<p className="truncate text-xs font-bold text-dark-text">{formatCurrency(segment?.totalRevenue || 0)}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -386,7 +388,7 @@ const Rfm = () => {
|
|||||||
<span className={`text-sm font-bold ${style.text}`}>{segment.count}</span>
|
<span className={`text-sm font-bold ${style.text}`}>{segment.count}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-1 text-xs font-semibold text-dark-muted">
|
<p className="mt-1 text-xs font-semibold text-dark-muted">
|
||||||
{(segmentInsights[segment.key]?.customerPercent || 0).toFixed(1)}% clientes · {formatCurrency(segment.totalRevenue)}
|
{(segmentInsights[segment.key]?.customerPercent || 0).toFixed(1)}% clientes · {formatCurrency(segment.totalRevenue)} no período
|
||||||
</p>
|
</p>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
@@ -413,11 +415,11 @@ const Rfm = () => {
|
|||||||
<p className="mt-1 text-base font-bold text-dark-text">{selectedSegmentInsights.customerPercent.toFixed(1)}%</p>
|
<p className="mt-1 text-base font-bold text-dark-text">{selectedSegmentInsights.customerPercent.toFixed(1)}%</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-xl border border-dark-border bg-black/10 p-3">
|
<div className="rounded-xl border border-dark-border bg-black/10 p-3">
|
||||||
<p className="text-[10px] font-bold uppercase tracking-wider text-dark-muted">Receita</p>
|
<p className="text-[10px] font-bold uppercase tracking-wider text-dark-muted">Receita período</p>
|
||||||
<p className="mt-1 text-base font-bold text-dark-text">{selectedSegmentInsights.revenuePercent.toFixed(1)}%</p>
|
<p className="mt-1 text-base font-bold text-dark-text">{selectedSegmentInsights.revenuePercent.toFixed(1)}%</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-xl border border-dark-border bg-black/10 p-3">
|
<div className="rounded-xl border border-dark-border bg-black/10 p-3">
|
||||||
<p className="text-[10px] font-bold uppercase tracking-wider text-dark-muted">Ticket</p>
|
<p className="text-[10px] font-bold uppercase tracking-wider text-dark-muted">Ticket período</p>
|
||||||
<p className="mt-1 text-base font-bold text-dark-text">{formatCurrency(selectedSegmentInsights.averageTicket)}</p>
|
<p className="mt-1 text-base font-bold text-dark-text">{formatCurrency(selectedSegmentInsights.averageTicket)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-xl border border-dark-border bg-black/10 p-3">
|
<div className="rounded-xl border border-dark-border bg-black/10 p-3">
|
||||||
@@ -483,9 +485,9 @@ const Rfm = () => {
|
|||||||
<th className="px-6 py-4 font-bold uppercase tracking-wider text-[10px]">Segmento</th>
|
<th className="px-6 py-4 font-bold uppercase tracking-wider text-[10px]">Segmento</th>
|
||||||
<th className="px-6 py-4 font-bold uppercase tracking-wider text-[10px]">RFM</th>
|
<th className="px-6 py-4 font-bold uppercase tracking-wider text-[10px]">RFM</th>
|
||||||
<th className="px-6 py-4 font-bold uppercase tracking-wider text-[10px]">Última Compra</th>
|
<th className="px-6 py-4 font-bold uppercase tracking-wider text-[10px]">Última Compra</th>
|
||||||
<th className="px-6 py-4 font-bold uppercase tracking-wider text-[10px]">Pedidos</th>
|
<th className="px-6 py-4 font-bold uppercase tracking-wider text-[10px]">Pedidos no Período</th>
|
||||||
<th className="px-6 py-4 font-bold uppercase tracking-wider text-[10px]">Ticket Médio</th>
|
<th className="px-6 py-4 font-bold uppercase tracking-wider text-[10px]">Ticket no Período</th>
|
||||||
<th className="px-6 py-4 font-bold uppercase tracking-wider text-[10px]">Total Gasto</th>
|
<th className="px-6 py-4 font-bold uppercase tracking-wider text-[10px]">Receita no Período</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-dark-border">
|
<tbody className="divide-y divide-dark-border">
|
||||||
|
|||||||
Reference in New Issue
Block a user