-
Notifications
You must be signed in to change notification settings - Fork 0
[Feature/new post] 커뮤니티 새로운 글 작성 페이지 구현 #30
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
base: main
Are you sure you want to change the base?
Changes from all commits
f42bb4a
7f42e99
dc58c88
c861f32
d289eb8
bf9a18b
0518a5b
9495443
46eac53
60e3269
663fe11
da94c4d
adfe56e
a76f4e8
64969ba
d004ab4
ab05816
1239a21
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,45 @@ | ||
| import React, { useState } from 'react'; | ||
| import { DownArrowIcon, UpArrowIcon } from '@/components/ui/icons/ArrowIcon'; | ||
|
|
||
| interface PBoardDropdown { | ||
| boards: string[]; | ||
| selectedBoard: string; | ||
| onSelectBoard: (board: string) => void; | ||
| } | ||
|
|
||
| const BoardDropdown: React.FC<PBoardDropdown> = ({ boards, selectedBoard, onSelectBoard }) => { | ||
| const [isOpen, setIsOpen] = useState(false); | ||
|
|
||
| const handleBoardSelect = (board: string) => { | ||
| onSelectBoard(board); | ||
| setIsOpen(false); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className='relative'> | ||
| <div className='flex h-[38px] w-[200px] items-center justify-between rounded-[8px] border border-border bg-white px-[16px] py-[12px]'> | ||
| <div className={`text-body2 ${selectedBoard ? 'text-deepgray' : 'text-disabled'}`}> | ||
| {selectedBoard || '카테고리'} | ||
| </div> | ||
| <div className='cursor-pointer' onClick={() => setIsOpen(!isOpen)}> | ||
|
Contributor
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.
Contributor
Author
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. 원래는 그런 방식으로 구현을 하였다가, 화살표 아이콘이 명확하게 있어서 화살표 아이콘만 선택해서 드롭다운을 열고 닫고 하는 방식으로 바꾸었습니다. 추후에 공통으로 재사용 가능한 드롭다운을 만들 예정이라 그때 이 부분 반영해서 구현하겠습니다 :) |
||
| {isOpen ? <UpArrowIcon size={18} color='#e9e9e9' /> : <DownArrowIcon size={18} color='#e9e9e9' />} | ||
| </div> | ||
| </div> | ||
| {isOpen && ( | ||
| <div className='absolute z-10 mt-[4px] w-[200px] rounded-[6px] border border-border bg-white shadow-sm'> | ||
| {boards.map((board) => ( | ||
| <div | ||
| key={board} | ||
| onClick={() => handleBoardSelect(board)} | ||
| className='cursor-pointer px-[16px] py-[8px] text-body2 text-deepgray hover:bg-background' | ||
| > | ||
| {board} | ||
| </div> | ||
| ))} | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default BoardDropdown; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| import React, { useState } from 'react'; | ||
| import MDEditor from '@uiw/react-md-editor'; | ||
| import Button from '@/components/ui/button/Button'; | ||
| import BoardDropdown from '@/components/community/BoardDropdown'; | ||
|
|
||
| const boards = ['공지사항', '자유게시판', '질문게시판', '취업정보']; | ||
|
|
||
| const NewPostPage: React.FC = () => { | ||
| const [title, setTitle] = useState<string>(''); | ||
| const [content, setContent] = useState<string>(''); | ||
| const [selectedBoard, setSelectedBoard] = useState<string>('카테고리'); | ||
|
|
||
| const handleChange = (setter: React.Dispatch<React.SetStateAction<string>>) => { | ||
| return (newValue?: string) => { | ||
| setter(newValue || ''); | ||
| }; | ||
| }; | ||
|
|
||
| const handleCancel = () => {}; | ||
|
|
||
| const handleSubmit = () => {}; | ||
|
|
||
| return ( | ||
| <div className='flex h-[calc(100vh-64px)] justify-center'> | ||
| <div className='flex w-base flex-col'> | ||
| <div className='mt-[42px]'> | ||
| <BoardDropdown boards={boards} selectedBoard={selectedBoard} onSelectBoard={setSelectedBoard} /> | ||
| </div> | ||
| <div className='mt-[10px] flex h-3/4 justify-center'> | ||
| <div className='w-1/2'> | ||
| <div className='title-section h-[44px]'> | ||
| <MDEditor | ||
| textareaProps={{ | ||
| placeholder: '제목을 입력하세요.', | ||
| maxLength: 30, | ||
| }} | ||
| minHeight={44} | ||
| visibleDragbar={false} | ||
| value={title} | ||
| onChange={handleChange(setTitle)} | ||
| extraCommands={[]} | ||
| hideToolbar={true} | ||
| enableScroll={false} | ||
| preview='edit' | ||
| className='border border-b-0 border-border shadow-none' | ||
| /> | ||
| </div> | ||
| <div className='h-[calc(100%-44px)]'> | ||
| <MDEditor | ||
| textareaProps={{ | ||
| placeholder: '내용을 작성하세요.', | ||
| }} | ||
| visibleDragbar={false} | ||
| height='100%' | ||
| value={content} | ||
| onChange={handleChange(setContent)} | ||
| extraCommands={[]} | ||
| preview='edit' | ||
| className='border border-border shadow-none' | ||
| /> | ||
| </div> | ||
| </div> | ||
| <div className='border-#ccc h-full w-1/2 rounded-[4px] border border-l-0'> | ||
| <MDEditor.Markdown source={`### ${title}`} className='h-[44px] rounded-t-[3px] px-[20px] py-[20px]' /> | ||
| <MDEditor.Markdown | ||
| source={content} | ||
| className='h-[calc(100%-44px)] overflow-auto rounded-b-[3px] px-[20px] py-[10px]' | ||
| /> | ||
| </div> | ||
| </div> | ||
| <div className='mt-[32px] flex justify-end'> | ||
| <Button className='mr-[10px]' type='outlined' onClick={handleCancel}> | ||
| 취소 | ||
| </Button> | ||
| <Button type='filled' onClick={handleSubmit}> | ||
| 등록 | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default NewPostPage; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import RecruitForm from '@/components/recruit/new/RecruitForm'; | ||
| import { H1 } from '@/components/ui/typography/Heading'; | ||
|
|
||
| const NewRecruitPage: React.FC = () => { | ||
| return ( | ||
| <div className='mx-auto w-base'> | ||
| <H1 className='mb-6 mt-9'>채용공고 등록</H1> | ||
| <RecruitForm /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default NewRecruitPage; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import SmallCalendar from '@/components/recruit/calendar/SmallCalendar'; | ||
| import CalendarIcon from '@/components/ui/icons/CalendarIcon'; | ||
| import useClickOutsideRef from '@/hooks/useClickOutsideRef'; | ||
| import { useState } from 'react'; | ||
|
|
||
| interface PDatePicker { | ||
| placeholder: string; | ||
| value: Date | null; | ||
| id?: string; | ||
| setDate?: React.Dispatch<React.SetStateAction<Date | null>>; | ||
| startDate?: Date | null; | ||
| } | ||
|
|
||
| const DatePicker: React.FC<PDatePicker> = ({ placeholder, value, id, setDate, startDate }) => { | ||
| const [isOpen, setIsOpen] = useState<boolean>(false); | ||
|
|
||
| const ref = useClickOutsideRef<HTMLDivElement>(() => setIsOpen(false)); | ||
|
|
||
| return ( | ||
| <div className='relative w-full' ref={ref}> | ||
| <button | ||
| id={id} | ||
| className='flex w-full items-center gap-2 rounded-sm border border-input px-4 py-2.5 text-body2 focus:outline-activate' | ||
| onClick={() => setIsOpen((prev) => !prev)} | ||
| > | ||
| <p className={`flex-grow text-start ${value ? '' : 'text-disabled'}`}> | ||
| {value ? `${value.getFullYear()}. ${value.getMonth() + 1}. ${value.getDate()}` : placeholder} | ||
| </p> | ||
| <CalendarIcon /> | ||
| </button> | ||
| {isOpen && ( | ||
| <div className='absolute right-0 top-12 z-10'> | ||
| <SmallCalendar selectedDate={value} setDate={setDate} startDate={startDate} /> | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default DatePicker; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| import CalendarNavigation from '@/components/recruit/calendar/CalendarNavigation'; | ||
| import useCalendar from '@/hooks/useCalendar'; | ||
| import { useEffect } from 'react'; | ||
|
|
||
| interface PSmallCalendar { | ||
| selectedDate?: Date | null; | ||
| setDate?: React.Dispatch<React.SetStateAction<Date | null>>; | ||
| startDate?: Date | null; | ||
| } | ||
|
|
||
| const SmallCalendar: React.FC<PSmallCalendar> = ({ selectedDate, setDate, startDate }) => { | ||
| const { weekCalendarList, currentDate, setCurrentDate } = useCalendar(); | ||
|
|
||
| useEffect(() => { | ||
| if (startDate) { | ||
| if (startDate > currentDate) setCurrentDate(startDate); | ||
| } else if (selectedDate) { | ||
| setCurrentDate(selectedDate); | ||
| } | ||
| }, [selectedDate, setCurrentDate, startDate]); | ||
|
|
||
| const isSelectedDate = (date: number) => { | ||
| if (!selectedDate) return false; | ||
| return ( | ||
| currentDate.getFullYear() === selectedDate.getFullYear() && | ||
| currentDate.getMonth() === selectedDate.getMonth() && | ||
| selectedDate.getDate() === date | ||
| ); | ||
| }; | ||
|
|
||
| const canSelect = (date: number) => { | ||
| if (!startDate) return true; | ||
| return new Date(currentDate.getFullYear(), currentDate.getMonth(), date) > startDate; | ||
| }; | ||
|
|
||
| const handleDateClick = (clickedDate: number) => { | ||
| if (!setDate) return; | ||
| const date = new Date(currentDate.getFullYear(), currentDate.getMonth(), clickedDate); | ||
| setDate(date); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className='rounded-md border border-input bg-white px-2 pb-1'> | ||
| <CalendarNavigation currentDate={currentDate} setCurrentDate={setCurrentDate} size='sm' /> | ||
| <table className='w-full'> | ||
| <thead> | ||
| <tr className='grid grid-cols-7 text-body2 font-bold'> | ||
| <th>일</th> | ||
| <th>월</th> | ||
| <th>화</th> | ||
| <th>수</th> | ||
| <th>목</th> | ||
| <th>금</th> | ||
| <th>토</th> | ||
| </tr> | ||
| </thead> | ||
| <tbody> | ||
| {weekCalendarList.map((week, rIdx) => ( | ||
| <tr key={rIdx} className='grid grid-cols-7 justify-center'> | ||
| {week.map((date, cIdx) => ( | ||
| <td key={cIdx} className='flex justify-center'> | ||
| {date === 0 || !canSelect(date) ? ( | ||
| <button disabled className='h-8 w-8 text-center text-body2 text-disabled'> | ||
| {date !== 0 && date} | ||
| </button> | ||
| ) : ( | ||
| <button | ||
| className={`h-8 w-8 rounded-full text-center text-body2 transition-colors ${isSelectedDate(date) ? 'bg-primary-light' : ''} hover:bg-primary-light`} | ||
| onClick={() => handleDateClick(date)} | ||
| > | ||
| {date} | ||
| </button> | ||
| )} | ||
| </td> | ||
| ))} | ||
| </tr> | ||
| ))} | ||
| </tbody> | ||
| </table> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default SmallCalendar; |
Uh oh!
There was an error while loading. Please reload this page.