-
Notifications
You must be signed in to change notification settings - Fork 0
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
Implement lecture search/#10 #46
Changes from all commits
e73e216
62c96e6
06e0d9e
6222592
559e166
07346d9
deb4de2
5f39f00
79dd277
e81447e
bc330c0
1cecdeb
88d1a88
0a84620
7361b74
9ac053e
5611eb6
c5af991
0a9c473
b9f5106
ccf604c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { atom } from 'jotai'; | ||
import { LectureInfo } from '../type/lecture'; | ||
|
||
export const isCustomizingAtom = atom<boolean>(false); | ||
|
||
export const customLectureAtom = atom<LectureInfo[]>([]); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,17 @@ | ||
export type LectureInfo = { | ||
export interface LectureInfo { | ||
[index: string]: string | number; | ||
id: number; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. object.keys ๋ฉ์๋๋ฅผ ์ฌ์ฉํ ๋ ์ฌ์ฉํ๋ key๋ string ํ์ ์ธ๋ฐ, ์ด string ํ์ ์ string literal type์ ํ ๋นํ๋ ค ํ๊ธฐ ๋๋ฌธ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ์๊ณ ์ ๊ตฌ๋ฌธ์ ํตํด ํด๊ฒฐํ์ต๋๋ค |
||
year: string; | ||
semester: string; | ||
lectureCode: string; | ||
lectureName: string; | ||
credit: number; | ||
}; | ||
} | ||
|
||
export interface SearchedLectureInfo { | ||
[index: string]: string | number; | ||
id: number; | ||
lectureCode: string; | ||
name: string; | ||
credit: number; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
'use client'; | ||
import React from 'react'; | ||
import LectureSearchBar from './lecture-search-bar'; | ||
import LectureSearchResultContainer from './lecture-search-result-container'; | ||
import { isCustomizingAtom } from '@/app/store/custom-taken-lecture'; | ||
import { useAtomValue } from 'jotai'; | ||
|
||
export default function LectureSearch() { | ||
const isCustomizing = useAtomValue(isCustomizingAtom); | ||
if (!isCustomizing) return null; | ||
return ( | ||
<div className="flex flex-col gap-4" data-testid="lecture-search-component"> | ||
<LectureSearchBar /> | ||
<LectureSearchResultContainer /> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import Select from '../../view/molecule/select'; | ||
import TextInput from '../../view/atom/text-input/text-input'; | ||
import { MagnifyingGlassIcon } from '@radix-ui/react-icons'; | ||
|
||
export default function LectureSearchBar() { | ||
// ๊ฒ์ ๊ธฐ๋ฅ์ ํด๋น ์ปดํฌ๋ํธ์์ ๊ตฌํ ์์ | ||
return ( | ||
<div className="flex justify-between"> | ||
<div className="w-[15%]"> | ||
<Select defaultValue="lectureName" placeholder="๊ณผ๋ชฉ๋ช "> | ||
<Select.Item value="lectureName" placeholder="๊ณผ๋ชฉ๋ช " /> | ||
<Select.Item value="lectureCode" placeholder="๊ณผ๋ชฉ์ฝ๋" /> | ||
</Select> | ||
</div> | ||
<div className="w-[40%] flex justify-between"> | ||
<TextInput placeholder="๊ฒ์์ด๋ฅผ ์ ๋ ฅํด์ฃผ์ธ์" icon={MagnifyingGlassIcon} /> | ||
</div> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import List from '../../view/molecule/list'; | ||
import Image from 'next/image'; | ||
import searchResultIcon from '@/public/assets/searchResultIcon.svg'; | ||
import Grid from '../../view/molecule/grid'; | ||
import { SearchedLectureInfo } from '@/app/type/lecture'; | ||
import AddTakenLectureButton from '../taken-lecture/add-taken-lecture-button'; | ||
|
||
const emptyDataRender = () => { | ||
return ( | ||
<div className="flex flex-col items-center justify-center gap-2"> | ||
<Image src={searchResultIcon} alt="search-result-icon" width={40} height={40} /> | ||
<div className="text-md font-medium text-gray-400">๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ํ์๋ฉ๋๋ค</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default function LectureSearchResultContainer() { | ||
const renderAddActionButton = (item: SearchedLectureInfo) => { | ||
return <AddTakenLectureButton lectureItem={item} />; | ||
}; | ||
const render = (item: SearchedLectureInfo, index: number) => { | ||
const searchLectureItem = item; | ||
return ( | ||
<List.Row key={index}> | ||
<Grid cols={4}> | ||
{Object.keys(searchLectureItem).map((key, index) => { | ||
if (key === 'id') return null; | ||
return <Grid.Column key={index}>{searchLectureItem[key]}</Grid.Column>; | ||
})} | ||
{renderAddActionButton ? <Grid.Column>{renderAddActionButton(searchLectureItem)}</Grid.Column> : null} | ||
</Grid> | ||
</List.Row> | ||
); | ||
}; | ||
|
||
return ( | ||
<List | ||
data={[ | ||
{ id: 3, lectureCode: 'HCB03490', name: '๊ฒฝ์์ ๋ณด์ฌ๋ก์ฐ๊ตฌ', credit: 3 }, | ||
{ id: 4, lectureCode: 'HCB03490', name: '๊ฒ์์ํตํ๊ฒฝ์์์ดํด', credit: 3 }, | ||
]} | ||
render={render} | ||
isScrollList={true} | ||
emptyDataRender={emptyDataRender} | ||
/> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { SearchedLectureInfo } from '@/app/type/lecture'; | ||
import Button from '../../view/atom/button/button'; | ||
import { useAtom } from 'jotai'; | ||
import { customLectureAtom } from '@/app/store/custom-taken-lecture'; | ||
|
||
interface AddTakenLectureButtonProps { | ||
lectureItem: SearchedLectureInfo; | ||
} | ||
export default function AddTakenLectureButton({ lectureItem }: AddTakenLectureButtonProps) { | ||
const [customLecture, setCustomLecture] = useAtom(customLectureAtom); | ||
const addLecture = () => { | ||
setCustomLecture([ | ||
...customLecture, | ||
{ | ||
id: lectureItem.id, | ||
year: 'CUSTOM', | ||
semester: 'CUSTOM', | ||
lectureCode: lectureItem.lectureCode, | ||
lectureName: lectureItem.name, | ||
credit: lectureItem.credit, | ||
}, | ||
]); | ||
}; | ||
return <Button variant="list" label="์ถ๊ฐ" onClick={addLecture} />; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { useAtom } from 'jotai'; | ||
import Button from '../../view/atom/button/button'; | ||
import { customLectureAtom } from '@/app/store/custom-taken-lecture'; | ||
|
||
interface DeleteTakenLectureButtonProps { | ||
lectureId: number; | ||
} | ||
export default function DeleteTakenLectureButton({ lectureId }: DeleteTakenLectureButtonProps) { | ||
const [customLecture, setCustomLecture] = useAtom(customLectureAtom); | ||
const deleteLecture = () => { | ||
setCustomLecture(customLecture.filter((lecture) => lecture.id !== lectureId)); | ||
}; | ||
return <Button label="์ญ์ " variant="list" data-testid="taken-lecture-delete-button" onClick={deleteLecture} />; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,14 @@ | ||
import { fetchTakenLectures } from '@/app/business/lecture/taken-lecture.query'; | ||
import TakenLectureList from './taken-lecture-list'; | ||
import TakenLectureLabel from './taken-lecture-label'; | ||
|
||
export default async function TakenLecture() { | ||
const data = await fetchTakenLectures(); | ||
return <TakenLectureList data={data.takenLectures} />; | ||
return ( | ||
<div className="flex flex-col gap-2"> | ||
{/* w-[800px]์ w-full๋ก ๋ณ๊ฒฝ ์์ */} | ||
<TakenLectureLabel data={data.takenLectures} /> | ||
<TakenLectureList data={data.takenLectures} /> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,23 @@ | ||
import { cn } from '@/app/utils/shadcn/utils'; | ||
import { ReactNode } from 'react'; | ||
|
||
export interface ListRow { | ||
id: number; | ||
[key: string]: string | number; | ||
} | ||
interface ListRootProps { | ||
data: ListRow[]; | ||
render: (item: ListRow, index: number) => ReactNode; | ||
interface ListRootProps<T extends ListRow> { | ||
data: T[]; | ||
render: (item: T, index: number) => ReactNode; | ||
isScrollList?: boolean; | ||
emptyDataRender?: () => ReactNode; | ||
} | ||
|
||
export function ListRoot({ data, render }: ListRootProps) { | ||
export function ListRoot<T extends ListRow>({ data, render, isScrollList = false, emptyDataRender }: ListRootProps<T>) { | ||
const hasNotData = emptyDataRender && data.length === 0; | ||
return ( | ||
<div className="rounded-2xl border-[1px] border-black-2 w-full"> | ||
<div className={cn('rounded-xl border-[1px] border-gray-300 w-full ', isScrollList && 'h-72 overflow-auto')}> | ||
{data.map((item, index) => render(item, index))} | ||
{hasNotData ? emptyDataRender() : null} | ||
</div> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[comment]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
์ ๋ ํ ์ค์ ์์ฑํ ๊ฒ๋ณด๋ค ๋ ์ค์ ๋๋ ์ ์์ฑํ๋ ๊ฒ ์์ ์ ํ๋ฆ์ด ๋ ์ ์ฝํ๋ค๊ณ ์๊ฐํด์ ๊ทธ๋ ๊ฒ ์์ ํ์ต๋๋ค!