frontend add

This commit is contained in:
shinmj
2021-10-21 09:03:17 +09:00
parent 8caa4bbc5a
commit cb9d50511e
443 changed files with 88282 additions and 0 deletions

View File

@@ -0,0 +1,488 @@
import React, { useCallback, useEffect, useState } from 'react'
import { AxiosError } from 'axios'
import { useSetRecoilState } from 'recoil'
import { useTranslation } from 'next-i18next'
import classNames from 'classnames'
import {
createStyles,
makeStyles,
Theme,
useTheme,
} from '@material-ui/core/styles'
import Box from '@material-ui/core/Box'
import Card from '@material-ui/core/Card'
import CardActions from '@material-ui/core/CardActions'
import CardContent from '@material-ui/core/CardContent'
import Typography from '@material-ui/core/Typography'
import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline'
import RefreshIcon from '@material-ui/icons/Refresh'
import Link from '@material-ui/core/Link'
import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
import Button from '@material-ui/core/Button'
import { detailButtonsSnackAtom, errorStateSelector } from '@stores'
import useUser from '@hooks/useUser'
import { commentService, IComment } from '@service'
import { convertStringToDateFormat } from '@libs/date'
import { ConfirmDialog, ConfirmDialogProps } from '@components/Confirm'
import { CustomButtons } from '@components/Buttons'
import { CommentForm } from './form'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
commentRoot: {
marginTop: theme.spacing(1),
padding: theme.spacing(0, 2, 1, 2),
},
commentBox: {
padding: theme.spacing(2, 2, 1, 2),
},
commentTitle: {
display: 'flex',
padding: theme.spacing(2, 1),
},
commentView: {
padding: theme.spacing(0),
},
commentIcon: {
marginRight: theme.spacing(0.5),
verticalAlign: 'middle',
},
commentContent: {
whiteSpace: 'pre-wrap',
},
commentDate: {
marginRight: theme.spacing(3),
padding: theme.spacing(1, 0),
},
moreBox: {
textAlign: 'center',
marginTop: theme.spacing(2),
},
black: {
color: 'black',
},
ml1: {
marginLeft: theme.spacing(1),
},
pd1: {
padding: theme.spacing(1),
},
pdtb1: {
padding: theme.spacing(1, 0),
},
cancel: {
textDecoration: 'line-through',
},
}),
)
interface ICommentProps {
boardNo: number
postsNo: number
commentUseAt: boolean
deleteAt: number
// eslint-disable-next-line @typescript-eslint/ban-types
refreshCommentCount: (count) => void
}
interface ICommentSearchProps {
_page: number
_mode: 'replace' | 'append' | 'until'
}
const Comment: React.FC<ICommentProps> = ({
boardNo,
postsNo,
commentUseAt,
deleteAt,
refreshCommentCount,
}: ICommentProps) => {
const classes = useStyles()
const { user } = useUser()
const { t } = useTranslation()
const theme = useTheme()
const pagingSize = 2
// 현 페이지내 필요한 hook
const [page, setPage] = useState<number>(undefined)
const [totalPages, setTotalPages] = useState<number>(0)
// 버튼 component 상태 전이
const setSuccessSnackBar = useSetRecoilState(detailButtonsSnackAtom)
// 상태관리 hook
const setErrorState = useSetRecoilState(errorStateSelector)
const [openConfirm, setOpenConfirm] = useState<boolean>(false)
const [confirm, setConfirm] = useState<ConfirmDialogProps>({
open: openConfirm,
handleConfirm: () => {
setOpenConfirm(false)
},
handleClose: () => {
setOpenConfirm(false)
},
})
// const [comments, setComments] = useRecoilState(commentState)
const [comments, setComments] = useState<any[]>([])
// 댓글 데이터 복사본 리턴
const cloneComments = useCallback(
() => comments.slice(0, comments.length),
[comments],
)
// 페이지 조회
const getComments = useCallback(
({ _page, _mode }: ICommentSearchProps) => {
let searchPage = _page
let searchSize = pagingSize
if (_mode === 'until') {
searchSize = pagingSize * (_page + 1)
searchPage = 0
}
commentService
.list(boardNo, postsNo, searchSize, searchPage)
.then(result => {
setPage(_page)
// setTotalPages(result.totalPages)
setTotalPages(Math.ceil(result.groupElements / pagingSize))
refreshCommentCount(result.totalElements)
let arr = _mode === 'append' ? cloneComments() : []
arr.push(...result.content)
setComments(arr)
})
},
[boardNo, cloneComments, postsNo, refreshCommentCount],
)
// 전체 조회
const getAllComments = () => {
commentService.all(boardNo, postsNo).then(result => {
setPage(result.number)
setTotalPages(result.totalPages)
refreshCommentCount(result.totalElements)
let arr = []
arr.push(...result.content)
setComments(arr)
})
}
useEffect(() => {
if (page === undefined) {
getComments({ _page: 0, _mode: 'replace' })
}
}, [getComments, page])
// 댓글 갱신
const handleRefresh = useCallback(() => {
// getComments({ _page: 0, _mode: 'replace' }) // 첫페이지 재조회
getComments({ _page: page, _mode: 'until' }) // 현재 페이지까지 재조회
}, [getComments, page])
// 댓글 상태 초기화
const initComments = useCallback(() => {
let arr: IComment[] = cloneComments()
while (true) {
const index = arr.findIndex(a => a.mode === 'reply' || a.mode === 'edit')
if (index === -1) break
if (arr[index].mode === 'reply') {
arr.splice(index, 1)
} else {
arr[index].mode = 'none'
}
}
return arr
}, [cloneComments])
// 성공 callback
const successCallback = useCallback(() => {
setSuccessSnackBar('success')
// handleRefresh()
let arr: IComment[] = initComments()
setComments(arr)
}, [initComments, setSuccessSnackBar])
// 에러 callback
const errorCallback = useCallback(
(error: AxiosError) => {
setErrorState({
error,
})
},
[setErrorState],
)
// 댓글 더보기
const handleCommentMore = () => {
getComments({ _page: page + 1, _mode: 'append' })
}
// 댓글 답글쓰기
const handleCommentReply = async (parentCommentNo: number) => {
let arr: IComment[] = initComments()
const parentIndex = arr.findIndex(a => a.commentNo === parentCommentNo)
const reply: IComment = {
boardNo,
postsNo,
groupNo: arr[parentIndex].groupNo,
parentCommentNo,
depthSeq: arr[parentIndex].depthSeq + 1,
createdBy: user.userId,
createdName: user.userName,
commentContent: '',
mode: 'reply',
}
arr.splice(parentIndex + 1, 0, reply)
setComments(arr)
}
// 댓글 수정
const handleCommentEdit = async (commentNo: number) => {
let arr: IComment[] = initComments()
const index = arr.findIndex(a => a.commentNo === commentNo)
arr[index].mode = 'edit'
setComments(arr)
}
// 댓글 삭제
const handleCommentDelete = async (commentNo: number) => {
setConfirm({
open: openConfirm,
contentText: t('msg.confirm.delete'),
handleConfirm: () => {
setOpenConfirm(false)
commentService.delete({
boardNo,
postsNo,
commentNo,
callback: successCallback,
errorCallback,
})
},
handleClose: () => {
setOpenConfirm(false)
},
})
setOpenConfirm(true)
}
// handleSubmit 댓글 저장
const handleCommentSave = async (comment: IComment) => {
if (comment.commentNo > 0) {
await commentService.update({
callback: () => {
successCallback()
getComments({ _page: page, _mode: 'until' }) // 현재 페이지까지 재조회
},
errorCallback,
data: comment,
})
} else {
await commentService.save({
callback: () => {
successCallback()
if (comment.parentCommentNo) {
getComments({ _page: page, _mode: 'until' }) // 현재 페이지까지 재조회
} else {
getAllComments() // 마지막 페이지까지 조회
}
},
errorCallback,
data: comment,
})
}
}
// 취소
const handleCommentCancel = async () => {
let arr: IComment[] = initComments()
setComments(arr)
}
return (
<Box boxShadow={1} className={classes.commentRoot}>
<Box className={classes.commentTitle}>
<Typography variant="h4" component="h3" className={classes.pdtb1}>
{t('comment')}
</Typography>
<Link
href="#"
className={classNames({
[classes.black]: true,
[classes.ml1]: true,
[classes.pdtb1]: true,
})}
onClick={(event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault()
handleRefresh()
}}
>
<RefreshIcon fontSize="small" className={classes.commentIcon} />
</Link>
</Box>
{comments &&
comments.map(comment => {
if (comment.mode !== 'edit' && comment.mode !== 'reply') {
let buttons = []
if (commentUseAt && deleteAt === 0) {
buttons.push({
label: t('label.button.reply'),
size: 'small',
handleButton: () => {
handleCommentReply(comment.commentNo)
},
})
if (user?.userId === comment.createdBy) {
buttons.push({
label: t('label.button.edit'),
size: 'small',
handleButton: () => {
handleCommentEdit(comment.commentNo)
},
})
buttons.push({
label: t('label.button.delete'),
size: 'small',
handleButton: () => {
handleCommentDelete(comment.commentNo)
},
completeMessage: t('msg.success.delete'),
})
}
}
return (
<Card
key={`comment${comment.commentNo}`}
className={classNames({
[classes.commentBox]: true,
})}
style={{
paddingLeft: `${
theme.spacing(2) + comment.depthSeq * theme.spacing(4)
}px`,
}}
>
<CardContent className={classes.commentView}>
<Typography gutterBottom variant="h6" component="h4">
{comment.createdName}
</Typography>
{comment.deleteAt !== 0 && (
<>
<ErrorOutlineIcon
fontSize="small"
className={classes.commentIcon}
/>
{comment.deleteAt === 1 && t('common.delete.creator')}
{comment.deleteAt === 2 && t('common.delete.manager')}
</>
)}
<Typography variant="body2" color="textPrimary" component="p">
<Box
className={classNames({
[classes.commentContent]: true,
[classes.cancel]: comment.deleteAt !== 0,
})}
component="span"
>
{comment.commentContent}
</Box>
</Typography>
</CardContent>
<CardActions className={classes.commentView}>
<Typography
variant="body2"
color="textSecondary"
component="p"
className={classes.commentDate}
>
{comment.createdDate
? convertStringToDateFormat(
comment.createdDate,
'yyyy-MM-dd HH:mm:ss',
)
: ''}
</Typography>
{comment.deleteAt === 0 && (
<CustomButtons buttons={buttons} className="mg0" />
)}
</CardActions>
</Card>
)
}
return (
<Box
key={`comment${comment.commentNo}`}
boxShadow={1}
className={classes.pd1}
>
<CommentForm
boardNo={boardNo}
postsNo={postsNo}
commentNo={comment.commentNo}
commentContent={comment.commentContent}
groupNo={comment.groupNo}
parentCommentNo={comment.parentCommentNo}
depthSeq={comment.depthSeq}
handleCommentSave={handleCommentSave}
handleCommentCancel={handleCommentCancel}
/>
</Box>
)
})}
<Box className={classes.moreBox} hidden={page + 1 >= totalPages}>
<Button
startIcon={<ExpandMoreIcon />}
endIcon={<ExpandMoreIcon />}
onClick={handleCommentMore}
>
{t('common.more')}
</Button>
</Box>
{commentUseAt && deleteAt === 0 && (
<Box className={classes.pdtb1}>
<CommentForm
handleCommentSave={handleCommentSave}
boardNo={boardNo}
postsNo={postsNo}
/>
</Box>
)}
<ConfirmDialog
open={openConfirm}
contentText={confirm.contentText}
handleClose={confirm.handleClose}
handleConfirm={confirm.handleConfirm}
/>
</Box>
)
}
export { Comment }