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,270 @@
import { DetailButtons } from '@components/Buttons'
import ValidationAlert from '@components/EditForm/ValidationAlert'
import Editor from '@components/Editor'
import { getCurrentDate } from '@libs/date'
import Box from '@material-ui/core/Box'
import Grid from '@material-ui/core/Grid'
import MenuItem from '@material-ui/core/MenuItem'
import Paper from '@material-ui/core/Paper'
import Select from '@material-ui/core/Select'
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
import Switch from '@material-ui/core/Switch'
import TextField from '@material-ui/core/TextField'
import { PolicySavePayload, policyService } from '@service'
import { detailButtonsSnackAtom, errorStateSelector } from '@stores'
import { AxiosError } from 'axios'
import { GetServerSideProps } from 'next'
import { useRouter } from 'next/router'
import React, { useState } from 'react'
import { Controller, FormProvider, useForm } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
import { useSetRecoilState } from 'recoil'
import { IPolicyType } from '.'
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
flexGrow: 1,
marginTop: theme.spacing(1),
'& .MuiOutlinedInput-input': {
padding: theme.spacing(2),
},
},
label: {
padding: theme.spacing(2),
textAlign: 'center',
backgroundColor: theme.palette.background.default,
},
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',
},
}),
)
interface IPolicyFormInput {
policyType: string
isUse: boolean
title: string
contents: string
}
export interface IPolicyItemsProps {
id: string
initData: PolicySavePayload | null
typeList: IPolicyType[]
}
const PolicyItem = ({ id, initData, typeList }: IPolicyItemsProps) => {
const classes = useStyles()
const route = useRouter()
const { t } = useTranslation()
//상태관리 hook
const setErrorState = useSetRecoilState(errorStateSelector)
//Editor contents
const [policyContents, setPolicyContents] = useState<string>(
initData?.contents || '',
)
//form hook
const methods = useForm<IPolicyFormInput>({
defaultValues: {
policyType: initData?.type || 'TOS',
isUse: typeof initData?.isUse !== 'undefined' ? initData?.isUse : true,
title: initData?.title,
},
})
const {
formState: { errors },
control,
handleSubmit,
} = methods
// <목록, 저장> 버튼 component 상태 전이
const setSuccessSnackBar = useSetRecoilState(detailButtonsSnackAtom)
const successCallback = () => {
setSuccessSnackBar('success')
route.back()
}
const errorCallback = (error: AxiosError) => {
setSuccessSnackBar('none')
setErrorState({
error,
})
}
//onsubmit 저장
const onSubmit = async (formData: IPolicyFormInput) => {
setSuccessSnackBar('loading')
const saved: PolicySavePayload = {
title: formData.title,
isUse: formData.isUse,
type: formData.policyType,
regDate: id === '-1' ? getCurrentDate() : initData.regDate,
contents: policyContents,
}
if (id === '-1') {
policyService.save({
callback: successCallback,
errorCallback,
data: saved,
})
} else {
policyService.update({
id,
callback: successCallback,
errorCallback,
data: saved,
})
}
}
return (
<div className={classes.root}>
<FormProvider {...methods}>
<form>
<Grid container spacing={1}>
<Grid item xs={12} sm={2}>
<Paper className={classes.label}>{t('common.type')}</Paper>
</Grid>
<Grid item xs={12} sm={4}>
<Controller
name="policyType"
render={({ field }) => (
<Select variant="outlined" fullWidth {...field}>
{typeList?.map(option => (
<MenuItem key={option.codeId} value={option.codeId}>
{option.codeName}
</MenuItem>
))}
</Select>
)}
control={control}
defaultValue={initData?.type || 'TOS'}
/>
</Grid>
<Grid item xs={12} sm={2}>
<Paper className={classes.label}>{t('common.use_at')}</Paper>
</Grid>
<Grid item xs={12} sm={4}>
<Paper className={classes.switch}>
<Controller
name="isUse"
render={({ field: { onChange, ref, value } }) => (
<Switch
inputProps={{ 'aria-label': 'secondary checkbox' }}
onChange={onChange}
inputRef={ref}
checked={value}
/>
)}
control={control}
/>
</Paper>
</Grid>
<Grid item xs={12} sm={2}>
<Paper className={classes.label}>{t('policy.title')}</Paper>
</Grid>
<Grid item xs={12} sm={10}>
<Box boxShadow={1}>
<Controller
name="title"
render={({ field, fieldState }) => (
<TextField
id="outlined-full-width"
placeholder={`${t('policy.title')} ${t(
'msg.placeholder',
)}`}
fullWidth
variant="outlined"
error={!!fieldState.error}
{...field}
/>
)}
control={control}
rules={{ required: true }}
/>
{errors.title && (
<ValidationAlert
fieldError={errors.title}
label={t('policy.title')}
/>
)}
</Box>
</Grid>
</Grid>
<Editor contents={policyContents} setContents={setPolicyContents} />
</form>
</FormProvider>
<DetailButtons
handleList={() => {
route.push('/policy')
}}
handleSave={handleSubmit(onSubmit)}
/>
</div>
)
}
export const getServerSideProps: GetServerSideProps = async ({
req,
res,
query,
}) => {
const { id } = query
let data = {}
let typeList = []
try {
const typeResult = await policyService.getTypeList()
if (typeResult) {
typeList = (await typeResult.data) as IPolicyType[]
}
if (id !== '-1') {
const result = await policyService.getOne(id as string)
if (result) {
data = (await result.data) as PolicySavePayload
}
}
} catch (error) {
console.error(`policy item query error ${error.message}`)
if (error.response?.data?.code === 'E003') {
return {
notFound: true,
}
}
}
return {
props: {
id,
initData: data,
typeList,
},
}
}
export default PolicyItem

View File

@@ -0,0 +1,294 @@
import { GridButtons } from '@components/Buttons'
import Search from '@components/Search'
import CustomDataGrid from '@components/Table/CustomDataGrid'
import { GRID_PAGE_SIZE } from '@constants'
import usePage from '@hooks/usePage'
import useSearchTypes from '@hooks/useSearchType'
// 내부 컴포넌트 및 custom hook, etc...
import { convertStringToDateFormat } from '@libs/date'
// material-ui deps
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
import Switch from '@material-ui/core/Switch'
import {
GridCellParams,
GridColDef,
GridValueFormatterParams,
GridValueGetterParams,
} from '@material-ui/data-grid'
//api
import { policyService } from '@service'
import { conditionAtom, errorStateSelector } from '@stores'
import { Page, rownum } from '@utils'
import { AxiosError } from 'axios'
import { GetServerSideProps, NextPage } from 'next'
import { TFunction, useTranslation } from 'next-i18next'
import { useRouter } from 'next/router'
import React, { useCallback, useMemo } from 'react'
// 상태관리 recoil
import { useRecoilValue, useSetRecoilState } from 'recoil'
// material-ui style
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
flexGrow: 1,
'& .MuiOutlinedInput-input': {
padding: theme.spacing(1),
},
},
}),
)
//그리드 컬럼 정의
type ColumnsType = (
data: Page,
typeList: IPolicyType[],
deletePolicy: (id: string) => void,
updatePolicy: (id: string) => void,
toggleIsUse: (event: React.ChangeEvent<HTMLInputElement>, id: string) => void,
t?: TFunction,
) => GridColDef[]
const getColumns: ColumnsType = (
data,
typeList,
deletePolicy,
updatePolicy,
toggleIsUse,
t,
) => {
return [
{
field: 'rownum',
headerName: t('common.no'),
headerAlign: 'center',
align: 'center',
sortable: false,
valueGetter: (params: GridValueGetterParams) =>
rownum(data, params.api.getRowIndex(params.id), 'desc'),
},
{
field: 'type',
headerName: t('common.type'),
headerAlign: 'center',
align: 'center',
width: 150,
sortable: false,
valueGetter: (params: GridValueGetterParams) => {
const type = typeList?.find(item => item.codeId === params.value)
return type?.codeName || ''
},
},
{
field: 'title',
headerName: t('policy.title'),
headerAlign: 'center',
width: 200,
sortable: false,
},
{
field: 'isUse',
headerName: t('common.use_at'),
headerAlign: 'center',
align: 'center',
width: 120,
sortable: false,
renderCell: (params: GridCellParams) => (
<Switch
checked={Boolean(params.value)}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
toggleIsUse(event, params.row.id)
}
/>
),
},
{
field: 'regDate',
headerName: t('common.created_datetime'),
headerAlign: 'center',
align: 'center',
width: 120,
sortable: false,
valueFormatter: (params: GridValueFormatterParams) => {
return convertStringToDateFormat(
params.value as string,
'yyyy-MM-dd HH:mm:ss',
)
},
},
{
field: 'id',
headerName: t('common.manage'),
headerAlign: 'center',
align: 'center',
width: 200,
sortable: false,
renderCell: (params: GridCellParams) => (
<GridButtons
id={params.value as string}
handleDelete={deletePolicy}
handleUpdate={updatePolicy}
/>
),
},
]
}
const conditionKey = 'policy'
export interface IPolicyType {
codeId: string
codeName: string
sortSeq: number
}
export interface IPolicyProps {
typeList: IPolicyType[]
}
// 실제 render되는 컴포넌트
const Policy: NextPage<IPolicyProps> = ({ typeList }) => {
// props 및 전역변수
// const { id } = props
const classes = useStyles()
const route = useRouter()
const { t } = useTranslation()
/**
* 상태관리 필요한 훅
*/
//조회조건 상태관리
const keywordState = useRecoilValue(conditionAtom(conditionKey))
const setErrorState = useSetRecoilState(errorStateSelector)
//현 페이지내 필요한 hook
const { page, setPageValue } = usePage(conditionKey)
//목록 데이터 조회 및 관리
const { data, mutate } = policyService.search({
keywordType: keywordState?.keywordType || 'title',
keyword: keywordState?.keyword || '',
size: GRID_PAGE_SIZE,
page,
})
//조회조건 select items
const searchTypes = useSearchTypes([
{
key: 'title',
label: t('policy.title'),
},
{
key: 'contents',
label: t('comment.comment_content'),
},
])
/**
* 비지니스 로직
*/
//에러 callback
const errorCallback = useCallback((error: AxiosError) => {
setErrorState({
error,
})
}, [])
//삭제
const deletePolicy = useCallback((id: string) => {
policyService.delete({
callback: mutate,
errorCallback,
id,
})
}, [])
//수정 시 상세 화면 이동
const updatePolicy = useCallback((id: string) => {
route.push(`/policy/${id}`)
}, [])
//사용여부 toggle 시 바로 update
const toggleIsUse = useCallback(
async (event: React.ChangeEvent<HTMLInputElement>, id: string) => {
policyService.updateUse({
callback: mutate,
errorCallback,
id,
isUse: event.target.checked,
})
},
[page],
)
// 목록컬럼 재정의 > 컬럼에 비지니스 로직이 필요한 경우
const columns = useMemo(() => {
return getColumns(
data,
typeList,
deletePolicy,
updatePolicy,
toggleIsUse,
t,
)
}, [data])
//목록 조회
const handleSearch = () => {
if (page === 0) {
mutate()
} else {
setPageValue(0)
}
}
//datagrid page change event
const handlePageChange = (page: number, details?: any) => {
setPageValue(page)
}
return (
<div className={classes.root}>
<Search
keywordTypeItems={searchTypes}
handleSearch={handleSearch}
handleRegister={() => {
route.push('policy/-1')
}}
conditionKey={conditionKey}
/>
<CustomDataGrid
classes={classes}
rows={data?.content}
columns={columns}
rowCount={data?.totalElements}
paginationMode="server"
pageSize={GRID_PAGE_SIZE}
page={page}
onPageChange={handlePageChange}
/>
</div>
)
}
export const getServerSideProps: GetServerSideProps = async context => {
let typeList: IPolicyType[] = []
try {
const result = await policyService.getTypeList()
if (result) {
typeList = result.data
}
} catch (error) {
console.error(`policy list getServerSideProps error ${error.message}`)
}
return {
props: {
typeList,
},
}
}
export default Policy