From e8deba93cc97c5db628db7010c1c6dbba5b99b7d Mon Sep 17 00:00:00 2001 From: MythEclipse Date: Wed, 3 Jun 2026 03:18:04 +0700 Subject: [PATCH] style(frontend): refactor UI components with sky color palette, improved spacing, and design polish --- .../features/analytics/components/Heatmap.tsx | 30 ++-- .../analytics/components/TrendChart.tsx | 40 +++-- .../analytics/components/UserTable.tsx | 20 ++- .../analytics/components/ViolatorTable.tsx | 1 + .../live/components/ActiveSpeakers.tsx | 2 +- .../live/components/MusicSubPanel.tsx | 8 +- .../features/live/components/NowPlaying.tsx | 10 +- .../live/components/RecordingsSubPanel.tsx | 14 +- .../live/components/ScreenSubPanel.tsx | 4 +- .../messages/components/ImageGrid.tsx | 26 ++- .../messages/components/MessageCard.tsx | 34 ++-- .../messages/components/MessageFeed.tsx | 36 ++-- .../components/ModerationAlertListener.tsx | 18 +- .../frontend/src/features/messages/index.tsx | 158 +++++++++++------- 14 files changed, 240 insertions(+), 161 deletions(-) diff --git a/services/frontend/src/features/analytics/components/Heatmap.tsx b/services/frontend/src/features/analytics/components/Heatmap.tsx index d1d5760..fdd215f 100644 --- a/services/frontend/src/features/analytics/components/Heatmap.tsx +++ b/services/frontend/src/features/analytics/components/Heatmap.tsx @@ -38,13 +38,13 @@ export function Heatmap({ cells, loading }: HeatmapProps) { } function getHeatClass(intensity: number): string { - if (intensity === 0) return "bg-muted/30"; - if (intensity < 0.1) return "bg-blue-500/10"; - if (intensity < 0.2) return "bg-blue-500/20"; - if (intensity < 0.35) return "bg-blue-500/30"; - if (intensity < 0.5) return "bg-blue-500/45"; - if (intensity < 0.7) return "bg-blue-500/60"; - return "bg-blue-500/80"; + if (intensity === 0) return "bg-sky-50"; + if (intensity < 0.1) return "bg-sky-200/30"; + if (intensity < 0.2) return "bg-sky-300/30"; + if (intensity < 0.35) return "bg-sky-400/35"; + if (intensity < 0.5) return "bg-sky-400/55"; + if (intensity < 0.7) return "bg-primary/60"; + return "bg-primary/80"; } return ( @@ -54,14 +54,14 @@ export function Heatmap({ cells, loading }: HeatmapProps) { Heatmap Aktivitas - Hari × jam — area biru = lebih ramai. + Hari × jam — area biru langit = lebih ramai.
{/* Header row */} -
+
{Array.from({ length: 24 }, (_, h) => (
{/* Rows */} {DAYS.map((day, d) => ( -
+
{day}
@@ -84,7 +84,7 @@ export function Heatmap({ cells, loading }: HeatmapProps) {
Sepi - - - - + + + + Ramai
diff --git a/services/frontend/src/features/analytics/components/TrendChart.tsx b/services/frontend/src/features/analytics/components/TrendChart.tsx index 2cb46e3..2b62b40 100644 --- a/services/frontend/src/features/analytics/components/TrendChart.tsx +++ b/services/frontend/src/features/analytics/components/TrendChart.tsx @@ -37,7 +37,7 @@ export function TrendChart({ trend, loading }: TrendChartProps) { Tren Harian - Volume pesan per hari dengan status moderasi. + Volume pesan per hari dengan status moderasi — area pink = flagged. @@ -48,7 +48,7 @@ export function TrendChart({ trend, loading }: TrendChartProps) {
-
+
Rangkuman 7 hari terakhir {totalMessages} total pesan @@ -61,16 +61,24 @@ export function TrendChart({ trend, loading }: TrendChartProps) { > - + + + + + - + {Array.from({ length: 4 }, (_, index) => { const y = 40 + index * 45; return ( @@ -89,32 +97,32 @@ export function TrendChart({ trend, loading }: TrendChartProps) { data={data} keyName="total" fill="url(#trendFill)" - stroke="#3b82f6" + stroke="#7EC8E3" /> - + {data.map((item, index) => { const x = @@ -124,7 +132,7 @@ export function TrendChart({ trend, loading }: TrendChartProps) { Math.max((data.length - 1) * 56, 56); return ( - + - + User Paling Aktif @@ -48,7 +49,7 @@ export function UserTable({ users, loading }: UserTableProps) { - + @@ -56,11 +57,14 @@ export function UserTable({ users, loading }: UserTableProps) { - + {users.map((user, i) => (
# User PesanFlag
{medals[i] ?? i + 1} @@ -71,11 +75,11 @@ export function UserTable({ users, loading }: UserTableProps) { ) : ( -
+
{user.username.charAt(0).toUpperCase()}
)} @@ -86,9 +90,9 @@ export function UserTable({ users, loading }: UserTableProps) {
-
+
+
setSource(e.target.value)} @@ -51,7 +51,7 @@ export function MusicSubPanel({ placeholder="YouTube URL, Spotify track, or search terms" />
- + setDraftVolume(Number(e.target.value))} - className="h-2 w-full cursor-pointer accent-primary" + className="h-2 w-full cursor-pointer accent-[#7EC8E3]" /> {draftVolume}%
- {aiStatus === "error" && ( - + Click to retry analysis )} @@ -383,7 +387,7 @@ export function MessageCard({ export function MessageCardSkeleton() { return ( -
+
diff --git a/services/frontend/src/features/messages/components/MessageFeed.tsx b/services/frontend/src/features/messages/components/MessageFeed.tsx index f46926a..0770758 100644 --- a/services/frontend/src/features/messages/components/MessageFeed.tsx +++ b/services/frontend/src/features/messages/components/MessageFeed.tsx @@ -1,7 +1,13 @@ +import { motion } from "framer-motion"; import { useEffect, useMemo, useRef } from "react"; import type { MessageRecord } from "../../../shared/api/client"; +import { + cardStagger, + cardItem, +} from "../../../shared/hooks/useFramerStagger"; import { ScrollArea } from "../../../shared/ui"; import { MessageCard, MessageCardSkeleton } from "./MessageCard"; +import { EmptyStateMascot } from "../../../widgets/mascot/ChibiMascot"; export interface MessageFeedProps { messages: MessageRecord[]; @@ -81,27 +87,29 @@ export function MessageFeed({ } if (messages.length === 0) { - return ( -
- {emptyText} -
- ); + return ; } return ( -
+ {groupedMessages.map((group) => group.messages.map((message, idx) => { const isFirstInGroup = idx === 0; const isCompact = !isFirstInGroup; return ( - + + + ); }), )} @@ -115,11 +123,11 @@ export function MessageFeed({ {loadingMore ? ( ) : ( -
+
)}
)} -
+
); } diff --git a/services/frontend/src/features/messages/components/ModerationAlertListener.tsx b/services/frontend/src/features/messages/components/ModerationAlertListener.tsx index dee204f..a11afe6 100644 --- a/services/frontend/src/features/messages/components/ModerationAlertListener.tsx +++ b/services/frontend/src/features/messages/components/ModerationAlertListener.tsx @@ -13,6 +13,22 @@ interface AlertDetail { brief: string; } +function severityToToastType( + severity: string, +): "error" | "warning" | "info" | "success" { + switch (severity) { + case "critical": + case "high": + return "error"; + case "medium": + return "warning"; + case "low": + return "info"; + default: + return "warning"; + } +} + export function ModerationAlertListener() { const { addToast } = useToast(); @@ -29,7 +45,7 @@ export function ModerationAlertListener() { addToast( `🚨 ${username} ${sevLabel}${catLabel}: ${brief}`, - "error", + severityToToastType(severity), ); }; diff --git a/services/frontend/src/features/messages/index.tsx b/services/frontend/src/features/messages/index.tsx index 6c9c11c..18b4547 100644 --- a/services/frontend/src/features/messages/index.tsx +++ b/services/frontend/src/features/messages/index.tsx @@ -1,5 +1,6 @@ import { Filter, RotateCw, Search, X } from "lucide-react"; import { useMemo, useState } from "react"; +import { motion } from "framer-motion"; import type { MessageRecord } from "../../shared/api/client"; import { Badge, @@ -14,6 +15,7 @@ import { TabsList, TabsTrigger, } from "../../shared/ui"; +import { cardStagger, cardItem } from "../../shared/hooks/useFramerStagger"; import { ImageGrid } from "./components/ImageGrid"; import { MessageFeed } from "./components/MessageFeed"; @@ -94,53 +96,65 @@ export function MessagesPanel({ }, [messages, searchResults, showSearch, aiFilter]); return ( -
- - - Messages - {guildName && ( -

- Monitoring all text channels in{" "} - {guildName} + + + + + Messages + {guildName && ( +

+ Monitoring all text channels in{" "} + + {guildName} + +

+ )} +
+ +

+ Messages are automatically captured from all text channels in the + monitored guild. Real-time updates arrive via WebSocket.

- )} - - -

- Messages are automatically captured from all text channels in the - monitored guild. Real-time updates arrive via WebSocket. -

-
-
+ + + {stats.total > 0 && ( -
- + + {stats.total} total{hasMore && !showSearch ? "+" : ""} {stats.clean} clean {stats.flagged} flagged {stats.error} error - + {stats.pending} pending {stats.deleted > 0 && ( - + {stats.deleted} deleted )} @@ -149,14 +163,17 @@ export function MessagesPanel({ {stats.edited} edited )} -
+ )} -
+
- + setSearchQuery(e.target.value)} @@ -168,6 +185,7 @@ export function MessagesPanel({ onClick={handleSearch} disabled={isSearching || !searchQuery.trim()} size="sm" + className="rounded-xl" > {isSearching ? "Searching..." : "Search"} @@ -199,6 +217,7 @@ export function MessagesPanel({ setRetryingAll(false); } }} + className="rounded-xl bg-pink-100 text-pink-700 hover:bg-pink-200 border-pink-200" > )} {retriedCount !== null && ( - + {retriedCount} message{retriedCount !== 1 ? "s" : ""} queued for re-analysis )}
- + {( [ "all", @@ -228,51 +247,60 @@ export function MessagesPanel({ ))}
-
+
{showSearch && searchResults.length > 0 && ( -
+ Found {searchResults.length} result {searchResults.length !== 1 ? "s" : ""} -
+ )} - setViewTab(v as "all" | "images")} - > - - - {showSearch - ? `Search (${filteredMessages.length})` - : `All (${filteredMessages.length})`} - - Images - - - - - - - - -
+ + setViewTab(v as "all" | "images")} + > + + + {showSearch + ? `Search (${filteredMessages.length})` + : `All (${filteredMessages.length})`} + + Images + + + + + + + + + + ); }