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
49 changes: 28 additions & 21 deletions src/components/timeline/TimelineStatusLegend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,38 @@ export default function TimelineStatusLegend({
return (
<div
className={twMerge(
"flex w-fit max-w-full flex-wrap items-center justify-between gap-4 rounded-2xl bg-surface-200 px-5 py-3",
"flex min-w-0 w-full items-center gap-4 rounded-2xl bg-surface-200 px-5 py-3",
className,
)}
>
<div className="flex min-w-0 flex-1 flex-wrap items-center gap-x-12 gap-y-2">
{TIMELINE_STATUS_LEGEND_ORDER.map((status) => {
const style = TIMELINE_PERFORMANCE_STATUS_STYLE[status];
<div className="min-w-0 flex-1 overflow-x-auto">
<div className="flex w-max items-center gap-x-8">
{TIMELINE_STATUS_LEGEND_ORDER.map((status) => {
const style = TIMELINE_PERFORMANCE_STATUS_STYLE[status];

return (
<div
key={status}
className="flex min-w-0 items-center gap-2 font-body2"
>
<span
className={twMerge("h-2 w-2 shrink-0 rounded-full", style.dot)}
aria-hidden
/>
<span className="shrink-0 text-text-title">{style.label}</span>
<span className="text-text-placeholder" aria-hidden>
:
</span>
<span className="text-text-muted">{style.legendDescription}</span>
</div>
);
})}
return (
<div
key={status}
className="flex shrink-0 items-center gap-2 whitespace-nowrap font-body2"
>
<span
className={twMerge(
"h-2 w-2 shrink-0 rounded-full",
style.dot,
)}
aria-hidden
/>
<span className="shrink-0 text-text-title">{style.label}</span>
<span className="text-text-placeholder" aria-hidden>
:
</span>
<span className="text-text-muted">
{style.legendDescription}
</span>
</div>
);
})}
</div>
</div>

<div className="group/help relative shrink-0">
Expand Down
29 changes: 20 additions & 9 deletions src/constants/timeline/statusStyle.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
TIMELINE_PERFORMANCE_STATUS,
type TTimelinePerformanceStatus,
type TTimelinePerformanceStatusUi,
} from "@/types/timeline/api";

export interface ITimelinePerformanceStatusStyle {
Expand All @@ -16,14 +17,15 @@ export interface ITimelinePerformanceStatusStyle {
export const TIMELINE_STATUS_LEGEND_ORDER = [
"ON_TRACK",
"ABOVE_AVERAGE",
"AT_RISK",
] as const satisfies readonly TTimelinePerformanceStatus[];
"UNDERPERFORM",
"PENDING",
] as const satisfies readonly TTimelinePerformanceStatusUi[];

export const TIMELINE_STATUS_BASELINE_HELP =
"성과 상태는 타임라인에 설정한 비교 기간의 평균 성과를 기준으로, 현재 기간 성과가 어느 수준인지 보여줍니다.";
"성과 상태는 타임라인에 설정한 비교 기간의 평균 성과를 기준으로, 현재 기간 성과가 어느 수준인지 보여줍니다. 미정은 성과가 아직 산출되지 않은 상태입니다.";

export const TIMELINE_PERFORMANCE_STATUS_STYLE: Record<
TTimelinePerformanceStatus,
TTimelinePerformanceStatusUi,
ITimelinePerformanceStatusStyle
> = {
ON_TRACK: {
Expand All @@ -44,7 +46,7 @@ export const TIMELINE_PERFORMANCE_STATUS_STYLE: Record<
dot: "bg-oauth-naver",
ring: "ring-oauth-naver",
},
AT_RISK: {
UNDERPERFORM: {
label: "Underperform",
description: "최근 평균 대비 성과가 눈에 띄게 낮아요.",
legendDescription: "최근 평균 대비 성과가 눈에 띄게 낮음",
Expand All @@ -53,22 +55,31 @@ export const TIMELINE_PERFORMANCE_STATUS_STYLE: Record<
dot: "bg-info-red",
ring: "ring-info-red",
},
PENDING: {
label: "Pending",
description: "성과가 아직 산출되지 않았어요.",
legendDescription: "성과가 아직 산출되지 않음",
barBg: "bg-surface-300",
accent: "bg-text-muted",
dot: "bg-text-muted",
ring: "ring-text-muted",
},
};

/** API가 null/미정의/알 수 없는 값을 줄 때 ON_TRACK으로 정규화 */
/** API가 null/미정의/알 수 없는 값을 줄 때 PENDING(미정)으로 정규화 */
export function resolveTimelinePerformanceStatus(
status: string | null | undefined,
): TTimelinePerformanceStatus {
): TTimelinePerformanceStatusUi {
if (
status != null &&
(TIMELINE_PERFORMANCE_STATUS as readonly string[]).includes(status)
) {
return status as TTimelinePerformanceStatus;
}
return "ON_TRACK";
return "PENDING";
}

/** 성과 상태 → 바/패널 스타일 (미정의 시 기본 스타일) */
/** 성과 상태 → 바/패널 스타일 (미정의 시 회색 미정 스타일) */
export function resolveTimelinePerformanceStatusStyle(
status: string | null | undefined,
): ITimelinePerformanceStatusStyle {
Expand Down
4 changes: 2 additions & 2 deletions src/pages/dashboard/timeline/Timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,8 @@ export default function Timeline() {
>
<div className="flex min-h-0 flex-1 w-full min-w-0 flex-col rounded-2xl border border-surface-400/70 bg-surface-100">
<div className="flex shrink-0 flex-col gap-4 border-b border-surface-400/80 px-5 py-5">
<div className="flex items-center justify-between gap-12">
<TimelineStatusLegend />
<div className="flex items-center justify-between gap-8">
<TimelineStatusLegend className="min-w-0 flex-1" />
<Button
type="button"
size="small"
Expand Down
10 changes: 8 additions & 2 deletions src/types/timeline/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,22 @@ export type TTimelineMetric = (typeof TIMELINE_METRICS)[number];
/*성과 상태
* ON_TRACK: 최근 추세와 유사한 수준 유지
* ABOVE_AVERAGE: 최근 평균 대비 눈에 띄게 좋음
* AT_RISK: 최근 평균 대비 눈에 띄게 낮음
* UNDERPERFORM: 최근 평균 대비 눈에 띄게 낮음
* (미산출 시 null — UI에서는 PENDING으로 표시)
*/
export const TIMELINE_PERFORMANCE_STATUS = [
"ON_TRACK",
"ABOVE_AVERAGE",
"AT_RISK",
"UNDERPERFORM",
] as const;
export type TTimelinePerformanceStatus =
(typeof TIMELINE_PERFORMANCE_STATUS)[number];

/** API 3종 + UI 전용 미정(PENDING). null/미지 값은 UI에서 PENDING으로 정규화 */
export type TTimelinePerformanceStatusUi =
| TTimelinePerformanceStatus
| "PENDING";

/*비교 기간 타입*/
export const TIMELINE_COMPARISON_PERIOD_TYPES = [
"LAST_WEEK",
Expand Down
4 changes: 2 additions & 2 deletions src/types/timeline/summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { TProviderType } from "@/types/dashboard/provider";
import type {
ITimelineDailyTrend,
TTimelineMetric,
TTimelinePerformanceStatus,
TTimelinePerformanceStatusUi,
} from "@/types/timeline/api";

/*패널 상단 KPI 카드 한개*/
Expand All @@ -24,7 +24,7 @@ export interface ITimelineSummaryPlatformShare {
export interface ITimelineSummaryPanelData {
timelineName: string;
periodLabel: string;
performanceStatus: TTimelinePerformanceStatus;
performanceStatus: TTimelinePerformanceStatusUi;
aiSummary: string;
metrics: ITimelineSummaryMetric[];
platformShare: ITimelineSummaryPlatformShare[];
Expand Down
4 changes: 2 additions & 2 deletions src/types/timeline/timeline.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const TIMELINE_LIST_MOCK: ITimelineListItem[] = [
name: "리타겟팅 집중 기간",
startDate: "2026-06-10",
endDate: "2026-06-30",
performanceStatus: "AT_RISK",
performanceStatus: "UNDERPERFORM",
},
];

Expand Down Expand Up @@ -197,7 +197,7 @@ export const TIMELINE_GRID_MOCK_MONTH: ITimelineGridData = {
colStart: 16,
colEnd: 31,
row: 2,
performanceStatus: "ON_TRACK",
performanceStatus: "UNDERPERFORM",
},
],
};
Expand Down
4 changes: 2 additions & 2 deletions src/types/timeline/ui.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { TProviderType } from "@/types/dashboard/provider";
import type { TTimelinePerformanceStatus } from "@/types/timeline/api";
import type { TTimelinePerformanceStatusUi } from "@/types/timeline/api";

export type TTimelineViewUnit = "DAY" | "WEEK" | "MONTH";

Expand All @@ -22,7 +22,7 @@ export interface ITimelineCampaignBar {
colStart: number; //그리드 시작 위치
colEnd: number;
row: number;
performanceStatus: TTimelinePerformanceStatus;
performanceStatus: TTimelinePerformanceStatusUi;
}

/*그리드 + 바 묶음*/
Expand Down
Loading