Skip to content
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

투표 상세 통계 컴포넌트 제작_#54 #56

Merged
merged 18 commits into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
56dd63c
feat: (#54) 투표 통계 mockData 생성
chsua Jul 16, 2023
1ed4e8b
feat: (#54) 투표 통계 관련 type, interface 생성
chsua Jul 16, 2023
49891d3
feat: (#54) 그래프 공통 스타일 생성
chsua Jul 16, 2023
52c2e00
feat: (#54) 막대 하나 그래프 구현
chsua Jul 16, 2023
ebefca5
test: (#54) 막대 하나 그래프 사이즈별 테스트 구현
chsua Jul 16, 2023
65e207a
feat: (#54) 막대 두개 그래프 구현
chsua Jul 16, 2023
ea88e1a
test: (#54) 막대 두개 그래프 사이즈별 테스트 구현
chsua Jul 16, 2023
757ef4f
design: (#54) 그래프 공통 스타일 수정
chsua Jul 16, 2023
c0bfdd5
feat: (#54) 라디오를 포함한 투표 통계 결과 그래프 컴포넌트 구현
chsua Jul 16, 2023
c912306
test: (#54) 라디오를 포함한 투표 통계 결과 그래프 컴포넌트 테스트 구현
chsua Jul 16, 2023
84ed1a4
refactor: 라디오를 포함한 투표 통계결과 그래프 컴포넌트명/폴더명 변경
chsua Jul 16, 2023
e05a213
style: (#54) styled component 파일 컨벤션에 맞춰 순서 수정
chsua Jul 16, 2023
87fce16
refactor: (#54) 통계 컴포넌트 타입/인터베이스 오타 수정
chsua Jul 17, 2023
7c28fa4
refactor: (#54) 그래프 스타일 상수화하여 코드 정리
chsua Jul 17, 2023
717d09c
refactor: (#54) 공통된 그래프 프롭스 interface 리팩토링
chsua Jul 17, 2023
54d3a9b
refactor: 투표 통계 나이대 속성 type 리팩토링
chsua Jul 17, 2023
6e77579
refactor: (#54) 몇몇 컴포넌트 반응형 웹 기준 상수화 적용
chsua Jul 17, 2023
0646711
refactor: (#54) 몇몇 컴포넌트 반응형 웹 기준 상수화 재적용
chsua Jul 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions frontend/src/components/Example/style.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { styled } from 'styled-components';

import { theme } from '@styles/theme';

export const Button = styled.button`
width: 80px;
color: ${theme.color.white};
background: ${theme.color.primary};
color: red;
background: black;
font-size: 1rem;
`;
33 changes: 33 additions & 0 deletions frontend/src/components/VoteStatistics/GraphStyle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { styled } from 'styled-components';

import { Size } from '@components/common/AddButton/type';

import { theme } from '@styles/theme';

const size: { [key in Size]: { height: string; linePositionTop: string } } = {
sm: { height: '200px', linePositionTop: '165px' },
md: { height: '230px', linePositionTop: '194px' },
lg: { height: '260px', linePositionTop: '224px' },
};

export const GraphContainer = styled.div<{ $size: Size }>`
display: flex;

height: ${props => `${size[props.$size].height}`};

position: relative;

font-size: 1.2rem;

@media (min-width: ${theme.breakpoint.sm}) {
font-size: 1.4rem;
}
`;

export const Line = styled.div<{ $size: Size }>`
width: 100%;
border-bottom: 2px solid black;

position: absolute;
top: ${props => `${size[props.$size].linePositionTop}`};
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Meta, StoryObj } from '@storybook/react';

import { mockVoteResult } from '../mockData';

import OneLineGraph from '.';

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

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

export const SizeSm: Story = {
render: () => <OneLineGraph size="sm" voteResult={mockVoteResult} />,
};

export const SizeMd: Story = {
render: () => <OneLineGraph size="md" voteResult={mockVoteResult} />,
};

export const SizeLg: Story = {
render: () => <OneLineGraph size="lg" voteResult={mockVoteResult} />,
};
28 changes: 28 additions & 0 deletions frontend/src/components/VoteStatistics/OneLineGraph/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as GS from '../GraphStyle';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오~~~ 저는 한 파일에 다 몰아넣었었는데😅 GS 로 파일 구분한 점이 너무 좋네요~~ 참고해야겠습니다👍

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 참고하겠습니다 👍👍👍

import { AGE_OPTION, GraphProps } from '../type';

import * as S from './style';

export default function OneLineGraph({ voteResult, size }: GraphProps) {
const maxVoteAmount = Math.max(
...Object.values(voteResult.age).map(voteResult => voteResult.total)
);

return (
<GS.GraphContainer $size={size}>
<GS.Line $size={size} />
{AGE_OPTION.map(option => {
const voteResultFilteredByAge = voteResult.age[option];
const amount = Math.floor((voteResultFilteredByAge.total / maxVoteAmount) * 100);

return (
<S.OptionContainer $size={size}>
<span aria-label="투표한 인원">{voteResultFilteredByAge.total}</span>
<S.OptionLength $amount={amount} />
<span aria-label="투표한 나이대">{voteResultFilteredByAge.name}</span>
</S.OptionContainer>
);
})}
</GS.GraphContainer>
);
}
34 changes: 34 additions & 0 deletions frontend/src/components/VoteStatistics/OneLineGraph/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { styled } from 'styled-components';

import { Size } from '@components/common/AddButton/type';

import { theme } from '@styles/theme';

export const OptionContainer = styled.div<{ $size: Size }>`
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: center;
gap: 5px;

width: ${props => (props.$size === 'sm' ? '30px' : props.$size === 'md' ? '40px' : '50px')};

& > :last-child {
height: 30px;

text-align: center;
word-break: keep-all;
}

@media (min-width: ${theme.breakpoint.sm}) {
width: ${props => (props.$size === 'sm' ? '40px' : props.$size === 'md' ? '50px' : '60px')};
}
`;

export const OptionLength = styled.div<{ $amount: number }>`
height: ${props => `${props.$amount * 0.8}%`};
width: 40%;
border-radius: 5px 5px 0 0;

background-color: #f27676;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Meta, StoryObj } from '@storybook/react';

import { mockVoteResult } from '../mockData';

import TwoLineGraph from '.';

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

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

export const SizeSm: Story = {
render: () => <TwoLineGraph size="sm" voteResult={mockVoteResult} />,
};

export const SizeMd: Story = {
render: () => <TwoLineGraph size="md" voteResult={mockVoteResult} />,
};

export const SizeLg: Story = {
render: () => <TwoLineGraph size="lg" voteResult={mockVoteResult} />,
};
41 changes: 41 additions & 0 deletions frontend/src/components/VoteStatistics/TwoLineGraph/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as GS from '../GraphStyle';
import { AGE_OPTION, GraphProps } from '../type';

import * as S from './style';

export default function TwoLineGraph({ voteResult, size }: GraphProps) {
const maxVoteAmount = Math.max(
...Object.values(voteResult.age).map(voteResult => Math.max(voteResult.female, voteResult.male))
);

return (
<GS.GraphContainer $size={size}>
<GS.Line $size={size} />
{AGE_OPTION.map(option => {
const voteResultFilteredByAge = voteResult.age[option];

return (
<S.OptionContainer $size={size}>
<S.DataWrapper>
<S.OptionLengthWrapper $gender="female">
<span aria-label="투표한 여자수">{voteResultFilteredByAge.female}</span>
<S.OptionLength
$amount={(voteResultFilteredByAge.female / maxVoteAmount) * 100}
$gender="female"
/>
</S.OptionLengthWrapper>
<S.OptionLengthWrapper $gender="male">
<span aria-label="투표한 남자수">{voteResultFilteredByAge.male}</span>
<S.OptionLength
$amount={(voteResultFilteredByAge.male / maxVoteAmount) * 100}
$gender="male"
/>
</S.OptionLengthWrapper>
</S.DataWrapper>
<span aria-label="투표한 나이대">{voteResultFilteredByAge.name}</span>
</S.OptionContainer>
);
})}
</GS.GraphContainer>
);
}
63 changes: 63 additions & 0 deletions frontend/src/components/VoteStatistics/TwoLineGraph/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { styled } from 'styled-components';

import { Size } from '@components/common/AddButton/type';

import { theme } from '@styles/theme';

export const OptionContainer = styled.div<{ $size: Size }>`
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: center;
gap: 5px;

width: ${props => (props.$size === 'sm' ? '30px' : props.$size === 'md' ? '40px' : '50px')};

& > :last-child {
height: 30px;

text-align: center;
word-break: keep-all;
}

@media (min-width: ${theme.breakpoint.sm}) {
width: ${props => (props.$size === 'sm' ? '40px' : props.$size === 'md' ? '50px' : '60px')};
}
`;

export const DataWrapper = styled.div`
display: flex;
justify-content: center;

height: 90%;
width: 50px;

@media (min-width: ${theme.breakpoint.sm}) {
width: 60px;
}
`;

export const OptionLengthWrapper = styled.div<{ $gender: 'female' | 'male' }>`
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: center;
gap: 5px;

height: 100%;
width: 20%;

& > :first-child {
position: relative;
left: ${props => props.$gender === 'male' && '3px'};
right: ${props => props.$gender === 'female' && '3px'};
}
`;

export const OptionLength = styled.div<{ $amount: number; $gender: 'female' | 'male' }>`
height: ${props => `${props.$amount}% `};
width: 100%;
border-radius: 5px 5px 0 0;

background-color: ${props => (props.$gender === 'female' ? '#853DE1' : '#5AEAA5')};
`;
24 changes: 24 additions & 0 deletions frontend/src/components/VoteStatistics/VoteStatistics.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Meta, StoryObj } from '@storybook/react';

import { mockVoteResult } from './mockData';

import VoteStatistics from '.';

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

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

export const SizeSm: Story = {
render: () => <VoteStatistics size="sm" voteResult={mockVoteResult} />,
};

export const SizeMd: Story = {
render: () => <VoteStatistics size="md" voteResult={mockVoteResult} />,
};

export const SizeLg: Story = {
render: () => <VoteStatistics size="lg" voteResult={mockVoteResult} />,
};
53 changes: 53 additions & 0 deletions frontend/src/components/VoteStatistics/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { MouseEvent, useState } from 'react';

import OneLineGraph from './OneLineGraph';
import * as S from './style';
import TwoLineGraph from './TwoLineGraph';
import { GraphProps } from './type';

interface RadioMode {
all: string;
gender: string;
}

const radioMode: RadioMode = {
all: '전체보기',
gender: '성별보기',
};

type RadioCategory = keyof RadioMode;

export default function VoteStatistics({ voteResult, size }: GraphProps) {
const [nowRadioMode, setNowRadioMode] = 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);
};

return (
<S.Container>
<S.CategoryWrapper>
{radioModeKey.map(mode => {
return (
<S.RadioLabel>
<input
type="radio"
name="radio-category"
value={mode}
checked={mode === nowRadioMode}
onClick={changeMode}
/>
{radioMode[mode]}
</S.RadioLabel>
Comment on lines +36 to +45
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

데이터에 변화가 있더라도 대응이 잘 되는 코드라고 생각됩니다 👍👍👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감사합니다!! 더 모드가 추가되어도 문제없이 돌아갔으면 좋겠다고 생각하면서 만들었는데 그 부분 말해주셔서 너무너무 뿌듯하네요 감사합니다!

);
})}
</S.CategoryWrapper>
{nowRadioMode === 'all' && <OneLineGraph size={size} voteResult={voteResult} />}
{nowRadioMode === 'gender' && <TwoLineGraph size={size} voteResult={voteResult} />}
</S.Container>
);
}
15 changes: 15 additions & 0 deletions frontend/src/components/VoteStatistics/mockData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const mockVoteResult = {
total: 100,
female: 30,
name: '총합',
male: 70,
age: {
underTeenager: { total: 10, female: 10, male: 0, name: '10대 미만' },
teenager: { total: 20, female: 10, male: 10, name: '10대' },
twenties: { total: 10, female: 2, male: 8, name: '20대' },
thirties: { total: 20, female: 16, male: 4, name: '30대' },
forties: { total: 40, female: 30, male: 10, name: '40대' },
fifties: { total: 2, female: 1, male: 1, name: '50대' },
aboveFifties: { total: 3, female: 2, male: 1, name: '60대 이상' },
},
};
26 changes: 26 additions & 0 deletions frontend/src/components/VoteStatistics/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { styled } from 'styled-components';

import { theme } from '@styles/theme';

export const Container = styled.div`
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;

font-size: 1.2rem;

@media (min-width: ${theme.breakpoint.sm}) {
font-size: 1.4rem;
}
`;

export const CategoryWrapper = styled.fieldset`
display: flex;
gap: 10px;
`;

export const RadioLabel = styled.label`
display: flex;
gap: 5px;
`;
Loading