Skip to content

Commit 22bb547

Browse files
authored
Merge pull request #521 from KEEPER31337/feature/게시판_리스트_그리드_뷰_전환_기능_#134
Feature/게시판 리스트 그리드 뷰 전환 기능 #134
2 parents 9295448 + d1b820c commit 22bb547

File tree

9 files changed

+101
-43
lines changed

9 files changed

+101
-43
lines changed

src/api/dto.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ export interface PostSummaryInfo {
160160
id: number;
161161
title: string;
162162
writerName: string;
163+
writerThumbnailPath: string;
163164
visitCount: number;
164165
commentCount: number;
165166
isSecret: boolean;

src/components/Button/TableViewSwitchButton.tsx

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
11
import React from 'react';
2+
import { useRecoilState } from 'recoil';
3+
import { ToggleButton, ToggleButtonGroup } from '@mui/material';
24
import { MdOutlineApps, MdOutlineViewHeadline } from 'react-icons/md';
5+
import tableViewState from '@recoil/view.recoil';
36

4-
type switchType = 'List' | 'Grid';
7+
export type TableType = 'List' | 'Grid';
58

6-
interface TableViewSwitchButtonProps {
7-
type: switchType;
8-
isActive?: boolean;
9-
}
9+
const TableViewSwitchButton = () => {
10+
const [tableView, setTableView] = useRecoilState(tableViewState);
11+
12+
const handleTableView = (event: React.MouseEvent<HTMLElement>, newTableView: TableType | null) => {
13+
if (!newTableView) return;
14+
setTableView(newTableView);
15+
};
1016

11-
const TableViewSwitchButton = ({ type, isActive }: TableViewSwitchButtonProps) => {
1217
return (
13-
<div
14-
className={`flex h-8 w-8 items-center justify-center rounded-sm border ${
15-
isActive ? 'border-pointBlue' : 'border-white'
16-
}`}
17-
>
18-
{type === 'List' ? <MdOutlineViewHeadline size="20" color="#4CEEF9" /> : <MdOutlineApps size="20" />}
19-
</div>
18+
<ToggleButtonGroup exclusive size="small" value={tableView} onChange={handleTableView}>
19+
<ToggleButton value="List" aria-label="List">
20+
<MdOutlineViewHeadline size="20" />
21+
</ToggleButton>
22+
<ToggleButton value="Grid" aria-label="Grid">
23+
<MdOutlineApps size="20" />
24+
</ToggleButton>
25+
</ToggleButtonGroup>
2026
);
2127
};
2228

src/components/Card/PostingCard.interface.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export interface CardMainInfoProps {
55

66
export interface CardDetailInfoProps {
77
writerThumbnailPath: string | null;
8-
writer: string;
8+
writerName: string;
99
registerTime: string;
1010
}
1111

src/components/Card/PostingCard.tsx

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import React from 'react';
2-
import { Avatar, Card, CardContent, CardMedia, Typography } from '@mui/material';
2+
import { Avatar, Card, CardContent, CardMedia, Stack, Typography } from '@mui/material';
33
import { VscComment, VscEye } from 'react-icons/vsc';
4-
import { DateTime } from 'luxon';
54
import { ReactComponent as Logo } from '@assets/logo/logo_neon.svg';
5+
import { getServerImgUrl } from '@utils/converter';
66
import { CardDetailInfoProps, CardMainInfoProps, InteractionScoreProps } from './PostingCard.interface';
77

88
export interface PostingCardProps extends CardMainInfoProps, CardDetailInfoProps, InteractionScoreProps {
@@ -22,18 +22,18 @@ const CardMainInfo = ({ type, title }: CardMainInfoProps) => {
2222
);
2323
};
2424

25-
const CardDetailInfo = ({ writerThumbnailPath, writer, registerTime }: CardDetailInfoProps) => {
25+
const CardDetailInfo = ({ writerThumbnailPath, writerName, registerTime }: CardDetailInfoProps) => {
2626
return (
2727
<div className="flex">
2828
<Avatar className="mr-2 !h-6 !w-6" src={writerThumbnailPath ?? undefined} />
29-
<div>
29+
<Stack>
3030
<Typography className="font-medium" variant="small">
31-
{writer}
31+
{writerName}
3232
</Typography>
3333
<Typography className="font-semibold text-subGray" variant="small">
34-
{DateTime.fromISO(registerTime).toFormat('yyyy.MM.dd')}
34+
{registerTime}
3535
</Typography>
36-
</div>
36+
</Stack>
3737
</div>
3838
);
3939
};
@@ -62,22 +62,31 @@ const PostingCard = ({
6262
type,
6363
title,
6464
writerThumbnailPath,
65-
writer,
65+
writerName,
6666
registerTime,
6767
visitCount,
6868
commentCount,
6969
}: PostingCardProps) => {
7070
return (
7171
<Card className="w-52 !rounded-none !bg-middleBlack !bg-none">
7272
{thumbnailPath ? (
73-
<CardMedia className="h-[118px] bg-middleBlack" component="img" src={thumbnailPath} alt="썸네일" />
73+
<CardMedia
74+
className="h-[118px] bg-middleBlack"
75+
component="img"
76+
src={getServerImgUrl(thumbnailPath)}
77+
alt="썸네일"
78+
/>
7479
) : (
7580
<Logo className="m-auto h-[118px] w-28" />
7681
)}
7782
<CardContent className="flex h-24 flex-col justify-between !bg-mainBlack !p-3">
7883
<CardMainInfo type={type} title={title} />
7984
<div className="flex items-end justify-between">
80-
<CardDetailInfo writerThumbnailPath={writerThumbnailPath} writer={writer} registerTime={registerTime} />
85+
<CardDetailInfo
86+
writerThumbnailPath={writerThumbnailPath}
87+
writerName={writerName}
88+
registerTime={registerTime}
89+
/>
8190
<InteractionScore visitCount={visitCount} commentCount={commentCount} />
8291
</div>
8392
</CardContent>

src/components/Image/ServerImg.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useState } from 'react';
22
import LogoNeon from '@assets/logo/logo_neon.svg';
3+
import { getServerImgUrl } from '@utils/converter';
34

45
interface ServerImg {
56
src: string;
@@ -10,7 +11,7 @@ interface ServerImg {
1011

1112
const ServerImg = ({ src, alt, className, errorClassName }: ServerImg) => {
1213
const [error, setError] = useState(false);
13-
const srcWithServerUrl = `${process.env.REACT_APP_API_URL}/${src}`;
14+
const srcWithServerUrl = getServerImgUrl(src);
1415

1516
const handleImgError: React.ReactEventHandler<HTMLImageElement> = (e) => {
1617
e.currentTarget.src = LogoNeon;

src/components/Table/GridTable.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,36 @@
11
import React from 'react';
22
import PostingCard, { PostingCardProps } from '@components/Card/PostingCard';
33
import StandardTablePagination from '@components/Pagination/StandardTablePagination';
4+
import { PaginationOption } from '@components/Pagination/StandardTablePagination.interface';
45
import { Row } from './StandardTable.interface';
56

67
interface GridTableProps<T> {
78
rows: (PostingCardProps & Row<T>)[];
9+
paginationOption?: PaginationOption;
810
}
911

10-
const GridTable = <T,>({ rows }: GridTableProps<T>) => {
12+
const GridTable = <T,>({ rows, paginationOption }: GridTableProps<T>) => {
1113
return (
1214
<div>
1315
<div className="mb-1 grid grid-cols-5 gap-2">
1416
{rows.map(
15-
({ id, thumbnailPath, type, title, writerThumbnailPath, writer, registerTime, visitCount, commentCount }) => (
17+
({
18+
id,
19+
thumbnailPath,
20+
type,
21+
title,
22+
writerThumbnailPath,
23+
writerName,
24+
registerTime,
25+
visitCount,
26+
commentCount,
27+
}) => (
1628
<PostingCard
1729
key={id}
1830
type={type}
1931
title={title}
2032
writerThumbnailPath={writerThumbnailPath}
21-
writer={writer}
33+
writerName={writerName}
2234
registerTime={registerTime}
2335
visitCount={visitCount}
2436
commentCount={commentCount}
@@ -27,7 +39,7 @@ const GridTable = <T,>({ rows }: GridTableProps<T>) => {
2739
),
2840
)}
2941
</div>
30-
<StandardTablePagination rowsPerPage={10} />
42+
<StandardTablePagination {...paginationOption} />
3143
</div>
3244
);
3345
};

src/pages/board/BoardList.tsx

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import { useGetPostListQuery } from '@api/postApi';
99
import { categoryNameToId } from '@utils/converter';
1010
import { Column, Row } from '@components/Table/StandardTable.interface';
1111
import usePagination from '@hooks/usePagination';
12+
import tableViewState from '@recoil/view.recoil';
13+
import { useRecoilValue } from 'recoil';
14+
import GridTable from '@components/Table/GridTable';
1215

1316
interface BoardRow {
1417
no: number;
@@ -37,6 +40,7 @@ const BoardList = () => {
3740

3841
const navigate = useNavigate();
3942
const { data: posts } = useGetPostListQuery({ categoryId, page });
43+
const tableView = useRecoilValue(tableViewState);
4044

4145
if (!posts) {
4246
return null;
@@ -60,20 +64,28 @@ const BoardList = () => {
6064
</div>
6165
<div className="flex items-center justify-between pb-5">
6266
<SearchSection />
63-
<div className="flex gap-2">
64-
<TableViewSwitchButton type="List" isActive />
65-
<TableViewSwitchButton type="Grid" />
66-
</div>
67+
<TableViewSwitchButton />
6768
</div>
68-
<StandardTable
69-
columns={boardColumn}
70-
rows={posts.content.map((post, postIndex) => ({
71-
no: getRowNumber({ size: posts.size, index: postIndex }),
72-
...post,
73-
}))}
74-
onRowClick={handlePostRowClick}
75-
paginationOption={{ rowsPerPage: posts.size, totalItems: posts.totalElements }}
76-
/>
69+
{tableView === 'List' && (
70+
<StandardTable
71+
columns={boardColumn}
72+
rows={posts.content.map((post, postIndex) => ({
73+
no: getRowNumber({ size: posts.size, index: postIndex }),
74+
...post,
75+
}))}
76+
onRowClick={handlePostRowClick}
77+
paginationOption={{ rowsPerPage: posts.size, totalItems: posts.totalElements }}
78+
/>
79+
)}
80+
{tableView === 'Grid' && (
81+
<GridTable<BoardRow>
82+
rows={posts.content.map((post, postIndex) => ({
83+
no: getRowNumber({ size: posts.size, index: postIndex }),
84+
...post,
85+
}))}
86+
paginationOption={{ rowsPerPage: posts.size, totalItems: posts.totalElements }}
87+
/>
88+
)}
7789
</div>
7890
);
7991
};

src/recoil/view.recoil.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { TableType } from '@components/Button/TableViewSwitchButton';
2+
import { atom } from 'recoil';
3+
import { recoilPersist } from 'recoil-persist';
4+
5+
const { persistAtom } = recoilPersist();
6+
7+
const tableViewState = atom<TableType>({
8+
key: 'tableViewState',
9+
default: 'List',
10+
effects_UNSTABLE: [persistAtom],
11+
});
12+
13+
export default tableViewState;

src/utils/converter.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,8 @@ const categoryNameToId = (categoryName: string) => {
2525
return null;
2626
};
2727

28-
export { formatFileSize, categoryNameToId };
28+
const getServerImgUrl = (url: string) => {
29+
return `${process.env.REACT_APP_API_URL}/${url}`;
30+
};
31+
32+
export { formatFileSize, categoryNameToId, getServerImgUrl };

0 commit comments

Comments
 (0)