Skip to content

Commit

Permalink
✨ feat: 시급 입력 시 최저시급 유효성 검사 기능 추가 #127
Browse files Browse the repository at this point in the history
  • Loading branch information
MrMirror21 committed Jan 14, 2025
1 parent ff5e587 commit 2aa7ace
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 9 deletions.
15 changes: 13 additions & 2 deletions src/components/Common/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type InputProps = {
placeholder: string; // 플레이스홀더 텍스트
value: string | null; // 입력 필드의 현재 값
onChange: (value: string) => void; // 입력값 변경 시 호출될 함수
onBlur?: (value: string) => void; // 입력 필드에서 포커스가 빠져나갈 때 호출될 함수 (선택적)
canDelete: boolean; // 삭제 버튼 표시 여부
clearInvalid?: () => void; // 토글 시 invalid 상태를 해제할 함수 (선택적)
isInvalid?: boolean; // 유효하지 않은 입력 상태 여부 (선택적)
Expand Down Expand Up @@ -45,6 +46,7 @@ const Input = ({
inputType,
placeholder,
onChange,
onBlur,
canDelete,
onDelete,
clearInvalid,
Expand Down Expand Up @@ -83,6 +85,11 @@ const Input = ({
setCurrentStatus(INPUT_STATUS.DISABLED);
};

const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
if (onBlur) onBlur(e.target.value);
handleFocus('blur');
};

return (
<div
className={`w-full flex gap-2 whitespace-nowrap items-center justify-between text-left body-2 border rounded-xl ${inputStyler(currentStatus)} bg-white py-[10px] pl-4 pr-[14px]`}
Expand All @@ -96,7 +103,7 @@ const Input = ({
value={value ?? ''}
className={'w-full outline-none placeholder:text-[var(--input-color)]'}
onClick={() => handleFocus('click')}
onBlur={() => handleFocus('blur')}
onBlur={handleBlur}
onChange={handleInputChange}
type={
inputType === 'PASSWORD'
Expand All @@ -114,7 +121,11 @@ const Input = ({
{/* 입력값 삭제 가능한 경우 삭제 아이콘을 표시합니다. */}
{canDelete && <CloseIcon onClick={onDelete} />}
{/* 단위가 존재할 경우 표시합니다. */}
{isUnit && <div className="text-right w-full body-2 text-[#464646]"><div className='w-full'>{unit}</div></div>}
{isUnit && (
<div className="text-right w-full body-2 text-[#464646]">
<div className="w-full">{unit}</div>
</div>
)}
</div>
);
};
Expand Down
20 changes: 16 additions & 4 deletions src/components/Employer/PostCreate/Step1.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ import {
} from '@/utils/post';
import WorkDayTimeBottomSheet from '@/components/Common/WorkDayTimeBottomSheet';
import { WorkDayTime, WorkPeriod } from '@/types/api/document';
import { parseStringToSafeNumber } from '@/utils/document';
import {
handleHourlyRateBlur,
MINIMUM_HOURLY_RATE,
parseStringToSafeNumber,
} from '@/utils/document';
import { WorkPeriodInfo, WorkPeriodNames } from '@/constants/documents';

const Step1 = ({
Expand All @@ -33,7 +37,8 @@ const Step1 = ({
}) => {
// 현재 step내에서 입력받는 정보를 따로 관리할 state, 추후 다음 step으로 넘어갈 때 funnel 관리 페이지의 state로 통합된다.
const [newPostInfo, setNewPostInfo] = useState<JobPostingForm>(postInfo);

// 시급이 10030원 미만일 경우 경고 메시지를 표시
const [warning, setWarning] = useState(false);
// 버튼 활성화 여부를 위한 플래그
const [isInvalid, setIsInvalid] = useState(true);
// 근무 시간 모달 활성화 여부 위한 플래그
Expand All @@ -49,7 +54,7 @@ const Step1 = ({
job_category !== '' &&
work_day_times.length &&
work_period !== '' &&
hourly_rate !== 0;
hourly_rate >= MINIMUM_HOURLY_RATE;
setIsInvalid(!isFormValid);
}, [newPostInfo]);

Expand Down Expand Up @@ -134,12 +139,19 @@ const Step1 = ({
},
})
}
onBlur={() =>
handleHourlyRateBlur({
value: String(newPostInfo.body.hourly_rate),
setWarning,
})
}
isInvalid={warning}
canDelete={false}
isUnit
unit="원"
/>
<div className="w-full relative body-3 px-1 py-1.5 text-[#222] text-left">
2024년 기준 최저시급은 9,860원입니다.
2025년 기준 최저시급은 10,030원입니다.
</div>
</InputLayout>
{/* 타입 선택 */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import BottomButtonPanel from '@/components/Common/BottomButtonPanel';
import Button from '@/components/Common/Button';
import { usePutLaborContractEmployer } from '@/hooks/api/useDocument';
import {
handleHourlyRateBlur,
parseStringToSafeNumber,
validateLaborContractEmployerInformation,
} from '@/utils/document';
Expand Down Expand Up @@ -77,6 +78,8 @@ const EmployerLaborContractForm = ({

const [isLoading, setIsLoading] = useState(false);
const [isInvalid, setIsInvalid] = useState(false);
// 시급 10030원 미만일 경우 경고 표시
const [warning, setWarning] = useState(false);
// 근무시간, 요일 선택 모달 활성화 플래그
const [isModal, setIsModal] = useState(false);
// 입력 완료 시 제출
Expand Down Expand Up @@ -432,6 +435,13 @@ const EmployerLaborContractForm = ({
hourly_rate: parseStringToSafeNumber(value),
})
}
onBlur={() =>
handleHourlyRateBlur({
value: String(newDocumentData.hourly_rate),
setWarning,
})
}
isInvalid={warning}
canDelete={false}
isUnit
unit="원"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import Button from '@/components/Common/Button';
import { usePutPartTimeEmployPermitEmployer } from '@/hooks/api/useDocument';
import { useParams } from 'react-router-dom';
import {
handleHourlyRateBlur,
parseStringToSafeNumber,
validateEmployerInformation,
} from '@/utils/document';
Expand Down Expand Up @@ -69,6 +70,8 @@ const EmployerPartTimePermitForm = ({
} = useAddressSearch();
const [isLoading, setIsLoading] = useState(false);
const [isInvalid, setIsInvalid] = useState(false);
// 시급 10030원 미만일 경우 경고 표시
const [warning, setWarning] = useState(false);
// 입력 완료 시 제출
const { mutate: putDocument } = usePutPartTimeEmployPermitEmployer(
Number(id),
Expand Down Expand Up @@ -352,6 +355,13 @@ const EmployerPartTimePermitForm = ({
hourly_rate: parseStringToSafeNumber(value),
})
}
onBlur={() =>
handleHourlyRateBlur({
value: String(newDocumentData.hourly_rate),
setWarning,
})
}
isInvalid={warning}
canDelete={false}
isUnit
unit="원"
Expand Down
23 changes: 20 additions & 3 deletions src/utils/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { extractNumbersAsNumber } from './post';
import { InsuranceInfo } from '@/constants/documents';
import { isValidPhoneNumber, parsePhoneNumber } from './information';

export const MINIMUM_HOURLY_RATE = 10030;

// 객체의 모든 프로퍼티가 공백이 아닌지 확인하는 함수
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const isNotEmpty = (obj: Record<string, any>): boolean => {
Expand Down Expand Up @@ -86,7 +88,7 @@ export const validateEmployerInformation = (
// 시급 유효성 검사(0이 아닌지)
if (
!info.hourly_rate ||
extractNumbersAsNumber(String(info.hourly_rate)) === 0
extractNumbersAsNumber(String(info.hourly_rate)) >= MINIMUM_HOURLY_RATE
) {
return false;
}
Expand Down Expand Up @@ -131,7 +133,7 @@ export const validateLaborContractEmployerInformation = (
// 시급 유효성 검사(0이 아닌지)
if (
!info.hourly_rate ||
extractNumbersAsNumber(String(info.hourly_rate)) === 0
extractNumbersAsNumber(String(info.hourly_rate)) >= MINIMUM_HOURLY_RATE
) {
console.log('시급');
return false;
Expand Down Expand Up @@ -177,11 +179,26 @@ export const validateLaborContractEmployerInformation = (
// number만 가능한 필드에서 NaN 입력으로 input이 멈추지 않게 값 검증
export const parseStringToSafeNumber = (value: string): number => {
const numberValue = Number(value);

if (isNaN(numberValue)) return 0;
else return numberValue;
};

// 시급 필드 onBlur 이벤트 핸들러
export const handleHourlyRateBlur = ({
value,
setWarning,
}: {
value: string;
setWarning: React.Dispatch<React.SetStateAction<boolean>>;
}) => {
const parsedValue = parseStringToSafeNumber(value);
if (parsedValue < MINIMUM_HOURLY_RATE) {
setWarning(true);
} else {
setWarning(false);
}
};

// base64 데이터를 디코딩해 이미지 타입을 추론하는 함수
export const getImageType = (base64String: string) => {
// base64 디코딩
Expand Down

0 comments on commit 2aa7ace

Please sign in to comment.