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
11 changes: 11 additions & 0 deletions src/assets/icon/ads/google-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/assets/icon/ads/kakao-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/icon/ads/naver-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
65 changes: 65 additions & 0 deletions src/components/ads/CampaignRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { ReactNode } from "react";

import type { TCampaignStatus, TPlatform } from "@/types/ads/campaign";

import Badge from "../common/badge/Badge";
import ProgressBar from "../common/progressbar/ProgressBar";

import GoogleLogo from "@/assets/icon/ads/google-logo.svg?react";
import KakaoLogo from "@/assets/icon/ads/kakao-logo.svg?react";
import NaverLogo from "@/assets/icon/ads/naver-logo.svg?react";

interface ICampaignRowProps {
platforms: TPlatform[];
name: string;
status: TCampaignStatus;
statusText: string;
progress: number;
}

const LogoMap: Record<TPlatform, ReactNode> = {
kakao: <KakaoLogo className="w-full h-full" />,
google: <GoogleLogo className="w-full h-full" />,
naver: <NaverLogo className="w-full h-full" />,
};

export default function CampaignRow({
platforms,
name,
status,
statusText,
progress,
}: ICampaignRowProps) {
return (
<li className="flex items-center px-7 py-5 border-b border-bg-disabled hover:bg-gray-50 hover:cursor-pointer transition-colors list-none">
{/* 플랫폼 */}
<div className="flex w-[20%] shrink-0">
{platforms.map((p, idx) => (
<div
key={idx}
className="flex h-8 w-8 mr-3 items-center justify-center rounded-full shadow-sm overflow-hidden shrink-0"
>
{LogoMap[p]}
</div>
))}
</div>

{/* 캠페인 명 */}
<div className="w-[35%] min-w-0 pr-10 shrink-0">
<div className="font-body1 text-text-main truncate">{name}</div>
</div>

{/* 동기화 상태 */}
<div className="w-[15%] shrink-0 pr-10">
<Badge variant={status} size="sm">
{statusText}
</Badge>
</div>

{/* 예산 소진 현황 */}
<div className="w-[30%] shrink-0">
<ProgressBar value={progress} />
</div>
</li>
);
}
70 changes: 70 additions & 0 deletions src/components/ads/CampaignTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { ICampaign } from "@/types/ads/campaign";

import CampaignRow from "./CampaignRow";

export default function CampaignTable() {
const campaigns: ICampaign[] = [
{
id: 1,
platforms: ["kakao", "google", "naver"],
name: "봄 프로모션 캠페인",
status: "syncing",
statusText: "동기화 중",
progress: 65,
},
{
id: 2,
platforms: ["kakao", "naver"],
name: "폭염 대비 냉장 가전 얼리버드 특가 프로모션",
status: "syncing",
statusText: "동기화 중",
progress: 32,
},
{
id: 3,
platforms: ["kakao"],
name: "가을 감성 FW 시즌 신상 코트 선주문 캐시백 이벤트",
status: "inactive",
statusText: "미동기화",
progress: 0,
},
{
id: 4,
platforms: ["google", "naver"],
name: "크리스마스 홈파티 소품 대방출 및 연말 결산 세일",
status: "success",
statusText: "완료",
progress: 98,
},
];

return (
<div className="w-full bg-white overflow-x-auto">
<div className="min-w-180">
{/* Table Header */}
<div className="flex items-center px-7 py-3 border-b border-bg-disabled">
<div className="w-[20%] font-body1 text-text-main">플랫폼</div>
<div className="w-[35%] font-body1 text-text-main">캠페인 명</div>
<div className="w-[15%] font-body1 text-text-main">동기화 상태</div>
<div className="w-[30%] font-body1 text-text-main">
예산 소진 현황
</div>
</div>

{/* Row */}
<ul className="divide-y divide-bg-disabled">
{campaigns.map((campaign) => (
<CampaignRow
key={campaign.id}
platforms={campaign.platforms}
name={campaign.name}
status={campaign.status}
statusText={campaign.statusText}
progress={campaign.progress}
/>
))}
</ul>
</div>
</div>
);
}
19 changes: 19 additions & 0 deletions src/components/common/progressbar/ProgressBar.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Meta, StoryObj } from "@storybook/react";

import ProgressBar from "./ProgressBar";

const meta: Meta<typeof ProgressBar> = {
title: "Common/ProgressBar",
component: ProgressBar,
parameters: { layout: "padded" },
};

export default meta;
type TProgressBarStory = StoryObj<typeof ProgressBar>;

export const Default: TProgressBarStory = {
args: {
value: 70,
className: "w-[300px]",
},
};
34 changes: 34 additions & 0 deletions src/components/common/progressbar/ProgressBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";

interface IProgressBarProps extends HTMLAttributes<HTMLDivElement> {
value: number;
}

export default function ProgressBar({
value,
className,
...rest
}: IProgressBarProps) {
const progress = Math.min(Math.max(value, 0), 100);

return (
<div
className={twMerge("flex items-center gap-3 w-full", className)}
{...rest}
>
{/* 바 전체 */}
<div className="h-3 flex-1 rounded-full bg-bg-disabled overflow-hidden relative shrink-0">
{/* 진행률 바 */}
<div
className="h-full bg-chart-3 transition-all duration-300"
style={{ width: `${progress}%` }}
/>
</div>
{/* 퍼센트 텍스트 */}
<span className="font-body1 shrink-0 w-11 text-text-sub min-w-8 text-right">
{progress}%
</span>
</div>
);
}
20 changes: 19 additions & 1 deletion src/pages/ads/list/AdsListPage.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
import CampaignTable from "@/components/ads/CampaignTable";

export default function AdsListPage() {
return <div>AdsListPage</div>;
return (
<section className="flex flex-col gap-16 bg-white rounded-component-lg py-15 px-25 min-h-[90vh]">
<header className="w-full flex flex-col">
<h1 className="font-heading2 text-text-main">광고 운영 관리</h1>
<p className="font-body2 text-text-placeholder mt-3">
연결된 캠페인 및 광고 소재의 상세 운영 설정을 확인하고 제어할 수
있습니다.
</p>
</header>
{/* 테이블 */}
<div className="w-full flex flex-col">
<CampaignTable />
</div>
{/* 하단 배너 */}
<div />
</section>
);
}
11 changes: 11 additions & 0 deletions src/types/ads/campaign.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type TPlatform = "kakao" | "google" | "naver";
export type TCampaignStatus = "inactive" | "syncing" | "success";

export interface ICampaign {
id: number;
platforms: TPlatform[];
name: string;
status: TCampaignStatus;
statusText: string;
progress: number;
}