Skip to content

Commit

Permalink
Merge pull request #37 from Pro-Pofol/feature/#28-application_api
Browse files Browse the repository at this point in the history
지원서 자료 관련 api 연동
  • Loading branch information
eternrust committed Jun 4, 2024
2 parents 02ea2cd + 2c2f89e commit 15a3c59
Show file tree
Hide file tree
Showing 24 changed files with 500 additions and 208 deletions.
131 changes: 81 additions & 50 deletions src/app/application/ShowSection.tsx
Original file line number Diff line number Diff line change
@@ -1,80 +1,111 @@
'use client'
import { ApplicationBox } from "@/components"
import { getApplicationData } from "@/services"
import { ApplicationFileType, ApplicationPreviewType, MajorType } from "@/types"
import { getCookie, toast } from "@/utils"
import { useSearchParams } from "next/navigation"
import { useEffect, useState } from "react"
import { useCallback, useEffect, useState } from "react"

const ApplyData: ApplicationPreviewType[] = [
{
"post_id": 1,
"post_post_type": "Portfolio",
"post_title": "엄청나고 위대한 포트폴리오 자료",
"post_major": "Backend",
"post_created_at": "2024-05-27T02:48:52.347Z",
"user_oauth_id": "110159413387878573726"
},
{
"post_id": 2,
"post_post_type": "Portfolio",
"post_title": "야호",
"post_major": "Frontend",
"post_created_at": "2024-05-27T04:26:28.899Z",
"user_oauth_id": "107359038156703139645"
},
{
"post_id": 3,
"post_post_type": "Portfolio",
"post_title": "테스트...",
"post_major": "Frontend",
"post_created_at": "2024-05-27T04:47:00.694Z",
"user_oauth_id": "107359038156703139645"
}
]

const majorData: MajorType[] = ['Frontend', 'Backend', 'Android', 'iOS', 'CrossPlatform', 'AI', 'DevOps', 'Design', 'Game', 'Blockchain']
interface ApplicationDataModal {
kind: 'everything' | ApplicationFileType
major: MajorType,
keyword: string
sort: 'ASC' | 'DESC'
}

const ShowSection = () => {
const [orderType, setOrderType] = useState<'first' | 'last'>('first')
const [selectedKind, setSelectedKind] = useState<'everything' | ApplicationFileType>('everything')
const [selectedMajor, setSelectedMajor] = useState<Partial<Record<MajorType, boolean>>>({})
const [hasToken, setHasToken] = useState<boolean>(false)
const [orderType, setOrderType] = useState<'ASC' | 'DESC'>('ASC')
const [searchWord, setSearchWord] = useState<string>('')
const [applicationData, setApplicationData] = useState<ApplicationPreviewType[]>([])
const searchParams = useSearchParams()

const getData = useCallback(async (data: ApplicationDataModal) => {
const token = getCookie('access_token')
if (!token) {
toast.error('토큰이 없습니다!')
return
}

const newData = await getApplicationData(token, data)
.then(res => {
toast.success('데이터를 성공적으로 불러왔습니다.')
return res.data.posts
}).catch(() => {
toast.error('데이터를 불러오는데 문제가 생겼습니다.')
return []
})

setApplicationData(newData)
}, [orderType])

useEffect(() => {
const token = getCookie('access_token')
const data: ApplicationDataModal = {
kind: 'everything',
major: 'Frontend',
keyword: '',
sort: orderType
}

if (searchParams.has('word')) {
setSearchWord(searchParams.get('word') || '')
data.keyword = searchParams.get('word') || ''
}
if (searchParams.has('kind') && ['everything', 'Portfolio', 'PersonalStatement', 'Resume'].includes(searchParams.get('kind') as string)) {
setSelectedKind(searchParams.get('kind') as ('everything' | ApplicationFileType))
data.kind = searchParams.get('kind') as ('everything' | ApplicationFileType)
}
if (searchParams.has('major') && majorData.some(v => searchParams.getAll('major').includes(v))) {
setSelectedMajor(majorData.filter(v => searchParams.getAll('major').includes(v)).reduce((acc, v) => ({ ...acc, [v]: true }), {}))
if (searchParams.has('major')) {
data.major = searchParams.get('major') as MajorType
}

if (token && data.keyword.length) {
getData(data)
}
}, [searchParams])
setHasToken(!!token)
}, [searchParams, orderType])

return (
<section className="relative pb-[120px] w-full">
<div className="py-6 px-10 flex justify-between items-center sticky bg-white top-0 left-0 w-full h-fit flex-wrap gap-2">
<div className="flex gap-x-3 gap-y-2 flex-wrap break-keep">
{searchWord && <span className="text-bodyLarge text-blue500">{searchWord}”에 대한</span>}
<span className="text-bodyLarge text-black">182개의 지원서 자료</span>
<span className="text-bodyLarge text-black">{applicationData.length}개의 지원서 자료</span>
</div>
<div className="p-1 gap-0.5 flex rounded-full border h-12 border-gray200 bg-gray50 text-bodySmall relative text-gray600 ml-auto">
<div className={`absolute top-[2px] ${orderType === 'first' ? 'left-[3px]' : 'left-[80px]'} border border-gray100 bg-white py-2 px-4 text-transparent rounded-full transition-all`}>{orderType === 'first' ? '최신순' : '오래된순'}</div>
<span className={`transition-all py-2 px-4 z-10 cursor-pointer ${orderType === 'first' ? 'text-blue500' : 'text-gray600'}`} onClick={() => setOrderType('first')}>최신순</span>
<span className={`transition-all py-2 px-4 z-10 cursor-pointer ${orderType === 'last' ? 'text-blue500' : 'text-gray600'}`} onClick={() => setOrderType('last')}>오래된순</span>
<div className={`absolute top-[2px] ${orderType === 'ASC' ? 'left-[3px]' : 'left-[80px]'} border border-gray100 bg-white py-2 px-4 text-transparent rounded-full transition-all`}>{orderType === 'ASC' ? '최신순' : '오래된순'}</div>
<span className={`transition-all py-2 px-4 z-10 cursor-pointer ${orderType === 'ASC' ? 'text-blue500' : 'text-gray600'}`} onClick={() => setOrderType('ASC')}>최신순</span>
<span className={`transition-all py-2 px-4 z-10 cursor-pointer ${orderType === 'DESC' ? 'text-blue500' : 'text-gray600'}`} onClick={() => setOrderType('DESC')}>오래된순</span>
</div>
</div>
<section className="grid grid-cols-3 md:grid-cols-2 sm:grid-cols-1 gap-x-3 gap-y-6 px-10">
{
ApplyData.map((item, index) =>
<ApplicationBox
key={index}
{...item}
/>
)
}
</section>
{
hasToken ?
searchWord ?
<section className="grid grid-cols-3 md:grid-cols-2 sm:grid-cols-1 gap-x-3 gap-y-6 px-10">
{
applicationData.map((item, index) =>
<ApplicationBox
key={index}
{...item}
/>
)
}
</section>
:
<section className="px-10">
<div className="border border-gray200 bg-gray50 w-full h-[360px] rounded-xl flex flex-col items-center justify-center gap-2 break-keep px-5">
<span className="text-titleMedium sm:text-titleSmall"><span className="text-blue500">검색어</span>를 입력해주세요.</span>
<span className="text-bodyMedium sm:text-bodySmall text-gray600">그 후 Enter를 누르거나 검색 버튼을 클릭해주세요.</span>
</div>
</section>
:
<section className="px-10">
<div className="border border-gray200 bg-gray50 w-full h-[360px] rounded-xl flex flex-col items-center justify-center gap-2 break-keep px-5">
<span className="text-titleMedium sm:text-titleSmall"><span className="text-blue500">로그인</span>이 필요한 서비스입니다.</span>
<span className="text-bodyMedium sm:text-bodySmall text-gray600">먼저 로그인 후 이용해주세요.</span>
</div>
</section>
}
</section>
)
}
Expand Down
58 changes: 31 additions & 27 deletions src/app/application/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,40 +23,44 @@ const tagToEnglish: Record<KindType, 'everything' | ApplicationFileType> = {

const majorData: MajorType[] = ['Frontend', 'Backend', 'Android', 'iOS', 'CrossPlatform', 'AI', 'DevOps', 'Design', 'Game', 'Blockchain']

interface ApplicationDataModal {
kind: KindType
major: MajorType,
searchWord: string
}

const SideBar = () => {
const [selectedKind, setSelectedKind] = useState<string>('모든 종류')
const [selectedMajor, setSelectedMajor] = useState<Partial<Record<MajorType, boolean>>>({})
const [searchWord, setSearchWord] = useState<string>('')
const [data, setData] = useState<ApplicationDataModal>({
kind: '모든 종류',
major: 'Frontend',
searchWord: ''
})
const router = useRouter()
const searchParams = useSearchParams()

const changeParams = useCallback((name: 'kind' | 'major' | 'word') => (value: string) => {
const changeData = useCallback((name: 'kind' | 'major' | 'searchWord') => (value: string) => {
setData(prev => ({ ...prev, [name]: value }))
}, [])

const changeParams = useCallback(() => {
const param = new URLSearchParams(searchParams.toString())

if (name == 'major') {
setSelectedMajor((prev) => ({ ...prev, [value as MajorType]: !prev[value as MajorType] }))
const majorParams = [...param.getAll('major')]
param.set(name, value)
Object.entries(selectedMajor).forEach(v => v[1] && majorParams.includes(v[0]) && param.append(name, v[0]))
} else if (name === 'kind') {
setSelectedKind(value)
param.set(name, tagToEnglish[value as KindType])
} else {
param.set(name, value)
}
param.set('major', data.major)
param.set('kind', tagToEnglish[data.kind as KindType])
param.set('word', data.searchWord)

router.push(`application?${param}`)
}, [searchParams])
}, [searchParams, data])

useEffect(() => {
if (searchParams.has('word')) {
setSearchWord(searchParams.get('word') || '')
changeData('searchWord')(searchParams.get('word') || '')
}
if (searchParams.has('kind') && ['everything', 'Portfolio', 'PersonalStatement', 'Resume'].includes(searchParams.get('kind') as string)) {
setSelectedKind(tagToKorean[searchParams.get('kind') as ('everything' | ApplicationFileType)])
changeData('kind')(tagToKorean[searchParams.get('kind') as ('everything' | ApplicationFileType)])
}
if (searchParams.has('major') && majorData.some(v => searchParams.getAll('major').includes(v))) {
setSelectedMajor(majorData.filter(v => searchParams.getAll('major').includes(v)).reduce((acc, v) => ({ ...acc, [v]: true }), {}))
if (searchParams.has('major')) {
changeData('major')(searchParams.get('major') as MajorType)
}
}, [searchParams])

Expand All @@ -68,9 +72,9 @@ const SideBar = () => {
id="searchInput"
placeholder="포트폴리오/자기소개서 검색"
className="bg-transparent w-full border-none outline-none placeholder:text-gray500 text-black"
defaultValue={searchWord}
onChange={(e) => setSearchWord(e.currentTarget.value)}
onKeyDown={(e) => e.key === 'Enter' && changeParams('word')(searchWord)}
defaultValue={data.searchWord}
onChange={(e) => changeData('searchWord')(e.currentTarget.value)}
onKeyDown={(e) => e.key === 'Enter' && changeParams()}
/>
</div>
<div className="gap-2 flex flex-col flex-2 w-full">
Expand All @@ -80,8 +84,8 @@ const SideBar = () => {
icon={<Portfolio size={16} />}
title='지원서 종류'
display={kindData}
value={selectedKind}
setValue={changeParams('kind')}
value={data.kind}
setValue={changeData('kind')}
open
/>
<div className="w-full h-px bg-gray200" />
Expand All @@ -90,8 +94,8 @@ const SideBar = () => {
icon={<Bag size={16} />}
title='작성자 종류'
display={majorData}
value={selectedMajor}
setValue={changeParams('major')}
value={data.major}
setValue={changeData('major')}
/>
</div>
</section>
Expand Down
1 change: 1 addition & 0 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const TipData: TipBoxProps[] = [

export default async function Home() {
const applicationData = await getRecommend().then(res => res.data.posts) || []

return (
<main>
<MainBanner />
Expand Down
Loading

0 comments on commit 15a3c59

Please sign in to comment.