diff --git a/src/frontend/src/components/Sidebar/MemberList/index.tsx b/src/frontend/src/components/Sidebar/MemberList/index.tsx deleted file mode 100644 index 5588e1ea..00000000 --- a/src/frontend/src/components/Sidebar/MemberList/index.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { useState } from 'react'; -import { SmallProfile } from '@/components/common/SmallProfile'; -import { ProfileDetail } from '@/components/common/ProfileDetail'; -import { MemberListFooter } from '@/components/Sidebar/MemberList/MemberListFooter'; - -import { SidebarType } from '@/types/enums/SidebarType'; -import { ProfileType } from '@/types/enums/ProfileType'; -import { UserRole } from '@/types/enums/UserRole'; -import { memberListTest } from '@/assets/data/memberListTest'; - -import { Container, UserList, ProfileWrapper } from './index.css'; -interface IMemberListProps { - sidebarType: SidebarType; -} - -export const MemberList = ({ sidebarType }: IMemberListProps) => { - const [activeProfile, setActiveProfile] = useState(null); - const handleProfileClick = (id: number) => { - setActiveProfile(prevId => (prevId === id ? null : id)); - }; - - return ( - - - {memberListTest - .sort((a, b) => a.role - b.role) - .map(member => ( - -
handleProfileClick(member.id)}> - -
- {activeProfile === member.id ? ( -
- -
- ) : ( - '' - )} -
- ))} -
- -
- ); -}; diff --git a/src/frontend/src/components/Sidebar/MemberList/MemberListFooter/index.tsx b/src/frontend/src/components/Sidebar/UserList/UserListFooter/index.tsx similarity index 82% rename from src/frontend/src/components/Sidebar/MemberList/MemberListFooter/index.tsx rename to src/frontend/src/components/Sidebar/UserList/UserListFooter/index.tsx index 7c9db400..7854edbb 100644 --- a/src/frontend/src/components/Sidebar/MemberList/MemberListFooter/index.tsx +++ b/src/frontend/src/components/Sidebar/UserList/UserListFooter/index.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { CommonInput } from '@/components/common/Input'; -import { MemberFooter, VoiceChatFooter, ActionButton, JoinButton } from '../index.css'; +import { UserFooter, VoiceChatFooter, ActionButton, JoinButton } from '../index.css'; import { SidebarType } from '@/types/enums/SidebarType'; @@ -10,23 +10,23 @@ import HeadphoneOn from '@/assets/img/HeadphoneOn.svg'; import MicrophoneOffRed from '@/assets/img/MicrophoneOffRed.svg'; import HeadphoneOffRed from '@/assets/img/HeadphoneOffRed.svg'; -interface IMemberListFooter { +interface IUserListFooter { sidebarType: SidebarType; } -export const MemberListFooter = (props: IMemberListFooter) => { +export const UserListFooter = (props: IUserListFooter) => { const [micOn, setMicOn] = useState(true); const [soundOn, setSoundOn] = useState(true); const handleMicrophone = () => setMicOn(!micOn); const handleSound = () => setSoundOn(!soundOn); - if (props.sidebarType === SidebarType.MEMBER) { + if (props.sidebarType === SidebarType.USERLIST) { return ( - + Add User - + ); } diff --git a/src/frontend/src/components/Sidebar/UserList/index.css.ts b/src/frontend/src/components/Sidebar/UserList/index.css.ts new file mode 100644 index 00000000..78934be7 --- /dev/null +++ b/src/frontend/src/components/Sidebar/UserList/index.css.ts @@ -0,0 +1,75 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; +`; + +export const UserListContainer = styled.div` + display: flex; + flex-direction: column; + width: 100%; + overflow-y: auto; +`; + +export const UserFooter = styled.div` + width: 100%; + display: flex; + align-items: center; + padding: 8px 0; + + border-top: 1px solid #d4d4d4; +`; + +export const VoiceChatFooter = styled.div` + width: 100%; + display: flex; + justify-content: space-around; + padding: 8px 0px; + border-top: 1px solid #d4d4d4; +`; + +export const ActionButton = styled.button` + border: none; + padding: 10px; + border-radius: 50%; + background-color: #f4f4f4; + cursor: pointer; +`; + +export const JoinButton = styled.button` + width: 194px; + border: none; + background-color: #ff9100; + padding: 10px 20px; + color: white; + font-weight: bold; + border-radius: 5px; + cursor: pointer; +`; + +export const ProfileWrapper = styled.div` + position: relative; + cursor: pointer; + + & .profile-detail { + position: absolute; + top: 0px; + right: 60%; + transform: translateX(-50%); + opacity: 0; + visibility: hidden; + transition: + opacity 0.3s ease, + visibility 0.3s ease; + z-index: 10; + } + + .profile-detail.active { + opacity: 1; + visibility: visible; + } +`; diff --git a/src/frontend/src/components/Sidebar/UserList/index.tsx b/src/frontend/src/components/Sidebar/UserList/index.tsx new file mode 100644 index 00000000..7011120a --- /dev/null +++ b/src/frontend/src/components/Sidebar/UserList/index.tsx @@ -0,0 +1,93 @@ +import { useState, useEffect, useRef } from 'react'; +import { SmallProfile } from '@/components/common/SmallProfile'; +import { ProfileDetail } from '@/components/common/ProfileDetail'; +import { UserListFooter } from '@/components/Sidebar/UserList/UserListFooter'; +import { RedBlackTree } from '@/hooks/utils/RedBlackTree'; + +import { SidebarType } from '@/types/enums/SidebarType'; +import { ProfileType } from '@/types/enums/ProfileType'; +import { UserRole } from '@/types/enums/UserRole'; + +import { memberListTest } from '@/assets/data/memberListTest'; +import { Container, UserListContainer, ProfileWrapper } from './index.css'; + +interface IUser { + id: number; + role: number; + nickname: string; + profileImg: string; +} + +const compareUsers = (a: IUser, b: IUser): number => { + if (a.role !== b.role) return a.role - b.role; + const nicknameCompare = a.nickname.localeCompare(b.nickname, 'ko'); + if (nicknameCompare !== 0) return nicknameCompare; + return a.id - b.id; +}; + +export const UserList = () => { + const treeRef = useRef | null>(null); + const [, setVersion] = useState(0); + + useEffect(() => { + treeRef.current = new RedBlackTree(compareUsers); + memberListTest.forEach(user => treeRef.current?.insert(user)); + setVersion(v => v + 1); + }, []); + + const addUser = (user: IUser) => { + if (!treeRef.current) return; + treeRef.current.insert(user); + setVersion(v => v + 1); + }; + + const getSortedUsers = (): IUser[] => { + return treeRef.current ? treeRef.current.inOrderTraversal() : []; + }; + const [activeProfile, setActiveProfile] = useState(null); + + const handleProfileClick = (id: number) => { + setActiveProfile(prevId => (prevId === id ? null : id)); + }; + + const handleAddUser = () => { + const newUser: IUser = { + id: Date.now(), + role: Math.floor(Math.random() * 3), + nickname: `User${Math.floor(Math.random() * 1000)}`, + profileImg: '', + }; + addUser(newUser); + }; + + const users = getSortedUsers(); + + return ( + + + {users.map(member => ( + +
handleProfileClick(member.id)}> + +
+ {activeProfile === member.id && ( + + )} +
+ ))} +
+ + +
+ ); +}; diff --git a/src/frontend/src/components/Sidebar/MemberList/index.css.ts b/src/frontend/src/components/Sidebar/VoiceChat/index.css.ts similarity index 98% rename from src/frontend/src/components/Sidebar/MemberList/index.css.ts rename to src/frontend/src/components/Sidebar/VoiceChat/index.css.ts index 739f9a8f..2e4d881e 100644 --- a/src/frontend/src/components/Sidebar/MemberList/index.css.ts +++ b/src/frontend/src/components/Sidebar/VoiceChat/index.css.ts @@ -12,6 +12,7 @@ export const UserList = styled.div` display: flex; flex-direction: column; width: 100%; + overflow-y: auto; `; export const MemberFooter = styled.div` diff --git a/src/frontend/src/components/Sidebar/VoiceChat/index.tsx b/src/frontend/src/components/Sidebar/VoiceChat/index.tsx new file mode 100644 index 00000000..6cfaaaf8 --- /dev/null +++ b/src/frontend/src/components/Sidebar/VoiceChat/index.tsx @@ -0,0 +1,57 @@ +import { useState } from 'react'; +import { SmallProfile } from '@/components/common/SmallProfile'; +import { ProfileDetail } from '@/components/common/ProfileDetail'; +import { UserListFooter } from '@/components/Sidebar/UserList/UserListFooter'; + +import { SidebarType } from '@/types/enums/SidebarType'; +import { ProfileType } from '@/types/enums/ProfileType'; +import { UserRole } from '@/types/enums/UserRole'; +import { memberListTest } from '@/assets/data/memberListTest'; + +import { Container, UserList, ProfileWrapper } from './index.css'; + +export const VoiceChat = () => { + const [activeProfile, setActiveProfile] = useState(null); + const handleProfileClick = (id: number) => { + setActiveProfile(prevId => (prevId === id ? null : id)); + }; + + const sortedUsers = memberListTest.sort((a, b) => { + if (a.role !== b.role) { + return a.role - b.role; + } + return a.nickname.localeCompare(b.nickname, 'ko'); + }); + + return ( + + + {sortedUsers.map(member => ( + +
handleProfileClick(member.id)}> + +
+ {activeProfile === member.id ? ( +
+ +
+ ) : ( + '' + )} +
+ ))} +
+ +
+ ); +}; diff --git a/src/frontend/src/components/Sidebar/index.tsx b/src/frontend/src/components/Sidebar/index.tsx index d63a89b4..c1c3f708 100644 --- a/src/frontend/src/components/Sidebar/index.tsx +++ b/src/frontend/src/components/Sidebar/index.tsx @@ -1,7 +1,8 @@ import { useState } from 'react'; import { ChatBox } from './Chating'; -import { MemberList } from './MemberList'; +import { UserList } from './UserList'; import { Playlist } from './Playlist'; +import { VoiceChat } from './VoiceChat'; import SidebarChat from '@/assets/img/SidebarChat.svg'; import SidebarPlaylist from '@/assets/img/SidebarPlaylist.svg'; import SidebarVoicechat from '@/assets/img/SidebarVoicechat.svg'; @@ -12,25 +13,22 @@ import { SidebarType } from '@/types/enums/SidebarType'; export const Sidebar = () => { const [interfaceType, setInterfaceType] = useState(SidebarType.CHAT); + const contentComponents = { + [SidebarType.CHAT]: ChatBox, + [SidebarType.PLAYLIST]: Playlist, + [SidebarType.VOICECHAT]: VoiceChat, + [SidebarType.USERLIST]: UserList, + }; + const renderContent = () => { - switch (interfaceType) { - case SidebarType.CHAT: - return ; - case SidebarType.PLAYLIST: - return ; - case SidebarType.VOICECHAT: - return ; - case SidebarType.MEMBER: - return ; - default: - return null; - } + const Component = contentComponents[interfaceType]; + return Component ? : null; }; return (