From 72132d26af35e9ba1a823e49edea1c8b9bfe6d32 Mon Sep 17 00:00:00 2001 From: kwang Date: Tue, 9 Jul 2024 01:37:43 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=EB=82=98=EC=9D=98=20=EC=82=AC?= =?UTF-8?q?=EC=97=85=EC=9E=A5=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EB=B0=B1?= =?UTF-8?q?=EC=97=94=EB=93=9C=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 +- src/api/apiClient.ts | 29 +++ src/api/interfaces/ownerApi.ts | 12 ++ src/components/employee/CalendarEmployee.tsx | 130 ++++++++++++++ .../owner-calendar/AttendanceCreate.tsx | 168 +++++++++--------- .../owner-calendar/AttendanceEdit.tsx | 8 +- src/components/owner-calendar/DateDetail.tsx | 8 +- src/components/owner-calendar/TimeBox.tsx | 4 +- .../owner-calendar/TimeBoxForContract.tsx | 4 +- .../owner-calendar/TimePickerCustom.css | 6 +- .../owner-calendar/TimePickerCustom.tsx | 5 +- .../owner-workplace/MyWorkPlaceDetail.tsx | 46 ----- .../owner-workplace/MyWorkPlaces.tsx | 43 ----- .../owner-workplace/Notification.tsx | 4 +- .../WorkEmployeeAdd-Complete.tsx | 25 +-- .../owner-workplace/WorkEmployeeAdd-First.tsx | 16 +- .../WorkEmployeeAdd-Second.tsx | 122 ++++++++----- .../owner-workplace/WorkEmployeeAdd-Third.tsx | 135 +++++++++++--- src/components/ui/CalendarCustom.tsx | 2 +- src/contexts/EmployeeContract-Context.tsx | 37 ++++ src/types/api/owner-api.d.ts | 18 ++ src/types/contract.d.ts | 1 + src/utils/date-util.ts | 9 +- src/utils/get-TimeString.ts | 7 +- yarn.lock | 29 ++- 25 files changed, 577 insertions(+), 294 deletions(-) create mode 100644 src/components/employee/CalendarEmployee.tsx diff --git a/package.json b/package.json index 89f9891..20add67 100644 --- a/package.json +++ b/package.json @@ -14,10 +14,11 @@ "axios": "^1.7.2", "clsx": "^2.1.1", "date-fns": "^3.6.0", + "moment-timezone": "^0.5.45", "react": "^18.3.1", "react-calendar": "^5.0.0", - "react-datepicker": "^7.2.0", "react-cookie": "^7.1.4", + "react-datepicker": "^7.2.0", "react-dom": "^18.3.1", "react-icons": "^5.2.1", "react-kakao-maps-sdk": "^1.1.27", diff --git a/src/api/apiClient.ts b/src/api/apiClient.ts index 7e0ce1c..dfd68a8 100644 --- a/src/api/apiClient.ts +++ b/src/api/apiClient.ts @@ -3,6 +3,7 @@ import employeeApi from './interfaces/employeeApi'; import ownerApi from './interfaces/ownerApi'; import userApi from './interfaces/userApi'; import { getToken } from '../utils/token'; +import { EmployeeContract } from '../types/contract'; class ApiClient implements employeeApi, userApi, ownerApi { //singleton pattern @@ -336,6 +337,8 @@ class ApiClient implements employeeApi, userApi, ownerApi { return response.data; } + // ========================== + // 사장님 - 캘린더 데이터 public async getCalendarData( year: number, @@ -398,6 +401,32 @@ class ApiClient implements employeeApi, userApi, ownerApi { return response.data; } + // 사장님 - 근로자 추가 + public async registerEmployee( + id: number, + request: Partial + ): Promise { + const respnose: BaseResponse = + await this.axiosInstance.request({ + method: 'post', + url: `/papers/${id}/employment-contracts`, + data: request, + }); + return respnose.data; + } + // 사장님 - 근무 수동 추가 + public async registerAttendance( + request: RegisterAttendanceManualRequest + ): Promise { + const response: BaseResponse = + await this.axiosInstance.request({ + method: 'post', + url: `/owner/attendances/manual`, + data: request, + }); + return response.data; + } + //========================== // 생성 메소드 private static createAxiosInstance() { diff --git a/src/api/interfaces/ownerApi.ts b/src/api/interfaces/ownerApi.ts index 58384c0..d137499 100644 --- a/src/api/interfaces/ownerApi.ts +++ b/src/api/interfaces/ownerApi.ts @@ -1,3 +1,5 @@ +import { EmployeeContract } from '../../types/contract'; + interface ownerApi { getCalendarData(year: number, month: number): Promise; @@ -12,5 +14,15 @@ interface ownerApi { getMyEmployees(employeeStatus: string): Promise; getNotifications(id: number): Promise; + + // 사장님 - 근로자 추가 + registerEmployee( + id: number, + request: Partial + ): Promise; + + registerAttendance( + request: RegisterAttendanceManualRequest + ): Promise; } export default ownerApi; diff --git a/src/components/employee/CalendarEmployee.tsx b/src/components/employee/CalendarEmployee.tsx new file mode 100644 index 0000000..b575599 --- /dev/null +++ b/src/components/employee/CalendarEmployee.tsx @@ -0,0 +1,130 @@ +import { useEffect, useState } from 'react'; +import './CalendarCustom.css'; +import Calendar, { OnArgs } from 'react-calendar'; +import 'react-calendar/dist/Calendar.css'; +import CalendarMark from './CalendarMark'; +import { useNavigate } from 'react-router-dom'; +import { useCalendarData } from '../../contexts/Calender-Data-Context'; +import ApiClient from '../../api/apiClient'; +import { parseYYYMMDD } from '../../utils/date-util'; + +type ValuePiece = Date | null; +type Value = ValuePiece | [ValuePiece, ValuePiece]; + +const currentDate = new Date(); + +const CalendarEmployee = () => { + const [value, setValue] = useState(currentDate); + const { calendarData, setCalendarData } = useCalendarData(); + const navigate = useNavigate(); + + console.log('🚀 CalendarCustom value:', value); + + useEffect(() => { + if (value instanceof Date) { + const year = value.getFullYear(); + const month = value.getMonth() + 1; + fetchData(year, month); + } + }, [value]); + + const fetchData = async (year: number, month: number) => { + try { + const response = await ApiClient.getInstance().getCalendarData( + year, + month + ); + console.log('API 호출 결과:', response); + setCalendarData(response); + } catch (error) { + console.error('API 호출 실패:', error); + } + }; + + const onChangeCurrentDate = (args: OnArgs) => { + const { action, activeStartDate, value, view } = args; + // console.log('🚀 activeStartDate:', activeStartDate); + // console.log('🚀 view:', view); + // console.log('🚀 value:', value); + // console.log('🚀 action:', action); + setValue(activeStartDate!); + }; + + const onClickDate = (date: Date) => { + const localDateString = date.toLocaleDateString('en-CA'); // 'YYYY-MM-DD' 형식 + navigate(`/owner/calendar/${localDateString}`); + }; + + const getListForDate = (date: Date) => { + const list = []; + calendarData && + calendarData.workPlaceList.map((workPlace) => { + if ( + new Date(parseYYYMMDD(workPlace.attendDate)).toDateString() === + date.toDateString() + ) { + list.push({ + ...workPlace, + }); + } + }); + return list; + }; + + // const getEventsForDate = (date: Date) => { + // const events = []; + // calendarData.forEach((workPlace) => { + // workPlace.days.forEach((day) => { + // if (new Date(day.startTime).toDateString() === date.toDateString()) { + // events.push({ + // ...day, + // workPlaceName: workPlace.workPlaceName, + // workPlaceColor: workPlace.workPlaceColor, + // }); + // } + // }); + // }); + // return events; + // }; + + // 날짜 타일에 맞춤 콘텐츠를 추가한다. + const tileContent = ({ date, view }: { date: Date; view: string }) => { + if (view === 'month') { + // const events = getEventsForDate(date); + const list = getListForDate(date); + return ( +
+ {list.map((data) => ( + + ))} + {/* {events.map((event) => ( + + //
+ // {event.employeeName} - {event.workPlaceName} + //
+ ))} */} +
+ ); + } + }; + + return ( +
+ +
+ ); +}; +export default CalendarEmployee; diff --git a/src/components/owner-calendar/AttendanceCreate.tsx b/src/components/owner-calendar/AttendanceCreate.tsx index b3a4c0d..33e87d8 100644 --- a/src/components/owner-calendar/AttendanceCreate.tsx +++ b/src/components/owner-calendar/AttendanceCreate.tsx @@ -1,4 +1,4 @@ -import { ChangeEvent, useState } from 'react'; +import { ChangeEvent, useEffect, useState } from 'react'; import InputBox from '../ui/InputBox'; import { Spacer, VStack } from '../ui/Stack'; import TimeBox from './TimeBox'; @@ -7,69 +7,50 @@ import { getTimeString } from '../../utils/get-TimeString'; import { MINIMUM_PAY_PER_HOUR } from '../../utils/const-value'; import ModalCenter from '../ModalCenter'; import { useNavigate, useParams } from 'react-router-dom'; +import ApiClient from '../../api/apiClient'; +import BtnBottom from '../BtnBottom'; -const mockData = [ - { - workPlaceId: 1, // Long - workPlaceNm: '롯데리아 자양점', - workPlaceColor: '01', // String - workPlaceEmployeeId: 10, // Long - employeeNm: '이신광', // String - }, - { - workPlaceId: 1, // Long - workPlaceNm: '롯데리아 자양점', - workPlaceColor: '01', // String - workPlaceEmployeeId: 12, // Long - employeeNm: '이서하', // String - }, - { - workPlaceId: 1, // Long - workPlaceNm: '롯데리아 자양점', - workPlaceColor: '01', - workPlaceEmployeeId: 13, // Long - employeeNm: '정연주', // String - }, - { - workPlaceId: 2, // Long - workPlaceNm: '버거킹', - workPlaceColor: '02', // String - workPlaceEmployeeId: 14, // Long - employeeNm: '고영우', // String - }, - { - workPlaceId: 2, // Long - workPlaceNm: '버거킹', - workPlaceColor: '02', // String - workPlaceEmployeeId: 18, // Long - employeeNm: '최은진', // String - }, -]; +const convertDate = ( + date: string | undefined, + time: string | undefined +): Date | undefined => { + if (!date || !time) return undefined; + const newDate = new Date(date); + const [hours, minutes] = time.split(':').map(Number); + newDate.setHours(hours, minutes); + return newDate; +}; const AttendanceCreate = () => { const navigation = useNavigate(); const { date } = useParams(); - console.log(date); - const [workEmployeeList] = useState(mockData); // 나의 전체 사업장의 전 직원들 + const [workEmployeeList, setworkEmployeeList] = useState([]); // 나의 전체 사업장의 전 직원들 + const fetchData = async (workingStatus: string) => { + try { + const response = + await ApiClient.getInstance().getMyEmployees(workingStatus); + console.log('API 호출 결과:', response); + setworkEmployeeList(response.employeeList); + } catch (error) { + console.error('API 호출 실패:', error); + } + }; + useEffect(() => { + fetchData('WORKING'); + }, []); - // const { attendance, changeAttendance } = useAttendance(); - const [workPlaceEmployeeId, setWorkPlaceEmployeeId] = useState(1); - // const [workPlaceEmployeeId, setWorkPlaceEmployeeId] = useState( - // attendance?.workPlaceEmployeeId - // ); - const [startTime, setStartTime] = useState(new Date(date!)); - // const [startTime, setStartTime] = useState( - // attendance?.startTime - // ); - const [endTime, setEndTime] = useState(new Date(date!)); - // const [endTime, setEndTime] = useState(attendance?.endTime); + const [workPlaceEmployeeId, setWorkPlaceEmployeeId] = useState< + number | undefined + >(undefined); + const [startDate, setStartDate] = useState(undefined); + console.log('🚀 AttendanceCreate startDate:', startDate); + const [endDate, setEndDate] = useState(undefined); + console.log('🚀 AttendanceCreate endDate:', endDate); const [restMinutes, setRestMinutes] = useState(0); - // const [restMinutes, setRestMinutes] = useState(attendance?.restMinutes); - const [payPerHour, setPayPerHour] = useState(0); - // const [payPerHour, setPayPerHour] = useState(attendance?.payPerHour); - - const dateUrl = startTime!.toLocaleDateString('en-CA'); + const [payPerHour, setPayPerHour] = useState(MINIMUM_PAY_PER_HOUR); + const [startTime, setStartTime] = useState(undefined); + const [endTime, setEndTime] = useState(undefined); // 모달 관련 const [isModalOpen, setModalOpen] = useState(false); @@ -85,17 +66,42 @@ const AttendanceCreate = () => { setPayPerHour(Number(e.target.value)); }; - const updateTime = ( - date: Date | undefined, - time: string - ): Date | undefined => { - if (!date) return undefined; - const [hours, minutes] = time.split(':').map(Number); - const newDate = new Date(date); - newDate.setHours(hours, minutes); - return newDate; + const fetchAttendance = async (request: RegisterAttendanceManualRequest) => { + try { + const response = + await ApiClient.getInstance().registerAttendance(request); + console.log('API 호출 결과:', response); + } catch (error) { + console.error('API 호출 실패:', error); + } }; + const onClickConfirm = () => { + if (workPlaceEmployeeId && startDate && endDate) { + const request = { + workPlaceEmployeeId, + payPerHour, + startTime: startDate, + endTime: endDate, + restMinute: restMinutes, + }; + console.log(request); + fetchAttendance(request); + navigation(`/owner/calendar/${date}`, { replace: true }); + } + }; + + useEffect(() => { + setStartDate(convertDate(date, startTime)); + setEndDate(convertDate(date, endTime)); + }, [date, startTime, endTime]); + + // // KST로 초기 시간을 설정 + // useEffect(() => { + // const nowKST = moment().tz('Asia/Seoul'); + // setTime(nowKST.format('HH:mm')); + // }, []); + return ( <> {isModalOpen && ( @@ -103,7 +109,7 @@ const AttendanceCreate = () => { title={' '} closeModal={() => setModalOpen(false)} hasDecline - confirmAction={() => navigation(`/owner/calendar/${dateUrl}`)} + confirmAction={onClickConfirm} >{`추가하시겠습니까?`} )} @@ -117,14 +123,10 @@ const AttendanceCreate = () => { - setStartTime(updateTime(startTime, time)) - } - endTime={getTimeString(end.endTime)} - changeEndTime={(time: string) => - setEndTime(updateTime(endTime, time)) - } + startTime={startTime} + changeStartTime={(time: string) => setStartTime(time)} + endTime={endTime} + changeEndTime={(time: string) => setEndTime(time)} restMinutes={restMinutes!} changeRestMinutes={(minutes: number) => setRestMinutes(minutes)} /> @@ -147,19 +149,19 @@ const AttendanceCreate = () => { - {/* { - changeAttendance({ - workPlaceEmployeeId: workPlaceEmployeeId!, - payPerHour: payPerHour!, - startTime: startTime!, - endTime: endTime!, - restMinutes: restMinutes!, - }); + // changeAttendance({ + // workPlaceEmployeeId: workPlaceEmployeeId!, + // payPerHour: payPerHour!, + // startTime: startTime!, + // endTime: endTime!, + // restMinutes: restMinutes!, + // }); setModalOpen(true); }} - /> */} + /> ); diff --git a/src/components/owner-calendar/AttendanceEdit.tsx b/src/components/owner-calendar/AttendanceEdit.tsx index 8448897..5225b48 100644 --- a/src/components/owner-calendar/AttendanceEdit.tsx +++ b/src/components/owner-calendar/AttendanceEdit.tsx @@ -122,14 +122,14 @@ const AttendanceEdit = () => { />
- 현재 최저시급은 + 현재 최저시급은 {`${MINIMUM_PAY_PER_HOUR}`}원 - 이에요.. + 이에요.
근무 시간이 4시간 이상이면 30분 이상,
- 8시간 이상이면 1시간 이상 휴게시간이 필요해요.
(근로기준법 - 54조) + 8시간 이상이면 1시간 이상 휴게시간이 필요해요.
+ (근로기준법 54조)
diff --git a/src/components/owner-calendar/DateDetail.tsx b/src/components/owner-calendar/DateDetail.tsx index fcb1818..225e7ff 100644 --- a/src/components/owner-calendar/DateDetail.tsx +++ b/src/components/owner-calendar/DateDetail.tsx @@ -53,6 +53,9 @@ const DateDetail = () => { const onClickEditAttendance = (attendanceId: number) => { navigate(`/owner/calendar/attendance/${attendanceId}/edit`); }; + const onClickAddAttendance = () => { + navigate(`/owner/calendar/${date}/add`); + }; const targetDate = new Date(date!); @@ -66,7 +69,10 @@ const DateDetail = () => {
총 근무 시간: {calculateWorkedHours(temp)}
- diff --git a/src/components/owner-calendar/TimeBox.tsx b/src/components/owner-calendar/TimeBox.tsx index aff121b..0abf063 100644 --- a/src/components/owner-calendar/TimeBox.tsx +++ b/src/components/owner-calendar/TimeBox.tsx @@ -5,9 +5,9 @@ import TimePickerCustom from './TimePickerCustom'; import { ChangeEvent } from 'react'; type TimeBoxProps = { - startTime: string; + startTime: string | undefined; changeStartTime: (time: string) => void; - endTime: string; + endTime: string | undefined; changeEndTime: (time: string) => void; restMinutes: number; changeRestMinutes: (minutes: number) => void; diff --git a/src/components/owner-calendar/TimeBoxForContract.tsx b/src/components/owner-calendar/TimeBoxForContract.tsx index 9b1b437..9db58ae 100644 --- a/src/components/owner-calendar/TimeBoxForContract.tsx +++ b/src/components/owner-calendar/TimeBoxForContract.tsx @@ -63,7 +63,7 @@ const TimeBoxForContract = (props: TimeBoxForContractProps) => { return ( -
근무 시간
+
근무 시간
{ />
-
휴게 시간
+
휴게 시간
{ - // const [value, setValue] = useState('10:00'); const [isFocused, setIsFocused] = useState(false); const handleTimeChange = (timeValue: string | null) => { @@ -41,7 +40,7 @@ const TimePickerCustom = ({ onCancel={() => setIsFocused(false)} />
); diff --git a/src/components/owner-workplace/MyWorkPlaceDetail.tsx b/src/components/owner-workplace/MyWorkPlaceDetail.tsx index bd52856..14bd35a 100644 --- a/src/components/owner-workplace/MyWorkPlaceDetail.tsx +++ b/src/components/owner-workplace/MyWorkPlaceDetail.tsx @@ -17,52 +17,6 @@ enum ToggleStatus { NOTIFICATIONS = 'notifications', } -const mockData = { - year: '2024', - month: '06', - workPlaceId: 1, - workPlaceName: '롯데리아 어디어디어디 점', - workPlaceColor: '1', - totalPayPerMonth: 18000000, - workEmployees: [ - { - workEmployeeId: 1, - employeeName: '이신광', - monthPay: 500000, - fromDay: '2021-04-05', - }, - { - workEmployeeId: 2, - employeeName: '이서하', - monthPay: 1951400, - fromDay: '2024-03-14', - }, - { - workEmployeeId: 3, - employeeName: '정연주', - monthPay: 102900, - fromDay: '2024-06-01', - }, - ], -}; - -const mockNotifiaction = { - list: [ - { - notificationId: 2, - title: '위치 정보 저장하세요', - content: '저장되는거 확인하세요', - createdAt: '2024-07-02T11:10:29.282145', - }, - { - notificationId: 3, - title: '제목입니다', - content: '내용입니다', - createdAt: '2024-07-02T17:03:26.696708', - }, - ], -}; - const MyWorkPlaceDetail = () => { const navigate = useNavigate(); const { id } = useParams(); diff --git a/src/components/owner-workplace/MyWorkPlaces.tsx b/src/components/owner-workplace/MyWorkPlaces.tsx index 7155c3b..2ab600e 100644 --- a/src/components/owner-workplace/MyWorkPlaces.tsx +++ b/src/components/owner-workplace/MyWorkPlaces.tsx @@ -6,49 +6,6 @@ import { AiOutlinePlusCircle } from 'react-icons/ai'; import { isCurrentDate } from '../../utils/is-current-date'; import ApiClient from '../../api/apiClient'; -const mockData = { - year: 2024, - month: 6, - totalPayment: 140333000, - workPlaceList: [ - { - workPlaceId: 1, - workPlaceName: '롯데리아 자양점', - workPlaceColor: '01', - payment: 140333000, - employeeList: [ - { - workPlaceEmployeeId: 1, - employeeName: '이서하', - workStartDate: '2024-07-01', - payment: 140333, - }, - ], - }, - { - id: 2, - workPlaceName: 'Example Work Place Name', - workPlaceColor: '02', - payment: 0, - ownerSalaryGetResponseList: [], - }, - { - id: 3, - workPlaceName: '롯데월드 어드벤쳐 부산', - workPlaceColor: '03', - payment: 0, - ownerSalaryGetResponseList: [ - { - id: 2, - employeeName: '최은진', - workStartDate: '2024-07-01', - payment: 0, - }, - ], - }, - ], -}; - const MyWorkPlaces = () => { const [data, setData] = useState(null); const currentDate = new Date(); diff --git a/src/components/owner-workplace/Notification.tsx b/src/components/owner-workplace/Notification.tsx index d4621c5..8e87c93 100644 --- a/src/components/owner-workplace/Notification.tsx +++ b/src/components/owner-workplace/Notification.tsx @@ -11,6 +11,7 @@ type NotificationProps = { const Notification = ({ title, content, createdAt }: NotificationProps) => { const [expanded, setExpanded] = useState(false); + const created = new Date(createdAt); const toggleExpand = () => { setExpanded(!expanded); @@ -27,8 +28,7 @@ const Notification = ({ title, content, createdAt }: NotificationProps) => {
- {/* {`${new Date(createdAt).toLocaleString()}`} */} - {`${createdAt.toLocaleString()}`} + {`${created.toLocaleDateString()} ${created.toLocaleTimeString()}`}
{ - const [selectDayOfWeek, setSelectDayOfWeek] = useState(Bonus.OFF); - - const [bonus, setBonus] = useState('bonusOff'); - const [allowance, setAllowance] = useState(Allowance.OFF); + const navigate = useNavigate(); + const { placeId } = useParams(); - // 요일 버튼을 클릭했을 때 호출되는 함수 - const handleDayClick = (day: string) => { - setSelectDayOfWeek(day); // 선택된 요일을 변경 + const onClickConfirm = () => { + navigate(`/owner`); }; return ( @@ -33,7 +20,7 @@ const WorkEmployeeAddComplete = () => {
직원을
-
등록했어요👍
+
등록했어요 👍
{ setWorkStartTime(time); @@ -207,6 +226,13 @@ const WorkEmployeeAddSecond = () => { {errorMessage && (
{errorMessage}
)} + + {workTimes.map((w) => ( +
{`${w.workDayOfWeek} - ${w.workStartTime} - ${w.workEndTime}`}
+ ))}
@@ -216,7 +242,7 @@ const WorkEmployeeAddSecond = () => { {}} + onAction={onClickAddSecond} onClose={() => history.back()} /> diff --git a/src/components/owner-workplace/WorkEmployeeAdd-Third.tsx b/src/components/owner-workplace/WorkEmployeeAdd-Third.tsx index 2ca79a5..343bf75 100644 --- a/src/components/owner-workplace/WorkEmployeeAdd-Third.tsx +++ b/src/components/owner-workplace/WorkEmployeeAdd-Third.tsx @@ -1,9 +1,14 @@ -import { useState } from 'react'; +import { ChangeEvent, useState } from 'react'; import { MINIMUM_PAY_PER_HOUR } from '../../utils/const-value'; import { HStack, Spacer, VStack } from '../ui/Stack'; import { DayOfWeekShort } from './WorkEmployeeAdd-Second'; import BtnChoiceBox from '../ui/BtnChoiceBox'; import ThreeLevelUi from '../ui/ThreeLevelUi'; +import { useEmployeeContract } from '../../contexts/EmployeeContract-Context'; +import { set } from 'date-fns'; +import { addSuffixDayOfWeek } from '../../utils/date-util'; +import { useNavigate, useParams } from 'react-router-dom'; +import ApiClient from '../../api/apiClient'; enum Bonus { ON = 'bonusOn', @@ -17,14 +22,94 @@ enum Allowance { const DayOfWeeks = ['없음', '월', '화', '수', '목', '금', '토', '일']; const WorkEmployeeAddThird = () => { - const [selectDayOfWeek, setSelectDayOfWeek] = useState(Bonus.OFF); + const { placeId } = useParams(); + const navigate = useNavigate(); + const { employeeContract, addThirdInfo, setThirdInfo } = + useEmployeeContract(); + console.log('🚀 WorkEmployeeAddThird id:', placeId); + console.log(employeeContract); - const [bonus, setBonus] = useState('bonusOff'); - const [allowance, setAllowance] = useState(Allowance.OFF); + const [payPerHour, setPayPerHour] = useState(MINIMUM_PAY_PER_HOUR); + const onChangePayperHour = (e: ChangeEvent) => { + if (Number(e.target.value) <= MINIMUM_PAY_PER_HOUR) { + setPayPerHour(MINIMUM_PAY_PER_HOUR); + } else { + setPayPerHour(Number(e.target.value)); + } + }; + + const [paymentDay, setPaymentDay] = useState(15); + const onChangePaymentDay = (e: ChangeEvent) => { + if (Number(e.target.value) <= 1) { + setPaymentDay(1); + } else if (Number(e.target.value) >= 28) { + setPaymentDay(28); + } else { + setPaymentDay(Number(e.target.value)); + } + }; - // 요일 버튼을 클릭했을 때 호출되는 함수 + const [restDayOfWeek, setRestDayofWeek] = useState('없음'); const handleDayClick = (day: string) => { - setSelectDayOfWeek(day); // 선택된 요일을 변경 + setRestDayofWeek(day); // 선택된 요일을 변경 + }; + + const [bonus, setBonus] = useState(Bonus.OFF); + const [allowance, setAllowance] = useState(Allowance.OFF); + const [overtimeRate, setOverTimeRate] = useState(0); + const onChangeOvertimeRate = (e: ChangeEvent) => { + if (Number(e.target.value) <= 0) { + setOverTimeRate(0); + } else { + setOverTimeRate(Number(e.target.value)); + } + }; + + const fetchContract = async () => { + try { + const response = await ApiClient.getInstance().registerEmployee( + Number(placeId), + employeeContract! + ); + console.log('API 호출 결과:', response); + } catch (error) { + console.error('API 호출 실패:', error); + } + }; + const [ready, setReady] = useState(false); + if (ready) { + console.log(employeeContract); + fetchContract(); + setReady(false); + } + + const onClickAddThird = () => { + addThirdInfo({ + payPerHour, + paymentDay, + restDayOfWeek: + restDayOfWeek === '없음' + ? undefined + : addSuffixDayOfWeek(restDayOfWeek as DayOfWeekShort), + bonusAmount: bonus === Bonus.OFF ? 0 : 10000, + otherAllowancesAmount: allowance === Allowance.OFF ? 0 : 10000, + otherAllowancesName: allowance === Allowance.OFF ? undefined : '제수당', + overtimeRate, + }); + setThirdInfo({ + payPerHour, + paymentDay, + restDayOfWeek: + restDayOfWeek === '없음' + ? undefined + : addSuffixDayOfWeek(restDayOfWeek as DayOfWeekShort), + bonusAmount: bonus === Bonus.OFF ? 0 : 10000, + otherAllowancesAmount: allowance === Allowance.OFF ? 0 : 10000, + otherAllowancesName: allowance === Allowance.OFF ? undefined : '제수당', + overtimeRate, + }); + setReady(true); + navigate(`/owner/myWorkPlaces/${placeId}/addEmployee/third`); }; return ( @@ -32,7 +117,7 @@ const WorkEmployeeAddThird = () => { - + @@ -56,19 +144,22 @@ const WorkEmployeeAddThird = () => { + 일 - -
주휴일 선택
+ +
주휴일 선택
{/* 요일 선택 버튼 */} -
+
{DayOfWeeks.map((day) => ( ))}
- + - -
상여금
+ +
상여금
{
- -
기타급여
+ +
기타급여
@@ -131,13 +222,15 @@ const WorkEmployeeAddThird = () => {
- + -
초과 근로
+
초과 근로
%
@@ -155,7 +248,7 @@ const WorkEmployeeAddThird = () => { {}} + onAction={onClickAddThird} onClose={() => history.back()} />
diff --git a/src/components/ui/CalendarCustom.tsx b/src/components/ui/CalendarCustom.tsx index 6a5c227..76abd2b 100644 --- a/src/components/ui/CalendarCustom.tsx +++ b/src/components/ui/CalendarCustom.tsx @@ -6,7 +6,7 @@ import CalendarMark from './CalendarMark'; import { useNavigate } from 'react-router-dom'; import { useCalendarData } from '../../contexts/Calender-Data-Context'; import ApiClient from '../../api/apiClient'; -import parseYYYMMDD from '../../utils/date-util'; +import { parseYYYMMDD } from '../../utils/date-util'; type ValuePiece = Date | null; type Value = ValuePiece | [ValuePiece, ValuePiece]; diff --git a/src/contexts/EmployeeContract-Context.tsx b/src/contexts/EmployeeContract-Context.tsx index 898bce7..1789a7b 100644 --- a/src/contexts/EmployeeContract-Context.tsx +++ b/src/contexts/EmployeeContract-Context.tsx @@ -3,6 +3,7 @@ import { PropsWithChildren, useContext, useReducer, + useState, } from 'react'; import { EmployeeContract, @@ -18,6 +19,13 @@ type EmployeeContractContextProps = { addSecondInfo: (secondInfo: SecondInfo) => void; addThirdInfo: (thirdInfo: ThirdInfo) => void; resetInfo: () => void; + firstInfo: FirstInfo | undefined; + setFirstInfo: (first: FirstInfo) => void; + secondInfo: SecondInfo | undefined; + setSecondInfo: (second: SecondInfo) => void; + thirdInfo: ThirdInfo | undefined; + setThirdInfo: (third: ThirdInfo) => void; + contract: EmployeeContract | undefined; }; const EmployeeContractContext = createContext({ @@ -27,6 +35,13 @@ const EmployeeContractContext = createContext({ addSecondInfo: () => {}, addThirdInfo: () => {}, resetInfo: () => {}, + firstInfo: undefined, + setFirstInfo: () => {}, + secondInfo: undefined, + setSecondInfo: () => {}, + thirdInfo: undefined, + setThirdInfo: () => {}, + contract: undefined, }); type ReducerAction = @@ -71,6 +86,21 @@ const reducer = ( export const EmployeeContractProvider = ({ children }: PropsWithChildren) => { const [employeeContract, dispatch] = useReducer(reducer, undefined); + const [firstInfo, setFirstInfo] = useState(undefined); + const [secondInfo, setSecondInfo] = useState( + undefined + ); + const [thirdInfo, setThirdInfo] = useState(undefined); + const [contract, setContract] = useState( + undefined + ); + + // const create = () => { + // if (firstInfo && secondInfo && thirdInfo) { + // setContract({ ...firstInfo, ...secondInfo }); + // } + // }; + const prepareInfo = (workPlaceName: string) => { dispatch({ type: 'PREPARE', @@ -111,6 +141,13 @@ export const EmployeeContractProvider = ({ children }: PropsWithChildren) => { addSecondInfo, addThirdInfo, resetInfo, + firstInfo, + setFirstInfo, + secondInfo, + setSecondInfo, + thirdInfo, + setThirdInfo, + contract, }} > {children} diff --git a/src/types/api/owner-api.d.ts b/src/types/api/owner-api.d.ts index a2ebc29..53c4a1e 100644 --- a/src/types/api/owner-api.d.ts +++ b/src/types/api/owner-api.d.ts @@ -91,3 +91,21 @@ type OwnerAddMainAccountRequest = { type OwnerAddMainAccountResponse = { ownerId: number; }; + +// 사장님 - 근로자 등록 응답 +type RegisterEmployeeResponse = { + employmentContractId: number; + workPlaceEmployeeId: number; +}; + +// 사장님 - 근무 수동 추가 요청, 응답 +type RegisterAttendanceManualRequest = { + workPlaceEmployeeId: number; + payPerHour: number; + startTime: Date; + endTime: Date; + restMinute: number; +}; +type RegisterAttendanceManualResponse = { + attendanceId: number; +}; diff --git a/src/types/contract.d.ts b/src/types/contract.d.ts index 024abad..3bef53b 100644 --- a/src/types/contract.d.ts +++ b/src/types/contract.d.ts @@ -16,6 +16,7 @@ export type EmployeeContract = { otherAllowancesName: string; overtimeRate: number; }; +export type DayOfWeekShort = '월' | '화' | '수' | '목' | '금' | '토' | '일'; export type DayOfWeek = | '월요일' diff --git a/src/utils/date-util.ts b/src/utils/date-util.ts index 7f12e37..d8f1132 100644 --- a/src/utils/date-util.ts +++ b/src/utils/date-util.ts @@ -1,4 +1,6 @@ -const parseYYYMMDD = (dateString: string): Date => { +import { DayOfWeek, DayOfWeekShort } from '../types/contract'; + +export const parseYYYMMDD = (dateString: string): Date => { // 문자열에서 연도, 월, 일을 추출합니다. const year = parseInt(dateString.substring(0, 4), 10); // 앞의 4자리: 연도 const month = parseInt(dateString.substring(4, 6), 10) - 1; // 중간 2자리: 월 (0부터 시작하므로 -1) @@ -7,4 +9,7 @@ const parseYYYMMDD = (dateString: string): Date => { // Date 객체를 생성합니다. return new Date(year, month, day); }; -export default parseYYYMMDD; + +export const addSuffixDayOfWeek = (day: DayOfWeekShort) => { + return `${day}요일` as DayOfWeek; +}; diff --git a/src/utils/get-TimeString.ts b/src/utils/get-TimeString.ts index 069f785..ac8883e 100644 --- a/src/utils/get-TimeString.ts +++ b/src/utils/get-TimeString.ts @@ -1,4 +1,9 @@ -export const getTimeString = (dateString: string | Date): string => { +export const getTimeString = ( + dateString: string | Date | undefined +): string | undefined => { + if (!dateString) { + return undefined; + } const date = new Date(dateString); const hours = date.getHours().toString().padStart(2, '0'); // "09" const minutes = date.getMinutes().toString().padStart(2, '0'); // "00" diff --git a/yarn.lock b/yarn.lock index 9d4bdff..6bea50f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2214,6 +2214,18 @@ minimatch@^9.0.4: resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== +moment-timezone@^0.5.45: + version "0.5.45" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.45.tgz#cb685acd56bac10e69d93c536366eb65aa6bcf5c" + integrity sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ== + dependencies: + moment "^2.29.4" + +moment@^2.29.4: + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -2549,6 +2561,15 @@ react-clock@^5.0.0: clsx "^2.0.0" get-user-locale "^2.2.1" +react-cookie@^7.1.4: + version "7.1.4" + resolved "https://registry.yarnpkg.com/react-cookie/-/react-cookie-7.1.4.tgz#1e35c9f11394b44fbbc30c5eba1287d89258e993" + integrity sha512-wDxxa/HYaSXSMlyWJvJ5uZTzIVtQTPf1gMksFgwAz/2/W3lCtY8r4OChCXMPE7wax0PAdMY97UkNJedGv7KnDw== + dependencies: + "@types/hoist-non-react-statics" "^3.3.5" + hoist-non-react-statics "^3.3.2" + universal-cookie "^7.0.0" + react-datepicker@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-7.2.0.tgz#2d430caebb5c8b0fb988286d004aa2676ace665f" @@ -2559,14 +2580,6 @@ react-datepicker@^7.2.0: date-fns "^3.3.1" prop-types "^15.7.2" react-onclickoutside "^6.13.0" -react-cookie@^7.1.4: - version "7.1.4" - resolved "https://registry.yarnpkg.com/react-cookie/-/react-cookie-7.1.4.tgz#1e35c9f11394b44fbbc30c5eba1287d89258e993" - integrity sha512-wDxxa/HYaSXSMlyWJvJ5uZTzIVtQTPf1gMksFgwAz/2/W3lCtY8r4OChCXMPE7wax0PAdMY97UkNJedGv7KnDw== - dependencies: - "@types/hoist-non-react-statics" "^3.3.5" - hoist-non-react-statics "^3.3.2" - universal-cookie "^7.0.0" react-dom@^18.3.1: version "18.3.1" From 474753a6d1fdeff14f1abab34c6f326df65c837b Mon Sep 17 00:00:00 2001 From: kwang Date: Tue, 9 Jul 2024 09:14:41 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat/#88=20=EA=B3=B5=EC=A7=80=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 4 + src/api/apiClient.ts | 25 ++++++ src/api/interfaces/employeeApi.ts | 5 ++ src/api/interfaces/ownerApi.ts | 7 ++ src/components/employee/CalendarEmployee.css | 42 +++++++++ src/components/employee/CalendarEmployee.tsx | 27 +++--- .../owner-workplace/MyWorkPlaceDetail.tsx | 12 ++- .../owner-workplace/NotificationAdd.tsx | 61 +++++++++---- .../owner-workplace/WorkPlaceAdd.tsx | 18 ++++ src/contexts/Calender-Data-Context.tsx | 87 ------------------- .../Employee-Calender-Data-Context.tsx | 50 +++++++++++ src/types/api/employee-api.d.ts | 21 +++++ src/types/api/owner-api.d.ts | 7 ++ 13 files changed, 243 insertions(+), 123 deletions(-) create mode 100644 src/components/employee/CalendarEmployee.css create mode 100644 src/components/owner-workplace/WorkPlaceAdd.tsx create mode 100644 src/contexts/Employee-Calender-Data-Context.tsx diff --git a/src/App.tsx b/src/App.tsx index 8d71a84..c890d25 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -30,6 +30,8 @@ import WorkEmployeeAddComplete from './components/owner-workplace/WorkEmployeeAd import OwnerGreeting from './pages/LandingPage/OwnerGreeting'; import OwnerAddMainAccount from './pages/LandingPage/OwnerAddMainAccount'; import LoginPage from './pages/LoginPgae/LoginPage'; +import CalendarEmployee from './components/employee/CalendarEmployee'; +import WorkPlaceAdd from './components/owner-workplace/WorkPlaceAdd'; function App() { return ( @@ -40,7 +42,9 @@ function App() { } /> {/* } /> */} {/* } /> */} + } /> }> + } /> } /> } /> } /> diff --git a/src/api/apiClient.ts b/src/api/apiClient.ts index dfd68a8..d8fb3c8 100644 --- a/src/api/apiClient.ts +++ b/src/api/apiClient.ts @@ -338,6 +338,18 @@ class ApiClient implements employeeApi, userApi, ownerApi { } // ========================== + // 알바생 - 캘린더 데이터 + public async getEmployeeCalendarData( + year: number, + month: number + ): Promise { + const response: BaseResponse = + await this.axiosInstance.request({ + method: 'get', + url: `employee/salaries/calendar?year=${year}&month=${month}`, + }); + return response.data; + } // 사장님 - 캘린더 데이터 public async getCalendarData( @@ -426,6 +438,19 @@ class ApiClient implements employeeApi, userApi, ownerApi { }); return response.data; } + // 사장님 - 공지 추가 + public async registerNotice( + id: number, + request: RegisterNoticeRequest + ): Promise { + const response: BaseResponse = + await this.axiosInstance.request({ + method: 'post', + url: `/owner/work-places/${id}/notifications`, + data: request, + }); + return response.data; + } //========================== // 생성 메소드 diff --git a/src/api/interfaces/employeeApi.ts b/src/api/interfaces/employeeApi.ts index b583a2d..3c8b96b 100644 --- a/src/api/interfaces/employeeApi.ts +++ b/src/api/interfaces/employeeApi.ts @@ -75,6 +75,11 @@ interface employeeApi { employeeContractSign( employmentContractId: number ): Promise; + + getEmployeeCalendarData( + year: number, + month: number + ): Promise; } export default employeeApi; diff --git a/src/api/interfaces/ownerApi.ts b/src/api/interfaces/ownerApi.ts index d137499..e6346d9 100644 --- a/src/api/interfaces/ownerApi.ts +++ b/src/api/interfaces/ownerApi.ts @@ -21,8 +21,15 @@ interface ownerApi { request: Partial ): Promise; + // 사장님 - 근로 수동 추가 registerAttendance( request: RegisterAttendanceManualRequest ): Promise; + + // 사장님 - 공지 추가 + registerNotice( + id: number, + request: RegisterNoticeRequest + ): Promise; } export default ownerApi; diff --git a/src/components/employee/CalendarEmployee.css b/src/components/employee/CalendarEmployee.css new file mode 100644 index 0000000..49eef0d --- /dev/null +++ b/src/components/employee/CalendarEmployee.css @@ -0,0 +1,42 @@ +.react-calendar { + border: none !important; + width: 100% !important; + height: 70% !important; + display: flex; + flex-direction: column; + justify-content: space-around; +} + +.react-calendar__navigation { + margin-top: 8px; +} + +/* 요일 타일 스타일링 */ +.react-calendar__month-view__weekdays { + @apply bg-hanaLightGreen text-center p-1 font-semibold rounded-md; +} + +/* 요일 타일 텍스트 스타일링 */ +.react-calendar__month-view__weekdays__weekday { + @apply text-sm text-white; +} + +/* 날짜 타일 기본 스타일링 */ +.react-calendar__tile { + @apply text-center p-2 m-1 hover:bg-blue-100 flex flex-col border border-b-2; + border-bottom: 2px solid #ccc !important; +} + +/* 오늘 날짜 타일 스타일링 */ +/* .react-calendar__tile--now { + @apply !bg-blue-300 !text-white; +} */ + +/* 선택된 날짜 타일 스타일링 .react-calendar__tile--active { + @apply !bg-yellow-100 !text-white; +} */ + +/* 선택할 수 없는 날짜 타일 스타일링 */ +.react-calendar__tile--disabled { + @apply text-gray-400; +} diff --git a/src/components/employee/CalendarEmployee.tsx b/src/components/employee/CalendarEmployee.tsx index b575599..2283003 100644 --- a/src/components/employee/CalendarEmployee.tsx +++ b/src/components/employee/CalendarEmployee.tsx @@ -1,12 +1,14 @@ import { useEffect, useState } from 'react'; -import './CalendarCustom.css'; +import './CalendarEmployee.css'; import Calendar, { OnArgs } from 'react-calendar'; import 'react-calendar/dist/Calendar.css'; -import CalendarMark from './CalendarMark'; +// import CalendarMark from './CalendarMark'; import { useNavigate } from 'react-router-dom'; import { useCalendarData } from '../../contexts/Calender-Data-Context'; import ApiClient from '../../api/apiClient'; import { parseYYYMMDD } from '../../utils/date-util'; +import { useEmployeeCalendarData } from '../../contexts/Employee-Calender-Data-Context'; +import CalendarMark from '../ui/CalendarMark'; type ValuePiece = Date | null; type Value = ValuePiece | [ValuePiece, ValuePiece]; @@ -15,7 +17,7 @@ const currentDate = new Date(); const CalendarEmployee = () => { const [value, setValue] = useState(currentDate); - const { calendarData, setCalendarData } = useCalendarData(); + const { calendarData, setCalendarData } = useEmployeeCalendarData(); const navigate = useNavigate(); console.log('🚀 CalendarCustom value:', value); @@ -30,7 +32,7 @@ const CalendarEmployee = () => { const fetchData = async (year: number, month: number) => { try { - const response = await ApiClient.getInstance().getCalendarData( + const response = await ApiClient.getInstance().getEmployeeCalendarData( year, month ); @@ -58,7 +60,7 @@ const CalendarEmployee = () => { const getListForDate = (date: Date) => { const list = []; calendarData && - calendarData.workPlaceList.map((workPlace) => { + calendarData.list.map((workPlace) => { if ( new Date(parseYYYMMDD(workPlace.attendDate)).toDateString() === date.toDateString() @@ -92,21 +94,14 @@ const CalendarEmployee = () => { if (view === 'month') { // const events = getEventsForDate(date); const list = getListForDate(date); + console.log(list, '>>>>>>>>>>>>>>>>>'); return ( + //
헬로
{list.map((data) => ( - +
{data.workPlaceName}
+ // ))} - {/* {events.map((event) => ( - - //
- // {event.employeeName} - {event.workPlaceName} - //
- ))} */}
); } diff --git a/src/components/owner-workplace/MyWorkPlaceDetail.tsx b/src/components/owner-workplace/MyWorkPlaceDetail.tsx index 14bd35a..d3f2428 100644 --- a/src/components/owner-workplace/MyWorkPlaceDetail.tsx +++ b/src/components/owner-workplace/MyWorkPlaceDetail.tsx @@ -8,7 +8,7 @@ import WorkEmployeeListView from './WorkEmployeeListView'; import { AiOutlinePlusCircle } from 'react-icons/ai'; import NotificationAdd from './NotificationAdd'; import { useEmployeeContract } from '../../contexts/EmployeeContract-Context'; -import { useNavigate, useParams } from 'react-router-dom'; +import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; import ApiClient from '../../api/apiClient'; import Notification from './Notification'; @@ -20,6 +20,8 @@ enum ToggleStatus { const MyWorkPlaceDetail = () => { const navigate = useNavigate(); const { id } = useParams(); + const [searchParams] = useSearchParams(); + const selected = searchParams.get('selected'); const [data, setData] = useState(null); const currentDate = new Date(); @@ -34,8 +36,10 @@ const MyWorkPlaceDetail = () => { fetchData(year, month); }, [year, month]); useEffect(() => { + console.log('🚀 showAddNotification:', showAddNotification); + console.log('>>>>>>>>>>>> 호출되나여?'); fetchNotifications(); - }, []); + }, [showAddNotification]); const fetchData = async (year: number, month: number) => { try { @@ -77,7 +81,7 @@ const MyWorkPlaceDetail = () => { return ( data && ( - +