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 = { /**