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
87 changes: 37 additions & 50 deletions src/components/ads/CampaignPlatformSection.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { ReactNode } from "react";
import { twMerge } from "tailwind-merge";

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

import { canSubmitPlatformBudgetEdit } from "@/utils/ads/budgetEdit";
import {
BUDGET_EDIT_BLOCK_MESSAGES,
canSubmitPlatformBudgetEdit,
} from "@/utils/ads/budgetEdit";
import { mapPlatformProjectBudgetToGauges } from "@/utils/ads/projectBudget";
mapPlatformBudgetSummariesToGauges,
pickEditablePlatformBudget,
} from "@/utils/ads/projectBudget";

import PlatformBudgetItem from "@/components/ads/PlatformBudgetItem";
import Button from "@/components/common/button/Button";
Expand All @@ -30,55 +30,42 @@ const PLATFORM_LABEL: Record<TPlatform, string> = {

interface ICampaignPlatformSectionProps {
platform: TPlatform;
platformBudget?: IPlatformProjectBudget;
onEditBudget?: () => void;
platformBudgets?: IPlatformBudgetSummary[];
onEditBudget?: (budget: IPlatformBudgetSummary) => void;
}

export default function CampaignPlatformSection({
platform,
platformBudget,
platformBudgets = [],
onEditBudget,
}: ICampaignPlatformSectionProps) {
const gauges = platformBudget
? mapPlatformProjectBudgetToGauges(platformBudget)
: [];
const gauges = mapPlatformBudgetSummariesToGauges(platformBudgets);
const editTarget = pickEditablePlatformBudget(platformBudgets);

const editCheck = canSubmitPlatformBudgetEdit(platformBudget);
const isBudgetEditDisabled = !onEditBudget || !editCheck.ok;
const budgetEditDisabledReason = !onEditBudget
? undefined
: editCheck.ok
? undefined
: BUDGET_EDIT_BLOCK_MESSAGES[editCheck.reason];
const budgetEditHintId = `budget-edit-hint-${platform}`;
const editCheck = canSubmitPlatformBudgetEdit(editTarget ?? undefined);
const isBudgetEditDisabled = !onEditBudget || !editTarget || !editCheck.ok;

const campaignName = platformBudgets.find(
(row) => row.adCampaignName,
)?.adCampaignName;

const budgetEditAction = (
<div className="flex flex-col items-end gap-1">
<Button
type="button"
size="small"
variant="outline"
className={twMerge(
"h-9 shrink-0 px-3.5",
isBudgetEditDisabled && "opacity-60",
)}
onClick={onEditBudget}
disabled={isBudgetEditDisabled}
aria-describedby={
budgetEditDisabledReason ? budgetEditHintId : undefined
}
>
예산 수정
</Button>
{budgetEditDisabledReason ? (
<p
id={budgetEditHintId}
className="max-w-48 text-right font-caption text-text-muted"
>
{budgetEditDisabledReason}
</p>
) : null}
</div>
<Button
type="button"
size="small"
variant="outline"
className={twMerge(
"h-9 shrink-0 px-3.5",
isBudgetEditDisabled && "opacity-60",
)}
onClick={() => {
if (!editTarget || !onEditBudget) return;
onEditBudget(editTarget);
}}
disabled={isBudgetEditDisabled}
>
예산 수정
</Button>
);

return (
Expand All @@ -100,9 +87,9 @@ export default function CampaignPlatformSection({
{PLATFORM_LABEL[platform]}
</span>
</h2>
{platformBudget?.adCampaignName ? (
{campaignName ? (
<p className="truncate font-body2 text-text-muted">
{platformBudget.adCampaignName}
{campaignName}
</p>
) : null}
</div>
Expand All @@ -115,12 +102,12 @@ export default function CampaignPlatformSection({
예산 정보가 없습니다.
</p>
) : (
<div className="w-full min-w-0">
{gauges.map((gauge) => (
<div className="flex w-full min-w-0 flex-col gap-5">
{gauges.map((gauge, index) => (
<PlatformBudgetItem
key={`${platform}-${gauge.label}`}
{...gauge}
headerAction={budgetEditAction}
headerAction={index === 0 ? budgetEditAction : undefined}
/>
))}
</div>
Expand Down
21 changes: 4 additions & 17 deletions src/components/ads/CampaignRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import { twMerge } from "tailwind-merge";

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

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

import GoogleLogo from "@/assets/logo/social-logo/circle/google-circle.svg?react";
import MetaLogo from "@/assets/logo/social-logo/circle/meta-circle.svg?react";
import NaverLogo from "@/assets/logo/social-logo/circle/naver-circle.svg?react";
Expand All @@ -14,19 +12,14 @@ interface ICampaignRowProps {
name: string;
providers: TPlatform[];
status: TStatus;
budgetUsageRate: number;
isSelected: boolean;
onToggleSelect: () => void;
onClick?: () => void;
}

/** 헤더·행·스켈레톤 플랫폼 열 — 모바일에서 로고 최대 3개 폭 예약 */
export const CAMPAIGN_PLATFORM_COL_CLASS =
"mr-24 w-28 shrink-0 tablet:mr-20 tablet:w-24 mobile:mr-2 mobile:w-16";

/** 헤더·행·스켈레톤 예산 열 */
export const CAMPAIGN_BUDGET_COL_CLASS =
"w-[28%] shrink-0 tablet:w-[26%] mobile:w-[30%] mobile:max-w-28";
"w-28 shrink-0 tablet:w-24 mobile:w-16";

const LogoMap: Record<TPlatform, ReactNode> = {
meta: (
Expand All @@ -44,7 +37,6 @@ export default function CampaignRow({
name,
providers,
status,
budgetUsageRate,
isSelected,
onToggleSelect,
onClick,
Expand Down Expand Up @@ -99,11 +91,12 @@ export default function CampaignRow({
<div
className={twMerge(
CAMPAIGN_PLATFORM_COL_CLASS,
"flex items-center justify-start",
"flex items-center justify-end",
isPaused && "opacity-80",
)}
>
{providers && providers.length > 0 ? (
<div className="flex items-center justify-start gap-1 mobile:gap-0.5">
<div className="flex items-center justify-end gap-2 mobile:gap-1.5">
{providers.map((p, idx) => (
<span key={idx} className="flex shrink-0">
{LogoMap[p.toLowerCase() as TPlatform] ?? (
Expand All @@ -116,12 +109,6 @@ export default function CampaignRow({
<div className="font-caption text-text-placeholder">미연결</div>
)}
</div>

<div
className={twMerge(CAMPAIGN_BUDGET_COL_CLASS, isPaused && "opacity-80")}
>
<ProgressBar value={budgetUsageRate} />
</div>
</li>
);
}
16 changes: 2 additions & 14 deletions src/components/ads/CampaignTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import { twMerge } from "tailwind-merge";

import type { ICampaign } from "@/types/ads/campaign";

import CampaignRow, {
CAMPAIGN_BUDGET_COL_CLASS,
CAMPAIGN_PLATFORM_COL_CLASS,
} from "./CampaignRow";
import CampaignRow, { CAMPAIGN_PLATFORM_COL_CLASS } from "./CampaignRow";

interface ICampaignTableProps {
campaigns: ICampaign[];
Expand Down Expand Up @@ -77,20 +74,11 @@ export default function CampaignTable({
<div
className={twMerge(
CAMPAIGN_PLATFORM_COL_CLASS,
"whitespace-nowrap text-left font-label text-text-muted",
"whitespace-nowrap text-right font-label text-text-muted",
)}
>
<span className="mobile:hidden">플랫폼</span>
</div>
<div
className={twMerge(
CAMPAIGN_BUDGET_COL_CLASS,
"text-left font-label text-text-muted",
)}
>
<span className="tablet:hidden">예산 소진 현황</span>
<span className="hidden tablet:inline">예산</span>
</div>
</div>

<ul className="m-0 list-none p-0">
Expand Down
14 changes: 7 additions & 7 deletions src/components/ads/EditPlatformBudgetModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { zodResolver } from "@hookform/resolvers/zod";
import { toast } from "sonner";

import type { IPlatformProjectBudget } from "@/types/ads/campaign";
import type { IPlatformBudgetSummary } from "@/types/ads/campaign";

import {
buildUpdatePlatformBudgetVariables,
Expand Down Expand Up @@ -89,7 +89,7 @@ const BudgetAmountInput = forwardRef<HTMLInputElement, IBudgetAmountInputProps>(
},
);

const PROVIDER_LABEL: Record<IPlatformProjectBudget["providerType"], string> = {
const PROVIDER_LABEL: Record<IPlatformBudgetSummary["provider"], string> = {
META: "Meta",
GOOGLE: "Google",
NAVER: "NAVER",
Expand All @@ -99,7 +99,7 @@ interface IEditPlatformBudgetModalProps {
isOpen: boolean;
onClose: () => void;
onClosed?: () => void;
budget: IPlatformProjectBudget | null;
budget: IPlatformBudgetSummary | null;
orgId: number;
projectId: number;
}
Expand All @@ -112,7 +112,7 @@ export default function EditPlatformBudgetModal({
orgId,
projectId,
}: IEditPlatformBudgetModalProps) {
const budgetRef = useRef<IPlatformProjectBudget | null>(null);
const budgetRef = useRef<IPlatformBudgetSummary | null>(null);
if (budget) budgetRef.current = budget;

const activeBudget = budget ?? budgetRef.current;
Expand Down Expand Up @@ -184,12 +184,12 @@ export default function EditPlatformBudgetModal({
onExitComplete={onClosed}
size="md"
padding="lg"
title={`${PROVIDER_LABEL[activeBudget.providerType]} 예산 수정`}
title={`${PROVIDER_LABEL[activeBudget.provider]} 예산 수정`}
disableOverlayClick={isPending}
>
<div className="flex w-full flex-col items-start">
<p aria-hidden className="mb-2 font-heading3 text-text-title">
{PROVIDER_LABEL[activeBudget.providerType]} 예산 수정
{PROVIDER_LABEL[activeBudget.provider]} 예산 수정
</p>
{activeBudget.adCampaignName ? (
<p className="mb-1 max-w-full truncate font-body2 text-text-muted">
Expand All @@ -201,7 +201,7 @@ export default function EditPlatformBudgetModal({
</p>

<form
key={`${activeBudget.providerType}-${effectiveBudget?.activeBudgetType ?? "daily"}`}
key={`${activeBudget.provider}-${effectiveBudget?.activeBudgetType ?? "daily"}`}
onSubmit={handleSubmit(onSubmit)}
className="flex w-full flex-col gap-8"
noValidate
Expand Down
13 changes: 2 additions & 11 deletions src/components/ads/skeleton/AdsSkeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ import {
getAdListTableHeaderGridClass,
getAdListTableRowGridClass,
} from "@/components/ads/AdRow";
import {
CAMPAIGN_BUDGET_COL_CLASS,
CAMPAIGN_PLATFORM_COL_CLASS,
} from "@/components/ads/CampaignRow";
import { CAMPAIGN_PLATFORM_COL_CLASS } from "@/components/ads/CampaignRow";
import Card from "@/components/common/card/Card";
import {
Skeleton,
Expand All @@ -33,15 +30,12 @@ function CampaignTableRowSkeleton() {
<div
className={twMerge(
CAMPAIGN_PLATFORM_COL_CLASS,
"flex items-center gap-1 mobile:gap-0.5",
"flex items-center justify-end gap-2 mobile:gap-1.5",
)}
>
<SkeletonCircle className="h-7 w-7 tablet:h-6 tablet:w-6 mobile:h-5 mobile:w-5" />
<SkeletonCircle className="h-7 w-7 tablet:h-6 tablet:w-6 mobile:h-5 mobile:w-5" />
</div>
<div className={CAMPAIGN_BUDGET_COL_CLASS}>
<Skeleton className="h-2 w-full rounded-full" />
</div>
</li>
);
}
Expand All @@ -60,9 +54,6 @@ export function CampaignTableSkeleton() {
<Skeleton className="h-4 w-16" />
</div>
<div className={CAMPAIGN_PLATFORM_COL_CLASS} aria-hidden />
<div className={CAMPAIGN_BUDGET_COL_CLASS}>
<Skeleton className="h-4 w-24 mobile:w-16" />
</div>
</div>

<ul className="m-0 list-none p-0">
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/ads/useUpdatePlatformBudget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function useUpdatePlatformBudget(orgId: number, projectId: number) {
throw new Error("일일 예산을 입력해 주세요.");
}
if (
vars.activeBudgetType === "LIFETIME" &&
vars.activeBudgetType === "TOTAL" &&
vars.lifetimeBudget === undefined
) {
throw new Error("전체 예산을 입력해 주세요.");
Expand All @@ -63,7 +63,7 @@ export function useUpdatePlatformBudget(orgId: number, projectId: number) {
throw new Error("일일 예산을 입력해 주세요.");
}
if (
vars.activeBudgetType === "LIFETIME" &&
vars.activeBudgetType === "TOTAL" &&
vars.lifetimeBudget === undefined
) {
throw new Error("전체 예산을 입력해 주세요.");
Expand Down
Loading
Loading