Skip to content

Commit

Permalink
게시글 통계 컴포넌트 제작_Feat/#65 (#75)
Browse files Browse the repository at this point in the history
* feat: (#65) msw를 위한 mock 게시물 상세정보 생성

* feat: (#65) 한 게시물 상세정보 fetch mocking

* feat: (#65) 데이터, 로딩, 에러 정보 전달하는 fetch훅 생성

* feat: (#65) 게시물 하나 상세정보 fetch 함수 구현

* refactor: (#65) mockData 수정

* feat: (#65)  통계정보 get하는 api msw작성

* feat: (#65)  전체통계정보, 선택지 통계정보 get하는 fetch함수 작성

* refactor: (#65) useFetch 내 데이터이름 범용성 향성을 위해 수정

* feat: (#65)  통계그래프를 포함한 선택지 컴포넌트 생성

* test: (#65)  통계그래프를 포함한 선택지 컴포넌트 테스트 구현

* feat: (#65)  게시글 투표결과 통계 페이지 구현

* test: (#65)  게시글 투표결과 통계 페이지 테스트 구현

* feat: (#65) 로딩컴포넌트 구현

* test: (#65) 로딩컴포넌트 크기별 테스트

* refactor: 선택지변경 api 인자 interface 리팩터링

* style: (#65) 사용하지 않는 스타일컴포넌트 삭제 및 코드 정리

* feat: (#65) 통계컴포넌트에 로딩스피너 적용

* fix: (#65) 라디오 name속성이 공통되어 생긴 오작동 오류 수정

* refactor: ($65) map에 키 값 부여

* fix: 라디오에서 발생하는 checked 관련 오류 해결

- checked를 사용하는 경우 onChange 이벤트를 사용해야 함.
- 때문에 defaultChecked로 수정하여 해결

* feat: (#65) 모바일 화면 외 크기에서는 헤더 감추기

* style: (#65) 로딩스피너 오타수정

* style: (#65) css 컨벤션에 따라 순서 수정

* refactor: (#65) 불필요한 코드 정리

- key와 value가 같다면 value 기재 생략
- useFetch 인자 수정
- msw 테스트 정리
- 안쓰는 코드 각주 삭제

* refactor: (#65) 목적에 맞지 않는 선택지 통계 토글 함수명 수정

* feat: (#54) 헤더에 있는 이전페이지로 가기 버튼 navigate 연결

* refactor: (#65) font-size를 var로 수정

* refactor: (#65) 통계 컴포넌트 라디오 상태명 변경

- 수정전: nowRadioMode
- 수정후: currentRadioMode

* refactor: (#65) 대소문자/오탈자 수정
  • Loading branch information
chsua authored and tjdtls690 committed Sep 12, 2023
1 parent b493e83 commit c345811
Show file tree
Hide file tree
Showing 21 changed files with 484 additions and 22 deletions.
20 changes: 14 additions & 6 deletions frontend/src/api/sua/post.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import { patchFetch, postFetch } from '@utils/fetch';
import { PostInfo } from '@type/post';

import { getFetch, patchFetch, postFetch } from '@utils/fetch';

export const votePost = async (postId: number, optionId: number) => {
return await postFetch(`/posts/${postId}/options/${optionId}`, '');
};

export const changeVotedOption = async (
postId: number,
{ originOptionId, newOptionId }: { originOptionId: number; newOptionId: number }
) => {
interface OptionData {
originOptionId: number;
newOptionId: number;
}

export const changeVotedOption = async (postId: number, optionData: OptionData) => {
return await patchFetch(
`/posts/${postId}/options?source=${originOptionId}&target=${newOptionId}`
`/posts/${postId}/options?source=${optionData.originOptionId}&target=${optionData.newOptionId}`
);
};

export const getVoteDetail = async (postId: number): Promise<PostInfo> => {
return await getFetch<PostInfo>(`/posts/${postId}`);
};
17 changes: 17 additions & 0 deletions frontend/src/api/sua/voteResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { VoteResult } from '@components/VoteStatistics/type';

import { getFetch } from '@utils/fetch';

export const getPostStatistics = async (postId: number): Promise<VoteResult> => {
return await getFetch<VoteResult>(`/posts/${postId}/options`);
};

export const getOptionStatistics = async ({
postId,
optionId,
}: {
postId: number;
optionId: number;
}): Promise<VoteResult> => {
return await getFetch<VoteResult>(`/posts/${postId}/options/${optionId}`);
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default function OneLineGraph({ voteResult, size }: GraphProps) {
const amount = Math.floor((voteResultFilteredByAge.total / maxVoteAmount) * 100);

return (
<S.OptionContainer $size={size}>
<S.OptionContainer key={option} $size={size}>
<span aria-label="투표한 인원">{voteResultFilteredByAge.total}</span>
<S.OptionLength $amount={amount} />
<span aria-label="투표한 나이대">{voteResultFilteredByAge.name}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default function TwoLineGraph({ voteResult, size }: GraphProps) {
const voteResultFilteredByAge = voteResult.age[option];

return (
<S.OptionContainer $size={size}>
<S.OptionContainer key={option} $size={size}>
<S.DataWrapper>
<S.OptionLengthWrapper $gender="female">
<span aria-label="투표한 여자수">{voteResultFilteredByAge.female}</span>
Expand Down
16 changes: 9 additions & 7 deletions frontend/src/components/VoteStatistics/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,38 @@ const radioMode: RadioMode = {
type RadioCategory = keyof RadioMode;

export default function VoteStatistics({ voteResult, size }: GraphProps) {
const [nowRadioMode, setNowRadioMode] = useState<RadioCategory>('all');
const [currentRadioMode, setCurrentRadioMode] = useState<RadioCategory>('all');

const radioModeKey = Object.keys(radioMode) as RadioCategory[];

const changeMode = (e: MouseEvent<HTMLInputElement>) => {
const target = e.target as HTMLInputElement;
const targetCategory = target.value as RadioCategory;
setNowRadioMode(targetCategory);
setCurrentRadioMode(targetCategory);
};

const random = Date.now();

return (
<S.Container>
<S.CategoryWrapper>
{radioModeKey.map(mode => {
return (
<S.RadioLabel>
<S.RadioLabel key={mode}>
<input
type="radio"
name="radio-category"
name={`radio-category-${random}`}
value={mode}
checked={mode === nowRadioMode}
defaultChecked={mode === currentRadioMode}
onClick={changeMode}
/>
{radioMode[mode]}
</S.RadioLabel>
);
})}
</S.CategoryWrapper>
{nowRadioMode === 'all' && <OneLineGraph size={size} voteResult={voteResult} />}
{nowRadioMode === 'gender' && <TwoLineGraph size={size} voteResult={voteResult} />}
{currentRadioMode === 'all' && <OneLineGraph size={size} voteResult={voteResult} />}
{currentRadioMode === 'gender' && <TwoLineGraph size={size} voteResult={voteResult} />}
</S.Container>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Meta, StoryObj } from '@storybook/react';

import LoadingSpinner from '.';

const meta: Meta<typeof LoadingSpinner> = {
component: LoadingSpinner,
};

export default meta;
type Story = StoryObj<typeof LoadingSpinner>;

export const SizeS: Story = {
render: () => <LoadingSpinner size="sm" />,
};

export const sizeM: Story = {
render: () => <LoadingSpinner size="md" />,
};

export const sizeL: Story = {
render: () => <LoadingSpinner size="lg" />,
};
17 changes: 17 additions & 0 deletions frontend/src/components/common/LoadingSpinner/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Size } from '../AddButton/type';

import * as S from './style';

interface LoadingSpinnerProps {
size: Size;
}

export default function LoadingSpinner({ size }: LoadingSpinnerProps) {
return (
<S.Container $size={size}>
<S.unit $size={size} />
<S.unit $size={size} />
<S.unit $size={size} />
</S.Container>
);
}
44 changes: 44 additions & 0 deletions frontend/src/components/common/LoadingSpinner/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { keyframes, styled } from 'styled-components';

import { Size } from '../AddButton/type';

interface LoadingSpinnerProps {
$size: Size;
}

const size = {
sm: '10px',
md: '15px',
lg: '30px',
};

const Animation = keyframes`
to {
transform: translate(0, -15px);
}
`;

export const Container = styled.div<LoadingSpinnerProps>`
display: flex;
justify-content: center;
align-items: center;
& > :nth-child(2) {
animation-delay: 0.1s;
margin: 0 ${props => size[props.$size]};
}
& > :nth-child(3) {
animation-delay: 0.2s;
}
`;

export const unit = styled.div<LoadingSpinnerProps>`
width: ${props => size[props.$size]};
height: ${props => size[props.$size]};
border-radius: 50%;
background-color: #747474;
animation: ${Animation} 0.5s ease-in-out infinite alternate;
`;
2 changes: 1 addition & 1 deletion frontend/src/components/common/NarrowMainHeader/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const Container = styled.div`
background-color: #1f1f1f;
& :nth-child(2) {
& > :nth-child(2) {
margin-right: auto;
height: 60%;
}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/common/Post/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ export default function Post({ postInfo, isPreview }: PostProps) {
<S.Wrapper>
<S.Writer>{writer.nickname}</S.Writer>
<S.Wrapper>
<S.Time>{startTime}</S.Time>
<S.Time>{endTime}</S.Time>
<span>{startTime}</span>
<span>{endTime}</span>
</S.Wrapper>
</S.Wrapper>
<S.Content $isPreview={isPreview}>{content}</S.Content>
Expand Down
4 changes: 1 addition & 3 deletions frontend/src/components/common/Post/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const Wrapper = styled.div`
font-size: 1.2rem;
:nth-child(2) {
& > :nth-child(2) {
margin-left: 10px;
}
Expand All @@ -59,8 +59,6 @@ export const Wrapper = styled.div`
}
`;

export const Time = styled.span``;

export const Content = styled.p<{ $isPreview: boolean }>`
display: -webkit-box;
Expand Down
28 changes: 28 additions & 0 deletions frontend/src/hooks/useFetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useEffect, useState } from 'react';

export const useFetch = <T>(fetchFn: () => Promise<T>) => {
const [data, setData] = useState<T>();
const [errorMessage, setErrorMessage] = useState<string>();
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
fetchFn()
.then(res => {
setData(res);
})
.catch(rej => {
setErrorMessage(rej.message);
})
.finally(() => {
setIsLoading(false);
});

return (() => {
setData(undefined);
setIsLoading(true);
setErrorMessage(undefined);
})();
}, []);

return { data, errorMessage, isLoading };
};
3 changes: 2 additions & 1 deletion frontend/src/mocks/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { example } from './example/get';
import { getVoteDetailTest } from './sua/getVoteDetail';
import { votePostTest } from './sua/vote';

export const handlers = [...example, ...votePostTest];
export const handlers = [...example, ...votePostTest, ...getVoteDetailTest];
56 changes: 56 additions & 0 deletions frontend/src/mocks/mockData/voteDetail.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"postId": 1,
"title": "어느 곳에서 정보를 찾아야 할지도 막막한 사람들을 위한, 심심풀이로 나의 취향과 남의 취향을 비교해보고 싶은 사람들을 위한 프로젝트",
"writer": {
"id": 12121221,
"nickname": "우아한 잔치국수"
},
"content": "이는 사람들에게 재미와 정보, 두 가지를 줄 수 있습니다. 사람들은 MBTI, 밸런스게임처럼 나와 같은 사람들을 찾고, 나와 다른 사람들과 비교하는 것을 즐깁니다. 이를 컨텐츠화하여 보다 빠르게 질문하고 답변하며, 사람들의 반응을 확인할 수 있다면 사람들은 충분한 즐거움을 느낄 것입니다. 또한 20대가 많은 대학가에 창업을 하고 싶지만 20대의 의견을 모르겠을 때, 확실한 답은 아닐지라도 어느 정도의 가이드를 줄 수 있을 것입니다. 질문자에게 제공되는 성별/나이대별 투표 결과 정보는 질문자가 하고자 하는 의사결정의 근거가 될 수 있을 것입니다.",
"category": [
{
"id": 76767,
"name": "개발"
},
{
"id": 74632,
"name": "연애"
},
{
"id": 71347,
"name": "상담"
}
],
"startTime": "2023-07-12 12:40",
"endTime": "2023-07-13 18:40",
"voteInfo": {
"selectedOptionId": 2,
"allPeopleCount": 123,
"options": [
{
"id": 12,
"text": "당선",
"peopleCount": 30,
"percent": 30
},
{
"id": 123,
"text": "votogether",
"peopleCount": 40,
"percent": 40
},
{
"id": 1234,
"text": "인스타그램, 블라인드와 같은 SNS의 형식을 차용합니다. 누군가는 글을 쓰고, 누군가는 반응합니다. 다만, 댓글은 없습니다. 투표로 자신의 의견을 표현하고 이를 사람들에게 보여줍니다.",
"peopleCount": 20,
"percent": 20
},
{
"id": 2,
"text": "fun from choice, 오늘도 즐거운 한 표 ",
"imageUrl": "https://source.unsplash.com/random",
"peopleCount": 10,
"percent": 10
}
]
}
}
19 changes: 19 additions & 0 deletions frontend/src/mocks/sua/getVoteDetail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { rest } from 'msw';

import { mockVoteResult } from '@components/VoteStatistics/mockData';

import voteDetail from '../mockData/voteDetail.json';

export const getVoteDetailTest = [
rest.get(`/posts/:postId`, (req, res, ctx) => {
return res(ctx.status(200), ctx.delay(1000), ctx.json(voteDetail));
}),

rest.get(`/posts/:postId/options`, (req, res, ctx) => {
return res(ctx.status(200), ctx.delay(1000), ctx.json(mockVoteResult));
}),

rest.get(`/posts/:postId/options/:optionId`, (req, res, ctx) => {
return res(ctx.status(200), ctx.delay(1000), ctx.json(mockVoteResult));
}),
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { Meta, StoryObj } from '@storybook/react';

import OptionStatistics from '.';

const meta: Meta<typeof OptionStatistics> = {
component: OptionStatistics,
};

export default meta;
type Story = StoryObj<typeof OptionStatistics>;

const MOCK_MAX_VOTE_OPTION = {
id: 2,
text: '',
imageUrl: 'https://source.unsplash.com/random',
peopleCount: 10,
percent: 10,
};

export const defaultPage: Story = {
render: () => (
<OptionStatistics
postId={1}
voteOption={MOCK_MAX_VOTE_OPTION}
isSelectedOption={true}
size="sm"
/>
),
};
Loading

0 comments on commit c345811

Please sign in to comment.