-
Notifications
You must be signed in to change notification settings - Fork 1
[Feature/#66] 통합 대시보드 공용 카드 UI 컴포넌트 추출 및 레이아웃 구현 #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| import type { Meta, StoryObj } from "@storybook/react"; | ||
|
|
||
| import Card from "./Card"; | ||
| import Button from "../button/Button"; | ||
|
|
||
| const meta: Meta<typeof Card> = { | ||
| title: "Common/Card", | ||
| component: Card, | ||
| args: { | ||
| title: "카드 제목", | ||
| className: "w-80", | ||
| }, | ||
| parameters: { layout: "centered" }, | ||
| }; | ||
|
|
||
| export default meta; | ||
| type TCardStory = StoryObj<typeof Card>; | ||
|
|
||
| export const Default: TCardStory = { | ||
| args: { | ||
| title: undefined, | ||
| children: ( | ||
| <p className="font-body2 text-text-sub">카드 내용이 들어갑니다.</p> | ||
| ), | ||
| }, | ||
| }; | ||
|
|
||
| export const WithTitle: TCardStory = { | ||
| args: { | ||
| children: ( | ||
| <p className="font-body2 text-text-sub">카드 내용이 들어갑니다.</p> | ||
| ), | ||
| }, | ||
| }; | ||
|
|
||
| export const WithDescription: TCardStory = { | ||
| args: { | ||
| description: "제목에 대한 설명이 들어갑니다.", | ||
| children: ( | ||
| <p className="font-body2 text-text-sub">카드 내용이 들어갑니다.</p> | ||
| ), | ||
| }, | ||
| }; | ||
|
|
||
| export const WithRightElement: TCardStory = { | ||
| args: { | ||
| RightElement: <Button size="small">플랫폼 대시보드 보기</Button>, | ||
| children: ( | ||
| <p className="font-body2 text-text-sub">카드 내용이 들어갑니다.</p> | ||
| ), | ||
| }, | ||
| }; | ||
|
|
||
| export const WithDescriptionAndRightElement: TCardStory = { | ||
| args: { | ||
| description: "데이터 기준 | 2026.01.06 09:13", | ||
| RightElement: <Button size="small">보기</Button>, | ||
| children: ( | ||
| <p className="font-body2 text-text-sub">카드 내용이 들어갑니다.</p> | ||
| ), | ||
| }, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import type { HTMLAttributes, ReactNode } from "react"; | ||
| import { twMerge } from "tailwind-merge"; | ||
|
|
||
| export interface ICardProps extends HTMLAttributes<HTMLDivElement> { | ||
| title?: string; | ||
| description?: string; | ||
| RightElement?: ReactNode; | ||
| } | ||
|
|
||
| export default function Card({ | ||
| title, | ||
| description, | ||
| RightElement, | ||
| children, | ||
| className, | ||
| ...rest | ||
| }: ICardProps) { | ||
| const hasHeader = title || description || RightElement; | ||
|
|
||
| return ( | ||
| <div | ||
| className={twMerge( | ||
| "bg-white rounded-component-md border border-chart-inactive p-5", | ||
| className, | ||
| )} | ||
| {...rest} | ||
| > | ||
| {hasHeader && ( | ||
| <div className="flex flex-wrap items-start justify-between gap-2 mb-4"> | ||
| <div className="flex flex-col gap-0.5"> | ||
| {title && <h3 className="font-body1 text-text-main">{title}</h3>} | ||
| {description && ( | ||
| <p className="font-caption text-text-sub">{description}</p> | ||
| )} | ||
| </div> | ||
| {RightElement} | ||
| </div> | ||
| )} | ||
| {children} | ||
| </div> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import { Fragment } from "react"; | ||
| import type { Meta, StoryObj } from "@storybook/react"; | ||
|
|
||
| import StatCard from "./StatCard"; | ||
|
|
||
| const meta: Meta<typeof StatCard> = { | ||
| title: "Common/StatCard", | ||
| component: StatCard, | ||
| args: { | ||
| title: "클릭수(CTR)", | ||
| value: "1,280", | ||
| }, | ||
| parameters: { layout: "centered" }, | ||
| }; | ||
|
|
||
| export default meta; | ||
| type TStatCardStory = StoryObj<typeof StatCard>; | ||
|
|
||
| export const Default: TStatCardStory = {}; | ||
|
|
||
| export const WithTrendUp: TStatCardStory = { | ||
| args: { | ||
| trend: { direction: "up", value: "13.77%" }, | ||
| }, | ||
| }; | ||
|
|
||
| export const WithTrendDown: TStatCardStory = { | ||
| args: { | ||
| title: "노출수", | ||
| value: "3,175,630", | ||
| trend: { direction: "down", value: "13.77%" }, | ||
| }, | ||
| }; | ||
|
|
||
| export const KpiGroup: TStatCardStory = { | ||
| render: () => ( | ||
| <div className="flex bg-white rounded-component-md border border-chart-inactive w-200"> | ||
| {[ | ||
| { | ||
| title: "클릭수(CTR)", | ||
| value: "1,280", | ||
| trend: { direction: "up" as const, value: "13.77%" }, | ||
| }, | ||
| { | ||
| title: "노출수", | ||
| value: "3,175,630", | ||
| trend: { direction: "down" as const, value: "13.77%" }, | ||
| }, | ||
| { | ||
| title: "전환율(CVR)", | ||
| value: "27.09%", | ||
| trend: { direction: "up" as const, value: "13.77%" }, | ||
| }, | ||
| { | ||
| title: "광고비 대비 매출", | ||
| value: "37.58%", | ||
| trend: { direction: "down" as const, value: "13.77%" }, | ||
| }, | ||
| ].map((kpi, index) => ( | ||
| <Fragment key={kpi.title}> | ||
| {index > 0 && <div className="w-px bg-bg-disabled my-5" />} | ||
| <StatCard className="flex-1 border-0 rounded-none" {...kpi} /> | ||
| </Fragment> | ||
| ))} | ||
| </div> | ||
| ), | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| import type { HTMLAttributes } from "react"; | ||
| import { twMerge } from "tailwind-merge"; | ||
|
|
||
| import TrendDownIcon from "@/assets/icon/dashboard/trend-down.svg?react"; | ||
| import TrendUpIcon from "@/assets/icon/dashboard/trend-up.svg?react"; | ||
|
|
||
| export interface ITrend { | ||
| direction: "up" | "down"; | ||
| value: string; | ||
| } | ||
|
|
||
| export interface IStatCardProps extends HTMLAttributes<HTMLDivElement> { | ||
| title: string; | ||
| value: string | number; | ||
| trend?: ITrend; | ||
| } | ||
|
|
||
| const trendClasses: Record<ITrend["direction"], string> = { | ||
| up: "bg-status-red/10 text-status-red", | ||
| down: "bg-status-blue/10 text-status-blue", | ||
| }; | ||
|
|
||
| export default function StatCard({ | ||
| title, | ||
| value, | ||
| trend, | ||
| className, | ||
| ...rest | ||
| }: IStatCardProps) { | ||
| return ( | ||
| <div | ||
| className={twMerge( | ||
| "bg-white rounded-component-md border border-chart-inactive p-5 flex flex-col gap-2", | ||
| className, | ||
| )} | ||
| {...rest} | ||
| > | ||
| <p className="font-body2 text-text-main">{title}</p> | ||
| <p className="font-heading2 text-text-main">{value}</p> | ||
| {trend && ( | ||
| <span | ||
| aria-label={`${trend.value} ${trend.direction === "up" ? "상승" : "하락"}`} | ||
| className={twMerge( | ||
| "inline-flex items-center gap-1 px-2 py-1 rounded-full font-caption w-fit", | ||
| trendClasses[trend.direction], | ||
| )} | ||
| > | ||
| {trend.direction === "up" ? ( | ||
| <TrendUpIcon aria-hidden className="w-4 h-4" /> | ||
| ) : ( | ||
| <TrendDownIcon aria-hidden className="w-4 h-4" /> | ||
| )} | ||
| {trend.value} | ||
| </span> | ||
| )} | ||
| </div> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -333,3 +333,4 @@ button { | |
| animation: shimmer 1.5s infinite linear; | ||
| } | ||
| } | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,53 @@ | ||
| import { Fragment } from "react"; | ||
|
|
||
| import Button from "@/components/common/button/Button"; | ||
| import Card from "@/components/common/card/Card"; | ||
| import StatCard from "@/components/common/card/StatCard"; | ||
|
|
||
| import { overviewMockData } from "./overview.mock"; | ||
|
|
||
| export default function OverviewDashboard() { | ||
| return <div>OverviewDashboard</div>; | ||
| return ( | ||
| <div className="flex flex-col gap-5 p-6"> | ||
| <div className="flex items-center justify-between"> | ||
| <div> | ||
| <h1 className="font-heading3 text-text-main">통합 대시보드</h1> | ||
| <p className="font-caption text-text-sub mt-1"> | ||
| 데이터 기준 | 2026.01.06 09:13 | ||
| </p> | ||
| </div> | ||
| {/* TODO: AI 요약 버튼 */} | ||
| </div> | ||
|
|
||
| <div className="grid grid-cols-2 bg-white rounded-component-md border border-chart-inactive lg:flex"> | ||
| {overviewMockData.kpis.map((kpi, index) => ( | ||
| <Fragment key={kpi.title}> | ||
| {index > 0 && ( | ||
| <div className="hidden w-px bg-bg-surface lg:my-5 lg:block" /> | ||
| )} | ||
| <div className="relative flex-1"> | ||
| <StatCard className="border-0 rounded-none" {...kpi} /> | ||
| {index % 2 === 0 && ( | ||
| <div className="absolute right-0 top-4 bottom-4 w-px bg-bg-surface lg:hidden" /> | ||
| )} | ||
| {index < 2 && ( | ||
| <div className="absolute bottom-0 left-4 right-4 h-px bg-bg-surface lg:hidden" /> | ||
| )} | ||
| </div> | ||
| </Fragment> | ||
| ))} | ||
| </div> | ||
|
|
||
| <div className="grid grid-cols-1 lg:grid-cols-[5fr_3fr] gap-4"> | ||
| <Card title="실시간 트래픽 변화" className="min-h-100" /> | ||
| <Card title="예산 소진 현황" className="min-h-100" /> | ||
| </div> | ||
|
|
||
| <Card | ||
| title="플랫폼별 비교" | ||
| className="min-h-80" | ||
| RightElement={<Button size="small">플랫폼 대시보드 보기</Button>} | ||
| /> | ||
| </div> | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import type { IOverviewData } from "@/types/dashboard/overview"; | ||
|
|
||
| export const overviewMockData: IOverviewData = { | ||
| kpis: [ | ||
| { | ||
| title: "클릭수(CTR)", | ||
| value: "1,280", | ||
| trend: { direction: "up", value: "13.77%" }, | ||
| }, | ||
| { | ||
| title: "노출수", | ||
| value: "3,175,630", | ||
| trend: { direction: "down", value: "13.77%" }, | ||
| }, | ||
| { | ||
| title: "전환율(CVR)", | ||
| value: "27.09%", | ||
| trend: { direction: "up", value: "13.77%" }, | ||
| }, | ||
| { | ||
| title: "광고비 대비 매출", | ||
| value: "37.58%", | ||
| trend: { direction: "down", value: "13.77%" }, | ||
| }, | ||
| ], | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| interface ITrend { | ||
| direction: "up" | "down"; | ||
| value: string; | ||
| } | ||
|
|
||
| export interface IKpiMetric { | ||
| title: string; | ||
| value: string | number; | ||
| trend?: ITrend; | ||
| } | ||
|
|
||
| export interface IOverviewData { | ||
| kpis: IKpiMetric[]; | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.