From 4fe5cbc948041a9241abe6d36bcb697f398a6c55 Mon Sep 17 00:00:00 2001 From: Minsu Kim Date: Mon, 7 Oct 2024 22:19:41 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=EC=9D=B8=ED=92=8B=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EB=B3=BC=20=EC=88=98?= =?UTF-8?q?=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ShowCastInfoFormDialogContent.styles.ts | 29 ++- .../ShowCastInfoFormDialogContent/index.tsx | 199 +++++++++++------- 2 files changed, 144 insertions(+), 84 deletions(-) diff --git a/apps/admin/src/components/ShowCastInfoFormDialogContent/ShowCastInfoFormDialogContent.styles.ts b/apps/admin/src/components/ShowCastInfoFormDialogContent/ShowCastInfoFormDialogContent.styles.ts index 58ef49f8..a27ccd98 100644 --- a/apps/admin/src/components/ShowCastInfoFormDialogContent/ShowCastInfoFormDialogContent.styles.ts +++ b/apps/admin/src/components/ShowCastInfoFormDialogContent/ShowCastInfoFormDialogContent.styles.ts @@ -7,6 +7,7 @@ interface ShowInfoFormLabelProps { interface InputWrapperProps { text: string; + isError?: boolean; } const ShowInfoFormLabel = styled.span` @@ -32,19 +33,30 @@ const MemberList = styled.div` } `; +const FieldWrap = styled.div` + flex: 1; + margin-right: 8px; + width: calc(50% - 32px); +`; + const InputWrapper = styled.div` ${({ theme }) => theme.typo.b3}; - border: 1px solid ${({ text, theme }) => (text ? theme.palette.grey.g90 : theme.palette.grey.g20)}; + border: 1px solid + ${({ text, theme, isError }) => + isError + ? theme.palette.status.error + : text + ? theme.palette.grey.g90 + : theme.palette.grey.g20}; border-radius: 4px; background-color: ${({ theme }) => theme.palette.grey.w}; padding: 8px 12px; height: 48px; - margin-right: 8px; flex: auto; position: relative; display: flex; align-items: center; - width: calc(50% - 32px); + width: 100%; `; const TextFieldWrap = styled.div` @@ -73,16 +85,18 @@ const Input = styled.input` const Row = styled.div` display: flex; justify-content: center; - align-items: center; + align-items: flex-start; margin-bottom: 20px; `; const TrashCanButton = styled.button` + margin-top: 10px; cursor: pointer; height: 100%; `; const MemberAddButton = styled.button` + cursor: pointer; display: flex; justify-content: center; align-items: center; @@ -141,6 +155,11 @@ const ButtonWrap = styled.div` margin-top: 32px; `; +const ErrorMessage = styled.span` + ${({ theme }) => theme.typo.b1}; + color: ${({ theme }) => theme.palette.status.error}; +`; + export default { ShowInfoFormLabel, InputWrapper, @@ -157,4 +176,6 @@ export default { TextFieldWrap, ButtonWrap, DeleteButton, + ErrorMessage, + FieldWrap, }; diff --git a/apps/admin/src/components/ShowCastInfoFormDialogContent/index.tsx b/apps/admin/src/components/ShowCastInfoFormDialogContent/index.tsx index 4b49adc0..0f8aa3f3 100644 --- a/apps/admin/src/components/ShowCastInfoFormDialogContent/index.tsx +++ b/apps/admin/src/components/ShowCastInfoFormDialogContent/index.tsx @@ -4,7 +4,7 @@ import Styled from './ShowCastInfoFormDialogContent.styles'; import { useState } from 'react'; import { useBodyScrollLock } from '~/hooks/useBodyScrollLock'; import { ClearIcon, PlusIcon, TrashIcon } from '@boolti/icon'; -import { Member, ShowCastTeamCreateOrUpdateRequest, queryKeys, useQueryClient } from '@boolti/api'; +import { Member, queryKeys, useQueryClient } from '@boolti/api'; import { replaceUserCode } from '~/utils/replace'; export interface TempShowCastInfoFormInput { @@ -48,12 +48,14 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P useBodyScrollLock(true); - const [hasBlurred, setHasBlurred] = useState< - Record - >({ - name: false, - members: [], - }); + const [isNameFieldBlurred, setIsNameFieldBlurred] = useState(false); + const [isMemberFieldBlurred, setIsMemberFieldBlurred] = useState< + Array<{ userCode: boolean; roleName: boolean }> + >( + prevShowCastInfo?.members + ? prevShowCastInfo.members.map(() => ({ userCode: false, roleName: false })) + : [{ userCode: false, roleName: false }], + ); return ( <> @@ -73,10 +75,10 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P onChange={onChange} onBlur={() => { onBlur(); - setHasBlurred((prev) => ({ ...prev, name: true })); + setIsNameFieldBlurred(true); }} value={value ?? ''} - errorMessage={hasBlurred.name && !value ? '필수 입력사항입니다.' : undefined} + errorMessage={isNameFieldBlurred && !value ? '필수 입력사항입니다.' : undefined} /> )} @@ -89,64 +91,78 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P ( - - {controlledField.userImgPath && controlledField.userNickname ? ( - <> - - {controlledField.userNickname} - { - update(index, { roleName: controlledField.roleName }); - }} - > - - - - ) : ( - <> - # - { - const nextValue = replaceUserCode(e.target.value); - onChange(nextValue); - }} - onBlur={async (event) => { - onBlur(); - const userCode = event.target.value; - if (userCode !== '') { - try { - const { imgPath, nickname } = await queryClient.fetchQuery( - queryKeys.user.userCode(event.target.value), - ); - update(index, { - ...controlledField, - userImgPath: imgPath, - userNickname: nickname, - }); - } catch { - toast.error( - '불티에 회원으로 등록된 식별 코드로만 등록이 가능합니다.' + - '\n' + - '식별 코드를 확인 후 다시 시도해 주세요.', - ); + render={({ field: { onChange, onBlur } }) => { + const isError = Boolean( + (isMemberFieldBlurred[index].userCode && !controlledField.userImgPath) || + !controlledField.userNickname, + ); + return ( + + + {controlledField.userImgPath && controlledField.userNickname ? ( + <> + - - )} - - )} + /> + {controlledField.userNickname} + { + update(index, { roleName: controlledField.roleName }); + }} + > + + + + ) : ( + <> + # + { + const nextValue = replaceUserCode(e.target.value); + onChange(nextValue); + }} + onBlur={async (event) => { + onBlur(); + setIsMemberFieldBlurred((prev) => { + const nextMemberFieldBlurred = [...prev]; + nextMemberFieldBlurred[index].userCode = true; + return nextMemberFieldBlurred; + }); + const userCode = event.target.value; + if (userCode !== '') { + try { + const { imgPath, nickname } = await queryClient.fetchQuery( + queryKeys.user.userCode(event.target.value), + ); + update(index, { + ...controlledField, + userImgPath: imgPath, + userNickname: nickname, + }); + } catch { + toast.error( + '불티에 회원으로 등록된 식별 코드로만 등록이 가능합니다.' + + '\n' + + '식별 코드를 확인 후 다시 시도해 주세요.', + ); + } + } + }} + value={controlledField.userCode ?? ''} + /> + + )} + + {isError && 필수 입력사항입니다.} + + ); + }} name={`members.${index}.userCode`} /> ( - - { - onBlur(); - }} - value={controlledField.roleName ?? ''} - /> - - )} + render={({ field: { onChange, onBlur } }) => { + const isError = isMemberFieldBlurred[index].roleName && !controlledField.roleName; + return ( + + + { + onBlur(); + setIsMemberFieldBlurred((prev) => { + const nextMemberFieldBlurred = [...prev]; + nextMemberFieldBlurred[index].roleName = true; + return nextMemberFieldBlurred; + }); + }} + value={controlledField.roleName ?? ''} + /> + + {isError && 필수 입력사항입니다.} + + ); + }} name={`members.${index}.roleName`} /> + prev.filter((_, blurredIndex) => blurredIndex !== index), + ); remove(index); } }} @@ -221,7 +254,13 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P type="button" colorTheme="primary" size="bold" - disabled={disabled} + disabled={ + disabled || + controlledFields.some( + ({ userImgPath, userNickname, roleName }) => + !userImgPath || !userNickname || !roleName, + ) + } onClick={async (e) => { e.preventDefault(); From fcf460c514c7d06be4377cbb9af77ba541b0ce93 Mon Sep 17 00:00:00 2001 From: Minsu Kim Date: Mon, 7 Oct 2024 22:24:31 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=ED=8C=80=EC=9B=90=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=ED=96=88=EC=9D=84=20=EB=95=8C=20=EB=8C=80=EC=9D=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/ShowCastInfoFormDialogContent/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/admin/src/components/ShowCastInfoFormDialogContent/index.tsx b/apps/admin/src/components/ShowCastInfoFormDialogContent/index.tsx index 0f8aa3f3..ca4d0a77 100644 --- a/apps/admin/src/components/ShowCastInfoFormDialogContent/index.tsx +++ b/apps/admin/src/components/ShowCastInfoFormDialogContent/index.tsx @@ -93,8 +93,9 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P defaultValue={controlledField.userCode} render={({ field: { onChange, onBlur } }) => { const isError = Boolean( - (isMemberFieldBlurred[index].userCode && !controlledField.userImgPath) || - !controlledField.userNickname, + isMemberFieldBlurred[index].userCode && + controlledField.userCode && + (!controlledField.userImgPath || !controlledField.userNickname), ); return ( @@ -222,6 +223,7 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P { append({}); + setIsMemberFieldBlurred((prev) => [...prev, { userCode: false, roleName: false }]); }} > From 52f46d5dac305ca3d18ecbc430e1b6c5b2598ea1 Mon Sep 17 00:00:00 2001 From: Minsu Kim Date: Mon, 7 Oct 2024 22:55:41 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=EC=9D=B8=ED=92=8B=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EC=9D=B4=EC=8A=88=20=EB=B0=8F=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ShowCastInfo/ShowCastInfo.styles.ts | 1 + .../ShowCastInfoFormDialogContent.styles.ts | 2 +- .../ShowCastInfoFormDialogContent/index.tsx | 48 ++++++++++--------- apps/admin/src/pages/ShowInfoPage/index.tsx | 4 +- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/apps/admin/src/components/ShowCastInfo/ShowCastInfo.styles.ts b/apps/admin/src/components/ShowCastInfo/ShowCastInfo.styles.ts index 323d4b52..225c95fc 100644 --- a/apps/admin/src/components/ShowCastInfo/ShowCastInfo.styles.ts +++ b/apps/admin/src/components/ShowCastInfo/ShowCastInfo.styles.ts @@ -91,6 +91,7 @@ const Rolename = styled.span` text-overflow: ellipsis; overflow: hidden; flex: 0 1 auto; + max-width: 100px; `; const CollapseButton = styled.button` diff --git a/apps/admin/src/components/ShowCastInfoFormDialogContent/ShowCastInfoFormDialogContent.styles.ts b/apps/admin/src/components/ShowCastInfoFormDialogContent/ShowCastInfoFormDialogContent.styles.ts index a27ccd98..21b50659 100644 --- a/apps/admin/src/components/ShowCastInfoFormDialogContent/ShowCastInfoFormDialogContent.styles.ts +++ b/apps/admin/src/components/ShowCastInfoFormDialogContent/ShowCastInfoFormDialogContent.styles.ts @@ -74,7 +74,7 @@ const HashTag = styled.span` `; const Input = styled.input` - width: ${({ value }) => (value ? 'calc(100% - 80px)' : '100%')}; + width: 100%; line-height: 24px; &::placeholder { diff --git a/apps/admin/src/components/ShowCastInfoFormDialogContent/index.tsx b/apps/admin/src/components/ShowCastInfoFormDialogContent/index.tsx index ca4d0a77..1b3e0071 100644 --- a/apps/admin/src/components/ShowCastInfoFormDialogContent/index.tsx +++ b/apps/admin/src/components/ShowCastInfoFormDialogContent/index.tsx @@ -32,6 +32,7 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P const { fields, append, remove, update } = useFieldArray({ control, name: 'members', + keyName: '_id', }); const watchMemberFields = watch('members') ?? []; const controlledFields = fields.map((field, index) => { @@ -86,33 +87,33 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P /> 팀원 - {controlledFields.map((controlledField, index) => ( - + {fields.map((field, index) => ( + { + defaultValue={field.userCode} + render={({ field: { onChange, onBlur, value } }) => { const isError = Boolean( isMemberFieldBlurred[index].userCode && - controlledField.userCode && - (!controlledField.userImgPath || !controlledField.userNickname), + value && + (!field.userImgPath || !field.userNickname), ); return ( - - {controlledField.userImgPath && controlledField.userNickname ? ( + + {field.userImgPath && field.userNickname ? ( <> - {controlledField.userNickname} + {field.userNickname} { - update(index, { roleName: controlledField.roleName }); + update(index, { roleName: field.roleName }); }} > @@ -130,11 +131,6 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P }} onBlur={async (event) => { onBlur(); - setIsMemberFieldBlurred((prev) => { - const nextMemberFieldBlurred = [...prev]; - nextMemberFieldBlurred[index].userCode = true; - return nextMemberFieldBlurred; - }); const userCode = event.target.value; if (userCode !== '') { try { @@ -142,7 +138,7 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P queryKeys.user.userCode(event.target.value), ); update(index, { - ...controlledField, + ...controlledFields[index], userImgPath: imgPath, userNickname: nickname, }); @@ -152,10 +148,16 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P '\n' + '식별 코드를 확인 후 다시 시도해 주세요.', ); + } finally { + setIsMemberFieldBlurred((prev) => { + const nextMemberFieldBlurred = [...prev]; + nextMemberFieldBlurred[index].userCode = true; + return nextMemberFieldBlurred; + }); } } }} - value={controlledField.userCode ?? ''} + value={value ?? ''} /> )} @@ -171,13 +173,13 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P rules={{ required: true, }} - render={({ field: { onChange, onBlur } }) => { - const isError = isMemberFieldBlurred[index].roleName && !controlledField.roleName; + render={({ field: { onChange, onBlur, value } }) => { + const isError = isMemberFieldBlurred[index].roleName && !value; return ( {isError && 필수 입력사항입니다.} diff --git a/apps/admin/src/pages/ShowInfoPage/index.tsx b/apps/admin/src/pages/ShowInfoPage/index.tsx index fbc312e5..a8e83da9 100644 --- a/apps/admin/src/pages/ShowInfoPage/index.tsx +++ b/apps/admin/src/pages/ShowInfoPage/index.tsx @@ -215,7 +215,7 @@ const ShowInfoPage = () => { showId, name, members: members - ?.filter(({ id, userCode, roleName }) => id && userCode && roleName) + ?.filter(({ userCode, roleName }) => userCode && roleName) .map(({ id, userCode, roleName }) => ({ id, userCode, @@ -239,7 +239,7 @@ const ShowInfoPage = () => { { name, members: members - ?.filter(({ id, userCode, roleName }) => id && userCode && roleName) + ?.filter(({ userCode, roleName }) => userCode && roleName) .map(({ id, userCode, roleName }) => ({ id, userCode,