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; +};