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

[FE] feature: 제출할 리뷰를 한 번에 볼 수 있는 preview 모달 제작 #302

Merged
merged 11 commits into from
Aug 12, 2024
Merged
3 changes: 3 additions & 0 deletions frontend/src/assets/x.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
115 changes: 115 additions & 0 deletions frontend/src/components/AnswerListPreviewModal/answerList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
interface Option {
optionId: number;
content: string;
isChecked: boolean;
}

interface OptionGroup {
optionGroupId: number;
minCount: number;
maxCount: number;
options: Option[];
}

interface BaseQuestion {
questionId: number;
required: boolean;
questionType: 'CHECKBOX' | 'TEXT';
content: string;
}

interface CheckboxQuestion extends BaseQuestion {
questionType: 'CHECKBOX';
optionGroup: OptionGroup;
}

interface TextQuestion extends BaseQuestion {
questionType: 'TEXT';
optionGroup: null;
hasGuideline: boolean;
guideline: string | null;
answer: string | null;
}

type Question = CheckboxQuestion | TextQuestion;

export interface Section {
sectionId: number;
header: string;
questions: Question[];
}

// NOTE: 리뷰 상세 조회의 Section 속성 이하를 가져옴
export const ANSWER_LIST: Section[] = [
{
sectionId: 1,
header: '기억을 떠올려볼게요.',
questions: [
{
questionId: 1,
required: true,
questionType: 'CHECKBOX',
content: '프로젝트 기간동안 강점이 드러난 ~ 골라주세요',
optionGroup: {
optionGroupId: 1,
minCount: 1,
maxCount: 2,
options: [
{ optionId: 1, content: '코드리뷰', isChecked: true },
{ optionId: 2, content: '프로젝트 관리', isChecked: false },
{ optionId: 3, content: '커뮤니케이션 능력', isChecked: true },
],
},
},
],
},

{
sectionId: 2,
header: '이제 선택한 순간을 바탕으로 리뷰를 작성해볼게요',
questions: [
{
questionId: 2,
required: true,
questionType: 'CHECKBOX',
content: '어떤 부분이 인상깊었나요 ?',
optionGroup: {
optionGroupId: 1,
minCount: 1,
maxCount: 3,
options: [
{ optionId: 4, content: '코드리뷰', isChecked: true },
{ optionId: 5, content: '프로젝트 관리', isChecked: true },
{ optionId: 6, content: '커뮤니케이션 능력', isChecked: false },
],
},
},
{
questionId: 3,
required: true,
questionType: 'TEXT',
content: '인상깊은 상황을 이야기해주세요',
optionGroup: null,
hasGuideline: true,
guideline: '가이드라인',
answer: '쑤쑤 쑤퍼노바 인상깊어요',
},
],
},
{
sectionId: 3,
header: '응원의 한마디를 남겨주세요',
questions: [
{
questionId: 4,
required: false,
questionType: 'TEXT',
content: '응원의 한마디',
optionGroup: null,
hasGuideline: false,
guideline: null,
answer: '응원합니다 화이팅!!',
},
],
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { QuestionCardStyleType } from '@/types';

import * as S from './styles';

interface QuestionCardProps {
questionType: QuestionCardStyleType;
question: string;
}

const QuestionCard = ({ questionType, question }: QuestionCardProps) => {
return <S.QuestionCard questionType={questionType}>{question}</S.QuestionCard>;
};

export default QuestionCard;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import styled from '@emotion/styled';

import { QuestionCardStyleType } from '@/types';

export const QuestionCard = styled.div<{ questionType: QuestionCardStyleType }>`
margin-bottom: 2rem;
font-size: ${({ questionType, theme }) => (questionType === 'guideline' ? theme.fontSize.basic : '1.8rem')};
font-weight: ${({ questionType, theme }) =>
questionType === 'guideline' ? theme.fontWeight.normal : theme.fontWeight.semibold};
color: ${({ questionType, theme }) => (questionType === 'guideline' ? theme.colors.placeholder : theme.colors.black)};
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { EssentialPropsWithChildren } from '@/types';

import * as S from './styles';

interface ReviewWritingCardProps {
title: string;
}

const ReviewWritingCard = ({ title, children }: EssentialPropsWithChildren<ReviewWritingCardProps>) => {
return (
<S.Container>
<S.Header>
<S.Title>{title}</S.Title>
</S.Header>
<S.Main>{children}</S.Main>
</S.Container>
);
};

export default ReviewWritingCard;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import styled from '@emotion/styled';

export const Container = styled.div`
display: flex;
flex-direction: column;
`;

export const Header = styled.div`
width: 100%;
height: 5rem;
padding: 1rem 2rem;
background-color: ${({ theme }) => theme.colors.lightPurple};
`;

export const Main = styled.div`
padding: 2rem;
`;

export const Title = styled.span`
margin-bottom: 2rem;
font-size: ${({ theme }) => theme.fontSize.mediumSmall};
font-weight: ${({ theme }) => theme.fontWeight.semibold};
`;
56 changes: 56 additions & 0 deletions frontend/src/components/AnswerListPreviewModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Fragment } from 'react';

import CheckboxItem from '../common/CheckboxItem';
import ContentModal from '../common/modals/ContentModal';

import { Section } from './answerList';
import QuestionCard from './components/QuestionCard';
import ReviewWritingCard from './components/ReviewWritingCard';
import * as S from './styles';

interface AnswerListPreviewModalProps {
answerList: Section[];
closeModal: () => void;
}

const AnswerListPreviewModal = ({ answerList, closeModal }: AnswerListPreviewModalProps) => {
return (
<ContentModal handleClose={closeModal}>
<S.AnswerListContainer>
<S.CardLayout>
{answerList.map((section) => (
<S.ReviewWritingCardWrapper key={section.sectionId}>
<ReviewWritingCard title={section.header}>
{section.questions.map((question) => (
<Fragment key={question.questionId}>
<QuestionCard questionType="normal" question={question.content} />
<S.ContentContainer>
{question.questionType === 'CHECKBOX' && (
<div>
{question.optionGroup?.options.map((option, index) => (
<CheckboxItem
key={`${question.questionId}_${index}`}
id={`${question.questionId}_${index}`}
name={`${question.questionId}_${index}`}
isChecked={option.isChecked}
isDisabled={true}
label={option.content}
$isReadonly={true}
/>
))}
</div>
)}
<div>{question.questionType === 'TEXT' && <div>{question.answer ?? ''}</div>}</div>
</S.ContentContainer>
</Fragment>
))}
</ReviewWritingCard>
</S.ReviewWritingCardWrapper>
))}
</S.CardLayout>
</S.AnswerListContainer>
</ContentModal>
);
};

export default AnswerListPreviewModal;
27 changes: 27 additions & 0 deletions frontend/src/components/AnswerListPreviewModal/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import styled from '@emotion/styled';

export const AnswerListContainer = styled.div``;

export const CardLayout = styled.div`
display: flex;
flex-direction: column;

gap: 1.2rem;
position: relative;

overflow: hidden;

width: ${({ theme }) => theme.formWidth};
`;

export const ContentContainer = styled.div`
display: flex;
flex-direction: column;
gap: 3rem;
`;

export const ReviewWritingCardWrapper = styled.div`
overflow: hidden;
border: 0.2rem solid ${({ theme }) => theme.colors.lightPurple};
border-radius: ${({ theme }) => theme.borderRadius.basic};
`;
17 changes: 5 additions & 12 deletions frontend/src/components/common/Checkbox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,23 @@ import * as S from './styles';

// NOTE: 공통 컴포넌트에서 이 스타일 속성을 계속 쓰는 것 같은데 이걸 아예 공통 타입으로 빼버릴지 고민
export interface CheckboxStyleProps {
$isReadonly?: boolean;
$style?: React.CSSProperties;
}

export interface CheckboxProps extends CheckboxStyleProps {
id: string;
isChecked: boolean;
onChange: (event: ChangeEvent<HTMLInputElement>, label?: string) => void;
onChange?: (event: ChangeEvent<HTMLInputElement>, label?: string) => void;
Copy link
Contributor

Choose a reason for hiding this comment

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

컨벤션에 맞게 handleChange로 수정해야 할 것 같아요

name?: string;
isDisabled?: boolean;
}

const Checkbox = ({ id, name, isChecked, isDisabled = false, onChange, $style }: CheckboxProps) => {
const Checkbox = ({ id, isChecked, $style, $isReadonly = false, ...rest }: CheckboxProps) => {
return (
<S.CheckboxContainer $style={$style}>
<S.CheckboxContainer $style={$style} $isReadonly={$isReadonly}>
<S.CheckboxLabel>
<input
id={id}
name={name}
checked={isChecked}
onChange={onChange}
disabled={isDisabled}
aria-hidden={false}
type="checkbox"
/>
<input id={id} checked={isChecked} type="checkbox" {...rest} />
<img src={isChecked ? CheckedIcon : UncheckedIcon} alt="체크박스" />
</S.CheckboxLabel>
</S.CheckboxContainer>
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/components/common/Checkbox/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export const CheckboxContainer = styled.div<CheckboxStyleProps>`
width: 0;
height: 0;
}

${(props) => props.$isReadonly && 'pointer-events: none;'}
${({ $style }) => $style && { ...$style }};
`;

Expand Down
30 changes: 30 additions & 0 deletions frontend/src/components/common/modals/ContentModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import CloseIcon from '@/assets/x.svg';
import { EssentialPropsWithChildren } from '@/types';

import ModalBackground from '../ModalBackground';
import ModalPortal from '../ModalPortal';

import * as S from './styles';

interface ContentModalProps {
handleClose: () => void;
}

const ContentModal = ({ handleClose, children }: EssentialPropsWithChildren<ContentModalProps>) => {
return (
<ModalPortal>
<ModalBackground closeModal={handleClose}>
<S.ContentModalContainer>
<S.ContentModalHeader>
<S.CloseButton onClick={handleClose}>
<img src={CloseIcon} alt="모달 닫기" />
</S.CloseButton>
</S.ContentModalHeader>
<S.Contents>{children}</S.Contents>
</S.ContentModalContainer>
</ModalBackground>
</ModalPortal>
);
};

export default ContentModal;
44 changes: 44 additions & 0 deletions frontend/src/components/common/modals/ContentModal/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import styled from '@emotion/styled';

export const ContentModalContainer = styled.div`
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);

display: flex;
flex-direction: column;
gap: 2rem;
align-items: center;

min-width: 30rem;
max-width: 80vw;
max-height: 90vh;
padding: 3.2rem;

background-color: ${({ theme }) => theme.colors.white};
border-radius: ${({ theme }) => theme.borderRadius.basic};

overflow: hidden;
`;

export const ContentModalHeader = styled.div`
display: flex;
justify-content: flex-end;
width: 100%;
height: 3rem;
`;

export const Contents = styled.div`
display: flex;
flex-direction: column;
align-items: center;
white-space: pre-line;

overflow-y: auto;
`;

export const CloseButton = styled.button`
width: 2.4rem;
height: 2.4rem;
`;
Loading
Loading