Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] 어드민 UI 구현 #135

Merged
merged 4 commits into from
Aug 12, 2024
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
56 changes: 56 additions & 0 deletions admin/src/apis/lotteryAPI.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
GetLotteryExpectationsParams,
GetLotteryExpectationsResponse,
GetLotteryParticipantResponse,
GetLotteryResponse,
GetLotteryWinnerParams,
GetLotteryWinnerResponse,
Expand Down Expand Up @@ -52,6 +53,61 @@ export const LotteryAPI = {
throw error;
}
},
async getLotteryParticipant({
id,
size,
page,
phoneNumber,
}: GetLotteryWinnerParams): Promise<GetLotteryParticipantResponse> {
try {
return new Promise((resolve) =>
resolve({
data: [
{
id: 1,
phoneNumber: "010-1111-2222",
linkClickedCounts: 1,
expectation: 1,
appliedCount: 3,
createdAt: "2024-08-12T02:10:37.279369",
updatedAt: "2024-08-12T02:13:48.390954",
},
{
id: 2,
phoneNumber: "010-1111-2223",
linkClickedCounts: 1,
expectation: 1,
appliedCount: 3,
createdAt: "2024-08-12T02:10:37.279369",
updatedAt: "2024-08-12T02:13:48.390954",
},
{
id: 3,
phoneNumber: "010-1111-2224",
linkClickedCounts: 1,
expectation: 1,
appliedCount: 3,
createdAt: "2024-08-12T02:10:37.279369",
updatedAt: "2024-08-12T02:13:48.390954",
},
],
isLastPage: true,
totalParticipants: 10000,
})
);
const response = await fetchWithTimeout(
`${baseURL}/${id}/participants?size=${size}&page=${page}&number=${phoneNumber}`,
{
method: "GET",
headers: headers,
}
);
return response.json();
} catch (error) {
console.error("Error:", error);
throw error;
}
},
async getLotteryWinner({
id,
size,
Expand Down
19 changes: 19 additions & 0 deletions admin/src/constants/lottery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,22 @@ export const LOTTERY_HEADER = [
"추첨 당첨 인원 수",
"진행 상태",
];

export const LOTTERY_WINNER_HEADER = [
"등수",
"ID",
"전화번호",
"공유 링크 클릭 횟수",
"기대평 작성 여부",
"총 응모 횟수",
];
export const LOTTERY_EXPECTATIONS_HEADER = ["캐스퍼 ID", "기대평"];
export const LOTTERY_PARTICIPANT_HEADER = [
"ID",
"생성 날짜",
"생성 시간",
"전화 번호",
"공유 링크 클릭 횟수",
"기대평 작성 여부",
"총 응모 횟수",
];
9 changes: 8 additions & 1 deletion admin/src/pages/Lottery/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,14 @@ export default function Lottery() {
<Table headers={LOTTERY_HEADER} data={getLotteryData()} height="auto" />

<div className="self-end flex gap-4">
<Button buttonSize="sm" onClick={() => navigate("/lottery/participant-list")}>
<Button
buttonSize="sm"
onClick={() =>
navigate("/lottery/participant-list", {
state: { id: lottery.lotteryEventId },
})
}
>
참여자 리스트 보러가기
</Button>
<Button
Expand Down
143 changes: 143 additions & 0 deletions admin/src/pages/LotteryParticipantList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { useMemo, useRef, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { LotteryAPI } from "@/apis/lotteryAPI";
import Button from "@/components/Button";
import TabHeader from "@/components/TabHeader";
import Table from "@/components/Table";
import { LOTTERY_EXPECTATIONS_HEADER, LOTTERY_PARTICIPANT_HEADER } from "@/constants/lottery";
import useInfiniteFetch from "@/hooks/useInfiniteFetch";
import useIntersectionObserver from "@/hooks/useIntersectionObserver";
import useModal from "@/hooks/useModal";
import { LotteryExpectationsType } from "@/types/lottery";
import { GetLotteryParticipantResponse } from "@/types/lotteryApi";

export default function LotteryParticipantList() {
const location = useLocation();
const navigate = useNavigate();

const lotteryId = location.state.id;

const { handleOpenModal, ModalComponent } = useModal();
const [selectedParticipant, setSelectedParticipant] = useState<LotteryExpectationsType[]>([]);
const phoneNumberRef = useRef<string>("");
const phoneNumberInputRef = useRef<HTMLInputElement>(null);

const {
data: participantInfo,
totalLength: participantLength,
isSuccess: isSuccessGetParticipant,
fetchNextPage: getParticipantInfo,
refetch: refetchParticipantInfo,
} = useInfiniteFetch({
fetch: (pageParam: number) =>
LotteryAPI.getLotteryParticipant({
id: lotteryId,
size: 10,
page: pageParam,
phoneNumber: phoneNumberRef.current,
}),
initialPageParam: 1,
getNextPageParam: (currentPageParam: number, lastPage: GetLotteryParticipantResponse) => {
return lastPage.isLastPage ? undefined : currentPageParam + 1;
},
});

const tableContainerRef = useRef<HTMLDivElement>(null);
const { targetRef } = useIntersectionObserver<HTMLTableRowElement>({
onIntersect: getParticipantInfo,
enabled: isSuccessGetParticipant,
});

const handleRefetch = () => {
phoneNumberRef.current = phoneNumberInputRef.current?.value || "";
refetchParticipantInfo();
};

const handleLotteryWinner = () => {
navigate("/lottery/winner-list", { state: { id: lotteryId } });
};

const handleClickExpectation = async (participantId: number) => {
handleOpenModal();

const data = await LotteryAPI.getLotteryExpectations({
lotteryId,
participantId: participantId,
});
setSelectedParticipant(data);
};

const expectations = selectedParticipant.map((participant) => [
participant.casperId,
participant.expectation,
]);

const participantList = useMemo(
() =>
participantInfo.map((participant) => [
participant.id,
participant.createdAt,
participant.createdAt,
participant.phoneNumber,
participant.linkClickedCounts,
<div className="flex justify-between">
<span>{participant.expectation}</span>
<span
className="cursor-pointer"
onClick={() => handleClickExpectation(participant.id)}
>
기대평 보기
</span>
</div>,
participant.appliedCount,
]),
[participantInfo]
);

return (
<div className="flex flex-col items-center h-screen">
<TabHeader />

<div className="w-[1560px] flex flex-col items-center justify-center gap-8 mt-10">
<div className="flex w-full justify-between">
<div className="flex items-center gap-2">
<img
alt="뒤로 가기 버튼"
src="/assets/icons/left-arrow.svg"
className="cursor-pointer"
onClick={() => navigate(-1)}
/>
<p className="h-body-1-medium">
전체 참여자 리스트 {participantLength.toLocaleString("en-US")} 명
</p>
</div>

<div className="flex gap-2">
<input
ref={phoneNumberInputRef}
className="border border-neutral-950 rounded-lg text-neutral-950 h-body-1-medium"
/>
<Button buttonSize="sm" onClick={handleRefetch}>
검색
</Button>
</div>
</div>

<Table
ref={tableContainerRef}
headers={LOTTERY_PARTICIPANT_HEADER}
data={participantList}
dataLastItem={targetRef}
/>

<Button buttonSize="lg" onClick={handleLotteryWinner}>
당첨자 보러가기
</Button>
</div>

<ModalComponent>
<Table headers={LOTTERY_EXPECTATIONS_HEADER} data={expectations} height="auto" />
</ModalComponent>
</div>
);
}
11 changes: 1 addition & 10 deletions admin/src/pages/LotteryWinnerList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,13 @@ import { LotteryAPI } from "@/apis/lotteryAPI";
import Button from "@/components/Button";
import TabHeader from "@/components/TabHeader";
import Table from "@/components/Table";
import { LOTTERY_EXPECTATIONS_HEADER, LOTTERY_WINNER_HEADER } from "@/constants/lottery";
import useInfiniteFetch from "@/hooks/useInfiniteFetch";
import useIntersectionObserver from "@/hooks/useIntersectionObserver";
import useModal from "@/hooks/useModal";
import { LotteryExpectationsType } from "@/types/lottery";
import { GetLotteryWinnerResponse } from "@/types/lotteryApi";

const LOTTERY_WINNER_HEADER = [
"등수",
"ID",
"전화번호",
"공유 링크 클릭 횟수",
"기대평 작성 여부",
"총 응모 횟수",
];
const LOTTERY_EXPECTATIONS_HEADER = ["캐스퍼 ID", "기대평"];

export default function LotteryWinnerList() {
const location = useLocation();
const navigate = useNavigate();
Expand Down
5 changes: 4 additions & 1 deletion admin/src/pages/RushWinnerList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,10 @@ export default function RushWinnerList() {
/>
<p className="h-body-1-medium">
선착순 참여자 리스트{" "}
{isWinnerToggle ? winnersLength : participantsLength}
{(isWinnerToggle ? winnersLength : participantsLength).toLocaleString(
"en-US"
)}{" "}
</p>
<Button
buttonSize="sm"
Expand Down
5 changes: 5 additions & 0 deletions admin/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ProtectedRoute, UnProtectedRoute } from "./components/Route";
import RushLayout from "./features/Rush/Layout";
import Login from "./pages/Login";
import Lottery from "./pages/Lottery";
import LotteryParticipantList from "./pages/LotteryParticipantList";
import LotteryWinner from "./pages/LotteryWinner";
import LotteryWinnerList from "./pages/LotteryWinnerList";
import Rush from "./pages/Rush";
Expand Down Expand Up @@ -63,6 +64,10 @@ export const router = createBrowserRouter([
element: <LotteryWinner />,
loader: LotteryAPI.getLottery,
},
{
path: "participant-list",
element: <LotteryParticipantList />,
},
{
path: "winner-list",
element: <LotteryWinnerList />,
Expand Down
5 changes: 5 additions & 0 deletions admin/src/types/lottery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,8 @@ export interface LotteryWinnerType {
expectation: number;
appliedCount: number;
}

export interface LotteryParticipantType extends LotteryWinnerType {
createdAt: string;
updatedAt: string;
}
9 changes: 8 additions & 1 deletion admin/src/types/lotteryApi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { InfiniteListData } from "./common";
import { LotteryEventType, LotteryExpectationsType, LotteryWinnerType } from "./lottery";
import {
LotteryEventType,
LotteryExpectationsType,
LotteryParticipantType,
LotteryWinnerType,
} from "./lottery";

export type GetLotteryResponse = LotteryEventType[];

Expand All @@ -22,3 +27,5 @@ export interface GetLotteryExpectationsParams {
export type GetLotteryExpectationsResponse = LotteryExpectationsType[];

export type GetLotteryWinnerResponse = InfiniteListData<LotteryWinnerType>;

export type GetLotteryParticipantResponse = InfiniteListData<LotteryParticipantType>;
Loading