Skip to content

Commit

Permalink
[Feature/FE] 사용자 통계 기능 구현 (#310)
Browse files Browse the repository at this point in the history
[FE] 사용자 통계 기능 구현 (#309)

* feat: 사용자 통계 기능 구현

* fix: BarGraph storybook 에러 수정
  • Loading branch information
jswith authored and Ohzzi committed Aug 2, 2022
1 parent d2800d8 commit 2e39a9b
Show file tree
Hide file tree
Showing 9 changed files with 248 additions and 42 deletions.
2 changes: 1 addition & 1 deletion backend/security
21 changes: 19 additions & 2 deletions frontend/src/components/common/BarGraph/BarGraph.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ export default {
title: 'Components/BarGraph',
};

const Template: ComponentStory<typeof BarGraph> = () => <BarGraph />;
const Template: ComponentStory<typeof BarGraph> = (args) => (
<BarGraph {...args} />
);

export const Default = () => <Template />;
const statistics = {
careerLevel: {
midlevel: 0.2,
senior: 0.3,
none: 0.1,
junior: 0.4,
},
jobType: {
frontend: 0.45,
backend: 0.25,
mobile: 0.2,
etc: 0.1,
},
};

export const Default = () => <Template statistics={statistics} />;
23 changes: 19 additions & 4 deletions frontend/src/components/common/BarGraph/BarGraph.style.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,28 @@ import styled from 'styled-components';

export const Container = styled.div`
display: flex;
position: relative;
flex-direction: column;
align-items: center;
width: 500px;
width: 448px;
padding: 3rem 1rem;
background-color: #ffffff;
`;

export const BarGraphTitleWrapper = styled.div`
position: absolute;
display: flex;
gap: 1rem;
margin-bottom: 0.5rem;
bottom: 0rem;
`;

export const BarGraphTitle = styled.button`
font-size: 1.1rem;
background-color: #fff;
padding: 0.2rem;
`;

export const DataWrapper = styled.div`
display: flex;
justify-content: center;
Expand Down Expand Up @@ -51,12 +66,12 @@ export const PercentWrapper = styled.div`
position: relative;
justify-content: center;
align-items: center;
font-size: 0.8rem;
font-size: 1.2rem;
`;

export const Percent = styled.div`
position: absolute;
top: -1rem;
top: -1.3rem;
color: ${({ theme }) => theme.colors.black};
`;

Expand All @@ -71,5 +86,5 @@ export const JobType = styled.div`
justify-content: center;
align-items: center;
width: calc(320px / 4);
font-size: 0.8rem;
font-size: 1.1rem;
`;
166 changes: 131 additions & 35 deletions frontend/src/components/common/BarGraph/BarGraph.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,138 @@
import * as S from '@/components/common/BarGraph/BarGraph.style';
import theme from '@/style/theme';
import { useState } from 'react';

type Prop = {
statistics: Statistics;
};

function BarGraph({ statistics }: Prop) {
const [isJobType, setIsJobType] = useState(true);

const toggleGraph = () => {
setIsJobType((state) => !state);
};

const { careerLevel, jobType } = statistics;

function BarGraph() {
return (
<S.Container>
<S.DataWrapper>
<S.BarWrapper>
<S.Bar color={theme.colors.secondary} height={25} />
<S.PercentWrapper>
<S.Percent>25%</S.Percent>
</S.PercentWrapper>
</S.BarWrapper>
<S.BarWrapper>
<S.Bar color={theme.colors.black} height={50} />
<S.PercentWrapper>
<S.Percent>50%</S.Percent>
</S.PercentWrapper>
</S.BarWrapper>
<S.BarWrapper>
<S.Bar color={theme.colors.primary} height={75} />
<S.PercentWrapper>
<S.Percent>75%</S.Percent>
</S.PercentWrapper>
</S.BarWrapper>
<S.BarWrapper>
<S.Bar color={theme.colors.primaryDark} height={100} />
<S.PercentWrapper>
<S.Percent>100%</S.Percent>
</S.PercentWrapper>
</S.BarWrapper>
</S.DataWrapper>
<S.JobTypeWrapper>
<S.JobType>프론트엔드</S.JobType>
<S.JobType>백엔드</S.JobType>
<S.JobType>모바일</S.JobType>
<S.JobType>기타</S.JobType>
</S.JobTypeWrapper>
</S.Container>
<>
{isJobType ? (
<S.Container>
<S.BarGraphTitleWrapper>
<S.BarGraphTitle onClick={toggleGraph}>
연차별 통계 보기
</S.BarGraphTitle>
</S.BarGraphTitleWrapper>
<S.DataWrapper>
<S.BarWrapper>
<S.Bar
key={Math.random()}
color={theme.colors.secondary}
height={jobType.frontend * 100}
/>
<S.PercentWrapper>
<S.Percent>{jobType.frontend * 100}%</S.Percent>
</S.PercentWrapper>
</S.BarWrapper>
<S.BarWrapper>
<S.Bar
key={Math.random()}
color={theme.colors.black}
height={jobType.backend * 100}
/>
<S.PercentWrapper>
<S.Percent>{jobType.backend * 100}%</S.Percent>
</S.PercentWrapper>
</S.BarWrapper>
<S.BarWrapper>
<S.Bar
key={Math.random()}
color={theme.colors.primary}
height={jobType.mobile * 100}
/>
<S.PercentWrapper>
<S.Percent>{jobType.mobile * 100}%</S.Percent>
</S.PercentWrapper>
</S.BarWrapper>
<S.BarWrapper>
<S.Bar
key={Math.random()}
color={theme.colors.primaryDark}
height={jobType.etc * 100}
/>
<S.PercentWrapper>
<S.Percent>{jobType.etc * 100}%</S.Percent>
</S.PercentWrapper>
</S.BarWrapper>
</S.DataWrapper>
<S.JobTypeWrapper>
{['프론트엔드', '백엔드', '모바일', '기타'].map(
(jobType, index) => {
return <S.JobType key={index}>{jobType}</S.JobType>;
}
)}
</S.JobTypeWrapper>
</S.Container>
) : (
<S.Container>
<S.BarGraphTitleWrapper>
<S.BarGraphTitle onClick={toggleGraph}>
직군별 통계 보기
</S.BarGraphTitle>
</S.BarGraphTitleWrapper>
<S.DataWrapper>
<S.BarWrapper>
<S.Bar
key={Math.random()}
color={theme.colors.secondary}
height={careerLevel.none * 100}
/>
<S.PercentWrapper>
<S.Percent>{careerLevel.none * 100}%</S.Percent>
</S.PercentWrapper>
</S.BarWrapper>
<S.BarWrapper>
<S.Bar
key={Math.random()}
color={theme.colors.black}
height={careerLevel.junior * 100}
/>
<S.PercentWrapper>
<S.Percent>{careerLevel.junior * 100}%</S.Percent>
</S.PercentWrapper>
</S.BarWrapper>
<S.BarWrapper>
<S.Bar
key={Math.random()}
color={theme.colors.primary}
height={careerLevel.midlevel * 100}
/>
<S.PercentWrapper>
<S.Percent>{careerLevel.midlevel * 100}%</S.Percent>
</S.PercentWrapper>
</S.BarWrapper>
<S.BarWrapper>
<S.Bar
key={Math.random()}
color={theme.colors.primaryDark}
height={careerLevel.senior * 100}
/>
<S.PercentWrapper>
<S.Percent>{careerLevel.senior * 100}%</S.Percent>
</S.PercentWrapper>
</S.BarWrapper>
</S.DataWrapper>
<S.JobTypeWrapper>
{['경력 없음', '0-2년차', '3-5년차', '5년차 이상'].map(
(jobType, index) => {
return <S.JobType key={index}>{jobType}</S.JobType>;
}
)}
</S.JobTypeWrapper>
</S.Container>
)}
</>
);
}

Expand Down
15 changes: 15 additions & 0 deletions frontend/src/custom.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,19 @@ declare type ReviewInput = {
rating: number;
};

declare type Statistics = {
careerLevel: {
midlevel: number;
senior: number;
none: number;
junior: number;
};
jobType: {
frontend: number;
backend: number;
mobile: number;
etc: number;
};
};

declare const __API_URL__: string;
20 changes: 20 additions & 0 deletions frontend/src/hooks/useStatistics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ENDPOINTS } from '@/constants/api';
import useGetOne from '@/hooks/api/useGetOne';

type Props = {
productId: number;
};

function useStatistics({ productId }: Props): [Statistics, boolean, boolean] {
const {
data: statistics,
isReady,
isError,
} = useGetOne<Statistics>({
url: `${ENDPOINTS.PRODUCT(productId)}/statistics`,
});

return [statistics, isReady, isError];
}

export default useStatistics;
21 changes: 21 additions & 0 deletions frontend/src/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,26 @@ const getKeyboard = (req, res, ctx) => {
return res(ctx.status(200), ctx.json(response), ctx.delay());
};

// 상품 사용자 통계 조회
// 상품 상세 조회
const getStatistics = (req, res, ctx) => {
const response = {
careerLevel: {
midlevel: 0.2,
senior: 0.3,
none: 0.1,
junior: 0.4,
},
jobType: {
frontend: 0.45,
backend: 0.25,
mobile: 0.2,
etc: 0.1,
},
};
return res(ctx.status(200), ctx.json(response), ctx.delay());
};

// 전체 리뷰 목록 조회
const getReviews = (req, res, ctx) => {
const page = Number(req.url.searchParams.get('page'));
Expand Down Expand Up @@ -158,6 +178,7 @@ const submitAdditionalInfo = (req, res, ctx) => {
export const handlers = [
rest.get(`${BASE_URL}${ENDPOINTS.PRODUCTS}`, getKeyboards),
rest.get(`${BASE_URL}${ENDPOINTS.PRODUCT(':id')}`, getKeyboard),
rest.get(`${BASE_URL}${ENDPOINTS.PRODUCT(':id')}/statistics`, getStatistics),
rest.get(`${BASE_URL}${ENDPOINTS.REVIEWS}`, getReviews),
rest.get(
`${BASE_URL}${ENDPOINTS.REVIEWS_BY_PRODUCT_ID(':id')}`,
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/pages/Product/Product.style.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ export const ProductDetailWrapper = styled.div`
width: 30rem;
`;

export const BarGraphWrapper = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 1rem;
`;

export const Wrapper = styled.div`
position: relative;
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/pages/Product/Product.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import theme from '@/style/theme';
import useAuth from '@/hooks/useAuth';
import AsyncWrapper from '@/components/common/AsyncWrapper/AsyncWrapper';
import Loading from '@/components/common/Loading/Loading';
import BarGraph from '@/components/common/BarGraph/BarGraph';
import useStatistics from '@/hooks/useStatistics';

function Product() {
const { isLoggedIn } = useAuth();
Expand All @@ -37,6 +39,9 @@ function Product() {
size: '6',
productId,
});
const [statistics, isStatisticsReady, isStatisticsError] = useStatistics({
productId: Number(productId),
});

const [isSheetOpen, toggleSheetOpen] = useReducer(
(isSheetOpen: boolean) => !isSheetOpen,
Expand Down Expand Up @@ -89,6 +94,15 @@ function Product() {
>
<ProductDetail product={product} />
</AsyncWrapper>
<S.BarGraphWrapper>
<AsyncWrapper
fallback={<Loading />}
isReady={isStatisticsReady}
isError={isStatisticsError}
>
<BarGraph statistics={statistics} />
</AsyncWrapper>
</S.BarGraphWrapper>
</S.ProductDetailWrapper>
</StickyWrapper>
<S.Wrapper ref={reviewListRef}>
Expand Down

0 comments on commit 2e39a9b

Please sign in to comment.