Highlight client purchase patterns
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 44s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 44s
This commit is contained in:
@@ -29,6 +29,22 @@ type CustomTooltipProps = {
|
|||||||
label?: string;
|
label?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type PatternPoint = {
|
||||||
|
label: string;
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTopPatternPoint = (points: PatternPoint[]) => (
|
||||||
|
points.reduce<PatternPoint | null>((topPoint, point) => {
|
||||||
|
if (!topPoint || point.value > topPoint.value) return point;
|
||||||
|
return topPoint;
|
||||||
|
}, null)
|
||||||
|
);
|
||||||
|
|
||||||
|
const getPatternShare = (value: number, total: number) => (
|
||||||
|
total > 0 ? Math.round((value / total) * 100) : 0
|
||||||
|
);
|
||||||
|
|
||||||
const CustomTooltip = ({ active, payload, label }: CustomTooltipProps) => {
|
const CustomTooltip = ({ active, payload, label }: CustomTooltipProps) => {
|
||||||
if (active && payload && payload.length) {
|
if (active && payload && payload.length) {
|
||||||
return (
|
return (
|
||||||
@@ -248,6 +264,10 @@ const ClientDetails = () => {
|
|||||||
const paginatedOrders = groupedOrders.slice(startIndex, startIndex + ordersPerPage);
|
const paginatedOrders = groupedOrders.slice(startIndex, startIndex + ordersPerPage);
|
||||||
const hasWeekdayPattern = purchaseWeekdays.some(day => day.value > 0);
|
const hasWeekdayPattern = purchaseWeekdays.some(day => day.value > 0);
|
||||||
const hasHourPattern = purchaseHours.some(hour => hour.value > 0);
|
const hasHourPattern = purchaseHours.some(hour => hour.value > 0);
|
||||||
|
const weekdayPatternTotal = purchaseWeekdays.reduce((total, day) => total + day.value, 0);
|
||||||
|
const hourPatternTotal = purchaseHours.reduce((total, hour) => total + hour.value, 0);
|
||||||
|
const topWeekday = getTopPatternPoint(purchaseWeekdays);
|
||||||
|
const topHour = getTopPatternPoint(purchaseHours);
|
||||||
const isRefreshing = isLoading && Boolean(details);
|
const isRefreshing = isLoading && Boolean(details);
|
||||||
const isSingleDayRange = formatDateKey(dateRange.start) === formatDateKey(dateRange.end);
|
const isSingleDayRange = formatDateKey(dateRange.start) === formatDateKey(dateRange.end);
|
||||||
|
|
||||||
@@ -390,14 +410,26 @@ const ClientDetails = () => {
|
|||||||
|
|
||||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||||
<div className="bg-white dark:bg-dark-card border border-zinc-200 dark:border-dark-border rounded-2xl p-6 shadow-sm">
|
<div className="bg-white dark:bg-dark-card border border-zinc-200 dark:border-dark-border rounded-2xl p-6 shadow-sm">
|
||||||
<div className="mb-5 flex items-start justify-between gap-4">
|
<div className="mb-4 flex items-start justify-between gap-4">
|
||||||
|
<div>
|
||||||
<h4 className="text-sm font-bold uppercase tracking-widest text-zinc-500 dark:text-dark-muted">Compras por Dia</h4>
|
<h4 className="text-sm font-bold uppercase tracking-widest text-zinc-500 dark:text-dark-muted">Compras por Dia</h4>
|
||||||
<span className="shrink-0 rounded-full border border-zinc-200 bg-zinc-50 px-2.5 py-1 text-[10px] font-bold uppercase tracking-wide text-zinc-500 dark:border-dark-border dark:bg-dark-surface dark:text-dark-muted">
|
{hasWeekdayPattern && topWeekday && (
|
||||||
|
<p className="mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs font-semibold text-zinc-600 dark:text-dark-muted">
|
||||||
|
<span className="h-2 w-2 rounded-full" style={{ backgroundColor: WEEKDAY_BAR_COLOR }} />
|
||||||
|
<span>Dia mais forte: <span className="text-zinc-900 dark:text-dark-text">{topWeekday.label}</span></span>
|
||||||
|
<span className="text-zinc-400 dark:text-dark-border">·</span>
|
||||||
|
<span>{topWeekday.value} {topWeekday.value === 1 ? 'pedido' : 'pedidos'}</span>
|
||||||
|
<span className="text-zinc-400 dark:text-dark-border">·</span>
|
||||||
|
<span>{getPatternShare(topWeekday.value, weekdayPatternTotal)}%</span>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span className="shrink-0 rounded-full border border-zinc-200 bg-zinc-100 px-2.5 py-1 text-[10px] font-bold uppercase tracking-wide text-zinc-600 dark:border-dark-border dark:bg-white/5 dark:text-dark-muted">
|
||||||
{purchaseWeekdayRangeLabel}
|
{purchaseWeekdayRangeLabel}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{hasWeekdayPattern ? (
|
{hasWeekdayPattern ? (
|
||||||
<div className="h-56">
|
<div className="h-52">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<BarChart data={purchaseWeekdays} margin={{ top: 8, right: 10, left: -18, bottom: 0 }}>
|
<BarChart data={purchaseWeekdays} margin={{ top: 8, right: 10, left: -18, bottom: 0 }}>
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke={CHART_GRID_COLOR} vertical={false} />
|
<CartesianGrid strokeDasharray="3 3" stroke={CHART_GRID_COLOR} vertical={false} />
|
||||||
@@ -420,14 +452,26 @@ const ClientDetails = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white dark:bg-dark-card border border-zinc-200 dark:border-dark-border rounded-2xl p-6 shadow-sm">
|
<div className="bg-white dark:bg-dark-card border border-zinc-200 dark:border-dark-border rounded-2xl p-6 shadow-sm">
|
||||||
<div className="mb-5 flex items-start justify-between gap-4">
|
<div className="mb-4 flex items-start justify-between gap-4">
|
||||||
|
<div>
|
||||||
<h4 className="text-sm font-bold uppercase tracking-widest text-zinc-500 dark:text-dark-muted">Compras por Horário</h4>
|
<h4 className="text-sm font-bold uppercase tracking-widest text-zinc-500 dark:text-dark-muted">Compras por Horário</h4>
|
||||||
<span className="shrink-0 rounded-full border border-zinc-200 bg-zinc-50 px-2.5 py-1 text-[10px] font-bold uppercase tracking-wide text-zinc-500 dark:border-dark-border dark:bg-dark-surface dark:text-dark-muted">
|
{hasHourPattern && topHour && (
|
||||||
|
<p className="mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs font-semibold text-zinc-600 dark:text-dark-muted">
|
||||||
|
<span className="h-2 w-2 rounded-full" style={{ backgroundColor: HOUR_BAR_COLOR }} />
|
||||||
|
<span>Horário mais forte: <span className="text-zinc-900 dark:text-dark-text">{topHour.label}</span></span>
|
||||||
|
<span className="text-zinc-400 dark:text-dark-border">·</span>
|
||||||
|
<span>{topHour.value} {topHour.value === 1 ? 'pedido' : 'pedidos'}</span>
|
||||||
|
<span className="text-zinc-400 dark:text-dark-border">·</span>
|
||||||
|
<span>{getPatternShare(topHour.value, hourPatternTotal)}%</span>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span className="shrink-0 rounded-full border border-zinc-200 bg-zinc-100 px-2.5 py-1 text-[10px] font-bold uppercase tracking-wide text-zinc-600 dark:border-dark-border dark:bg-white/5 dark:text-dark-muted">
|
||||||
{purchaseHourRangeLabel}
|
{purchaseHourRangeLabel}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{hasHourPattern ? (
|
{hasHourPattern ? (
|
||||||
<div className="h-56">
|
<div className="h-52">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<BarChart data={purchaseHours} margin={{ top: 8, right: 10, left: -18, bottom: 0 }}>
|
<BarChart data={purchaseHours} margin={{ top: 8, right: 10, left: -18, bottom: 0 }}>
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke={CHART_GRID_COLOR} vertical={false} />
|
<CartesianGrid strokeDasharray="3 3" stroke={CHART_GRID_COLOR} vertical={false} />
|
||||||
|
|||||||
Reference in New Issue
Block a user