Skip to content

Commit e36f753

Browse files
committed
feat: 댓글 리스트에 “더보기” 버튼 추가 및 페이징 처리(#354)
1 parent 733a676 commit e36f753

4 files changed

Lines changed: 210 additions & 192 deletions

File tree

src/features/project/post/components/CommentSection.jsx

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from "@mui/material";
1010
import ReplyIcon from "@mui/icons-material/Reply";
1111
import CommentInput from "./CommentInput";
12+
import CustomButton from "@/components/common/customButton/CustomButton";
1213

1314
// 재귀적으로 리뷰를 렌더링하는 컴포넌트
1415
function CommentItem({ review, level = 0, postId, onReply }) {
@@ -57,7 +58,7 @@ function CommentItem({ review, level = 0, postId, onReply }) {
5758

5859
{/* 대댓글 입력창 */}
5960
{replying && (
60-
<Box sx={{ ml: (level + 1) * 4 }}>
61+
<Box sx={{ ml: (level + 1) * 4, mt: 2 }}>
6162
<CommentInput
6263
postId={postId}
6364
parentId={review.reviewId}
@@ -84,27 +85,24 @@ function CommentItem({ review, level = 0, postId, onReply }) {
8485
</Box>
8586
)}
8687

87-
{/* 최상위 댓글 뒤에 구분선 */}
8888
{level === 0 && <Divider sx={{ mt: 2 }} />}
8989
</Box>
9090
);
9191
}
9292

93-
// CommentSection: 댓글 입력창을 최상단에, 그 아래 댓글 목록, 더보기 버튼 렌더링
9493
export default function CommentSection({
9594
postId,
9695
comments = [],
9796
onSubmit,
9897
onReply,
98+
hasMore,
9999
onLoadMore,
100100
currentPage = 1,
101101
}) {
102102
return (
103103
<Box sx={{ mt: 3 }}>
104-
{/* 최상위 댓글 입력창 */}
105104
<CommentInput postId={postId} onSubmit={(text) => onSubmit(text)} />
106105

107-
{/* 댓글 목록 */}
108106
{comments.map((review) => (
109107
<CommentItem
110108
key={review.reviewId}
@@ -114,16 +112,17 @@ export default function CommentSection({
114112
/>
115113
))}
116114

117-
{/* 더보기 버튼 */}
118-
{/* <Box sx={{ display: "flex", justifyContent: "center", mt: 2 }}>
119-
<CustomButton
120-
kind="ghost"
121-
size="small"
122-
onClick={() => onLoadMore({ postId, page: currentPage + 1 })}
123-
>
124-
더보기
125-
</CustomButton>
126-
</Box> */}
115+
{hasMore && (
116+
<Box sx={{ display: "flex", justifyContent: "center", mt: 2 }}>
117+
<CustomButton
118+
kind="ghost"
119+
size="small"
120+
onClick={() => onLoadMore({ postId, page: currentPage + 1 })}
121+
>
122+
더보기
123+
</CustomButton>
124+
</Box>
125+
)}
127126
</Box>
128127
);
129128
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// src/components/common/attachment/AttachmentList.jsx
2+
import React from "react";
3+
import {
4+
Box,
5+
Stack,
6+
Typography,
7+
IconButton,
8+
Tooltip,
9+
Alert,
10+
Divider,
11+
} from "@mui/material";
12+
import ZoomInIcon from "@mui/icons-material/ZoomIn";
13+
import DownloadIcon from "@mui/icons-material/Download";
14+
import ImageIcon from "@mui/icons-material/Image";
15+
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
16+
import {
17+
PictureAsPdf,
18+
Description,
19+
TableChart,
20+
Archive,
21+
Code,
22+
Movie,
23+
MusicNote,
24+
} from "@mui/icons-material";
25+
26+
function formatFileSize(bytes) {
27+
if (bytes === 0) return "0 Bytes";
28+
const k = 1024;
29+
const sizes = ["Bytes", "KB", "MB", "GB"];
30+
const i = Math.floor(Math.log(bytes) / Math.log(k));
31+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
32+
}
33+
34+
function getFileIcon(fileName, fileType) {
35+
const ext = fileName.toLowerCase().split(".").pop();
36+
if (fileType.startsWith("image/")) return <ImageIcon color="primary" />;
37+
if (["pdf"].includes(ext)) return <PictureAsPdf color="error" />;
38+
if (["doc", "docx", "txt", "rtf"].includes(ext)) return <Description />;
39+
if (["xls", "xlsx", "csv"].includes(ext))
40+
return <TableChart color="success" />;
41+
if (["zip", "rar", "7z", "tar", "gz"].includes(ext))
42+
return <Archive color="warning" />;
43+
if (["js", "ts", "jsx", "tsx", "html", "css", "json", "xml"].includes(ext))
44+
return <Code color="info" />;
45+
if (["mp4", "avi", "mov", "wmv", "flv", "mkv"].includes(ext))
46+
return <Movie color="secondary" />;
47+
if (["mp3", "wav", "flac", "aac", "ogg"].includes(ext))
48+
return <MusicNote color="secondary" />;
49+
return <InsertDriveFileIcon color="action" />;
50+
}
51+
52+
export default function FileAttachmentViewer({
53+
attachments = [],
54+
loading = false,
55+
error = null,
56+
onPreview,
57+
onDownload,
58+
}) {
59+
if (!attachments.length && !loading) return null;
60+
61+
return (
62+
<Box>
63+
<Stack direction="row" alignItems="center" spacing={1} mb={2}>
64+
<Typography variant="h6" fontWeight={600}>
65+
첨부파일 ({attachments.length}개)
66+
{loading && " (로딩 중...)"}
67+
</Typography>
68+
</Stack>
69+
<Divider sx={{ mb: 3 }} />
70+
{error && (
71+
<Alert severity="error" sx={{ mb: 2 }}>
72+
파일 로드 실패: {error}
73+
</Alert>
74+
)}
75+
<Stack spacing={1}>
76+
{attachments.map((file) => (
77+
<Box
78+
key={file.id}
79+
sx={{
80+
p: 2,
81+
border: "1px solid",
82+
borderColor: "grey.300",
83+
borderRadius: 1,
84+
bgcolor: "background.paper",
85+
"&:hover": {
86+
borderColor: "primary.main",
87+
bgcolor: "grey.50",
88+
},
89+
}}
90+
>
91+
<Stack direction="row" alignItems="center" spacing={2}>
92+
<Box
93+
sx={{
94+
width: 40,
95+
height: 40,
96+
display: "flex",
97+
alignItems: "center",
98+
justifyContent: "center",
99+
bgcolor: "grey.100",
100+
borderRadius: 1,
101+
}}
102+
>
103+
{getFileIcon(file.fileName, file.fileType)}
104+
</Box>
105+
<Box sx={{ flex: 1, minWidth: 0 }}>
106+
<Typography variant="body2" fontWeight={600} noWrap>
107+
{file.fileName}
108+
</Typography>
109+
<Typography variant="caption" color="text.secondary">
110+
{formatFileSize(file.fileSize)}
111+
{file.fileType && ` • ${file.fileType}`}
112+
</Typography>
113+
</Box>
114+
<Stack direction="row" spacing={1}>
115+
{file.isImage && file.imageUrl && onPreview && (
116+
<Tooltip title="미리보기">
117+
<IconButton size="small" onClick={() => onPreview(file)}>
118+
<ZoomInIcon />
119+
</IconButton>
120+
</Tooltip>
121+
)}
122+
{onDownload && (
123+
<Tooltip title="다운로드">
124+
<IconButton size="small" onClick={() => onDownload(file)}>
125+
<DownloadIcon />
126+
</IconButton>
127+
</Tooltip>
128+
)}
129+
</Stack>
130+
</Stack>
131+
{file.error && (
132+
<Alert severity="error" sx={{ mt: 1 }}>
133+
파일 로드 실패: {file.error}
134+
</Alert>
135+
)}
136+
</Box>
137+
))}
138+
</Stack>
139+
</Box>
140+
);
141+
}

0 commit comments

Comments
 (0)