feat(analytics): add AI stats, attachment stats, and moderation actions queries to useAnalytics hook

This commit is contained in:
MythEclipse
2026-06-03 18:17:25 +07:00
parent f1bc36d6d8
commit caa9ecca63
2 changed files with 61 additions and 20 deletions
@@ -27,7 +27,7 @@ export function ActivityChart({ hourly, loading }: ActivityChartProps) {
return {
hour: `${String(jakartaHour).padStart(2, "0")}:00`,
clean: b.clean,
warned: 0,
warned: b.warned,
flagged: b.flagged,
error: b.error,
total: b.count,
@@ -46,8 +46,9 @@ export function ActivityChart({ hourly, loading }: ActivityChartProps) {
</CardHeader>
<CardContent>
<div className="space-y-3">
<div className="grid grid-cols-3 gap-2 text-[10px] uppercase tracking-wider text-muted-foreground">
<div className="grid grid-cols-4 gap-2 text-[10px] uppercase tracking-wider text-muted-foreground">
<span>Clean</span>
<span>Warned</span>
<span>Flagged</span>
<span>Error</span>
</div>
@@ -55,6 +56,7 @@ export function ActivityChart({ hourly, loading }: ActivityChartProps) {
{data.map((bucket) => {
const total = Math.max(bucket.total, 1);
const clean = bucket.clean / total;
const warned = bucket.warned / total;
const flagged = bucket.flagged / total;
const error = bucket.error / total;
return (
@@ -70,9 +72,13 @@ export function ActivityChart({ hourly, loading }: ActivityChartProps) {
</div>
<div className="flex h-3 overflow-hidden rounded-full bg-sky-50">
<div
className="bg-gradient-to-r from-primary to-pink-300"
className="bg-gradient-to-r from-primary to-teal-300"
style={{ width: `${clean * 100}%` }}
/>
<div
className="bg-yellow-400/60"
style={{ width: `${warned * 100}%` }}
/>
<div
className="bg-accent/70"
style={{ width: `${flagged * 100}%` }}
@@ -1,17 +1,23 @@
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { useCallback, useEffect } from "react";
import type {
AIStats,
AnalyticsOverview,
AttachmentStats,
HeatmapCell,
HourlyBucket,
ModerationActionRecord,
TopicTrend,
TrendBucket,
UserStat,
ViolatorStat,
} from "../../../shared/api/client";
import {
fetchAIStats,
fetchAnalyticsOverview,
fetchAttachmentStats,
fetchHeatmap,
fetchModerationActions,
fetchTrend,
fetchViolators,
} from "../../../shared/api/client";
@@ -21,23 +27,15 @@ function analyticsKeys(
channelId: string | undefined,
hours: number,
) {
const base = [guildId, channelId ?? "", hours] as const;
return {
overview: [
"analytics",
"overview",
guildId,
channelId ?? "",
hours,
] as const,
violators: [
"analytics",
"violators",
guildId,
channelId ?? "",
hours,
] as const,
trend: ["analytics", "trend", guildId, channelId ?? "", hours] as const,
heatmap: ["analytics", "heatmap", guildId, channelId ?? "", hours] as const,
overview: ["analytics", "overview", ...base] as const,
violators: ["analytics", "violators", ...base] as const,
trend: ["analytics", "trend", ...base] as const,
heatmap: ["analytics", "heatmap", ...base] as const,
aiStats: ["analytics", "ai-stats", ...base] as const,
attachmentStats: ["analytics", "attachment-stats", ...base] as const,
moderationActions: ["analytics", "moderation-actions", ...base] as const,
};
}
@@ -86,6 +84,30 @@ export function useAnalytics({
placeholderData: keepPreviousData,
});
const aiStatsQuery = useQuery({
queryKey: keys.aiStats,
queryFn: () => fetchAIStats({ guildId, channelId, hours }),
enabled: !!guildId,
staleTime: 30_000,
placeholderData: keepPreviousData,
});
const attachmentStatsQuery = useQuery({
queryKey: keys.attachmentStats,
queryFn: () => fetchAttachmentStats({ guildId, channelId, hours }),
enabled: !!guildId,
staleTime: 30_000,
placeholderData: keepPreviousData,
});
const moderationActionsQuery = useQuery({
queryKey: keys.moderationActions,
queryFn: () => fetchModerationActions({ guildId, channelId, hours, limit: 50 }),
enabled: !!guildId,
staleTime: 30_000,
placeholderData: keepPreviousData,
});
const refresh = useCallback(() => {
if (!guildId) return;
window.dispatchEvent(new CustomEvent("analytics_refresh"));
@@ -94,7 +116,6 @@ export function useAnalytics({
useEffect(() => {
const handler = () => {
if (!guildId) return;
// Use queryClient.invalidateQueries from the React Query internals
window.dispatchEvent(new CustomEvent("analytics_force_refresh"));
};
window.addEventListener("analytics_refresh", handler);
@@ -128,6 +149,17 @@ export function useAnalytics({
heatmapLoading: heatmapQuery.isLoading && !heatmapQuery.data,
heatmapFetching: heatmapQuery.isFetching && !heatmapQuery.isLoading,
aiStats: aiStatsQuery.data ?? null,
aiStatsLoading: aiStatsQuery.isLoading && !aiStatsQuery.data,
attachmentStats: attachmentStatsQuery.data ?? null,
attachmentStatsLoading:
attachmentStatsQuery.isLoading && !attachmentStatsQuery.data,
moderationActions: moderationActionsQuery.data ?? [],
moderationActionsLoading:
moderationActionsQuery.isLoading && !moderationActionsQuery.data,
hourly: overview?.hourly ?? ([] as HourlyBucket[]),
topics: overview?.topics ?? ([] as TopicTrend[]),
topUsers: overview?.top_users ?? ([] as UserStat[]),
@@ -139,9 +171,12 @@ export function useAnalytics({
}
export type {
AIStats,
AnalyticsOverview,
AttachmentStats,
HeatmapCell,
HourlyBucket,
ModerationActionRecord,
TopicTrend,
TrendBucket,
UserStat,