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,195 @@
import { NormalEditForm } from '@components/EditForm'
import {
boardService,
fileService,
IAttachmentResponse,
IBoard,
IPosts,
IPostsForm,
PostsReqPayload,
SKINT_TYPE_CODE_NORMAL,
SKINT_TYPE_CODE_QNA,
} from '@service'
import { errorStateSelector } from '@stores'
import { AxiosError } from 'axios'
import { GetServerSideProps } from 'next'
import { useRouter } from 'next/router'
import React, { createContext, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useSetRecoilState } from 'recoil'
interface BoardEditProps {
post: IPosts
board: IBoard
}
export const BoardFormContext = createContext<{
post: IPostsForm
board: IBoard
attachList: IAttachmentResponse[]
setPostDataHandler: (data: IPostsForm) => void
setAttachListHandler: (data: IAttachmentResponse[]) => void
}>({
post: undefined,
board: undefined,
attachList: undefined,
setPostDataHandler: () => {},
setAttachListHandler: () => {},
})
const BoardEdit = (props: BoardEditProps) => {
const { post, board } = props
const router = useRouter()
const { t } = useTranslation()
const setErrorState = useSetRecoilState(errorStateSelector)
const [postData, setPostData] = useState<IPostsForm>(undefined)
const setPostDataHandler = (data: IPostsForm) => {
setPostData(data)
}
const [attachList, setAttachList] = useState<IAttachmentResponse[]>(undefined)
const setAttachListHandler = (data: IAttachmentResponse[]) => {
setAttachList(data)
}
// callback
const errorCallback = useCallback(
(error: AxiosError) => {
setErrorState({
error,
})
},
[setErrorState],
)
const successCallback = useCallback(() => {
router.back()
}, [])
const save = useCallback(() => {
const data: IPosts = {
boardNo: post.boardNo,
postsNo: post.postsNo,
...postData,
}
if (post.postsNo === -1) {
boardService.savePost({
boardNo: post.boardNo,
callback: successCallback,
errorCallback,
data,
})
} else {
boardService.updatePost({
boardNo: post.boardNo,
postsNo: post.postsNo,
callback: successCallback,
errorCallback,
data,
})
}
// boardService.
}, [postData, post, successCallback, errorCallback])
useEffect(() => {
if (postData) {
save()
}
}, [postData, attachList])
useEffect(() => {
if (post.attachmentCode) {
const getAttachments = async () => {
try {
const result = await fileService.getAttachmentList(
post.attachmentCode,
)
if (result?.data) {
setAttachList(result.data)
}
} catch (error) {
setErrorState({ error })
}
}
getAttachments()
}
return () => setAttachList(null)
}, [post])
return (
<div className="qnaWrite">
<div className="table_write01">
<span>{t('common.required_fields')}</span>
<BoardFormContext.Provider
value={{
post,
board,
attachList,
setPostDataHandler,
setAttachListHandler,
}}
>
{board.skinTypeCode === SKINT_TYPE_CODE_NORMAL && (
<NormalEditForm post={post} />
)}
{board.skinTypeCode === SKINT_TYPE_CODE_QNA && (
<NormalEditForm post={post} />
)}
{/* <QnAEditForm /> */}
</BoardFormContext.Provider>
</div>
</div>
)
}
export const getServerSideProps: GetServerSideProps = async context => {
const boardNo = Number(context.query.board)
const postsNo = Number(context.query.id)
const { keywordType, keyword } = context.query
let board = {}
let post = {}
try {
if (postsNo !== -1) {
const result = await boardService.getPostById({
boardNo,
postsNo,
keywordType,
keyword,
} as PostsReqPayload)
if (result && result.data && result.data.board) {
board = (await result.data.board) as IBoard
post = (await result.data) as IPosts
}
} else {
const result = await boardService.getBoardById(boardNo)
if (result && result.data) {
board = (await result.data) as IBoard
post = {
boardNo,
postsNo,
}
}
}
} catch (error) {
console.error(`posts item query error ${error.message}`)
if (error.response?.data?.code === 'E003') {
return {
notFound: true,
}
}
}
return {
props: {
board,
post,
},
}
}
export default BoardEdit

View File

@@ -0,0 +1,149 @@
import { FAQBaordList, NormalBoardList } from '@components/BoardList'
import { BottomButtons, IButtons } from '@components/Buttons'
import usePage from '@hooks/usePage'
import { boardService, IBoard } from '@service'
import { conditionAtom, userAtom } from '@stores'
import { GetServerSideProps } from 'next'
import { useTranslation } from 'next-i18next'
import { useRouter } from 'next/router'
import React, { useMemo, useState } from 'react'
import { useRecoilValue } from 'recoil'
interface BoardProps {
board: IBoard
}
const Board = ({ board }: BoardProps) => {
const router = useRouter()
const { query } = router
const { t } = useTranslation()
const user = useRecoilValue(userAtom)
const conditionKey = useMemo(() => {
if (query) {
return `board-${query.board}`
}
return undefined
}, [query])
// 조회조건 상태관리
const keywordState = useRecoilValue(conditionAtom(conditionKey))
const { page, setPageValue } = usePage(conditionKey)
const [pageSize, setPageSize] = useState<number>(board?.postDisplayCount)
const { data, mutate } = boardService.search(
parseInt(query?.board as string, 10),
{
keywordType: keywordState?.keywordType || 'postsTitle',
keyword: keywordState?.keyword || '',
size: pageSize,
page,
},
)
const handlePageSize = (size: number) => {
setPageSize(size)
}
const handleSearch = () => {
if (page === 0) {
mutate()
} else {
setPageValue(0)
}
}
const handleButtons = useMemo(
(): IButtons[] => [
{
id: 'regist',
title: t('label.button.reg'),
href: `${router.asPath}/edit/-1`,
className: 'blue',
},
],
[t, router.asPath],
)
// datagrid page change event
const handlePageChange = (_page: number, details?: any) => {
setPageValue(_page)
}
const handleChangePage = (
event: React.MouseEvent<HTMLButtonElement> | null,
_page: number,
) => {
setPageValue(_page)
}
return (
<div className={query?.skin === 'faq' ? 'qna_list' : 'table_list01'}>
{query?.skin === 'normal' && (
<NormalBoardList
data={data}
pageSize={pageSize}
handlePageSize={handlePageSize}
handleSearch={handleSearch}
conditionKey={conditionKey}
page={page}
handlePageChange={handlePageChange}
/>
)}
{query?.skin === 'faq' && (
<FAQBaordList
data={data}
pageSize={pageSize}
page={page}
handleChangePage={handleChangePage}
/>
)}
{query?.skin === 'qna' && (
/* <QnABaordList
data={data}
pageSize={pageSize}
page={page}
handleChangePage={handleChangePage}
/> */
<NormalBoardList
data={data}
pageSize={pageSize}
handlePageSize={handlePageSize}
handleSearch={handleSearch}
conditionKey={conditionKey}
page={page}
handlePageChange={handlePageChange}
/>
)}
{user && board.userWriteAt === true && (
<BottomButtons handleButtons={handleButtons} />
)}
</div>
)
}
export const getServerSideProps: GetServerSideProps = async context => {
const boardNo = Number(context.query.board)
let board = {}
try {
const result = await boardService.getBoardById(boardNo)
if (result) {
board = result.data
}
} catch (error) {
console.error(`board query error : ${error.message}`)
}
return {
props: {
board,
},
}
}
export default Board

View File

@@ -0,0 +1,411 @@
import AttachList from '@components/AttachList'
import { BottomButtons, IButtons } from '@components/Buttons'
import {
CommentsList,
EditComments,
EditCommentsType,
} from '@components/Comments'
import { COMMENTS_PAGE_SIZE } from '@constants'
import { format as dateFormat } from '@libs/date'
import {
boardService,
CommentSavePayload,
fileService,
IAttachmentResponse,
IBoard,
ICommentSearchProps,
IPosts,
PostsReqPayload,
SKINT_TYPE_CODE_NORMAL,
SKINT_TYPE_CODE_QNA,
} from '@service'
import { errorStateSelector, userAtom } from '@stores'
import { GetServerSideProps } from 'next'
import { useRouter } from 'next/router'
import React, {
createRef,
useCallback,
useEffect,
useMemo,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import { useRecoilValue, useSetRecoilState } from 'recoil'
interface BaordViewProps {
post: IPosts
board: IBoard
}
const BoardView = (props: BaordViewProps) => {
const { post, board } = props
const router = useRouter()
const { t } = useTranslation()
const user = useRecoilValue(userAtom)
const replyRef = createRef<EditCommentsType>()
const setErrorState = useSetRecoilState(errorStateSelector)
// 첨부파일
const [attachList, setAttachList] = useState<IAttachmentResponse[]>(undefined)
useEffect(() => {
if (post.attachmentCode) {
const getAttachments = async () => {
try {
const result = await fileService.getAttachmentList(
post.attachmentCode,
)
if (result?.data) {
setAttachList(result.data)
}
} catch (error) {
setErrorState({ error })
}
}
getAttachments()
}
return () => setAttachList(null)
}, [post, setErrorState])
const [page, setPage] = useState<number>(undefined)
const [totalPages, setTotalPages] = useState<number>(0)
const [commentCount, setCommentCount] = useState<number>(undefined)
const [comments, setComments] = useState<CommentSavePayload[]>(undefined)
// 댓글 데이터 복사본 리턴
const cloneComments = useCallback(
() => comments.slice(0, comments.length),
[comments],
)
// 페이지 조회
const getComments = useCallback(
({ boardNo, postsNo, _page, _mode }: ICommentSearchProps) => {
let searchPage = _page
let searchSize = COMMENTS_PAGE_SIZE
if (_mode === 'until') {
searchSize = COMMENTS_PAGE_SIZE * (_page + 1)
searchPage = 0
}
boardService
.getComments(boardNo, postsNo, searchSize, searchPage)
.then(result => {
setPage(_page)
// setTotalPages(result.totalPages)
setTotalPages(Math.ceil(result.groupElements / COMMENTS_PAGE_SIZE))
setCommentCount(result.totalElements)
let arr = _mode === 'append' ? cloneComments() : []
arr.push(...result.content)
setComments(arr)
})
},
[cloneComments],
)
// 전체 조회
const getAllComments = () => {
boardService.getAllComments(post.boardNo, post.postsNo).then(result => {
setPage(result.number)
setTotalPages(result.totalPages)
setCommentCount(result.totalElements)
let arr = []
arr.push(...result.content)
setComments(arr)
})
}
useEffect(() => {
if (post) {
getComments({
boardNo: post.boardNo,
postsNo: post.postsNo,
_page: 0,
_mode: 'replace',
})
}
}, [post])
// 댓글 등록
const handleCommentRegist = (comment: CommentSavePayload) => {
if (comment.commentNo > 0) {
boardService.updateComment(comment).then(() => {
getComments({
boardNo: post.boardNo,
postsNo: post.postsNo,
_page: page,
_mode: 'until',
}) // 현재 페이지까지 재조회
})
} else {
boardService.saveComment(comment).then(() => {
if (comment.parentCommentNo) {
getComments({
boardNo: post.boardNo,
postsNo: post.postsNo,
_page: page,
_mode: 'until',
}) // 현재 페이지까지 재조회
} else {
getAllComments() // 마지막 페이지까지 조회
}
})
}
}
// 댓글 삭제
const handleCommentDelete = (comment: CommentSavePayload) => {
boardService.deleteComment(comment).then(() => {
getComments({
boardNo: post.boardNo,
postsNo: post.postsNo,
_page: page,
_mode: 'until',
}) // 현재 페이지까지 재조회
})
}
const handleCommentCancel = () => {
replyRef.current?.clear()
}
const handleCommentMore = (e: React.MouseEvent<HTMLAnchorElement>) => {
e.preventDefault()
getComments({
boardNo: post.boardNo,
postsNo: post.postsNo,
_page: page + 1,
_mode: 'append',
})
}
// 이전글, 다음글 클릭
const handleNearPostClick = nearPost => {
router.push(
`/board/${router.query.skin}/${router.query.board}/view/${nearPost.postsNo}?size=${router.query.size}&page=${router.query.page}&keywordType=${router.query.keywordType}&keyword=${router.query.keyword}`,
)
}
// 버튼
const bottomButtons = useMemo((): IButtons[] => {
const buttons: IButtons[] = [
{
id: 'board-list-button',
title: t('label.button.list'),
href: `/board/${router.query.skin}/${router.query.board}`,
},
]
if (user && board.userWriteAt && post.createdBy === user.userId) {
buttons.push({
id: 'board-edit-button',
title: t('label.button.edit'),
href: router.asPath.replace('view', 'edit'),
className: 'blue',
})
return buttons.reverse()
}
return buttons
}, [
t,
router.query.skin,
router.query.board,
router.asPath,
user,
board.userWriteAt,
post.createdBy,
])
return (
<div className="table_view01">
<h4>
{post.noticeAt ? `[${t('common.notice')}] ` : ''}
{post.postsTitle}
</h4>
<div className="view">
<div className="top">
<dl>
<dt>{t('common.written_by')}</dt>
<dd>{post.createdName}</dd>
</dl>
<dl>
<dt>{t('common.written_date')}</dt>
<dd>{dateFormat(new Date(post.createdDate), 'yyyy-MM-dd')}</dd>
</dl>
<dl>
<dt>{t('common.read_count')}</dt>
<dd>{post.readCount}</dd>
</dl>
{board.uploadUseAt && (
<dl className="file">
<dt>{t('common.attachment')}</dt>
<dd>
<AttachList
data={attachList}
setData={setAttachList}
readonly
/>
</dd>
</dl>
)}
</div>
{board.skinTypeCode === SKINT_TYPE_CODE_QNA && (
<div className="qna-box">
<div className="qna-question">
<p
className="qna-content"
dangerouslySetInnerHTML={{ __html: post.postsContent }}
/>
</div>
<div className="qna-answer">
<p
className="qna-content"
dangerouslySetInnerHTML={{ __html: post.postsAnswerContent }}
/>
</div>
</div>
)}
{board.skinTypeCode !== SKINT_TYPE_CODE_QNA && (
<div
className="content"
dangerouslySetInnerHTML={{ __html: post.postsContent }}
/>
)}
</div>
{board.commentUseAt && (
<div className="commentWrap">
<dl>
<dt>{t('comment')}</dt>
<dd>{commentCount}</dd>
</dl>
{user && (
<EditComments
ref={replyRef}
handleCancel={handleCommentCancel}
handleRegist={handleCommentRegist}
comment={
{
boardNo: post.boardNo,
postsNo: post.postsNo,
depthSeq: 0,
} as CommentSavePayload
}
/>
)}
{comments?.length > 0 ? (
<CommentsList
comments={comments}
handleRegist={handleCommentRegist}
handleDelete={handleCommentDelete}
/>
) : null}
{page + 1 >= totalPages ? null : (
<a href="#" onClick={handleCommentMore}>
{t('posts.see_more')}
</a>
)}
</div>
)}
{board.skinTypeCode === SKINT_TYPE_CODE_NORMAL && (
<div className="skip">
<dl>
<dt>{t('posts.prev_post')}</dt>
<dd>
{(!post.prevPosts[0] || post.prevPosts.length === 0) && (
<span>{t('posts.notexists.prev')}</span>
)}
{post.prevPosts[0] && (
<a
href="#"
onClick={event => {
event.preventDefault()
handleNearPostClick(post.prevPosts[0])
}}
>
{post.prevPosts[0].postsTitle}
</a>
)}
</dd>
</dl>
<dl className="next">
<dt>{t('posts.next_post')}</dt>
<dd>
{(!post.nextPosts[0] || post.nextPosts.length === 0) && (
<span>{t('posts.notexists.next')}</span>
)}
{post.nextPosts[0] && (
<a
href="#"
onClick={event => {
event.preventDefault()
handleNearPostClick(post.nextPosts[0])
}}
>
{post.nextPosts[0].postsTitle}
</a>
)}
</dd>
</dl>
</div>
)}
<BottomButtons handleButtons={bottomButtons} />
</div>
)
}
export const getServerSideProps: GetServerSideProps = async context => {
const boardNo = Number(context.query.board)
const postsNo = Number(context.query.id)
const { keywordType, keyword } = context.query
let board = {}
let post = {}
try {
if (postsNo !== -1) {
const result = await boardService.getPostById({
boardNo,
postsNo,
keywordType,
keyword,
} as PostsReqPayload)
if (result && result.data && result.data.board) {
board = (await result.data.board) as IBoard
post = (await result.data) as IPosts
}
} else {
const result = await boardService.getBoardById(boardNo)
if (result && result.data) {
board = (await result.data) as IBoard
}
}
} catch (error) {
console.error(`posts item query error ${error.message}`)
if (error.response?.data?.code === 'E003') {
return {
notFound: true,
}
}
}
return {
props: {
board,
post,
},
}
}
export default BoardView