refactor: remove Review tab, make UI state client-side, fix Jakarta time in analytics

- Remove Review tab completely (was redundant with Messages flagged view)
- UI state now client-side only (localStorage) — no server API calls for tab/channel/guild selection
- Fixes dashboard crash when server is down — now loads fully client-side
- Live panel still uses server API for voice/media operations (only what needs it)
- Analytics hourly chart labels now show Jakarta time (WIB/UTC+7) instead of UTC
- Analytics formatTimeAgo uses Jakarta time reference
- Reduced tabs to 3: Live, Messages, Analytics
- Removed unused uiState API imports and server-side state fetching

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-05-30 16:31:27 +07:00
co-authored by Claude Opus 4.8
parent 9019e24263
commit c7abe39728
6 changed files with 46 additions and 45 deletions
+5 -9
View File
@@ -2,7 +2,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { DashboardLayout } from "./components/layout/DashboardLayout";
import { LivePanel } from "./components/live/LivePanel";
import { MessagesPanel } from "./components/messages/MessagesPanel";
import { ReviewPanel } from "./components/review/ReviewPanel";
import { Tabs, TabsContent } from "./components/ui/tabs";
import { AnalyticsPanel } from "./components/analytics/AnalyticsPanel";
import { AuthOverlay } from "./components/layout/AuthOverlay";
@@ -122,8 +121,8 @@ export default function App() {
}, [socket.socketRef]);
const toggleStreaming = useCallback(async () => {
if (isStreaming) { stopStreamingLocal(); await patchUIState({ isStreaming: false }); }
else { await startStreamingLocal(); await patchUIState({ isStreaming: true }); }
if (isStreaming) { stopStreamingLocal(); patchUIState({ isStreaming: false }); }
else { await startStreamingLocal(); patchUIState({ isStreaming: true }); }
}, [isStreaming, startStreamingLocal, stopStreamingLocal, patchUIState]);
useEffect(() => { if (selectedVoiceGuild) voice.loadVoiceChannels(selectedVoiceGuild).catch(() => undefined); }, [selectedVoiceGuild]);
@@ -131,15 +130,15 @@ export default function App() {
useEffect(() => { if (selectedTextChannel) messages.fetchMessages(selectedTextChannel).catch(() => undefined); }, [selectedTextChannel]);
const toggleListening = useCallback(async () => {
if (isListening) { await audioContextListenRef.current?.suspend(); userTimelinesRef.current.clear(); setIsListening(false); await patchUIState({ isListening: false }); return; }
if (isListening) { await audioContextListenRef.current?.suspend(); userTimelinesRef.current.clear(); setIsListening(false); patchUIState({ isListening: false }); return; }
const AudioContextCtor = window.AudioContext || window.webkitAudioContext;
audioContextListenRef.current ??= new AudioContextCtor({ sampleRate: SAMPLE_RATE });
await audioContextListenRef.current.resume();
setIsListening(true);
await patchUIState({ isListening: true });
patchUIState({ isListening: true });
}, [isListening, patchUIState]);
const tabs = useMemo(() => ["live", "messages", "analytics", "review"] as DashboardTab[], []);
const tabs = useMemo(() => ["live", "messages", "analytics"] as DashboardTab[], []);
return (
<DashboardLayout
@@ -213,9 +212,6 @@ export default function App() {
onChannelChange={(channelId) => patchUIState({ selectedTextChannel: channelId })}
/>
</TabsContent>
<TabsContent value="review">
<ReviewPanel messages={messages.messages} onReanalyze={messages.reanalyze} />
</TabsContent>
</Tabs>
</DashboardLayout>
);