diff --git a/browser-extension/src/entrypoints/posts/Home/HomePage.tsx b/browser-extension/src/entrypoints/posts/Home/HomePage.tsx
index 47edbb0..dbdd2da 100644
--- a/browser-extension/src/entrypoints/posts/Home/HomePage.tsx
+++ b/browser-extension/src/entrypoints/posts/Home/HomePage.tsx
@@ -65,7 +65,10 @@ function HomePage() {
/>
>
)}
diff --git a/browser-extension/src/entrypoints/posts/Posts/PostDetailPage.tsx b/browser-extension/src/entrypoints/posts/Posts/PostDetailPage.tsx
index baae6d9..c6b924d 100644
--- a/browser-extension/src/entrypoints/posts/Posts/PostDetailPage.tsx
+++ b/browser-extension/src/entrypoints/posts/Posts/PostDetailPage.tsx
@@ -110,7 +110,10 @@ function PostDetailPage() {
postComments={post.comments}
isLoading={isLoading}
/>
-
+
diff --git a/browser-extension/src/entrypoints/posts/Report/ReportContent.tsx b/browser-extension/src/entrypoints/posts/Report/ReportContent.tsx
index a3482a3..b3916a9 100644
--- a/browser-extension/src/entrypoints/posts/Report/ReportContent.tsx
+++ b/browser-extension/src/entrypoints/posts/Report/ReportContent.tsx
@@ -87,7 +87,10 @@ export const ReportContent = ({
postComments={reportQueryData?.postCommentList ?? []}
isLoading={isLoadingPosts}
/>
-
+ p.comments) ?? []}
+ isLoading={isLoadingPosts}
+ />
{groupedCommentsByPost.map(([postKey, commentList], index) => {
const post = posts?.find(
diff --git a/browser-extension/src/entrypoints/posts/Shared/CategoryDistribution.tsx b/browser-extension/src/entrypoints/posts/Shared/CategoryDistribution.tsx
index 6081800..4ebd828 100644
--- a/browser-extension/src/entrypoints/posts/Shared/CategoryDistribution.tsx
+++ b/browser-extension/src/entrypoints/posts/Shared/CategoryDistribution.tsx
@@ -1,20 +1,152 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
-import WorkInProgress from "../WorkInProgress";
+import { Spinner } from "@/components/ui/spinner";
+import { PostComment } from "@/shared/model/post/Post";
+import {
+ ChartConfig,
+ ChartContainer,
+ ChartTooltip,
+ ChartTooltipContent,
+} from "@/components/ui/chart";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+import { Pie, PieChart } from "recharts";
+import { Info } from "lucide-react";
+import { getCategoryStats } from "@/shared/utils/report-stats";
+
+const CATEGORY_COLORS = [
+ "oklch(from var(--primary) 0.76 c h)",
+ "oklch(from var(--primary) 0.68 c h)",
+ "oklch(from var(--primary) 0.60 c h)",
+ "var(--primary)",
+ "oklch(from var(--primary) 0.41 c h)",
+ "oklch(from var(--primary) 0.33 c h)",
+ "oklch(from var(--primary) 0.25 c h)",
+];
+
+type CategoryDistributionProps = {
+ postComments: PostComment[];
+ isLoading: boolean;
+};
+
+type ChartDataPoint = {
+ name: string;
+ value: number;
+ fill: string;
+};
+
+function CategoryDistribution({
+ postComments,
+ isLoading,
+}: Readonly) {
+ const categoryStats = getCategoryStats(postComments);
+ const total = categoryStats.reduce((sum, s) => sum + s.count, 0);
+
+ const dataPoints: ChartDataPoint[] = categoryStats.map((s, i) => ({
+ name: s.label,
+ value: s.count,
+ fill: CATEGORY_COLORS[i % CATEGORY_COLORS.length],
+ }));
+
+ const chartConfig = dataPoints.reduce((acc, d) => {
+ acc[d.name] = { label: d.name, color: d.fill };
+ return acc;
+ }, {}) satisfies ChartConfig;
-function CategoryDistribution() {
return (
-
+
-
+
Répartition par catégories
+
+
+
+
+
+ Répartition des commentaires par type d'infraction. Voir le
+ détail des catégories dans Aide et ressources.
+
+
-
-
-
- Graphique circulaire présentant la répartition des commentaires par
- catégorie.
-
+
+ {isLoading && }
+
+ {!isLoading && dataPoints.length === 0 && (
+
+ Aucun commentaire malveillant.
+
+ )}
+
+ {!isLoading && dataPoints.length > 0 && (
+
+
+
+
+ (
+ <>
+
+ ).fill as string,
+ }}
+ />
+
+
+ Commentaires malveillants
+
+
+ {total > 0
+ ? `${((Number(value) / total) * 100).toLocaleString("fr-FR", { minimumFractionDigits: 1, maximumFractionDigits: 1 })} %`
+ : "0 %"}
+
+
+ >
+ )}
+ />
+ }
+ />
+
+
+
+
+ {dataPoints.map((d) => (
+ -
+
+ {d.name}
+
+ {((d.value / total) * 100).toLocaleString("fr-FR", {
+ minimumFractionDigits: 1,
+ maximumFractionDigits: 1,
+ })}{" "}
+ %
+
+
+ ))}
+
+
+ )}
);
diff --git a/browser-extension/src/shared/utils/report-stats.ts b/browser-extension/src/shared/utils/report-stats.ts
index 14b9818..c8b293c 100644
--- a/browser-extension/src/shared/utils/report-stats.ts
+++ b/browser-extension/src/shared/utils/report-stats.ts
@@ -1,6 +1,38 @@
import { getPercentage } from "@/shared/utils/maths";
import { PostComment } from "@/shared/model/post/Post";
import { isCommentHateful } from "@/shared/utils/post-util";
+import {
+ HatefulCategory,
+ HatefulCategoryLabels,
+} from "@/shared/model/HatefulCategory";
+
+export type CategoryStat = {
+ label: string;
+ count: number;
+};
+
+const HATEFUL_CATEGORIES = Object.values(HatefulCategory).filter(
+ (c) => c !== HatefulCategory.ABSENCE_DE_CYBERHARCELEMENT,
+);
+
+const ABSENCE_LABEL =
+ HatefulCategoryLabels[HatefulCategory.ABSENCE_DE_CYBERHARCELEMENT];
+
+export const getCategoryStats = (
+ postComments: readonly PostComment[],
+): CategoryStat[] => {
+ const hatefulComments = postComments.filter(
+ (c) => c.classification?.[0] && c.classification[0] !== ABSENCE_LABEL,
+ );
+ return HATEFUL_CATEGORIES.map((cat) => ({
+ label: HatefulCategoryLabels[cat],
+ count: hatefulComments.filter(
+ (c) => c.classification?.[0] === HatefulCategoryLabels[cat],
+ ).length,
+ }))
+ .filter((s) => s.count > 0)
+ .sort((a, b) => b.count - a.count);
+};
export type HatefulAuthorStats = {
/**