Skip to content

Commit

Permalink
Merge pull request #114 from softeerbootcamp4th/feat/CLAP-175
Browse files Browse the repository at this point in the history
feat(CLAP-175): 어드민 미구현 기능 추가
  • Loading branch information
DaeWon9 authored Aug 22, 2024
2 parents 0ccb775 + fb098aa commit 96e722a
Show file tree
Hide file tree
Showing 21 changed files with 592 additions and 73 deletions.
Binary file added packages/admin/public/images/modal/pending.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions packages/admin/src/apis/partsEvent/apiGetLotteries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { customFetch } from "@watermelon-clap/core/src/utils";
import { getAccessToken } from "@watermelon-clap/core/src/utils";
import { IWinner } from "./type";

export const apiGetLotteries = async (): Promise<IWinner[]> => {
const url = `${import.meta.env.VITE_BACK_BASE_URL}/admin/lotteries`;

return customFetch(url, {
headers: {
Authorization: `Bearer ${getAccessToken()}`,
},
})
.then((response) => response.json())
.catch((error) => {
throw error;
});
};
17 changes: 17 additions & 0 deletions packages/admin/src/apis/partsEvent/apiGetPartsWinner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { customFetch } from "@watermelon-clap/core/src/utils";
import { getAccessToken } from "@watermelon-clap/core/src/utils";
import { IWinner } from "./type";

export const apiGetPartsWinner = async (): Promise<IWinner[]> => {
const url = `${import.meta.env.VITE_BACK_BASE_URL}/admin/event/parts`;

return customFetch(url, {
headers: {
Authorization: `Bearer ${getAccessToken()}`,
},
})
.then((response) => response.json())
.catch((error) => {
throw error;
});
};
17 changes: 17 additions & 0 deletions packages/admin/src/apis/partsEvent/apiPostLotteries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { customFetch } from "@watermelon-clap/core/src/utils";
import { getAccessToken } from "@watermelon-clap/core/src/utils";

export const apiPostLottries = async (): Promise<Response> => {
const url = `${import.meta.env.VITE_BACK_BASE_URL}/admin/event/lotteries
`;
return customFetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${getAccessToken()}`,
},
})
.then((response) => response)
.catch((error) => {
throw error;
});
};
18 changes: 18 additions & 0 deletions packages/admin/src/apis/partsEvent/apiPostLotteriesCheckStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { customFetch } from "@watermelon-clap/core/src/utils";
import { getAccessToken } from "@watermelon-clap/core/src/utils";

export const apiPostLotteriesCheckStatus = async (
id: string,
): Promise<Response> => {
const url = `${import.meta.env.VITE_BACK_BASE_URL}/admin/event/lotteries/${id}/done`;
return customFetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${getAccessToken()}`,
},
})
.then((response) => response)
.catch((error) => {
throw error;
});
};
39 changes: 27 additions & 12 deletions packages/admin/src/apis/partsEvent/apiPostPartsEvent.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,41 @@
import { IOrderEvent } from "@watermelon-clap/core/src/types";
import { customFetch, getAccessToken } from "@watermelon-clap/core/src/utils";
import { IAdminPostOrderEventRequest } from "../orderEvent/type";
import { IAdminPostPartsEventRequest } from "./type";

export const apiPostPartsEvent = async ({
orderEvent,
rewardImage,
quizImage,
}: IAdminPostOrderEventRequest): Promise<IOrderEvent> => {
name,
startTime,
endTime,
rewards,
}: IAdminPostPartsEventRequest): Promise<Response> => {
const formdata = new FormData();

// rank와 name 속성만 추출
const processedRewards = rewards.map((reward) => ({
rank: reward.rewardRank,
winnerCount: reward.winnerCount,
name: reward.rewardName,
}));

const eventData = {
name: name,
startTime: startTime,
endTime: endTime,
rewards: processedRewards,
};

formdata.append(
"orderEvent",
new Blob([JSON.stringify(orderEvent)], {
"event",
new Blob([JSON.stringify(eventData)], {
type: "application/json",
}),
);

formdata.append("rewardImage", rewardImage);
formdata.append("quizImage", quizImage);
rewards.map((reward) => {
formdata.append("files", reward.rewardFile as File);
});

return customFetch(
`${import.meta.env.VITE_BACK_BASE_URL}/admin/event/order`,
`${import.meta.env.VITE_BACK_BASE_URL}/admin/event/lotteries/create`,
{
method: "POST",
headers: {
Expand All @@ -30,7 +45,7 @@ export const apiPostPartsEvent = async ({
},
)
.then((response) => {
return response.json();
return response;
})
.catch((error) => {
throw error;
Expand Down
17 changes: 17 additions & 0 deletions packages/admin/src/apis/partsEvent/apiPostPartsEventWinner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { customFetch } from "@watermelon-clap/core/src/utils";
import { getAccessToken } from "@watermelon-clap/core/src/utils";

export const apiPostPartsEventWinner = async (): Promise<Response> => {
const url = `${import.meta.env.VITE_BACK_BASE_URL}/admin/event/parts
`;
return customFetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${getAccessToken()}`,
},
})
.then((response) => response)
.catch((error) => {
throw error;
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { customFetch } from "@watermelon-clap/core/src/utils";
import { getAccessToken } from "@watermelon-clap/core/src/utils";

export const apiPostPartsWinnerCheckStatus = async (
id: string,
): Promise<Response> => {
const url = `${import.meta.env.VITE_BACK_BASE_URL}/admin/event/parts/${id}/done`;
return customFetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${getAccessToken()}`,
},
})
.then((response) => response)
.catch((error) => {
throw error;
});
};
26 changes: 26 additions & 0 deletions packages/admin/src/apis/partsEvent/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,29 @@ export interface IParticipant {
export interface IGetPartsEventResponse {
content: IParticipant[];
}

export interface IWinner {
rank: number;
reward: string;
uid: string;
email: string;
name: string;
phoneNumber: string;
address: string;
status: "DONE" | "READY";
}

export interface IAdminReward {
rewardName: string;
rewardRank: string;
winnerCount: number | string;
rewardFile: File | null;
rewardImageUrl: string | null;
}

export interface IAdminPostPartsEventRequest {
name: string;
startTime: string;
endTime: string;
rewards: IAdminReward[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import {
GoogleLoginModal,
ImageModal,
ImageModalProps,
PendingModal,
} from "./modal";

export type ModalType = "login" | "alert" | "confirm" | "image";
export type ModalType = "login" | "alert" | "confirm" | "image" | "pending";

export interface DefaultModalProps {
isOpen: boolean;
Expand All @@ -24,13 +25,15 @@ interface ModalComponentMap {
alert: React.FC<AlertModalProps>;
confirm: React.FC<ConfirmModalProps>;
image: React.FC<ImageModalProps>;
pending: React.FC<DefaultModalProps>;
}

const MODAL_COMPONENT_MAP: ModalComponentMap = {
login: GoogleLoginModal,
alert: AlertModal,
confirm: ConfirmModal,
image: ImageModal,
pending: PendingModal,
};

export const ModalContainer = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { css } from "@emotion/react";
import { theme } from "@watermelon-clap/core/src/theme";

export const pendingModalStyles = {
content: {
top: "50%",
left: "50%",
right: "auto",
bottom: "auto",
marginRight: "-50%",
transform: "translate(-50%, -50%)",
borderRadius: "8px",
border: "none",
boxShadow: "0 2px 10px rgba(0, 0, 0, 0.2)",
},
overlay: {
backgroundColor: "rgba(0, 0, 0, 0.5)",
zIndex: 1000,
},
};

export const pendingModalBodyStyles = css`
${theme.flex.center}
${theme.flex.column}
gap: 48.5px;
padding: 23px 46px;
text-align: center;
`;

export const pendingImgStyle = css`
width: 60%;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Modal from "react-modal";
import { useScrollStop } from "@watermelon-clap/core/src/hooks";
import { theme } from "@watermelon-clap/core/src/theme";
import {
pendingModalStyles,
pendingModalBodyStyles,
pendingImgStyle,
} from "./PendingModal.css";
import { DefaultModalProps } from "../../ModalContainer";
import { css } from "@emotion/react";

export const PendingModal = ({ isOpen, onRequestClose }: DefaultModalProps) => {
useScrollStop(isOpen);

return (
<Modal
shouldCloseOnEsc={false}
shouldCloseOnOverlayClick={false}
isOpen={isOpen}
onRequestClose={onRequestClose}
style={pendingModalStyles}
ariaHideApp={false}
>
<div css={pendingModalBodyStyles}>
<h2>당첨자 추첨</h2>
<span>
<p>당첨자를 추첨중입니다.</p>
<p>잠시만 기다려주세요.</p>
</span>
<div
css={css`
overflow: hidden;
${theme.flex.center};
height: 40px;
`}
>
<img
css={pendingImgStyle}
src="/images/modal/pending.gif"
alt="로딩중 이미지"
/>
</div>
</div>
</Modal>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { PendingModal } from "./PendingModal";
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from "./AlertModal";
export * from "./ConfirmModal";
export * from "./GoolgleLoginModal";
export * from "./ImageModal";
export * from "./PendingModal";
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { apiGetLotteries } from "@admin/apis/partsEvent/apiGetLotteries";
import { apiPostLotteriesCheckStatus } from "@admin/apis/partsEvent/apiPostLotteriesCheckStatus";
import { IWinner } from "@admin/apis/partsEvent/type";
import { Checkbox } from "@mui/material";
import { DataGrid, GridColDef } from "@mui/x-data-grid";
import { useQuery } from "@tanstack/react-query";
import { ChangeEvent, useEffect, useState } from "react";

function mapDataToGrid(data: IWinner[] | undefined) {
if (!Array.isArray(data)) {
return [];
}

return data.map((item) => ({
rank: item.rank,
reward: item.reward,
id: item.uid,
email: item.email,
name: item.name,
phoneNumber: item.phoneNumber,
address: item.address,
status: item.status,
}));
}

export const MainEventWinnerDataGrid = () => {
const { data: winnerList } = useQuery<IWinner[]>({
queryKey: ["mainEventWinnerList"],
queryFn: () => apiGetLotteries(),
});

const [gridData, setGridData] = useState(mapDataToGrid(winnerList));

const columns: GridColDef[] = [
{ field: "rank", headerName: "순위", width: 60 },
{ field: "reward", headerName: "경품", width: 200 },
{ field: "email", headerName: "수령인 이메일", width: 280 },
{ field: "name", headerName: "수령인 이름", width: 200 },
{ field: "phoneNumber", headerName: "수령인 핸드폰번호", width: 200 },
{ field: "address", headerName: "수령인 주소", width: 200 },
{
field: "status",
headerName: "담당자 확인 정보",
width: 200,
renderCell: (params) => (
<Checkbox
checked={params.value === "READY"}
onChange={(event) => {
handleCheckboxChange(params.row.id, event);
}}
/>
),
},
];

const handleCheckboxChange = (
id: string,
event: ChangeEvent<HTMLInputElement>,
) => {
event.stopPropagation();
apiPostLotteriesCheckStatus(id);

setGridData((prevData) =>
prevData.map((row) =>
row.id === id
? { ...row, status: event.target.checked ? "READY" : "DONE" }
: row,
),
);
};

useEffect(() => {
setGridData(mapDataToGrid(winnerList));
}, [winnerList]);

return (
<div style={{ height: 400, width: "100%" }}>
<DataGrid rows={gridData} columns={columns} />
</div>
);
};
Loading

0 comments on commit 96e722a

Please sign in to comment.