Skip to content

Commit abb5ad4

Browse files
authored
Merge pull request #13 from RealMatchTeam/feat/business-calendar
[feat] 매칭 현황 및 EmptyState 구현
2 parents 9d3591d + 583b7c6 commit abb5ad4

9 files changed

Lines changed: 341 additions & 84 deletions

File tree

src/assets/cancel.svg

Lines changed: 3 additions & 0 deletions
Loading

src/assets/chat-icon2.svg

Lines changed: 3 additions & 0 deletions
Loading

src/assets/empty.png

102 KB
Loading

src/assets/search2.svg

Lines changed: 10 additions & 0 deletions
Loading

src/routes/_main/_business/calendar/calendar-content.tsx

Lines changed: 89 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,125 @@
11
import { useState } from "react";
22

3+
import FilterBottomSheet from "../components/FilterBottomSheet";
34
import WeeklyCalendar from "../components/WeeklyCalendar";
45
import MonthlyCalendar from "../components/MonthlyCalendar";
56
import CampaignCard from "../components/CampaignCard";
67
import SectionTitle from "../components/SectionTitle";
8+
import MatchingCard from "../components/MatchingCard";
9+
import MatchingTabSection from "../components/MatchingTabSection";
10+
import dropdownIcon from "../../../../assets/arrow-down.svg";
11+
import EmptyState from "../components/EmptyState";
712

813
export default function CalendarContent() {
9-
const [mainTab, setMainTab] =
10-
useState<"collaboration" | "matching">("collaboration");
11-
const [activeTab, setActiveTab] =
12-
useState<"thisMonth" | "today">("thisMonth");
14+
const [mainTab, setMainTab] = useState<"collaboration" | "matching">("collaboration");
15+
const [activeTab, setActiveTab] = useState<"thisMonth" | "today">("thisMonth");
16+
const [matchingSubTab, setMatchingSubTab] = useState<"sent" | "received">("sent");
17+
const [isFilterOpen, setIsFilterOpen] = useState(false);
18+
const [activeFilter, setActiveFilter] = useState("전체");
19+
20+
const hasData = matchingSubTab === "sent";
1321

1422
return (
1523
<div className="flex flex-col w-full min-h-screen bg-bluegray-1">
16-
{/* 1. 상단 협업/매칭 현황 탭 */}
17-
<div className="flex w-full bg-[var(--color-bg-w)] border-b border-[var(--color-text-gray5)]">
24+
{/* 탭 네비게이션 */}
25+
<div className="flex w-full bg-bg-w border-b border-text-gray5">
1826
<button
1927
onClick={() => setMainTab("collaboration")}
20-
className={`flex-1 py-4 text-[16px] font-bold relative transition-colors
21-
${
22-
mainTab === "collaboration"
23-
? "text-[var(--color-core-1)]"
24-
: "text-[var(--color-text-gray3)]"
28+
className={`flex-1 py-4 text-[16px] font-bold relative transition-colors ${mainTab === "collaboration" ? "text-core-1" : "text-text-gray3"
2529
}`}
2630
>
2731
협업 현황
2832
{mainTab === "collaboration" && (
29-
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-[120px] h-[2px] bg-[var(--color-core-1)]" />
33+
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-[120px] h-[2px] bg-core-1" />
3034
)}
3135
</button>
32-
3336
<button
3437
onClick={() => setMainTab("matching")}
35-
className={`flex-1 py-4 text-[16px] font-bold relative transition-colors
36-
${
37-
mainTab === "matching"
38-
? "text-[var(--color-core-1)]"
39-
: "text-[var(--color-text-gray3)]"
38+
className={`flex-1 py-4 text-[16px] font-bold relative transition-colors ${mainTab === "matching" ? "text-core-1" : "text-text-gray3"
4039
}`}
4140
>
4241
매칭 현황
4342
{mainTab === "matching" && (
44-
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-[120px] h-[2px] bg-[var(--color-core-1)]" />
43+
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-[120px] h-[2px] bg-core-1" />
4544
)}
4645
</button>
4746
</div>
4847

49-
<main className="flex flex-col gap-6 px-4 py-6">
50-
{/* 진행 중인 협업 섹션 */}
51-
<section className="flex flex-col gap-3">
52-
<SectionTitle title="진행 중인 협업" />
53-
<p className="text-title1 font-bold text-text-black">
54-
이번주 일정
55-
</p>
56-
<WeeklyCalendar />
57-
</section>
58-
59-
{/* 이번달 일정 섹션 */}
60-
<section className="flex flex-col gap-3">
61-
<p className="text-title1 font-bold text-text-black">
62-
이번달 일정
63-
</p>
64-
<MonthlyCalendar />
65-
</section>
66-
67-
{/* 협업 리스트 섹션 */}
68-
<section className="flex flex-col gap-4">
69-
<div className="flex items-center gap-2">
70-
<button
71-
onClick={() => setActiveTab("thisMonth")}
72-
className={`text-[14px] font-semibold
73-
${
74-
activeTab === "thisMonth"
75-
? "text-[var(--color-core-1)]"
76-
: "text-[var(--color-text-gray3)]"
77-
}`}
78-
>
79-
이번달
80-
</button>
81-
82-
<span className="text-[14px] text-[var(--color-text-gray3)]">|</span>
83-
84-
<button
85-
onClick={() => setActiveTab("today")}
86-
className={`text-[14px] font-semibold
87-
${
88-
activeTab === "today"
89-
? "text-[var(--color-core-1)]"
90-
: "text-[var(--color-text-gray3)]"
91-
}`}
92-
>
93-
오늘
94-
</button>
48+
<main className="flex flex-col flex-1">
49+
{mainTab === "collaboration" ? (
50+
/* [A] 협업 현황 */
51+
<div className="flex flex-col gap-6 px-4 py-6">
52+
<section className="flex flex-col gap-3">
53+
<SectionTitle title="진행 중인 협업" />
54+
<p className="text-title1 font-bold text-text-black">이번주 일정</p>
55+
<WeeklyCalendar />
56+
</section>
57+
<section className="flex flex-col gap-3">
58+
<p className="text-title1 font-bold text-text-black">이번달 일정</p>
59+
<MonthlyCalendar />
60+
</section>
61+
<section className="flex flex-col gap-4">
62+
<div className="flex items-center gap-2">
63+
<button
64+
onClick={() => setActiveTab("thisMonth")}
65+
className={`text-[14px] font-semibold ${activeTab === "thisMonth" ? "text-core-1" : "text-text-gray3"}`}
66+
>
67+
이번달
68+
</button>
69+
<span className="text-[14px] text-text-gray3">|</span>
70+
<button
71+
onClick={() => setActiveTab("today")}
72+
className={`text-[14px] font-semibold ${activeTab === "today" ? "text-core-1" : "text-text-gray3"}`}
73+
>
74+
오늘
75+
</button>
76+
</div>
77+
<div className="flex flex-col gap-4">
78+
<CampaignCard brand="비플레인" title="비플레인 클렌징 및 세럼 리뷰" startDate="12.21" endDate="12.24" />
79+
<CampaignCard brand="비플레인" title="비플레인 클렌징 및 세럼 리뷰" startDate="12.23" endDate="12.26" />
80+
</div>
81+
</section>
9582
</div>
83+
) : (
84+
/* [B] 매칭 현황 */
85+
<div className="flex flex-col flex-1">
86+
<MatchingTabSection subTab={matchingSubTab} setSubTab={setMatchingSubTab} />
9687

97-
<div className="flex flex-col gap-4">
98-
<CampaignCard
99-
brand="비플레인"
100-
title="비플레인 클렌징 및 세럼 리뷰"
101-
startDate="12.21"
102-
endDate="12.24"
103-
/>
104-
<CampaignCard
105-
brand="비플레인"
106-
title="비플레인 클렌징 및 세럼 리뷰"
107-
startDate="12.23"
108-
endDate="12.26"
109-
/>
110-
<CampaignCard
111-
brand="비플레인"
112-
title="비플레인 클렌징 및 세럼 리뷰"
113-
startDate="01.10"
114-
endDate="01.15"
88+
{hasData ? (
89+
<div className="flex flex-col gap-4 px-4 py-6 flex-1">
90+
<div className="flex items-center justify-between mb-1">
91+
<h2 className="text-title1 font-bold text-text-black">매칭 현황</h2>
92+
<button
93+
onClick={() => setIsFilterOpen(true)}
94+
className="flex items-center gap-1 px-3 py-1 border border-text-gray4 rounded-full bg-white active:bg-bluegray-2 transition-colors"
95+
>
96+
<span className="text-callout1 text-text-gray2">{activeFilter}</span>
97+
<img src={dropdownIcon} alt="open filter" />
98+
</button>
99+
</div>
100+
<div className="flex flex-col gap-4">
101+
<MatchingCard brand="라운드랩" status="매칭" date="12.23.25" actionLabel="제안 보기" />
102+
<MatchingCard brand="비플레인" status="검토 중" date="12.23.25" actionLabel="제안 보기" />
103+
<MatchingCard brand="땡큐파머" status="검토 중" date="12.23.25" actionLabel="제안 보기" />
104+
<MatchingCard brand="이즈트리" status="거절" date="12.23.25" actionLabel="거절 사유 보기" />
105+
</div>
106+
</div>
107+
) : (
108+
<div className="flex-1 flex flex-col items-center justify-center">
109+
<EmptyState
110+
message={`${(matchingSubTab as string) === "sent" ? "보낸" : "받은"} 제안이 없어요`}
111+
/>
112+
</div>
113+
)}
114+
115+
<FilterBottomSheet
116+
isOpen={isFilterOpen}
117+
onClose={() => setIsFilterOpen(false)}
118+
onApply={(filter) => setActiveFilter(filter)}
119+
currentFilter={activeFilter}
115120
/>
116121
</div>
117-
</section>
122+
)}
118123
</main>
119124
</div>
120125
);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import emptyIcon from "../../../../assets/empty.png"; // 보라색 이미지 경로
2+
3+
interface EmptyStateProps {
4+
message: string;
5+
}
6+
7+
export default function EmptyState({ message }: EmptyStateProps) {
8+
return (
9+
<div className="flex flex-col items-center justify-center flex-1 py-20">
10+
{/* 중앙 보라색 이미지 */}
11+
<img
12+
src={emptyIcon}
13+
alt="Empty state"
14+
className="w-[180px] h-auto mb-6"
15+
/>
16+
17+
<p className="text-body1 text-[var(--color-text-gray3)]">
18+
{message}
19+
</p>
20+
</div>
21+
);
22+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { useState } from "react";
2+
3+
interface FilterBottomSheetProps {
4+
isOpen: boolean;
5+
onClose: () => void;
6+
onApply: (filter: string) => void;
7+
currentFilter: string;
8+
}
9+
10+
const FILTERS = ["전체", "검토 중", "매칭", "거절"];
11+
12+
export default function FilterBottomSheet({
13+
isOpen,
14+
onClose,
15+
onApply,
16+
currentFilter,
17+
}: FilterBottomSheetProps) {
18+
const [selected, setSelected] = useState(currentFilter);
19+
20+
if (!isOpen) return null;
21+
22+
return (
23+
/* 최상위 컨테이너 */
24+
<div className="fixed inset-0 z-50 flex flex-col items-center justify-end">
25+
26+
{/* 배경 오버레이 */}
27+
<div
28+
className="absolute inset-0 bg-black/40"
29+
onClick={onClose}
30+
/>
31+
32+
{/* 바텀 시트 본체 */}
33+
<div
34+
className="relative bg-[var(--color-bg-w)] rounded-t-[24px] overflow-hidden animate-slide-up w-full max-w-[430px] h-[530px] flex flex-col"
35+
>
36+
{/* 1. 헤더 영역 */}
37+
<div className="px-4 pt-8 pb-4">
38+
<div className="inline-block border-b-2 border-[var(--color-core-1)] pb-1">
39+
<span className="text-title7 text-[var(--color-text-black)]">정렬 필터</span>
40+
</div>
41+
</div>
42+
43+
{/* 2. 필터 옵션 영역 */}
44+
<div className="w-full bg-[var(--color-bluegray-1)] py-4">
45+
<div className="flex items-center justify-start gap-8 px-4">
46+
{FILTERS.map((filter) => (
47+
<button
48+
key={filter}
49+
onClick={() => setSelected(filter)}
50+
className={`text-title3 transition-colors whitespace-nowrap ${
51+
selected === filter
52+
? "text-[var(--color-text-black)] font-bold"
53+
: "text-[var(--color-text-gray3)]"
54+
}`}
55+
>
56+
{filter}
57+
</button>
58+
))}
59+
</div>
60+
</div>
61+
62+
{/* 3. 하단 적용하기 버튼 */}
63+
<div className="mt-auto px-4 pb-10">
64+
<button
65+
onClick={() => {
66+
onApply(selected);
67+
onClose();
68+
}}
69+
className="w-full py-4 bg-[var(--color-core-1)] text-white rounded-xl text-title1 active:opacity-90"
70+
>
71+
적용하기
72+
</button>
73+
</div>
74+
</div>
75+
</div>
76+
);
77+
}

0 commit comments

Comments
 (0)