Skip to content

Commit 3278eab

Browse files
authored
Merge pull request #82 from Chaellimi/feat/#80
[Feat/#80] 챌린지 현황페이지 퍼블리싱 및 API 제작
2 parents 29de77b + 661c87d commit 3278eab

File tree

21 files changed

+636
-253
lines changed

21 files changed

+636
-253
lines changed

public/icons/Challenge/progress/Coin.tsx

Lines changed: 19 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -35,136 +35,27 @@ const CoinIcon = ({ disabled, width = 60, height = 60 }: OwnProps) => {
3535
</svg>
3636
) : (
3737
<svg
38-
width="72"
39-
height="72"
40-
viewBox="0 0 72 72"
41-
fill="none"
4238
xmlns="http://www.w3.org/2000/svg"
39+
width="61"
40+
height="60"
41+
viewBox="0 0 61 60"
42+
fill="none"
4343
>
44-
<g clipPath="url(#clip0_1116_6888)">
45-
<g filter="url(#filter0_dii_1116_6888)">
46-
<rect width="72" height="72" rx="36" fill="#EFEFEF" />
47-
<rect
48-
x="7.20007"
49-
y="7.2002"
50-
width="57.6"
51-
height="57.6"
52-
rx="28.8"
53-
stroke="white"
54-
strokeWidth="1.8"
55-
strokeMiterlimit="16"
56-
strokeDasharray="5.14 5.14"
57-
/>
58-
<path
59-
d="M33.943 25.8137C33.4982 20.274 40.1068 18.8514 42.358 23.4514C42.4668 23.6735 43.1332 24.9093 43.526 25.7228C43.7424 26.1712 43.573 26.5778 43.1422 26.8273L38.5201 29.5051C34.8319 32.4518 37.1925 37.786 41.57 37.2531C41.6369 37.245 41.6314 37.1653 41.5641 37.1652L41.4645 37.1682C40.8074 37.1901 40.1343 37.1322 39.5592 36.8137C36.8962 35.3382 36.5938 31.6983 39.3151 29.7121C40.5639 28.8008 42.9756 27.4995 44.3541 26.76C49.1764 25.2846 51.2392 27.1736 52.4635 31.4426C56.6902 46.2088 38.9167 57.2464 26.7379 47.8918C23.4935 45.3999 22.014 42.3554 20.111 39.0549C19.5638 37.8456 19.1264 36.4828 18.9616 35.1965C18.3835 30.7028 21.9646 28.5069 25.7418 30.3654C25.7687 30.3786 25.7992 30.3504 25.7887 30.3225C23.6325 24.662 29.9879 21.2539 33.2594 25.2141L33.8873 25.842C33.9089 25.8636 33.9454 25.8441 33.943 25.8137ZM32.4264 40.5451C31.2185 40.3695 30.0785 40.3695 29.2418 40.4133C28.8232 40.4352 28.4787 40.4678 28.2389 40.4953C28.1192 40.509 28.0249 40.5214 27.9606 40.5305C27.9285 40.535 27.9032 40.5386 27.8864 40.5412C27.8781 40.5425 27.8712 40.5444 27.8668 40.5451C27.8649 40.5453 27.863 40.5449 27.8619 40.5451L27.861 40.5461H27.86C27.7373 40.5663 27.6535 40.6822 27.6735 40.8049C27.6911 40.9123 27.7826 40.9892 27.8873 40.9934L27.9323 40.9904H27.9332C27.934 40.9903 27.9355 40.9897 27.9371 40.9894C27.9408 40.9889 27.9472 40.9886 27.9547 40.9875C27.9699 40.9852 27.993 40.981 28.0231 40.9768C28.0838 40.9682 28.1741 40.9568 28.2897 40.9435C28.5215 40.917 28.857 40.8848 29.2653 40.8635C30.0827 40.8207 31.1917 40.8212 32.3619 40.9914C34.64 41.3227 37.0868 42.2859 38.1412 44.756L38.2389 44.9992L38.2594 45.0402C38.3151 45.1289 38.4271 45.1691 38.5289 45.131C38.631 45.0927 38.6891 44.9886 38.6725 44.885L38.6608 44.841L38.5543 44.5764C37.4073 41.8907 34.7575 40.8842 32.4264 40.5451Z"
60-
fill="#C9C9C9"
61-
/>
62-
</g>
63-
</g>
64-
<defs>
65-
<filter
66-
id="filter0_dii_1116_6888"
67-
x="0"
68-
y="0"
69-
width="78"
70-
height="77.1429"
71-
filterUnits="userSpaceOnUse"
72-
colorInterpolationFilters="sRGB"
73-
>
74-
<feFlood floodOpacity="0" result="BackgroundImageFix" />
75-
<feColorMatrix
76-
in="SourceAlpha"
77-
type="matrix"
78-
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
79-
result="hardAlpha"
80-
/>
81-
<feMorphology
82-
radius="1.71429"
83-
operator="erode"
84-
in="SourceAlpha"
85-
result="effect1_dropShadow_1116_6888"
86-
/>
87-
<feOffset dx="4.28571" dy="3.42857" />
88-
<feGaussianBlur stdDeviation="1.71429" />
89-
<feComposite in2="hardAlpha" operator="out" />
90-
<feColorMatrix
91-
type="matrix"
92-
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"
93-
/>
94-
<feBlend
95-
mode="normal"
96-
in2="BackgroundImageFix"
97-
result="effect1_dropShadow_1116_6888"
98-
/>
99-
<feBlend
100-
mode="normal"
101-
in="SourceGraphic"
102-
in2="effect1_dropShadow_1116_6888"
103-
result="shape"
104-
/>
105-
<feColorMatrix
106-
in="SourceAlpha"
107-
type="matrix"
108-
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
109-
result="hardAlpha"
110-
/>
111-
<feMorphology
112-
radius="0.857143"
113-
operator="dilate"
114-
in="SourceAlpha"
115-
result="effect2_innerShadow_1116_6888"
116-
/>
117-
<feOffset dx="3.42857" dy="3.42857" />
118-
<feGaussianBlur stdDeviation="0.9" />
119-
<feComposite
120-
in2="hardAlpha"
121-
operator="arithmetic"
122-
k2="-1"
123-
k3="1"
124-
/>
125-
<feColorMatrix
126-
type="matrix"
127-
values="0 0 0 0 0.804669 0 0 0 0 0.804669 0 0 0 0 0.804669 0 0 0 0.25 0"
128-
/>
129-
<feBlend
130-
mode="normal"
131-
in2="shape"
132-
result="effect2_innerShadow_1116_6888"
133-
/>
134-
<feColorMatrix
135-
in="SourceAlpha"
136-
type="matrix"
137-
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
138-
result="hardAlpha"
139-
/>
140-
<feMorphology
141-
radius="6"
142-
operator="dilate"
143-
in="SourceAlpha"
144-
result="effect3_innerShadow_1116_6888"
145-
/>
146-
<feOffset dx="-6" dy="-3.42857" />
147-
<feGaussianBlur stdDeviation="1.71429" />
148-
<feComposite
149-
in2="hardAlpha"
150-
operator="arithmetic"
151-
k2="-1"
152-
k3="1"
153-
/>
154-
<feColorMatrix
155-
type="matrix"
156-
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"
157-
/>
158-
<feBlend
159-
mode="normal"
160-
in2="effect2_innerShadow_1116_6888"
161-
result="effect3_innerShadow_1116_6888"
162-
/>
163-
</filter>
164-
<clipPath id="clip0_1116_6888">
165-
<rect width="72" height="72" fill="white" />
166-
</clipPath>
167-
</defs>
44+
<rect
45+
x="1.70007"
46+
y="1.2002"
47+
width="57.6"
48+
height="57.6"
49+
rx="28.8"
50+
stroke="white"
51+
strokeWidth="1.8"
52+
strokeMiterlimit="16"
53+
strokeDasharray="5.14 5.14"
54+
/>
55+
<path
56+
d="M28.443 19.8137C27.9982 14.274 34.6068 12.8514 36.858 17.4514C36.9668 17.6735 37.6332 18.9093 38.026 19.7228C38.2424 20.1712 38.073 20.5778 37.6422 20.8273L33.0201 23.5051C29.3319 26.4518 31.6925 31.786 36.07 31.2531C36.1369 31.245 36.1314 31.1653 36.0641 31.1652L35.9645 31.1682C35.3074 31.1901 34.6343 31.1322 34.0592 30.8137C31.3962 29.3382 31.0938 25.6983 33.8151 23.7121C35.0639 22.8008 37.4756 21.4995 38.8541 20.76C43.6764 19.2846 45.7392 21.1736 46.9635 25.4426C51.1902 40.2088 33.4167 51.2464 21.2379 41.8918C17.9935 39.3999 16.514 36.3554 14.611 33.0549C14.0638 31.8456 13.6264 30.4828 13.4616 29.1965C12.8835 24.7028 16.4646 22.5069 20.2418 24.3654C20.2687 24.3786 20.2992 24.3504 20.2887 24.3225C18.1325 18.662 24.4879 15.2539 27.7594 19.2141L28.3873 19.842C28.4089 19.8636 28.4454 19.8441 28.443 19.8137ZM26.9264 34.5451C25.7185 34.3695 24.5785 34.3695 23.7418 34.4133C23.3232 34.4352 22.9787 34.4678 22.7389 34.4953C22.6192 34.509 22.5249 34.5214 22.4606 34.5305C22.4285 34.535 22.4032 34.5386 22.3864 34.5412C22.3781 34.5425 22.3712 34.5444 22.3668 34.5451C22.3649 34.5453 22.363 34.5449 22.3619 34.5451L22.361 34.5461H22.36C22.2373 34.5663 22.1535 34.6822 22.1735 34.8049C22.1911 34.9123 22.2826 34.9892 22.3873 34.9934L22.4323 34.9904H22.4332C22.434 34.9903 22.4355 34.9897 22.4371 34.9894C22.4408 34.9889 22.4472 34.9886 22.4547 34.9875C22.4699 34.9852 22.493 34.981 22.5231 34.9768C22.5838 34.9682 22.6741 34.9568 22.7897 34.9435C23.0215 34.917 23.357 34.8848 23.7653 34.8635C24.5827 34.8207 25.6917 34.8212 26.8619 34.9914C29.14 35.3227 31.5868 36.2859 32.6412 38.756L32.7389 38.9992L32.7594 39.0402C32.8151 39.1289 32.9271 39.1691 33.0289 39.131C33.131 39.0927 33.1891 38.9886 33.1725 38.885L33.1608 38.841L33.0543 38.5764C31.9073 35.8907 29.2575 34.8842 26.9264 34.5451Z"
57+
fill="#C9C9C9"
58+
/>
16859
</svg>
16960
)}
17061
</>

src/app/(private)/()/page.tsx

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
useGetPopularChallenge,
1414
} from '@/service/Challenge/challenge.query';
1515
import Loading from '@/components/shared/Loading';
16+
import { useRouter } from 'next/navigation';
1617

1718
interface ParticipatingChallenge {
1819
challengeId: number;
@@ -47,6 +48,8 @@ export interface ChallengeWithParticipantCount {
4748
}
4849

4950
const Home = () => {
51+
const router = useRouter();
52+
5053
const [isSearchbarVisible, setIsSearchbarVisible] = useState(false);
5154
const [searchText, setSearchText] = useState('');
5255

@@ -66,15 +69,24 @@ const Home = () => {
6669
return `${certifiedCount}/${total}`;
6770
}
6871

72+
function getCompletedChallengeCount() {
73+
return ParticipatingChallengeData?.data.filter(
74+
(item: ParticipatingChallenge) => item.achievementRate >= 100
75+
).length;
76+
}
77+
6978
if (getParticipatingChallengePending || getPopularChallengePending) {
7079
return <Loading />;
7180
}
7281

7382
return (
74-
<div className="w-full h-full">
83+
<div className="w-full h-full cursor-pointer">
7584
<Header type="logo" icon={<PointIcon />} iconClick="/point" />
7685

77-
<div className="px-6">
86+
<div
87+
className="flex-1 flex flex-col gap-2 px-6 mt-2 pb-[5rem] overflow-y-scroll scrollbar-hide overscroll-contain"
88+
style={{ height: 'calc(100% - 3rem)' }}
89+
>
7890
{/* Search Bar */}
7991
<Header
8092
type="searchNoBack"
@@ -102,17 +114,60 @@ const Home = () => {
102114
<div className="flex w-full overflow-scroll gap-[0.62rem] scrollbar-hide">
103115
{ParticipatingChallengeData?.data.map(
104116
(item: ParticipatingChallenge) => {
105-
return (
106-
<ActiveChallenge
107-
key={item.challengeId}
108-
isActive={item.isCertifiedToday}
109-
progress={item.achievementRate}
110-
title={item.challenge.title}
111-
time={'고정값'}
112-
imgURL={item.challenge.imgURL}
113-
link={`/challenge/${item.challengeId}/certification`}
114-
/>
115-
);
117+
if (item.achievementRate >= 100) {
118+
return '';
119+
} else {
120+
return (
121+
<ActiveChallenge
122+
key={item.challengeId}
123+
isActive={
124+
item.isCertifiedToday || item.achievementRate >= 100
125+
}
126+
progress={item.achievementRate}
127+
title={item.challenge.title}
128+
time={'고정값'}
129+
imgURL={item.challenge.imgURL}
130+
certificationLink={`/challenge/${item.challengeId}/certification`}
131+
progressLink={`/challenge/${item.challengeId}/progress`}
132+
/>
133+
);
134+
}
135+
}
136+
)}
137+
</div>
138+
</div>
139+
) : null}
140+
141+
{/* Complete Challenge */}
142+
{ParticipatingChallengeData?.data.length != 0 ? (
143+
<div className="flex flex-col gap-2 mt-5">
144+
<div className="flex items-center justify-between">
145+
<div className="text-he">완료한 챌린지</div>
146+
<div className="text-gray-300 text-fn">
147+
{getCompletedChallengeCount()}개 완료
148+
</div>
149+
</div>
150+
151+
<div className="flex w-full overflow-scroll gap-[0.62rem] scrollbar-hide">
152+
{ParticipatingChallengeData?.data.map(
153+
(item: ParticipatingChallenge) => {
154+
if (item.achievementRate >= 100) {
155+
return (
156+
<ActiveChallenge
157+
key={item.challengeId}
158+
isActive={
159+
item.isCertifiedToday || item.achievementRate >= 100
160+
}
161+
title={item.challenge.title}
162+
time={'고정값'}
163+
imgURL={item.challenge.imgURL}
164+
certificationLink={`/challenge/${item.challengeId}/certification`}
165+
progressLink={`/challenge/${item.challengeId}/progress`}
166+
/>
167+
);
168+
} else {
169+
return '';
170+
}
116171
}
117172
)}
118173
</div>
@@ -121,7 +176,12 @@ const Home = () => {
121176

122177
{/* Hot Challenge */}
123178
<div className="flex flex-col gap-2 mt-5">
124-
<div className="flex items-center justify-between">
179+
<div
180+
className="flex items-center justify-between"
181+
onClick={() => {
182+
router.push('/challenge');
183+
}}
184+
>
125185
<div className="text-he">요즘 뜨는 챌린지</div>
126186
<ArrowIcon width="24" height="24" location="right" />
127187
</div>
@@ -133,7 +193,7 @@ const Home = () => {
133193
count={item.participantCount}
134194
title={item.title}
135195
imgUrl={item.imgURL}
136-
link={`/challenge/${item.id}`}
196+
link={`/challenge/${item.id}?back=/`}
137197
/>
138198
)
139199
)}

src/app/(private)/challenge/[id]/all/page.tsx

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import Header from '@/components/shared/Header';
55
import { CoinIcon, InfoIcon } from '@public/icons/Challenge/progress';
66
import { useParams } from 'next/navigation';
77
import useStatusBarBridge from '@/lib/hooks/useStatusBarBridge';
8+
import { useGetChallengeProgressLog } from '@/service/Challenge/challenge.query';
9+
import Loading from '@/components/shared/Loading';
810

9-
const ALl = () => {
11+
const ALL = () => {
1012
const { id } = useParams();
1113

1214
useStatusBarBridge({
@@ -15,34 +17,71 @@ const ALl = () => {
1517
bottomBackgroundColor: '#FFF0E5',
1618
});
1719

20+
const { data: data, isLoading } = useGetChallengeProgressLog(id as string);
21+
const progressLog = data?.data;
22+
23+
const getChallengeDates = (joinedAt: string, totalDay: number): string[] => {
24+
const start = new Date(joinedAt);
25+
return Array.from({ length: totalDay }).map((_, i) => {
26+
const d = new Date(start);
27+
d.setDate(start.getDate() + i);
28+
return d.toISOString().split('T')[0];
29+
});
30+
};
31+
32+
const challengeDates = getChallengeDates(
33+
progressLog.joinedAt,
34+
progressLog.totalDay
35+
);
36+
const certifiedSet = new Set(progressLog.certifiedDays || []);
37+
38+
const today = new Date();
39+
today.setHours(23, 59, 59, 59);
40+
41+
if (isLoading || !progressLog) {
42+
return <Loading />;
43+
}
44+
1845
return (
19-
<div className="flex flex-col w-full h-full pb-4 overflow-y-auto bg-primary-light gap-[0.62rem]">
20-
<Header
21-
type="default"
22-
title="진행사항"
23-
icon={<InfoIcon />}
24-
backClick={`/challenge/${id}/progress`}
25-
/>
46+
<div className="flex flex-col w-full h-full pb-4 overflow-y-auto bg-primary-light gap-[0.62rem] custom601:mt-[-40px]">
47+
<div className="bg-primary-light custom601:pt-[40px] min-h-fit">
48+
<Header
49+
type="default"
50+
title="진행사항"
51+
icon={<InfoIcon />}
52+
backClick={`/challenge/${id}/progress`}
53+
/>
54+
</div>
2655

2756
<div className="flex flex-col gap-[0.62rem] w-full items-center px-6">
2857
<div className="w-full px-[0.62rem] py-5 bg-gray-white rounded-[1.25rem]">
2958
<div className="grid grid-cols-3 gap-y-4 gap-x-2 justify-items-center">
30-
{Array.from({ length: 30 }).map((_, index) => (
31-
<div
32-
key={index}
33-
className="flex flex-col items-center justify-between gap-2"
34-
>
35-
<div className="bg-primary-light w-fit h-fit p-[0.45rem] rounded-full">
36-
<CoinIcon disabled={false} />
59+
{challengeDates.map((dateStr, index) => {
60+
const thisDate = new Date(dateStr);
61+
const isPassed = thisDate <= today;
62+
const isCertified = certifiedSet.has(dateStr);
63+
64+
return isPassed ? (
65+
<div
66+
key={index}
67+
className="flex flex-col items-center justify-between gap-2"
68+
>
69+
<div
70+
className={`w-fit h-fit p-[0.45rem] rounded-full ${
71+
isCertified ? 'bg-primary-light' : 'bg-gray-100'
72+
}`}
73+
>
74+
<CoinIcon disabled={!isCertified} />
75+
</div>
76+
<div className="text-gray-500 text-c1">{index + 1}일차</div>
3777
</div>
38-
<div className="text-gray-500 text-c1">{index + 1}일차</div>
39-
</div>
40-
))}
78+
) : null;
79+
})}
4180
</div>
4281
</div>
4382
</div>
4483
</div>
4584
);
4685
};
4786

48-
export default ALl;
87+
export default ALL;

0 commit comments

Comments
 (0)