Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/components/dashboard/platform/AllPlatformView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,17 @@ export default function AllPlatformView({ isLoading }: IAllPlatformViewProps) {
</div>

{/* 실시간 트래픽 변화 */}
<Card title="실시간 트래픽 변화" className="min-h-125">
{isLoading ? <TrafficChartSkeleton /> : null}
<Card title="실시간 트래픽 변화" className="min-h-125 flex flex-col">
{isLoading ? (
<TrafficChartSkeleton />
) : (
<div className="flex flex-1 flex-col items-center justify-center gap-3">
<Badge variant="running">Coming Soon</Badge>
<p className="text-text-sub font-caption text-center">
실시간 클릭 트래픽 모니터링 기능을 준비하고 있습니다.
</p>
</div>
)}
</Card>

{/* 개별 플랫폼 상세 */}
Expand Down
9 changes: 7 additions & 2 deletions src/components/dashboard/platform/SinglePlatformView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,14 @@ export default function SinglePlatformView({
<div className="grid grid-cols-3 tablet:grid-cols-1 gap-6">
<Card
title="실시간 트래픽 변화"
className="col-span-2 tablet:col-span-1 min-h-125"
className="col-span-2 tablet:col-span-1 min-h-125 flex flex-col"
>
<div className="text-text-sub" />
<div className="flex flex-1 flex-col items-center justify-center gap-3">
<Badge variant="running">Coming Soon</Badge>
<p className="text-text-sub font-caption text-center">
실시간 클릭 트래픽 모니터링 기능을 준비하고 있습니다.
</p>
</div>
</Card>

<Card
Expand Down
4 changes: 3 additions & 1 deletion src/components/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ export default function Sidebar() {
aria-label="사이드바 내비게이션"
className={twMerge(
"flex flex-1 flex-col gap-1 px-2 min-h-0",
isCollapsed ? "overflow-visible" : "overflow-y-auto",
isCollapsed
? "overflow-visible"
: "overflow-y-auto overflow-x-hidden",
)}
>
{mainNav.map((item) => {
Expand Down
6 changes: 0 additions & 6 deletions src/constants/sidebarNav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,6 @@ export const mainNav: INavItem[] = [
},
],
},
{
id: "settings",
label: "설정",
icon: SettingsIcon,
path: "/setting",
},
];

export const footerNav: INavItem[] = [
Expand Down
67 changes: 38 additions & 29 deletions src/hooks/dashboard/useClickStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import useAuthStore from "@/store/useAuthStore";
import useWorkspaceStore from "@/store/useWorkspaceStore";

const MAX_RETRIES = 3;
const BASE_URL = (import.meta.env.VITE_API_BASE_URL ?? "").replace(/\/$/, "");

export function useClickStream(mode: "real" | "dummy" = "dummy") {
// TODO: 테스트용 - 더미 데이터가 orgId 1에만 있어서 임시 고정, 테스트 후 원래대로 복구할 예정
Expand All @@ -26,40 +27,48 @@ export function useClickStream(mode: "real" | "dummy" = "dummy") {
const controller = new AbortController();
retryCountRef.current = 0;

fetchEventSource(`/api/dashboard/${orgId}/clicks/stream?mode=${mode}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
signal: controller.signal,
onopen: async (response) => {
if (response.ok) {
fetchEventSource(
`${BASE_URL}/api/dashboard/${orgId}/clicks/stream?mode=${mode}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
signal: controller.signal,
onopen: async (response) => {
const contentType = response.headers.get("content-type") ?? "";
const isSse = contentType.includes("text/event-stream");
if (!response.ok || !isSse) {
throw new Error(
`Invalid SSE response: status=${response.status}, content-type=${contentType}`,
);
}
retryCountRef.current = 0;
setIsError(false);
}
},
onmessage(event) {
if (event.event === "org-click-update") {
try {
const wrapper = JSON.parse(event.data);
const parsed: IClickStreamItem = wrapper.data ?? wrapper;
setData(parsed);
if (parsed.suspectDetail) {
setSuspectDetail(parsed.suspectDetail);
},
Comment thread
Seojegyeong marked this conversation as resolved.
onmessage(event) {
if (event.event === "org-click-update") {
try {
const wrapper = JSON.parse(event.data);
const parsed: IClickStreamItem = wrapper.data ?? wrapper;
setData(parsed);
if (parsed.suspectDetail) {
setSuspectDetail(parsed.suspectDetail);
}
} catch {
// 파싱 실패 시 해당 이벤트 무시하고 스트림 유지
}
} catch {
// 파싱 실패 시 해당 이벤트 무시하고 스트림 유지
}
}
},
onerror(err) {
retryCountRef.current += 1;
if (retryCountRef.current >= MAX_RETRIES) {
setIsError(true);
throw err; // 재시도 중단
}
// MAX_RETRIES 미만이면 기본 재시도 동작 유지
},
onerror(err) {
retryCountRef.current += 1;
if (retryCountRef.current >= MAX_RETRIES) {
setIsError(true);
throw err; // 재시도 중단
}
// MAX_RETRIES 미만이면 기본 재시도 동작 유지
},
},
});
);

return () => controller.abort();
}, [orgId, accessToken, mode]);
Expand Down
Loading