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: 예매자 관리 페이지 작업 2 #57

Merged
merged 15 commits into from
Feb 16, 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
37 changes: 37 additions & 0 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file modified .yarn/install-state.gz
Binary file not shown.
1 change: 1 addition & 0 deletions apps/admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@boolti/ui": "*",
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@tanstack/react-table": "^8.12.0",
"date-fns": "^3.3.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
30 changes: 30 additions & 0 deletions apps/admin/src/components/Pagination/Pagination.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import styled from '@emotion/styled';

const Container = styled.div`
display: flex;
`;

const Button = styled.button<{ isCurrent?: boolean }>`
cursor: ${({ isCurrent }) => (isCurrent ? 'unset' : 'pointer')};
display: flex;
justify-content: center;
align-items: center;
width: 24px;
height: 24px;
border-radius: 4px;
${({ theme }) => theme.typo.c1};
background-color: ${({ isCurrent, theme }) => (isCurrent ? theme.palette.grey.g90 : 'none')};
color: ${({ isCurrent, theme }) => (isCurrent ? theme.palette.grey.w : theme.palette.grey.g90)};
&:not(:last-of-type) {
margin-right: 8px;
}
&:disabled {
cursor: unset;
color: ${({ theme }) => theme.palette.grey.g20};
}
`;

export default {
Container,
Button,
};
46 changes: 46 additions & 0 deletions apps/admin/src/components/Pagination/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { ChevronLeftIcon, ChevronRightIcon } from '@boolti/icon';

import Styled from './Pagination.styles';

interface Props {
totalPages: number;
currentPage: number;
onClickPage?: (page: number) => void;
}

const Pagination = ({ totalPages, currentPage, onClickPage }: Props) => {
const pages = Array.from({ length: totalPages }, (_, i) => i);
return (
<Styled.Container>
<Styled.Button
disabled={currentPage === 0}
onClick={() => {
onClickPage?.(0);
}}
>
<ChevronLeftIcon />
</Styled.Button>
{pages.map((page) => (
<Styled.Button
key={page}
isCurrent={page === currentPage}
onClick={() => {
onClickPage?.(page);
}}
>
{page + 1}
</Styled.Button>
))}
<Styled.Button
disabled={currentPage === totalPages - 1}
onClick={() => {
onClickPage?.(totalPages - 1);
}}
>
<ChevronRightIcon />
</Styled.Button>
</Styled.Container>
);
};

export default Pagination;
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { Button } from '@boolti/ui';
import styled from '@emotion/styled';

const Container = styled.div`
display: flex;
flex-direction: column;
width: 100%;
overflow: hidden;
margin: 16px 0;
height: 547px;
`;

const Header = styled.div`
border-top: 1px solid ${({ theme }) => theme.palette.grey.g20};
border-bottom: 1px solid ${({ theme }) => theme.palette.grey.g20};
background-color: ${({ theme }) => theme.palette.grey.g00};
`;

const HeaderRow = styled.div`
display: flex;
flex-wrap: nowrap;
width: 100%;
padding-left: 8px;
`;

const HeaderItem = styled.span`
display: block;
flex: 0 0 auto;
text-align: center;
padding: 12px;
${({ theme }) => theme.typo.b2};
color: ${({ theme }) => theme.palette.grey.g60};
&:not(:last-of-type) {
margin-right: 12px;
}
&:nth-of-type(1) {
width: 88px;
}
&:nth-of-type(2) {
width: 80px;
}
&:nth-of-type(3) {
width: 180px;
}
&:nth-of-type(4) {
width: 100px;
}
&:nth-of-type(5) {
width: 140px;
}
&:nth-of-type(6) {
width: 96px;
}
&:nth-of-type(7) {
text-align: right;
width: 92px;
}
&:nth-of-type(8) {
width: 92px;
}
`;

const Body = styled.div`
width: 100%;
`;

const Row = styled.div`
display: flex;
flex-wrap: nowrap;
width: 100%;
padding-left: 8px;
border-bottom: 1px solid ${({ theme }) => theme.palette.grey.g20};
`;

const Item = styled.span`
display: block;
overflow: hidden;
flex: 0 0 auto;
padding: 14px 12px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
text-align: center;
${({ theme }) => theme.typo.b2};
color: ${({ theme }) => theme.palette.grey.g90};
&:not(:last-of-type) {
margin-right: 12px;
}
&:nth-of-type(1) {
width: 88px;
}
&:nth-of-type(2) {
width: 80px;
}
&:nth-of-type(3) {
width: 180px;
}
&:nth-of-type(4) {
width: 100px;
}
&:nth-of-type(5) {
width: 140px;
}
&:nth-of-type(6) {
width: 96px;
}
&:nth-of-type(7) {
text-align: right;
width: 92px;
}
&:nth-of-type(8) {
width: 92px;
}
&:nth-of-type(9) {
width: 100px;
}
`;

const Empty = styled.div`
display: flex;
flex-direction: column;
flex: 1;
justify-content: center;
align-items: center;
white-space: pre-wrap;
text-align: center;
${({ theme }) => theme.typo.b4};
color: ${({ theme }) => theme.palette.grey.g40};
`;

const ResetButton = styled(Button)`
display: block;
margin-top: 18px;
`;

export default {
Container,
Header,
HeaderItem,
HeaderRow,
Body,
Row,
Item,
Empty,
ResetButton,
};
107 changes: 107 additions & 0 deletions apps/admin/src/components/ReservationTable/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { ReservationResponse, TicketStatus } from '@boolti/api';
import {
createColumnHelper,
flexRender,
getCoreRowModel,
useReactTable,
} from '@tanstack/react-table';

import { formatPhoneNumber } from '~/utils/format';

import Styled from './ReservationTable.styles';

const columnHelper = createColumnHelper<ReservationResponse>();

const columns = [
columnHelper.accessor('ticketId', {
header: '티켓 번호',
}),
columnHelper.accessor('ticketType', {
header: '티켓 타입',
cell: (props) => `${props.getValue() === 'INVITE' ? '초청' : '일반'}티켓`,
}),
columnHelper.accessor('ticketName', {
header: '티켓 이름',
}),
columnHelper.accessor('reservationName', {
header: '예매자 이름',
}),
columnHelper.accessor('reservationPhoneNumber', {
header: '연락처',
cell: (props) => formatPhoneNumber(props.getValue()),
}),
columnHelper.accessor('reservationId', {
header: '주문 번호',
}),
columnHelper.accessor('ticketPrice', {
header: '결제 금액',
cell: (props) => `${props.getValue().toLocaleString()}원`,
}),
columnHelper.accessor('means', {
header: '결제 방법',
cell: (props) => (props.getValue() === 'CARD' ? '카드 결제' : '초청 코드'),
}),
columnHelper.accessor('ticketIssuedAt', {
header: '발권일시',
}),
];

const emptyLabel: Record<TicketStatus, string> = {
WAIT: '발권 대기중인 티켓이 없어요.',
COMPLETE: '발권 왼료된 티켓이 없어요.',
CANCEL: '발권 취소된 티켓이 없어요.',
};

interface Props {
data: ReservationResponse[];
selectedTicketStatus: TicketStatus;
isSearchResult: boolean;
onClickReset?: VoidFunction;
}

const ReservationTable = ({ isSearchResult, data, selectedTicketStatus, onClickReset }: Props) => {
const table = useReactTable({ columns, data, getCoreRowModel: getCoreRowModel() });
return (
<Styled.Container>
<Styled.Header>
{table.getHeaderGroups().map((headerGroup) => (
<Styled.HeaderRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<Styled.HeaderItem key={header.id}>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
</Styled.HeaderItem>
))}
</Styled.HeaderRow>
))}
</Styled.Header>
{data.length === 0 ? (
<Styled.Empty>
{isSearchResult ? (
<>
검색 결과가 없어요.{'\n'}예매자 이름 또는 연락처를 변경해보세요.
<Styled.ResetButton colorTheme='line' size='bold' onClick={onClickReset}>검색 초기화</Styled.ResetButton>
</>
) : (
emptyLabel[selectedTicketStatus]
)}
</Styled.Empty>
) : (
<Styled.Body>
{table.getRowModel().rows.map((row) => (
<Styled.Row key={row.id}>
{row.getVisibleCells().map((cell) => (
<Styled.Item key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</Styled.Item>
))}
</Styled.Row>
))}
</Styled.Body>
)}
</Styled.Container>
);
};

export default ReservationTable;
Loading
Loading