diff --git a/fe/client/src/Pages/IssueListPage.tsx b/fe/client/src/Pages/IssueListPage.tsx index 2547a7b36..8f57bf4dc 100644 --- a/fe/client/src/Pages/IssueListPage.tsx +++ b/fe/client/src/Pages/IssueListPage.tsx @@ -1,26 +1,12 @@ -import React, { useCallback, useEffect } from 'react' +import React, { useCallback } from 'react' import HeadContent from '@components/issueList/HeadContent'; import { ListWrapper } from '@components/common/baseStyle/baseStyle'; -import ListHeader from '@components/issueList/ListHeader'; -import ListItem from '@components/issueList/ListItem'; -import { issueListItemAtom } from '@components/common/atoms/issueListAtom'; import { filterAtom, FilterBooleanType } from '@/components/common/atoms/filterAtom'; -import { IssueListItemType } from '@components/common/types/APIType'; -import API from '@/utils/API'; -import useFetch, { AsyncState } from '@/utils/hook/useFetch'; import { useRecoilState } from '@/utils/myRecoil/useRecoilState'; +import IssueList from './../components/issueList/IssueList'; const IssueListPage = () => { - const [issueItems] = useFetch(API.get.issues); - const { data, loading, error }: AsyncState = issueItems; const [, setFilterModalState] = useRecoilState(filterAtom); - const [, setFilteredList] = useRecoilState(issueListItemAtom) - - useEffect(() => { - if (!data) return; - setFilteredList(data); - }, [data]); - const handleClickShowFilterModal = useCallback((title: string) => () => { setFilterModalState((filterModalState: FilterBooleanType) => ({ ...filterModalState, [title]: true })); }, []); @@ -29,16 +15,7 @@ const IssueListPage = () => { <> - { - data && <> - - {data.map((issueItem: IssueListItemType) => { - return - })} - - } - {loading && <>loading...} - {error && <>error...} + ) diff --git a/fe/client/src/components/common/FilterTab.tsx b/fe/client/src/components/common/FilterTab.tsx index 7be6b616a..bd19c0326 100644 --- a/fe/client/src/components/common/FilterTab.tsx +++ b/fe/client/src/components/common/FilterTab.tsx @@ -9,7 +9,7 @@ import { type FilterTabType = { header: string; - filterList: Array; + filterList: Array<{ name: string, info?: any }>; inputType: string; } @@ -18,7 +18,8 @@ const filterHeaderNames: { [key: string]: string } = { manager: '담당자', label: '레이블', milestone: '마일스톤', - writer: '작성자' + writer: '작성자', + stateChange: '상태 수정' } const FilterTab = ({ header, filterList, inputType }: FilterTabType) => { @@ -36,6 +37,7 @@ const FilterTab = ({ header, filterList, inputType }: FilterTabType) => { label: false, milestone: false, writer: false, + stateChange: false, }) }, []); @@ -73,6 +75,7 @@ const FilterTab = ({ header, filterList, inputType }: FilterTabType) => { label: false, milestone: false, writer: false, + stateChange: false, }); }, [defaultCheckerState]) @@ -84,13 +87,14 @@ const FilterTab = ({ header, filterList, inputType }: FilterTabType) => { {filterList.map((listItem: any, idx) => { return (
- + item.name === listItem.name) !== -1} /> + .findIndex((item: any) => item.name === listItem.name) !== -1} + style={{ display: header === 'stateChange' ? 'none' : '' }} /> {listItem.name}
) @@ -128,10 +132,10 @@ const FilterTabHeader = styled.div` `; -const FilterLabel = styled.label` +const FilterLabel = styled.label<{ isReverse: boolean }>` display:flex; justify-content: space-between; - flex-direction: row-reverse; + flex-direction: ${({ isReverse }) => isReverse && 'row-reverse'}; & > span{ color: #A3A1B0; font-size: 12px; diff --git a/fe/client/src/components/common/LabelMilestoneToggle.tsx b/fe/client/src/components/common/LabelMilestoneToggle.tsx index 2a6ee56f3..0296af1ce 100644 --- a/fe/client/src/components/common/LabelMilestoneToggle.tsx +++ b/fe/client/src/components/common/LabelMilestoneToggle.tsx @@ -10,7 +10,6 @@ import MilestoneIcon from '@/Icons/Milestone.svg'; const LabelMilestoneToggle = () => { const location = useLocation(); - const defaultChecked = location.pathname === '/labelList'; const [labelState] = useFetch(API.get.labelsCount); const [milestoneState] = useFetch(API.get.milestonesCount); const { data: labelData }: AsyncState = labelState; @@ -29,7 +28,8 @@ const LabelMilestoneToggle = () => { - +   레이블 {labelData && ` (${labelData.count})`} @@ -37,7 +37,8 @@ const LabelMilestoneToggle = () => { - +   마일스톤 {milestoneData && ` (${milestoneData.count})`} diff --git a/fe/client/src/components/common/atoms/filterAtom.ts b/fe/client/src/components/common/atoms/filterAtom.ts index b07ecb4a0..298b5434b 100644 --- a/fe/client/src/components/common/atoms/filterAtom.ts +++ b/fe/client/src/components/common/atoms/filterAtom.ts @@ -7,6 +7,7 @@ export type FilterBooleanType = { label: boolean; milestone: boolean; writer: boolean; + stateChange: boolean; } export const filterAtom = atom({ @@ -16,7 +17,8 @@ export const filterAtom = atom({ manager: false, label: false, milestone: false, - writer: false + writer: false, + stateChange: false, } }) @@ -31,7 +33,8 @@ export const filterRadioButtonListAtom = atom({ manager: { name: '', info: {} }, label: { name: '', info: {} }, milestone: { name: '', info: {} }, - writer: { name: '', info: {} } + writer: { name: '', info: {} }, + stateChange: { name: '', info: {} } } }) diff --git a/fe/client/src/components/common/atoms/issueListAtom.ts b/fe/client/src/components/common/atoms/issueListAtom.ts deleted file mode 100644 index b5f439221..000000000 --- a/fe/client/src/components/common/atoms/issueListAtom.ts +++ /dev/null @@ -1,8 +0,0 @@ -import atom from '@/utils/myRecoil/atom'; -import { IssueListItemType } from '@components/common/types/APIType'; - -export const issueListItemAtom = atom({ - key: 'issueListItemAtom', - default: [] -}); - diff --git a/fe/client/src/components/createIssue/SendButton.tsx b/fe/client/src/components/createIssue/SendButton.tsx index 9fde06a25..22d9d256f 100644 --- a/fe/client/src/components/createIssue/SendButton.tsx +++ b/fe/client/src/components/createIssue/SendButton.tsx @@ -19,18 +19,17 @@ const SendButton = () => { const [commentInputValue] = useRecoilState(issueCommentInputAtom); const handleClickSendData = async () => { - const data = { title: titleInputValue, mainCommentContents: commentInputValue, authorId: 3, //수정필요 assigneeIds: checkedItems.manager.map(getCheckedItemId), labelIds: checkedItems.label.map(getCheckedItemId), - milestoneId: checkedItems.milestone.map(getCheckedItemId)[0] + milestoneId: checkedItems.milestone.length && checkedItems.milestone[0].id }; - - const a = await API.post.issues(data); - console.log(a); + console.log(data) + const responseData = await API.post.issues(data); + console.log(responseData); } return ( @@ -43,8 +42,6 @@ const SendButton = () => { ) } -function getCheckedItemId({ info: { id } }: FilteredIdType) { - return id; -} +const getCheckedItemId = ({ info: { id } }: FilteredIdType) => id; export default SendButton; diff --git a/fe/client/src/components/issueList/HeadContent.tsx b/fe/client/src/components/issueList/HeadContent.tsx index 6dc23ee87..fd622b834 100644 --- a/fe/client/src/components/issueList/HeadContent.tsx +++ b/fe/client/src/components/issueList/HeadContent.tsx @@ -8,11 +8,11 @@ import ArrowBottomIcon from '@/Icons/ArrowBottom.svg'; import SearchIcon from '@/Icons/Search.svg'; const issueFilterList = [ - '열린 이슈', - '내가 작성한 이슈', - '나에게 할당된 이슈', - '내가 댓글을 남긴 이슈', - '닫힌 이슈' + { name: '열린 이슈' }, + { name: '내가 작성한 이슈' }, + { name: '나에게 할당된 이슈' }, + { name: '내가 댓글을 남긴 이슈' }, + { name: '닫힌 이슈' }, ]; type HeadContentType = { diff --git a/fe/client/src/components/issueList/IssueList.tsx b/fe/client/src/components/issueList/IssueList.tsx new file mode 100644 index 000000000..7759677dd --- /dev/null +++ b/fe/client/src/components/issueList/IssueList.tsx @@ -0,0 +1,57 @@ +import React, { useEffect } from 'react' +import ListHeader from '@components/issueList/ListHeader'; +import ListItem from '@components/issueList/ListItem'; +import { IssueListItemType } from '@components/common/types/APIType'; +import { issueCheckedItemAtom } from '@components/common/atoms/checkBoxAtom'; +import { filterRadioButtonListAtom } from '@components/common/atoms/filterAtom'; +import API from '@/utils/API'; +import { useRecoilState } from '@/utils/myRecoil/useRecoilState'; +import useFetch, { AsyncState } from '@/utils/hook/useFetch'; +import { pipe, _ } from '@/utils/functionalUtils'; + +type IssueListType = { + handleClickShowFilterModal: (title: string) => () => void; +} + +const IssueList = ({ handleClickShowFilterModal }: IssueListType) => { + const [issueItems] = useFetch(API.get.issues); + const { data, loading, error }: AsyncState = issueItems; + const [checkedFilteredState, setCheckedFilteredState] = useRecoilState(filterRadioButtonListAtom); + + if (data) { + console.log( + pipe( + _.filter(getFilteredIssueItems('assignees', checkedFilteredState.manager.info.id)), + _.filter(getFilteredIssueItems('labels', checkedFilteredState.label.info.id)), + _.filter(getFilteredIssueItems('milestone', checkedFilteredState.milestone.info.id)), + _.filter(getFilteredIssueItems('author', checkedFilteredState.writer.info.id)) + )(data)) + } + + return ( + <> + { + data && <> + + {data.map((issueItem: IssueListItemType) => { + return + })} + + } + {loading && <>loading...} + {error && <>error...} + + ) +} + + +const getFilteredIssueItems = (filterName: string, targetId: any) => (data: any) => { + if (!targetId) return data; + const property = data[filterName]; + if (!Array.isArray(property)) { + return property.id === targetId; + } else { + return property.find(({ id }: { id: number }) => id === targetId); + } +} +export default IssueList; diff --git a/fe/client/src/components/issueList/IssueStateChangeFilter.tsx b/fe/client/src/components/issueList/IssueStateChangeFilter.tsx new file mode 100644 index 000000000..304529f05 --- /dev/null +++ b/fe/client/src/components/issueList/IssueStateChangeFilter.tsx @@ -0,0 +1,49 @@ +import React from 'react' +import FilterTab from '@components/common/FilterTab'; +import styled from 'styled-components'; +import ArrowBottomIcon from '@/Icons/ArrowBottom.svg'; + +type IssueStateChangeFilterType = { + title: string; + handleClickShowFilterModal: (title: string) => () => void +} +const filterItems = [ + { name: '선택한 이슈 열기' }, + { name: '선택한 이슈 닫기' } +]; + +const IssueStateChangeFilter = ({ title, handleClickShowFilterModal }: IssueStateChangeFilterType) => { + return ( + +
+ 상태 수정 + +
+ +
+ ) +} +const FilterWrapper = styled.div` + display: flex; + position: relative; + &:hover{ + cursor:pointer; + } +`; + +const ArrowImageTag = styled.img` + width: 16px; + height: 16px; + transform: translateY(3px); +`; + +const FilterTitleSpan = styled.span` + max-width: 70px; + padding-right: 10px; + line-height: 24px; +`; + +export default IssueStateChangeFilter; diff --git a/fe/client/src/components/issueList/ListHeader.tsx b/fe/client/src/components/issueList/ListHeader.tsx index ddda18a98..dbde91ee8 100644 --- a/fe/client/src/components/issueList/ListHeader.tsx +++ b/fe/client/src/components/issueList/ListHeader.tsx @@ -1,5 +1,7 @@ +import { useEffect } from 'react'; import styled from 'styled-components'; import FilterItem from './FilterItem'; +import IssueStateChangeFilter from './IssueStateChangeFilter'; import IconButton from '@components/common/IconButton'; import { IssueListItemType } from '@components/common/types/APIType'; import { issueCheckedItemAtom, issueCheckedAllItemAtom } from '@components/common/atoms/checkBoxAtom'; @@ -12,13 +14,13 @@ type ListHeaderType = { } const ListHeader = ({ issueItems, handleClickShowFilterModal }: ListHeaderType) => { - const [, setCheckedIssueItems] = useRecoilState(issueCheckedItemAtom); + const [checkedIssueItem, setCheckedIssueItems] = useRecoilState(issueCheckedItemAtom); const [isAllIssueChecked, setAllIssueChecked] = useRecoilState(issueCheckedAllItemAtom); - + const handleChangeAllCheck = () => { isAllIssueChecked - ? setCheckedIssueItems(new Set(issueItems.map(({ id }: { id: number }) => id))) - : setCheckedIssueItems(new Set()); + ? setCheckedIssueItems(new Set()) + : setCheckedIssueItems(new Set(issueItems.map(({ id }: { id: number }) => id))); setAllIssueChecked(!isAllIssueChecked); }; @@ -36,25 +38,37 @@ const ListHeader = ({ issueItems, handleClickShowFilterModal }: ListHeaderType)
- - - - 열린 이슈 ({openIssueCount}) - - - - - - 닫힌 이슈 ({closeIssueCount}) - - -
+ {checkedIssueItem.size + ? <>{checkedIssueItem.size}개 이슈 선택 + : + <> + + + + 열린 이슈 ({openIssueCount}) + + + + + + 닫힌 이슈 ({closeIssueCount}) + + + + } - - {filterItems.map((title) => { - return - })} - + + {checkedIssueItem.size + ? + + + + : + {filterItems.map((title) => { + return + })} + + }
)