Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ 통합검색 구현, 디자인 반영 #179

Merged
merged 13 commits into from
Mar 27, 2024
Merged
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches:
- main
- fix/image
- feat/search

jobs:
build:
Expand Down
21 changes: 21 additions & 0 deletions apis/image.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use server';

import { postRequest } from '.';

export const postImage = async (formData: FormData) => {
const resp = (await postRequest('/file/upload', {
body: formData,
jsessionID: true,
})) as ImageUploadResponse;

return resp;
};

type ImageUploadResponse = {
errorMessage: string;
result: {
url: string;
name: string;
size: number;
}[];
};
10 changes: 9 additions & 1 deletion app/[locale]/community/news/[id]/NewsViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,15 @@ export default async function NewsViewer({ id, searchParams }: NewsPostPageProps
style={{ paddingBottom: PAGE_PADDING_BOTTOM_PX }}
>
<Attachments files={news.attachments} />
<HTMLViewer htmlContent={news.description} className="mb-10" />
<HTMLViewer
htmlContent={news.description}
topRightContent={
news.imageURL
? { type: 'image', url: news.imageURL, widthPX: 320, heightPX: 240 }
: undefined
}
className="mb-10"
/>
<StraightNode />
<Tags tags={news.tags} margin="mt-3 ml-6" searchPath={newsPath} />
<PostFooter post={news} postType="news" id={id.toString()} margin="mt-12" />
Expand Down
12 changes: 9 additions & 3 deletions app/[locale]/community/seminar/SeminarContent.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Fragment } from 'react';

import { getSeminarPosts } from '@/apis/seminar';

import SeminarRow from '@/app/[locale]/community/seminar/helper/SeminarRow';
Expand Down Expand Up @@ -29,10 +31,14 @@ export default async function SeminarContent({
<NoSearchResult />
) : (
searchList.map((post, index) => (
<div key={post.id}>
<Fragment key={post.id}>
{post.isYearLast && <SeminarYear index={index} startDate={post.startDate} />}
<SeminarRow seminar={post} hideDivider={false} />
</div>
<div
className={`border-neutral-200 py-[1.2rem] ${!post.isYearLast ? 'border-t' : null}`}
>
<SeminarRow seminar={post} />
</div>
</Fragment>
))
)}
</div>
Expand Down
10 changes: 2 additions & 8 deletions app/[locale]/community/seminar/helper/SeminarRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,18 @@ import { seminar } from '@/utils/segmentNode';

export interface SeminarRowProps {
seminar: SeminarPreview;
hideDivider: boolean;
}

const seminarPath = getPath(seminar);

export default function SeminarRow({
seminar: { id, isYearLast, imageURL, title, name, affiliation, startDate, location },
hideDivider,
seminar: { id, imageURL, title, name, affiliation, startDate, location },
}: SeminarRowProps) {
const seminarPostPath = `${seminarPath}/${id}`;

return (
<Link href={seminarPostPath}>
<article
className={`group flex flex-col gap-4 border-neutral-200 py-[1.2rem] sm:flex-row sm:gap-5 ${
!isYearLast && !hideDivider ? 'border-t' : null
}`}
>
<article className="group flex flex-col gap-4 sm:flex-row sm:gap-5">
<ImageCell imageURL={imageURL} />
<div className="flex flex-col items-start gap-1 break-all sm:gap-0">
<TitleCell title={title} />
Expand Down
8 changes: 2 additions & 6 deletions app/[locale]/search/AboutSection.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { searchAbout } from '@/apis/search';

import { AboutPreview } from '@/types/search';
import { AboutPreview, AboutSearchResult } from '@/types/search';

import { getPath } from '@/utils/page';
import {
Expand All @@ -17,9 +15,7 @@ import {
import BasicRow from './helper/BasicRow';
import Section from './helper/Section';

export default async function AboutSection({ keyword }: { keyword: string }) {
const about = await searchAbout({ keyword, number: 3, amount: 200 });

export default async function AboutSection({ about }: { about: AboutSearchResult }) {
return (
<Section title="소개" size={about.total}>
<div className="flex flex-col gap-9">
Expand Down
70 changes: 63 additions & 7 deletions app/[locale]/search/AcademicSection.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
import { searchAcademics } from '@/apis/search';
import { Academic, AcademicsSearchResult } from '@/types/search';

import { getPath } from '@/utils/page';
import { undergraduateGuide } from '@/utils/segmentNode';
import {
curriculum,
degree,
generalStudies,
graduateCourseChanges,
graduateCourses,
graduateGuide,
graduateScholarship,
undergraduateCourseChanges,
undergraduateCourses,
undergraduateGuide,
undergraduateScholarship,
} from '@/utils/segmentNode';

import BasicRow from './helper/BasicRow';
import Section from './helper/Section';

export default async function AcademicSection({ keyword }: { keyword: string }) {
const academic = await searchAcademics({ keyword, number: 3, amount: 200 });

// TODO: 장학 제도 등 상세 페이지로 연결
export default async function AcademicSection({ academic }: { academic: AcademicsSearchResult }) {
return (
<Section title="학사 및 교과" size={academic.total}>
<div className="flex flex-col gap-7">
{academic.results.map((result) => {
// TODO
const node = undergraduateGuide;
const node = toNode(result);

return (
<BasicRow
Expand All @@ -30,3 +40,49 @@ export default async function AcademicSection({ keyword }: { keyword: string })
</Section>
);
}

const toNode = (academic: Academic) => {
// 공통

// 학부/대학원 안내
if (academic.academicType === 'GUIDE')
return academic.studentType === 'UNDERGRADUATE' ? undergraduateGuide : graduateGuide;

// 교과과정
if (academic.postType === 'COURSE')
return academic.studentType === 'UNDERGRADUATE' ? undergraduateCourses : graduateCourses;

// 교과목 변경 내역
if (academic.academicType === 'COURSE_CHANGES')
return academic.studentType === 'UNDERGRADUATE'
? undergraduateCourseChanges
: graduateCourseChanges;

// 장학 제도
if (academic.postType === 'SCHOLARSHIP')
return academic.studentType === 'UNDERGRADUATE'
? undergraduateScholarship
: graduateScholarship;

// 학부 전용

// 전공이수표준형태
if (academic.academicType === 'CURRICULUM') return curriculum;

// 필수 교양 과목
if (
academic.academicType === 'GENERAL_STUDIES_REQUIREMENTS' ||
academic.academicType === 'GENERAL_STUDIES_REQUIREMENTS_SUBJECT_CHANGES'
)
return generalStudies;

// 졸업 규정
if (
academic.academicType === 'DEGREE_REQUIREMENTS' ||
academic.academicType === 'DEGREE_REQUIREMENTS_YEAR_LIST'
)
return degree;

// TODO: fallback 없애기
return undergraduateCourses;
};
10 changes: 6 additions & 4 deletions app/[locale]/search/AdmissionSection.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { searchAdmissions } from '@/apis/search';
import { AdmissionsSearchResult } from '@/types/search';

import { getPath } from '@/utils/page';
import { admissions } from '@/utils/segmentNode';

import BasicRow from './helper/BasicRow';
import Section from './helper/Section';

export default async function AdmissionSection({ keyword }: { keyword: string }) {
const admission = await searchAdmissions({ keyword, number: 3, amount: 200 });

export default async function AdmissionSection({
admission,
}: {
admission: AdmissionsSearchResult;
}) {
return (
<Section title="입학" size={admission.total}>
<div className="flex flex-col gap-7">
Expand Down
39 changes: 21 additions & 18 deletions app/[locale]/search/CommunitySection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,41 @@ import Link from 'next/link';
import { useTranslations } from 'next-intl';
import { ReactNode } from 'react';

import { searchNews, searchNotice } from '@/apis/search';
import { getSeminarPosts } from '@/apis/seminar';
import { NewsSearchResult, NoticeSearchResult } from '@/types/search';
import { SeminarList } from '@/types/seminar';

import { getPath } from '@/utils/page';
import { news, notice, seminar } from '@/utils/segmentNode';

import CircleTitle from './helper/CircleTitle';
import Divider from './helper/Divider';
import NewsRow from './helper/NewsRow';
import NoticeRow from './helper/NoticeRow';
import Section from './helper/Section';
import NewsRow from '../community/news/helper/NewsRow';
import SeminarRow from '../community/seminar/helper/SeminarRow';

const newsPath = getPath(news);
const noticePath = getPath(notice);
const seminarPath = getPath(seminar);

export default async function CommunitySection({ keyword }: { keyword: string }) {
const [notice, news, seminar] = await Promise.all([
searchNotice({ keyword, number: 3, amount: 200 }),
searchNews({ keyword, number: 3, amount: 200 }),
getSeminarPosts({ keyword, pageNum: '1' }),
]);

export default async function CommunitySection({
keyword,
notice,
news,
seminar,
}: {
keyword: string;
notice: NoticeSearchResult;
news: NewsSearchResult;
seminar: SeminarList;
}) {
return (
<Section title="소식" size={notice.total + news.total + seminar.total}>
<CommunitySubSection
title="공지사항"
size={notice.total}
href={`${noticePath}?keyword=${keyword}`}
divider
divider={news.total != 0 || seminar.total != 0}
>
{notice.results.map((notice) => (
<NoticeRow
Expand All @@ -49,19 +53,16 @@ export default async function CommunitySection({ keyword }: { keyword: string })
title="새 소식"
size={news.total}
href={`${newsPath}?keyword=${keyword}`}
divider
divider={seminar.total != 0}
>
{news.results.map((news) => (
<NewsRow
key={news.id}
href={`${newsPath}/${news.id}`}
title={news.title}
description={news.partialDescription}
tags={news.tags}
description={news}
date={new Date(news.date)}
imageURL={news.imageUrl}
descriptionBold={{ startIndex: news.boldStartIndex, endIndex: news.boldEndIndex }}
hideDivider
/>
))}
</CommunitySubSection>
Expand All @@ -72,7 +73,7 @@ export default async function CommunitySection({ keyword }: { keyword: string })
href={`${seminarPath}?keyword=${keyword}`}
>
{seminar.searchList.slice(0, 3).map((seminar) => (
<SeminarRow key={seminar.id} seminar={seminar} hideDivider />
<SeminarRow key={seminar.id} seminar={seminar} />
))}
</CommunitySubSection>
</Section>
Expand All @@ -97,7 +98,9 @@ const CommunitySubSection = ({
return (
<>
<CircleTitle title={title} size={size} />
<div className="ml-5 mr-10 mt-8 flex flex-col gap-9">{children}</div>
<div className="ml-5 mr-10 mt-8 flex flex-col gap-7" id={`nav_${title.replace(' ', '_')}`}>
{children}
</div>
<MoreResultLink href={href} />
{divider && <Divider />}
</>
Expand Down
20 changes: 9 additions & 11 deletions app/[locale]/search/MemberSection.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import Link from 'next/link';

import { searchMember } from '@/apis/search';

import ImageWithFallback from '@/components/common/ImageWithFallback';

import { Member } from '@/types/search';
import { Member, MemberSearchResult } from '@/types/search';

import { getPath } from '@/utils/page';
import { faculty, staff } from '@/utils/segmentNode';
Expand All @@ -13,14 +11,12 @@ import CircleTitle from './helper/CircleTitle';
import Divider from './helper/Divider';
import Section from './helper/Section';

export default async function MemberSection({ keyword }: { keyword: string }) {
const resp = await searchMember({ keyword, number: 10, amount: 200 });

const professorList = resp.results.filter((x) => x.memberType === 'PROFESSOR');
const staffList = resp.results.filter((x) => x.memberType === 'STAFF');
export default async function MemberSection({ member }: { member: MemberSearchResult }) {
const professorList = member.results.filter((x) => x.memberType === 'PROFESSOR');
const staffList = member.results.filter((x) => x.memberType === 'STAFF');

return (
<Section title="구성원" size={resp.total}>
<Section title="구성원" size={member.total}>
{professorList.length !== 0 && (
<>
<CircleTitle title="교수진" />
Expand Down Expand Up @@ -53,7 +49,7 @@ const MemberCell = ({ name, academicRankOrRole, imageURL, memberType, id }: Memb
const href = `${memberType === 'PROFESSOR' ? facultyPath : staffPath}/${id}`;

return (
<Link className="flex flex-col gap-3" href={href}>
<Link className="group flex flex-col gap-3" href={href}>
<ImageWithFallback
src={imageURL}
alt={`${name} 프로필`}
Expand All @@ -68,7 +64,9 @@ const MemberCell = ({ name, academicRankOrRole, imageURL, memberType, id }: Memb
}}
/>
<div className="flex items-end gap-1">
<h3 className="text-[1.04169rem] font-bold text-neutral-950">{name}</h3>
<h3 className="text-[1.04169rem] font-bold text-neutral-950 group-hover:underline">
{name}
</h3>
<p className="text-md font-normal text-neutral-500">{academicRankOrRole}</p>
</div>
</Link>
Expand Down
8 changes: 2 additions & 6 deletions app/[locale]/search/ResearchSection.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { searchResearch } from '@/apis/search';

import { ResearchType } from '@/types/search';
import { ResearchSearchResult, ResearchType } from '@/types/search';

import { getPath } from '@/utils/page';
import {
Expand All @@ -13,9 +11,7 @@ import {
import BasicRow from './helper/BasicRow';
import Section from './helper/Section';

export default async function ResearchSection({ keyword }: { keyword: string }) {
const research = await searchResearch({ keyword, number: 3, amount: 200 });

export default async function ResearchSection({ research }: { research: ResearchSearchResult }) {
return (
<Section title="연구" size={research.total}>
<div className="flex flex-col gap-7">
Expand Down
Loading
Loading