Skip to content

Commit 85f914f

Browse files
authored
Merge pull request #118 from FE9-2/feat/myAlbaform
feat: ๋‚ด ์•Œ๋ฐ”ํผ > ์ง€์›์ž ํŽ˜์ด์ง€ ์ถ”๊ฐ€
2 parents a8beed9 + 9379b85 commit 85f914f

File tree

13 files changed

+244
-80
lines changed

13 files changed

+244
-80
lines changed

โ€Žpackage-lock.jsonโ€Ž

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

โ€Žpackage.jsonโ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
"@typescript-eslint/parser": "^8.9.0",
6161
"@vitejs/plugin-react": "^4.3.3",
6262
"autoprefixer": "^10.4.20",
63-
"chromatic": "^11.18.1",
63+
"chromatic": "^11.20.0",
6464
"dotenv-cli": "^7.4.3",
6565
"eslint": "^8.57.1",
6666
"eslint-config-next": "14.2.15",

โ€Žsrc/app/(pages)/albaList/page.tsxโ€Ž

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useUser } from "@/hooks/queries/user/me/useUser";
1313
import Link from "next/link";
1414
import { IoAdd } from "react-icons/io5";
1515
import { userRoles } from "@/constants/userRoles";
16+
import FloatingBtn from "@/app/components/button/default/FloatingBtn";
1617

1718
const FORMS_PER_PAGE = 10;
1819

@@ -130,15 +131,11 @@ export default function AlbaList() {
130131
<div className="w-full pt-[132px]">
131132
{/* ํผ ๋งŒ๋“ค๊ธฐ ๋ฒ„ํŠผ - ๊ณ ์ • ์œ„์น˜ */}
132133
{isOwner && (
133-
<div className="fixed bottom-[50%] right-4 z-[9999] translate-y-1/2">
134-
<Link
135-
href="/addform"
136-
className="flex items-center gap-2 rounded-lg bg-[#FFB800] px-4 py-3 text-base font-semibold text-white shadow-lg transition-all hover:bg-[#FFA800] md:px-6 md:text-lg"
137-
>
138-
<IoAdd className="size-6" />
139-
<span>ํผ ๋งŒ๋“ค๊ธฐ</span>
140-
</Link>
141-
</div>
134+
<Link href="/addform" className="fixed bottom-[50%] right-4 z-[9999] translate-y-1/2">
135+
<FloatingBtn icon={<IoAdd className="size-6" />} variant="orange">
136+
ํผ ๋งŒ๋“ค๊ธฐ
137+
</FloatingBtn>
138+
</Link>
142139
)}
143140

144141
{!data?.pages?.[0]?.data?.length ? (
@@ -152,11 +149,7 @@ export default function AlbaList() {
152149
<React.Fragment key={page.nextCursor}>
153150
{page.data.map((form) => (
154151
<div key={form.id}>
155-
<Link
156-
href={isOwner ? `/albaFormDetail/owner/${form.id}` : `/albaFormDetail/applicant/${form.id}`}
157-
>
158-
<AlbaListItem {...form} />
159-
</Link>
152+
<AlbaListItem {...form} />
160153
</div>
161154
))}
162155
</React.Fragment>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"use client";
2+
3+
import React from "react";
4+
import FilterDropdown from "@/app/components/button/dropdown/FilterDropdown";
5+
import { formStatusOptions } from "@/constants/formOptions";
6+
import { useRouter } from "next/navigation";
7+
8+
const APPLICANT_SORT_OPTIONS = [
9+
{ label: "์ „์ฒด", value: "" },
10+
{ label: "์ตœ์‹ ์ˆœ", value: formStatusOptions.INTERVIEW_PENDING },
11+
{ label: "์‹œ๊ธ‰๋†’์€์ˆœ", value: formStatusOptions.INTERVIEW_COMPLETED },
12+
{ label: "์ง€์›์ž ๋งŽ์€์ˆœ", value: formStatusOptions.HIRED },
13+
{ label: "์Šคํฌ๋žฉ ๋งŽ์€์ˆœ", value: formStatusOptions.REJECTED },
14+
];
15+
16+
interface ApplicantSortSectionProps {
17+
pathname: string;
18+
searchParams: URLSearchParams;
19+
}
20+
21+
export default function ApplicantSortSection({ pathname, searchParams }: ApplicantSortSectionProps) {
22+
const router = useRouter();
23+
const currentOrderBy = searchParams.get("orderBy") || "";
24+
25+
const currentLabel =
26+
APPLICANT_SORT_OPTIONS.find((opt) => opt.value === currentOrderBy)?.label || APPLICANT_SORT_OPTIONS[0].label;
27+
28+
const handleSortChange = (selected: string) => {
29+
const option = APPLICANT_SORT_OPTIONS.find((opt) => opt.label === selected);
30+
if (option) {
31+
const params = new URLSearchParams(searchParams);
32+
params.set("orderBy", option.value);
33+
router.push(`${pathname}?${params.toString()}`);
34+
}
35+
};
36+
37+
return (
38+
<FilterDropdown
39+
options={APPLICANT_SORT_OPTIONS.map((option) => option.label)}
40+
className="!w-28 md:!w-40"
41+
initialValue={currentLabel}
42+
onChange={handleSortChange}
43+
/>
44+
);
45+
}
Lines changed: 113 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,141 @@
11
"use client";
22

3+
import React from "react";
34
import { useEffect } from "react";
4-
import { useRouter } from "next/navigation";
5+
import { usePathname, useRouter, useSearchParams } from "next/navigation";
6+
import { useInView } from "react-intersection-observer";
57
import { useUser } from "@/hooks/queries/user/me/useUser";
68
import { userRoles } from "@/constants/userRoles";
9+
import ApplicantSortSection from "./components/ApplicantSortSection";
10+
import SearchSection from "@/app/components/layout/forms/SearchSection";
11+
import MyApplicationListItem from "@/app/components/card/cardList/MyApplicationListItem";
12+
import { useMyApplications } from "@/hooks/queries/user/me/useMyApplications";
13+
14+
const APPLICATIONS_PER_PAGE = 10;
715

816
export default function ApplicantPage() {
917
const router = useRouter();
10-
const { user, isLoading } = useUser();
18+
const pathname = usePathname();
19+
const searchParams = useSearchParams();
20+
const { user, isLoading: isUserLoading } = useUser();
21+
22+
// ๋ฌดํ•œ ์Šคํฌ๋กค์„ ์œ„ํ•œ Intersection Observer ์„ค์ •
23+
const { ref, inView } = useInView({
24+
threshold: 0.1,
25+
triggerOnce: false,
26+
rootMargin: "100px",
27+
});
28+
29+
// ๊ฒ€์ƒ‰ ๋ฐ ์ •๋ ฌ ์ƒํƒœ ๊ด€๋ฆฌ
30+
const status = searchParams.get("status") || undefined;
31+
const keyword = searchParams.get("keyword") || undefined;
32+
33+
const {
34+
data,
35+
fetchNextPage,
36+
hasNextPage,
37+
isFetchingNextPage,
38+
isLoading: isLoadingData,
39+
error,
40+
} = useMyApplications({
41+
limit: APPLICATIONS_PER_PAGE,
42+
status,
43+
keyword,
44+
});
1145

1246
useEffect(() => {
13-
if (!isLoading) {
47+
if (!isUserLoading) {
1448
if (!user) {
1549
router.push("/login");
1650
} else if (user.role === userRoles.OWNER) {
1751
router.push("/myAlbaform/owner");
1852
}
1953
}
20-
}, [user, isLoading, router]);
54+
}, [user, isUserLoading, router]);
55+
56+
// ์Šคํฌ๋กค์ด ํ•˜๋‹จ์— ๋„๋‹ฌํ•˜๋ฉด ๋‹ค์Œ ํŽ˜์ด์ง€ ๋กœ๋“œ
57+
useEffect(() => {
58+
if (inView && hasNextPage && !isFetchingNextPage) {
59+
fetchNextPage();
60+
}
61+
}, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]);
62+
63+
// ์—๋Ÿฌ ์ƒํƒœ ์ฒ˜๋ฆฌ
64+
if (error) {
65+
return (
66+
<div className="flex h-[calc(100vh-200px)] items-center justify-center">
67+
<p className="text-red-500">์ง€์› ๋‚ด์—ญ์„ ๋ถˆ๋Ÿฌ์˜ค๋Š”๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.</p>
68+
</div>
69+
);
70+
}
2171

22-
if (isLoading) {
72+
// ๋กœ๋”ฉ ์ƒํƒœ ์ฒ˜๋ฆฌ
73+
if (isUserLoading || isLoadingData) {
2374
return (
2475
<div className="flex h-[calc(100vh-200px)] items-center justify-center">
2576
<div>๋กœ๋”ฉ ์ค‘...</div>
2677
</div>
2778
);
2879
}
2980

30-
// ์ง€์›์ž์šฉ ํŽ˜์ด์ง€ ์ปจํ…์ธ 
3181
return (
32-
<div>
33-
<h1>์ง€์›์ž ํŽ˜์ด์ง€</h1>
34-
{/* ์ง€์›์ž์šฉ ์ปจํ…์ธ  */}
82+
<div className="flex min-h-screen flex-col items-center">
83+
{/* ๊ฒ€์ƒ‰ ์„น์…˜๊ณผ ํ•„ํ„ฐ๋ฅผ ๊ณ ์ • ์œ„์น˜๋กœ ์„ค์ • */}
84+
<div className="fixed left-0 right-0 top-16 z-40 bg-white shadow-sm">
85+
{/* ๊ฒ€์ƒ‰ ์„น์…˜ */}
86+
<div className="w-full border-b border-grayscale-100">
87+
<div className="mx-auto flex max-w-screen-2xl flex-col gap-4 px-4 py-4 md:px-6 lg:px-8">
88+
<SearchSection />
89+
</div>
90+
</div>
91+
92+
{/* ํ•„ํ„ฐ ์„น์…˜ */}
93+
<div className="w-full border-b border-grayscale-100">
94+
<div className="mx-auto flex max-w-screen-2xl items-center justify-between gap-2 px-4 py-4 md:px-6 lg:px-8">
95+
<ApplicantSortSection pathname={pathname} searchParams={searchParams} />
96+
</div>
97+
</div>
98+
</div>
99+
100+
{/* ๋ฉ”์ธ ์ฝ˜ํ…์ธ  ์˜์—ญ */}
101+
<div className="w-full pt-[132px]">
102+
{!data?.pages?.[0]?.data?.length ? (
103+
<div className="flex h-[calc(100vh-200px)] flex-col items-center justify-center">
104+
<p className="text-grayscale-500">์ง€์› ๋‚ด์—ญ์ด ์—†์Šต๋‹ˆ๋‹ค.</p>
105+
</div>
106+
) : (
107+
<div className="mx-auto mt-4 w-full max-w-screen-xl px-3">
108+
<div className="flex flex-wrap justify-start gap-6">
109+
{data?.pages.map((page) => (
110+
<React.Fragment key={page.nextCursor}>
111+
{page.data.map((application) => (
112+
<div key={application.id}>
113+
<MyApplicationListItem
114+
id={application.id}
115+
createdAt={application.createdAt}
116+
updatedAt={application.updatedAt}
117+
status={application.status}
118+
resumeId={application.resumeId}
119+
resumeName={application.resumeName}
120+
form={application.form}
121+
/>
122+
</div>
123+
))}
124+
</React.Fragment>
125+
))}
126+
</div>
127+
128+
{/* ๋ฌดํ•œ ์Šคํฌ๋กค ํŠธ๋ฆฌ๊ฑฐ ์˜์—ญ */}
129+
<div ref={ref} className="h-4 w-full">
130+
{isFetchingNextPage && (
131+
<div className="flex justify-center py-4">
132+
<div className="h-6 w-6 animate-spin rounded-full border-2 border-primary-orange-300 border-t-transparent" />
133+
</div>
134+
)}
135+
</div>
136+
</div>
137+
)}
138+
</div>
35139
</div>
36140
);
37141
}

โ€Žsrc/app/(pages)/myAlbaform/(role)/owner/page.tsxโ€Ž

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useUser } from "@/hooks/queries/user/me/useUser";
1313
import Link from "next/link";
1414
import { IoAdd } from "react-icons/io5";
1515
import { userRoles } from "@/constants/userRoles";
16+
import FloatingBtn from "@/app/components/button/default/FloatingBtn";
1617

1718
const FORMS_PER_PAGE = 10;
1819

@@ -186,15 +187,11 @@ export default function AlbaList() {
186187
<div className="w-full pt-[132px]">
187188
{/* ํผ ๋งŒ๋“ค๊ธฐ ๋ฒ„ํŠผ - ๊ณ ์ • ์œ„์น˜ */}
188189
{isOwner && (
189-
<div className="fixed bottom-[50%] right-4 z-[9999] translate-y-1/2">
190-
<Link
191-
href="/addform"
192-
className="flex items-center gap-2 rounded-lg bg-[#FFB800] px-4 py-3 text-base font-semibold text-white shadow-lg transition-all hover:bg-[#FFA800] md:px-6 md:text-lg"
193-
>
194-
<IoAdd className="size-6" />
195-
<span>ํผ ๋งŒ๋“ค๊ธฐ</span>
196-
</Link>
197-
</div>
190+
<Link href="/addform" className="fixed bottom-[50%] right-4 z-[9999] translate-y-1/2">
191+
<FloatingBtn icon={<IoAdd className="size-6" />} variant="orange">
192+
ํผ ๋งŒ๋“ค๊ธฐ
193+
</FloatingBtn>
194+
</Link>
198195
)}
199196

200197
{!data?.pages?.[0]?.data?.length ? (

โ€Žsrc/app/components/card/cardList/AlbaListItem.tsxโ€Ž

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,26 @@ const AlbaListItem = ({
6262
openModal("customForm", {
6363
isOpen: true,
6464
title: "์ง€์›ํ•˜๊ธฐ",
65-
content: "์ •๋ง๋กœ ์ง€์›ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?",
65+
content: "์ง€์›ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?",
6666
onConfirm: () => {
67+
openModal("customForm", {
68+
isOpen: false,
69+
title: "",
70+
content: "",
71+
onConfirm: () => {},
72+
onCancel: () => {},
73+
});
6774
router.push(`/apply/${id}`);
6875
},
69-
onCancel: () => {},
76+
onCancel: () => {
77+
openModal("customForm", {
78+
isOpen: false,
79+
title: "",
80+
content: "",
81+
onConfirm: () => {},
82+
onCancel: () => {},
83+
});
84+
},
7085
});
7186
};
7287

@@ -76,11 +91,26 @@ const AlbaListItem = ({
7691
openModal("customForm", {
7792
isOpen: true,
7893
title: "์Šคํฌ๋žฉ ํ™•์ธ",
79-
content: "์ด ๊ณต๊ณ ๋ฅผ ์Šคํฌ๋žฉํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?",
94+
content: "์Šคํฌ๋žฉํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?",
8095
onConfirm: () => {
96+
openModal("customForm", {
97+
isOpen: false,
98+
title: "",
99+
content: "",
100+
onConfirm: () => {},
101+
onCancel: () => {},
102+
});
81103
scrap();
82104
},
83-
onCancel: () => {},
105+
onCancel: () => {
106+
openModal("customForm", {
107+
isOpen: false,
108+
title: "",
109+
content: "",
110+
onConfirm: () => {},
111+
onCancel: () => {},
112+
});
113+
},
84114
});
85115
};
86116

0 commit comments

Comments
ย (0)