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
1 change: 1 addition & 0 deletions app/routes/business/calendar/api/calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface CampaignCollaboration {
endDate: string;
keyword?: string;
type: "APPLIED" | "SENT" | "RECEIVED";
category?: string;
}

interface CollaborationResponse {
Expand Down
2 changes: 2 additions & 0 deletions app/routes/business/calendar/calendar-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ export default function CalendarContent() {
key={`${cp.campaignId}-${cp.proposalId}-${cp.status}`}
campaignId={cp.campaignId}
proposalId={cp.proposalId ?? undefined}
brandId={cp.brandId}
category={cp.category || "beauty"}
type={cp.type}
brand={cp.brandName}
title={cp.title}
Expand Down
33 changes: 27 additions & 6 deletions app/routes/business/components/CampaignBrandCard.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useNavigate } from "react-router-dom";
import chatIcon from "../../../assets/chat-icon.svg";
import arrowRightIcon from "../../../assets/icon/arrow-right.svg";

Expand All @@ -8,6 +9,8 @@ interface CampaignBrandCardProps {
brandTags?: string[];
brandImageUrl?: string;
matchingRate?: number;
brandId?: number | string;
category?: string;
}

export default function CampaignBrandCard({
Expand All @@ -16,8 +19,19 @@ export default function CampaignBrandCard({
brandName,
brandTags,
brandImageUrl,
matchingRate // Props
matchingRate,
brandId,
category
}: CampaignBrandCardProps) {
const navigate = useNavigate();
const handleBrandClick = (e: React.MouseEvent) => {
e.stopPropagation();
if (brandId && category) {
navigate(`/brand?brandId=${brandId}&domain=${category}`);
} else {
console.warn("brandId 또는 category 정보가 부족합니다.");
}
};
return (
<section className="bg-bg-w p-5 flex flex-col gap-4 -mx-4 -mt-6">
{/* 상단 브랜드 정보 */}
Expand All @@ -28,22 +42,29 @@ export default function CampaignBrandCard({
<img src={brandImageUrl} alt={brandName} className="w-full h-full object-cover" />
</div>

<div className="flex flex-col gap-1">
<div className="flex flex-col justify-center gap-1">
<div className="flex items-center gap-1">
<h2 className="text-title1 text-text-black">
<h2 className="text-title1 text-text-black leading-tight">
{brandName || "브랜드명"}
</h2>
<img src={arrowRightIcon} alt="arrow"/>
<button
type="button"
onClick={handleBrandClick}
className="flex items-center justify-center active:scale-90 transition-transform p-1"
aria-label="브랜드 상세 보기"
>
<img src={arrowRightIcon} alt="arrow" />
</button>
</div>
<p className="text-callout1 text-text-gray3 mt-3">
<p className="text-callout1 text-text-gray3 mt-1">
{brandTags && brandTags.length > 0
? brandTags.map(tag => `#${tag}`).join(" ")
: "#태그정보없음"}
</p>
</div>
</div>

<div className="flex flex-col items-end gap-1">
<div className="flex flex-col items-end justify-center h-[64px]">
<div className="flex items-baseline gap-1">
<span className="text-title5 text-core-1">매칭률</span>
<span className="text-title6 text-core-1">{matchingRate || 0}%</span>
Expand Down
17 changes: 16 additions & 1 deletion app/routes/business/components/CampaignCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import Card from "../../../components/common/Card";

interface CampaignCardProps {
brand: string;
brandId?: string | number;
category?: string;
title: string;
startDate: string;
endDate: string;
Expand All @@ -18,6 +20,8 @@ interface CampaignCardProps {

export default function CampaignCard({
brand,
brandId,
category = "beauty",
title,
startDate,
endDate,
Expand All @@ -30,6 +34,14 @@ export default function CampaignCard({

const navigate = useNavigate();

const handleBrandClick = (e: React.MouseEvent) => {
e.stopPropagation();
if (brandId) {
// 2. 알려주신 경로 규칙 적용 (Query Parameter 방식)
navigate(`/brand?brandId=${brandId}&domain=${category}`);
}
};

const handleDetailClick = () => {
// 사용자가 제공한 라우팅 규칙에 따른 분기 로직
const id = proposalId || campaignId;
Expand Down Expand Up @@ -62,7 +74,10 @@ export default function CampaignCard({

<div className="flex flex-col w-full gap-1">
{/* 브랜드명 */}
<div className="flex items-center gap-1">
<div
className="flex items-center gap-1 cursor-pointer w-fit"
onClick={handleBrandClick}
>
<span className="text-[17px] font-bold text-text-black leading-tight">
{brand}
</span>
Expand Down
13 changes: 11 additions & 2 deletions app/routes/business/proposal/received-proposal-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export default function ReceivedProposalContent() {
};
const closeModal = () => {
setModalType("none");
setRejectReason("");
setRejectReason("");
};

const handleRejectClick = () => setModalType("reject");
Expand Down Expand Up @@ -113,6 +113,13 @@ export default function ReceivedProposalContent() {

const formatDate = (dateStr: string) => (dateStr || "").replace(/-/g, ". ");

const STATUS_TEXT_MAP: Record<string, string> = {
REVIEWING: "검토 중",
MATCHED: "매칭 완료",
REJECTED: "거절됨",
CANCELED: "취소됨",
};

if (isLoading) return <LoadingSpinner className="py-10" />;
if (!proposal) return <div className="p-10 text-center text-text-gray3 font-pretendard">데이터를 찾을 수 없습니다.</div>;

Expand All @@ -126,11 +133,13 @@ export default function ReceivedProposalContent() {
<div className="bg-[var(--color-bg-w)] px-4 py-6 flex flex-col gap-2">
<CampaignBrandCard
showChatSection={false}
statusText={proposal.status === "MATCHED" ? "매칭 완료" : "검토 중"}
statusText={STATUS_TEXT_MAP[proposal.status] || proposal.status}
brandName={brand?.brandName}
brandTags={brand?.brandTags}
brandImageUrl={brand?.brandImageUrl}
matchingRate={brand?.matchingRate}
brandId={brand?.brandId || proposal.brandId}
category={proposal.contentTags.categories[0]?.name || "beauty"}
/>
<div>
<h2 className="text-title1 text-text-black">{proposal.title}</h2>
Expand Down
56 changes: 34 additions & 22 deletions app/routes/business/proposal/sent-proposal-content.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useEffect } from "react";
import { useSearchParams, useNavigate } from "react-router-dom";
import { getProposalDetail, type ProposalDetail} from "./api/proposal";
import { getProposalDetail, type ProposalDetail } from "./api/proposal";
import { getBrandSummary, type BrandSummary } from "./api/brand";
import { getProfileCard, type ProfileCard } from "./api/user";
import { cancelCampaignProposal } from "./api/proposal";
Expand Down Expand Up @@ -28,11 +28,18 @@ export default function ProposalContent() {

const proposalId = searchParams.get("proposalId");

const STATUS_TEXT_MAP: Record<string, string> = {
REVIEWING: "검토 중",
MATCHED: "매칭 완료",
REJECTED: "거절됨",
CANCELED: "취소됨",
};

useEffect(() => {
if (!proposalId) {
setIsLoading(false);
return;
}
setIsLoading(false);
return;
}

const fetchData = async () => {
console.log("실제 넘길 ID:", proposalId);
Expand All @@ -46,7 +53,7 @@ export default function ProposalContent() {
setBrand(brandResult);
}

const profileResult = await getProfileCard();
const profileResult = await getProfileCard();
setProfileCard(profileResult);


Expand All @@ -66,21 +73,21 @@ export default function ProposalContent() {

const handleCancel = async () => {
if (!proposalId) return;

if (!window.confirm("제안을 취소하시겠습니까?")) return;

try {
const response = await cancelCampaignProposal(proposalId);

if (response.isSuccess) {
alert("캠페인 제안을 취소했습니다.");
navigate(-1);
navigate(-1);
}
} catch (error: unknown) {
console.error("제안 취소 실패:", error);

let errorMessage = "취소 중 오류가 발생했습니다.";

if (error instanceof Error) {
errorMessage = error.message;
} else if (typeof error === "string") {
Expand All @@ -100,11 +107,13 @@ export default function ProposalContent() {
<div className="px-4 py-6">
<CampaignBrandCard
showChatSection={false}
statusText={data.status}
statusText={STATUS_TEXT_MAP[data.status] || data.status}
brandName={brand?.brandName}
brandTags={brand?.brandTags || []}
brandImageUrl={brand?.brandImageUrl}
matchingRate={brand?.matchingRate}
brandId={brand?.brandId || data.brandId}
category={data.contentTags.categories[0]?.name || "beauty"}
/>

<div className="flex flex-col gap-4">
Expand All @@ -113,7 +122,10 @@ export default function ProposalContent() {
<div className="flex flex-col gap-2">
<p className="text-title3 text-text-gray2">제안 프로필</p>

<div className="w-full p-4 bg-bluegray-2 rounded-2xl flex justify-between items-center border border-core-70">
<div
onClick={() => navigate("/mypage/profileCard")}
className="w-full p-4 bg-bluegray-2 rounded-2xl flex justify-between items-center border border-core-70 cursor-pointer active:scale-[0.98] transition-all"
>
<div className="flex items-center gap-3">
<div className="flex items-center justify-center">
<img src={profileIcon} alt="profile" />
Expand Down Expand Up @@ -204,16 +216,16 @@ export default function ProposalContent() {
</div>

<div className="px-4 py-5 flex justify-end bg-bg-w">
{/* 상태가 REVIEWING일 때만 취소 버튼 */}
{data.status === "REVIEWING" && (
<button
onClick={handleCancel}
className="px-4 py-2 bg-bg-w border border-core-3 rounded-xl text-core-1 text-title3 hover:bg-bluegray-1 transition-colors"
>
취소하기
</button>
)}
</div>
{/* 상태가 REVIEWING일 때만 취소 버튼 */}
{data.status === "REVIEWING" && (
<button
onClick={handleCancel}
className="px-4 py-2 bg-bg-w border border-core-3 rounded-xl text-core-1 text-title3 hover:bg-bluegray-1 transition-colors"
>
취소하기
</button>
)}
</div>
</main>
</div>
);
Expand Down
Loading