diff --git a/app/[locale]/search/fetchContent.ts b/app/[locale]/search/fetchContent.ts
new file mode 100644
index 00000000..8a3b8751
--- /dev/null
+++ b/app/[locale]/search/fetchContent.ts
@@ -0,0 +1,116 @@
+import {
+ searchAbout,
+ searchNotice,
+ searchNews,
+ searchMember,
+ searchResearch,
+ searchAdmissions,
+ searchAcademics,
+} from '@/apis/search';
+import { getSeminarPosts } from '@/apis/seminar';
+
+import {
+ AboutSearchResult,
+ NoticeSearchResult,
+ NewsSearchResult,
+ MemberSearchResult,
+ ResearchSearchResult,
+ AdmissionsSearchResult,
+ AcademicsSearchResult,
+} from '@/types/search';
+import { SeminarList } from '@/types/seminar';
+
+import { TreeNode } from './helper/SearchSubNavbar';
+
+type SectionContent = [
+ about?: AboutSearchResult,
+ notice?: NoticeSearchResult,
+ news?: NewsSearchResult,
+ seminar?: SeminarList,
+ member?: MemberSearchResult,
+ research?: ResearchSearchResult,
+ admission?: AdmissionsSearchResult,
+ academics?: AcademicsSearchResult,
+];
+
+// TOOD: 리팩터링
+export default async function fetchContent(keyword: string, tag?: string[]) {
+ const noTag = tag === undefined || tag.length === 0;
+
+ // fetch
+ const sectionContent: SectionContent = await Promise.all([
+ isSectionVisible('소개', tag) ? searchAbout({ keyword, number: 3, amount: 200 }) : undefined,
+ isSectionVisible('소식', tag) ? searchNotice({ keyword, number: 3, amount: 200 }) : undefined,
+ isSectionVisible('소식', tag) ? searchNews({ keyword, number: 3, amount: 200 }) : undefined,
+ isSectionVisible('소식', tag) ? getSeminarPosts({ keyword, pageNum: '1' }) : undefined,
+ isSectionVisible('구성원', tag)
+ ? searchMember({ keyword, number: 10, amount: 200 })
+ : undefined,
+ isSectionVisible('연구', tag) ? searchResearch({ keyword, number: 3, amount: 200 }) : undefined,
+ isSectionVisible('입학', tag)
+ ? searchAdmissions({ keyword, number: 3, amount: 200 })
+ : undefined,
+ isSectionVisible('학사 및 교과', tag)
+ ? searchAcademics({ keyword, number: 3, amount: 200 })
+ : undefined,
+ ]);
+
+ // 전체 개수 계산
+ const total = sectionContent.reduce((prev, cur) => prev + (cur?.total ?? 0), 0);
+
+ // 서브네비 구성
+ const node: TreeNode[] = [];
+ node.push({
+ name: `전체`,
+ size: tag === undefined || tag.length === 0 ? total : undefined,
+ bold: noTag,
+ });
+ node.push({
+ name: `소개`,
+ size: sectionContent[0]?.total,
+ bold: !noTag && sectionContent[0] !== undefined,
+ });
+
+ const noticeTotal = sectionContent[1]?.total;
+ const newsTotal = sectionContent[2]?.total;
+ const seminarTotal = sectionContent[3]?.total;
+ const sectionTotal = noticeTotal && (noticeTotal ?? 0) + (newsTotal ?? 0) + (seminarTotal ?? 0);
+ node.push({
+ name: `소식`,
+ size: sectionTotal,
+ children: [
+ { name: `공지사항`, size: noticeTotal },
+ { name: `새 소식`, size: newsTotal },
+ { name: `세미나`, size: seminarTotal },
+ ],
+ bold: !noTag && sectionContent[1] !== undefined,
+ });
+
+ node.push({
+ name: `구성원`,
+ size: sectionContent[4]?.total,
+ bold: !noTag && sectionContent[4] !== undefined,
+ });
+ node.push({
+ name: `연구`,
+ size: sectionContent[5]?.total,
+ bold: !noTag && sectionContent[5] !== undefined,
+ });
+ node.push({
+ name: `입학`,
+ size: sectionContent[6]?.total,
+ bold: !noTag && sectionContent[6] !== undefined,
+ });
+ node.push({
+ name: `학사 및 교과`,
+ size: sectionContent[7]?.total,
+ bold: !noTag && sectionContent[7] !== undefined,
+ });
+
+ return { sectionContent, node, total };
+}
+
+const isSectionVisible = (
+ sectionName: '소개' | '소식' | '구성원' | '연구' | '입학' | '학사 및 교과',
+ tagList?: string[],
+) => tagList === undefined || tagList.length === 0 || tagList.includes(sectionName);
diff --git a/app/[locale]/search/helper/BasicRow.tsx b/app/[locale]/search/helper/BasicRow.tsx
index e4b572d3..2dd0449a 100644
--- a/app/[locale]/search/helper/BasicRow.tsx
+++ b/app/[locale]/search/helper/BasicRow.tsx
@@ -2,6 +2,7 @@ import { Link } from '@/navigation';
import RangeBolded from '@/components/common/RangeBolded';
+import { getPath } from '@/utils/page';
import { SegmentNode } from '@/utils/segmentNode';
type BasicRowProps = {
@@ -15,10 +16,15 @@ type BasicRowProps = {
export default function BasicRow({ href, title, node, ...description }: BasicRowProps) {
return (
-
-
{title}
+
+
+ {title}
+
-
{`${node.parent?.name} > ${node?.name}`}
-
+
{`${node.parent?.name} > ${node?.name}`}
+
);
}
diff --git a/app/[locale]/search/helper/CircleTitle.tsx b/app/[locale]/search/helper/CircleTitle.tsx
index 8ab9e550..58d7dd41 100644
--- a/app/[locale]/search/helper/CircleTitle.tsx
+++ b/app/[locale]/search/helper/CircleTitle.tsx
@@ -2,7 +2,7 @@ export default function CircleTitle({ title, size }: { title: string; size?: num
// TODO: 번역 적용
// const t = useTranslations();
return (
-
+
{title}
diff --git a/app/[locale]/search/helper/NavbarButton.tsx b/app/[locale]/search/helper/NavbarButton.tsx
new file mode 100644
index 00000000..3368728c
--- /dev/null
+++ b/app/[locale]/search/helper/NavbarButton.tsx
@@ -0,0 +1,28 @@
+'use client';
+
+import { CSSProperties } from 'react';
+
+import { TreeNode } from './SearchSubNavbar';
+
+export default function NavbarButton({ node, style }: { node: TreeNode; style: CSSProperties }) {
+ return (
+
+ );
+}
+
+const scrollToSection = (id: string) => {
+ const target = document.getElementById(id.replace(' ', '_'));
+ if (target === null) return;
+
+ const pos = target.getBoundingClientRect();
+ window.scrollTo({ top: pos.top + window.scrollY - 100, behavior: 'smooth' });
+};
diff --git a/app/[locale]/search/helper/NewsRow.tsx b/app/[locale]/search/helper/NewsRow.tsx
new file mode 100644
index 00000000..64131c50
--- /dev/null
+++ b/app/[locale]/search/helper/NewsRow.tsx
@@ -0,0 +1,67 @@
+import { Link } from '@/navigation';
+
+import ImageWithFallback from '@/components/common/ImageWithFallback';
+
+export interface NewsRowProps {
+ href: string;
+ title: string;
+ date: Date;
+ imageURL: string | null;
+
+ description: {
+ partialDescription: string;
+ boldStartIndex: number;
+ boldEndIndex: number;
+ };
+}
+
+export default function NewsRow({
+ href,
+ title,
+ description: { partialDescription, boldEndIndex, boldStartIndex },
+ date,
+ imageURL,
+}: NewsRowProps) {
+ const dateStr = date.toLocaleDateString('ko', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ weekday: 'long',
+ });
+
+ return (
+
+
+
+
+
+
+
+
{title}
+
+
+
+
+ {partialDescription.slice(0, boldStartIndex)}
+
+ {partialDescription.slice(boldStartIndex, boldEndIndex)}
+
+ {partialDescription.slice(boldEndIndex)}
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/[locale]/search/helper/NoSearchResultError.tsx b/app/[locale]/search/helper/NoSearchResultError.tsx
new file mode 100644
index 00000000..7d19b8da
--- /dev/null
+++ b/app/[locale]/search/helper/NoSearchResultError.tsx
@@ -0,0 +1,10 @@
+import MagnificentGlass from '@/public/image/search/magnificent_glass.svg';
+
+export default function NoSearchResultError() {
+ return (
+
+ );
+}
diff --git a/app/[locale]/search/helper/NoticeRow.tsx b/app/[locale]/search/helper/NoticeRow.tsx
index 32c22c7e..06712c05 100644
--- a/app/[locale]/search/helper/NoticeRow.tsx
+++ b/app/[locale]/search/helper/NoticeRow.tsx
@@ -18,11 +18,12 @@ const noticePath = getPath(notice);
export default function NoticeRow({ id, title, dateStr, ...description }: NoticeRowProps) {
const date = new Date(dateStr);
+
return (
{title}
-