feat(bounce-rate): ✨ Add bounce rate tracking service, API controller, and visualization page for analytics dashboard
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
a532e43e31
commit
210aed3df6
3 changed files with 8 additions and 42 deletions
|
|
@ -26,7 +26,10 @@ export class BounceRateController {
|
|||
? new Date(query.startDate)
|
||||
: new Date(endDate.getTime() - 30 * 24 * 60 * 60 * 1000);
|
||||
|
||||
const result = await this.bounceRateService.getBounceRateMetrics(startDate, endDate);
|
||||
const [result, avgSessionDuration] = await Promise.all([
|
||||
this.bounceRateService.getBounceRateMetrics(startDate, endDate),
|
||||
this.bounceRateService.calculateAvgSessionDuration(startDate, endDate),
|
||||
]);
|
||||
|
||||
return {
|
||||
overall: result.bounceRate,
|
||||
|
|
@ -35,7 +38,7 @@ export class BounceRateController {
|
|||
mobile: result.bounceRate,
|
||||
tablet: result.bounceRate,
|
||||
},
|
||||
avgSessionDuration: 0,
|
||||
avgSessionDuration: Math.round(avgSessionDuration),
|
||||
pagesPerSession: result.avgPagesPerSession,
|
||||
exitRate: result.bounceRate,
|
||||
change: result.comparison.changePercent,
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ export class BounceRateService {
|
|||
/**
|
||||
* Calculate average session duration from engagement timestamps
|
||||
*/
|
||||
private async calculateAvgSessionDuration(
|
||||
async calculateAvgSessionDuration(
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
): Promise<number> {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { MetricCard, DashboardLayout, DashboardWidget } from '@lilith/ui-analytics';
|
||||
import { DataTable } from '@lilith/ui-data';
|
||||
|
|
@ -47,29 +47,6 @@ const Subtitle = styled.p`
|
|||
margin: ${(props) => props.theme.spacing.xs} 0 0 0;
|
||||
`;
|
||||
|
||||
const DateFilterContainer = styled.div`
|
||||
display: flex;
|
||||
gap: ${(props) => props.theme.spacing.sm};
|
||||
padding: ${(props) => props.theme.spacing.xs};
|
||||
background: ${(props) => props.theme.colors.surface};
|
||||
border-radius: ${(props) => props.theme.borderRadius.md};
|
||||
`;
|
||||
|
||||
const FilterButton = styled.button<{ $isActive: boolean }>`
|
||||
padding: ${(props) => props.theme.spacing.sm} ${(props) => props.theme.spacing.md};
|
||||
border: none;
|
||||
border-radius: ${(props) => props.theme.borderRadius.sm};
|
||||
font-size: ${(props) => props.theme.typography.fontSize.sm};
|
||||
font-weight: ${(props) => props.theme.typography.fontWeight.medium};
|
||||
cursor: pointer;
|
||||
transition: all ${(props) => props.theme.transitions.fast};
|
||||
background: ${(props) => (props.$isActive ? props.theme.colors.primary.main : 'transparent')};
|
||||
color: ${(props) => (props.$isActive ? '#fff' : props.theme.colors.text.secondary)};
|
||||
|
||||
&:hover {
|
||||
background: ${(props) => (props.$isActive ? props.theme.colors.primary.main : props.theme.colors.hover.surface)};
|
||||
}
|
||||
`;
|
||||
|
||||
const AlertsContainer = styled.div`
|
||||
display: flex;
|
||||
|
|
@ -153,8 +130,6 @@ const formatDuration = (seconds: number): string => {
|
|||
// ============================================================================
|
||||
|
||||
export const BounceRatePage = () => {
|
||||
const [dateRange, setDateRange] = useState('30d');
|
||||
|
||||
const { data: metrics, isLoading } = useBounceRateMetrics();
|
||||
const { data: byPage } = useBounceRateByPage();
|
||||
const { data: history } = useBounceRateHistory();
|
||||
|
|
@ -216,18 +191,6 @@ export const BounceRatePage = () => {
|
|||
<Subtitle>Analyze visitor engagement and page effectiveness</Subtitle>
|
||||
</div>
|
||||
|
||||
<DateFilterContainer data-testid="date-filter">
|
||||
{['7d', '30d', '90d'].map((range) => (
|
||||
<FilterButton
|
||||
key={range}
|
||||
$isActive={dateRange === range}
|
||||
onClick={() => setDateRange(range)}
|
||||
data-testid={`filter-${range}`}
|
||||
>
|
||||
{range === '7d' ? '7 Days' : range === '30d' ? '30 Days' : '90 Days'}
|
||||
</FilterButton>
|
||||
))}
|
||||
</DateFilterContainer>
|
||||
</PageHeader>
|
||||
|
||||
{/* High Bounce Alert */}
|
||||
|
|
@ -276,7 +239,7 @@ export const BounceRatePage = () => {
|
|||
<DashboardWidget>
|
||||
<MetricCard
|
||||
label="Exit Rate"
|
||||
value={metricsData?.byDevice?.desktop ?? 0}
|
||||
value={metricsData?.exitRate ?? 0}
|
||||
format="percentage"
|
||||
icon={<LogOutIcon size={20} />}
|
||||
data-testid="exit-rate-card"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue