feat: replace CSS bar charts with recharts, fix label readability
Some checks failed
CI / Check / Test (push) Failing after 6m20s

This commit is contained in:
2026-06-04 17:04:29 +02:00
parent a76386345f
commit ebf9a9f4a8
4 changed files with 60 additions and 58 deletions

View File

@@ -298,6 +298,11 @@ body > #root {
color: oklch(0.985 0 0); color: oklch(0.985 0 0);
} }
/* Chart label readability */
[data-slot="chart"] .recharts-cartesian-axis-tick text {
fill: rgba(255, 255, 255, 0.85) !important;
}
/* Star glow for filled amber stars */ /* Star glow for filled amber stars */
.aero-star-filled { .aero-star-filled {
filter: drop-shadow(0 0 4px var(--aero-primary-glow)) drop-shadow(0 0 1px var(--aero-primary)); filter: drop-shadow(0 0 4px var(--aero-primary-glow)) drop-shadow(0 0 1px var(--aero-primary));

View File

@@ -1,7 +1,9 @@
import { Link } from "@tanstack/react-router" import { Link } from "@tanstack/react-router"
import { useCallback } from "react" import { useCallback } from "react"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { Bar, BarChart, XAxis, YAxis } from "recharts"
import { User } from "lucide-react" import { User } from "lucide-react"
import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from "@/components/ui/chart"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Skeleton } from "@/components/ui/skeleton" import { Skeleton } from "@/components/ui/skeleton"
@@ -136,6 +138,10 @@ function DiaryTab({ sortBy }: { sortBy: string; userId?: string }) {
) )
} }
const trendChartConfig = {
count: { label: "Movies", color: "var(--primary)" },
} satisfies ChartConfig
function TrendsView({ function TrendsView({
data, data,
}: { }: {
@@ -183,17 +189,14 @@ function TrendsView({
<CardTitle className="text-sm">{t("profile.monthlyActivity")}</CardTitle> <CardTitle className="text-sm">{t("profile.monthlyActivity")}</CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{data.trends.monthly_ratings.map((m) => ( <ChartContainer config={trendChartConfig} className="aspect-[2/1] w-full">
<div <BarChart data={data.trends.monthly_ratings} margin={{ top: 8, right: 0, bottom: 0, left: -20 }}>
key={m.month_label} <XAxis dataKey="month_label" tickFormatter={(v: string) => v.slice(0, 3)} tick={{ fontSize: 10, fill: "rgba(255,255,255,0.85)" }} tickLine={false} axisLine={false} />
className="flex items-center justify-between py-1 text-sm" <YAxis allowDecimals={false} tick={{ fontSize: 10, fill: "rgba(255,255,255,0.85)" }} tickLine={false} axisLine={false} width={30} />
> <ChartTooltip content={<ChartTooltipContent />} />
<span>{m.month_label}</span> <Bar dataKey="count" fill="var(--color-count)" radius={[4, 4, 0, 0]} />
<span className="text-xs text-muted-foreground"> </BarChart>
{t("common.filmsAvg", { count: m.count, avg: m.avg_rating.toFixed(1) })} </ChartContainer>
</span>
</div>
))}
</CardContent> </CardContent>
</Card> </Card>
)} )}

View File

@@ -1,26 +1,24 @@
import { Bar, BarChart, XAxis } from "recharts"
import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from "@/components/ui/chart"
const chartConfig = {
count: { label: "Reviews", color: "var(--primary)" },
} satisfies ChartConfig
type RatingHistogramProps = { type RatingHistogramProps = {
histogram: number[] histogram: number[]
} }
export function RatingHistogram({ histogram }: RatingHistogramProps) { export function RatingHistogram({ histogram }: RatingHistogramProps) {
const max = Math.max(...histogram, 1) const data = histogram.map((count, i) => ({ rating: `${i + 1}`, count }))
return ( return (
<div> <ChartContainer config={chartConfig} className="aspect-[3/1] w-full">
<div className="flex items-end gap-1" style={{ height: 40 }}> <BarChart data={data} margin={{ top: 4, right: 0, bottom: 0, left: 0 }}>
{histogram.map((count, i) => ( <XAxis dataKey="rating" tick={{ fontSize: 11, fill: "rgba(255,255,255,0.85)" }} tickLine={false} axisLine={false} />
<div <ChartTooltip content={<ChartTooltipContent hideIndicator />} />
key={i} <Bar dataKey="count" fill="var(--color-count)" radius={[4, 4, 0, 0]} />
className="flex-1 rounded-t bg-amber-500/80" </BarChart>
style={{ height: `${(count / max) * 100}%`, minHeight: count > 0 ? 2 : 0 }} </ChartContainer>
/>
))}
</div>
<div className="mt-1 flex gap-1">
{[1, 2, 3, 4, 5].map((n) => (
<div key={n} className="flex-1 text-center text-[10px] text-muted-foreground/40">{n}</div>
))}
</div>
</div>
) )
} }

View File

@@ -1,10 +1,20 @@
import { createFileRoute, Link } from "@tanstack/react-router" import { createFileRoute, Link } from "@tanstack/react-router"
import { lazy, Suspense, useState } from "react" import { lazy, Suspense, useState } from "react"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { Bar, BarChart, XAxis, YAxis } from "recharts"
import { BarChart3, DollarSign, Globe, Hash, Share2, Star, Users } from "lucide-react" import { BarChart3, DollarSign, Globe, Hash, Share2, Star, Users } from "lucide-react"
import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from "@/components/ui/chart"
import { BackButton } from "@/components/back-button" import { BackButton } from "@/components/back-button"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
const monthlyChartConfig = {
count: { label: "Movies", color: "var(--primary)" },
} satisfies ChartConfig
const genreChartConfig = {
count: { label: "Movies", color: "var(--primary)" },
} satisfies ChartConfig
const WrapUpShareCard = lazy(() => import("@/components/wrapup-share-card").then((m) => ({ default: m.WrapUpShareCard }))) const WrapUpShareCard = lazy(() => import("@/components/wrapup-share-card").then((m) => ({ default: m.WrapUpShareCard })))
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
@@ -111,18 +121,14 @@ function WrapUpReportPage() {
<CardDescription>{t("wrapup.genresExplored", { count: report.genre_diversity })}</CardDescription> <CardDescription>{t("wrapup.genresExplored", { count: report.genre_diversity })}</CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-2"> <CardContent className="space-y-2">
{report.top_genres.slice(0, 8).map((g) => { <ChartContainer config={genreChartConfig} className="w-full" style={{ height: Math.min(report.top_genres.length, 8) * 28 + 16 }}>
const max = report.top_genres[0]?.count ?? 1 <BarChart data={report.top_genres.slice(0, 8)} layout="vertical" margin={{ top: 0, right: 4, bottom: 0, left: 0 }}>
return ( <XAxis type="number" hide />
<div key={g.genre} className="flex items-center gap-2 text-sm"> <YAxis type="category" dataKey="genre" tick={{ fontSize: 11, fill: "rgba(255,255,255,0.85)" }} tickLine={false} axisLine={false} width={80} />
<span className="w-20 truncate">{g.genre}</span> <ChartTooltip content={<ChartTooltipContent />} />
<div className="h-2 flex-1 overflow-hidden rounded-full bg-muted"> <Bar dataKey="count" fill="var(--color-count)" radius={[0, 4, 4, 0]} />
<div className="h-full rounded-full bg-primary" style={{ width: `${(g.count / max) * 100}%` }} /> </BarChart>
</div> </ChartContainer>
<span className="w-6 text-right text-xs text-muted-foreground">{g.count}</span>
</div>
)
})}
<div className="flex flex-wrap gap-2 pt-2"> <div className="flex flex-wrap gap-2 pt-2">
{report.highest_rated_genre && ( {report.highest_rated_genre && (
<Badge variant="secondary">{t("wrapup.highestRated", { genre: report.highest_rated_genre })}</Badge> <Badge variant="secondary">{t("wrapup.highestRated", { genre: report.highest_rated_genre })}</Badge>
@@ -144,24 +150,14 @@ function WrapUpReportPage() {
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{(() => { <ChartContainer config={monthlyChartConfig} className="aspect-[2/1] w-full">
const max = Math.max(...report.movies_per_month.map((x) => x.count)) <BarChart data={report.movies_per_month} margin={{ top: 8, right: 0, bottom: 0, left: -20 }}>
const barHeight = 96 <XAxis dataKey="year_month" tickFormatter={(v: string) => v.slice(5)} tick={{ fontSize: 10, fill: "rgba(255,255,255,0.85)" }} tickLine={false} axisLine={false} />
return ( <YAxis allowDecimals={false} tick={{ fontSize: 10, fill: "rgba(255,255,255,0.85)" }} tickLine={false} axisLine={false} width={30} />
<div className="flex items-end gap-1"> <ChartTooltip content={<ChartTooltipContent labelFormatter={(v) => report.movies_per_month.find((m) => m.year_month === String(v))?.label ?? String(v)} />} />
{report.movies_per_month.map((m) => { <Bar dataKey="count" fill="var(--color-count)" radius={[4, 4, 0, 0]} />
const h = max > 0 ? Math.max((m.count / max) * barHeight, 4) : 4 </BarChart>
return ( </ChartContainer>
<div key={m.year_month} className="flex flex-1 flex-col items-center gap-1">
<span className="text-[10px] text-muted-foreground">{m.count}</span>
<div className="w-full rounded-t bg-primary" style={{ height: h }} />
<span className="text-[9px] text-muted-foreground">{m.year_month.slice(5)}</span>
</div>
)
})}
</div>
)
})()}
</CardContent> </CardContent>
</Card> </Card>
)} )}