Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
4 changes: 2 additions & 2 deletions src/components/common/chart/ChartLegend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export interface IChartLegendProps {
export default function ChartLegend({ items, className }: IChartLegendProps) {
return (
<div className={twMerge("flex items-center gap-4", className)}>
{items.map((item, index) => (
<div key={index} className="flex items-center gap-1.5">
{items.map((item) => (
<div key={item.label} className="flex items-center gap-1.5">
<div
className={twMerge("w-1.5 h-1.5 rounded-full", item.colorClass)}
/>
Expand Down
46 changes: 0 additions & 46 deletions src/components/common/toast/Toast.tsx

This file was deleted.

61 changes: 45 additions & 16 deletions src/components/dashboard/charts/BudgetGaugeChart.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useEffect, useState } from "react";
import { twMerge } from "tailwind-merge";

interface IBudgetGaugeChartProps {
Expand Down Expand Up @@ -25,8 +26,10 @@ export default function BudgetGaugeChart({
warningThreshold,
dangerThreshold,
}: IBudgetGaugeChartProps) {
const percentage = Math.round((spent / totalBudget) * 100);
const remaining = totalBudget - spent;
const percentage =
totalBudget > 0 ? Math.round((spent / totalBudget) * 100) : 0;
const remaining = Math.max(0, totalBudget - spent);
const isOverBudget = spent > totalBudget;
Comment thread
Seojegyeong marked this conversation as resolved.
Resolved

const getStatus = () => {
if (percentage >= dangerThreshold) return "위험";
Expand All @@ -36,6 +39,12 @@ export default function BudgetGaugeChart({

const status = getStatus();

const [mounted, setMounted] = useState(false);
useEffect(() => {
const raf = requestAnimationFrame(() => setMounted(true));
return () => cancelAnimationFrame(raf);
}, []);

return (
<div className="flex flex-col w-full h-full justify-between font-pretendard">
<div className="flex flex-col gap-4 mb-6">
Expand All @@ -59,18 +68,31 @@ export default function BudgetGaugeChart({
</span>
</div>

<div className="relative h-3 w-full bg-bg-disabled/50 rounded-full overflow-hidden">
<div className="relative py-1.5">
<div
className={twMerge(
"absolute top-0 left-0 h-full rounded-full transition-all duration-1000 ease-out",
status === "안정"
? "bg-status-green"
: status === "주의"
? "bg-status-yellow"
: "bg-status-red",
)}
style={{ width: `${Math.min(percentage, 100)}%` }}
/>
role="progressbar"
aria-valuenow={Math.min(percentage, 100)}
aria-valuemin={0}
aria-valuemax={100}
aria-label={`예산 소진율 ${Math.min(percentage, 100)}%`}
className="relative h-3 w-full bg-bg-disabled/40 rounded-full overflow-hidden shadow-[inset_0_1px_3px_rgba(0,0,0,0.08)]"
Comment thread
Seojegyeong marked this conversation as resolved.
Resolved
>
<div
className={twMerge(
"absolute top-0 left-0 h-full rounded-full transition-all duration-1000 ease-out",
status === "안정"
? "bg-linear-to-r from-status-green/50 to-status-green"
: status === "주의"
? "bg-linear-to-r from-status-yellow/50 to-status-yellow"
: "bg-linear-to-r from-status-red/50 to-status-red",
)}
style={{
width: mounted ? `${Math.min(percentage, 100)}%` : "0%",
}}
>
<div className="absolute inset-0 rounded-full bg-linear-to-b from-white/30 to-transparent" />
</div>
</div>
</div>
</div>

Expand All @@ -93,8 +115,15 @@ export default function BudgetGaugeChart({

<div className="bg-bg-surface/40 rounded-component-lg p-6 flex flex-col gap-4 border border-white/40 mt-auto">
<div className="flex flex-col gap-1">
<span className="font-caption text-text-sub">이번 달 잔액</span>
<span className="font-heading2 text-text-main font-extrabold tracking-tight leading-none pt-1">
<span className="font-caption text-text-sub">
{isOverBudget ? "초과 지출" : "이번 달 잔액"}
</span>
<span
className={twMerge(
"font-heading2 font-extrabold tracking-tight leading-none pt-1",
isOverBudget ? "text-status-red" : "text-text-main",
)}
>
₩{remaining.toLocaleString()}
</span>
</div>
Expand All @@ -104,7 +133,7 @@ export default function BudgetGaugeChart({
<div className="flex items-start gap-1.5">
<div
className={twMerge(
"w-1.5 h-1.5 rounded-full mt-1.5 shrink-0 animate-pulse",
"w-1.5 h-1.5 rounded-full mt-1.5 shrink-0 motion-safe:animate-pulse",
statusPointClasses[status],
)}
/>
Expand Down
14 changes: 8 additions & 6 deletions src/components/dashboard/charts/TrafficChart.tsx
Comment thread
Seojegyeong marked this conversation as resolved.
Resolved
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,13 @@ const series = [

export default function TrafficChart() {
return (
<ReactApexChart
type="area"
options={options}
series={series}
height={360}
/>
<div role="img" aria-label="실시간 트래픽 변화 차트: 시간대별 클릭수 추이">
<ReactApexChart
type="area"
options={options}
series={series}
height={360}
/>
</div>
Comment thread
Seojegyeong marked this conversation as resolved.
Resolved
);
}
6 changes: 3 additions & 3 deletions src/components/dashboard/platform/PlatformRoasTable.tsx
Comment thread
Seojegyeong marked this conversation as resolved.
Resolved
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ export default function PlatformRoasTable() {

<div className="flex flex-col gap-1.5 overflow-visible">
{platformRoasRanking.map((platform, index) => (
<div
<button
key={platform.name}
onClick={() => navigate("/platform")}
className="group grid grid-cols-[2.5rem_9rem_5rem_1fr] gap-4 items-center px-3 py-4 rounded-component-lg transition-all duration-300 hover:bg-bg-surface hover:shadow-Medium border border-transparent hover:border-white active:scale-[0.98] cursor-pointer"
className="group grid grid-cols-[2.5rem_9rem_5rem_1fr] gap-4 items-center px-3 py-4 rounded-component-lg transition-all duration-300 hover:bg-bg-surface hover:shadow-Medium border border-transparent hover:border-white active:scale-[0.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-status-blue cursor-pointer text-left w-full"
Comment thread
Seojegyeong marked this conversation as resolved.
Resolved
>
<span className="text-center font-extrabold text-text-placeholder font-body1 group-hover:text-status-blue transition-colors">
{index + 1}
Expand All @@ -63,7 +63,7 @@ export default function PlatformRoasTable() {
₩{platform.adCost.toLocaleString()}
</span>
</div>
</div>
</button>
))}
</div>
</div>
Expand Down
21 changes: 12 additions & 9 deletions src/pages/dashboard/overview/OverviewAiReportPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,33 @@ export default function OverviewAiReportPanel() {
<SparkleCircleIcon className="w-4.5 h-4.5 text-logo-1 fill-current" />
<span>{data.label}</span>
</div>
<h2 className="font-heading3 text-[26px] font-extrabold text-text-main tracking-[-0.02em] leading-tight break-keep">
<h2 className="font-heading2 font-extrabold text-text-main tracking-[-0.02em] leading-tight break-keep">
{data.title}
</h2>
</div>

{/* 전략 제안 */}
<div className="rounded-[24px] bg-logo-1/6 p-7 text-left mx-1">
<h3 className="flex items-center gap-2 text-logo-1 font-body1 font-bold text-[16px] mb-3">
<h3 className="flex items-center gap-2 text-logo-1 font-body1 font-bold mb-3">
<SparkleIcon className="w-4.5 h-4.5 text-logo-1 fill-current" />
{data.strategySuggestion.title}
</h3>
<p className="text-text-main font-body2 font-medium text-[15px] leading-[1.65] whitespace-pre-line break-keep">
<p className="text-text-main font-body2 font-medium leading-[1.65] whitespace-pre-line break-keep">
{data.strategySuggestion.content}
</p>
</div>

{/* 성과 요약 */}
<div className="flex flex-col px-2 gap-6">
{data.sections.map((section, idx) => (
<div key={idx} className="flex flex-col gap-3 text-left shrink-0">
<h3 className="text-text-main font-body1 font-extrabold text-[17px] tracking-[-0.01em]">
{data.sections.map((section) => (
<div
key={section.title}
className="flex flex-col gap-3 text-left shrink-0"
>
<h3 className="text-text-main font-body1 font-extrabold tracking-[-0.01em]">
Comment thread
Seojegyeong marked this conversation as resolved.
Resolved
{section.title}
</h3>
<div className="text-text-auth-sub font-body2 font-medium text-[15px] leading-[1.7] whitespace-pre-line break-keep tracking-[-0.01em]">
<div className="text-text-auth-sub font-body2 font-medium leading-[1.7] whitespace-pre-line break-keep tracking-[-0.01em]">
{section.content}
</div>
</div>
Expand All @@ -48,11 +51,11 @@ export default function OverviewAiReportPanel() {

{/* 주의 */}
<div className="rounded-[24px] bg-status-red/6 p-7 text-left mx-1">
<h3 className="flex items-center gap-2 text-status-red font-body1 font-bold text-[16px] mb-3 tracking-tight">
<h3 className="flex items-center gap-2 text-status-red font-body1 font-bold mb-3 tracking-tight">
<WarningIcon className="w-4.5 h-4.5 fill-current text-status-red" />
{data.warning.title}
</h3>
<p className="text-text-auth-sub font-body2 font-medium text-[15px] leading-[1.65] whitespace-pre-line break-keep">
<p className="text-text-auth-sub font-body2 font-medium leading-[1.65] whitespace-pre-line break-keep">
{data.warning.content}
</p>
</div>
Expand Down
47 changes: 16 additions & 31 deletions src/pages/dashboard/overview/OverviewDashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useState } from "react";
import { useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { toast } from "sonner";

import Button from "@/components/common/button/Button";
import Card from "@/components/common/card/Card";
import StatCard from "@/components/common/card/StatCard";
import ChartLegend from "@/components/common/chart/ChartLegend";
import Drawer from "@/components/common/drawer/Drawer";
import Toast from "@/components/common/toast/Toast";
import BudgetGaugeChart from "@/components/dashboard/charts/BudgetGaugeChart";
import { budgetGaugeChartMock } from "@/components/dashboard/charts/budgetGaugeChart.mock";
import TrafficChart from "@/components/dashboard/charts/TrafficChart";
Expand All @@ -23,17 +23,17 @@ import AiButtonSvg from "@/assets/logo/ai-요약버튼.svg?react";
export default function OverviewDashboard() {
const navigate = useNavigate();
const [isAiPanelOpen, setIsAiPanelOpen] = useState(false);
const [toast, setToast] = useState<{
message: string;
variant: "success" | "error";
} | null>(null);

const showToast = (
message: string,
variant: "success" | "error" = "success",
) => {
setToast({ message, variant });
};
const currentDate = useMemo(
() =>
new Date().toLocaleString("ko-KR", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
}),
[],
);

return (
<div className="flex flex-col gap-8 p-6 lg:p-8 w-full min-w-0">
Expand All @@ -43,14 +43,7 @@ export default function OverviewDashboard() {
통합 대시보드
</h1>
<p className="font-body2 text-text-sub">
데이터 기준 ·{" "}
{new Date().toLocaleString("ko-KR", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
})}
데이터 기준 · {currentDate}
</p>
</div>
<button
Expand Down Expand Up @@ -137,8 +130,8 @@ export default function OverviewDashboard() {
onClick: () => {
navigator.clipboard
.writeText(window.location.href)
.then(() => showToast("링크가 복사되었습니다."))
.catch(() => showToast("링크 복사에 실패했습니다.", "error"));
.then(() => toast.success("링크가 복사되었습니다."))
.catch(() => toast.error("링크 복사에 실패했습니다."));
},
},
{
Expand All @@ -164,14 +157,6 @@ export default function OverviewDashboard() {
>
<OverviewAiReportPanel />
</Drawer>

{toast && (
<Toast
message={toast.message}
variant={toast.variant}
onClose={() => setToast(null)}
/>
)}
</div>
);
}