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/components/common/RealmatchHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import RealMatchLogo from "../../assets/logo/RealMatchLogo_ex.svg"

type RealMatchHeaderProps = {
/** 뒤로가기 버튼 노출 여부 */
title?: string;
showBack?: boolean;
/** 뒤로가기 클릭 시 동작 커스텀 (없으면 navigate({to: ".."}) 시도 후 history.back) */
onBack?: () => void;
Expand Down
61 changes: 60 additions & 1 deletion app/data/campaign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,63 @@ export const getProposalDetail = async (id: string): Promise<ProposalDetail> =>
console.log("API 전체 응답:", response.data);

return response.data.result;
};
};

export interface CampaignCollaboration {
campaignId: number;
proposalId: string | null;
brandName: string;
thumbnailUrl: string;
title: string;
status: "NONE" | "REVIEWING" | "MATCHED" | "REJECTED";
startDate: string;
endDate: string;
type: "APPLIED" | "SENT" | "RECEIVED";
}
// MatchingCard 연동을 위한 전용 더미 데이터
export const MATCHING_DUMMY_DATA: CampaignCollaboration[] = [
{
campaignId: 1,
proposalId: "p1",
brandName: "라운드랩",
thumbnailUrl: "",
title: "자작나무 수분크림 체험단",
status: "MATCHED",
startDate: "2025-12-23",
endDate: "2025-12-30",
type: "SENT",
},
{
campaignId: 2,
proposalId: "p2",
brandName: "비플레인",
thumbnailUrl: "",
title: "'글로우업' 선크림 신제품 홍보",
status: "REVIEWING",
startDate: "2025-12-23",
endDate: "2025-12-30",
type: "RECEIVED",
},
{
campaignId: 3,
proposalId: "p3",
brandName: "그레이스유",
thumbnailUrl: "",
title: "봄 신상 코디 콘텐츠 제작",
status: "REVIEWING",
startDate: "2025-12-23",
endDate: "2025-12-30",
type: "RECEIVED",
},
{
campaignId: 4,
proposalId: "p4",
brandName: "이즈앤트리",
thumbnailUrl: "",
title: "비타민C 세럼 리뷰 캠페인",
status: "REJECTED",
startDate: "2025-12-23",
endDate: "2025-12-30",
type: "SENT",
},
];
49 changes: 48 additions & 1 deletion app/routes/business/calendar/api/calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,51 @@ export const getMyCollaborations = async (params?: {
{ params }
);
return response.data.result;
};
};

/*export const MATCHING_DUMMY_DATA: CampaignCollaboration[] = [
{
campaignId: 1,
proposalId: "p1",
brandName: "라운드랩",
thumbnailUrl: "",
title: "자작나무 수분크림 체험단",
status: "MATCHED",
startDate: "2026-02-02",
endDate: "2026-02-07",
type: "SENT",
},
{
campaignId: 2,
proposalId: "p2",
brandName: "비플레인",
thumbnailUrl: "",
title: "'글로우업' 선크림 신제품 홍보",
status: "REVIEWING",
startDate: "2026-02-04",
endDate: "2025-02-05",
type: "RECEIVED",
},
{
campaignId: 3,
proposalId: "p3",
brandName: "그레이스유",
thumbnailUrl: "",
title: "봄 신상 코디 콘텐츠 제작",
status: "REVIEWING",
startDate: "2026-02-23",
endDate: "2026-02-24",
type: "RECEIVED",
},
{
campaignId: 4,
proposalId: "p4",
brandName: "이즈앤트리",
thumbnailUrl: "",
title: "비타민C 세럼 리뷰 캠페인",
status: "REJECTED",
startDate: "2026-02-01",
endDate: "2025-02-04",
type: "SENT",
},
];*/
180 changes: 101 additions & 79 deletions app/routes/business/calendar/calendar-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,76 @@ import MatchingCard from "../components/MatchingCard";
import MatchingTabSection from "../components/MatchingTabSection";
import dropdownIcon from "../../../assets/arrow-down.svg";
import EmptyState from "../components/EmptyState";
//import { MATCHING_DUMMY_DATA } from "../calendar/api/calendar";


export default function CalendarContent() {
const navigate = useNavigate();
const [mainTab, setMainTab] = useState<"collaboration" | "matching">("collaboration");
const [activeTab, setActiveTab] = useState<"thisMonth" | "today">("thisMonth");
const [matchingSubTab, setMatchingSubTab] = useState<"sent" | "received">("sent");
const [matchingSubTab, setMatchingSubTab] = useState<"sent" | "received" | "applied">("sent");
const [isFilterOpen, setIsFilterOpen] = useState(false);
const [activeFilter, setActiveFilter] = useState("전체");

const hasData = true;
//const hasData = true;

// API 데이터 상태
const [campaigns, setCampaigns] = useState<CampaignCollaboration[]>([]);
const [isLoading, setIsLoading] = useState(true);
// 데이터 로드

useEffect(() => {
const fetchCampaigns = async () => {
try {
setIsLoading(true);
// 협업 현황 조회를 위해 전체 데이터를 가져옵니다.
const data = await getMyCollaborations();
setCampaigns(data);
// matchingSubTab 값에 따라 대문자로 변환하여 API 요청
const data = await getMyCollaborations({
type: matchingSubTab.toUpperCase() as "APPLIED" | "SENT" | "RECEIVED"
});
setCampaigns(data || []);
} catch (error) {
console.error("캠페인 로드 실패:", error);
console.error("로드 실패:", error);
} finally {
setIsLoading(false);
}
};

fetchCampaigns();
}, []);
}, [matchingSubTab]); // 탭 클릭 시마다 API 다시 호출

// 상태 변환 헬퍼 함수
const getStatusLabel = (status: CampaignCollaboration["status"]): "매칭" | "검토 중" | "거절" => {
switch (status) {
case "MATCHED":
return "매칭";
case "REVIEWING":
case "NONE": // NONE 상태도 '검토 중'으로 처리하거나 기획에 맞게 할당
return "검토 중";
case "REJECTED":
return "거절";
default:
// API에서 예상치 못한 값이 올 경우 기본값으로 "검토 중"을 반환하여 에러 방지
return "검토 중";
}
};


const matchingList = campaigns.filter((item) => {
const isCorrectSubTab =
matchingSubTab === "sent" ? item.type === "SENT" :
matchingSubTab === "received" ? item.type === "RECEIVED" :
item.type === "APPLIED";

if (!isCorrectSubTab) return false;

// activeFilter가 "전체"가 아닐 때 데이터가 사라지는지 확인
if (activeFilter === "전체") return true;
return getStatusLabel(item.status) === activeFilter;
});

console.log("전체 데이터:", campaigns);
console.log("필터된 데이터:", matchingList);

// [필터링 로직]
const todayStr = new Date().toISOString().split('T')[0];
const currentMonthStr = todayStr.substring(0, 7); // "2026-02"
const currentMonthStr = todayStr.substring(0, 7);
const calendarEvents = campaigns.filter(item => item.status === "MATCHED");

const filteredList = campaigns.filter((item) => {
if (activeTab === "today") {
Expand All @@ -56,8 +90,17 @@ export default function CalendarContent() {
return item.startDate.includes(currentMonthStr) || item.endDate.includes(currentMonthStr);
});

const handleCardClick = (type: "sent" | "received") => {
navigate(`/business/proposal?type=${type}`);
const handleCardClick = (item: CampaignCollaboration) => {
// 1. 거절된 상태일 경우 거절 사유 페이지로 이동
if (item.status === "REJECTED") {
navigate(`/rejection?id=${item.campaignId || item.proposalId}`);
}
// 2. 그 외(매칭, 검토 중) 상태일 경우 제안 상세 조회 페이지로 이동
else {
// API 명세에 따른 campaignProposalId (item의 proposalId 혹은 campaignId)를 경로에 전달
const proposalId = item.proposalId || item.campaignId;
navigate(`/business/proposal?id=${proposalId}`);
}
};

return (
Expand Down Expand Up @@ -90,15 +133,15 @@ export default function CalendarContent() {
{mainTab === "collaboration" ? (
/* [A] 협업 현황 */
<div className="flex flex-col gap-6 px-4 py-6">
{/* 주간 캘린더 연동 */}
{/* 주간 캘린더 연동 */}
<section className="flex flex-col gap-3">
<SectionTitle title="진행 중인 협업" />
<p className="text-title1 font-bold text-text-black">이번주 일정</p>
<WeeklyCalendar events={campaigns} />
<WeeklyCalendar events={calendarEvents} />
</section>
<section className="flex flex-col gap-3">
<p className="text-title1 font-bold text-text-black">이번달 일정</p>
<MonthlyCalendar events={campaigns} />
<MonthlyCalendar events={calendarEvents} />
</section>

{/* 하단 리스트 섹션 */}
Expand Down Expand Up @@ -129,7 +172,7 @@ export default function CalendarContent() {
brand={cp.brandName}
title={cp.title}
logo={cp.thumbnailUrl}
// 날짜 포맷 변경 (2026-02-01 -> 02.01)

startDate={cp.startDate.split('-').slice(1).join('.')}
endDate={cp.endDate.split('-').slice(1).join('.')}
/>
Expand All @@ -145,70 +188,49 @@ export default function CalendarContent() {
) : (
/* [B] 매칭 현황 */
<div className="flex flex-col flex-1">
<MatchingTabSection subTab={matchingSubTab} setSubTab={setMatchingSubTab} />

{hasData ? (
<div className="flex flex-col gap-4 px-4 flex-1">
<div className="flex items-center justify-between mb-1">
<h2 className="text-title1 font-bold text-text-black">매칭 현황</h2>
<button
onClick={() => setIsFilterOpen(true)}
className="flex items-center gap-1 px-3 py-1 border border-text-gray4 rounded-full bg-white active:bg-bluegray-2 transition-colors"
>
<span className="text-callout1 text-text-gray2">{activeFilter}</span>
<img src={dropdownIcon} alt="open filter" />
</button>
</div>
<div className="flex flex-col gap-4">
{matchingSubTab === "sent" ? (
<>
<MatchingCard
brand="라운드랩" status="매칭" date="12.23.25" actionLabel="제안 보기"
onClick={() => handleCardClick("sent")} // 4. 핸들러 연결
/>
<MatchingCard
brand="비플레인" status="검토 중" date="12.23.25" actionLabel="제안 보기"
onClick={() => handleCardClick("sent")}
/>
<MatchingCard
brand="땡큐파머" status="검토 중" date="12.23.25" actionLabel="제안 보기"
onClick={() => handleCardClick("sent")}
/>
<MatchingCard
brand="이즈트리" status="거절" date="12.23.25" actionLabel="거절 사유 보기"
onClick={() => handleCardClick("sent")}
/>
</>
) : (
<>
<MatchingCard
brand="라운드랩" status="매칭" date="12.23.25" actionLabel="제안 보기"
onClick={() => handleCardClick("received")} // 4. 핸들러 연결
/>
<MatchingCard
brand="비플레인" status="검토 중" date="12.23.25" actionLabel="제안 보기"
onClick={() => handleCardClick("received")}
/>
<MatchingCard
brand="그레이스유" status="검토 중" date="12.23.25" actionLabel="제안 보기"
onClick={() => handleCardClick("received")}
/>
<MatchingCard
brand="이즈트리" status="거절" date="12.23.25" actionLabel="거절 사유 보기"
onClick={() => handleCardClick("received")}
/>
</>
)}
</div>
<MatchingTabSection
subTab={matchingSubTab}
setSubTab={setMatchingSubTab}
// 전체 캠페인 중 RECEIVED 타입인 것의 개수 전달
receivedCount={campaigns.filter(c => c.type === "RECEIVED").length}
/>

<div className="flex flex-col gap-4 px-4 flex-1">
<div className="flex items-center justify-between mb-1">
<h2 className="text-title1 font-bold text-text-black">매칭 현황</h2>
<button
onClick={() => setIsFilterOpen(true)}
className="flex items-center gap-1 px-3 py-1 border border-text-gray4 rounded-full bg-white active:bg-bluegray-2 transition-colors"
>
<span className="text-callout1 text-text-gray2">{activeFilter}</span>
<img src={dropdownIcon} alt="open filter" />
</button>
</div>
) : (
<div className="flex-1 flex flex-col items-center justify-center">
<EmptyState
message={`${(matchingSubTab as string) === "sent" ? "보낸" : "받은"} 제안이 없어요`}
/>

<div className="flex flex-col gap-4">
{isLoading ? (
<p>로딩 중...</p>
) : matchingList.length > 0 ? (
matchingList.map((item) => (
<MatchingCard
key={item.campaignId || item.proposalId}
brand={item.brandName}
status={getStatusLabel(item.status)}
date={item.startDate.split('-').slice(1).join('.') + "." + item.startDate.split('-')[0].slice(2)}
actionLabel={item.status === "REJECTED" ? "거절 사유 보기" : "제안 보기"}
// [수정된 부분] item 객체 자체를 넘겨서 상태에 따라 분기 처리
onClick={() => handleCardClick(item)}
/>
))
) : (
<div className="flex-1 flex flex-col items-center justify-center py-20">
<EmptyState
message={`${matchingSubTab === "sent" ? "보낸" : "받은"} 제안이 없어요`}
/>
</div>
)}
</div>
)}
</div>

<FilterBottomSheet
isOpen={isFilterOpen}
Expand Down
1 change: 1 addition & 0 deletions app/routes/business/components/CampaignCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default function CampaignCard({
showButton = true,
campaignId = 1,
}: CampaignCardProps) {

const navigate = useNavigate();

// 로고 컴포넌트
Expand Down
Loading
Loading