-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'develop' of https://github.com/GU-99/grow-up-fe into fe…
…ature/#123-logout
- Loading branch information
Showing
16 changed files
with
465 additions
and
259 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { IoMdCloseCircle } from 'react-icons/io'; | ||
import RoleIcon from '@components/common/RoleIcon'; | ||
import type { UserWithRole } from '@/types/UserType'; | ||
|
||
type AssigneeListProps = { | ||
assigneeList: UserWithRole[]; | ||
onAssigneeDeleteClick: (user: UserWithRole) => void; | ||
}; | ||
|
||
export default function AssigneeList({ | ||
assigneeList, | ||
onAssigneeDeleteClick: handleWorkerDeleteClick, | ||
}: AssigneeListProps) { | ||
return ( | ||
<section className="flex w-full flex-wrap items-center gap-4"> | ||
{assigneeList.map((user) => ( | ||
<div key={user.userId} className="flex items-center space-x-4 rounded-md bg-button px-5"> | ||
<RoleIcon roleName={user.roleName} /> | ||
<div>{user.nickname}</div> | ||
<button type="button" aria-label="delete-worker" onClick={() => handleWorkerDeleteClick(user)}> | ||
<IoMdCloseCircle className="text-close" /> | ||
</button> | ||
</div> | ||
))} | ||
</section> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { GoPlusCircle } from 'react-icons/go'; | ||
import { IoMdCloseCircle } from 'react-icons/io'; | ||
import type { CustomFile } from '@/types/FileType'; | ||
|
||
type FileDropZoneProps = { | ||
id: string; | ||
label: string; | ||
files: CustomFile[]; | ||
onFileChange: (e: React.ChangeEvent<HTMLInputElement>) => void; | ||
onFileDrop: (e: React.DragEvent<HTMLElement>) => void; | ||
onFileDeleteClick: (fileId: string) => void; | ||
}; | ||
|
||
// ToDo: 파일 업로드 API 작업시 구조 다시 한 번 확인해보기 | ||
export default function FileDropZone({ | ||
id, | ||
label, | ||
files, | ||
onFileChange: handleFileChange, | ||
onFileDrop: handleFileDrop, | ||
onFileDeleteClick: handleFileDeleteClick, | ||
}: FileDropZoneProps) { | ||
return ( | ||
<label htmlFor={id}> | ||
<h3 className="text-large">{label}</h3> | ||
<input type="file" id={id} className="h-0 w-0 opacity-0" multiple hidden onChange={handleFileChange} /> | ||
<section | ||
className="flex cursor-pointer items-center gap-4 rounded-sl border-2 border-dashed border-input p-10" | ||
onDrop={handleFileDrop} | ||
> | ||
<ul className="flex grow flex-wrap gap-4"> | ||
{files.map(({ id, file }) => ( | ||
<li key={id} className="flex items-center gap-4 rounded-md bg-button px-4 py-2"> | ||
<span>{file.name}</span> | ||
<IoMdCloseCircle | ||
className="text-close" | ||
onClick={(e: React.MouseEvent<HTMLOrSVGElement>) => { | ||
e.preventDefault(); | ||
handleFileDeleteClick(id); | ||
}} | ||
/> | ||
</li> | ||
))} | ||
</ul> | ||
<div> | ||
<GoPlusCircle className="size-15 text-[#5E5E5E]" /> | ||
</div> | ||
</section> | ||
</label> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { useState } from 'react'; | ||
import { useFormContext } from 'react-hook-form'; | ||
import ToggleButton from '@components/common/ToggleButton'; | ||
import CustomMarkdown from '@components/common/CustomMarkdown'; | ||
|
||
type MarkdownEditorProps = { | ||
id: string; | ||
label: string; | ||
contentFieldName: string; | ||
}; | ||
|
||
export default function MarkdownEditor({ id, label, contentFieldName }: MarkdownEditorProps) { | ||
const [preview, setPreview] = useState(false); | ||
const { watch, register } = useFormContext(); | ||
|
||
const handlePreviewToggle = () => setPreview((prev) => !prev); | ||
|
||
return ( | ||
<label htmlFor={id} className="mb-20"> | ||
<h3 className="flex items-center space-x-2"> | ||
<span className="text-large">{label}</span> | ||
<ToggleButton id="preview" checked={preview} onChange={handlePreviewToggle} /> | ||
</h3> | ||
{preview ? ( | ||
<CustomMarkdown markdown={watch(contentFieldName)} /> | ||
) : ( | ||
<textarea | ||
id={id} | ||
rows={10} | ||
className="w-full border border-input p-10 placeholder:text-xs" | ||
placeholder="마크다운 형식으로 입력해주세요." | ||
{...register(contentFieldName)} | ||
/> | ||
)} | ||
</label> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { useState } from 'react'; | ||
import { useFormContext } from 'react-hook-form'; | ||
import { TASK_VALIDATION_RULES } from '@constants/formValidationRules'; | ||
import ToggleButton from '@components/common/ToggleButton'; | ||
|
||
import type { FieldError } from 'react-hook-form'; | ||
import type { Project } from '@/types/ProjectType'; | ||
|
||
type PeriodDateInputProps = { | ||
startDateLabel: string; | ||
endDateLabel: string; | ||
startDateId: string; | ||
endDateId: string; | ||
startDate: Project['startDate']; | ||
endDate: Project['endDate']; | ||
startDateFieldName: string; | ||
endDateFieldName: string; | ||
}; | ||
|
||
export default function PeriodDateInput({ | ||
startDateLabel, | ||
endDateLabel, | ||
startDateId, | ||
endDateId, | ||
startDate, | ||
endDate, | ||
startDateFieldName, | ||
endDateFieldName, | ||
}: PeriodDateInputProps) { | ||
const [hasDeadline, setHasDeadline] = useState(false); | ||
const { | ||
setValue, | ||
getValues, | ||
clearErrors, | ||
watch, | ||
register, | ||
formState: { errors }, | ||
} = useFormContext(); | ||
|
||
const handleDeadlineToggle = () => { | ||
setValue(endDateFieldName, getValues(startDateFieldName)); | ||
clearErrors(endDateFieldName); | ||
setHasDeadline((prev) => !prev); | ||
}; | ||
|
||
return ( | ||
<div className="flex items-center justify-center gap-10"> | ||
<label htmlFor={startDateId} className="w-1/2"> | ||
<h3 className="text-large">{startDateLabel}</h3> | ||
<input | ||
id={startDateId} | ||
type="date" | ||
{...register(startDateFieldName, { | ||
...TASK_VALIDATION_RULES.START_DATE(startDate, endDate), | ||
onChange: (e) => { | ||
if (!hasDeadline) setValue(endDateFieldName, e.target.value); | ||
}, | ||
})} | ||
/> | ||
<div className={`my-5 h-10 grow text-xs text-error ${errors[startDateFieldName] ? 'visible' : 'invisible'}`}> | ||
{(errors[startDateFieldName] as FieldError | undefined)?.message} | ||
</div> | ||
</label> | ||
<label htmlFor={endDateId} className="w-1/2"> | ||
<h3 className="flex items-center space-x-2 text-large"> | ||
<span>{endDateLabel}</span> | ||
<ToggleButton id="deadline" checked={hasDeadline} onChange={handleDeadlineToggle} /> | ||
</h3> | ||
<input | ||
id={endDateId} | ||
type="date" | ||
className={`${hasDeadline ? '' : '!bg-disable'}`} | ||
disabled={!hasDeadline} | ||
{...register( | ||
endDateFieldName, | ||
TASK_VALIDATION_RULES.END_DATE(hasDeadline, startDate, endDate, watch(startDateFieldName)), | ||
)} | ||
/> | ||
<div className={`my-5 h-10 grow text-xs text-error ${errors[endDateFieldName] ? 'visible' : 'invisible'}`}> | ||
{(errors[endDateFieldName] as FieldError | undefined)?.message} | ||
</div> | ||
</label> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { IoSearch } from 'react-icons/io5'; | ||
import type { UserWithRole } from '@/types/UserType'; | ||
|
||
type SearchInputProps = { | ||
id: string; | ||
label: string; | ||
keyword: string; | ||
loading: boolean; | ||
userList: UserWithRole[]; | ||
onKeywordChange: (event: React.ChangeEvent<HTMLInputElement>) => void; | ||
onSearchKeyup: (event: React.KeyboardEvent<HTMLInputElement>) => void; | ||
onSearchClick: () => void; | ||
onUserClick: (user: UserWithRole) => void; | ||
}; | ||
|
||
export default function SearchUserInput({ | ||
id, | ||
label, | ||
keyword, | ||
loading, | ||
userList, | ||
onKeywordChange: handleKeywordChange, | ||
onSearchKeyup: handleSearchKeyUp, | ||
onSearchClick: handleSearchClick, | ||
onUserClick: handleUserClick, | ||
}: SearchInputProps) { | ||
return ( | ||
<label htmlFor={id} className="group mb-10 flex items-center gap-5"> | ||
<h3 className="text-large">{label}</h3> | ||
<section className="relative grow"> | ||
<input | ||
type="text" | ||
id={id} | ||
className="h-25 w-full rounded-md border border-input pl-10 pr-25 text-regular placeholder:text-xs" | ||
value={keyword} | ||
onChange={handleKeywordChange} | ||
onKeyDown={handleSearchKeyUp} | ||
placeholder="닉네임을 검색해주세요." | ||
/> | ||
<button | ||
type="button" | ||
aria-label="search" | ||
className="absolute right-5 top-1/2 -translate-y-1/2 cursor-pointer" | ||
onClick={handleSearchClick} | ||
> | ||
<IoSearch className="size-15 text-emphasis hover:text-black" /> | ||
</button> | ||
{keyword && !loading && ( | ||
<ul className="invisible absolute left-0 right-0 z-10 max-h-110 overflow-auto rounded-md border-2 bg-white group-focus-within:visible"> | ||
{userList.length === 0 ? ( | ||
<div className="h-20 border px-10 leading-8">'{keyword}' 의 검색 결과가 없습니다.</div> | ||
) : ( | ||
userList?.map((user) => ( | ||
<li className="h-20 border" key={user.userId}> | ||
<button | ||
type="button" | ||
className="h-full w-full px-10 text-left hover:bg-sub" | ||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => { | ||
e.currentTarget.blur(); | ||
handleUserClick(user); | ||
}} | ||
> | ||
{user.nickname} | ||
</button> | ||
</li> | ||
)) | ||
)} | ||
</ul> | ||
)} | ||
</section> | ||
</label> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { useFormContext } from 'react-hook-form'; | ||
import { TASK_VALIDATION_RULES } from '@constants/formValidationRules'; | ||
|
||
import type { FieldError } from 'react-hook-form'; | ||
import type { ProjectStatus } from '@/types/ProjectStatusType'; | ||
|
||
type StatusRadioProps = { | ||
statusFieldName: string; | ||
statusList: ProjectStatus[]; | ||
}; | ||
|
||
export default function StatusRadio({ statusFieldName, statusList }: StatusRadioProps) { | ||
const { | ||
watch, | ||
register, | ||
formState: { errors }, | ||
} = useFormContext(); | ||
|
||
return ( | ||
<> | ||
{/* ToDo: 상태 선택 리팩토링 할 것 */} | ||
<div className="flex items-center justify-start gap-4"> | ||
{statusList.map((status) => { | ||
const { statusId, statusName, colorCode } = status; | ||
const isChecked = Number(watch('statusId')) === statusId; | ||
return ( | ||
<label | ||
key={statusId} | ||
htmlFor={statusName} | ||
className={`flex cursor-pointer items-center rounded-lg border px-5 py-3 text-emphasis ${isChecked ? 'border-input bg-white' : 'bg-button'}`} | ||
> | ||
<input | ||
id={statusName} | ||
type="radio" | ||
className="invisible h-0 w-0" | ||
value={statusId} | ||
checked={isChecked} | ||
{...register(statusFieldName, TASK_VALIDATION_RULES.STATUS)} | ||
/> | ||
<div style={{ borderColor: colorCode }} className="mr-3 h-8 w-8 rounded-full border" /> | ||
<h3 className="text-xs">{statusName}</h3> | ||
</label> | ||
); | ||
})} | ||
</div> | ||
<div className={`my-5 h-10 grow text-xs text-error ${errors[statusFieldName] ? 'visible' : 'invisible'}`}> | ||
{(errors[statusFieldName] as FieldError | undefined)?.message} | ||
</div> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.