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: 0 additions & 1 deletion app/api/axios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,5 +121,4 @@ axiosInstance.interceptors.response.use(
},
);

// apiClient alias for backward compatibility
export const apiClient = axiosInstance;
4 changes: 2 additions & 2 deletions app/components/common/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ export default function Modal({ isOpen, onClose, children }: ModalProps) {
<div className="fixed inset-0 z-[100] flex items-center justify-center px-6">
{/* 배경 레이어 */}
<div
className="absolute inset-0 bg-black/30"
className="absolute inset-0 bg-black/50 transition-opacity"
onClick={onClose}
/>
{/* 모달 본체 */}
<div className="relative w-full max-w-[327px] bg-white rounded-[10px] p-6 shadow-xl animate-in fade-in zoom-in duration-200">
<div className="relative w-full max-w-[340px] bg-white rounded-[24px] p-8 shadow-xl animate-in fade-in zoom-in duration-200">
{children}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/components/layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function Header({
rightElement,
}: HeaderProps) {
return (
<header className="sticky top-0 z-50 flex items-center justify-between w-full h-[107px] px-4.5 bg-white border-b border-text-gray5 safe-area-top">
<header className="sticky top-0 z-50 flex items-center justify-between w-full h-[60px] px-4.5 bg-white border-b border-text-gray5 safe-area-top">
{/* 왼쪽: 뒤로가기 버튼 */}
<div className="flex items-center">
{showBack && (
Expand Down
2 changes: 1 addition & 1 deletion app/routes/business/calendar/api/calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const getMyCollaborations = async (params?: {
}) => {
// axios -> axiosInstance로 수정
const response = await axiosInstance.get<CollaborationResponse>(
"/api/v1/campaigns/collaborations/me",
"/v1/campaigns/collaborations/me",
{ params }
);
return response.data.result;
Expand Down
43 changes: 20 additions & 23 deletions app/routes/business/calendar/calendar-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ 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");
Expand All @@ -22,16 +21,13 @@ export default function CalendarContent() {
const [isFilterOpen, setIsFilterOpen] = useState(false);
const [activeFilter, setActiveFilter] = useState("전체");

//const hasData = true;

const [campaigns, setCampaigns] = useState<CampaignCollaboration[]>([]);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
const fetchCampaigns = async () => {
try {
setIsLoading(true);
// matchingSubTab 값에 따라 대문자로 변환하여 API 요청
const data = await getMyCollaborations({
type: matchingSubTab.toUpperCase() as "APPLIED" | "SENT" | "RECEIVED"
});
Expand All @@ -43,25 +39,23 @@ export default function CalendarContent() {
}
};
fetchCampaigns();
}, [matchingSubTab]); // 탭 클릭 시마다 API 다시 호출
}, [matchingSubTab]);

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


const matchingList = campaigns.filter((item) => {
const isCorrectSubTab =
matchingSubTab === "sent" ? item.type === "SENT" :
Expand All @@ -70,7 +64,6 @@ export default function CalendarContent() {

if (!isCorrectSubTab) return false;

// activeFilter가 "전체"가 아닐 때 데이터가 사라지는지 확인
if (activeFilter === "전체") return true;
return getStatusLabel(item.status) === activeFilter;
});
Expand All @@ -86,22 +79,27 @@ export default function CalendarContent() {
if (activeTab === "today") {
return item.startDate <= todayStr && item.endDate >= todayStr;
}
// 이번달 기준 (시작일이나 종료일이 이번 달에 포함된 경우)
return item.startDate.includes(currentMonthStr) || item.endDate.includes(currentMonthStr);
});

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}`);
}
};
const proposalId = item.proposalId || item.campaignId;

// 1. 거절 상태일 때
if (item.status === "REJECTED") {
navigate(`/rejection?proposalId=${proposalId}`);
return;
}

// 2. 지원하기 타입일 때
if (item.type === "APPLIED") {
navigate(`/business/proposal?type=applied&applicationId=${proposalId}`);
return;
}

// 3. 그 외 기본
navigate(`/business/proposal?proposalId=${proposalId}`);
};

return (
<div className="flex flex-col w-full min-h-screen bg-bluegray-1">
Expand Down Expand Up @@ -191,7 +189,7 @@ export default function CalendarContent() {
<MatchingTabSection
subTab={matchingSubTab}
setSubTab={setMatchingSubTab}
// 전체 캠페인 중 RECEIVED 타입인 것의 개수 전달

receivedCount={campaigns.filter(c => c.type === "RECEIVED").length}
/>

Expand All @@ -218,7 +216,6 @@ export default function CalendarContent() {
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)}
/>
))
Expand Down
8 changes: 4 additions & 4 deletions app/routes/business/components/MatchingCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default function MatchingCard({
</span>
</div>

<div className="flex justify-between items-center mb-2">
<div className="flex justify-between items-center mb-2 gap-2">
<span className="text-title1 font-bold text-text-black truncate">
{brand}
</span>
Expand All @@ -54,13 +54,13 @@ export default function MatchingCard({
</div>

<div className="flex gap-2 items-center">
{/* 제안 보기 버튼 */}
{/* 액션 버튼 (제안 보기 / 지원 보기 / 거절 사유 보기) */}
<button
onClick={onClick}
className="flex-2 py-2 flex items-center justify-center gap-1.5 bg-bluegray-2 rounded-lg transition-opacity hover:opacity-90 active:scale-[0.98]"
className="flex-1 py-2 flex items-center justify-center gap-1.5 bg-bluegray-2 rounded-lg transition-all hover:bg-bluegray-3 active:scale-[0.98]"
>
<img src={searchIcon} alt="조회" className="w-3.5 h-3.5" />
<span className="text-title7 text-text-gray2">{actionLabel}</span>
<span className="text-caption1 font-medium text-text-gray2">{actionLabel}</span>
</button>

{/* 채팅 버튼 */}
Expand Down
41 changes: 38 additions & 3 deletions app/routes/business/proposal/api/proposal.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AxiosError } from "axios";
import { axiosInstance } from "../../../../api/axios";
import type { BrandDetail } from "../../../../data/brand";

// 1. 응답 데이터의 공통 포맷 정의 (isSuccess 등을 포함)
export interface ApiResponse<T> {
isSuccess: boolean;
code: string;
Expand Down Expand Up @@ -33,7 +33,7 @@ export interface ProposalDetail {
export const getProposalDetail = async (proposalId: string): Promise<ProposalDetail> => {
try {
const response = await axiosInstance.get<ApiResponse<ProposalDetail>>(
`/api/v1/campaigns/proposal/${proposalId}`
`/v1/campaigns/proposal/${proposalId}`
);

if (response.data.isSuccess) {
Expand All @@ -51,7 +51,7 @@ export const getProposalDetail = async (proposalId: string): Promise<ProposalDet
export const getBrandDetail = async (brandId: number | string): Promise<BrandDetail> => {
try {
const response = await axiosInstance.get<ApiResponse<BrandDetail[]>>(
`/api/v1/brands/${brandId}`
`/v1/brands/${brandId}`
);

if (response.data.isSuccess) {
Expand All @@ -64,4 +64,39 @@ export const getBrandDetail = async (brandId: number | string): Promise<BrandDet
console.error("브랜드 상세 조회 실패:", error);
throw error;
}
};

// 지원 상세 데이터 인터페이스
export interface AppliedCampaignDetail {
campaignId: number;
campaignApplyId: number;
campaignTitle: string;
campaignReason: string;
status: "REVIEWING" | "MATCHED" | "REJECTED" | "CANCELED";
brandName?: string;
creatorId?: string;
}


export const getAppliedCampaignDetail = async (campaignId: string): Promise<AppliedCampaignDetail> => {
try {
const response = await axiosInstance.get<AppliedCampaignDetail>(
`/v1/campaigns/${campaignId}/apply/me`
);

if (response.data) {
return response.data;
}

throw new Error("데이터를 가져오지 못했습니다.");
} catch (error: unknown) {
console.error("지원 상세 조회 실패:", error);

if (error instanceof AxiosError) {
const errorMessage = error.response?.data?.message || "지원 상세 데이터 로드 실패";
throw new Error(errorMessage);
}

throw new Error("알 수 없는 에러가 발생했습니다.");;
}
};
Loading
Loading