From e337b261722e56249b628fa591ddf49fe398c1c1 Mon Sep 17 00:00:00 2001 From: jeewonMoon Date: Mon, 22 Jul 2024 23:50:42 +0900 Subject: [PATCH 1/7] =?UTF-8?q?rename:=20header=20=ED=8F=B4=EB=8D=94?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Header.tsx -> Header으로 변경했습니다. --- src/components/{Header.tsx => Header}/TImer.tsx | 0 src/components/{Header.tsx => Header}/TopMenu.tsx | 0 src/components/{Header.tsx => Header}/TopNav.tsx | 0 src/components/{Header.tsx => Header}/index.tsx | 0 src/pages/index/Home.tsx | 4 +++- 5 files changed, 3 insertions(+), 1 deletion(-) rename src/components/{Header.tsx => Header}/TImer.tsx (100%) rename src/components/{Header.tsx => Header}/TopMenu.tsx (100%) rename src/components/{Header.tsx => Header}/TopNav.tsx (100%) rename src/components/{Header.tsx => Header}/index.tsx (100%) diff --git a/src/components/Header.tsx/TImer.tsx b/src/components/Header/TImer.tsx similarity index 100% rename from src/components/Header.tsx/TImer.tsx rename to src/components/Header/TImer.tsx diff --git a/src/components/Header.tsx/TopMenu.tsx b/src/components/Header/TopMenu.tsx similarity index 100% rename from src/components/Header.tsx/TopMenu.tsx rename to src/components/Header/TopMenu.tsx diff --git a/src/components/Header.tsx/TopNav.tsx b/src/components/Header/TopNav.tsx similarity index 100% rename from src/components/Header.tsx/TopNav.tsx rename to src/components/Header/TopNav.tsx diff --git a/src/components/Header.tsx/index.tsx b/src/components/Header/index.tsx similarity index 100% rename from src/components/Header.tsx/index.tsx rename to src/components/Header/index.tsx diff --git a/src/pages/index/Home.tsx b/src/pages/index/Home.tsx index ea7d540..33cb890 100644 --- a/src/pages/index/Home.tsx +++ b/src/pages/index/Home.tsx @@ -1,6 +1,7 @@ import styled from 'styled-components'; import Menubar from '@components/Menubar'; -import Header from '@components/Header.tsx'; +import Header from '@components/Header'; +import LectureList from '@components/LectureList'; function Home() { return ( @@ -9,6 +10,7 @@ function Home() { 강의시간표/수업계획서조회 + ); From 8c28101c33fde3b8305c838793fd52a004d0e308 Mon Sep 17 00:00:00 2001 From: jeewonMoon Date: Tue, 23 Jul 2024 01:36:09 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=EC=83=81=EB=8B=A8=20=ED=83=AD=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상단에 탭 부분을 구현했습니다. - 아직 사이드 바와 연동되어 있지 않습니다. 탭 닫기 와 전체 닫기 버튼은 작동하지만 다시 탭을 열 수는 없습니다. - 테마에 선언한 텍스트 스타일의 이름을 변경했습니다. (tap->tab) --- src/assets/img/btn_gnb_cu.png | Bin 0 -> 989 bytes src/components/Menubar/Menu.tsx | 4 +- src/components/TabMenu/Tab.tsx | 78 +++++++++++++++++++++++++++++++ src/components/TabMenu/index.tsx | 75 +++++++++++++++++++++++++++++ src/styles/theme/Theme.ts | 4 +- 5 files changed, 157 insertions(+), 4 deletions(-) create mode 100644 src/assets/img/btn_gnb_cu.png create mode 100644 src/components/TabMenu/Tab.tsx create mode 100644 src/components/TabMenu/index.tsx diff --git a/src/assets/img/btn_gnb_cu.png b/src/assets/img/btn_gnb_cu.png new file mode 100644 index 0000000000000000000000000000000000000000..9caea972df5eebc6818e9cf1cdccc292c9ec1a7d GIT binary patch literal 989 zcmaJ=J#W)M7`7S#rBbDKg9xOPJFpOcrOwASrV4gKE!9<&SZX8~>e$!DYVC9F3vs$2 z!GuHw3!)tuSrDoi7?2pK1Pg1&POw0ckbr-{IZaXqf~E6)=y{&^dEa~QVQuBs^i+C^ zVVG%sNvqR&mX4Xp3Hra+NKMk|GO0941Fw^=6(XkW;1&Y9XKkQ5vYg)bCsbmX6mZQZ zX&TGW#vW%S7%uh#%4V3-VjNiZCL*APHe6p}f4+Oq0@qR4RoM`XKt*kLX(vRDofXsG z*|dueyLbbXVn_u%L@W?{TYdy%h3)G?x=xOH7W5%xQ(;d{HH{ijafpD-2^m|+2_PqO zVs<{C&tC_UAPT%7@uHLw^DrwxK>&k~rO`rX9oDt#AQs&zY@3h(@_e`3<+@o8ha0?D zEEW?DNy<<}ChGabiZgyRH&oD2WQT4*TUkq{MC$0w?>~hjvlk$~jw0L%ZF+I*b4jQGsv)un4o!@=hE=S!F~LzA z2S9DeVAb^<+>QEZ!+^RU5zDuct|=_#bFS;as+KFLvX(8Yd07-I6I1j;O5%>KV=IxTmS$7 literal 0 HcmV?d00001 diff --git a/src/components/Menubar/Menu.tsx b/src/components/Menubar/Menu.tsx index 93002d0..d29fe77 100644 --- a/src/components/Menubar/Menu.tsx +++ b/src/components/Menubar/Menu.tsx @@ -11,9 +11,9 @@ interface ItemProps { } const menuItems: ItemProps[] = [ - {id: 0, name: '수강신청', type: 'study'}, + {id: 0, name: '강의시간표 조회', type: 'view'}, {id: 1, name: '관심과목 담기', type: 'study'}, - {id: 2, name: '강의시간표 조회', type: 'view'}, + {id: 2, name: '수강신청', type: 'study'}, ]; function Menu() { diff --git a/src/components/TabMenu/Tab.tsx b/src/components/TabMenu/Tab.tsx new file mode 100644 index 0000000..b02636e --- /dev/null +++ b/src/components/TabMenu/Tab.tsx @@ -0,0 +1,78 @@ +import {useState} from 'react'; +import styled from 'styled-components'; +import close from '@assets/img/tab_close.png'; + +interface TabProps { + id: number; + label: string; + isActive: boolean; + onClick: (id: number) => void; +} + +function Tab({id, label, isActive, onClick}: TabProps) { + const [isOpen, setIsOpen] = useState(true); + + const handleClose = (e: React.MouseEvent) => { + e.stopPropagation(); + + if (id > 0) { + onClick(id - 1); + } else { + onClick(1); + } + + setIsOpen(false); + }; + + return ( + <> + {isOpen && ( + onClick(id)} isactive={isActive}> +

{label}

+ +
+ )} + + ); +} + +const TabContainer = styled.a<{isactive: boolean}>` + ${props => + props.isactive + ? props.theme.texts.tabTitleFocus + : props.theme.texts.tabTitle}; + background-color: ${props => + props.isactive ? props.theme.colors.white : 'transparent'}; + width: calc(99% / 8); + height: 100%; + border-top: 0.3rem solid + ${props => (props.isactive ? props.theme.colors.primary : 'none')}; + border-right: 1px solid #ccc; + border-radius: 0; + padding: 0 1rem; + display: flex; + align-items: center; + text-align: center; + cursor: pointer; + + > p { + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + word-break: break-all; + margin-right: 1.5rem; + } +`; + +const CloseBtn = styled.button<{isactive: boolean}>` + z-index: 5; + width: 1rem; + height: 100%; + background-image: url(${close}); + background-repeat: no-repeat; + background-position-y: center; + filter: ${props => (props.isactive ? 'grayscale(0)' : 'grayscale(100%)')}; +`; + +export default Tab; diff --git a/src/components/TabMenu/index.tsx b/src/components/TabMenu/index.tsx new file mode 100644 index 0000000..95e605c --- /dev/null +++ b/src/components/TabMenu/index.tsx @@ -0,0 +1,75 @@ +import {useState} from 'react'; +import styled from 'styled-components'; +import Tab from './Tab'; +import closeAll from '@assets/img/tab_close_all.png'; +import up from '@assets/img/btn_gnb_cu.png'; + +function TabMenu() { + const [focused, setFocused] = useState(0); + const [close, setClose] = useState(false); + + const handleClick = (id: number) => { + console.log('click ' + id); + + setFocused(id); + }; + + return ( + + + {!close && ( + <> + + + + )} + + + setClose(true)} /> + + + + ); +} + +const TabMenuContainer = styled.div` + width: 100%; + height: 4rem; + background-color: ${props => props.theme.colors.neutral6}; + display: flex; + justify-content: space-between; + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; +`; + +const TabWrap = styled.div` + width: 100%; + display: flex; +`; + +const ButtonWrap = styled.div` + display: flex; + align-items: center; + margin-right: 1rem; + column-gap: 0.8rem; +`; + +const CloseAllBtn = styled.button` + width: 1.2rem; + height: 100%; + background-image: url(${closeAll}); + background-repeat: no-repeat; + background-position-y: center; +`; + +export default TabMenu; diff --git a/src/styles/theme/Theme.ts b/src/styles/theme/Theme.ts index 10a72d4..bcec4aa 100644 --- a/src/styles/theme/Theme.ts +++ b/src/styles/theme/Theme.ts @@ -40,12 +40,12 @@ const texts = { fontWeight: '600', color: '#FAEBEE', }, - tapTitle: { + tabTitle: { fontSize: '1.3rem', fontWeight: '400', color: '#444', }, - tapTitleFocus: { + tabTitleFocus: { fontSize: '1.3rem', fontWeight: '600', color: '#a31432', From 30950e3349982b38214b0abd294ddf25f06e3e9e Mon Sep 17 00:00:00 2001 From: jeewonMoon Date: Thu, 25 Jul 2024 02:16:26 +0900 Subject: [PATCH 3/7] =?UTF-8?q?rename:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/img/red_edge.png | Bin 957 -> 0 bytes src/assets/img/search.png | Bin 1152 -> 1120 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/assets/img/red_edge.png diff --git a/src/assets/img/red_edge.png b/src/assets/img/red_edge.png deleted file mode 100644 index bee4c484a735f5e4a158b900dc8cfeab55a9ae59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 957 zcmaJ=O^ee&7|tR{cVW?^dK$9}BG_coY?~%DuHB|}15GJyp*@-=(>8RI8I!3^>qYkD z;>n{Ye}E!*78GK-sm{wrCg(H=!Sdr-w9p-BEu*%K#dbPBqLgT&IGs*~sVv}dC`ziTCK|b1mLamy zETDFr4WgY?!9bA{dW3p70EwdA#}lga%;|XwK3T~I(XvdeU}9_&Q4(O1QVO)J|3iI$ zg^p+oUHJW{aMYO*B(_k5C!xbGZm^StB3dOxHpO8F!6ISJTkl0#tGDF3>%A1$az$oFOs@5>Yfg_??CYG6&EsqJAG>dF-+OL! lpWHlo&Ar;KZ#2xSTn2Jq&Ne?$GI*LiD6`ry-W)tQ{sRzVDC7VD diff --git a/src/assets/img/search.png b/src/assets/img/search.png index 78880d70da497b974f87835278f18507baced5fd..ed1c40478afaa917ad13715ec0dd33d0fe2fd93c 100644 GIT binary patch delta 457 zcmZqRe88dD8Q|y6%O%Cdz`(%k>ERLtqJfx?gAGW2sh@UfqoOiXjGL>0i-m!?qobv( zi=m;bqotXVk%hUjg{g^|i;01So039BZh^0_l}mndX>Mv>iKmOL5>P}hB{Rhes@DmZ z-pLD?3>2V>EO09Nz$61xWQkLe4zp8zZQes8pwq8;x;TbZ+)DcK|Gz!6$Z5upCJJ>w z=m95II-j6HE95SZ_Dp5bC(O;7i$xAM7d8jz>~f91$x? zclemK;!jG7um6IkT^1%Uou1A=a73c>(w5!SvywKN(VcXG(B1_dC2{JFFVdQ&MBb@0B!o3g8%>k delta 490 zcmaFB(ZH$L8Q|y6%O%Cdz`(%k>ERLtq~(BEh=UDC`h3c}xlvJ>DaOgv*wM(?!qm~s zz`)SZ)zQ+)#mUmy+|0<(*vQ=3&_qe0BDcWT*UBY7ximL5uf)^ERtYGgmy(%c1=VYY zOYh_bOa=;2MaDQ4ePEJ-DKf#SNQc>}{%k|vX`s`;dAc};RNPAX^Z);T5oYGY#%#Zs zG}YP|nU+N8uphbH@Iz7HfBPllg^!jn2Lv!1GBdv6Ix_!%GW-06pLkRfG?R3uF+buD z@ZQF}QB`2G$XMi-p8zHsH@sInlJQ(mtZtenZ{wchzuP}dc| zn3hk5JSvwDCCxaW&h)W(h3ol+e Date: Thu, 25 Jul 2024 02:19:10 +0900 Subject: [PATCH 4/7] =?UTF-8?q?rename:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/img/red_edge.png | Bin 0 -> 957 bytes src/assets/img/tag.png | Bin 0 -> 957 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/assets/img/red_edge.png create mode 100644 src/assets/img/tag.png diff --git a/src/assets/img/red_edge.png b/src/assets/img/red_edge.png new file mode 100644 index 0000000000000000000000000000000000000000..bee4c484a735f5e4a158b900dc8cfeab55a9ae59 GIT binary patch literal 957 zcmaJ=O^ee&7|tR{cVW?^dK$9}BG_coY?~%DuHB|}15GJyp*@-=(>8RI8I!3^>qYkD z;>n{Ye}E!*78GK-sm{wrCg(H=!Sdr-w9p-BEu*%K#dbPBqLgT&IGs*~sVv}dC`ziTCK|b1mLamy zETDFr4WgY?!9bA{dW3p70EwdA#}lga%;|XwK3T~I(XvdeU}9_&Q4(O1QVO)J|3iI$ zg^p+oUHJW{aMYO*B(_k5C!xbGZm^StB3dOxHpO8F!6ISJTkl0#tGDF3>%A1$az$oFOs@5>Yfg_??CYG6&EsqJAG>dF-+OL! lpWHlo&Ar;KZ#2xSTn2Jq&Ne?$GI*LiD6`ry-W)tQ{sRzVDC7VD literal 0 HcmV?d00001 diff --git a/src/assets/img/tag.png b/src/assets/img/tag.png new file mode 100644 index 0000000000000000000000000000000000000000..bee4c484a735f5e4a158b900dc8cfeab55a9ae59 GIT binary patch literal 957 zcmaJ=O^ee&7|tR{cVW?^dK$9}BG_coY?~%DuHB|}15GJyp*@-=(>8RI8I!3^>qYkD z;>n{Ye}E!*78GK-sm{wrCg(H=!Sdr-w9p-BEu*%K#dbPBqLgT&IGs*~sVv}dC`ziTCK|b1mLamy zETDFr4WgY?!9bA{dW3p70EwdA#}lga%;|XwK3T~I(XvdeU}9_&Q4(O1QVO)J|3iI$ zg^p+oUHJW{aMYO*B(_k5C!xbGZm^StB3dOxHpO8F!6ISJTkl0#tGDF3>%A1$az$oFOs@5>Yfg_??CYG6&EsqJAG>dF-+OL! lpWHlo&Ar;KZ#2xSTn2Jq&Ne?$GI*LiD6`ry-W)tQ{sRzVDC7VD literal 0 HcmV?d00001 From 690f8ab297db938ba67803780384c9dbbb923e83 Mon Sep 17 00:00:00 2001 From: jeewonMoon Date: Thu, 25 Jul 2024 02:19:56 +0900 Subject: [PATCH 5/7] =?UTF-8?q?Revert=20"rename:=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=ED=8C=8C=EC=9D=BC=EB=AA=85=20=EB=B3=80=EA=B2=BD"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit c9bd819a2c8feee97fcde2ae1b8580ddff615756. --- src/assets/img/red_edge.png | Bin 957 -> 0 bytes src/assets/img/tag.png | Bin 957 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/assets/img/red_edge.png delete mode 100644 src/assets/img/tag.png diff --git a/src/assets/img/red_edge.png b/src/assets/img/red_edge.png deleted file mode 100644 index bee4c484a735f5e4a158b900dc8cfeab55a9ae59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 957 zcmaJ=O^ee&7|tR{cVW?^dK$9}BG_coY?~%DuHB|}15GJyp*@-=(>8RI8I!3^>qYkD z;>n{Ye}E!*78GK-sm{wrCg(H=!Sdr-w9p-BEu*%K#dbPBqLgT&IGs*~sVv}dC`ziTCK|b1mLamy zETDFr4WgY?!9bA{dW3p70EwdA#}lga%;|XwK3T~I(XvdeU}9_&Q4(O1QVO)J|3iI$ zg^p+oUHJW{aMYO*B(_k5C!xbGZm^StB3dOxHpO8F!6ISJTkl0#tGDF3>%A1$az$oFOs@5>Yfg_??CYG6&EsqJAG>dF-+OL! lpWHlo&Ar;KZ#2xSTn2Jq&Ne?$GI*LiD6`ry-W)tQ{sRzVDC7VD diff --git a/src/assets/img/tag.png b/src/assets/img/tag.png deleted file mode 100644 index bee4c484a735f5e4a158b900dc8cfeab55a9ae59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 957 zcmaJ=O^ee&7|tR{cVW?^dK$9}BG_coY?~%DuHB|}15GJyp*@-=(>8RI8I!3^>qYkD z;>n{Ye}E!*78GK-sm{wrCg(H=!Sdr-w9p-BEu*%K#dbPBqLgT&IGs*~sVv}dC`ziTCK|b1mLamy zETDFr4WgY?!9bA{dW3p70EwdA#}lga%;|XwK3T~I(XvdeU}9_&Q4(O1QVO)J|3iI$ zg^p+oUHJW{aMYO*B(_k5C!xbGZm^StB3dOxHpO8F!6ISJTkl0#tGDF3>%A1$az$oFOs@5>Yfg_??CYG6&EsqJAG>dF-+OL! lpWHlo&Ar;KZ#2xSTn2Jq&Ne?$GI*LiD6`ry-W)tQ{sRzVDC7VD From f788aae3b47bf0340eed98fad57ea2fdec0dfce8 Mon Sep 17 00:00:00 2001 From: jeewonMoon Date: Thu, 25 Jul 2024 02:38:23 +0900 Subject: [PATCH 6/7] =?UTF-8?q?feat:=20=ED=95=84=ED=84=B0=EB=B0=95?= =?UTF-8?q?=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 드롭다운, 인풋, 조회 버튼을 컴포넌트화 했습니다. - Warning: Received "true" for a non-boolean attribute 오류를 해결했습니다. - 스타일 요소들을 수정했습니다. --- src/assets/data/filter.ts | 171 +++++++++++++++++++++ src/assets/img/tag.png | Bin 0 -> 957 bytes src/components/LectureList/Filters.tsx | 159 ++++++++++++++++++++ src/components/Menubar/BarTitle.tsx | 1 - src/components/Menubar/MenuItem.tsx | 8 +- src/components/TabMenu/Tab.tsx | 27 ++-- src/components/TabMenu/index.tsx | 2 - src/components/common/FilterButton.tsx | 39 +++++ src/components/common/FilterInput.tsx | 45 ++++++ src/components/common/SelectBox.tsx | 198 +++++++++++++++++++++++++ src/styles/GlobalStyle.tsx | 11 ++ src/styles/theme/Theme.ts | 2 +- 12 files changed, 646 insertions(+), 17 deletions(-) create mode 100644 src/assets/data/filter.ts create mode 100644 src/assets/img/tag.png create mode 100644 src/components/LectureList/Filters.tsx create mode 100644 src/components/common/FilterButton.tsx create mode 100644 src/components/common/FilterInput.tsx create mode 100644 src/components/common/SelectBox.tsx diff --git a/src/assets/data/filter.ts b/src/assets/data/filter.ts new file mode 100644 index 0000000..ea06a02 --- /dev/null +++ b/src/assets/data/filter.ts @@ -0,0 +1,171 @@ +export const term = [ + {id: 0, value: '2024/1학기'}, + {id: 1, value: '2024/2학기'}, +]; + +export const completion = [ + {id: 0, value: '-전체-'}, + {id: 1, value: '교양필수'}, + {id: 2, value: '공통교양필수'}, + {id: 3, value: '교양선택(1영역)'}, + {id: 4, value: '학문기초교양필수'}, + {id: 5, value: '교양선택'}, + {id: 6, value: '학문기초교양'}, + {id: 7, value: '전공기초'}, + {id: 8, value: '전공필수'}, + {id: 9, value: '전공선택'}, + {id: 10, value: '교직'}, + {id: 11, value: '무관후보생교육'}, +]; + +export const optional = [ + {id: 0, value: '-전체-'}, + {id: 1, value: '인성과도덕'}, + {id: 2, value: '역사와문화'}, + {id: 3, value: '사회와제도'}, + {id: 4, value: '생명과 과학'}, + {id: 5, value: '예술과생활'}, + {id: 6, value: '지구촌의이해'}, + {id: 7, value: '학문기초'}, + {id: 8, value: '인성과창의력'}, + {id: 9, value: '역량강화'}, + {id: 10, value: '사상과역사'}, + {id: 11, value: '사회와문화'}, + {id: 12, value: '융합과창업'}, + {id: 13, value: '자연과과학기술'}, + {id: 14, value: '세계와지구촌'}, + {id: 15, value: '예술과체육'}, + {id: 17, value: '역사와사상'}, + {id: 18, value: '자연과과학'}, + {id: 19, value: '경제와사회'}, + {id: 20, value: '문화와예술'}, + {id: 21, value: '사상과 역사'}, + {id: 22, value: '대학위성강좌'}, +]; + +export const major = [ + {id: 0, value: '-선택-'}, + {id: 1, value: 'AI로봇학과【3517 학부】인공지능융합대학'}, + {id: 2, value: 'AI연계융합전공【3317 학부】연계전공'}, + { + id: 3, + value: 'AI연계융합전공 소셜미디어매니지먼트소프트웨어【3328 학부】연계전공', + }, + { + id: 4, + value: + 'AI연계융합전공 스마트투어리즘매니지먼트소프트웨어【3331 학부】연계전공', + }, + {id: 5, value: 'AI연계융합전공 시스템생명공학【3324 학부】연계전공'}, + { + id: 6, + value: 'AI연계융합전공 에듀테크콘텐츠애널리틱스【3326 학부】연계전공', + }, + {id: 7, value: '건설환경공학과【2733 학부】공과대학'}, + {id: 8, value: '건축공학과【2720 학부】공과대학'}, + {id: 9, value: '건축공학부 건축공학전공【2779 학부】공과대학'}, + {id: 10, value: '건축공학부 건축학전공【2780 학부】공과대학'}, + {id: 11, value: '건축학과【2739 학부】공과대학'}, + {id: 12, value: '경영학부【2274 학부】경영경제대학'}, + {id: 13, value: '경제학과【2273 학부】경영경제대학'}, + {id: 14, value: '교육학과【2114 학부】인문과학대학'}, + {id: 15, value: '국방시스템공학과【2784 학부】공과대학'}, + {id: 17, value: '국어국문학과【2111 학부】인문과학대학'}, + {id: 18, value: '국제학부【2130 학부】인문과학대학'}, + {id: 19, value: '국제학부 영어영문학전공【2131 학부】인문과학대학'}, + {id: 20, value: '국제학부 일어일문학전공【2132 학부】인문과학대학'}, + {id: 21, value: '국제학부 중국통상학전공【2133 학부】인문과학대학'}, + {id: 22, value: '글로벌미디어소프트웨어 융합전공【3330 학부】연계전공'}, + {id: 23, value: '글로벌인재학부【2122 학부】인문과학대학'}, + {id: 24, value: '글로벌조리학과【3037 학부】호텔관광대학'}, + {id: 25, value: '기계공학과【2725 학부】공과대학'}, + {id: 26, value: '기계항공우주공학부 기계공학전공【2723 학부】공과대학'}, + {id: 27, value: '기계항공우주공학부 항공우주공학전공【2724 학부】공과대학'}, + {id: 28, value: '나노신소재공학과【2786 학부】공과대학'}, + {id: 29, value: '뉴미디어퍼포먼스 융합전공【3376 학부】연계전공'}, + {id: 30, value: '대양휴머니티칼리지【9005 학부】대양휴머니티칼리지'}, + {id: 31, value: '데이터사이언스학과【3225 학부】소프트웨어융합대학'}, + {id: 32, value: '디지털역사문화자원큐레이션융합전공【3395 학부】연계전공'}, + {id: 33, value: '럭셔리 브랜드 디자인 융합전공【3370 학부】연계전공'}, + {id: 34, value: '무용과【2515 학부】예체능대학'}, + {id: 35, value: '문화산업경영 융합전공【3366 학부】연계전공'}, + {id: 36, value: '물리천문학과【2450 학부】자연과학대학'}, + {id: 37, value: '미디어커뮤니케이션학과【2233 학부】사회과학대학'}, + {id: 38, value: '반도체시스템공학과【2931 학부】전자정보공학대학'}, + {id: 39, value: '반도체시스템공학과【3512 학부】인공지능융합대학'}, + {id: 40, value: '법학과【2053 학부】사회과학대학'}, + {id: 41, value: '법학부 법학전공【2052 학부】대학'}, + {id: 42, value: '비즈니스 애널리틱스 융합전공【3350 학부】연계전공'}, + {id: 43, value: '생명시스템학부【3140 학부】생명과학대학'}, + { + id: 44, + value: '생명시스템학부 바이오산업자원공학전공【3144 학부】생명과학대학', + }, + {id: 45, value: '생명시스템학부 바이오융합공학전공【3142 학부】생명과학대학'}, + {id: 46, value: '생명시스템학부 식품생명공학전공【3145 학부】생명과학대학'}, + {id: 47, value: '소프트웨어학과【3220 학부】소프트웨어융합대학'}, + {id: 48, value: '소프트웨어학과【3515 학부】인공지능융합대학'}, + {id: 49, value: '수학통계학과【2658 학부】자연과학대학'}, + {id: 50, value: '수학통계학부 수학전공【2648 학부】자연과학대학'}, + {id: 51, value: '수학통계학부 응용통계학전공【2649 학부】자연과학대학'}, + {id: 52, value: '스마트생명산업융합학과【3146 학부】생명과학대학'}, + {id: 53, value: '양자원자력공학과【2789 학부】공과대학'}, + {id: 54, value: '엔터테인먼트 소프트웨어 융합전공【3320 학부】연계전공'}, + {id: 55, value: '역사학과【2115 학부】인문과학대학'}, + {id: 56, value: '영상디자인 융합전공【3360 학부】연계전공'}, + {id: 57, value: '영화예술학과【2525 학부】예체능대학'}, + {id: 58, value: '예술융합콘텐츠 융합전공【3386 학부】연계전공'}, + {id: 59, value: '우주항공드론공학부【2757 학부】공과대학'}, + {id: 60, value: '우주항공드론공학부 항공시스템공학전공【2761 학부】공과대학'}, + { + id: 61, + value: '우주항공시스템공학부 항공시스템공학전공【2793 학부】공과대학', + }, + {id: 62, value: '융합창업전공【3310 학부】연계전공'}, + {id: 63, value: '음악과【2513 학부】예체능대학'}, + {id: 64, value: '인공지능데이터사이언스학과【3516 학부】인공지능융합대학'}, + {id: 65, value: '인공지능학과【3238 학부】소프트웨어융합대학'}, + {id: 66, value: '전자정보통신공학과【2930 학부】전자정보공학대학'}, + {id: 67, value: '전자정보통신공학과【3511 학부】인공지능융합대학'}, + {id: 68, value: '정보보호학과【3215 학부】소프트웨어융합대학'}, + {id: 69, value: '정보보호학과【3514 학부】인공지능융합대학'}, + {id: 70, value: '지구자원시스템공학과【2788 학부】공과대학'}, + {id: 71, value: '지능기전공학과【3233 학부】소프트웨어융합대학'}, + { + id: 72, + value: '지능기전공학부 무인이동체공학전공【3231 학부】소프트웨어융합대학', + }, + { + id: 73, + value: '지능기전공학부 스마트기기공학전공【3232 학부】소프트웨어융합대학', + }, + { + id: 74, + value: '창의소프트학부 디자인이노베이션전공【3236 학부】소프트웨어융합대학', + }, + { + id: 75, + value: '창의소프트학부 만화애니메이션텍전공【3237 학부】소프트웨어융합대학', + }, + {id: 76, value: '창의소프트학부【3518 학부】인공지능융합대학'}, + {id: 77, value: '체육학과【2514 학부】예체능대학'}, + {id: 78, value: '컴퓨터공학과【3210 학부】소프트웨어융합대학'}, + {id: 79, value: '컴퓨터공학과【3513 학부】인공지능융합대학'}, + {id: 80, value: '패션디자인학과【2536 학부】예체능대학'}, + {id: 81, value: '항공시스템공학과【2787 학부】공과대학'}, + {id: 82, value: '행정학과【2223 학부】사회과학대학'}, + {id: 83, value: '호텔관광외식경영학부【3029 학부】호텔관광대학'}, + { + id: 84, + value: '호텔관광외식경영학부 외식경영학전공【3036 학부】호텔관광대학', + }, + { + id: 85, + value: '호텔관광외식경영학부 호텔관광경영학전공【3035 학부】호텔관광대학', + }, + {id: 86, value: '호텔외식관광프랜차이즈경영학과【3033 학부】호텔관광대학'}, + {id: 87, value: '호텔외식비즈니스학과【3034 학부】호텔관광대학'}, + {id: 88, value: '화학과【2433 학부】자연과학대학'}, + {id: 89, value: '환경에너지공간융합학과【2790 학부】공과대학'}, + {id: 90, value: '환경에너지공간융합학과【2790 학부】공과대학'}, +]; diff --git a/src/assets/img/tag.png b/src/assets/img/tag.png new file mode 100644 index 0000000000000000000000000000000000000000..bee4c484a735f5e4a158b900dc8cfeab55a9ae59 GIT binary patch literal 957 zcmaJ=O^ee&7|tR{cVW?^dK$9}BG_coY?~%DuHB|}15GJyp*@-=(>8RI8I!3^>qYkD z;>n{Ye}E!*78GK-sm{wrCg(H=!Sdr-w9p-BEu*%K#dbPBqLgT&IGs*~sVv}dC`ziTCK|b1mLamy zETDFr4WgY?!9bA{dW3p70EwdA#}lga%;|XwK3T~I(XvdeU}9_&Q4(O1QVO)J|3iI$ zg^p+oUHJW{aMYO*B(_k5C!xbGZm^StB3dOxHpO8F!6ISJTkl0#tGDF3>%A1$az$oFOs@5>Yfg_??CYG6&EsqJAG>dF-+OL! lpWHlo&Ar;KZ#2xSTn2Jq&Ne?$GI*LiD6`ry-W)tQ{sRzVDC7VD literal 0 HcmV?d00001 diff --git a/src/components/LectureList/Filters.tsx b/src/components/LectureList/Filters.tsx new file mode 100644 index 0000000..d0753c2 --- /dev/null +++ b/src/components/LectureList/Filters.tsx @@ -0,0 +1,159 @@ +import styled from 'styled-components'; +import FilterButton from '@components/common/FilterButton'; +import FilterInput from '@components/common/FilterInput'; +import SelectBox from '@components/common/SelectBox'; +import {completion, major, optional, term} from '@assets/data/filter'; +import {LectureProps} from '.'; + +interface FiltersProps { + lecture: LectureProps | undefined; + setLecture: React.Dispatch>; +} + +function Filters({lecture, setLecture}: FiltersProps) { + const handleSelect = ( + name: keyof LectureProps, + value: string | undefined, + ) => { + let dept = ''; + let colledge = ''; + + if (name === 'schDeptAlias') { + dept = value!.substring(0, value!.indexOf('【')); + colledge = value!.substring(value!.indexOf('】') + 1); + + setLecture(prevState => ({ + ...prevState, + schDeptAlias: dept, + schCollegeAlias: colledge, + })); + } else { + setLecture(prevState => ({ + ...prevState, + [name]: value, + })); + } + }; + + return ( + + + + + 조직분류 + handleSelect('curiTypeCdNm', value)} + /> + + + 년도/학기 + handleSelect('curiTypeCdNm', value)} + /> + + + 이수구분 + handleSelect('curiTypeCdNm', value)} + /> + + + 선택영역 + handleSelect('sltDomainCdNm', value)} + /> + + + 학과전공 + handleSelect('schDeptAlias', value)} + /> + + + 교과목명 + handleSelect('curiNm', value)} + /> + + + 교수명 + handleSelect('lesnEmp', value)} + /> + + + + + +

+ ※ 교양과목(중필, 중선, 전공기초교양, 자유선택교양), 교직과목, + ROTC과목은 개설학과전공을 대양휴머니티칼리지(또는 교양학부)로 하여 + 조회하시기 바랍니다. +

+

+ ※ 과목에 대한 문의는 개설학과가 아닌 주관학과에 문의하시기 바라며, + 영어과목에 대한 문의는 교양영어실로 문의하시기 바랍니다. +

+
+
+ ); +} + +const FilterContainer = styled.div` + border: 0.1rem solid #714656; + border-radius: 2px; + padding: 0.5rem 1.5rem; +`; + +const FilterBox = styled.div` + display: flex; + flex-wrap: wrap; + gap: 0.7rem 3rem; +`; + +const FilterArea = styled.div` + display: flex; + align-items: flex-end; + margin-bottom: 1rem; +`; + +const FilterWrap = styled.div` + ${props => props.theme.texts.tableTitle}; + > span { + display: inline-block; + margin-right: 1rem; + text-align: right; + min-width: 4.5rem; + } +`; + +const WarningWrap = styled.div` + ${props => props.theme.texts.warning}; + color: #c30e2e; + margin-bottom: -1.5rem; + + > p { + margin-bottom: 1.5rem; + } +`; + +export default Filters; diff --git a/src/components/Menubar/BarTitle.tsx b/src/components/Menubar/BarTitle.tsx index 90a0e27..9b6d6f7 100644 --- a/src/components/Menubar/BarTitle.tsx +++ b/src/components/Menubar/BarTitle.tsx @@ -23,7 +23,6 @@ function BarTitle({setIsOpen}: OpenProps) { } const BarTitleContainer = styled.div` - background: rgb(163, 20, 50); background: linear-gradient( 90deg, rgba(163, 20, 50, 1) 0%, diff --git a/src/components/Menubar/MenuItem.tsx b/src/components/Menubar/MenuItem.tsx index 80fd08a..2b6f1bb 100644 --- a/src/components/Menubar/MenuItem.tsx +++ b/src/components/Menubar/MenuItem.tsx @@ -12,14 +12,14 @@ interface DetailProps { function MenuItem({id, type, name, isActive, onClick}: DetailProps) { return ( - onClick(id)}> + onClick(id)}> {type === 'view' ? : } {name} ); } -const DetailWrap = styled.button<{isactive: boolean}>` +const DetailWrap = styled.button<{$isactive: boolean}>` ${props => props.theme.texts.tableTitle}; width: 17.5rem; height: 2.8rem; @@ -29,8 +29,8 @@ const DetailWrap = styled.button<{isactive: boolean}>` padding-left: 10px; background-color: ${props => - props.isactive ? props.theme.colors.primary : 'transparent'}; - color: ${props => props.isactive && props.theme.colors.white}; + props.$isactive ? props.theme.colors.primary : 'transparent'}; + color: ${props => props.$isactive && props.theme.colors.white}; `; export default MenuItem; diff --git a/src/components/TabMenu/Tab.tsx b/src/components/TabMenu/Tab.tsx index b02636e..8a34af7 100644 --- a/src/components/TabMenu/Tab.tsx +++ b/src/components/TabMenu/Tab.tsx @@ -1,5 +1,5 @@ import {useState} from 'react'; -import styled from 'styled-components'; +import styled, {css} from 'styled-components'; import close from '@assets/img/tab_close.png'; interface TabProps { @@ -27,26 +27,26 @@ function Tab({id, label, isActive, onClick}: TabProps) { return ( <> {isOpen && ( - onClick(id)} isactive={isActive}> + onClick(id)} $isactive={isActive}>

{label}

- +
)} ); } -const TabContainer = styled.a<{isactive: boolean}>` +const TabContainer = styled.a<{$isactive: boolean}>` ${props => - props.isactive + props.$isactive ? props.theme.texts.tabTitleFocus : props.theme.texts.tabTitle}; background-color: ${props => - props.isactive ? props.theme.colors.white : 'transparent'}; + props.$isactive ? props.theme.colors.white : 'transparent'}; width: calc(99% / 8); height: 100%; border-top: 0.3rem solid - ${props => (props.isactive ? props.theme.colors.primary : 'none')}; + ${props => (props.$isactive ? props.theme.colors.primary : 'none')}; border-right: 1px solid #ccc; border-radius: 0; padding: 0 1rem; @@ -54,6 +54,7 @@ const TabContainer = styled.a<{isactive: boolean}>` align-items: center; text-align: center; cursor: pointer; + filter: ${props => (props.$isactive ? 'grayscale(0)' : 'grayscale(100%)')}; > p { width: 100%; @@ -63,16 +64,24 @@ const TabContainer = styled.a<{isactive: boolean}>` word-break: break-all; margin-right: 1.5rem; } + + ${props => + !props.$isactive && + css` + &:hover { + background-color: white; + filter: grayscale(0); + } + `} `; -const CloseBtn = styled.button<{isactive: boolean}>` +const CloseBtn = styled.button` z-index: 5; width: 1rem; height: 100%; background-image: url(${close}); background-repeat: no-repeat; background-position-y: center; - filter: ${props => (props.isactive ? 'grayscale(0)' : 'grayscale(100%)')}; `; export default Tab; diff --git a/src/components/TabMenu/index.tsx b/src/components/TabMenu/index.tsx index 95e605c..ea0f137 100644 --- a/src/components/TabMenu/index.tsx +++ b/src/components/TabMenu/index.tsx @@ -9,8 +9,6 @@ function TabMenu() { const [close, setClose] = useState(false); const handleClick = (id: number) => { - console.log('click ' + id); - setFocused(id); }; diff --git a/src/components/common/FilterButton.tsx b/src/components/common/FilterButton.tsx new file mode 100644 index 0000000..d996262 --- /dev/null +++ b/src/components/common/FilterButton.tsx @@ -0,0 +1,39 @@ +import styled from 'styled-components'; +import search from '@assets/img/search.png'; +import {LectureProps} from '@components/LectureList'; + +interface ButtonProps { + label: string; + lecture: LectureProps | undefined; +} + +function FilterButton({label, lecture}: ButtonProps) { + const handleClick = () => { + console.log(lecture); + }; + + return ( + + + {label} + + ); +} + +const ButtonWrap = styled.button` + ${props => props.theme.texts.content}; + background: linear-gradient( + 90deg, + rgba(163, 20, 50, 1) 0%, + rgba(51, 77, 97, 1) 100% + ); + color: ${props => props.theme.colors.white}; + min-width: 6.5rem; + height: 2.4rem; + display: flex; + align-items: center; + justify-content: center; + gap: 0.8rem; +`; + +export default FilterButton; diff --git a/src/components/common/FilterInput.tsx b/src/components/common/FilterInput.tsx new file mode 100644 index 0000000..68a36d5 --- /dev/null +++ b/src/components/common/FilterInput.tsx @@ -0,0 +1,45 @@ +import styled, {css} from 'styled-components'; + +interface InputInterface { + sizes: string; + onChange: (value: string) => void; +} + +function FilterInput({sizes, onChange}: InputInterface) { + return ( + <> + ) => + onChange(e.target.value) + } + /> + + ); +} + +const InputWrap = styled.input<{sizes: string}>` + ${props => props.theme.texts.content}; + + ${props => + props.sizes === 's' && + css` + width: 14rem; + `}; + ${props => + props.sizes === 'm' && + css` + width: 19.5rem; + `}; + ${props => + props.sizes === 'xl' && + css` + width: 48.5rem; + `}; + + height: 2.4rem; + border: 1px solid ${props => props.theme.colors.neutral5}; + padding-left: 0.8rem; +`; + +export default FilterInput; diff --git a/src/components/common/SelectBox.tsx b/src/components/common/SelectBox.tsx new file mode 100644 index 0000000..ed1a4f2 --- /dev/null +++ b/src/components/common/SelectBox.tsx @@ -0,0 +1,198 @@ +import {useEffect, useRef, useState} from 'react'; +import styled, {css} from 'styled-components'; +import arrow from '@assets/img/input_dropdown.png'; +import tag from '@assets/img/tag.png'; + +interface OptionsInterface { + id: number; + value: string; +} + +interface SelectProps { + options: OptionsInterface[]; + tagged: boolean; + disabled?: boolean; + sizes: string; + onSelect: (value: string) => void; +} + +function SelectBox({ + options, + tagged, + disabled = false, + sizes, + onSelect, +}: SelectProps) { + const [open, setOpen] = useState(false); + const [input, setInput] = useState(options[0].value); + const [selected, setSelected] = useState(options[0].value); + const [filtered, setFiltered] = useState(options); + const dropdownRef = useRef(null); + + const handleBtnClick = () => { + if (!disabled) { + setFiltered(options); + setOpen(prev => !prev); + } + }; + + const handleInput = (e: React.ChangeEvent) => { + setInput(e.target.value); + setFiltered( + options.filter(option => option.value.includes(e.target.value)), + ); + setOpen(true); + }; + + const handleOptionClick = (value: string) => { + onSelect(value); + setInput(value); + setSelected(value); + setOpen(false); + }; + + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) + ) { + if (open) { + setOpen(false); + setInput(selected); + } + } + }; + + useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [open]); + + return ( + + + {tagged && } + + + + {open && ( + + {filtered.map(option => ( + handleOptionClick(option.value)} + > + {option.value} + + ))} + + )} + + ); +} + +const SelectContainer = styled.div<{sizes: string}>` + ${props => props.theme.texts.content}; + ${props => + props.sizes === 's' && + css` + width: 15rem; + `}; + ${props => + props.sizes === 'm' && + css` + width: 20.5rem; + `}; + ${props => + props.sizes === 'xl' && + css` + width: 50rem; + `}; + height: 2.4rem; + position: relative; + display: inline-block; +`; + +const InputContainer = styled.div<{disabled: boolean}>` + position: relative; + width: inherit; + height: inherit; + border: 1px solid ${props => props.theme.colors.neutral5}; + + &:hover { + background-color: ${props => props.theme.colors.blue}; + } + + &:disabled { + background-color: ${props => props.theme.colors.neutral5}; + } +`; + +const TagWrap = styled.img<{disabled: boolean}>` + position: absolute; + z-index: 2; + filter: ${props => (props.disabled ? 'grayscale(100%)' : 'none')}; +`; + +const InputWrap = styled.input` + ${props => props.theme.texts.content}; + width: calc(100% - 1rem); + height: inherit; + padding: 0 0 0 1rem; + + &:hover { + background-color: ${props => props.theme.colors.blue}; + } + + &:read-only { + background-color: ${props => props.theme.colors.neutral5}; + } +`; + +const ArrowWrap = styled.img` + position: absolute; + right: 0.3rem; + top: 10%; + border: 1px solid transparent; + border-radius: 5px; + + &:hover { + border: 1px solid ${props => props.theme.colors.neutral5}; + } +`; + +const SelectWrap = styled.ul` + width: inherit; + max-height: 12rem; + position: absolute; + top: 100%; + z-index: 5; + border: 1px solid ${props => props.theme.colors.neutral5}; + background-color: ${props => props.theme.colors.white}; + overflow-y: scroll; +`; + +const OptionWrap = styled.li<{$isselected: boolean}>` + height: 2.4rem; + display: flex; + align-items: center; + padding-left: 1rem; + + ${props => + props.$isselected && + css` + background-color: ${props => props.theme.colors.neutral6}; + color: ${props => props.theme.colors.primary}; + `} + + &:hover { + background-color: ${props => props.theme.colors.neutral6}; + color: ${props => props.theme.colors.primary}; + } +`; + +export default SelectBox; diff --git a/src/styles/GlobalStyle.tsx b/src/styles/GlobalStyle.tsx index 1bef676..47cccc5 100644 --- a/src/styles/GlobalStyle.tsx +++ b/src/styles/GlobalStyle.tsx @@ -28,6 +28,7 @@ const GlobalStyle = createGlobalStyle` border-radius: 2px; outline: none; border: none; + padding: 0; } button { cursor: pointer; @@ -37,6 +38,16 @@ const GlobalStyle = createGlobalStyle` border:none; padding:0; } + select::-ms-expand { + display: none; + } + select { + -o-appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + outline: none; + } `; export default GlobalStyle; diff --git a/src/styles/theme/Theme.ts b/src/styles/theme/Theme.ts index bcec4aa..072c622 100644 --- a/src/styles/theme/Theme.ts +++ b/src/styles/theme/Theme.ts @@ -9,7 +9,7 @@ const colors = { neutral4: '#777', neutral5: '#E8E8E8', neutral6: '#F7F7F7', - blue: '#A9D0F5', + blue: '#E5EFF7', black: '#000', white: '#FFF', }; From 2503b9e567fdf957f61abc4944192e3cd8c1f55e Mon Sep 17 00:00:00 2001 From: jeewonMoon Date: Mon, 29 Jul 2024 05:51:29 +0900 Subject: [PATCH 7/7] =?UTF-8?q?feat:=20=ED=85=8C=EC=9D=B4=EB=B8=94=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - API 연결이 되어있지 않아 임의로 데이터를 만들어 구현했습니다. - 열의 항목들이 페이지마다 달라서 Table을 사용할 때 열 데이터를 만들어 전달해야 합니다. --- src/assets/img/table_drodown.gif | Bin 0 -> 229 bytes src/assets/types/tableType.ts | 9 + src/components/LectureList/Filters.tsx | 16 +- src/components/LectureList/index.tsx | 108 ++++++++++++ src/components/common/FilterButton.tsx | 2 +- src/components/common/Table/TableHead.tsx | 159 ++++++++++++++++++ src/components/common/Table/index.tsx | 191 ++++++++++++++++++++++ src/pages/index/Home.tsx | 21 ++- src/styles/GlobalStyle.tsx | 8 + 9 files changed, 506 insertions(+), 8 deletions(-) create mode 100644 src/assets/img/table_drodown.gif create mode 100644 src/assets/types/tableType.ts create mode 100644 src/components/LectureList/index.tsx create mode 100644 src/components/common/Table/TableHead.tsx create mode 100644 src/components/common/Table/index.tsx diff --git a/src/assets/img/table_drodown.gif b/src/assets/img/table_drodown.gif new file mode 100644 index 0000000000000000000000000000000000000000..67de8115f1bae5ad3051e35bb3a758e894f0f7bf GIT binary patch literal 229 zcmZ?wbhEHb+Z^fkB2r2P6QplYuo!VS8_iXV<}{YjvtwA2XjdNlTt!e6Oazcjkfu z4HlvPnKzOXJ1pk*w3n2uG!yY}TvD~LNU-Sy>; +export interface LectureProps { + schCollegeAlias?: string | undefined; + schDeptAlias?: string | undefined; + curiTypeCdNm?: string | undefined; + sltDomainCdNm?: string | undefined; + curiNm?: string | undefined; + lesnEmp?: string | undefined; } -function Filters({lecture, setLecture}: FiltersProps) { +function Filters() { + const [lecture, setLecture] = useState(); const handleSelect = ( name: keyof LectureProps, value: string | undefined, @@ -122,6 +127,7 @@ const FilterContainer = styled.div` border: 0.1rem solid #714656; border-radius: 2px; padding: 0.5rem 1.5rem; + margin-bottom: 2rem; `; const FilterBox = styled.div` diff --git a/src/components/LectureList/index.tsx b/src/components/LectureList/index.tsx new file mode 100644 index 0000000..688a79a --- /dev/null +++ b/src/components/LectureList/index.tsx @@ -0,0 +1,108 @@ +import styled from 'styled-components'; +import Filters from './Filters'; +import Table from '@components/common/Table'; + +const data = [ + { + schDeptAlias: '대양휴머니티칼리지', + curiNo: '011312', + class_: '001', + schCollegeAlias: '대양휴머니티칼리지', + curiNm: '경영학', + curiLangNm: null, + curiTypeCdNm: '균형교양필수', + sltDomainCdNm: '경제와사회', + tmNum: '3.0 / 3 / 0', + studentYear: '2', + corsUnitGrpCdNm: '학사', + manageDeptNm: '대양휴머니티칼리지', + lesnEmp: '이지훈', + lesnTime: '목 18:00~19:00', + lesnRoom: '집301', + cyberTypeCdNm: '본교 e-러닝강의', + internshipTypeCdNm: null, + inoutSubCdtExchangeYn: null, + remark: '사회과학,경영경제,호텔관광대학2 제외', + }, + { + schDeptAlias: '대양휴머니티칼리지', + curiNo: '011312', + class_: '001', + schCollegeAlias: '대양휴머니티칼리지', + curiNm: '경영학', + curiLangNm: null, + curiTypeCdNm: '균형교양필수', + sltDomainCdNm: '경제와사회', + tmNum: '3.0 / 3 / 0', + studentYear: '2', + corsUnitGrpCdNm: '학사', + manageDeptNm: '대양휴머니티칼리지', + lesnEmp: '이지훈', + lesnTime: '목 18:00~19:00', + lesnRoom: '집301', + cyberTypeCdNm: '본교 e-러닝강의', + internshipTypeCdNm: null, + inoutSubCdtExchangeYn: null, + remark: '사회과학,경영경제,호텔관광대학2 제외', + }, + { + schDeptAlias: '대양휴머니티칼리지', + curiNo: '011312', + class_: '001', + schCollegeAlias: '대양휴머니티칼리지', + curiNm: '경제학', + curiLangNm: null, + curiTypeCdNm: '균형교양필수', + sltDomainCdNm: '경제와사회', + tmNum: '3.0 / 3 / 0', + studentYear: '2', + corsUnitGrpCdNm: '학사', + manageDeptNm: '대양휴머니티칼리지', + lesnEmp: '이지훈', + lesnTime: '목 18:00~19:00', + lesnRoom: '집301', + cyberTypeCdNm: '본교 e-러닝강의', + internshipTypeCdNm: null, + inoutSubCdtExchangeYn: null, + remark: '사회과학,경영경제,호텔관광대학2 제외', + }, +]; + +const colData = [ + {name: 'schDeptAlias', value: '개설학과전공', initialWidth: 167}, + {name: 'curiNo', value: '학수번호', initialWidth: 92}, + {name: 'class_', value: '분반', initialWidth: 58}, + {name: 'curiNm', value: '교과목명', initialWidth: 232}, + {name: 'curiLangNm', value: '강의언어', initialWidth: 73}, + {name: 'curiTypeCdNm', value: '이수구분'}, + {name: 'sltDomainCdNm', value: '선택영역', initialWidth: 136}, + {name: 'tmNum', value: '학점/이론/실습', initialWidth: 134}, + {name: 'studentYear', value: '학년 (학기)'}, + {name: 'corsUnitGrpCdNm', value: '대상과정'}, + {name: 'manageDeptNm', value: '주관학과', initialWidth: 135}, + {name: 'lesnEmp', value: '교수명'}, + {name: 'lesnTime', value: '요일 및 강의시간', initialWidth: 130}, + {name: 'lesnRoom', value: '강의실', initialWidth: 114}, + {name: 'cyberTypeCdNm', value: '사이버강좌', initialWidth: 104}, + {name: 'internshipTypeCdNm', value: '강좌유형', initialWidth: 126}, + {name: 'inoutSubCdtExchangeYn', value: '학점교류수강가능', initialWidth: 130}, + {name: 'remark', value: '수강대상및유의사항', initialWidth: 230}, +]; + +function LectureList() { + return ( + + + + + ); +} + +const ListContainer = styled.div``; + +export default LectureList; diff --git a/src/components/common/FilterButton.tsx b/src/components/common/FilterButton.tsx index d996262..35846f4 100644 --- a/src/components/common/FilterButton.tsx +++ b/src/components/common/FilterButton.tsx @@ -1,6 +1,6 @@ import styled from 'styled-components'; import search from '@assets/img/search.png'; -import {LectureProps} from '@components/LectureList'; +import {LectureProps} from '@components/LectureList/Filters'; interface ButtonProps { label: string; diff --git a/src/components/common/Table/TableHead.tsx b/src/components/common/Table/TableHead.tsx new file mode 100644 index 0000000..7871db5 --- /dev/null +++ b/src/components/common/Table/TableHead.tsx @@ -0,0 +1,159 @@ +import styled from 'styled-components'; +import dropdown from '@assets/img/table_drodown.gif'; +import {useEffect, useRef, useState} from 'react'; + +interface HeadProps { + label: string; + index: number; + width: number; + options: string[]; + handleMouseDown: (index: number) => (event: React.MouseEvent) => void; + selectedOptions: string[]; + onFilterChange: (index: number, selectedOptions: string[]) => void; +} + +function TableHead({ + label, + index, + width, + options, + selectedOptions, + onFilterChange, + handleMouseDown, +}: HeadProps) { + const [open, setOpen] = useState(false); + const dropdownRef = useRef(null); + const buttonRef = useRef(null); + + const handleCheckboxChange = (option: string) => { + const updatedSelection = selectedOptions.includes(option) + ? selectedOptions.filter(selectedOption => selectedOption !== option) + : [...selectedOptions, option]; + + if (updatedSelection.length === options.length) { + onFilterChange(index, options); + } else { + onFilterChange(index, updatedSelection); + } + }; + + const handleSelectAll = () => { + if (selectedOptions.length === options.length) { + onFilterChange(index, []); + } else { + onFilterChange(index, options); + } + }; + + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) && + buttonRef.current && + !buttonRef.current.contains(event.target as Node) + ) { + setOpen(false); + } + }; + + useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [open]); + + return ( + +
+ {label} + setOpen(prev => !prev)} /> + {open && ( + + + + + + {options?.map((option, index) => ( + + handleCheckboxChange(option)} + /> + + + ))} + + )} + +
+
+ ); +} + +const Wrap = styled.th<{width: number}>` + min-width: ${props => props.width}px; + text-align: ${props => (props.width > 100 ? 'center' : 'left')}; + + > div { + display: flex; + justify-content: space-between; + align-items: center; + + > span { + text-align: center; + flex-grow: 1; + } + } +`; + +const Resizer = styled.div` + width: 5px; + height: 100%; + position: absolute; + right: 0; + top: 0; + bottom: 0; + cursor: col-resize; + background-color: transparent; + z-index: 1; +`; + +const DropdownBtn = styled.button` + width: 1.5rem; + height: 1.5rem; + background: url(${dropdown}) no-repeat center; +`; + +const OptionBox = styled.ul` + width: 100%; + overflow: scroll; + background: white; + position: absolute; + top: 3rem; + left: 0; +`; + +const OptionWrap = styled.li` + ${props => props.theme.texts.content}; + text-align: left; + border-bottom: 1px solid #b3b3b3; + color: #066196; + height: 2rem; + + > label { + display: inline-block; + width: 86%; + height: 100%; + cursor: pointer; + } +`; + +export default TableHead; diff --git a/src/components/common/Table/index.tsx b/src/components/common/Table/index.tsx new file mode 100644 index 0000000..9440238 --- /dev/null +++ b/src/components/common/Table/index.tsx @@ -0,0 +1,191 @@ +import {useEffect, useRef, useState} from 'react'; +import styled from 'styled-components'; +import TableHead from './TableHead'; +import {TableHeadTypes, TableTypes} from '@assets/types/tableType'; + +interface TableProps { + colData: TableHeadTypes[]; + data: TableTypes[]; + initialWidth: string; + height: string; +} + +function Table({data, colData, initialWidth, height}: TableProps) { + const tableRef = useRef(null); + const [columnWidths, setColumnWidths] = useState([]); + const [filters, setFilters] = useState( + colData.map(col => { + const uniqueOptions = Array.from( + new Set(data.map(row => row[col.name])), + ).filter(option => option !== null) as string[]; + return uniqueOptions.length === 0 ? ['빈값'] : uniqueOptions.sort(); + }), + ); + + useEffect(() => { + if (tableRef.current) { + const initialWidths = Array.from( + tableRef.current.querySelectorAll('th'), + ).map(th => th.getBoundingClientRect().width); + setColumnWidths(initialWidths); + } + }, [tableRef]); + + const handleMouseDown = (index: number) => (event: React.MouseEvent) => { + const startX = event.clientX; + const startWidth = columnWidths[index]; + + const handleMouseMove = (moveEvent: MouseEvent) => { + const newWidth = startWidth + (moveEvent.clientX - startX); + setColumnWidths(prevWidths => + prevWidths.map((width, i) => (i === index ? newWidth : width)), + ); + }; + + const handleMouseUp = () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + }; + + const getOptions = colData.map(col => { + const uniqueOptions = Array.from( + new Set(data.map(row => row[col.name])), + ).filter(option => option !== null) as string[]; + return uniqueOptions.length === 0 ? ['빈값'] : uniqueOptions.sort(); + }); + + const handleFilterChange = (index: number, selectedOptions: string[]) => { + setFilters(prevFilters => { + const newFilters = [...prevFilters]; + newFilters[index] = selectedOptions; + return newFilters; + }); + }; + + const filteredData = data.filter(row => + colData.every( + (col, index) => + filters[index].includes('빈값') || + filters[index].includes(row[col.name] ?? ''), + ), + ); + + return ( + + + 개설강좌 + + + +
+ + {colData.map((item, index) => ( + + ))} + + + + + {colData.map((item, index) => ( + + ))} + + + + {filteredData.map((row, rowIdx) => ( + + {rowIdx + 1} + {colData.map((col, colIdx) => ( + + ))} + + ))} + + + + + ); +} + +const TableContainer = styled.div``; + +const TableTitleWrap = styled.div` + margin-bottom: 1rem; +`; + +const TableTitle = styled.div` + ${props => props.theme.texts.subtitle}; + border-left: 4px solid ${props => props.theme.colors.primary}; + padding-left: 0.5rem; +`; + +const TableBox = styled.div<{width: string; height: string}>` + width: ${props => props.width}; + height: ${props => props.height}; + overflow: scroll; + border-left: 1px solid #c3c3c3; + border-bottom: 1px solid #c3c3c3; +`; + +const TableWrap = styled.table` + ${props => props.theme.texts.content}; + border-top: 1px solid ${props => props.theme.colors.black}; + white-space: nowrap; + border-collapse: collapse; + + > thead > tr > th { + ${props => props.theme.texts.tableTitle}; + position: relative; + border-top: 1px solid black; + background-color: ${props => props.theme.colors.neutral5}; + } +`; + +const RowWrap = styled.tr` + height: 3rem; + > th, + td { + border: 1px solid #c3c3c3; + border-left: none; + padding: 0 0.5rem; + vertical-align: middle; + } +`; + +const IndexWrap = styled.td` + background-color: ${props => props.theme.colors.blue}; + text-align: center; +`; + +const ContentWrap = styled(RowWrap)<{$isEven: boolean}>` + background-color: ${props => + props.$isEven ? 'rgb(252, 252, 252)' : props.theme.colors.white}; + text-align: center; + &:hover { + background-color: rgb(250, 235, 238); + } + + &:focus { + background-color: rgb(252, 248, 227); + } +`; + +export default Table; diff --git a/src/pages/index/Home.tsx b/src/pages/index/Home.tsx index 33cb890..0ce6881 100644 --- a/src/pages/index/Home.tsx +++ b/src/pages/index/Home.tsx @@ -2,6 +2,7 @@ import styled from 'styled-components'; import Menubar from '@components/Menubar'; import Header from '@components/Header'; import LectureList from '@components/LectureList'; +import TabMenu from '@components/TabMenu'; function Home() { return ( @@ -9,8 +10,13 @@ function Home() {
- 강의시간표/수업계획서조회 - +
+ +
+

강의시간표/수업계획서조회

+ +
+
); @@ -24,4 +30,15 @@ const Box = styled.div` display: flex; `; +const Main = styled.div` + width: 100%; +`; + +const Article = styled.div` + padding: 2rem 1rem; + > p { + margin-bottom: 1.5rem; + } +`; + export default Home; diff --git a/src/styles/GlobalStyle.tsx b/src/styles/GlobalStyle.tsx index 47cccc5..acb27b5 100644 --- a/src/styles/GlobalStyle.tsx +++ b/src/styles/GlobalStyle.tsx @@ -48,6 +48,14 @@ const GlobalStyle = createGlobalStyle` appearance: none; outline: none; } + thead, + tbody { + white-space: nowrap; + } + thead { + position: sticky; + top: 0; + } `; export default GlobalStyle;
순번
{row[col.name]}