diff --git a/src/components/Out/Redisual/RedisualItem/index.tsx b/src/components/Out/Redisual/RedisualItem/index.tsx
new file mode 100644
index 0000000..4ea7e5a
--- /dev/null
+++ b/src/components/Out/Redisual/RedisualItem/index.tsx
@@ -0,0 +1,49 @@
+import * as S from './style';
+import { TBody, TD, TR } from '@b1nd/b1nd-dodamdodam-ui';
+import profileImg from 'assets/profileImg.svg';
+import { outSleepingDataFilter } from 'utils/Out/outSleepingDataFilter';
+import { useGetRedisualQuery } from 'queries/Redisual/redisual.query';
+
+interface OffBasePassProps {
+ studentName: string;
+ selectGrade: number;
+ selectApproval: string | undefined;
+ selectRoom: string;
+}
+
+const OffbaseRedisualItem = ({ selectApproval, selectGrade, studentName, selectRoom }: OffBasePassProps) => {
+ const { data: redisualStudent } = useGetRedisualQuery();
+
+ return (
+ <>
+ {!outSleepingDataFilter ||
+ outSleepingDataFilter(redisualStudent, studentName, selectGrade, selectApproval, selectRoom)?.length === 0 ? (
+ 현재 잔류 중인 학생이 없습니다.
+ ) : (
+ <>
+
+ {outSleepingDataFilter(redisualStudent, studentName, selectGrade, selectApproval, selectRoom)?.map(
+ (redisual) => (
+
+
+
+ |
+ {redisual.student.name} |
+
+ {redisual.student.grade}학년
+ {redisual.student.room}반{redisual.student.number}번
+ |
+
+ {redisual.phone}
+ |
+
+ )
+ )}
+
+ >
+ )}
+ >
+ );
+};
+
+export default OffbaseRedisualItem;
diff --git a/src/components/Out/Redisual/RedisualItem/style.ts b/src/components/Out/Redisual/RedisualItem/style.ts
new file mode 100644
index 0000000..f1778ba
--- /dev/null
+++ b/src/components/Out/Redisual/RedisualItem/style.ts
@@ -0,0 +1,110 @@
+import styled from "styled-components";
+import { CSSObject } from "styled-components";
+
+export const OffBaseTBody: CSSObject = {
+ width: "100%",
+ fontSize: "14px",
+ fontWeight: "600",
+ marginTop: "5px",
+ display: "flex",
+ rowGap: "5px",
+ flexDirection: "column",
+ whiteSpace: "normal",
+ cursor: "pointer",
+ tr: {
+ "&:hover": {
+ filter: "unset",
+ },
+ },
+};
+
+export const OffBaseTR: CSSObject = {
+ width: "100%",
+ height: "80px",
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "space-between",
+ columnGap: "10px",
+ borderTop: "1px solid #d9d9d9",
+ borderBottom: "1px solid #d9d9d9",
+};
+
+export const OffBaseTD: CSSObject = {
+ width: "14%",
+ fontSize: "16px",
+ lineHeight: "20px",
+};
+
+export const BusLeaveTime: CSSObject = {
+ width: "14%",
+ fontSize: "16px",
+ lineHeight: "20px",
+ whiteSpace: "nowrap",
+};
+
+export const PassengerStyle: CSSObject = {
+ width: "65px",
+ height: "30px",
+ borderRadius: "7px",
+ transition: "all 0.2s ease-in-out",
+ "&:hover": {
+ boxShadow: "0 0 0 1px #fff, 0 0 0 3px rgba(50, 100, 150, 0.4)",
+ },
+};
+
+export const ButtonContainerStyle: CSSObject = {
+ width: "14%",
+ display: "flex",
+ alignItems: "center",
+ columnGap: "5px",
+
+ marginRight: "10px",
+};
+
+export const EditStyle: CSSObject = {
+ width: "5rem",
+ height: "2rem",
+ borderRadius: "5px",
+ fontSize: "18px",
+ transition: "all 0.2s ease-in-out",
+
+ "&:hover": {
+ boxShadow: "0 0 0 1px #fff, 0 0 0 3px rgba(50, 100, 150, 0.4)",
+ },
+};
+
+export const DelStyle: CSSObject = {
+ width: "5rem",
+ height: "2rem",
+
+ fontSize: "18px",
+ borderRadius: "5px",
+ transition: "all 0.2s ease-in-out",
+ "&:hover": {
+ boxShadow: "0 0 0 1px #fff, 0 0 0 3px #ddd",
+ },
+};
+
+export const MemberImage = styled.img`
+ width: 38px;
+ height: 38px;
+ object-fit: cover;
+ border-radius: 4rem;
+`;
+
+export const NoneTile = styled.p`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ height: 64vh;
+
+ font-size: 16px;
+ color: #212529;
+`;
+
+export const DateContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ margin-right: 60px;
+`;
diff --git a/src/components/Out/Redisual/index.tsx b/src/components/Out/Redisual/index.tsx
new file mode 100644
index 0000000..9302c5a
--- /dev/null
+++ b/src/components/Out/Redisual/index.tsx
@@ -0,0 +1,73 @@
+import * as S from './style';
+import { Suspense, useState } from 'react';
+import ErrorBoundary from 'components/common/ErrorBoundary';
+import TableAttribute from 'components/common/TableAttribute';
+import TodayOffBaseItem from './RedisualItem/index';
+import { REDISUAL_ITEMS } from 'constants/Out/offbase.constant';
+import { SearchBar, Select } from '@b1nd/b1nd-dodamdodam-ui';
+import { useRecoilState } from 'recoil';
+import { SelectApprovalAtom, SelectGradeAtom } from 'stores/Out/out.store';
+import { changeApproval } from 'utils/Out/changeApproval';
+import { changeGrade } from 'utils/Member/changeGrade';
+import { GRADE_ITEMS } from 'constants/Grade/grade.constant';
+import { CsvButtonContainer } from 'components/Bus/BusModal/BusPassenger/style';
+import CsvButton from 'components/common/ExtractCsvData';
+import dayjs from 'dayjs';
+import useOffBaseLeave from 'hooks/Out/OutSleeping/useOutsleeping';
+import { PointSelectRoom } from 'stores/Point/point.store';
+import { useGetRedisualQuery } from 'queries/Redisual/redisual.query';
+import { redisualMemberCalc } from 'utils/Out/redisualMemberCalc';
+import useRedisual from 'hooks/Out/Redisual/useRedisual';
+
+const OffbaseRedisual = () => {
+ const [studentName, setStudentName] = useState('');
+ const [selectGrade, setSelectGrade] = useRecoilState(SelectGradeAtom);
+ const [selectApproval, setSelectApproval] = useRecoilState(SelectApprovalAtom);
+ const [room, setRoom] = useRecoilState(PointSelectRoom);
+
+ const { redisualStudent } = useRedisual();
+
+ const { data: redesualStudent } = useGetRedisualQuery();
+
+ return (
+ <>
+
+
+
+
+ {redisualMemberCalc(redesualStudent?.data!).length === 0
+ ? '오늘은 외박 잔류자가 없습니다.'
+ : `오늘의 외박 잔류자 수 : ${redisualMemberCalc(redesualStudent?.data!).length}명`}
+
+
+
+
+
+
+
+
+
+
+
+
+ 오늘의 외박 잔류자를 불러오지 못했습니다.>}>
+ 로딩중...>}>
+
+
+
+
+ >
+ );
+};
+
+export default OffbaseRedisual;
diff --git a/src/components/Out/Redisual/style.ts b/src/components/Out/Redisual/style.ts
new file mode 100644
index 0000000..2aec258
--- /dev/null
+++ b/src/components/Out/Redisual/style.ts
@@ -0,0 +1,13 @@
+import styled from "styled-components";
+export const SelectContainer = styled.div`
+ display: flex;
+ column-gap: 10px;
+`;
+
+export const OffBaseHeaderContainer = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+
+ z-index: 900;
+`;
diff --git a/src/components/Router/index.tsx b/src/components/Router/index.tsx
index 8458db9..52244fe 100644
--- a/src/components/Router/index.tsx
+++ b/src/components/Router/index.tsx
@@ -1,16 +1,17 @@
-import { Routes, Route } from "react-router-dom";
-import MemberPage from "pages/MemberPage";
-import BusListPage from "pages/Bus/BusListPage";
-import BusDatePage from "pages/Bus/BusDatePage";
-import AuthPage from "pages/Auth/AuthPage";
-import OffBasePassPage from "pages/Out/OutGoingPage";
-import OffBaseLeavePage from "pages/Out/OutSleepingPage";
-import TodayOffBaseLeavePage from "pages/Out/TodayOutSleepingPage";
-import { NightStudyAllowPage } from "pages/NightStudy/NightStudyAllowPage";
-import { NightStudyTodayPage } from "pages/NightStudy/NightStudyTodayPage";
-import PointScore from "components/Point/PointScore";
-import PointReason from "components/Point/PointReason";
-import ScheduleManage from "components/Schedule/ScheduleManage";
+import { Routes, Route } from 'react-router-dom';
+import MemberPage from 'pages/MemberPage';
+import BusListPage from 'pages/Bus/BusListPage';
+import BusDatePage from 'pages/Bus/BusDatePage';
+import AuthPage from 'pages/Auth/AuthPage';
+import OffBasePassPage from 'pages/Out/OutGoingPage';
+import OffBaseLeavePage from 'pages/Out/OutSleepingPage';
+import TodayOffBaseLeavePage from 'pages/Out/TodayOutSleepingPage';
+import { NightStudyAllowPage } from 'pages/NightStudy/NightStudyAllowPage';
+import { NightStudyTodayPage } from 'pages/NightStudy/NightStudyTodayPage';
+import PointScore from 'components/Point/PointScore';
+import PointReason from 'components/Point/PointReason';
+import ScheduleManage from 'components/Schedule/ScheduleManage';
+import OffbaseRedisualPage from 'pages/Out/RedisualPage';
const Router = () => {
return (
@@ -22,6 +23,7 @@ const Router = () => {
} />
} />
} />
+ } />
} />
} />
} />
diff --git a/src/components/common/SideBar/SideBarDropdown/index.tsx b/src/components/common/SideBar/SideBarDropdown/index.tsx
index ab9e01b..b416546 100644
--- a/src/components/common/SideBar/SideBarDropdown/index.tsx
+++ b/src/components/common/SideBar/SideBarDropdown/index.tsx
@@ -1,7 +1,7 @@
-import MenuDropdownChild from "../../MenuDropdown/MenuDropdownChild";
-import MenuDropdownWrapper from "../../MenuDropdown/MenuDropdownWrapper";
-import MenuItem from "../../MenuItem";
-import { SideBarDropdownContainer } from "./style";
+import MenuDropdownChild from '../../MenuDropdown/MenuDropdownChild';
+import MenuDropdownWrapper from '../../MenuDropdown/MenuDropdownWrapper';
+import MenuItem from '../../MenuItem';
+import { SideBarDropdownContainer } from './style';
const SideBarDropdown = () => {
return (
@@ -15,6 +15,7 @@ const SideBarDropdown = () => {
+
diff --git a/src/constants/Out/offbase.constant.ts b/src/constants/Out/offbase.constant.ts
index f94e8bf..b2b4da6 100644
--- a/src/constants/Out/offbase.constant.ts
+++ b/src/constants/Out/offbase.constant.ts
@@ -1,3 +1,5 @@
-export const OUT_GOING_ITEMS = ["사진", "이름", "학반", "출발", "도착", "사유", "식사 여부", "승인여부"];
+export const OUT_GOING_ITEMS = ['사진', '이름', '학반', '출발', '도착', '사유', '식사 여부', '승인여부'];
-export const OUT_SLEEPING_ITEMS = ["사진", "이름", "학반", "출발", "도착", "사유", "승인여부"];
+export const OUT_SLEEPING_ITEMS = ['사진', '이름', '학반', '출발', '도착', '사유', '승인여부'];
+
+export const REDISUAL_ITEMS = ['사진', '이름', '학반', '전화번호'];
diff --git a/src/hooks/Out/Redisual/useRedisual.ts b/src/hooks/Out/Redisual/useRedisual.ts
new file mode 100644
index 0000000..8f319b8
--- /dev/null
+++ b/src/hooks/Out/Redisual/useRedisual.ts
@@ -0,0 +1,33 @@
+import { QUERY_KEYS } from 'queries/queryKey';
+import { useGetRedisualQuery } from 'queries/Redisual/redisual.query';
+import { useEffect, useState } from 'react';
+
+const useRedisual = () => {
+ const { data: redisual } = useGetRedisualQuery();
+ const [redisualStudent, setRedisualStudent] = useState([
+ {
+ 이름: '',
+ 반번호: '',
+ 전화번호: '',
+ 비고: '',
+ },
+ ]);
+
+ useEffect(() => {
+ if (redisual?.data) {
+ const newData = redisual.data.map((data) => ({
+ 이름: data.student.name,
+ 반번호: `${data.student.grade}학년 ${data.student.room}반 ${data.student.number}번`,
+ 전화번호: data.phone,
+ 비고: '',
+ }));
+ setRedisualStudent(newData);
+ }
+ }, [redisual]);
+
+ return {
+ redisualStudent,
+ };
+};
+
+export default useRedisual;
diff --git a/src/pages/Out/RedisualPage.tsx b/src/pages/Out/RedisualPage.tsx
new file mode 100644
index 0000000..f6ec5c9
--- /dev/null
+++ b/src/pages/Out/RedisualPage.tsx
@@ -0,0 +1,12 @@
+import OffbaseRedisual from 'components/Out/Redisual';
+import SectionHeaderProvider from 'components/common/SectionHeaderProvider';
+
+const OffbaseRedisualPage = () => {
+ return (
+
+
+
+ );
+};
+
+export default OffbaseRedisualPage;
diff --git a/src/queries/Redisual/redisual.query.ts b/src/queries/Redisual/redisual.query.ts
new file mode 100644
index 0000000..6d7b478
--- /dev/null
+++ b/src/queries/Redisual/redisual.query.ts
@@ -0,0 +1,12 @@
+import { AxiosError } from 'axios';
+import { QUERY_KEYS } from 'queries/queryKey';
+import { useQuery, UseQueryOptions } from 'react-query';
+import redisualRepositoryImpl from 'repositories/Redisual/redisual.repositoryImpl';
+import { OutResponse } from 'types/Out/out.type';
+
+export const useGetRedisualQuery = (options?: UseQueryOptions) =>
+ useQuery(QUERY_KEYS.redisual.getResidual, () => redisualRepositoryImpl.getResdisual(), {
+ staleTime: 1000 * 60 * 60,
+ cacheTime: 1000 * 60 * 60,
+ ...options,
+ });
diff --git a/src/queries/queryKey.ts b/src/queries/queryKey.ts
index a152904..9ea3359 100644
--- a/src/queries/queryKey.ts
+++ b/src/queries/queryKey.ts
@@ -22,6 +22,9 @@ export const QUERY_KEYS = Object.freeze({
offbasemeal: {
getMealDemand: (date: string) => ["/out-going/meal-demand", date]
},
+ redisual: {
+ getResidual: "/out-sleeping/redisual",
+ },
nightstudy: {
getPendingNightStudy: "/night-study/pending",
getNightStudyList: "/night-study",
diff --git a/src/repositories/Redisual/redisual.repository.ts b/src/repositories/Redisual/redisual.repository.ts
new file mode 100644
index 0000000..57b747a
--- /dev/null
+++ b/src/repositories/Redisual/redisual.repository.ts
@@ -0,0 +1,5 @@
+import { OutResponse } from 'types/Out/out.type';
+
+export interface RedisualRepository {
+ getResdisual(): Promise;
+}
diff --git a/src/repositories/Redisual/redisual.repositoryImpl.ts b/src/repositories/Redisual/redisual.repositoryImpl.ts
new file mode 100644
index 0000000..c5bcdf9
--- /dev/null
+++ b/src/repositories/Redisual/redisual.repositoryImpl.ts
@@ -0,0 +1,13 @@
+import { OutResponse } from 'types/Out/out.type';
+import { RedisualRepository } from './redisual.repository';
+import { dodamAxios } from 'libs/Axios/customAxios';
+
+class RedisualRepositoryImpl implements RedisualRepository {
+ public async getResdisual(): Promise {
+ const { data } = await dodamAxios.get('/out-sleeping/residual');
+ return data;
+ }
+}
+
+const redisualRepositoryImpl = new RedisualRepositoryImpl();
+export default redisualRepositoryImpl;
diff --git a/src/types/Out/out.type.ts b/src/types/Out/out.type.ts
index 26317f9..c3b5c13 100644
--- a/src/types/Out/out.type.ts
+++ b/src/types/Out/out.type.ts
@@ -6,6 +6,7 @@ export interface OutListType {
id: number;
reason: string;
status: string;
+ phone: string;
student: StudentType;
startAt: string;
endAt: string;
diff --git a/src/utils/Out/redisualMemberCalc.ts b/src/utils/Out/redisualMemberCalc.ts
new file mode 100644
index 0000000..e7d49db
--- /dev/null
+++ b/src/utils/Out/redisualMemberCalc.ts
@@ -0,0 +1,7 @@
+import { OutListType } from "types/Out/out.type";
+
+export const redisualMemberCalc = (data: OutListType[]) => {
+ const allowedStudents =
+ data?.filter((redisual) => redisual.status === 'ACTIVE').map((redisual) => redisual.student.name) || [];
+ return allowedStudents;
+};