diff --git a/public/svgs/Icon_checkbox_selected_on.svg b/public/svgs/Icon_checkbox_selected_on.svg
new file mode 100644
index 00000000..69f4fe65
--- /dev/null
+++ b/public/svgs/Icon_checkbox_selected_on.svg
@@ -0,0 +1,7 @@
+
diff --git a/public/svgs/Icon_checkbox_unselected_on.svg b/public/svgs/Icon_checkbox_unselected_on.svg
new file mode 100644
index 00000000..0bbb7e17
--- /dev/null
+++ b/public/svgs/Icon_checkbox_unselected_on.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/svgs/icon_chevron_back.svg b/public/svgs/icon_chevron_back.svg
new file mode 100644
index 00000000..0da9be5d
--- /dev/null
+++ b/public/svgs/icon_chevron_back.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/svgs/icon_toggle_off.svg b/public/svgs/icon_toggle_off.svg
new file mode 100644
index 00000000..4a4197b0
--- /dev/null
+++ b/public/svgs/icon_toggle_off.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/svgs/icon_toggle_on.svg b/public/svgs/icon_toggle_on.svg
new file mode 100644
index 00000000..c23c6c7d
--- /dev/null
+++ b/public/svgs/icon_toggle_on.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/assets/svgs/IconCheckboxSelectedOn.tsx b/src/assets/svgs/IconCheckboxSelectedOn.tsx
new file mode 100644
index 00000000..f026c681
--- /dev/null
+++ b/src/assets/svgs/IconCheckboxSelectedOn.tsx
@@ -0,0 +1,17 @@
+import * as React from "react";
+import type { SVGProps } from "react";
+const SvgIconCheckboxSelectedOn = (props: SVGProps) => (
+
+);
+export default SvgIconCheckboxSelectedOn;
diff --git a/src/assets/svgs/IconCheckboxUnselectedOn.tsx b/src/assets/svgs/IconCheckboxUnselectedOn.tsx
new file mode 100644
index 00000000..746b0984
--- /dev/null
+++ b/src/assets/svgs/IconCheckboxUnselectedOn.tsx
@@ -0,0 +1,8 @@
+import * as React from "react";
+import type { SVGProps } from "react";
+const SvgIconCheckboxUnselectedOn = (props: SVGProps) => (
+
+);
+export default SvgIconCheckboxUnselectedOn;
diff --git a/src/assets/svgs/IconChevronBack.tsx b/src/assets/svgs/IconChevronBack.tsx
new file mode 100644
index 00000000..adc09dff
--- /dev/null
+++ b/src/assets/svgs/IconChevronBack.tsx
@@ -0,0 +1,13 @@
+import * as React from "react";
+import type { SVGProps } from "react";
+const SvgIconChevronBack = (props: SVGProps) => (
+
+);
+export default SvgIconChevronBack;
diff --git a/src/assets/svgs/IconToggleOff.tsx b/src/assets/svgs/IconToggleOff.tsx
new file mode 100644
index 00000000..d584f216
--- /dev/null
+++ b/src/assets/svgs/IconToggleOff.tsx
@@ -0,0 +1,9 @@
+import * as React from "react";
+import type { SVGProps } from "react";
+const SvgIconToggleOff = (props: SVGProps) => (
+
+);
+export default SvgIconToggleOff;
diff --git a/src/assets/svgs/IconToggleOn.tsx b/src/assets/svgs/IconToggleOn.tsx
new file mode 100644
index 00000000..1f6d2ca4
--- /dev/null
+++ b/src/assets/svgs/IconToggleOn.tsx
@@ -0,0 +1,9 @@
+import * as React from "react";
+import type { SVGProps } from "react";
+const SvgIconToggleOn = (props: SVGProps) => (
+
+);
+export default SvgIconToggleOn;
diff --git a/src/assets/svgs/index.tsx b/src/assets/svgs/index.tsx
index a43d9502..fe2f3d96 100644
--- a/src/assets/svgs/index.tsx
+++ b/src/assets/svgs/index.tsx
@@ -8,6 +8,9 @@ export { default as IconArrowUp } from "./IconArrowUp";
export { default as IconBnk } from "./IconBnk";
export { default as IconCalendar } from "./IconCalendar";
export { default as IconCheck } from "./IconCheck";
+export { default as IconCheckboxSelectedOn } from "./IconCheckboxSelectedOn";
+export { default as IconCheckboxUnselectedOn } from "./IconCheckboxUnselectedOn";
+export { default as IconChevronBack } from "./IconChevronBack";
export { default as IconEyeOff } from "./IconEyeOff";
export { default as IconEyeOn } from "./IconEyeOn";
export { default as IconHanna } from "./IconHanna";
@@ -26,6 +29,8 @@ export { default as IconShinhyup } from "./IconShinhyup";
export { default as IconSoohyup } from "./IconSoohyup";
export { default as IconTextfiedlDelete } from "./IconTextfiedlDelete";
export { default as IconTime } from "./IconTime";
+export { default as IconToggleOff } from "./IconToggleOff";
+export { default as IconToggleOn } from "./IconToggleOn";
export { default as IconToss } from "./IconToss";
export { default as IconWoochaegook } from "./IconWoochaegook";
export { default as IconWoori } from "./IconWoori";
diff --git a/src/pages/MyRegisterdShow/MyRegisterdShow.tsx b/src/pages/MyRegisterdShow/MyRegisterdShow.tsx
index 14da99c4..99dc86a7 100644
--- a/src/pages/MyRegisterdShow/MyRegisterdShow.tsx
+++ b/src/pages/MyRegisterdShow/MyRegisterdShow.tsx
@@ -5,7 +5,7 @@ import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import bannerNarrow from "../../assets/images/banner_narrow.png";
import * as S from "./MyRegisterdShow.styled";
-import RegisteredCard from "./components/RegisteredCard";
+import RegisteredCard from "./components/registeredcard/RegisteredCard";
import { MY_REGISTERED_SHOW, RegisteredObjProps } from "./constants/myRegisterShow";
const MyRegisterdShow = () => {
diff --git a/src/pages/MyRegisterdShow/components/RegisteredCard.styled.ts b/src/pages/MyRegisterdShow/components/registeredcard/RegisteredCard.styled.ts
similarity index 100%
rename from src/pages/MyRegisterdShow/components/RegisteredCard.styled.ts
rename to src/pages/MyRegisterdShow/components/registeredcard/RegisteredCard.styled.ts
diff --git a/src/pages/MyRegisterdShow/components/RegisteredCard.tsx b/src/pages/MyRegisterdShow/components/registeredcard/RegisteredCard.tsx
similarity index 93%
rename from src/pages/MyRegisterdShow/components/RegisteredCard.tsx
rename to src/pages/MyRegisterdShow/components/registeredcard/RegisteredCard.tsx
index ac4655bc..a685957d 100644
--- a/src/pages/MyRegisterdShow/components/RegisteredCard.tsx
+++ b/src/pages/MyRegisterdShow/components/registeredcard/RegisteredCard.tsx
@@ -1,5 +1,5 @@
import Button from "@components/commons/button/Button";
-import { RegisteredObjProps } from "../constants/myRegisterShow";
+import { RegisteredObjProps } from "../../constants/myRegisterShow";
import * as S from "./RegisteredCard.styled";
const RegisteredCard = ({ title, period, genre, image }: Omit) => {
diff --git a/src/pages/MyRegisterdShow/hooks/.gitkeep b/src/pages/MyRegisterdShow/hooks/.gitkeep
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/pages/MyRegisterdShow/types/.gitkeep b/src/pages/MyRegisterdShow/types/.gitkeep
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/pages/MyRegisterdShow/utils/.gitkeep b/src/pages/MyRegisterdShow/utils/.gitkeep
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/pages/ticketholderlist/TicketHolderList.styled.ts b/src/pages/ticketholderlist/TicketHolderList.styled.ts
new file mode 100644
index 00000000..e23be586
--- /dev/null
+++ b/src/pages/ticketholderlist/TicketHolderList.styled.ts
@@ -0,0 +1,71 @@
+import { IconToggleOff, IconToggleOn } from "@assets/svgs";
+import styled from "styled-components";
+
+export const BodyWrapper = styled.div`
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ width: 37.4rem;
+ height: auto;
+ min-height: 60.8rem; /* 60.8rem(body의 높이) + 5.6rem(버튼의 높이) */
+ margin-bottom: 10.4rem;
+ padding: 2.4rem;
+`;
+
+export const BodyLayout = styled.section`
+ display: flex;
+ flex: 1 0 0;
+ flex-direction: column;
+ gap: 2.4rem;
+ align-items: flex-start;
+`;
+
+export const LayoutHeaderBox = styled.div`
+ display: flex;
+ gap: 3.2rem;
+ align-items: flex-end;
+ align-self: stretch;
+`;
+
+export const LayoutFilterBox = styled.div`
+ display: flex;
+ gap: 0.6rem;
+ align-items: center;
+`;
+
+export const ToggleWrapper = styled.div`
+ display: flex;
+ gap: 0.4rem;
+ align-items: center;
+`;
+
+export const ToggleText = styled.span`
+ color: ${({ theme }) => theme.colors.gray_400};
+ ${({ theme }) => theme.fonts["body2-normal-medi"]};
+`;
+
+export const ToggleOnIcon = styled(IconToggleOn)<{ $width: string; $height: string }>`
+ width: ${({ $width }) => $width};
+ height: ${({ $height }) => $height};
+`;
+
+export const ToggleOffIcon = styled(IconToggleOff)<{ $width: string; $height: string }>`
+ width: ${({ $width }) => $width};
+ height: ${({ $height }) => $height};
+`;
+
+export const FooterButtonWrapper = styled.div`
+ position: fixed;
+ bottom: 0;
+ left: 50%;
+ z-index: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 37.4rem;
+ height: 10.4rem;
+ padding: 2.4rem;
+
+ background-color: ${({ theme }) => theme.colors.gray_900};
+ transform: translate(-50%, 0);
+`;
diff --git a/src/pages/ticketholderlist/TicketHolderList.tsx b/src/pages/ticketholderlist/TicketHolderList.tsx
new file mode 100644
index 00000000..c72fbac4
--- /dev/null
+++ b/src/pages/ticketholderlist/TicketHolderList.tsx
@@ -0,0 +1,132 @@
+import Button from "@components/commons/button/Button";
+import { useEffect, useState } from "react";
+import Banner from "./components/banner/Banner";
+import ManagerCard from "./components/managercard/ManagerCard";
+import NarrowDropDown from "./components/narrowDropDown/NarrowDropDown";
+import eximg from "./constants/silkagel.png";
+import { BookingListProps, RESPONSE_TICKETHOLDER } from "./constants/ticketholderlist";
+import * as S from "./TicketHolderList.styled";
+
+const TicketHolderList = () => {
+ const [reservedCount, setReservedCount] = useState(0);
+ //이거 판매 완료되었는지 여부에 따라서 렌더링하는거 다르게 할지 물어보기, 색깔도 어떻게 할 지 물어보기
+ const [isOutdated, setIsOutdated] = useState(false);
+ const [detail, setDetail] = useState(false);
+
+ // 0, undefined 일 때는 전체 렌더링 (필터링을 위한 state들)
+ const [schedule, setSchedule] = useState(0); //1,2,3 에 따라 필터링
+ const [payment, setPayment] = useState(undefined);
+ const [responseData, setResponseData] = useState(
+ RESPONSE_TICKETHOLDER.data.bookingList
+ );
+
+ const handleToggleButton = () => {
+ setDetail((prop) => !prop);
+ };
+
+ const count = RESPONSE_TICKETHOLDER.data.totalScheduleCount; //나중에 api로 받아와서 반영해야함. state로 바꿀 필요 있을까?
+
+ const filteredData = responseData.filter((obj) => {
+ const isScheduleMatched =
+ schedule === 0 ||
+ (obj.scheduleNumber === "FIRST" && schedule === 1) ||
+ (obj.scheduleNumber === "SECOND" && schedule === 2) ||
+ (obj.scheduleNumber === "THIRD" && schedule === 3);
+ const isPaymentMatched = payment === undefined || obj.isPaymentCompleted === payment;
+
+ return isScheduleMatched && isPaymentMatched;
+ });
+ //도영이가 axios 사용하면 useEffect 필요없다고 했는데, 나중에 리팩토링 할 수도 있음.
+ useEffect(() => {
+ const totalCount = filteredData.reduce(
+ (totalSum, obj) => obj.purchaseTicketCount + totalSum,
+ 0
+ );
+ setReservedCount(totalCount);
+ //그리고 여기서 바로 다시 axios 요청 쏘는 로직 구성해두기
+ }, [filteredData]);
+ //이해하기 어려울 것 같아 주석 남깁니다. 모든 회차, 입금 상태 2가지 필터를 사용하여 원하는 결과만 가져오는 형식입니다.
+ //schedule ===0 일 경우는 전체 회차, payment === undefined 일 경우는 전체 입금 여부(입금했든 안했든 렌더링)을 의미합니다.
+
+ //상위 컴포넌트에서 받아온 set함수와 bookingId를 이용하여 현재 오브젝트(state)의 payment 상태를 바꾸도록 한다.
+ const handlePaymentToggle = (bookingId: number) => {
+ setResponseData((arr) =>
+ arr.map((item) =>
+ item.bookingId === bookingId
+ ? { ...item, isPaymentCompleted: !item.isPaymentCompleted }
+ : item
+ )
+ );
+ };
+ return (
+ <>
+
+
+
+
+
+ {/*set 함수 직접 넘기는 거 안좋다고 했지만, 내부에서 감싸야 하므로 넘김 */}
+
+ 모든 회차
+
+
+ 입금 상태
+
+
+
+ {detail ? (
+ <>
+ 자세히
+
+ >
+ ) : (
+ <>
+ 간략히
+
+ >
+ )}
+
+
+ {filteredData.map((obj, index) => (
+ handlePaymentToggle(obj.bookingId)}
+ bookername={obj.bookerName}
+ purchaseTicketeCount={obj.purchaseTicketCount}
+ scheduleNumber={obj.scheduleNumber}
+ bookerPhoneNumber={obj.bookerPhoneNumber}
+ createAt={obj.createdAt}
+ />
+ ))}
+
+
+
+
+
+
+ >
+ );
+};
+
+export default TicketHolderList;
diff --git a/src/pages/ticketholderlist/components/banner/Banner.styled.ts b/src/pages/ticketholderlist/components/banner/Banner.styled.ts
new file mode 100644
index 00000000..bb682d5e
--- /dev/null
+++ b/src/pages/ticketholderlist/components/banner/Banner.styled.ts
@@ -0,0 +1,45 @@
+import styled from "styled-components";
+
+export const BannerWrapper = styled.div<{ $image: string }>`
+ width: 37.5rem;
+ height: 7.2rem;
+
+ /* 사용자가 입력한 이미지 background 적당히 잘라서 사용하도록 */
+ background-image: url(${({ $image }) => $image});
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: cover;
+`;
+
+export const BannerTextLayout = styled.div`
+ display: flex;
+ flex-shrink: 0;
+ align-items: center;
+ justify-content: center;
+ width: 37.5rem;
+ height: 7.2rem;
+
+ background: rgb(0 0 0 / 60%);
+`;
+
+export const BannerTextBox = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 32.7rem;
+`;
+
+export const BannerTitleText = styled.span`
+ color: ${({ theme }) => theme.colors.white};
+ ${({ theme }) => theme.fonts.heading4};
+`;
+
+export const BannerStateTextBox = styled.span`
+ color: ${({ theme }) => theme.colors.white};
+ ${({ theme }) => theme.fonts["body1-normal-semi"]};
+`;
+
+export const CountTextSpan = styled.span`
+ color: ${({ theme }) => theme.colors.pink_400};
+ ${({ theme }) => theme.fonts["body1-normal-semi"]};
+`;
diff --git a/src/pages/ticketholderlist/components/banner/Banner.tsx b/src/pages/ticketholderlist/components/banner/Banner.tsx
new file mode 100644
index 00000000..0189d61d
--- /dev/null
+++ b/src/pages/ticketholderlist/components/banner/Banner.tsx
@@ -0,0 +1,34 @@
+import * as S from "./Banner.styled";
+
+interface BannerProps {
+ image: string;
+ reservedCount: number;
+ isOutdated: boolean;
+}
+
+const Banner = ({ image, reservedCount, isOutdated }: BannerProps) => {
+ return (
+
+
+
+
+ 실리카겔 락앤롤
+ {isOutdated ? (
+
+ 총 {reservedCount}매
+ 판매 됨
+
+ ) : (
+
+ 총 {reservedCount}매
+ 예매됨
+
+ )}
+
+
+
+
+ );
+};
+
+export default Banner;
diff --git a/src/pages/ticketholderlist/components/managercard/ManagerCard.styled.ts b/src/pages/ticketholderlist/components/managercard/ManagerCard.styled.ts
new file mode 100644
index 00000000..d6fa26ec
--- /dev/null
+++ b/src/pages/ticketholderlist/components/managercard/ManagerCard.styled.ts
@@ -0,0 +1,97 @@
+import { IconCheckboxSelectedOn, IconCheckboxUnselectedOn } from "@assets/svgs";
+import styled from "styled-components";
+
+export const ManagerCardWrapper = styled.article<{ $isDetail: boolean }>`
+ display: flex;
+ flex-shrink: 0;
+ align-items: flex-start;
+ justify-content: center;
+ width: 32.6rem;
+ height: ${({ $isDetail }) => ($isDetail ? "14.6rem" : "7.4rem")};
+`;
+
+export const ManagerCardLayout = styled.div<{ $isDetail: boolean }>`
+ display: flex;
+ flex-direction: column;
+ flex-shrink: 0;
+ gap: 1.6rem;
+ align-items: flex-start;
+ width: 25.2rem;
+ height: ${({ $isDetail }) => ($isDetail ? "14.6rem" : "7.4rem")};
+ padding: 1.6rem;
+
+ background-color: ${({ theme }) => theme.colors.gray_800};
+ border-right: 1px solid ${({ theme }) => theme.colors.black};
+ border-radius: 6px;
+`;
+
+export const ManagerCardBox = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 0.4rem;
+ align-items: flex-start;
+ align-self: stretch;
+`;
+
+export const ManagerCardTextBox = styled.div`
+ display: flex;
+ gap: 2.2rem;
+ align-items: center;
+ justify-content: space-between;
+`;
+
+export const ManagerCardTextTitle = styled.span`
+ width: 3.7rem;
+
+ color: ${({ theme }) => theme.colors.gray_400};
+ ${({ theme }) => theme.fonts["body2-normal-medi"]};
+`;
+
+export const ManagerCardTextContent = styled.span`
+ width: 17.3rem;
+
+ color: ${({ theme }) => theme.colors.white};
+ ${({ theme }) => theme.fonts["body2-normal-medi"]};
+`;
+
+export const ManagerCardRadioLayout = styled.div<{ $isDetail: boolean; $isPaid: boolean }>`
+ display: flex;
+ flex-shrink: 0;
+ align-items: center;
+ justify-content: center;
+ width: 7.4rem;
+ height: ${({ $isDetail }) => ($isDetail ? "14.6rem" : "7.4rem")};
+ padding: 1.5rem 1.5rem 1.5rem 1.6rem;
+
+ background-color: ${({ theme, $isPaid }) =>
+ $isPaid ? theme.colors.pink_600 : theme.colors.gray_800};
+ border-radius: 6px;
+`;
+
+export const ManagerCardRadioBox = styled.div`
+ display: flex;
+ flex-direction: column;
+ flex-shrink: 0;
+ gap: 1rem;
+ align-items: center;
+ width: 4.3rem;
+
+ cursor: pointer;
+`;
+
+export const SelectedIcon = styled(IconCheckboxSelectedOn)`
+ width: 1.8rem;
+ height: 1.8rem;
+`;
+
+export const UnselectedIcon = styled(IconCheckboxUnselectedOn)`
+ width: 1.8rem;
+ height: 1.8rem;
+`;
+
+export const ManagerCardRadioText = styled.span`
+ color: ${({ theme }) => theme.colors.white};
+ text-align: center;
+
+ ${({ theme }) => theme.fonts["caption1-semi"]};
+`;
diff --git a/src/pages/ticketholderlist/components/managercard/ManagerCard.tsx b/src/pages/ticketholderlist/components/managercard/ManagerCard.tsx
new file mode 100644
index 00000000..2202aa82
--- /dev/null
+++ b/src/pages/ticketholderlist/components/managercard/ManagerCard.tsx
@@ -0,0 +1,82 @@
+import * as S from "./ManagerCard.styled";
+
+const ManagerCard = ({
+ bookingId,
+ isPaid,
+ isDetail,
+ setPaid,
+ bookername,
+ purchaseTicketeCount,
+ scheduleNumber,
+ bookerPhoneNumber,
+ createAt,
+}: {
+ bookingId: number;
+ isPaid: boolean;
+ isDetail: boolean;
+ setPaid: () => void;
+ bookername: string;
+ purchaseTicketeCount: number;
+ scheduleNumber: string;
+ bookerPhoneNumber: string;
+ createAt: string;
+}) => {
+ const date = createAt.split("T")[0];
+ const formattedDate = date.replace(/-/g, ".");
+ const convertingNumber = (scheduleNumberrr: string) => {
+ switch (scheduleNumberrr) {
+ case "FIRST":
+ return 1;
+ break;
+ case "SECOND":
+ return 2;
+ break;
+ case "THIRD":
+ return 3;
+ break;
+ default:
+ console.log("error");
+ }
+ };
+
+ return (
+
+
+
+
+ 이름
+ {bookername}
+
+
+ 매수
+ {`${purchaseTicketeCount}매`}
+
+ {isDetail && (
+ <>
+
+ 회차
+ {`${convertingNumber(scheduleNumber)}회차`}
+
+
+ 연락처
+ {bookerPhoneNumber}
+
+
+ 예매일
+ {formattedDate}
+
+ >
+ )}
+
+
+
+
+ {isPaid ? : }
+ {isPaid ? "입금 완료" : "미입금"}
+
+
+
+ );
+};
+
+export default ManagerCard;
diff --git a/src/pages/ticketholderlist/components/narrowDropDown/NarrowDropDown.styled.ts b/src/pages/ticketholderlist/components/narrowDropDown/NarrowDropDown.styled.ts
new file mode 100644
index 00000000..dcd2970a
--- /dev/null
+++ b/src/pages/ticketholderlist/components/narrowDropDown/NarrowDropDown.styled.ts
@@ -0,0 +1,107 @@
+import SvgIconChevronBack from "@assets/svgs/IconChevronBack";
+import styled, { css } from "styled-components";
+
+export const DropdownWrapper = styled.div`
+ position: relative;
+`;
+
+export const DropdownButton = styled.button<{ $isChoosed: boolean }>`
+ display: inline-flex;
+ flex-shrink: 0;
+ gap: 0.6rem;
+ align-items: center;
+ min-width: 9.3rem;
+ height: 4rem;
+ padding: 0.8rem 0.8rem 0.8rem 1.2rem;
+
+ border: 1px solid
+ ${({ theme, $isChoosed }) => ($isChoosed ? theme.colors.gray_0 : theme.colors.gray_700)};
+ border-radius: 0.4rem;
+`;
+
+export const DropDownButtonContent = styled.div`
+ display: flex;
+ gap: 0.4rem;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+`;
+
+export const ButtonContentSpan = styled.span`
+ color: ${({ theme }) => theme.colors.gray_0};
+ ${({ theme }) => theme.fonts["body2-normal-medi"]};
+`;
+
+export const SvgIcon = styled(SvgIconChevronBack)<{ $rotate: boolean }>`
+ width: 16px;
+ height: 16px;
+
+ transform: rotate(180deg);
+
+ ${(prop) =>
+ prop.$rotate &&
+ css`
+ transform: rotate(360deg);
+ `}
+`;
+
+export const DropdownContentWrapper = styled.div<{ $show: boolean }>`
+ position: absolute;
+ top: 4.06rem; /* 버튼의 높이 3.26rem + 간격 0.8rem */
+ z-index: 1;
+ display: ${(props) => (props.$show ? "flex" : "none")};
+ flex-shrink: 0;
+ gap: 0.6rem;
+ align-items: center;
+ width: 9.2rem;
+ margin-top: 0.8rem;
+ padding: 0.8rem 0.8rem 0.8rem 1.2rem;
+
+ background-color: ${({ theme }) => theme.colors.black};
+ border: 1px solid ${({ theme }) => theme.colors.gray_400};
+ border-radius: 0.4rem;
+`;
+
+export const DropdownContentLayout = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 0.6rem;
+ align-items: flex-start;
+ justify-content: center;
+`;
+
+export const DropdownContentButton = styled.button<{ $isLast: boolean }>`
+ display: flex;
+ flex-direction: column;
+ gap: 0.8rem;
+ align-items: flex-start;
+ width: 6.8rem;
+
+ /* 피그마에서는 겹치는 부분이 제대로 계산 안되서, 개발하면서 적당히 줄여둠 */
+ padding: 0.2rem 0 0.6rem;
+
+ ${({ $isLast }) =>
+ !$isLast &&
+ css`
+ border-bottom: 1px solid ${({ theme }) => theme.colors.gray_700};
+ `}
+`;
+
+export const DropdownContentText = styled.span<{
+ $selected: boolean;
+}>`
+ display: flex;
+ gap: 0.4rem;
+ align-items: center;
+ align-self: stretch;
+ justify-content: flex-start;
+
+ color: ${({ theme }) => theme.colors.gray_0};
+ ${({ theme }) => theme.fonts["body2-normal-medi"]};
+
+ ${({ $selected }) =>
+ $selected &&
+ css`
+ color: ${({ theme }) => theme.colors.pink_400};
+ `}
+`;
diff --git a/src/pages/ticketholderlist/components/narrowDropDown/NarrowDropDown.tsx b/src/pages/ticketholderlist/components/narrowDropDown/NarrowDropDown.tsx
new file mode 100644
index 00000000..6c3aedf5
--- /dev/null
+++ b/src/pages/ticketholderlist/components/narrowDropDown/NarrowDropDown.tsx
@@ -0,0 +1,138 @@
+import { ReactNode, useState } from "react";
+import * as S from "./NarrowDropDown.styled";
+
+interface DropdownProps {
+ children: ReactNode;
+ totalScheduleCount: number;
+ schedule: number;
+ payment: boolean | undefined;
+ setSchedule?: (param: number) => void;
+ setPayment?: (param: boolean | undefined) => void;
+}
+
+const NarrowDropDown = ({
+ children,
+ totalScheduleCount,
+ schedule,
+ payment,
+ setSchedule,
+ setPayment,
+}: DropdownProps) => {
+ const [showDropdown, setShowDropdown] = useState(false);
+ const [isOneChoosed, setIsOneChoosed] = useState(false);
+
+ const handleToggle = () => {
+ setShowDropdown(!showDropdown);
+ };
+
+ const renderDropdownContent = (count: number, childrenNode: ReactNode) => {
+ const items = [];
+ const childrenString = childrenNode?.toString();
+ if (childrenString === "모든 회차") {
+ const handleScheduleAll = () => {
+ setSchedule?.(0);
+ setIsOneChoosed(false);
+ setShowDropdown(false);
+ };
+ items.push(
+
+ 전체
+
+ );
+ for (let i = 1; i <= count; i++) {
+ const handleSchedule = () => {
+ setSchedule?.(i);
+ setIsOneChoosed(true);
+ setShowDropdown(false);
+ };
+
+ items.push(
+
+ {i}회차
+
+ );
+ }
+ } else if (childrenString === "입금 상태") {
+ const handlePaymentUndefined = () => {
+ setPayment?.(undefined);
+ setIsOneChoosed(false);
+ setShowDropdown(false);
+ };
+
+ const handlePaymentFalse = () => {
+ setPayment?.(false);
+ setIsOneChoosed(true);
+ setShowDropdown(false);
+ };
+
+ const handlePaymentTrue = () => {
+ setPayment?.(true);
+ setIsOneChoosed(true);
+ setShowDropdown(false);
+ };
+
+ items.push(
+
+ 전체
+
+ );
+
+ items.push(
+
+ 미입금
+
+ );
+ items.push(
+
+ 입금완료
+
+ );
+ }
+
+ return items;
+ };
+
+ //공연별로 총 회차 값으로 넘겨주기로 함.
+ //각 공연별 회차는, DB 테이블 구조에 따라 enum값으로 넘겨주기로 함
+ //필터링은 회차 번호(scheduleNumber : FIRST, SECOND, THIRD), 입금 완료 여부는 paymentStatus에 따라 나뉠거임
+ //
+
+ let changedChildren;
+ if (children === "입금 상태" && payment !== undefined) {
+ changedChildren = payment ? "입금완료" : "미입금";
+ } else if (children === "모든 회차" && schedule !== 0) {
+ changedChildren = `${schedule}차`;
+ }
+
+ return (
+
+
+
+
+ {changedChildren === undefined ? children : changedChildren}
+
+
+
+
+
+
+ {renderDropdownContent(totalScheduleCount, children)}
+
+
+
+ );
+};
+
+export default NarrowDropDown;
diff --git a/src/pages/ticketholderlist/constants/silkagel.png b/src/pages/ticketholderlist/constants/silkagel.png
new file mode 100644
index 00000000..a5295d7b
Binary files /dev/null and b/src/pages/ticketholderlist/constants/silkagel.png differ
diff --git a/src/pages/ticketholderlist/constants/ticketholderlist.ts b/src/pages/ticketholderlist/constants/ticketholderlist.ts
new file mode 100644
index 00000000..9f17fc35
--- /dev/null
+++ b/src/pages/ticketholderlist/constants/ticketholderlist.ts
@@ -0,0 +1,101 @@
+export interface BookingListProps {
+ bookingId: number;
+ bookerName: string;
+ bookerPhoneNumber: string;
+ scheduleId: number;
+ purchaseTicketCount: number;
+ createdAt: string;
+ isPaymentCompleted: boolean;
+ scheduleNumber: string;
+}
+
+export interface TicketHolderListProps {
+ performanceTitle: string;
+ isBooking: boolean;
+ totalScheduleCount: number;
+ bookingList: BookingListProps[];
+}
+
+export interface TicketHolderListDataProps {
+ data: TicketHolderListProps;
+}
+
+export const RESPONSE_TICKETHOLDER = {
+ data: {
+ performanceTitle: "비트밴드 정기공연",
+ isBooking: true,
+ totalScheduleCount: 3,
+ bookingList: [
+ {
+ bookingId: 1,
+ bookerName: "황혜린",
+ bookerPhoneNumber: "010-1234-5678",
+ scheduleId: 2, //scheduleId는 테이블의 특성에 따라 존재하는 것
+ purchaseTicketCount: 3,
+ createdAt: "2024-07-07T12:34:56.789Z",
+ isPaymentCompleted: true,
+ scheduleNumber: "SECOND",
+ },
+ {
+ bookingId: 2,
+ bookerName: "이동훈",
+ bookerPhoneNumber: "010-1234-0000",
+ scheduleId: 1,
+ purchaseTicketCount: 2,
+ createdAt: "2024-07-08T12:34:56.789Z",
+ isPaymentCompleted: false,
+ scheduleNumber: "FIRST",
+ },
+ {
+ bookingId: 3,
+ bookerName: "공준혁",
+ bookerPhoneNumber: "010-1234-9999",
+ scheduleId: 1,
+ purchaseTicketCount: 9,
+ createdAt: "2024-07-08T12:34:56.789Z",
+ isPaymentCompleted: true,
+ scheduleNumber: "THIRD",
+ },
+ {
+ bookingId: 4,
+ bookerName: "정도영",
+ bookerPhoneNumber: "010-1234-0000",
+ scheduleId: 3,
+ purchaseTicketCount: 4,
+ createdAt: "2024-07-11T12:34:56.789Z",
+ isPaymentCompleted: false,
+ scheduleNumber: "THIRD",
+ },
+ {
+ bookingId: 5,
+ bookerName: "김채현",
+ bookerPhoneNumber: "010-1234-0000",
+ scheduleId: 3,
+ purchaseTicketCount: 6,
+ createdAt: "2024-07-15T12:34:56.789Z",
+ isPaymentCompleted: false,
+ scheduleNumber: "THIRD",
+ },
+ {
+ bookingId: 6,
+ bookerName: "윤신지",
+ bookerPhoneNumber: "010-1234-0000",
+ scheduleId: 3,
+ purchaseTicketCount: 2,
+ createdAt: "2024-07-03T12:34:56.789Z",
+ isPaymentCompleted: true,
+ scheduleNumber: "THIRD",
+ },
+ {
+ bookingId: 7,
+ bookerName: "익명인",
+ bookerPhoneNumber: "010-1000-0000",
+ scheduleId: 1,
+ purchaseTicketCount: 2,
+ createdAt: "2024-07-08T12:34:56.789Z",
+ isPaymentCompleted: true,
+ scheduleNumber: "SECOND",
+ },
+ ],
+ },
+};
diff --git a/src/routes/Router.tsx b/src/routes/Router.tsx
index 40d9bf05..ebe30f46 100644
--- a/src/routes/Router.tsx
+++ b/src/routes/Router.tsx
@@ -10,6 +10,7 @@ import MyRegisterdShow from "@pages/MyRegisterdShow/MyRegisterdShow";
import NonMbLookup from "@pages/nonMbLookup/NonMbLookup";
import Register from "@pages/register/Register";
import TestPage from "@pages/test/TestPage";
+import TicketHolderList from "@pages/ticketholderlist/TicketHolderList";
import KakaoLoginTest from "@pages/KakaoLoginTest";
import KakaoLogin from "@pages/kakaoLogin/KakaoLogin";
import ViewBottomSheetTest from "@pages/ViewBottomSheetTest";
@@ -26,6 +27,7 @@ const router = createBrowserRouter([
{ path: "register", element: },
{ path: "gig/:performanceId", element: },
{ path: "manage", element: },
+ { path: "ticketholderlist", element: },
{ path: "myregisteredshow", element: },
// ... other pages
],
@@ -63,7 +65,7 @@ const router = createBrowserRouter([
// element: ,
// },
{ path: "/testpage", element: },
- { path: "/myregisteredshow", element: },
+
// ...
]);
export default router;