feat(dashboard): expose trust reputation + reactions leaderboard

- GET /api/dashboard/reactions: top reacted messages (net add-remove),
  joined with message content, channel name, top 3 emoji breakdown
- Users tab: colored trust tier badge (Trusted/Netral/At Risk/Kritis)
  in list rows + detail panel (was plain number badge)
- Dashboard: new Reactions sub-tab with leaderboard
  (rank, emoji cluster, message, author, channel, count)
This commit is contained in:
asepharyana
2026-08-05 10:20:12 +07:00
parent 2f51f94610
commit a309570d29
10 changed files with 235 additions and 2 deletions
@@ -13,6 +13,41 @@ import { useUserDetail, useUsers } from "@/hooks";
import { renderMessageContent } from "@/lib/format";
import type { DashboardUser } from "@/lib/types";
const TRUST_TIERS = [
{
min: 75,
label: "Trusted",
className: "border-green-500/40 text-green-500",
},
{ min: 40, label: "Netral", className: "border-sky-500/40 text-sky-500" },
{
min: 10,
label: "At Risk",
className: "border-orange-500/40 text-orange-500",
},
{ min: 0, label: "Kritis", className: "border-red-500/40 text-red-500" },
] as const;
export function trustTier(score: number) {
return (
TRUST_TIERS.find((t) => score >= t.min) ??
TRUST_TIERS[TRUST_TIERS.length - 1]
);
}
function TrustBadge({ score }: { score: number }) {
const tier = trustTier(score);
return (
<Badge
variant="outline"
className={tier.className}
title={`Trust score ${score}`}
>
{tier.label}: {score}
</Badge>
);
}
export function UsersSection() {
const [search, setSearch] = useState("");
const [selectedId, setSelectedId] = useState<string | null>(null);
@@ -109,7 +144,7 @@ export function UsersSection() {
Clean: {detail.clean_count}
</Badge>
{detail.trust_score != null && (
<Badge variant="outline">Trust: {detail.trust_score}</Badge>
<TrustBadge score={detail.trust_score} />
)}
{detail.clean_message_streak != null && (
<Badge variant="outline">
@@ -200,6 +235,7 @@ function UserRow({
{user.flagged_count > 0 && (
<Badge variant="destructive">{user.flagged_count}</Badge>
)}
{user.trust_score != null && <TrustBadge score={user.trust_score} />}
</div>
</CardContent>
</Card>