diff --git a/src/apis/api/course.ts b/src/apis/api/course.ts new file mode 100644 index 0000000..cc2eb87 --- /dev/null +++ b/src/apis/api/course.ts @@ -0,0 +1,10 @@ +import {baseAPI} from '../utils/instance'; + +export const getCourseList = async (body: object) => { + try { + const {data} = await baseAPI.get('/schedules/search', body); + return data; + } catch (error) { + console.log('get course list fail: ', error); + } +}; diff --git a/src/apis/utils/instance.ts b/src/apis/utils/instance.ts new file mode 100644 index 0000000..451ff9d --- /dev/null +++ b/src/apis/utils/instance.ts @@ -0,0 +1,20 @@ +import axios from 'axios'; + +const baseURL = import.meta.env.VITE_BASE_URL; + +export const baseAPI = axios.create({ + baseURL: baseURL, + headers: { + 'Content-Type': 'application/json', + }, + withCredentials: true, +}); + +// 토큰 받아오는 작업 필요 +export const authAPI = axios.create({ + baseURL: baseURL, + headers: { + // Authorization: `Bearer ${token}`, + }, + withCredentials: true, +}); 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/btn_gnb_cu.png b/src/assets/img/btn_gnb_cu.png new file mode 100644 index 0000000..9caea97 Binary files /dev/null and b/src/assets/img/btn_gnb_cu.png differ diff --git a/src/assets/img/btn_main_top_left.svg b/src/assets/img/btn_main_top_left.svg new file mode 100644 index 0000000..2f7a3dd --- /dev/null +++ b/src/assets/img/btn_main_top_left.svg @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/src/assets/img/btn_main_top_right.png b/src/assets/img/btn_main_top_right.png deleted file mode 100644 index d21d608..0000000 Binary files a/src/assets/img/btn_main_top_right.png and /dev/null differ diff --git a/src/assets/img/btn_main_top_right.svg b/src/assets/img/btn_main_top_right.svg new file mode 100644 index 0000000..da2e7a9 --- /dev/null +++ b/src/assets/img/btn_main_top_right.svg @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/src/assets/img/input_dropdown.png b/src/assets/img/input_dropdown.png new file mode 100644 index 0000000..9032549 Binary files /dev/null and b/src/assets/img/input_dropdown.png differ diff --git a/src/assets/img/search.png b/src/assets/img/search.png index 78880d7..ed1c404 100644 Binary files a/src/assets/img/search.png and b/src/assets/img/search.png differ diff --git a/src/assets/img/table_drodown.gif b/src/assets/img/table_drodown.gif new file mode 100644 index 0000000..67de811 Binary files /dev/null and b/src/assets/img/table_drodown.gif differ diff --git a/src/assets/img/tag.png b/src/assets/img/tag.png new file mode 100644 index 0000000..bee4c48 Binary files /dev/null and b/src/assets/img/tag.png differ diff --git a/src/assets/img/top_menu_down.png b/src/assets/img/top_menu_down.png new file mode 100644 index 0000000..60432f9 Binary files /dev/null and b/src/assets/img/top_menu_down.png differ diff --git a/src/assets/types/tableType.ts b/src/assets/types/tableType.ts new file mode 100644 index 0000000..aa1e31f --- /dev/null +++ b/src/assets/types/tableType.ts @@ -0,0 +1,9 @@ +export interface TableTypes { + [key: string]: string | null; +} + +export interface TableHeadTypes { + name: string; + value: string; + initialWidth?: number; +} diff --git a/src/components/Header/TImer.tsx b/src/components/Header/TImer.tsx new file mode 100644 index 0000000..03137fb --- /dev/null +++ b/src/components/Header/TImer.tsx @@ -0,0 +1,53 @@ +import {useEffect, useState} from 'react'; +import {useNavigate} from 'react-router-dom'; +import styled from 'styled-components'; + +interface TimerProps { + name: string; +} + +function Timer({name}: TimerProps) { + const navigate = useNavigate(); + const [time, setTime] = useState(1800); + + const formatTime = (time: number) => { + return time.toString().padStart(2, '0'); + }; + + const resetTime = () => { + setTime(1800); + }; + + useEffect(() => { + const timer = setInterval(() => { + setTime(prev => prev - 1); + }, 1000); + + if (time === 0) { + navigate('/login'); + } + + return () => clearInterval(timer); + }, [time]); + + return ( + + {name} + + 님 [00:{formatTime(Math.floor(time / 60))}:{formatTime(time % 60)}] + + + ); +} + +const TimerWrap = styled.button` + ${props => props.theme.texts.tableTitle}; + font-size: 1.3rem; + + > span { + color: ${props => props.theme.colors.neutral4}; + font-weight: 400; + } +`; + +export default Timer; diff --git a/src/components/Header/TopMenu.tsx b/src/components/Header/TopMenu.tsx new file mode 100644 index 0000000..53f34c9 --- /dev/null +++ b/src/components/Header/TopMenu.tsx @@ -0,0 +1,107 @@ +import styled from 'styled-components'; +import {useNavigate} from 'react-router-dom'; +import Left from '@assets/img/btn_main_top_left.svg?react'; +import Right from '@assets/img/btn_main_top_right.svg?react'; +import logout from '@assets/img/logout.png'; +import down from '@assets/img/top_menu_down.png'; +import notice from '@assets/img/notice.png'; +import setting from '@assets/img/setitng.png'; +import menu from '@assets/img/menu.png'; +import Timer from './TImer'; + +function TopMenu() { + const navigate = useNavigate(); + const name = '세종대'; + + const handleLogout = () => { + navigate('/login'); + }; + + return ( + + + + + + + + + PC + + + + KOR + + + + + + + + + ); +} + +const TopMenuContainer = styled.div` + display: flex; + align-items: center; + column-gap: 1rem; +`; + +const ArrowWrap = styled.div` + display: flex; + align-items: center; + column-gap: 1.2rem; + margin-right: 1.5rem; +`; + +const StyledLeft = styled(Left)` + fill: ${props => props.theme.colors.neutral4}; + &:hover { + fill: ${props => props.theme.colors.primary}; + } +`; + +const StyledRight = styled(Right)` + fill: ${props => props.theme.colors.neutral4}; + &:hover { + fill: ${props => props.theme.colors.primary}; + } +`; + +const LogoutBtn = styled.button` + background-image: url(${logout}); + width: 1.4rem; + height: 1.4rem; + + &:hover { + filter: brightness(20%); + } +`; + +const DropdownWrap = styled.div` + ${props => props.theme.texts.tableTitle}; + font-size: 1.3rem; + display: flex; + align-items: center; + column-gap: 1.5rem; + + > img { + &:hover { + filter: brightness(20%); + } + } +`; + +const GroupWrap = styled.div` + display: flex; + column-gap: 1rem; + + > img { + &:hover { + filter: brightness(20%); + } + } +`; + +export default TopMenu; diff --git a/src/components/Header/TopNav.tsx b/src/components/Header/TopNav.tsx new file mode 100644 index 0000000..66293f8 --- /dev/null +++ b/src/components/Header/TopNav.tsx @@ -0,0 +1,27 @@ +import styled from 'styled-components'; + +function TopNav() { + return ( + + 학부생학사정보 + 학생서비스 + + ); +} + +const TopNavContatiner = styled.div` + display: flex; + flex-shrink: 0; +`; + +const TopNavWrap = styled.button` + ${props => props.theme.texts.title}; + font-size: 1.6rem; + margin-right: 4rem; + + &:focus { + color: ${props => props.theme.colors.primary}; + } +`; + +export default TopNav; diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx new file mode 100644 index 0000000..1e99463 --- /dev/null +++ b/src/components/Header/index.tsx @@ -0,0 +1,37 @@ +import styled from 'styled-components'; +import logo from '@assets/img/main_logo.png'; +import TopNav from './TopNav'; +import TopMenu from './TopMenu'; + +function Header() { + return ( + +
+ +
+ + + + +
+ ); +} + +const HeaderContainer = styled.div` + border-top: 0.5rem solid ${props => props.theme.colors.primary}; + max-width: 100%; + height: 6rem; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 2rem; +`; + +const HeaderBox = styled.div` + display: flex; + flex-grow: 1; + justify-content: space-between; + margin-left: 7.5rem; +`; + +export default Header; diff --git a/src/components/LectureList/Filters.tsx b/src/components/LectureList/Filters.tsx new file mode 100644 index 0000000..40f22f1 --- /dev/null +++ b/src/components/LectureList/Filters.tsx @@ -0,0 +1,165 @@ +import {useState} from 'react'; +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'; + +export interface LectureProps { + schCollegeAlias?: string | undefined; + schDeptAlias?: string | undefined; + curiTypeCdNm?: string | undefined; + sltDomainCdNm?: string | undefined; + curiNm?: string | undefined; + lesnEmp?: string | undefined; +} + +function Filters() { + const [lecture, setLecture] = useState(); + 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; + margin-bottom: 2rem; +`; + +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/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/LoginForm/FormInput.tsx b/src/components/LoginForm/FormInput.tsx index 16977d3..a05cdc0 100644 --- a/src/components/LoginForm/FormInput.tsx +++ b/src/components/LoginForm/FormInput.tsx @@ -2,7 +2,7 @@ import styled from 'styled-components'; import {setType} from '.'; interface InputProps { - value: string | number; + value: string | number | undefined; setValue: React.Dispatch>; type: string; } diff --git a/src/components/LoginForm/index.tsx b/src/components/LoginForm/index.tsx index 94e64ca..8ce83f1 100644 --- a/src/components/LoginForm/index.tsx +++ b/src/components/LoginForm/index.tsx @@ -2,10 +2,10 @@ import styled from 'styled-components'; import FormInput from './FormInput'; import {useState} from 'react'; -export type setType = string | number; +export type setType = string | number | undefined; function LoginForm() { - const [id, setId] = useState(0); + const [id, setId] = useState(undefined); const [name, setName] = useState(''); return ( @@ -57,7 +57,7 @@ const CheckboxWrap = styled.div` const FindWrap = styled.div` ${props => props.theme.texts.tableTitle}; - color: #777; + color: ${props => props.theme.colors.neutral4}; float: inline-end; margin-bottom: 2.5rem; `; 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/Menu.tsx b/src/components/Menubar/Menu.tsx index f5cdfdb..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() { @@ -56,7 +56,7 @@ const MenuTitleBox = styled.div` align-items: center; justify-content: space-between; padding: 0 15px; - border-bottom: 1px solid ${props => props.theme.colors.neutral4}; + border-bottom: 1px solid ${props => props.theme.colors.neutral5}; `; const MenuTitleWrap = styled.div` ${props => props.theme.texts.menuTitle}; @@ -69,7 +69,7 @@ const MenuSubtitleBox = styled(MenuTitleBox)` `; const DetailBox = styled.div` - background-color: ${props => props.theme.colors.neutral5}; + background-color: ${props => props.theme.colors.neutral6}; display: flex; flex-direction: column; align-items: center; 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 new file mode 100644 index 0000000..8a34af7 --- /dev/null +++ b/src/components/TabMenu/Tab.tsx @@ -0,0 +1,87 @@ +import {useState} from 'react'; +import styled, {css} 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; + filter: ${props => (props.$isactive ? 'grayscale(0)' : 'grayscale(100%)')}; + + > p { + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + word-break: break-all; + margin-right: 1.5rem; + } + + ${props => + !props.$isactive && + css` + &:hover { + background-color: white; + filter: grayscale(0); + } + `} +`; + +const CloseBtn = styled.button` + z-index: 5; + width: 1rem; + height: 100%; + background-image: url(${close}); + background-repeat: no-repeat; + background-position-y: center; +`; + +export default Tab; diff --git a/src/components/TabMenu/index.tsx b/src/components/TabMenu/index.tsx new file mode 100644 index 0000000..ea0f137 --- /dev/null +++ b/src/components/TabMenu/index.tsx @@ -0,0 +1,73 @@ +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) => { + 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/components/common/FilterButton.tsx b/src/components/common/FilterButton.tsx new file mode 100644 index 0000000..35846f4 --- /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/Filters'; + +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/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 6dc66f2..e56aa9c 100644 --- a/src/pages/index/Home.tsx +++ b/src/pages/index/Home.tsx @@ -1,18 +1,44 @@ 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 ( - - 강의시간표/수업계획서조회 +
+ + +
+ +
+

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

+ +
+
+
); } const Container = styled.div` ${props => props.theme.texts.title}; +`; + +const Box = styled.div` display: flex; `; +const Main = styled.div` + width: calc(100% - 23rem); +`; + +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 e37b293..1ca4b7e 100644 --- a/src/styles/GlobalStyle.tsx +++ b/src/styles/GlobalStyle.tsx @@ -26,6 +26,9 @@ const GlobalStyle = createGlobalStyle` } input { border-radius: 2px; + outline: none; + border: none; + padding: 0; } button { cursor: pointer; @@ -35,6 +38,24 @@ 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; + } + thead, + tbody { + white-space: nowrap; + } + thead { + position: sticky; + top: -1px; + } `; export default GlobalStyle; diff --git a/src/styles/theme/Theme.ts b/src/styles/theme/Theme.ts index 062f09e..072c622 100644 --- a/src/styles/theme/Theme.ts +++ b/src/styles/theme/Theme.ts @@ -6,9 +6,10 @@ const colors = { neutral1: '#222', neutral2: '#333', neutral3: '#444', - neutral4: '#E8E8E8', - neutral5: '#F7F7F7', - blue: '#A9D0F5', + neutral4: '#777', + neutral5: '#E8E8E8', + neutral6: '#F7F7F7', + blue: '#E5EFF7', black: '#000', white: '#FFF', }; @@ -39,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', diff --git a/tsconfig.app.json b/tsconfig.app.json index 5fb7ab2..8eb668a 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -23,14 +23,15 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "baseUrl": ".", + "baseUrl": "./src", "paths": { - "@components/*": ["src/components/*"], - "@pages/*": ["src/pages/*"], - "@assets/*": ["src/assets/*"], - "@store/*": ["src/store/*"], - "@plugins/*": ["src/plugins/*"], - "@/*": ["./src/*"] + "@/*": ["./*"], + "@components/*": ["components/*"], + "@pages/*": ["pages/*"], + "@assets/*": ["assets/*"], + "@store/*": ["store/*"], + "@plugins/*": ["plugins/*"], + "@apis/*": ["apis/*"] }, "allowSyntheticDefaultImports": true }, diff --git a/vite.config.ts b/vite.config.ts index 9dd4100..64fb0dc 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,6 @@ -import { defineConfig } from 'vite'; +import {defineConfig} from 'vite'; import react from '@vitejs/plugin-react'; -import { resolve } from 'node:path'; +import {resolve} from 'node:path'; import svgr from 'vite-plugin-svgr'; // https://vitejs.dev/config/ @@ -12,7 +12,7 @@ export default defineConfig({ resolve: { alias: [ { - find: '@src', + find: '@', replacement: resolve(__dirname, './src'), }, { @@ -35,6 +35,10 @@ export default defineConfig({ find: '@plugins', replacement: resolve(__dirname, './src/plugins'), }, + { + find: '@apis', + replacement: resolve(__dirname, './src/apis'), + }, ], }, });
순번
{row[col.name]}