fe(fe): enrich dashboard Top Reacted Messages
Show relative time, always-on username with channel context, and collapse top emojis to two; add VIEW ALL toggle to reveal all 20 fetched reactions instead of the top 4.
This commit is contained in:
1 parent
b12eb0a038
commit
8583bcdf17
1 file changed
+57
-36
@@ -13,7 +13,7 @@ import {
|
|||||||
Zap,
|
Zap,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useAmbient } from "@/components/ambient/ambient-context";
|
import { useAmbient } from "@/components/ambient/ambient-context";
|
||||||
import { AreaActivity, RadialGauge } from "@/components/charts";
|
import { AreaActivity, RadialGauge } from "@/components/charts";
|
||||||
import { GlassPanel } from "@/components/primitives";
|
import { GlassPanel } from "@/components/primitives";
|
||||||
@@ -28,7 +28,7 @@ import {
|
|||||||
} from "@/components/shared";
|
} from "@/components/shared";
|
||||||
import { useActivity, useStats, useTopReactions } from "@/hooks";
|
import { useActivity, useStats, useTopReactions } from "@/hooks";
|
||||||
import { useStaggerReveal } from "@/hooks/use-gsap-animation";
|
import { useStaggerReveal } from "@/hooks/use-gsap-animation";
|
||||||
import { formatNumber } from "@/lib/format";
|
import { formatNumber, formatRelativeTime } from "@/lib/format";
|
||||||
import type { DashboardStats } from "@/lib/types";
|
import type { DashboardStats } from "@/lib/types";
|
||||||
import { staggerDelay } from "@/lib/utils";
|
import { staggerDelay } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -65,6 +65,7 @@ export function DashboardView({
|
|||||||
} = useStats(initialStats);
|
} = useStats(initialStats);
|
||||||
const { data: activity } = useActivity(14, initialActivity);
|
const { data: activity } = useActivity(14, initialActivity);
|
||||||
const { data: reactions } = useTopReactions();
|
const { data: reactions } = useTopReactions();
|
||||||
|
const [showAllReactions, setShowAllReactions] = useState(false);
|
||||||
const ambient = useAmbient();
|
const ambient = useAmbient();
|
||||||
|
|
||||||
const hudRef = useStaggerReveal<HTMLDivElement>(".linear-tile", {
|
const hudRef = useStaggerReveal<HTMLDivElement>(".linear-tile", {
|
||||||
@@ -359,44 +360,64 @@ export function DashboardView({
|
|||||||
<SectionHeader
|
<SectionHeader
|
||||||
eyebrow="Engagement"
|
eyebrow="Engagement"
|
||||||
title="Top Reacted Messages"
|
title="Top Reacted Messages"
|
||||||
|
action={
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowAllReactions((v) => !v)}
|
||||||
|
className="font-mono text-[10px] text-ink-muted transition-colors hover:text-ink"
|
||||||
|
>
|
||||||
|
{showAllReactions
|
||||||
|
? "SHOW TOP 4"
|
||||||
|
: `VIEW ALL ${reactions.length}`}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<div className="mt-3 space-y-2">
|
<div className="mt-3 space-y-2">
|
||||||
{reactions.slice(0, 4).map((r) => (
|
{(showAllReactions ? reactions : reactions.slice(0, 4)).map(
|
||||||
<div
|
(r) => (
|
||||||
key={r.message_id}
|
<div
|
||||||
className="hud-card flex items-start justify-between gap-2 p-2.5"
|
key={r.message_id}
|
||||||
>
|
className="hud-card flex items-start justify-between gap-2 p-2.5"
|
||||||
<div className="min-w-0 flex-1">
|
>
|
||||||
<span className="line-clamp-1 block text-xs text-ink-soft">
|
<div className="min-w-0 flex-1">
|
||||||
{r.content || "[Media/Attachment]"}
|
<span className="line-clamp-1 block text-xs text-ink-soft">
|
||||||
</span>
|
{r.content || "[Media/Attachment]"}
|
||||||
{r.username && (
|
|
||||||
<span className="mt-0.5 block font-mono text-[10px] text-ink-muted">
|
|
||||||
@{r.username}
|
|
||||||
{r.channel_name && ` in #${r.channel_name}`}
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
<span className="mt-0.5 block font-mono text-[10px] text-ink-muted">
|
||||||
|
@{r.username || "unknown"}
|
||||||
|
{r.channel_name && ` in #${r.channel_name}`}
|
||||||
|
{r.created_at != null && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
·{" "}
|
||||||
|
<span suppressHydrationWarning>
|
||||||
|
{formatRelativeTime(r.created_at)}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="shrink-0 text-right">
|
||||||
|
<span className="font-mono text-xs font-semibold text-signal">
|
||||||
|
{formatNumber(r.reaction_count)}
|
||||||
|
</span>
|
||||||
|
{r.top_emojis && r.top_emojis.length > 0 && (
|
||||||
|
<div className="mt-0.5 flex gap-0.5">
|
||||||
|
{r.top_emojis.slice(0, 2).map((e) => (
|
||||||
|
<span
|
||||||
|
key={e.emoji}
|
||||||
|
className="text-[10px]"
|
||||||
|
title={`${e.emoji} ×${e.count}`}
|
||||||
|
>
|
||||||
|
{e.emoji}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="shrink-0 text-right">
|
),
|
||||||
<span className="font-mono text-xs font-semibold text-signal">
|
)}
|
||||||
{formatNumber(r.reaction_count)}
|
|
||||||
</span>
|
|
||||||
{r.top_emojis && r.top_emojis.length > 0 && (
|
|
||||||
<div className="mt-0.5 flex gap-0.5">
|
|
||||||
{r.top_emojis.map((e) => (
|
|
||||||
<span
|
|
||||||
key={e.emoji}
|
|
||||||
className="text-[10px]"
|
|
||||||
title={`${e.emoji} ×${e.count}`}
|
|
||||||
>
|
|
||||||
{e.emoji}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</GlassPanel>
|
</GlassPanel>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in new issue
Block a user