-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor: 마이페이지 카테고리 SSR + RSC 적용 #317
base: develop
Are you sure you want to change the base?
Changes from 3 commits
014ca40
5e473a4
55ae966
5a00162
0dd1982
4cc50d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
'use client'; | ||
|
||
import styled from '@emotion/styled'; | ||
|
||
export const Container = styled.div` | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,127 +1,35 @@ | ||
import useGetCategoryList from '@/api/hooks/categoryHooks/useGetCategoryList'; | ||
import { Flex } from '@/components/common'; | ||
import { Button, Skeleton, styled, TableBody, TableCell, TableRow, Tooltip } from '@mui/material'; | ||
import useBooleanState from '@/hooks/utils/useBooleanState'; | ||
import { useState } from 'react'; | ||
import useUpdateCategory from '@/api/hooks/categoryHooks/useUpdateCategory'; | ||
import { CreateCategoryInfo } from '@/api/queryFn/categoryQueryFn'; | ||
import * as S from './Category.styled'; | ||
import CategoryModal from './CategoryModal'; | ||
|
||
interface StyledTableRowProps { | ||
disabled: boolean; | ||
} | ||
|
||
function CategoryTableBody() { | ||
const { value: isOpen, setTrue: open, setFalse: close } = useBooleanState(); | ||
const [id, setId] = useState<number | undefined>(undefined); | ||
|
||
const { categoryList, isLoading } = useGetCategoryList(); | ||
|
||
const { mutate: updateCategory } = useUpdateCategory(); | ||
|
||
const [data, setData] = useState<CreateCategoryInfo>(() => ({ | ||
title: '', | ||
isDisplayed: 0, | ||
color: '', | ||
})); | ||
|
||
const setCategoryData = (unit: keyof typeof data, value: number | string | boolean) => { | ||
setData((prevData) => ({ | ||
...prevData, | ||
[unit]: value, | ||
})); | ||
}; | ||
|
||
const handleUpdateCategory = (categoryId: number, createInfo: CreateCategoryInfo) => { | ||
updateCategory({ categoryId, createInfo }); | ||
}; | ||
|
||
const mutateFunction = (body: CreateCategoryInfo) => { | ||
handleUpdateCategory(id!, body); | ||
close(); | ||
}; | ||
|
||
const clickUpdateButton = (categoryId: number) => { | ||
setId(categoryId); | ||
open(); | ||
}; | ||
|
||
const handleCloseModal = () => { | ||
setId(undefined); | ||
close(); | ||
}; | ||
|
||
const convertBoolStateToString = (isDisplayed: number | null) => { | ||
return isDisplayed ? '포함' : '미포함'; | ||
}; | ||
|
||
const StyledTableRow = styled(TableRow)<StyledTableRowProps>(({ theme, disabled }) => ({ | ||
backgroundColor: disabled ? theme.palette.action.hover : 'inherit', | ||
cursor: disabled ? 'not-allowed' : 'default', | ||
opacity: disabled ? 0.5 : 1, | ||
'&:last-child td, &:last-child th': { | ||
border: 0, | ||
}, | ||
'& td': { | ||
color: disabled ? theme.palette.text.disabled : theme.palette.text.primary, | ||
}, | ||
})); | ||
|
||
return ( | ||
<TableBody> | ||
{isLoading ? ( | ||
<TableRow> | ||
<TableCell colSpan={4}> | ||
<Skeleton variant="rectangular" width="100%" height="100%" /> | ||
</TableCell> | ||
</TableRow> | ||
) : ( | ||
categoryList?.map((category) => { | ||
const isDisable = category.isDefault === 1; | ||
return ( | ||
<Tooltip key={category.id} title="기본 카테고리는 수정 불가능 합니다." disableHoverListener={!isDisable}> | ||
<StyledTableRow disabled={isDisable}> | ||
<TableCell component="th" scope="row" sx={{ maxWidth: '200px', overflowX: 'auto' }}> | ||
{category.title} | ||
</TableCell> | ||
<TableCell align="right"> | ||
<Flex $gap="20px"> | ||
<S.ColorState hexColor={category.color} /> | ||
<S.ColorCode>{category.color}</S.ColorCode> | ||
</Flex> | ||
</TableCell> | ||
<TableCell align="right">{convertBoolStateToString(category.isDisplayed)}</TableCell> | ||
<TableCell align="right"> | ||
<Button | ||
disabled={isDisable} | ||
onClick={() => { | ||
setCategoryData('title', category.title); | ||
setCategoryData('color', category.color || ''); | ||
setCategoryData('isDisplayed', category.isDisplayed || 0); | ||
clickUpdateButton(category.id); | ||
}} | ||
> | ||
수정 | ||
</Button> | ||
</TableCell> | ||
</StyledTableRow> | ||
</Tooltip> | ||
); | ||
}) | ||
)} | ||
|
||
<CategoryModal | ||
isOpen={isOpen} | ||
close={handleCloseModal} | ||
title="수정" | ||
mutateAction={mutateFunction} | ||
data={data} | ||
setData={setCategoryData} | ||
id={id} | ||
/> | ||
</TableBody> | ||
); | ||
import { Skeleton, TableBody, TableCell, TableRow } from '@mui/material'; | ||
|
||
import { getServerDataAboutCategory } from './server/categoryService'; | ||
import CategoryTableInfo from './CategoryTableInfo'; | ||
|
||
async function CategoryTableBody() { | ||
const categories = await getServerDataAboutCategory().catch((error) => { | ||
console.error(error); | ||
return null; | ||
}); | ||
|
||
let content; | ||
|
||
if (categories && 'error' in categories) { | ||
content = ( | ||
<TableRow> | ||
<TableCell colSpan={4}>에러가 났습니다잇..</TableCell> | ||
</TableRow> | ||
); | ||
} else if (categories === null) { | ||
content = ( | ||
<TableRow> | ||
<TableCell colSpan={4}> | ||
<Skeleton variant="rectangular" width="100%" height="100%" /> | ||
</TableCell> | ||
</TableRow> | ||
); | ||
} else if (Array.isArray(categories)) { | ||
content = <CategoryTableInfo categoryList={categories} />; | ||
} | ||
|
||
return <TableBody>{content}</TableBody>; | ||
} | ||
|
||
export default CategoryTableBody; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
'use client'; | ||
|
||
import { styled, TableRow, Button, TableCell } from '@mui/material'; | ||
import randomColor from 'randomcolor'; | ||
import { useState } from 'react'; | ||
import { Category, CreateCategoryInfo } from '@/api/queryFn/categoryQueryFn'; | ||
import useUpdateCategory from '@/api/hooks/categoryHooks/useUpdateCategory'; | ||
import useGetCategoryList from '@/api/hooks/categoryHooks/useGetCategoryList'; | ||
import CategoryModal from './CategoryModal'; | ||
|
||
interface CategoryTableInfoProps { | ||
categoryList: Category[]; | ||
} | ||
|
||
interface StyledTableRowProps { | ||
disabled: boolean; | ||
} | ||
|
||
const StyledTableRow = styled(TableRow)<StyledTableRowProps>(({ theme, disabled }) => ({ | ||
backgroundColor: disabled ? theme.palette.action.hover : 'inherit', | ||
cursor: disabled ? 'not-allowed' : 'default', | ||
opacity: disabled ? 0.5 : 1, | ||
|
||
'&:last-child td, &:last-child th': { | ||
border: 0, | ||
}, | ||
'& td': { | ||
color: disabled ? theme.palette.text.disabled : theme.palette.text.primary, | ||
}, | ||
})); | ||
|
||
function CategoryTableInfo({ categoryList }: CategoryTableInfoProps) { | ||
const { categoryList: data } = useGetCategoryList(categoryList); | ||
|
||
const [isEditing, setIsEditing] = useState(false); | ||
const [id, setId] = useState<number | undefined>(undefined); | ||
|
||
const [categoryData, setCategoryData] = useState<CreateCategoryInfo>({ | ||
title: '', | ||
color: '', | ||
isDisplayed: 0, | ||
}); | ||
|
||
const setCategoryDataForUnit = (unit: keyof typeof categoryData, value: number | string | boolean) => { | ||
setCategoryData((prevData) => ({ | ||
...prevData, | ||
[unit]: value, | ||
})); | ||
}; | ||
|
||
const handleEditClick = (category: Category) => { | ||
setId(category.id); | ||
setIsEditing(true); | ||
categoryData.title = category.title; | ||
categoryData.color = category.color; | ||
categoryData.isDisplayed = category.isDisplayed; | ||
}; | ||
|
||
const handleCloseModal = () => { | ||
setId(undefined); | ||
setIsEditing(false); | ||
}; | ||
|
||
const { mutate: updateCategory } = useUpdateCategory(); | ||
|
||
const handleUpdateCategory = (categoryId: number, createInfo: CreateCategoryInfo) => { | ||
updateCategory({ categoryId, createInfo }); | ||
}; | ||
|
||
const mutateFunction = (body: CreateCategoryInfo) => { | ||
handleUpdateCategory(id!, body); | ||
setIsEditing(false); | ||
}; | ||
|
||
if (!data) { | ||
return null; | ||
} | ||
|
||
return data.map((category) => { | ||
const isDisable = category.isDefault === 1; | ||
return ( | ||
<StyledTableRow key={category.id} disabled={isDisable}> | ||
<TableCell sx={{ width: '300px', maxWidth: '300px', overflow: 'auto', whiteSpace: 'nowrap' }}>{category.title}</TableCell> | ||
<TableCell align="right"> | ||
<div style={{ display: 'flex', gap: '20px' }}> | ||
<div style={{ backgroundColor: category.color || randomColor(), width: '20px', height: '20px' }} /> | ||
<span>{category.color}</span> | ||
</div> | ||
</TableCell> | ||
<TableCell align="right">{category.isDisplayed ? '포함' : '미포함'}</TableCell> | ||
<TableCell align="right"> | ||
<Button disabled={isDisable} onClick={() => handleEditClick(category)}> | ||
수정 | ||
</Button> | ||
</TableCell> | ||
<CategoryModal | ||
isOpen={isEditing} | ||
close={handleCloseModal} | ||
title="수정" | ||
mutateAction={mutateFunction} | ||
data={categoryData} | ||
setData={setCategoryDataForUnit} | ||
id={id} | ||
/> | ||
</StyledTableRow> | ||
); | ||
}); | ||
} | ||
|
||
export default CategoryTableInfo; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,3 @@ | ||
'use client'; | ||
|
||
import Paper from '@mui/material/Paper'; | ||
import { Table, TableContainer } from '@mui/material'; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { eq } from 'drizzle-orm'; | ||
import { getServerSession } from 'next-auth'; | ||
import { authOptions } from '@/lib/auth'; | ||
import { db } from '@/db'; | ||
import { categoriesTable } from '@/db/schema'; | ||
import { NextResponse } from 'next/server'; | ||
|
||
export async function getServerDataAboutCategory() { | ||
const session = await getServerSession(authOptions); | ||
if (!session) { | ||
return NextResponse.json({ error: 'unAuthorized Error' }, { status: 401 }); | ||
} | ||
|
||
const userId = session.user.id; | ||
|
||
try { | ||
const categories = await db.select().from(categoriesTable).where(eq(categoriesTable.userId, userId)).all(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
db.select({
id: category.id,
title: category.title,
}) 그러면 아래쪽에 나오는 필요한 필드만 선택해서 반환하는 코드를 따로 구현할 필요가 없을 것 같습니다~ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아하 그렇군요 !! 감사합니다! |
||
|
||
if (categories.length === 0) { | ||
throw new Error('카테고리가 존재하지 않습니다.'); | ||
} | ||
|
||
// 필요한 필드만 선택해서 반환 | ||
const filteredCategories = categories.map(({ createdAt, updatedAt, ...category }) => category); | ||
|
||
return filteredCategories; | ||
} catch (error) { | ||
throw new Error('Failed to fetch categories'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기서 error를 catch해서 뭔가 다른 로직을 넣을 것이 아니라면, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 바깥에서 외부 처리를 하는 것이 코드를 추가적으로 작성해야하는 거라 상대적으로 더 품이 드는 행위일 것이라 생각하는데 혹시 바깥쪽에서 예외처리를 해주었을 때 좋은 점이 무엇일까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @godhyzzang 이 코드의 경우에는 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아하 그런 의미였군요! 이해했습니다 감사합니다~ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 공통적으로 쓰이지 않는다면, 차라리 호출부에서 처리하는 것이 명확하게 보일 수 있을 것 같습니다! 의견 감사합니다! |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
'use client'; | ||
|
||
import styled from '@emotion/styled'; | ||
|
||
interface ContainerProps { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if-else문을 사용하는 것보다 아래와 같이 early return 하는 형태도 고려해보시면 좋을 것 같습니다~