✨ frontend add
This commit is contained in:
499
frontend/admin/src/pages/posts/[board]/edit/[id].tsx
Normal file
499
frontend/admin/src/pages/posts/[board]/edit/[id].tsx
Normal file
@@ -0,0 +1,499 @@
|
||||
import AttachList from '@components/AttachList'
|
||||
import { CustomButtons, IButtonProps } from '@components/Buttons'
|
||||
import CustomAlert from '@components/CustomAlert'
|
||||
import ValidationAlert from '@components/EditForm/ValidationAlert'
|
||||
import Editor from '@components/Editor'
|
||||
import { Upload, UploadType } from '@components/Upload'
|
||||
import Box from '@material-ui/core/Box'
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel'
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
|
||||
import Switch from '@material-ui/core/Switch'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import {
|
||||
BoardSavePayload,
|
||||
boardService,
|
||||
fileService,
|
||||
IAttachmentResponse,
|
||||
PostsSavePayload,
|
||||
postsService,
|
||||
SKINT_TYPE_CODE_FAQ,
|
||||
SKINT_TYPE_CODE_QNA,
|
||||
UploadInfoReqeust,
|
||||
} from '@service'
|
||||
import { detailButtonsSnackAtom, errorStateSelector } from '@stores'
|
||||
import { format } from '@utils'
|
||||
import { AxiosError } from 'axios'
|
||||
import { GetServerSideProps } from 'next'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useRouter } from 'next/router'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Controller, FormProvider, useForm } from 'react-hook-form'
|
||||
import { useSetRecoilState } from 'recoil'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
flexGrow: 1,
|
||||
marginTop: theme.spacing(1),
|
||||
'& .MuiOutlinedInput-input': {
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
},
|
||||
switchBox: {
|
||||
padding: theme.spacing(1, 0),
|
||||
},
|
||||
switch: {
|
||||
paddingTop: theme.spacing(1),
|
||||
paddingBottom: theme.spacing(1),
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(2),
|
||||
},
|
||||
buttonContainer: {
|
||||
display: 'flex',
|
||||
margin: theme.spacing(1),
|
||||
justifyContent: 'center',
|
||||
'& .MuiButton-root': {
|
||||
margin: theme.spacing(1),
|
||||
},
|
||||
},
|
||||
backdrop: {
|
||||
zIndex: theme.zIndex.drawer + 1,
|
||||
color: '#fff',
|
||||
},
|
||||
labelMultiline: {
|
||||
padding: theme.spacing(2),
|
||||
textAlign: 'center',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
upload: {
|
||||
padding: theme.spacing(2, 2, 0, 2),
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
interface IPostsFormInput {
|
||||
postsTitle: string
|
||||
noticeAt: boolean
|
||||
postsContent: string
|
||||
postsAnswerContent: string
|
||||
}
|
||||
|
||||
export interface IPostsItemsProps {
|
||||
boardNo: number
|
||||
postsNo: number
|
||||
board: BoardSavePayload | null
|
||||
initData: PostsSavePayload | null
|
||||
}
|
||||
|
||||
const PostsItem = ({ boardNo, postsNo, board, initData }: IPostsItemsProps) => {
|
||||
const classes = useStyles()
|
||||
const route = useRouter()
|
||||
const { t } = useTranslation()
|
||||
|
||||
// 버튼 component 상태 전이
|
||||
const setSuccessSnackBar = useSetRecoilState(detailButtonsSnackAtom)
|
||||
|
||||
// 상태관리 hook
|
||||
const setErrorState = useSetRecoilState(errorStateSelector)
|
||||
const uploadRef = useRef<UploadType>()
|
||||
|
||||
const [attachData, setAttachData] = useState<
|
||||
IAttachmentResponse[] | undefined
|
||||
>(undefined)
|
||||
|
||||
// alert
|
||||
const [customAlert, setCustomAlert] = useState<any>({
|
||||
open: false,
|
||||
message: '',
|
||||
handleAlert: () => setCustomAlert({ open: false }),
|
||||
})
|
||||
|
||||
// Editor
|
||||
const [postsContent, setPostsContent] = useState<string>(
|
||||
initData?.postsContent || '',
|
||||
)
|
||||
const [postsAnswerContent, setPostsAnswerContent] = useState<string>(
|
||||
initData?.postsAnswerContent || '',
|
||||
)
|
||||
|
||||
// form hook
|
||||
const methods = useForm<IPostsFormInput>({
|
||||
defaultValues: {
|
||||
postsTitle: initData?.postsTitle || '',
|
||||
noticeAt:
|
||||
typeof initData?.noticeAt !== 'undefined' ? initData?.noticeAt : false,
|
||||
},
|
||||
})
|
||||
const {
|
||||
formState: { errors },
|
||||
control,
|
||||
handleSubmit,
|
||||
} = methods
|
||||
|
||||
const successCallback = () => {
|
||||
setSuccessSnackBar('success')
|
||||
|
||||
route.back()
|
||||
}
|
||||
|
||||
const errorCallback = (error: AxiosError) => {
|
||||
setSuccessSnackBar('none')
|
||||
|
||||
setErrorState({
|
||||
error,
|
||||
})
|
||||
}
|
||||
|
||||
const getAttachments = useCallback(
|
||||
async (code: string) => {
|
||||
try {
|
||||
const result = await fileService.getAttachmentList(code)
|
||||
|
||||
if (result) {
|
||||
setAttachData(result.data)
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorState({
|
||||
error,
|
||||
})
|
||||
}
|
||||
},
|
||||
[setErrorState],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (initData.attachmentCode) {
|
||||
getAttachments(initData.attachmentCode)
|
||||
}
|
||||
}, [getAttachments, initData.attachmentCode])
|
||||
|
||||
// handleSubmit 저장
|
||||
const handleSave = async (formData: IPostsFormInput) => {
|
||||
setSuccessSnackBar('loading')
|
||||
let { attachmentCode } = initData
|
||||
try {
|
||||
const postsContentValue = board.editorUseAt
|
||||
? postsContent
|
||||
: formData.postsContent
|
||||
|
||||
if (!postsContentValue) {
|
||||
setCustomAlert({
|
||||
open: true,
|
||||
message: format(t('valid.required.format'), [
|
||||
t('posts.posts_content'),
|
||||
]),
|
||||
handleAlert: () => {
|
||||
setCustomAlert({ open: false })
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (board.uploadUseAt) {
|
||||
const isUpload = await uploadRef.current.isModified(attachData)
|
||||
|
||||
if (isUpload) {
|
||||
const info: UploadInfoReqeust = {
|
||||
entityName: 'posts',
|
||||
entityId: board.boardNo?.toString(),
|
||||
}
|
||||
|
||||
// 업로드 및 저장
|
||||
const result = await uploadRef.current.upload(info, attachData)
|
||||
if (result) {
|
||||
if (result !== 'no attachments' && result !== 'no update list') {
|
||||
attachmentCode = result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const data: PostsSavePayload = {
|
||||
boardNo,
|
||||
postsTitle: formData.postsTitle,
|
||||
noticeAt: formData.noticeAt,
|
||||
postsContent: postsContentValue,
|
||||
postsAnswerContent: board.editorUseAt
|
||||
? postsAnswerContent
|
||||
: formData.postsAnswerContent,
|
||||
attachmentCode,
|
||||
}
|
||||
|
||||
if (postsNo === -1) {
|
||||
await postsService.save({
|
||||
boardNo,
|
||||
callback: successCallback,
|
||||
errorCallback,
|
||||
data,
|
||||
})
|
||||
} else {
|
||||
await postsService.update({
|
||||
boardNo,
|
||||
postsNo,
|
||||
callback: successCallback,
|
||||
errorCallback,
|
||||
data,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorState({
|
||||
error,
|
||||
})
|
||||
|
||||
if (postsNo === -1) {
|
||||
uploadRef.current?.rollback(attachmentCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 저장 버튼
|
||||
const saveButton: IButtonProps = {
|
||||
label: t('label.button.save'),
|
||||
variant: 'contained',
|
||||
color: 'primary',
|
||||
confirmMessage: t('msg.confirm.save'),
|
||||
handleButton: handleSubmit(handleSave),
|
||||
}
|
||||
|
||||
// 이전 화면으로 이동
|
||||
const handlePrev = useCallback(() => {
|
||||
/* if (postsNo === -1) {
|
||||
route.push(
|
||||
{
|
||||
pathname: `/posts/${boardNo}`,
|
||||
query: {
|
||||
size: route.query.size,
|
||||
page: route.query.page,
|
||||
keywordType: route.query.keywordType,
|
||||
keyword: route.query.keyword,
|
||||
},
|
||||
},
|
||||
// `/posts/${boardNo}`,
|
||||
)
|
||||
} else {
|
||||
route.push(
|
||||
{
|
||||
pathname: `/posts/${boardNo}/view/${postsNo}`,
|
||||
query: {
|
||||
size: route.query.size,
|
||||
page: route.query.page,
|
||||
keywordType: route.query.keywordType,
|
||||
keyword: route.query.keyword,
|
||||
},
|
||||
},
|
||||
// `/posts/${boardNo}`,
|
||||
)
|
||||
} */
|
||||
route.back()
|
||||
}, [route])
|
||||
|
||||
// 이전 버튼
|
||||
const prevButton: IButtonProps = {
|
||||
label: t('label.button.prev'),
|
||||
variant: 'contained',
|
||||
handleButton: handlePrev,
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<FormProvider {...methods}>
|
||||
<form>
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12} sm={12}>
|
||||
<Box boxShadow={1}>
|
||||
<Controller
|
||||
name="postsTitle"
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
autoFocus
|
||||
label={t('posts.posts_title')}
|
||||
name="postsTitle"
|
||||
required
|
||||
inputProps={{ maxLength: 100 }}
|
||||
id="outlined-full-width"
|
||||
placeholder={format(t('msg.placeholder.format'), [
|
||||
t('posts.posts_title'),
|
||||
])}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
control={control}
|
||||
rules={{ required: true, maxLength: 100 }}
|
||||
/>
|
||||
{errors.postsTitle && (
|
||||
<ValidationAlert
|
||||
fieldError={errors.postsTitle}
|
||||
target={[100]}
|
||||
label={t('posts.posts_title')}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={12}>
|
||||
<Box boxShadow={1} className={classes.switchBox}>
|
||||
<FormControlLabel
|
||||
label={t('posts.notice_at')}
|
||||
labelPlacement="start"
|
||||
control={
|
||||
<Controller
|
||||
name="noticeAt"
|
||||
control={control}
|
||||
render={({ field: { onChange, ref, value } }) => (
|
||||
<Switch
|
||||
inputProps={{ 'aria-label': 'secondary checkbox' }}
|
||||
onChange={onChange}
|
||||
inputRef={ref}
|
||||
checked={value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={12}>
|
||||
{board.editorUseAt && (
|
||||
<Editor contents={postsContent} setContents={setPostsContent} />
|
||||
)}
|
||||
{!board.editorUseAt && (
|
||||
<Box boxShadow={1}>
|
||||
<Controller
|
||||
name="postsContent"
|
||||
control={control}
|
||||
rules={{ required: true }}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
label={t('posts.posts_content')}
|
||||
name="postsContent"
|
||||
multiline
|
||||
minRows={9.2}
|
||||
id="outlined-full-width"
|
||||
placeholder={format(t('msg.placeholder.format'), [
|
||||
t('posts.posts_content'),
|
||||
])}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.postsContent && (
|
||||
<ValidationAlert
|
||||
fieldError={errors.postsContent}
|
||||
label={t('posts.posts_content')}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Grid>
|
||||
{(board.skinTypeCode === SKINT_TYPE_CODE_FAQ ||
|
||||
board.skinTypeCode === SKINT_TYPE_CODE_QNA) && (
|
||||
<Grid item xs={12} sm={12}>
|
||||
{board.editorUseAt && (
|
||||
<Editor
|
||||
contents={postsAnswerContent}
|
||||
setContents={setPostsAnswerContent}
|
||||
/>
|
||||
)}
|
||||
{!board.editorUseAt && (
|
||||
<Box boxShadow={1}>
|
||||
<Controller
|
||||
name="postsAnswerContent"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
label={t('posts.posts_answer_content')}
|
||||
name="postsAnswerContent"
|
||||
multiline
|
||||
minRows={9.2}
|
||||
id="outlined-full-width"
|
||||
placeholder={format(t('msg.placeholder.format'), [
|
||||
t('posts.posts_answer_content'),
|
||||
])}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Grid>
|
||||
)}
|
||||
{board.uploadUseAt && (
|
||||
<Grid item xs={12} sm={12}>
|
||||
<Box boxShadow={1}>
|
||||
<Upload
|
||||
ref={uploadRef}
|
||||
multi
|
||||
uploadLimitCount={board.uploadLimitCount}
|
||||
uploadLimitSize={board.uploadLimitSize}
|
||||
attachmentCode={initData.attachmentCode}
|
||||
attachData={attachData}
|
||||
/>
|
||||
{attachData && (
|
||||
<AttachList data={attachData} setData={setAttachData} />
|
||||
)}
|
||||
</Box>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</form>
|
||||
</FormProvider>
|
||||
<CustomButtons buttons={[saveButton, prevButton]} />
|
||||
<CustomAlert
|
||||
contentText={customAlert.message}
|
||||
open={customAlert.open}
|
||||
handleAlert={() => setCustomAlert({ open: false })}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async ({ query }) => {
|
||||
const boardNo = Number(query.board)
|
||||
const postsNo = Number(query.id)
|
||||
|
||||
let board = {}
|
||||
let data = {}
|
||||
|
||||
try {
|
||||
if (postsNo !== -1) {
|
||||
const result = await postsService.get(boardNo, postsNo)
|
||||
if (result) {
|
||||
board = (await result.data.board) as BoardSavePayload
|
||||
data = (await result.data) as PostsSavePayload
|
||||
}
|
||||
} else {
|
||||
const result = await boardService.get(boardNo)
|
||||
if (result) {
|
||||
board = (await result.data) as BoardSavePayload
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`posts item query error ${error.message}`)
|
||||
if (error.response?.data?.code === 'E003') {
|
||||
return {
|
||||
notFound: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
boardNo,
|
||||
postsNo,
|
||||
board,
|
||||
initData: data,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default PostsItem
|
||||
Reference in New Issue
Block a user