Skip to content
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

[CHAT] 사이드바 UI 수정 및 채팅방 컴포넌트 생성 #137

Merged
merged 1 commit into from
Jun 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions front/src/components/Chat/ChatRoomSideBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { useCallback } from 'react'
import useChatRoomStore from '../../store/useChatRoomStore'
import { Box, styled } from '@mui/material';
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";

const ChatRoomSideBar = () => {
const setIsChatRoomShow = useChatRoomStore((state)=> state.setIsChatRoomShow);
const handleChatRoomClose = useCallback(()=>{
setIsChatRoomShow(false);
},[setIsChatRoomShow])


const DrawerHeader = styled("div")(({ theme }) => ({
display: "flex",
height: "8rem",
alignItems: "center",
padding: theme.spacing(0, 1),
...theme.mixins.toolbar,
justifyContent: "flex-start",
}));

return (
<DrawerHeader >
<ArrowForwardIosIcon
onClick={handleChatRoomClose}
sx={{color:"white"}}
className="cursor-pointer"
/>
<Box>
<div >다이렉트 메세지</div>
</Box>
</DrawerHeader>
)
}

export default ChatRoomSideBar
28 changes: 28 additions & 0 deletions front/src/components/Layout/DropDown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Box } from '@mui/material'
import React, { useMemo } from 'react'

interface DropDownProps{
dropDownItems : string[];
}

const DropDown: React.FC<DropDownProps> = ({dropDownItems}) => {

const dropDownList = useMemo(()=>{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useMemo와 useCallback 둘 다 사용하셔서 둘의 차이점 비교하는데 도움이 되었습니다! 혹시 처음부터 최적화를 미리 고려하셔서 사용하신건가요? 저는 아직 최적화를 고려해서 짜기 보다는 기능 먼저 되면 나중에 리펙토링하는 식으로 코드를 짜는편이라..허헣

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 최적화에 대해서 아직 많이 몰라서 ㅋㅋ... 좀 더 공부해야겠지만 메모이제이션된 값을 쓰면 좋을 것 같다고 개인적으로 판단했을 때 씁니다. 적절하게 사용했는지는 잘 모르겠네요 아래는 지피티 선생님에게 useMemo를 상태 업데이트가 빈번하게 나타나는 경우에 쓰는게 좋은지 여쭈어보았을 때 답입니다

상태 업데이트가 빈번하게 일어나는 경우 useMemo를 사용하는 것이 항상 최적의 선택은 아닙니다. useMemo는 특정 값이나 계산 결과를 메모이징하여 불필요한 재계산을 방지하기 위해 사용됩니다. 그러나 상태 업데이트가 빈번하게 발생하면 useMemo의 이점이 줄어들 수 있습니다. 왜냐하면 상태가 바뀔 때마다 useMemo의 의존성 배열이 변경되어 메모이제이션된 값을 재계산해야 하기 때문입니다.

상태 업데이트가 빈번한 경우의 고려 사항
컴포넌트 구조 최적화:

상태 업데이트가 빈번하다면, 컴포넌트를 더 작은 단위로 나누어 특정 부분만 업데이트되도록 하는 것이 좋습니다. 이를 통해 불필요한 전체 재렌더링을 피할 수 있습니다.
React.memo 사용:

자주 업데이트되지 않는 컴포넌트를 React.memo로 감싸서 불필요한 재렌더링을 방지할 수 있습니다. React.memo는 컴포넌트의 props가 변경되지 않으면 해당 컴포넌트를 재렌더링하지 않습니다.
useCallback 사용:

상태 업데이트로 인해 함수가 재생성되는 것을 방지하기 위해 useCallback을 사용할 수 있습니다. 이는 useMemo와 비슷하지만 함수의 메모이제이션에 특화되어 있습니다.
useMemo의 적절한 사용:

상태 업데이트가 빈번하더라도 계산 비용이 높은 작업이나 렌더링 비용이 높은 컴포넌트의 경우 useMemo를 사용하여 최적화할 수 있습니다. 그러나 이 경우, 의존성 배열을 신중하게 구성하여 불필요한 재계산을 최소화하는 것이 중요합니다.

return dropDownItems.map((item, index)=>{
return <li className='h-10 leading-10'>{item}</li>
})
},[dropDownItems])


return (
<Box className='absolute top-11 right-0 w-32 h-auto z-50 bg-dark text-pink shadow-md'>
<ul className='flex flex-col items-center'>
{
dropDownList
}
</ul>
</Box>
)
}

export default DropDown
82 changes: 46 additions & 36 deletions front/src/components/Layout/FriendsRightSideBar.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,69 @@
import {
Avatar,
Badge,
Box,
Divider,
List,
ListItem,
ListItemAvatar,
ListItemText,
styled,
TextField,
} from "@mui/material";
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
import { SideBarProps } from "../../types/layout";
import React from "react";
import MemberProfile from "../Profile/MemberProfile";
import React, { useState } from "react";
import DropDown from "../Layout/DropDown";
import MemberList from "./MemberList";
import { messageMemberList } from "../../utils/fakeData";

const FriendsRightSideBar: React.FC<SideBarProps> = ({ close }) => {
const DrawerHeader = styled("div")(({ theme }) => ({
display: "flex",
height: "8rem",
alignItems: "center",
padding: theme.spacing(0, 1),
...theme.mixins.toolbar,
justifyContent: "flex-start",
}));

const [openProfile, setOpenProfile] = React.useState(false);
const handleOpen = () => setOpenProfile(true);
const handleClose = () => setOpenProfile(false);
const [openDropDown, setOpenDropDown] = useState(false);
const toggleDropDown = () => setOpenDropDown(!openDropDown);
const dropDownItems = ['디엠', '그룹', '전체 메세지']

return (
<>
<DrawerHeader>
<ArrowForwardIosIcon onClick={close} />
<div>친구목록</div>
<DrawerHeader className="flex-col">
<Box className="w-full flex py-3 px-1">
<ArrowForwardIosIcon onClick={close} className="cursor-pointer"/>
<Box className="w-full flex justify-between">
<div className="pl-1">친구 목록</div>
<Box className="relative">
{ openDropDown?
<KeyboardArrowUpIcon
sx={{color: "purple"}}
className="cursor-pointer"
onClick={toggleDropDown}
/>:
<KeyboardArrowDownIcon className="cursor-pointer" onClick={toggleDropDown}/>
}
{ openDropDown &&
<DropDown dropDownItems={dropDownItems}/>
}
</Box>
</Box>
</Box>
<TextField
placeholder="친구 검색"
variant="filled"
InputProps={{
sx: {
'& .MuiFilledInput-input': {
padding: '15px 12px 8px 12px', // 내부 input 요소의 패딩
},
}
}}
sx={{paddingBottom: "1rem"}}
/>
</DrawerHeader>
<Divider />
<List>
<ListItem>
<ListItemAvatar onClick={handleOpen}>
<Badge
color="success"
variant="dot"
overlap="circular"
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
>
<Avatar
alt="user nickname"
src="https://images.unsplash.com/photo-1517849845537-4d257902454a?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
/>
</Badge>
</ListItemAvatar>
<ListItemText primary="민선님" />
</ListItem>
</List>
<MemberProfile
openModal={openProfile}
closeModal={handleClose}
></MemberProfile>
<MemberList memberList={messageMemberList}/>
</>
);
};
Expand Down
23 changes: 23 additions & 0 deletions front/src/components/Layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import SupervisedUserCircleIcon from "@mui/icons-material/SupervisedUserCircle";
import ChatBubbleIcon from "@mui/icons-material/ChatBubble";
import FriendsRightSideBar from "./FriendsRightSideBar";
import MessageRightSideBar from "./MessageRightSideBar";
import ChatRoomSideBar from "../Chat/ChatRoomSideBar";
import useChatRoomStore from "../../store/useChatRoomStore";

interface AppBarProps extends MuiAppBarProps {
open?: boolean;
Expand All @@ -14,6 +16,8 @@ const Header = () => {
const DRAWER_WIDTH = 240; // 오른쪽 사이드바 넓이
const [open, setOpen] = useState(false); // 사이드바 열고 닫힌 상태
const [openDrawer, setOpenDrawer] = useState<string | null>(null); // 메세지 또는 친구 사이드바 상태
const IsChatRoomShow = useChatRoomStore(state=>state.isChatRoomShow);


// 오른쪽 사이드바 오픈 핸들러
const handleDrawerOpen = (sidebar: string) => {
Expand Down Expand Up @@ -85,6 +89,25 @@ const Header = () => {
open={openDrawer === "message"}>
<MessageRightSideBar close={handleDrawerClose} />
</Drawer>


<Drawer
sx={{
width: DRAWER_WIDTH,
flexShrink: 0,
"& .MuiDrawer-paper": {
width: DRAWER_WIDTH,
backgroundColor: "#2A2F4F",
color: "white"
},
}}
variant="persistent"
anchor="right"
open={IsChatRoomShow}>
<ChatRoomSideBar />
</Drawer>


</header>
);
};
Expand Down
33 changes: 33 additions & 0 deletions front/src/components/Layout/MemberList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { List } from "@mui/material";
import MemberListItem from "./MemberListItem";
import MemberProfile from "../Profile/MemberProfile";
import React from "react";
import { Member } from "../../types/layout";

interface MemberListProps{
memberList: Member[]
}

// TODO : 오른쪽 사이드바 멤버 리스트
const MemberList : React.FC<MemberListProps> = ({memberList}) => {
const [openProfile, setOpenProfile] = React.useState(false);
const handleProfileOpen = () => setOpenProfile(true);
const handleProfileClose = () => setOpenProfile(false);

return (
<>
<List className="overflow-auto scrollbar-hide">
{memberList.map(member=>(
<MemberListItem member={member} handleProfileOpen = {handleProfileOpen}/>
))}
</List>
<MemberProfile
openModal={openProfile}
closeModal={handleProfileClose}
></MemberProfile>
</>

)
}

export default MemberList
53 changes: 53 additions & 0 deletions front/src/components/Layout/MemberListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Member } from "../../types/layout"
import {
Avatar,
Badge,
ListItem,
ListItemAvatar,
ListItemText,
} from "@mui/material";
import useChatRoomStore from "../../store/useChatRoomStore";
import { useCallback } from "react";
interface MemberListItemProps{
member: Member;
handleProfileOpen : () => void;
}

const MemberListItem : React.FC<MemberListItemProps>= ({member, handleProfileOpen}) => {

const setIsChatRoomShow = useChatRoomStore((state)=> state.setIsChatRoomShow);
const handleChatRoomOpen = useCallback(()=>{
setIsChatRoomShow(true)
},[setIsChatRoomShow])

return (
<div>
<ListItem sx={{'&:hover': {
backgroundColor: "#FDE2F3",
}
}}>
<ListItemAvatar onClick={handleProfileOpen}>
<Badge
color="success"
variant="dot"
overlap="circular"
sx={{cursor: "pointer"}}
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
>
<Avatar
alt="user nickname"
src={member.profilePath}
/>
</Badge>
</ListItemAvatar>
<ListItemText
primary={member.userName}
sx={{cursor: "pointer"}}
onClick={handleChatRoomOpen}
/>
</ListItem>
</div>
)
}

export default MemberListItem
Loading