diff --git a/app/components/common/ConfirmModal.tsx b/app/components/common/ConfirmModal.tsx new file mode 100644 index 0000000..c45ee0e --- /dev/null +++ b/app/components/common/ConfirmModal.tsx @@ -0,0 +1,64 @@ +import Modal from "./Modal"; +import Button from "./Button"; +import CheckCircleIcon from "../../assets/icon/icon-check-circle.svg"; +import closeIcon from "../../assets/icon/icon-close.svg"; + + +type ConfirmModalProps = { + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; + title: string; +}; + +export default function ConfirmModal({ + isOpen, + onClose, + onConfirm, + title, +}: ConfirmModalProps) { + return ( + + {/* 상단 좌측 X 아이콘 추가 */} + + +
+
+ {/* 이미지 섹션 */} + + + {/* 문구 섹션 */} +

+ {title} +

+
+ + {/* 버튼 섹션 */} +
+ {/* '예' 버튼 */} + + + {/* '아니오' 버튼 */} + +
+
+
+ ); +} \ No newline at end of file diff --git a/app/routes/business/calendar/calendar-content.tsx b/app/routes/business/calendar/calendar-content.tsx index 1d593e8..0380632 100644 --- a/app/routes/business/calendar/calendar-content.tsx +++ b/app/routes/business/calendar/calendar-content.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useMemo } from "react"; import { useNavigate, useLocation } from "react-router-dom"; -import { getMyCollaborations } from "./api/calendar"; -import type { CampaignCollaboration } from "./api/calendar"; +import { getMyCollaborations, searchCollaborations } from "./api/calendar"; +import type { CampaignCollaboration, } from "./api/calendar"; import FilterBottomSheet from "../components/FilterBottomSheet"; import WeeklyCalendar from "../components/WeeklyCalendar"; import MonthlyCalendar from "../components/MonthlyCalendar"; @@ -30,25 +30,31 @@ export default function CalendarContent() { const isFiltered = activeFilter !== "전체"; useEffect(() => { - const fetchAllCampaigns = async () => { + const fetchCampaigns = async () => { try { setIsLoading(true); + // 1. 키워드가 있을 때는 searchCollaborations API 사용 + // 2. 키워드가 없을 때는 getMyCollaborations API 사용 + + const fetchFunction = keyword.trim() ? searchCollaborations : getMyCollaborations; + const [applied, sent, received] = await Promise.all([ - getMyCollaborations({ type: "APPLIED", keyword: keyword.trim() || undefined }), - getMyCollaborations({ type: "SENT", keyword: keyword.trim() || undefined }), - getMyCollaborations({ type: "RECEIVED", keyword: keyword.trim() || undefined }), + fetchFunction({ type: "APPLIED", keyword: keyword.trim() || undefined }), + fetchFunction({ type: "SENT", keyword: keyword.trim() || undefined }), + fetchFunction({ type: "RECEIVED", keyword: keyword.trim() || undefined }), ]); setCampaigns([...applied, ...sent, ...received]); } catch (error) { - console.error("로드 실패:", error); + console.error("데이터 로드 실패:", error); + setCampaigns([]); // 에러 시 빈 배열 처리 } finally { setIsLoading(false); } }; - fetchAllCampaigns(); + fetchCampaigns(); }, [keyword, location.key]); // 날짜 계산을 별도 useMemo로 분리 @@ -92,8 +98,11 @@ export default function CalendarContent() { } }; + // CalendarContent.tsx 내부 matchingList + const matchingList = useMemo(() => { return campaigns.filter((item) => { + // 탭 필터링 const isCorrectSubTab = matchingSubTab === "sent" ? item.type === "SENT" : matchingSubTab === "received" ? item.type === "RECEIVED" : @@ -101,18 +110,14 @@ export default function CalendarContent() { if (!isCorrectSubTab) return false; - if (activeFilter === "전체") return true; - - const statusMatches = getStatusLabel(item.status) === activeFilter; - - const keywordMatches = keyword ? item.brandName.includes(keyword) : true; - - return statusMatches && keywordMatches; - - + // 상태 필터링 (activeFilter가 "전체"가 아닐 때만 적용) + if (activeFilter !== "전체") { + return getStatusLabel(item.status) === activeFilter; + } + return true; }); - }, [campaigns, matchingSubTab, activeFilter, keyword]); + }, [campaigns, matchingSubTab, activeFilter]); // keyword는 이제 API 결과(campaigns)에 반영되어 있으므로 제외 가능 console.log("전체 데이터:", campaigns); console.log("필터된 데이터:", matchingList); @@ -226,20 +231,18 @@ export default function CalendarContent() {

매칭 현황

- - {/* 로딩 표시 */} - {isLoading && } - - {/* 검색 결과 리스트 */} -
- {!isLoading && campaigns.length > 0 ? ( - campaigns.map((item) => ( -
- {item.brandName} - {item.title} {/* 브랜드명과 제목 표시 */} -
- )) - ) : ( - !isLoading && keyword &&
검색 결과가 없습니다.
- )} -
); } @@ -110,7 +63,7 @@ export default function MatchingTabSection({ subTab, setSubTab, receivedCount, k /> ); diff --git a/app/routes/business/proposal/application-content.tsx b/app/routes/business/proposal/application-content.tsx index 515defc..4456cfb 100644 --- a/app/routes/business/proposal/application-content.tsx +++ b/app/routes/business/proposal/application-content.tsx @@ -5,8 +5,9 @@ import { useLocation } from "react-router-dom"; import { getAppliedCampaignDetail, cancelCampaignApply, type AppliedCampaignDetail } from "./api/proposal"; import { getBrandSummary, type BrandSummary } from "./api/brand"; import { getProfileCard, type ProfileCard } from "./api/user"; +import { useHideHeader } from "../../../hooks/useHideHeader"; +import NavigationHeader from "../../../components/common/NavigateHeader"; import Modal from "../../../components/common/Modal"; -import Header from "../../../components/layout/Header"; import CampaignBrandCard from "../components/CampaignBrandCard"; import LoadingSpinner from "../../../components/common/LoadingSpinner"; import CampaignInfoGroup from "../components/CampaignInfoGroup"; @@ -28,9 +29,12 @@ export default function ApplicationContent() { const [modalStep, setModalStep] = useState<"CONFIRM" | "COMPLETE">("CONFIRM"); const applicationId = searchParams.get("applicationId"); + const navigate = useNavigate(); const location = useLocation(); const brandIdFromList = location.state?.brandId; + useHideHeader(true); + const handleCloseModal = () => { setIsModalOpen(false); setTimeout(() => setModalStep("CONFIRM"), 300); @@ -103,8 +107,6 @@ export default function ApplicationContent() { } }; - const navigate = useNavigate(); - const handleComplete = () => { setIsModalOpen(false); navigate(-1); @@ -125,7 +127,9 @@ export default function ApplicationContent() { return (
-
+
+ navigate(-1)} /> +
{/* 상단 브랜드 정보 섹션 */} diff --git a/app/routes/business/proposal/sent-proposal-content.tsx b/app/routes/business/proposal/sent-proposal-content.tsx index e81e08a..e918111 100644 --- a/app/routes/business/proposal/sent-proposal-content.tsx +++ b/app/routes/business/proposal/sent-proposal-content.tsx @@ -5,7 +5,8 @@ import { getBrandSummary, type BrandSummary } from "./api/brand"; import { getProfileCard, type ProfileCard } from "./api/user"; import { cancelCampaignProposal } from "./api/proposal"; -import Header from "../../../components/layout/Header"; +import { useHideHeader } from "../../../hooks/useHideHeader"; +import NavigationHeader from "../../../components/common/NavigateHeader"; import CampaignBrandCard from "../components/CampaignBrandCard"; import LoadingSpinner from "../../../components/common/LoadingSpinner"; import CampaignInfoGroup from "../components/CampaignInfoGroup"; @@ -15,10 +16,12 @@ import dropupIcon from "../../../assets/arrow-up.svg"; import arrowRightIcon from "../../../assets/icon/arrow-right.svg"; import arrowPurpleIcon from "../../../assets/arrow-purple.svg"; import profileIcon from "../../../assets/icon-profile.svg"; +import ConfirmModal from "../../../components/common/ConfirmModal"; export default function ProposalContent() { const [searchParams] = useSearchParams(); const [isContentOpen, setIsContentOpen] = useState(false); + const [isConfirmOpen, setIsConfirmOpen] = useState(false); const navigate = useNavigate(); const [data, setData] = useState(null); @@ -28,6 +31,8 @@ export default function ProposalContent() { const proposalId = searchParams.get("proposalId"); + useHideHeader(true); + const STATUS_TEXT_MAP: Record = { REVIEWING: "검토 중", MATCHED: "매칭 완료", @@ -71,36 +76,27 @@ export default function ProposalContent() { const getTagNames = (tags: { name: string }[]) => tags.map(t => t.name).join(", "); - const handleCancel = async () => { + const handleCancelSubmit = async () => { if (!proposalId) return; - if (!window.confirm("제안을 취소하시겠습니까?")) return; - try { const response = await cancelCampaignProposal(proposalId); - if (response.isSuccess) { - alert("캠페인 제안을 취소했습니다."); + // 성공 시 모달 닫고 바로 이전 페이지로 이동 + setIsConfirmOpen(false); navigate(-1); } - } catch (error: unknown) { + } catch (error) { console.error("제안 취소 실패:", error); - - let errorMessage = "취소 중 오류가 발생했습니다."; - - if (error instanceof Error) { - errorMessage = error.message; - } else if (typeof error === "string") { - errorMessage = error; - } - - alert(errorMessage); + setIsConfirmOpen(false); } }; return (
-
+
+ navigate(-1)} /> +
{/* 브랜드 카드 및 제안 프로필 */} @@ -219,13 +215,22 @@ export default function ProposalContent() { {/* 상태가 REVIEWING일 때만 취소 버튼 */} {data.status === "REVIEWING" && ( )}
+ + setIsConfirmOpen(false)} + onConfirm={handleCancelSubmit} + title="제안을 취소하시겠습니까?" + /> + +
);