From be8c6e76bccbb3e5c632ce5de194e451e70682b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B4=80=EC=8B=9D?= <39869096+gwansikk@users.noreply.github.com> Date: Sun, 31 Mar 2024 00:25:55 +0900 Subject: [PATCH] =?UTF-8?q?refactor(member):=20=ED=9A=8C=EB=B9=84=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=20=EC=8B=A0=EC=B2=AD=EC=84=9C=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20(#78)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SupportRequestForm/SupportRequestForm.tsx | 225 +++++++++++------- .../SupportRequestSection.tsx | 46 ++++ .../hooks/queries/useMembershipFeeMutation.ts | 7 +- apps/member/src/types/support.ts | 9 + 4 files changed, 206 insertions(+), 81 deletions(-) create mode 100644 apps/member/src/components/support/SupportRequestSection/SupportRequestSection.tsx create mode 100644 apps/member/src/types/support.ts diff --git a/apps/member/src/components/support/SupportRequestForm/SupportRequestForm.tsx b/apps/member/src/components/support/SupportRequestForm/SupportRequestForm.tsx index f2a161f3..a9ce6d5b 100644 --- a/apps/member/src/components/support/SupportRequestForm/SupportRequestForm.tsx +++ b/apps/member/src/components/support/SupportRequestForm/SupportRequestForm.tsx @@ -1,111 +1,176 @@ -import { Button } from '@clab/design-system'; -import { ChangeEvent, useCallback, useRef, useState } from 'react'; -import { Input } from '@clab/design-system'; -import Section from '@components/common/Section/Section'; -import { useMembershipFeeMutation } from '@hooks/queries/useMembershipFeeMutation'; -import Select from '@components/common/Select/Select'; -import { SELECT_OPTIONS } from '@constants/select'; +import { Button, Input, Checkbox, ButtonSelect } from '@clab/design-system'; +import { ChangeEvent, FormEvent, useCallback, useState } from 'react'; import { formatComma } from '@utils/math'; import useToast from '@hooks/common/useToast'; -import Label from '@components/common/Label/Label'; -import { DEFAULT } from '@constants/default'; -import { FORM_DATA_KEY } from '@constants/api'; +import Linker from '@components/common/Linker/Linker'; +import Uploader from '@components/common/Uploader/Uploader'; +import { FcAnswers, FcMultipleDevices, FcTemplate } from 'react-icons/fc'; +import { SELECT_DEFAULT_OPTION } from '@constants/select'; +import type { SupportRequestDataType } from '@type/support'; -const SupportRequestForm = () => { - const toast = useToast(); - const { membershipFeeMutate } = useMembershipFeeMutation(); +const typeSelectOptions = [ + { + icon: , + value: '도서', + }, + { + icon: , + value: '물품', + }, + { + icon: , + value: '기타', + }, +]; + +interface SupportRequestFormProps { + isPending: boolean; + onSubmit: (data: SupportRequestDataType) => void; +} - const imageUploader = useRef(null); - const [input, setInput] = useState({ - category: DEFAULT.SELECT, +const SupportRequestForm = ({ + isPending, + onSubmit, +}: SupportRequestFormProps) => { + const toast = useToast(); + const [checkList, setCheckList] = useState([false, false, false]); + const [formData, setFormData] = useState({ + category: typeSelectOptions[0].value, amount: 0, content: '', + file: null, }); - const { category, amount, content } = input; + const { category, amount, content, file } = formData; - const handleInputChange = useCallback( - (e: ChangeEvent) => { - setInput((prev) => ({ - ...prev, - [e.target.name]: - e.target.name === 'amount' - ? parseFloat(e.target.value.replace(/,/g, '')) - : e.target.value, - })); - }, - [], - ); + const checkSubmitValidation = + !checkList.includes(false) && // 체크박스가 모두 체크되어 있을 경우 + category !== SELECT_DEFAULT_OPTION && // 분류가 기본값이 아닐 경우 + amount > 0 && // 금액이 0보다 클 경우 + content.length !== 0 && // 사유가 작성되어 있을 경우 + file; // 파일이 첨부되어 있을 경우 + /** + * 입력값이 변경될 때마다 상태를 업데이트합니다. + */ + const handleInputChange = useCallback((e: ChangeEvent) => { + const { name, value } = e.target; + setFormData((prev) => ({ + ...prev, + [name]: name === 'amount' ? parseFloat(value.replace(/,/g, '')) : value, + })); + }, []); + /** + * 분류 선택값이 변경될 때마다 상태를 업데이트합니다. + */ + const handleSelectChange = useCallback((value: string) => { + setFormData((prev) => ({ + ...prev, + category: value, + })); + }, []); + /** + * 파일이 첨부될 때마다 상태를 업데이트합니다. + */ + const handleFileAccepted = useCallback((file: File | null) => { + setFormData((prev) => ({ + ...prev, + file, + })); + }, []); + /** + * 체크박스가 변경될 때마다 상태를 업데이트합니다. + */ + const handleCheckboxChange = (index: number) => { + setCheckList((prev) => { + const next = [...prev]; + next[index] = !prev[index]; + return next; + }); + }; + /** + * 폼을 제출할 때 실행되는 이벤트입니다. + */ + const handleOnSubmit = (e: FormEvent) => { + e.preventDefault(); + if (isPending) { + return; + } - const onClickRequest = async () => { - if ( - category === 'none' || - !amount || - !content || - !imageUploader.current?.files?.length - ) { + if (!checkSubmitValidation) { return toast({ state: 'error', - message: '신청서 항목을 모두 작성해주세요.', + message: '모든 항목을 입력해주세요', }); } - - const formData = new FormData(); - const files = imageUploader.current?.files[0]; - formData.append(FORM_DATA_KEY, files, encodeURIComponent(files.name)); - - membershipFeeMutate({ - body: input, - multipartFile: imageUploader.current?.files?.length ? formData : null, - }); + onSubmit(formData); }; return ( -
- - -
- - -
- -
+ - - -
+
    +
  • + handleCheckboxChange(0)} + label="해당 요청은 동아리 활동에 필요한 것이며, 동아리 활동과 관련이 없는 + 요청은 거절될 수 있어요." + /> +
  • +
  • + handleCheckboxChange(1)} + label="회비 요청은 동아리 활동에 필요한지 여부에 대하여 운영진 회의후에 + 승인돼요." + /> +
  • +
  • + handleCheckboxChange(2)} + label="회비를 통해 구매한 물품은 동아리의 소유가 되며, 모든 부원에게 공유 + 될 수 있어요." + /> +
  • +
  • + 동아리규칙 알아보기 +
  • +
+ + + ); }; diff --git a/apps/member/src/components/support/SupportRequestSection/SupportRequestSection.tsx b/apps/member/src/components/support/SupportRequestSection/SupportRequestSection.tsx new file mode 100644 index 00000000..44868da1 --- /dev/null +++ b/apps/member/src/components/support/SupportRequestSection/SupportRequestSection.tsx @@ -0,0 +1,46 @@ +import Section from '@components/common/Section/Section'; +import { useMembershipFeeMutation } from '@hooks/queries/useMembershipFeeMutation'; +import { FORM_DATA_KEY } from '@constants/api'; +import SupportRequestForm from '../SupportRequestForm/SupportRequestForm'; +import Linker from '@components/common/Linker/Linker'; +import { PATH } from '@constants/path'; +import type { SupportRequestDataType } from '@type/support'; + +const SupportRequestSection = () => { + const { membershipFeeMutate, isPending } = useMembershipFeeMutation(); + /** + * 사용 신청서를 제출합니다. + */ + const handleRequestSubmit = async (data: SupportRequestDataType) => { + const formData = new FormData(); + if (data.file) { + formData.append( + FORM_DATA_KEY, + data.file, + encodeURIComponent(data.file.name), + ); + } + membershipFeeMutate({ + body: data, + multipartFile: formData.get(FORM_DATA_KEY) ? formData : null, + }); + }; + + return ( +
+ + + 혹시 도서 신청을 하시나요? 이미 보유중인 책일 수도 있어요. + +
+ + + +
+ ); +}; + +export default SupportRequestSection; diff --git a/apps/member/src/hooks/queries/useMembershipFeeMutation.ts b/apps/member/src/hooks/queries/useMembershipFeeMutation.ts index 91d1c09e..87d067c5 100644 --- a/apps/member/src/hooks/queries/useMembershipFeeMutation.ts +++ b/apps/member/src/hooks/queries/useMembershipFeeMutation.ts @@ -24,5 +24,10 @@ export const useMembershipFeeMutation = () => { }, }); - return { membershipFeeMutate: membershipFeeMutation.mutate }; + membershipFeeMutation.isSuccess; + + return { + membershipFeeMutate: membershipFeeMutation.mutate, + isPending: membershipFeeMutation.isPending, + }; }; diff --git a/apps/member/src/types/support.ts b/apps/member/src/types/support.ts new file mode 100644 index 00000000..b9577903 --- /dev/null +++ b/apps/member/src/types/support.ts @@ -0,0 +1,9 @@ +/** + * SupportRequestForm에서 사용되는 데이터 타입 + */ +export interface SupportRequestDataType { + category: string; + amount: number; + content: string; + file: File | null; +}