Skip to content

Commit

Permalink
Feature/#204 스터디 생성 모달 구현 (#211)
Browse files Browse the repository at this point in the history
* feat: onBlur 속성 추가

#204

* feat: 스터디 생성 모달 구현

#204

* feat: api에 맞게 데이터 필드 추가

#204

* feat: import CurriculumItemDto

#204

* fix: 커리큘럼 추가 제거

#204

* refactor: 폴더 위치 변경 study -> team

#204

* chore: @types/react-datepicker 추가

#204

* refactor: 변수명 변경 onBlur -> handleClose

#204

* refactor: useState type 지정

#204

* feat: Selector placeholder 속성 추가

#204

* feat: 시작날짜 필수값 지정 및 Selector placeholder에 따른 상태값 변경

- 작물 state 이름 변경 crop -> cropName
- Selector placeholder에 따라 cropName 초기값 빈 문자열로 변경
- 시작날짜 필수값 지정

#204

* refactor: 작물 정보 constants로 분리

#204

* refactor: 불필요한 코드 제거

#204
  • Loading branch information
yeonddori authored May 30, 2024
1 parent 48e7d9b commit d24a185
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 3 deletions.
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@swc-jotai/react-refresh": "^0.1.0",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-datepicker": "^6.2.0",
"@types/react-beautiful-dnd": "^13.1.8",
"@types/react-dom": "^18",
"eslint": "^8.56.0",
Expand Down
6 changes: 3 additions & 3 deletions src/components/Selector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { BiChevronDown } from 'react-icons/bi';

import { SelectorProps } from './types';

const Selector = ({ selected, label, handleSelector }: SelectorProps) => {
const Selector = ({ placeholder, selected, label, handleSelector, handleClose }: SelectorProps) => {
const menuRef = useRef<HTMLDivElement>(null);
const [menuWidth, setMenuWidth] = useState('0px');

Expand All @@ -17,7 +17,7 @@ const Selector = ({ selected, label, handleSelector }: SelectorProps) => {
}, []);

return (
<Menu>
<Menu onClose={handleClose}>
<MenuButton
ref={menuRef}
as={Button}
Expand All @@ -30,7 +30,7 @@ const Selector = ({ selected, label, handleSelector }: SelectorProps) => {
_focus={{ bg: 'orange_light' }}
rightIcon={<BiChevronDown size="28px" />}
>
<Text textStyle="bold_md">{selected}</Text>
<Text textStyle="bold_md">{selected || placeholder}</Text>
</MenuButton>
<MenuList
overflow="hidden"
Expand Down
2 changes: 2 additions & 0 deletions src/components/Selector/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export interface SelectorProps {
placeholder?: string;
selected: string;
label: string[];
handleSelector: (data: string) => void;
handleClose?: () => void;
}
9 changes: 9 additions & 0 deletions src/constants/crop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const CROP = [
{ id: 1, name: '토마토' },
{ id: 2, name: '고구마' },
{ id: 3, name: '당근' },
{ id: 4, name: '완두콩' },
{ id: 5, name: '벼' },
];

export default CROP;
148 changes: 148 additions & 0 deletions src/containers/team/CreateStudyModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
'use client';

import { Box, Text, VStack } from '@chakra-ui/react';
import { useEffect, useRef, useState } from 'react';

import AutoResizeTextarea from '@/components/AutoResizeTextarea';
import ActionModal from '@/components/Modal/ActionModal';
import Selector from '@/components/Selector';
import StyledDatePicker from '@/components/StyledDatePicker';
import CROP from '@/constants/crop';

import { CreateStudyModalProps } from './types';

const AlertContent = ({ message }: { message: string }) => {
return (
<Text textStyle="md" color="orange_dark">
{message}
</Text>
);
};

const CreateStudyModal = ({ isOpen, setIsModalOpen }: CreateStudyModalProps) => {
const [step, setStep] = useState<number>(1);
const [name, setName] = useState<string>('');
const [description, setDescription] = useState<string>('');
const [cropName, setCropName] = useState<string>('');
const [cropId, setCropId] = useState<number>(0);
const [startDate, setStartDate] = useState<Date | null>(null);
const [endDate, setEndDate] = useState<Date | null>(null);
const [alertName, setAlertName] = useState<boolean>(false);
const [alertDescription, setAlertDescription] = useState<boolean>(false);
const [alertSelectedCropId, setAlertSelectedCropId] = useState<boolean>(false);
const [alertStartDate, setAlertStartDate] = useState<boolean>(false);

const handlePrevButtonClick = () => {
setStep(step - 1);
};
const handleNextButtonClick = () => {
if (name === '') setAlertName(true);
if (description === '') setAlertDescription(true);
if (name !== '' && description !== '') setStep(step + 1);
};
const handleSaveButtonClick = () => {
if (cropName === '') setAlertSelectedCropId(true);
if (startDate === null) setAlertStartDate(true);
if (cropName !== '' && startDate !== null) setIsModalOpen(false);
};
const handleNameChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setName(e.target.value);
};
const handleDescriptionChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setDescription(e.target.value);
};
const handleStartDateChange = (date: Date | null) => {
setStartDate(date);
};
const handleEndDateChange = (date: Date | null) => {
setEndDate(date);
};

const cropRef = useRef(cropName);

useEffect(() => {
cropRef.current = cropName;
}, [cropName]);

return (
<ActionModal
isOpen={isOpen}
onClose={() => setIsModalOpen(false)}
title="스터디 생성"
subButtonText={step === 1 ? '취소' : '이전'}
mainButtonText={step === 1 ? '다음' : '저장'}
onSubButtonClick={step === 1 ? () => setIsModalOpen(false) : handlePrevButtonClick}
onMainButtonClick={step === 1 ? handleNextButtonClick : handleSaveButtonClick}
>
<Box overflowY="auto" minH="60vh" maxH="60vh">
{step === 1 && (
<>
<Text textStyle="bold_xl" mt="4" mb="2">
스터디 이름 *
</Text>
{alertName && <AlertContent message="필수 입력 란입니다." />}
<AutoResizeTextarea
minH="10vh"
placeholder="스터디 이름을 작성해주세요"
value={name}
onChange={handleNameChange}
_placeholder={{ color: 'white' }}
textStyle="bold_xl"
onBlur={(e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.value !== '') setAlertName(false);
else setAlertName(true);
}}
/>
<Text textStyle="bold_xl" mt="4" mb="2">
스터디 소개 *
</Text>
{alertDescription && <AlertContent message="필수 입력 란입니다." />}
<AutoResizeTextarea
minH="30vh"
placeholder="스터디 소개글을 작성해주세요"
value={description}
onChange={handleDescriptionChange}
_placeholder={{ color: 'white' }}
textStyle="bold_xl"
onBlur={(e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.value !== '') setAlertDescription(false);
else setAlertDescription(true);
}}
/>
</>
)}
{step === 2 && (
<>
<Text textStyle="bold_xl" mt="4" mb="2">
작물 선택 *
</Text>
{alertSelectedCropId && <AlertContent message="필수 입력 란입니다." />}
<Selector
placeholder="작물을 선택해주세요"
selected={cropName}
label={CROP.map((crop) => crop.name)}
handleSelector={(value) => {
setCropName(value);
setCropId(CROP.find((crop) => crop.name === value)?.id || 0);
cropRef.current = value;
}}
handleClose={() => {
if (cropRef.current !== '') setAlertSelectedCropId(false);
else setAlertSelectedCropId(true);
}}
/>
<Text textStyle="bold_xl" mt="8" mb="2">
날짜 선택 *
</Text>
{alertStartDate && <AlertContent message="필수 입력 란입니다." />}
<VStack spacing="3">
<StyledDatePicker label="시작 날짜" selectedDate={startDate} onChange={handleStartDateChange} />
<StyledDatePicker label="종료 날짜" selectedDate={endDate} onChange={handleEndDateChange} />
</VStack>
</>
)}
</Box>
</ActionModal>
);
};
export default CreateStudyModal;
4 changes: 4 additions & 0 deletions src/containers/team/CreateStudyModal/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface CreateStudyModalProps {
isOpen: boolean;
setIsModalOpen: (isOpen: boolean) => void;
}

0 comments on commit d24a185

Please sign in to comment.