diff --git a/frontend/src/components/external/Filter.jsx b/frontend/src/components/external/Filter.jsx new file mode 100644 index 00000000..12fbecc7 --- /dev/null +++ b/frontend/src/components/external/Filter.jsx @@ -0,0 +1,38 @@ +import styles from './Filter.module.css'; + +const Filter = ({ items, value, onChange }) => { + if (!items || items.length === 0) { + return null; + } + + return ( +
+
+ +
+
Filter
+ +
    + {items.map((label) => { + const isActive = value === label; + + return ( +
  • + +
  • + ); + })} +
+
+
+ ); +}; + +export default Filter; diff --git a/frontend/src/components/external/Filter.module.css b/frontend/src/components/external/Filter.module.css new file mode 100644 index 00000000..ecb31775 --- /dev/null +++ b/frontend/src/components/external/Filter.module.css @@ -0,0 +1,68 @@ +.wrap { + display: flex; + align-items: flex-start; + padding: 28px 18px; + width: 220px; +} + +.line { + width: 1px; + height: 254px; + background: #cbcbcb; +} + +.content { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; +} + +.title { + font-size: 12px; + font-weight: 600; + color: #656565; + margin-bottom: 16px; +} + +.list { + list-style: none; + padding: 0; + margin: 0; + + max-height: 220px; + overflow-y: auto; + + display: flex; + flex-direction: column; + gap: 14px; + align-items: center; + + -webkit-overflow-scrolling: touch; +} + +.listItem { + margin: 0; + padding: 0; +} + +.item { + background: transparent; + border: 0; + padding: 0; + cursor: pointer; + + font-size: 16px; + font-weight: 400; + line-height: 146%; + color: #656565; +} + +.active { + font-weight: 700; + color: #142d56; +} + +.list::-webkit-scrollbar { + width: 0px; +} diff --git a/frontend/src/components/external/Info.jsx b/frontend/src/components/external/Info.jsx new file mode 100644 index 00000000..b81e8ac6 --- /dev/null +++ b/frontend/src/components/external/Info.jsx @@ -0,0 +1,28 @@ +import styles from './Info.module.css'; + +const teamInfo = { + '증권 1팀': + '주도업종 발굴, 매매전략 공유 및 분석, 시장에 영향을 미치는 뉴스에 대한 이해 도움, 세종모의투자 진행으로 매매 분석. (상대적으로 활동 난이도가 쉬운 편에 속하는 팀)', + '증권 2팀': + '주도업종 발굴, 매매전략 공유 및 분석, 시장에 영향을 미치는 뉴스에 대한 이해 도움, 세종모의투자 진행으로 매매 분석. (상대적으로 활동 난이도가 쉬운 편에 속하는 팀)', + '증권 3팀': + '주도업종 발굴, 매매전략 공유 및 분석, 시장에 영향을 미치는 뉴스에 대한 이해 도움, 세종모의투자 진행으로 매매 분석. (상대적으로 활동 난이도가 쉬운 편에 속하는 팀)', + '자산 운용팀': + '동아리 펀드 자금을 운용하는 부서, 주식투자 경험자 우대 + 운용 성과 보수 지급 (벤치마크 초과 수익률 비례) / 투자할 기업 재무제표에 대한 이해 & 기초 이론 바탕의 기업 실적 및 가치 분석 (실제 펀드 자금을 운영하는 만큼 실력과 책임감 중요)', + '금융 IT팀': '빅데이터와 ai를 활용해 다양한 금융 서비스를 제작', + 매크로팀: + '경제 지표(금리, 환율, 물가 등) 분석 / 국내외 거시경제 동향 분석 및 전망 / 중앙은행 정책(통화정책, 금리 정책) 모니터링 / 리서치 리포트 작성 및 투자 전략 제안 / 글로벌 경제 및 정치 이슈 분석 / 타 대학과 매크로 리포트 교류전 참여 (전반적인 경제에 대한 지식이 있어야 하고, 리포트 작성에 다소 시간 할애 필요)', + 트레이딩팀: + '실전 트레이딩을 위한 학습 / 차트 수급 및 기술적 분석 활용 / 기간 내 특징주 이슈 정리 / 매크로 / 산업 트렌드 분석 / 각종 투자대회 입상 목표 / 매 주 트레이딩 학습 적용 및 피드백 매매일지 작성 / 하나증권 본부장님 지원하에 트레이딩 교육 예정 (실제 개인 투자를 전제로 하며 모의투자대회 입상을 목표로 하는 만큼 높은 수준의 인원 요구)', +}; + +const Info = ({ team }) => { + return ( +
+ {team} + {teamInfo[team]} +
+ ); +}; + +export default Info; diff --git a/frontend/src/components/external/Info.module.css b/frontend/src/components/external/Info.module.css new file mode 100644 index 00000000..5e67eb9b --- /dev/null +++ b/frontend/src/components/external/Info.module.css @@ -0,0 +1,21 @@ +.container { + display: flex; + flex-direction: column; + gap: 45px; +} + +.title { + font-weight: 700; + font-size: 40px; + line-height: 146%; + color: #142d56; +} + +.desc { + font-weight: 500; + font-size: 20px; + line-height: 180%; + letter-spacing: -0.03em; + color: #000000; + word-break: keep-all; +} diff --git a/frontend/src/components/external/MemberCard.jsx b/frontend/src/components/external/MemberCard.jsx new file mode 100644 index 00000000..2fd3b6e6 --- /dev/null +++ b/frontend/src/components/external/MemberCard.jsx @@ -0,0 +1,21 @@ +import styles from './MemberCard.module.css'; + +const MemberCard = ({ datas }) => { + return ( +
+ {datas.map((data, index) => { + return ( +
+
+
+ {data.role} + {data.name} +
+
+ ); + })} +
+ ); +}; + +export default MemberCard; diff --git a/frontend/src/components/external/MemberCard.module.css b/frontend/src/components/external/MemberCard.module.css new file mode 100644 index 00000000..06496c5f --- /dev/null +++ b/frontend/src/components/external/MemberCard.module.css @@ -0,0 +1,41 @@ +.container { + max-width: 850px; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: 10px; +} + +.cardSection { + display: flex; + flex-direction: column; + gap: 15px; +} + +.card { + width: 260px; + height: 340px; + background-color: #f4f4f4; +} + +.nameSection { + display: flex; + justify-content: center; + gap: 10px; + margin-bottom: 15px; +} + +.role { + font-weight: 400; + font-size: 20px; + line-height: 146%; + letter-spacing: -0.04em; + color: #616161; +} + +.name { + font-weight: 700; + font-size: 20px; + line-height: 146%; + letter-spacing: -0.04em; + color: #171717; +} diff --git a/frontend/src/components/external/PortfolioItem.jsx b/frontend/src/components/external/PortfolioItem.jsx new file mode 100644 index 00000000..f43fdb59 --- /dev/null +++ b/frontend/src/components/external/PortfolioItem.jsx @@ -0,0 +1,21 @@ +import styles from './PortfolioItem.module.css'; +import profile from '../../assets/profile-image.png'; + +const PortfolioItem = ({ data }) => { + return ( +
+
+ 프로필 이미지 +
+
+ {data.role} + {data.time}분전 +
+ {data.title} +
+
+
+ ); +}; + +export default PortfolioItem; diff --git a/frontend/src/components/external/PortfolioItem.module.css b/frontend/src/components/external/PortfolioItem.module.css new file mode 100644 index 00000000..014a57ea --- /dev/null +++ b/frontend/src/components/external/PortfolioItem.module.css @@ -0,0 +1,50 @@ +.container { + display: flex; + flex-direction: column; + border-bottom: 1px solid #cdd4db; + padding-bottom: 35px; + cursor: pointer; +} + +.preview { + display: flex; + gap: 20px; +} + +.image { + width: 48px; + height: 48px; +} + +.titleSection { + display: flex; + flex-direction: column; + gap: 15px; +} + +.author { + display: flex; + gap: 10px; + align-items: center; +} + +.role { + font-weight: 500; + font-size: 16px; + line-height: 100%; + color: #828282; +} + +.time { + font-weight: 400; + font-size: 12px; + line-height: 100%; + color: #aaaaaa; +} + +.title { + font-weight: 700; + font-size: 20px; + line-height: 100%; + color: #616161; +} diff --git a/frontend/src/pages/external/External.module.css b/frontend/src/pages/external/External.module.css index 91db5617..bdc0d9cf 100644 --- a/frontend/src/pages/external/External.module.css +++ b/frontend/src/pages/external/External.module.css @@ -1,14 +1,16 @@ .container { - width: 100vw; - height: 100vh; + width: 100%; + min-height: 100vh; display: flex; flex-direction: column; + background-color: #ffffff; } .header { - background: url('../../assets/external/external-detail-image.png') center - no-repeat; - height: 25%; + background: url('../../assets/external/external-detail-image.png') no-repeat + center; + background-size: cover; + height: 250px; display: flex; flex-direction: column; align-items: center; @@ -26,3 +28,47 @@ .divider { width: 250px; } + +.info { + display: flex; + padding: 80px 100px; + gap: 45px; +} + +.logoSection { + display: flex; + gap: 10px; + width: 340px; + height: 58px; +} + +.logo { + width: 56px; + height: 56px; +} + +.name { + word-break: keep-all; + color: #142d56; + font-weight: 700; + font-size: 28px; + line-height: 100%; +} + +.filter { + display: flex; + flex-direction: column; + gap: 30px; +} + +.content { + max-width: 700px; +} + +.portfolio { + display: flex; + flex-direction: column; + justify-content: center; + padding: 80px 100px; + gap: 45px; +} diff --git a/frontend/src/pages/external/Intro.jsx b/frontend/src/pages/external/Intro.jsx index bea3c185..c209e34b 100644 --- a/frontend/src/pages/external/Intro.jsx +++ b/frontend/src/pages/external/Intro.jsx @@ -1,13 +1,40 @@ import styles from './External.module.css'; +import Filter from '../../components/external/Filter'; +import Info from '../../components/external/Info'; +import { useState } from 'react'; +import Logo from '../../assets/sejong_logo.png'; + +const teams = [ + '증권 1팀', + '증권 2팀', + '증권 3팀', + '자산 운용팀', + '금융 IT팀', + '매크로팀', + '트레이딩팀', +]; const Intro = () => { + const [selected, setSelected] = useState(teams[0]); + return (
동아리 소개
-
+
+
+
+ 세종투자연구회 + Sejong Investment Scholars Club +
+ +
+
+ +
+
); }; diff --git a/frontend/src/pages/external/Leaders.jsx b/frontend/src/pages/external/Leaders.jsx index 9509a7d2..e8358206 100644 --- a/frontend/src/pages/external/Leaders.jsx +++ b/frontend/src/pages/external/Leaders.jsx @@ -1,13 +1,25 @@ import styles from './External.module.css'; +import Filter from '../../components/external/Filter'; +import MemberCard from '../../components/external/MemberCard'; +import { useState } from 'react'; +import { executivesByGeneration } from '../../utils/executiveByGeneration'; + +const cohort = Array.from({ length: 24 }, (_, i) => `${24 - i}기`); const Leaders = () => { + const [selected, setSelected] = useState(cohort[0]); return (
임원진 소개
-
+
+
+ +
+ +
); }; diff --git a/frontend/src/pages/external/MonthlyReport.module.css b/frontend/src/pages/external/MonthlyReport.module.css index 2da1577d..13da911d 100644 --- a/frontend/src/pages/external/MonthlyReport.module.css +++ b/frontend/src/pages/external/MonthlyReport.module.css @@ -9,6 +9,7 @@ background: linear-gradient(rgba(0, 0, 0, 0.82), rgba(0, 0, 0, 0.82)), url('../../assets/external/monthly-report.png') center no-repeat; + background-size: cover; min-height: 300px; display: flex; flex-direction: column; diff --git a/frontend/src/pages/external/Portfolio.jsx b/frontend/src/pages/external/Portfolio.jsx index 0f69f21f..64a91c1b 100644 --- a/frontend/src/pages/external/Portfolio.jsx +++ b/frontend/src/pages/external/Portfolio.jsx @@ -1,13 +1,76 @@ import styles from './External.module.css'; +import SearchBar from '../../components/Board/SearchBar'; +import Pagination from '../../components/stockgame/Pagination'; +import PortfolioItem from '../../components/external/PortfolioItem'; +import { useState } from 'react'; + +const mockPortfolio = [ + { + role: '운영진', + time: '2', + title: '자산 배분 전략 및 성과 보고서', + }, + { + role: '운영진', + time: '3', + title: '미래를 디자인하는 투자 여정', + }, + { + role: '운영진', + time: '4', + title: '목표 수익률 달성을 위한 핵심 자산 포트폴리오', + }, + { + role: '운영진', + time: '5', + title: '리스크 대비 성장형 포트폴리오 분석', + }, +]; const Portfolio = () => { + const [currentPage, setCurrentPage] = useState(1); + const [searchTerm, setSearchTerm] = useState(''); + const itemsPerPage = 4; + + const filteredPortfolio = mockPortfolio.filter((item) => + item.title.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + const totalPages = Math.ceil(filteredPortfolio.length / itemsPerPage); + const currentData = filteredPortfolio.slice( + (currentPage - 1) * itemsPerPage, + currentPage * itemsPerPage + ); + + const handlePageChange = (page) => { + setCurrentPage(page); + }; + + const handleSearchChange = (term) => { + setSearchTerm(term); + setCurrentPage(1); + }; + return (
운용 포트폴리오
-
+
+ + {currentData.map((item, index) => { + return ; + })} + +
); }; diff --git a/frontend/src/utils/executiveByGeneration.js b/frontend/src/utils/executiveByGeneration.js new file mode 100644 index 00000000..c19c2c71 --- /dev/null +++ b/frontend/src/utils/executiveByGeneration.js @@ -0,0 +1,23 @@ +export const executivesByGeneration = { + '24기': [ + { role: '회장', name: '김진성' }, + { role: '증권 1팀장', name: '김진성' }, + { role: '증권 2팀장', name: '김진성' }, + { role: '증권 3팀장', name: '김진성' }, + { role: '금융 IT팀장', name: '김진성' }, + { role: '자산운용팀장', name: '김진성' }, + { role: '매크로팀장', name: '김진성' }, + { role: '트레이딩팀장', name: '김진성' }, + ], + + '23기': [ + { role: '회장', name: '김진성' }, + { role: '증권 1팀장', name: '김진성' }, + { role: '증권 2팀장', name: '김진성' }, + { role: '증권 3팀장', name: '김진성' }, + { role: '금융 IT팀장', name: '김진성' }, + { role: '자산운용팀장', name: '김진성' }, + { role: '매크로팀장', name: '김진성' }, + { role: '트레이딩팀장', name: '김진성' }, + ], +};