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 = ({ 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(undefined) const [totalPages, setTotalPages] = useState(0) // 버튼 component 상태 전이 const setSuccessSnackBar = useSetRecoilState(detailButtonsSnackAtom) // 상태관리 hook const setErrorState = useSetRecoilState(errorStateSelector) const [openConfirm, setOpenConfirm] = useState(false) const [confirm, setConfirm] = useState({ open: openConfirm, handleConfirm: () => { setOpenConfirm(false) }, handleClose: () => { setOpenConfirm(false) }, }) // const [comments, setComments] = useRecoilState(commentState) const [comments, setComments] = useState([]) // 댓글 데이터 복사본 리턴 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 ( {t('comment')} ) => { event.preventDefault() handleRefresh() }} > {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 ( {comment.createdName} {comment.deleteAt !== 0 && ( <> {comment.deleteAt === 1 && t('common.delete.creator')} {comment.deleteAt === 2 && t('common.delete.manager')} )} {comment.commentContent} {comment.createdDate ? convertStringToDateFormat( comment.createdDate, 'yyyy-MM-dd HH:mm:ss', ) : ''} {comment.deleteAt === 0 && ( )} ) } return ( ) })} {commentUseAt && deleteAt === 0 && ( )} ) } export { Comment }