Skip to content
22 changes: 22 additions & 0 deletions src/api/evaluation.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { apiEvaluatedUser } from '../models/evaluation';
import { httpClient } from './http.api';

export const postEvaluation = async (userEvaluation: apiEvaluatedUser) => {
try {
const response = await httpClient.post(`/evaluations`, userEvaluation);
return response.status;
} catch (e) {
console.error(e);
throw e;
}
};

export const getEvaluation = async (projectId: number) => {
try {
const response = await httpClient.get(`/evaluations/${projectId}/members`);
return response.data.data;
} catch (e) {
console.error(e);
throw e;
}
};
186 changes: 186 additions & 0 deletions src/components/evaluation/EvaluationContent.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import styled from 'styled-components';
import Button from '../common/Button/Button';

export const Container = styled.div`
display: flex;
width: 100%;
height: 100vh;
`;

export const SidebarLeft = styled.div`
width: 240px;
background-color: #fff;
border-right: 1px solid #e0e0e0;
padding: 20px;

@media (max-width: 1100px) {
width: 190px;
}
`;

export const ProjectName = styled.h2`
font-size: 20px;
margin-bottom: 20px;
`;

export const ParticipantButton = styled.button<{ $active?: boolean }>`
width: 100%;
padding: 10px;
margin-bottom: 10px;
background-color: ${({ $active }) => ($active ? '#2a3f5f' : '#3e5c7c')};
color: ${({ theme }) => theme.color.white};
border: none;
border-radius: 4px;
cursor: pointer;
`;

export const MainContent = styled.div`
flex: 1;
display: flex;
flex-direction: column;
padding: 20px 40px 40px 40px;
`;

export const Header = styled.div`
@media (min-width: 1100px) {
display: flex;
align-items: center;
margin-bottom: 3px;
}

@media (max-width: 1100px) {
margin-bottom: 3px;
}
`;

export const Title = styled.h1`
flex: 1;
font-size: 24px;
margin: 0;
`;

export const MessageContainer = styled.div`
@media (min-width: 1100px) {
display: flex;
justify-content: right;
margin-bottom: 30px;
}

@media (max-width: 1100px) {
margin-bottom: 30px;
}
`;

export const ErrorMessage = styled.p`
font-size: 11px;
margin: 0 10px 0 0;
color: ${({ theme }) => theme.color.red};
`;

export const SubmitButton = styled(Button)`
padding: 8px 16px;
font-size: 13px;
color: #fff;
cursor: pointer;
`;

export const ScrollArea = styled.div`
flex: 1;
overflow-y: auto;
padding-right: 16px;
padding-bottom: 70px;

&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.2);
border-radius: 4px;
}
`;

export const QuestionBlock = styled.div`
margin-bottom: 40px;
`;

export const QuestionHeader = styled.div`
background-color: #f0f0f0;
padding: 12px;
border-radius: 4px;
font-weight: 500;
margin-bottom: 16px;
`;

export const Options = styled.div`
display: flex;
align-items: center;
flex-wrap: wrap;
`;

export const Option = styled.label`
display: flex;
align-items: center;
margin-right: 24px;
margin-bottom: 6px;
`;

export const Radio = styled.input`
display: none;

&:checked + span {
background-color: #3e5c7c;
color: #fff;
}
`;

export const RadioLabel = styled.span`
display: inline-flex;
width: 32px;
height: 32px;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: #ddd;
margin-right: 8px;
cursor: pointer;
`;

export const OptionLabel = styled.span`
font-size: 14px;
color: #333;
`;

export const SidebarRight = styled.div`
width: 200px;
border-left: 1px solid #e0e0e0;
padding: 20px;
background-color: #fff;

@media (max-width: 1100px) {
width: 190px;
}
`;

export const CompletedTitle = styled.h3`
font-size: 18px;
margin-bottom: 20px;
`;

export const CompletedButton = styled.button<{ $active?: boolean }>`
width: 100%;
padding: 10px;
margin-bottom: 10px;
background-color: ${({ $active }) => ($active ? '#3e5c7c' : '#f0f0f0')};
color: ${({ $active }) => ($active ? '#fff' : '#999')};
border: none;
border-radius: 4px;
cursor: ${({ $active }) => ($active ? 'pointer' : 'default')};
transition: background-color 0.2s;

&:hover {
background-color: ${({ $active }) => ($active ? '#2f4a6b' : '#e0e0e0')};
}
`;
104 changes: 104 additions & 0 deletions src/components/evaluation/EvaluationContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import * as S from './EvaluationContent.styled';
import ScrollPreventor from '../common/modal/ScrollPreventor';
import useEvaluationStep from '../../hooks/evaluationHooks/useEvaluationStep';
import { apiMemberList } from '../../models/evaluation';
import { optionLabels, questions } from '../../constants/evaluation';

interface EvaluationContentProps {
projectId: number;
memberList: apiMemberList[];
}

const EvaluationContent = ({
projectId,
memberList,
}: EvaluationContentProps) => {
const {
step,
notDone,
handleClickLeftUser,
handleClickOption,
handleNextStep,
currentScores,
isNotFill,
} = useEvaluationStep({ projectId, memberList });

return (
<ScrollPreventor>
<S.Container>
<S.SidebarLeft>
<S.ProjectName>프로젝트 이름</S.ProjectName>
{notDone.map((name, idx) => (
<S.ParticipantButton
key={name.userId}
$active={step === idx}
onClick={() => handleClickLeftUser(idx)}
>
{name.nickname}
</S.ParticipantButton>
))}
</S.SidebarLeft>

<S.MainContent>
<S.Header>
<S.Title>{notDone[step]?.nickname}님 평가하기</S.Title>
<S.SubmitButton
size='primary'
schema='primary'
radius='primary'
onClick={handleNextStep}
>
제출하기
</S.SubmitButton>
</S.Header>
<S.MessageContainer>
{isNotFill && (
<S.ErrorMessage>모든 질문에 답변해주세요.</S.ErrorMessage>
)}
</S.MessageContainer>

<S.ScrollArea>
{questions.map((q, questionNumber) => (
<S.QuestionBlock key={questionNumber}>
<S.QuestionHeader>
{questionNumber + 1}. {q}
</S.QuestionHeader>
<S.Options>
{optionLabels.map((label, optionValue) => (
<S.Option key={optionValue}>
<S.Radio
type='radio'
name={`q${questionNumber}`}
id={`q${questionNumber}_o${optionValue}`}
checked={
currentScores[questionNumber] === optionValue + 1
}
onChange={() =>
handleClickOption(questionNumber, optionValue)
}
value={optionValue + 1}
/>
<S.RadioLabel>{optionValue + 1}</S.RadioLabel>
<S.OptionLabel>{label}</S.OptionLabel>
</S.Option>
))}
</S.Options>
</S.QuestionBlock>
))}
</S.ScrollArea>
</S.MainContent>

<S.SidebarRight>
<S.CompletedTitle>평가완료</S.CompletedTitle>
{memberList
.filter((memberData) => memberData.evaluated)
.map((memberData) => (
<S.CompletedButton>{memberData.nickname}</S.CompletedButton>
))}
</S.SidebarRight>
</S.Container>
</ScrollPreventor>
);
};

export default EvaluationContent;
16 changes: 16 additions & 0 deletions src/constants/evaluation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const questions = [
'다른 팀원에 비해 비상한 아이디어가 있었나요?',
'맡은 파트를 책임감 있게 한다고 생각하시나요',
'기술력이 좋다고 생각 하시나요?',
'다른 팀원들과의 협업이 잘 이루어졌다 생각 하시나요?',
'문제에 직면 했을 때 끈기 있게 해결 하려고 하나요?',
'참여자가 프로젝트에 성실히 임했다 생각 하시나요?',
] as const;

export const optionLabels = [
'매우그렇다',
'그렇다',
'보통이다',
'그렇지않다',
'매우그렇지않다',
] as const;
1 change: 1 addition & 0 deletions src/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ export const ROUTES = {
notice: 'notice',
noticeDetail: 'notice-detail',
inquiry: '/inquiry',
evaluation: '/evaluation',
loginSuccess: '/login/oauth2/code',
} as const;
Loading