diff --git a/config b/config
index 499029fd1..c1c4afb2c 160000
--- a/config
+++ b/config
@@ -1 +1 @@
-Subproject commit 499029fd15b4237ce1575038f139f20228c6b186
+Subproject commit c1c4afb2c7a7101f4c8f6e48933a7ad4ea3a4158
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 740f2a360..ece075b5f 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -8,8 +8,6 @@ import MainPage from "@/pages/MainPage";
import AdminMainPage from "@/pages/admin/AdminMainPage";
import LoadingAnimation from "@/components/Common/LoadingAnimation";
import ProfilePage from "./pages/ProfilePage";
-import "./firebase-messaging-sw"
-
const NotFoundPage = lazy(() => import("@/pages/NotFoundPage"));
const LoginFailurePage = lazy(() => import("@/pages/LoginFailurePage"));
diff --git a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx
index 02df2ba0d..143de05ad 100644
--- a/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx
+++ b/frontend/src/components/CabinetInfoArea/CabinetInfoArea.container.tsx
@@ -70,7 +70,7 @@ export type TModalState =
export type TAdminModalState = "returnModal" | "statusModal" | "clubLentModal";
-const calExpiredTime = (expireTime: Date) =>
+export const calExpiredTime = (expireTime: Date) =>
Math.floor(
(expireTime.getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24)
);
@@ -102,7 +102,9 @@ const getCabinetUserList = (selectedCabinetInfo: CabinetInfo): string => {
return userNameList;
};
-const getDetailMessage = (selectedCabinetInfo: CabinetInfo): string | null => {
+export const getDetailMessage = (
+ selectedCabinetInfo: CabinetInfo
+): string | null => {
const { status, lentType, lents } = selectedCabinetInfo;
// 밴, 고장 사물함
if (status === CabinetStatus.BANNED || status === CabinetStatus.BROKEN)
@@ -120,7 +122,9 @@ const getDetailMessage = (selectedCabinetInfo: CabinetInfo): string | null => {
else return null;
};
-const getDetailMessageColor = (selectedCabinetInfo: CabinetInfo): string => {
+export const getDetailMessageColor = (
+ selectedCabinetInfo: CabinetInfo
+): string => {
const { status, lentType, lents } = selectedCabinetInfo;
// 밴, 고장 사물함
if (status === CabinetStatus.BANNED || status === CabinetStatus.BROKEN)
diff --git a/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.tsx b/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.tsx
index 7dd21770e..ca630e1d4 100644
--- a/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.tsx
+++ b/frontend/src/components/LeftNav/LeftMainNav/LeftMainNav.tsx
@@ -72,53 +72,58 @@ const LeftMainNav = ({
? "active cabiButton"
: " cabiButton"
}
+ src={"/src/assets/images/search.svg"}
onClick={onClickSearchButton}
>
-
+
Search
-
+
-
+
Contact
-
+
Club
-
+
Logout
>
)}
{!isAdmin && (
-
-
- Profile
-
+ <>
+
+
+ Profile
+
+ >
)}
diff --git a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx b/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx
index 8b93607e5..c1f4b532d 100644
--- a/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx
+++ b/frontend/src/components/LeftNav/LeftSectionNav/LeftSectionNav.tsx
@@ -73,14 +73,14 @@ const LeftSectionNav = ({
title="슬랙 캐비닛 채널 새창으로 열기"
>
문의하기
-
+
onClickClubForm()}
title="동아리 사물함 사용 신청서 새창으로 열기"
>
동아리 신청서
-
+
>
@@ -147,7 +147,7 @@ const SectionLinkStyled = styled.div`
display: flex;
align-items: center;
color: var(--gray-color);
- & svg {
+ & img {
width: 15px;
height: 15px;
margin-left: auto;
@@ -155,9 +155,10 @@ const SectionLinkStyled = styled.div`
@media (hover: hover) and (pointer: fine) {
&:hover {
color: var(--main-color);
- svg {
- stroke: var(--main-color);
- }
+ }
+ &:hover img {
+ filter: invert(33%) sepia(55%) saturate(3554%) hue-rotate(230deg)
+ brightness(99%) contrast(107%);
}
}
`;
diff --git a/frontend/src/components/Modals/ExtendModal/ExtendModal.tsx b/frontend/src/components/Modals/ExtendModal/ExtendModal.tsx
new file mode 100644
index 000000000..ab2254f00
--- /dev/null
+++ b/frontend/src/components/Modals/ExtendModal/ExtendModal.tsx
@@ -0,0 +1,140 @@
+import React, { useState } from "react";
+import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";
+import {
+ currentCabinetIdState,
+ isCurrentSectionRenderState,
+ myCabinetInfoState,
+ targetCabinetInfoState,
+ userState,
+} from "@/recoil/atoms";
+import Modal, { IModalContents } from "@/components/Modals/Modal";
+import ModalPortal from "@/components/Modals/ModalPortal";
+import {
+ FailResponseModal,
+ SuccessResponseModal,
+} from "@/components/Modals/ResponseModal/ResponseModal";
+import { additionalModalType, modalPropsMap } from "@/assets/data/maps";
+import checkIcon from "@/assets/images/checkIcon.svg";
+import { MyCabinetInfoResponseDto } from "@/types/dto/cabinet.dto";
+import {
+ axiosCabinetById,
+ axiosExtendLentPeriod,
+ axiosMyLentInfo, // axiosExtend, // TODO: 연장권 api 생성 후 연결해야 함
+} from "@/api/axios/axios.custom";
+import {
+ getExtendedDateString,
+ getLastDayofMonthString,
+} from "@/utils/dateUtils";
+
+const ExtendModal: React.FC<{
+ onClose: () => void;
+ cabinetId: Number;
+}> = (props) => {
+ const [showResponseModal, setShowResponseModal] = useState(false);
+ const [hasErrorOnResponse, setHasErrorOnResponse] = useState(false);
+ const [modalTitle, setModalTitle] = useState("");
+ const currentCabinetId = useRecoilValue(currentCabinetIdState);
+ const [myInfo, setMyInfo] = useRecoilState(userState);
+ const [myLentInfo, setMyLentInfo] =
+ useRecoilState(myCabinetInfoState);
+ const setTargetCabinetInfo = useSetRecoilState(targetCabinetInfoState);
+ const setIsCurrentSectionRender = useSetRecoilState(
+ isCurrentSectionRenderState
+ );
+ const formattedExtendedDate = getExtendedDateString(
+ myLentInfo.lents ? myLentInfo.lents[0].expiredAt : undefined
+ );
+ const extendDetail = `사물함 연장권 사용 시,
+ 대여 기간이 ${formattedExtendedDate} 23:59으로
+ 연장됩니다.
+ 연장권 사용은 취소할 수 없습니다.
+ 연장권을 사용하시겠습니까?`;
+ const extendInfoDetail = `사물함을 대여하시면 연장권 사용이 가능합니다.
+연장권은 ${getLastDayofMonthString(
+ null,
+ "/"
+ )} 23:59 이후 만료됩니다.`;
+ const getModalTitle = (cabinetId: number | null) => {
+ return cabinetId === null
+ ? modalPropsMap[additionalModalType.MODAL_OWN_EXTENSION].title
+ : modalPropsMap[additionalModalType.MODAL_USE_EXTENSION].title;
+ };
+ const getModalDetail = (cabinetId: number | null) => {
+ return cabinetId === null ? extendInfoDetail : extendDetail;
+ };
+ const getModalProceedBtnText = (cabinetId: number | null) => {
+ return cabinetId === null
+ ? modalPropsMap[additionalModalType.MODAL_OWN_EXTENSION].confirmMessage
+ : modalPropsMap[additionalModalType.MODAL_USE_EXTENSION].confirmMessage;
+ };
+ const tryExtendRequest = async (e: React.MouseEvent) => {
+ if (currentCabinetId === 0 || myInfo.cabinetId === null) {
+ setHasErrorOnResponse(true);
+ setModalTitle("현재 대여중인 사물함이 없습니다.");
+ setShowResponseModal(true);
+ return;
+ }
+ try {
+ await axiosExtendLentPeriod();
+ setMyInfo({
+ ...myInfo,
+ cabinetId: currentCabinetId,
+ extensible: false,
+ });
+ setIsCurrentSectionRender(true);
+ setModalTitle("연장되었습니다");
+ try {
+ const { data } = await axiosCabinetById(currentCabinetId);
+ setTargetCabinetInfo(data);
+ } catch (error) {
+ throw error;
+ }
+ try {
+ const { data: myLentInfo } = await axiosMyLentInfo();
+ setMyLentInfo(myLentInfo);
+ } catch (error) {
+ throw error;
+ }
+ } catch (error: any) {
+ setHasErrorOnResponse(true);
+ setModalTitle(error.response.data.message);
+ } finally {
+ setShowResponseModal(true);
+ }
+ };
+
+ const extendModalContents: IModalContents = {
+ type: myInfo.cabinetId === null ? "penaltyBtn" : "hasProceedBtn",
+ icon: checkIcon,
+ title: getModalTitle(myInfo.cabinetId),
+ detail: getModalDetail(myInfo.cabinetId),
+ proceedBtnText: getModalProceedBtnText(myInfo.cabinetId),
+ onClickProceed:
+ myInfo.cabinetId === null
+ ? async (e: React.MouseEvent) => {
+ props.onClose();
+ }
+ : tryExtendRequest,
+ closeModal: props.onClose,
+ };
+
+ return (
+
+ {!showResponseModal && }
+ {showResponseModal &&
+ (hasErrorOnResponse ? (
+
+ ) : (
+
+ ))}
+
+ );
+};
+
+export default ExtendModal;
diff --git a/frontend/src/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx b/frontend/src/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx
index c9243af63..07b888c35 100644
--- a/frontend/src/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx
+++ b/frontend/src/components/TopNav/TopNavButtonGroup/TopNavButtonGroup.tsx
@@ -8,12 +8,37 @@ import {
} from "@/recoil/atoms";
import TopNavButton from "@/components/TopNav/TopNavButtonGroup/TopNavButton/TopNavButton";
import { CabinetInfo } from "@/types/dto/cabinet.dto";
+import { LentDto } from "@/types/dto/lent.dto";
+import { UserDto } from "@/types/dto/user.dto";
+import CabinetStatus from "@/types/enum/cabinet.status.enum";
+import CabinetType from "@/types/enum/cabinet.type.enum";
import {
axiosCabinetById,
axiosDeleteCurrentBanLog,
} from "@/api/axios/axios.custom";
import useMenu from "@/hooks/useMenu";
+export const getDefaultCabinetInfo = (myInfo: UserDto): CabinetInfo => ({
+ building: "",
+ floor: 0,
+ cabinetId: 0,
+ visibleNum: 0,
+ lentType: CabinetType.PRIVATE,
+ title: null,
+ maxUser: 0,
+ status: CabinetStatus.AVAILABLE,
+ section: "",
+ lents: [
+ {
+ userId: myInfo.userId,
+ name: myInfo.name,
+ lentHistoryId: 0,
+ startedAt: new Date(),
+ expiredAt: new Date(),
+ },
+ ] as LentDto[],
+ statusNote: "",
+});
const TopNavButtonGroup = ({ isAdmin }: { isAdmin?: boolean }) => {
const { toggleCabinet, toggleMap, openCabinet, closeAll } = useMenu();
const [currentCabinetId, setCurrentCabinetId] = useRecoilState(
diff --git a/frontend/src/pages/admin/SearchPage.tsx b/frontend/src/pages/admin/SearchPage.tsx
index 7552e3e2c..92f355210 100644
--- a/frontend/src/pages/admin/SearchPage.tsx
+++ b/frontend/src/pages/admin/SearchPage.tsx
@@ -61,7 +61,7 @@ const SearchPage = () => {
searchValue.current,
currentPage.current
);
-
+
setSearchListByIntraId(searchResult.data.result ?? []);
setTotalSearchList(Math.ceil(searchResult.data.totalLength / 10) ?? 0);
setTimeout(() => {
diff --git a/frontend/src/utils/dateUtils.ts b/frontend/src/utils/dateUtils.ts
index 81b509b43..f39e1f014 100644
--- a/frontend/src/utils/dateUtils.ts
+++ b/frontend/src/utils/dateUtils.ts
@@ -1,3 +1,15 @@
+export const padTo2Digits = (num: number) => {
+ return num.toString().padStart(2, "0");
+};
+
+export const formatDate = (date: Date, divider: string) => {
+ return [
+ date.getFullYear(),
+ padTo2Digits(date.getMonth() + 1),
+ padTo2Digits(date.getDate()),
+ ].join(divider);
+};
+
export const getExpireDateString = (
lentType: string,
existExpireDate?: Date
@@ -10,19 +22,38 @@ export const getExpireDateString = (
if (!existExpireDate) {
expireDate.setDate(expireDate.getDate() + parseInt(addDays));
- }
- const padTo2Digits = (num: number) => {
- return num.toString().padStart(2, "0");
- };
- const formatDate = (date: Date) => {
- return [
- date.getFullYear(),
- padTo2Digits(date.getMonth() + 1),
- padTo2Digits(date.getDate()),
- ].join("/");
- };
-
- return formatDate(expireDate);
+ return formatDate(expireDate, "/");
+};
+
+// 공유 사물함 반납 시 남은 대여일 수 차감 (원래 남은 대여일 수 * (남은 인원 / 원래 있던 인원))
+export const getShortenedExpireDateString = (
+ lentType: string,
+ currentNumUsers: number,
+ existExpireDate: Date | undefined
+) => {
+ if (lentType != "SHARE" || existExpireDate === undefined) return;
+ const dayInMilisec = 1000 * 60 * 60 * 24;
+ const expireDateInMilisec = new Date(existExpireDate).getTime();
+ let secondUntilExpire = expireDateInMilisec - new Date().getTime();
+ let daysUntilExpire = Math.ceil(secondUntilExpire / dayInMilisec) - 1;
+ let dateRemainig =
+ (daysUntilExpire * (currentNumUsers - 1)) / currentNumUsers;
+ let newExpireDate = new Date().getTime() + dateRemainig * dayInMilisec;
+ return formatDate(new Date(newExpireDate), "/");
+};
+
+export const getExtendedDateString = (existExpireDate?: Date) => {
+ let expireDate = existExpireDate ? new Date(existExpireDate) : new Date();
+ expireDate.setDate(
+ expireDate.getDate() + parseInt(import.meta.env.VITE_EXTENDED_LENT_PERIOD)
+ );
+ return formatDate(expireDate, "/");
+};
+
+export const getLastDayofMonthString = (date: Date | null, divider: string) => {
+ if (date === null) date = new Date();
+ let lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 0);
+ return formatDate(lastDay, divider);
};
export const getTotalPage = (totalLength: number, size: number) => {