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 (
+
+ onChange(label)}
+ aria-pressed={isActive}
+ >
+ {label}
+
+
+ );
+ })}
+
+
+
+ );
+};
+
+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: '김진성' },
+ ],
+};