Skip to content

Commit

Permalink
Fe/dev (#82)
Browse files Browse the repository at this point in the history
* feat: 마일스톤 그래프 계산 함수 분리

* feat: 필터 쿼리에 enable속성 추가

* feat: 사이드바 필터 컴포넌트로 분리

* feat: 사이드바 데이터 바인딩

* feat: 사이드바 팝업 띄우기

* feat: 사이드바 팝업 내용 레이아웃

* feat: 이슈 작성자만 이슈 삭제 가능

* feat: 사이드바 팝업 내용 컴포넌트 분리 및 바인딩

* feat: userImg 함수 추가

* refactor: header 아이디, 이미지 변경

* feat: 이슈 상세 - 레이블, 마일스톤, 담당자 수정

* style: 속성키값 변경

* feat: 이슈 상세 - 레이블, 마일스톤, 담당자 수정하는 useMutation

* feat: 닫힌 마일스톤 필터 useQuery

* refactor: clearInterval 수정

* feat: 이슈 상세 - 레이블, 마일스톤, 담당자 수정 적용

* feat: 사용자 이미지 없을 때

* fix: 이슈 필터 action 수정

* refactor: 이슈 필터 선택 후 알림내용 변경

* feat: ~가 없는 이슈 필터링

* style: scrip태그 head태그 안으로 이동

* fix: 이슈 메인 - 필터링 시 조건 추가

* refactor: previousData가 undefined일 경우 예외처리

* feat: 체크박스로 선택한 이슈 상태 변경하기

* feat: 이슈 파일 등록

* chore: 배포 환경 설정

* feat: 리액트쿼리 데브 툴 분기처리

* feat: 커스텀 에디터 height 추가

* feat: 사이드바 - 새로운 이슈일때, 이슈 상세일때 구분

* feat: 이슈 상세 페이지 레이아웃

* feat: 새로운 이슈 생성하는 훅 생성

* feat: 새로운 이슈 생성

* refactor: 사이드바 체크 상태 상위컴포넌트로 이동

* refactor: 체크 박스 눌렀을 때의 드롭다운 내용 수정

* fix: 이슈 상세 옵션바 수정하는 부분 오류

* feat: 레이블 페이지 레이아웃

* chore: 색상 유효성 체크

* docs: 색상 유효성 체크

* feat: 레이블 조회, 새로운 레이블 생성 - 훅 생성

* 레이블 컴포넌트 명 변경 및 레이블 페이지 작업

* feat: 새로운 레이블 추가

* feat: 레이블이 없을 때 & 레이블 리스트

* feat: 레이블 라우트 수정

* style: 사용 없는 코드 삭제

* refactor: 레이블 count 수 없을 때 0으로 표시

* feat: 마일스톤 페이지

* feat: 레이블 컴포넌트 명 변경 및 마일스톤 protectedRoute 추가

* feat: 레이블 추가버튼, 취소버튼 클릭 시 추가창 열고 닫기

* feat: 레이블 편집 시 데이터 바인딩

* feat: 레이블 수정

* refactor: useMutation에 enable속성 제거 및 편집완료 버튼 수정

* feat: useMutation에 enable속성 제거 및 레이블 삭제

* feat: 이슈 리스트 lazy loading적용

* remove: 파일삭제 및 gitignore 추가

* feat: 깃허브 로그인 콜백 컴포넌트 라우트 설정

* feat: 로그인, 마일스톤 개수 조회

* style: 이슈 리스트 버튼 레이아웃 변경

* WIP: 깃허브 로그인 중
  • Loading branch information
minjeongHEO authored May 30, 2024
1 parent a5c3950 commit e65d8b6
Show file tree
Hide file tree
Showing 27 changed files with 1,383 additions and 85 deletions.
2 changes: 2 additions & 0 deletions FE/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.env.production

# Logs
logs
*.log
Expand Down
4 changes: 4 additions & 0 deletions FE/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,7 @@ Web 용 react-router-dom을 React 애플리케이션 프로젝트에 설치해
### 스피너

`$ npm install --save react-spinners`

### 색상 유효성 체크

`$ npm i validate-color --save`
8 changes: 7 additions & 1 deletion FE/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion FE/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"react-spinners": "^0.13.8",
"react-syntax-highlighter": "^15.5.0",
"remark-gfm": "^4.0.0",
"styled-components": "^6.1.10"
"styled-components": "^6.1.10",
"validate-color": "^2.2.4"
},
"devDependencies": {
"@types/react": "^18.2.66",
Expand Down
35 changes: 35 additions & 0 deletions FE/src/api/fetchIssueData.js
Original file line number Diff line number Diff line change
Expand Up @@ -351,3 +351,38 @@ export const fetchUploadFile = async (formData) => {
throw error;
}
};

/**
* 새로운 이슈 생성
* @param {*String} title - 제목
* @param {*String} content - 내용
* @param {*String} authorId - 작성자
* @param {*String} milestoneId - 마일스톤 아이디
* @param {*String} fileId - 파일아이디(없을 시 null)
* @param {*Array} labelIds - 레이블 아이디(없을 시 [])
* @param {*Array} assigneeIds - 담당자 아이디(없을 시 [])
* @returns {*jsonObject}
* - 성공: 200
- 바인딩 에러시: 400
- 서버 내부 오류시: 500
*/
export const fetchCreateNewIssue = async (title, content, authorId, milestoneId, fileIdParam, labelIds, assigneeIds) => {
const fileId = fileIdParam || null;
try {
const response = await fetch(`${import.meta.env.VITE_TEAM_SERVER}${ISSUE_DEFAULT_API_URI}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ title, content, authorId, milestoneId, fileId, labelIds, assigneeIds }),
});

if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);

if (response.status === 200 || response.status === 201) {
return await response.json();
}
} catch (error) {
throw error;
}
};
141 changes: 141 additions & 0 deletions FE/src/api/fetchLabelData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
const LABEL_DEFAULT_API_URI = '/api/labels';
const HOME_DEFAULT_API_URI = '/api/home/components';

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

/**
* 레이블 마일스톤 - 개수 조회
* @param {*String} issueId
* @returns {jsonObject}
* - 성공: 200
*/
export const fetchLabelMilestoneCountData = async () => {
try {
// await delay(2000);
const response = await fetch(`${import.meta.env.VITE_TEAM_SERVER}${HOME_DEFAULT_API_URI}`);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);

if (response.status === 200 || response.status === 201) {
return await response.json();
}
} catch (error) {
throw error;
}
};
/**
* 레이블 조회
* @param {*String} issueId
* @returns {jsonObject}
*/
export const fetchLabelDetailData = async () => {
try {
// await delay(2000);
const response = await fetch(`${import.meta.env.VITE_TEAM_SERVER}${LABEL_DEFAULT_API_URI}`);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);

if (response.status === 200 || response.status === 201) {
return await response.json();
}
} catch (error) {
throw error;
}
};

/**
* 레이블 수정
* @param {*String} name
* @param {*String} descriptionParam 없을 경우 null
* @param {*String} textColor
* @param {*String} bgColor
* @param {*String} labelId
* @returns
* - 성공: 200
- 데이터 바인딩(즉, 이름이나 배경 색깔이 비어져서 왔을 때) 실패: 400
- 유효하지 않은 배경 색깔: 400
- 존재하지 않는 라벨 아이디: 404
*/
export const fetchModifyLabel = async (name, descriptionParam, textColor, bgColor, labelId) => {
const description = descriptionParam || null;
try {
const response = await fetch(`${import.meta.env.VITE_TEAM_SERVER}${LABEL_DEFAULT_API_URI}/${labelId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name, description, textColor, bgColor }),
});
const result = await response.json();

if (response.status === 400 || response.status === 404 || response.status === 409) {
throw new Error({ code: response.status, result });
}

if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);

if (response.status === 200 || response.status === 201) {
return result;
}
} catch (error) {
throw error;
}
};

/**
* 레이블 삭제
* @param {*String} labelId
* @returns
- 성공: 200
- 존재하지 않는 라벨 아이디: 404
*/
export const fetchDeleteLabel = async (labelId) => {
try {
const response = await fetch(`${import.meta.env.VITE_TEAM_SERVER}${LABEL_DEFAULT_API_URI}/${labelId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});

if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);

return {
status: response.status,
statusText: response.statusText,
};
} catch (error) {
throw error;
}
};

/**
* 새로운 레이블 생성
* @param {*String} name
* @param {*String} descriptionParam 없을 경우 null
* @param {*String} textColor
* @param {*String} bgColor
* @returns
* - 성공: 201
- 데이터 바인딩(즉, 이름이나 배경 색깔이 비어져서 왔을 때) 실패: 400
- 유효하지 않은 배경 색깔: 400
- 라벨 이름 중복: 409
*/
export const fetchCreateNewLabel = async (name, descriptionParam, textColor, bgColor) => {
const description = descriptionParam || null;
try {
const response = await fetch(`${import.meta.env.VITE_TEAM_SERVER}${LABEL_DEFAULT_API_URI}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name, description, textColor, bgColor }),
});

if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);

if (response.status === 200 || response.status === 201) {
return await response.json();
}
} catch (error) {
throw error;
}
};
29 changes: 29 additions & 0 deletions FE/src/api/fetchMembers.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,32 @@ export const fetchLogin = async ({ id, password }) => {
throw error;
}
};
/**
* 깃허브 로그인
* @param {Object} - {id, password}
* @returns {Object} - { result:boolean, data:{email, id, nickname} };
- 성공: 200
- 데이터 바인딩 실패: 400
- 로그인 실패 : 401
*/
export const fetchGithubLogin = async () => {
try {
const CLIENT_ID = 'Ov23liTzJL66RbPZt3fg';
const REDIRECT_URI = `${import.meta.env.VITE_TEAM_CLIENT}/members/callback`;
const githubAuthUrl = `https://github.com/login/oauth/authorize?client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}`;

// https://github.com/login/oauth/authorize?client_id=Ov23liTzJL66RbPZt3fg&redirect_uri=https://api.issue-tracker.site/api/oauth/github/callback
// https://github.com/login/oauth/authorize?client_id=Ov23liTzJL66RbPZt3fg&redirect_uri={server 주소}/api/oauth/github/callback

const response = await fetch(githubAuthUrl, {
method: 'GET',
});

if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);

const result = await response.json();
return { result: true, data: result };
} catch (error) {
throw error;
}
};
5 changes: 3 additions & 2 deletions FE/src/assets/CustomTextEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default function CustomTextEditor({
$fileOnClick = () => {},
$fileOnChange = () => {},
$isFileUploaded = false,
$height = '100',
}) {
const fileInputRef = useRef(null);

Expand All @@ -22,7 +23,7 @@ export default function CustomTextEditor({

return (
<>
<StyledTextArea value={$value} onChange={$onChange} onFocus={$onFocus} onBlur={$onBlur} />
<StyledTextArea value={$value} onChange={$onChange} onFocus={$onFocus} onBlur={$onBlur} $height={$height} />
<StyledNotifiy>띄어쓰기 포함 {$value?.length ?? 0}</StyledNotifiy>
<StyledLine />
<StyledFileBtn onClick={$fileOnClick}>
Expand Down Expand Up @@ -60,7 +61,7 @@ const StyledTextArea = styled.textarea`
position: relative;
resize: none;
width: 100%;
min-height: 100px;
min-height: ${(props) => props.$height}px;
padding: 15px;
background-color: transparent;
color: ${(props) => props.theme.fontColor};
Expand Down
10 changes: 5 additions & 5 deletions FE/src/components/issues/DropDownFilter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,15 @@ export default function DropDownFilter({ filterTitle, filterItems, dispatchTypeB
key: 'nonSelected',
};

const defaultTypeItems = () => {
const defaultTypeItems = (checkType) => {
return filterItems
? filterItems.reduce((acc, cur) => {
acc.push({
label: (
<ItemContainer>
<div className="itemTitle">
<DropTitle>
{cur.title} {filterTitle === 'state' && isClosedState(selectedFilters) ? '열기' : '닫기'}
{checkType ? (isClosedState(selectedFilters) ? `${cur.title} 열기` : `${cur.title} 닫기`) : cur.title}
</DropTitle>
</div>
<div className="ItemRadio">
Expand Down Expand Up @@ -202,12 +202,12 @@ export default function DropDownFilter({ filterTitle, filterItems, dispatchTypeB
};

const itemByType = {
issue: [titleItem, ...defaultTypeItems()],
issue: [titleItem, ...defaultTypeItems(false)],
assignee: [titleItem, clearTypeItem, ...imageTypeItems()],
label: [titleItem, clearTypeItem, ...labelTypeItems()],
milestone: [titleItem, clearTypeItem, ...defaultTypeItems()],
milestone: [titleItem, clearTypeItem, ...defaultTypeItems(false)],
author: [titleItem, ...imageTypeItems()],
state: [titleItem, , ...defaultTypeItems()],
state: [titleItem, , ...defaultTypeItems(true)],
};

const items = itemByType[filterTitle];
Expand Down
4 changes: 2 additions & 2 deletions FE/src/components/issues/IssueDetail.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ export default function IssueDetail() {
// const remainFileIds = remainFiles.map((file) => file.id);

const remainFileIds = fileMeta.map((file) => file.id).filter((e) => e !== '');
createIssueComment({ writerId: getUserId(), content: newCommentArea, fileId: remainFileIds[0] });

createIssueComment({ writerId: getUserId(), content: newCommentArea, fileId: remainFileIds?.[0] });
setNewCommentArea('');
setFileMeta(initFileDatas);
};
Expand Down Expand Up @@ -156,7 +157,6 @@ export default function IssueDetail() {
$fileOnChange={handleFileChange}
$onFocus={handleFocus}
$onBlur={handleBlur}
setFileMeta={setFileMeta}
/>
</form>
</Content>
Expand Down
34 changes: 30 additions & 4 deletions FE/src/components/issues/IssueDetailSidebar.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import styled from 'styled-components';
import { FlexRow } from '../../styles/theme';
import { Popconfirm, message } from 'antd';
Expand All @@ -7,26 +7,52 @@ import { useNavigate } from 'react-router-dom';
import OptionSidebar from './OptionSidebar';
import { IconTrash } from '../../assets/icons/IconTrash';

const initCheckedData = {
assignee: [],
label: [],
milestone: '',
};


export default function IssueDetailSidebar({ milestone, assignees, labels, issueId, isEditable = false }) {
const navigate = useNavigate();
const onSuccess = () => {
message.success('삭제되었습니다.');
navigate('/');
};
const { mutate: deleteIssue } = useDeleteIssue(issueId, onSuccess);
const [checkedDatas, setCheckedDatas] = useState(initCheckedData);

const deleteConfirm = () => deleteIssue();

return (
<StyledDiv>
<SidebarContainer>
<OptionSidebar filterName={'assignee'} filterData={assignees} issueId={issueId}>
<OptionSidebar
filterName={'assignee'}
filterData={assignees}
issueId={issueId}
checkedDatas={checkedDatas}
setCheckedDatas={setCheckedDatas}
>
담당자
</OptionSidebar>
<OptionSidebar filterName={'label'} filterData={labels} issueId={issueId}>
<OptionSidebar
filterName={'label'}
filterData={labels}
issueId={issueId}
checkedDatas={checkedDatas}
setCheckedDatas={setCheckedDatas}
>
레이블
</OptionSidebar>
<OptionSidebar filterName={'milestone'} filterData={milestone} issueId={issueId}>
<OptionSidebar
filterName={'milestone'}
filterData={milestone}
issueId={issueId}
checkedDatas={checkedDatas}
setCheckedDatas={setCheckedDatas}
>
마일스톤
</OptionSidebar>
</SidebarContainer>
Expand Down
Loading

0 comments on commit e65d8b6

Please sign in to comment.