fix(fe): crash 'reading channel_id' + recharts width/height warnings
Build & Deploy (Nix) / build-and-deploy (backend) (push) Successful in 2m14s
Build & Deploy (Nix) / build-and-deploy (discord-gateway) (push) Successful in 2m53s
Build & Deploy (Nix) / build-and-deploy (proxy) (push) Successful in 3m15s

- useMessageDetail: guard attachments fetcher — revalidate bisa race
  detail load, detail.data undefined saat fetcher jalan ->
  TypeError 'Cannot read properties of undefined (reading channel_id)'
  yang bikin halaman /messages mati (Next error boundary). Sekarang
  fetcher balikin [] kalau channel_id belum ada; hapus non-null
  assertion. Diverifikasi: /messages sebelumnya crash, sekarang render
  dengan data asli (list, verdict, confidence, sticker, emoji).
- ResponsiveContainer (recharts 3.8): initialDimension -1 di render
  pertama -> warning 'width(-1) and height(-1)'. Pakai height numerik
  tetap (192/160/48) + minWidth/minHeight 0 -> calculatedHeight >0,
  warning hilang; width tetap responsif via ResizeObserver. Chart
  baru dirender setelah mount (useMounted) biar container punya ukuran.
  Diverifikasi console: 0 warning, 0 error di dashboard & voice.
This commit is contained in:
asepharyana
2026-08-01 10:21:23 +07:00
parent 6f41c22cb5
commit ec89d64dbf
5 changed files with 126 additions and 73 deletions
@@ -1,6 +1,5 @@
"use client";
import { GlassCard } from "@/components/glass/card";
import {
Bar,
BarChart,
@@ -9,12 +8,16 @@ import {
XAxis,
YAxis,
} from "recharts";
import { GlassCard } from "@/components/glass/card";
import { useMounted } from "@/lib/hooks/use-mounted";
interface ActivityTimelineProps {
data?: { user: string; duration: number }[];
}
export function VoiceActivityTimeline({ data = [] }: ActivityTimelineProps) {
const mounted = useMounted();
return (
<GlassCard variant="base">
<div className="flex items-center gap-2 mb-3">
@@ -23,39 +26,51 @@ export function VoiceActivityTimeline({ data = [] }: ActivityTimelineProps) {
</span>
</div>
<div className="h-40">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={data} layout="vertical">
<XAxis
type="number"
axisLine={false}
tickLine={false}
tick={{ fill: "oklch(0.55 0.02 245)", fontSize: 10 }}
/>
<YAxis
type="category"
dataKey="user"
axisLine={false}
tickLine={false}
tick={{ fill: "oklch(0.55 0.02 245)", fontSize: 10 }}
width={80}
/>
<Tooltip
contentStyle={{
background: "oklch(0.11 0.02 245 / 0.9)",
border: "1px solid oklch(1 0 0 / 0.08)",
borderRadius: 8,
fontSize: 12,
color: "oklch(0.93 0.01 245)",
}}
formatter={(value) => [`${(Number(value) / 60).toFixed(1)}m`, "Duration"]}
/>
<Bar
dataKey="duration"
fill="var(--color-primary)"
radius={[0, 4, 4, 0]}
/>
</BarChart>
</ResponsiveContainer>
{mounted ? (
<ResponsiveContainer
width="100%"
height={160}
minWidth={0}
minHeight={0}
>
<BarChart data={data} layout="vertical">
<XAxis
type="number"
axisLine={false}
tickLine={false}
tick={{ fill: "oklch(0.55 0.02 245)", fontSize: 10 }}
/>
<YAxis
type="category"
dataKey="user"
axisLine={false}
tickLine={false}
tick={{ fill: "oklch(0.55 0.02 245)", fontSize: 10 }}
width={80}
/>
<Tooltip
contentStyle={{
background: "oklch(0.11 0.02 245 / 0.9)",
border: "1px solid oklch(1 0 0 / 0.08)",
borderRadius: 8,
fontSize: 12,
color: "oklch(0.93 0.01 245)",
}}
formatter={(value) => [
`${(Number(value) / 60).toFixed(1)}m`,
"Duration",
]}
/>
<Bar
dataKey="duration"
fill="var(--color-primary)"
radius={[0, 4, 4, 0]}
/>
</BarChart>
</ResponsiveContainer>
) : (
<div className="h-full w-full animate-pulse rounded-md bg-card/40" />
)}
</div>
</GlassCard>
);