feat(analytics): add AI stats, attachment stats, and moderation actions queries to useAnalytics hook
This commit is contained in:
@@ -27,7 +27,7 @@ export function ActivityChart({ hourly, loading }: ActivityChartProps) {
|
|||||||
return {
|
return {
|
||||||
hour: `${String(jakartaHour).padStart(2, "0")}:00`,
|
hour: `${String(jakartaHour).padStart(2, "0")}:00`,
|
||||||
clean: b.clean,
|
clean: b.clean,
|
||||||
warned: 0,
|
warned: b.warned,
|
||||||
flagged: b.flagged,
|
flagged: b.flagged,
|
||||||
error: b.error,
|
error: b.error,
|
||||||
total: b.count,
|
total: b.count,
|
||||||
@@ -46,8 +46,9 @@ export function ActivityChart({ hourly, loading }: ActivityChartProps) {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="space-y-3">
|
<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>Clean</span>
|
||||||
|
<span>Warned</span>
|
||||||
<span>Flagged</span>
|
<span>Flagged</span>
|
||||||
<span>Error</span>
|
<span>Error</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -55,6 +56,7 @@ export function ActivityChart({ hourly, loading }: ActivityChartProps) {
|
|||||||
{data.map((bucket) => {
|
{data.map((bucket) => {
|
||||||
const total = Math.max(bucket.total, 1);
|
const total = Math.max(bucket.total, 1);
|
||||||
const clean = bucket.clean / total;
|
const clean = bucket.clean / total;
|
||||||
|
const warned = bucket.warned / total;
|
||||||
const flagged = bucket.flagged / total;
|
const flagged = bucket.flagged / total;
|
||||||
const error = bucket.error / total;
|
const error = bucket.error / total;
|
||||||
return (
|
return (
|
||||||
@@ -70,9 +72,13 @@ export function ActivityChart({ hourly, loading }: ActivityChartProps) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex h-3 overflow-hidden rounded-full bg-sky-50">
|
<div className="flex h-3 overflow-hidden rounded-full bg-sky-50">
|
||||||
<div
|
<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}%` }}
|
style={{ width: `${clean * 100}%` }}
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
className="bg-yellow-400/60"
|
||||||
|
style={{ width: `${warned * 100}%` }}
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
className="bg-accent/70"
|
className="bg-accent/70"
|
||||||
style={{ width: `${flagged * 100}%` }}
|
style={{ width: `${flagged * 100}%` }}
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||||
import { useCallback, useEffect } from "react";
|
import { useCallback, useEffect } from "react";
|
||||||
import type {
|
import type {
|
||||||
|
AIStats,
|
||||||
AnalyticsOverview,
|
AnalyticsOverview,
|
||||||
|
AttachmentStats,
|
||||||
HeatmapCell,
|
HeatmapCell,
|
||||||
HourlyBucket,
|
HourlyBucket,
|
||||||
|
ModerationActionRecord,
|
||||||
TopicTrend,
|
TopicTrend,
|
||||||
TrendBucket,
|
TrendBucket,
|
||||||
UserStat,
|
UserStat,
|
||||||
ViolatorStat,
|
ViolatorStat,
|
||||||
} from "../../../shared/api/client";
|
} from "../../../shared/api/client";
|
||||||
import {
|
import {
|
||||||
|
fetchAIStats,
|
||||||
fetchAnalyticsOverview,
|
fetchAnalyticsOverview,
|
||||||
|
fetchAttachmentStats,
|
||||||
fetchHeatmap,
|
fetchHeatmap,
|
||||||
|
fetchModerationActions,
|
||||||
fetchTrend,
|
fetchTrend,
|
||||||
fetchViolators,
|
fetchViolators,
|
||||||
} from "../../../shared/api/client";
|
} from "../../../shared/api/client";
|
||||||
@@ -21,23 +27,15 @@ function analyticsKeys(
|
|||||||
channelId: string | undefined,
|
channelId: string | undefined,
|
||||||
hours: number,
|
hours: number,
|
||||||
) {
|
) {
|
||||||
|
const base = [guildId, channelId ?? "", hours] as const;
|
||||||
return {
|
return {
|
||||||
overview: [
|
overview: ["analytics", "overview", ...base] as const,
|
||||||
"analytics",
|
violators: ["analytics", "violators", ...base] as const,
|
||||||
"overview",
|
trend: ["analytics", "trend", ...base] as const,
|
||||||
guildId,
|
heatmap: ["analytics", "heatmap", ...base] as const,
|
||||||
channelId ?? "",
|
aiStats: ["analytics", "ai-stats", ...base] as const,
|
||||||
hours,
|
attachmentStats: ["analytics", "attachment-stats", ...base] as const,
|
||||||
] as const,
|
moderationActions: ["analytics", "moderation-actions", ...base] 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,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +84,30 @@ export function useAnalytics({
|
|||||||
placeholderData: keepPreviousData,
|
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(() => {
|
const refresh = useCallback(() => {
|
||||||
if (!guildId) return;
|
if (!guildId) return;
|
||||||
window.dispatchEvent(new CustomEvent("analytics_refresh"));
|
window.dispatchEvent(new CustomEvent("analytics_refresh"));
|
||||||
@@ -94,7 +116,6 @@ export function useAnalytics({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = () => {
|
const handler = () => {
|
||||||
if (!guildId) return;
|
if (!guildId) return;
|
||||||
// Use queryClient.invalidateQueries from the React Query internals
|
|
||||||
window.dispatchEvent(new CustomEvent("analytics_force_refresh"));
|
window.dispatchEvent(new CustomEvent("analytics_force_refresh"));
|
||||||
};
|
};
|
||||||
window.addEventListener("analytics_refresh", handler);
|
window.addEventListener("analytics_refresh", handler);
|
||||||
@@ -128,6 +149,17 @@ export function useAnalytics({
|
|||||||
heatmapLoading: heatmapQuery.isLoading && !heatmapQuery.data,
|
heatmapLoading: heatmapQuery.isLoading && !heatmapQuery.data,
|
||||||
heatmapFetching: heatmapQuery.isFetching && !heatmapQuery.isLoading,
|
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[]),
|
hourly: overview?.hourly ?? ([] as HourlyBucket[]),
|
||||||
topics: overview?.topics ?? ([] as TopicTrend[]),
|
topics: overview?.topics ?? ([] as TopicTrend[]),
|
||||||
topUsers: overview?.top_users ?? ([] as UserStat[]),
|
topUsers: overview?.top_users ?? ([] as UserStat[]),
|
||||||
@@ -139,9 +171,12 @@ export function useAnalytics({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
|
AIStats,
|
||||||
AnalyticsOverview,
|
AnalyticsOverview,
|
||||||
|
AttachmentStats,
|
||||||
HeatmapCell,
|
HeatmapCell,
|
||||||
HourlyBucket,
|
HourlyBucket,
|
||||||
|
ModerationActionRecord,
|
||||||
TopicTrend,
|
TopicTrend,
|
||||||
TrendBucket,
|
TrendBucket,
|
||||||
UserStat,
|
UserStat,
|
||||||
|
|||||||
Reference in New Issue
Block a user