Component properties
Props for the OverviewTab component
Whether statistics data is currently being loaded
Raw statistics data object or null if unavailable
Processed summary of key statistics for display
React component with statistics overview dashboard
export function OverviewTab({
loading,
statistics,
statsSummary,
}: OverviewTabProps) {
if (loading) {
return (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{Array(9)
.fill(0)
.map((_, i) => (
<Card
key={i}
className="border-border/40 overflow-hidden transition-all duration-200"
>
<CardHeader className="pb-2">
<Skeleton className="h-4 w-28" />
</CardHeader>
<CardContent className="pt-4">
<Skeleton className="h-9 w-24" />
<Skeleton className="mt-2 h-4 w-36" />
</CardContent>
</Card>
))}
</div>
);
}
if (!statistics) {
return (
<NoDataMessage message="No statistics data available yet. Keep listening to music to generate insights!" />
);
}
/**
* Determines progress bar color based on skip rate value
*
* Maps skip rate values to appropriate colors for visual feedback:
* - Low values (< 30%): Green to indicate healthy listening
* - Medium values (30-50%): Amber to indicate moderate skipping
* - High values (> 50%): Red to indicate frequent skipping
*
* @param value - Skip rate as a decimal (0-1)
* @returns CSS class string for the progress bar color
*/
const getProgressColor = (value: number) => {
if (value < 0.3) return "bg-emerald-500";
if (value < 0.5) return "bg-amber-500";
return "bg-rose-500";
};
/**
* Determines text color for repeat listening rate
*
* Maps repeat listening rate values to appropriate colors:
* - Low values (< 15%): Default muted color
* - Medium values (15-30%): Amber to indicate moderate repetition
* - High values (> 30%): Green to indicate significant repetition
*
* @param value - Repeat rate as a decimal (0-1)
* @returns CSS class string for the text color
*/
const getRepeatRateColor = (value: number) => {
if (value < 0.15) return "text-muted-foreground";
if (value < 0.3) return "text-amber-500";
return "text-emerald-500";
};
/**
* Determines text color for artist discovery rate
*
* Maps discovery rate percentage to appropriate colors:
* - Low values (< 10%): Default muted color
* - Medium values (10-25%): Amber to indicate moderate discovery
* - High values (> 25%): Green to indicate significant discovery
*
* @param value - Discovery rate as a percentage string
* @returns CSS class string for the text color
*/
const getDiscoveryColor = (value: string) => {
const numValue = parseFloat(value);
if (numValue < 10) return "text-muted-foreground";
if (numValue < 25) return "text-amber-500";
return "text-emerald-500";
};
return (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{/* Listening Time Card */}
<Card className="border-primary/20 hover:border-primary/30 group overflow-hidden transition-all duration-200 hover:shadow-md">
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-sm font-medium">
<Clock className="text-primary h-4 w-4" />
Total Listening Time
</CardTitle>
</CardHeader>
<CardContent className="pt-4">
<div className="text-3xl font-bold tracking-tight">
{statsSummary?.totalListeningTime}
</div>
<p className="text-muted-foreground mt-1 flex items-center gap-1.5 text-xs">
<Music className="h-3.5 w-3.5" />
{statistics.totalUniqueTracks} unique tracks played
</p>
</CardContent>
</Card>
{/* Skip Rate Card */}
<Card className="group overflow-hidden transition-all duration-200 hover:border-rose-200 hover:shadow-md">
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-sm font-medium">
<SkipForward className="h-4 w-4 text-rose-500" />
Skip Rate
</CardTitle>
</CardHeader>
<CardContent className="pt-4">
<div className="text-3xl font-bold tracking-tight">
{statsSummary?.skipRate}
</div>
<Progress
value={
statsSummary?.skipRateValue ? statsSummary.skipRateValue * 100 : 0
}
className={`mt-2 h-2 ${getProgressColor(statsSummary?.skipRateValue || 0)}`}
/>
</CardContent>
</Card>
{/* Artists Card */}
<Card className="group overflow-hidden transition-all duration-200 hover:border-blue-200 hover:shadow-md">
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-sm font-medium">
<User className="h-4 w-4 text-blue-500" />
Artists
</CardTitle>
</CardHeader>
<CardContent className="pt-4">
<div className="text-3xl font-bold tracking-tight">
{statistics.totalUniqueArtists}
</div>
<p className="text-muted-foreground mt-1 flex items-center gap-1.5 text-xs">
<Sparkles className="h-3.5 w-3.5" />
Discovery rate:{" "}
<span
className={getDiscoveryColor(statsSummary?.discoveryRate || "0%")}
>
{statsSummary?.discoveryRate}
</span>
</p>
</CardContent>
</Card>
{/* Most Active Day Card */}
<Card className="group overflow-hidden transition-all duration-200 hover:border-violet-200 hover:shadow-md">
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-sm font-medium">
<Calendar className="h-4 w-4 text-violet-500" />
Most Active Day
</CardTitle>
</CardHeader>
<CardContent className="pt-4">
<div className="text-3xl font-bold tracking-tight">
{statsSummary?.mostActiveDay}
</div>
<p className="text-muted-foreground mt-1 flex items-center gap-1.5 text-xs">
<Clock className="h-3.5 w-3.5" />
Peak hour: {statsSummary?.peakListeningHour}
</p>
</CardContent>
</Card>
{/* Today's Listening Card */}
<Card className="group overflow-hidden transition-all duration-200 hover:border-emerald-200 hover:shadow-md">
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-sm font-medium">
<PlayCircle className="h-4 w-4 text-emerald-500" />
Today's Listening
</CardTitle>
</CardHeader>
<CardContent className="pt-4">
<div className="text-3xl font-bold tracking-tight">
{statsSummary?.recentListeningTime}
</div>
<p className="text-muted-foreground mt-1 flex items-center gap-1.5 text-xs">
<TrendingUp className="h-3.5 w-3.5" />
{statsSummary?.recentTracksCount} tracks (
{statsSummary?.recentSkipCount} skipped)
</p>
</CardContent>
</Card>
{/* Tracks Card */}
<Card className="group overflow-hidden transition-all duration-200 hover:border-amber-200 hover:shadow-md">
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-sm font-medium">
<Music className="h-4 w-4 text-amber-500" />
Tracks
</CardTitle>
</CardHeader>
<CardContent className="pt-4">
<div className="text-3xl font-bold tracking-tight">
{statistics.totalUniqueTracks}
</div>
<p className="text-muted-foreground mt-1 flex items-center gap-1.5 text-xs">
<User className="h-3.5 w-3.5" />
{Object.keys(statistics.artistMetrics).length} artists tracked
</p>
</CardContent>
</Card>
{/* Repeat Rate Card */}
<Card className="group overflow-hidden transition-all duration-200 hover:border-cyan-200 hover:shadow-md">
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-sm font-medium">
<Repeat className="h-4 w-4 text-cyan-500" />
Repeat Rate
</CardTitle>
</CardHeader>
<CardContent className="pt-4">
<div
className={`text-3xl font-bold tracking-tight ${getRepeatRateColor(statistics.repeatListeningRate || 0)}`}
>
{formatPercent(statistics.repeatListeningRate || 0)}
</div>
<p className="text-muted-foreground mt-1 text-xs">
Tracks repeated within sessions
</p>
</CardContent>
</Card>
{/* Device Usage Card */}
<Card className="group overflow-hidden transition-all duration-200 hover:border-indigo-200 hover:shadow-md">
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-sm font-medium">
<Laptop className="h-4 w-4 text-indigo-500" />
Device Usage
</CardTitle>
</CardHeader>
<CardContent className="pt-4">
<div className="text-3xl font-bold tracking-tight">
{Object.keys(statistics.deviceMetrics || {}).length}
</div>
<p className="text-muted-foreground mt-1 text-xs">Devices tracked</p>
</CardContent>
</Card>
</div>
);
}
Statistics overview dashboard with key metrics
Renders a grid of metric cards displaying key statistics about the user's listening habits. Each card focuses on a specific aspect of listening behavior, such as skip rate, artist diversity, or listening duration.
The component handles multiple states:
Visual elements include color-coded indicators, progress bars, and iconography to help users quickly interpret the data and identify patterns in their listening behavior.