2026-08-18 20:45:12 +07:00
|
|
|
"use client";
|
|
|
|
|
|
2026-08-25 23:17:39 +07:00
|
|
|
import { Radio, Signal } from "lucide-react";
|
|
|
|
|
import { useMemo } from "react";
|
2026-08-18 20:45:12 +07:00
|
|
|
import { GlassPanel } from "@/components/primitives";
|
|
|
|
|
import { SectionHeader } from "@/components/shared";
|
2026-08-25 23:17:39 +07:00
|
|
|
import { useStaggerReveal } from "@/hooks/use-gsap-animation";
|
2026-08-18 20:45:12 +07:00
|
|
|
import { downloadCsv } from "@/lib/csv";
|
|
|
|
|
import { formatRelativeTime } from "@/lib/format";
|
|
|
|
|
import type { ChannelCultureRow } from "@/lib/types";
|
|
|
|
|
|
2026-08-25 23:17:39 +07:00
|
|
|
function deriveSignalStrength(c: ChannelCultureRow): number {
|
|
|
|
|
let score = 0.25;
|
|
|
|
|
if (c.culture_summary) {
|
|
|
|
|
score += Math.min(0.45, c.culture_summary.length / 400);
|
|
|
|
|
}
|
|
|
|
|
if (c.last_analyzed_at) {
|
|
|
|
|
const ageMs = Date.now() - c.last_analyzed_at;
|
|
|
|
|
const days = ageMs / (1000 * 60 * 60 * 24);
|
|
|
|
|
score += Math.max(0, 0.3 - days * 0.02);
|
|
|
|
|
}
|
|
|
|
|
return Math.max(0.08, Math.min(1, score));
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-18 20:45:12 +07:00
|
|
|
export function ChannelCultureGlossary({
|
|
|
|
|
cultures,
|
|
|
|
|
}: {
|
|
|
|
|
cultures: ChannelCultureRow[];
|
|
|
|
|
}) {
|
2026-08-25 23:17:39 +07:00
|
|
|
const ranked = useMemo(
|
|
|
|
|
() =>
|
|
|
|
|
cultures
|
|
|
|
|
.map((c) => ({ c, signal: deriveSignalStrength(c) }))
|
|
|
|
|
.sort((a, b) => b.signal - a.signal),
|
|
|
|
|
[cultures],
|
|
|
|
|
);
|
|
|
|
|
|
2026-08-26 03:37:14 +07:00
|
|
|
const containerRef = useStaggerReveal<HTMLDivElement>(".channel-row", {
|
|
|
|
|
stagger: 0.025,
|
|
|
|
|
y: 6,
|
2026-08-25 23:17:39 +07:00
|
|
|
dependencies: [cultures],
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-18 20:45:12 +07:00
|
|
|
return (
|
2026-08-26 03:37:14 +07:00
|
|
|
<GlassPanel>
|
2026-08-18 20:45:12 +07:00
|
|
|
<SectionHeader
|
2026-08-26 03:37:14 +07:00
|
|
|
eyebrow="Roster Intelligence"
|
|
|
|
|
title="Channel Culture & Activity Roster"
|
2026-08-18 20:45:12 +07:00
|
|
|
action={
|
|
|
|
|
cultures.length > 0 ? (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() =>
|
|
|
|
|
downloadCsv(
|
|
|
|
|
"channel-cultures.csv",
|
|
|
|
|
cultures.map((c) => ({
|
|
|
|
|
channel: c.channel_name ?? c.channel_id,
|
|
|
|
|
summary: c.culture_summary ?? "",
|
|
|
|
|
last_analyzed: c.last_analyzed_at ?? "",
|
|
|
|
|
})),
|
|
|
|
|
)
|
|
|
|
|
}
|
2026-08-26 03:37:14 +07:00
|
|
|
className="font-mono text-[11px] text-[#8a8f98] transition-colors hover:text-[#f7f8f8]"
|
2026-08-18 20:45:12 +07:00
|
|
|
>
|
2026-08-25 23:17:39 +07:00
|
|
|
EXPORT_CSV
|
2026-08-18 20:45:12 +07:00
|
|
|
</button>
|
|
|
|
|
) : null
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
{cultures.length === 0 ? (
|
2026-08-26 03:37:14 +07:00
|
|
|
<div className="py-8 text-center font-mono text-xs text-[#8a8f98]">
|
|
|
|
|
NO CHANNELS LOGGED
|
|
|
|
|
</div>
|
2026-08-18 20:45:12 +07:00
|
|
|
) : (
|
2026-08-26 03:37:14 +07:00
|
|
|
<div ref={containerRef} className="space-y-2 mt-3">
|
|
|
|
|
{ranked.map(({ c }) => (
|
2026-08-25 23:17:39 +07:00
|
|
|
<div
|
|
|
|
|
key={c.channel_id}
|
2026-08-26 03:37:14 +07:00
|
|
|
className="channel-row flex flex-col gap-1 rounded-[6px] border border-white/[0.06] bg-white/[0.02] p-3 transition-all hover:border-white/[0.12] hover:bg-white/[0.04]"
|
2026-08-25 23:17:39 +07:00
|
|
|
>
|
2026-08-26 03:37:14 +07:00
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<span className="font-mono text-xs font-semibold text-[#f7f8f8]">
|
2026-08-25 23:17:39 +07:00
|
|
|
#{c.channel_name ?? c.channel_id.slice(0, 8)}
|
2026-08-18 20:45:12 +07:00
|
|
|
</span>
|
2026-08-26 03:37:14 +07:00
|
|
|
{c.last_analyzed_at && (
|
|
|
|
|
<span className="font-mono text-[10px] text-[#8a8f98]">
|
|
|
|
|
{formatRelativeTime(c.last_analyzed_at)}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
2026-08-18 20:45:12 +07:00
|
|
|
</div>
|
2026-08-26 03:37:14 +07:00
|
|
|
{c.culture_summary && (
|
|
|
|
|
<p className="font-sans text-xs text-[#8a8f98] leading-relaxed line-clamp-2">
|
2026-08-25 23:17:39 +07:00
|
|
|
{c.culture_summary}
|
|
|
|
|
</p>
|
2026-08-18 20:45:12 +07:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</GlassPanel>
|
|
|
|
|
);
|
|
|
|
|
}
|