diff --git a/services/admin/src/apis/points/index.ts b/services/admin/src/apis/points/index.ts index 09c91b0..6a3c79e 100644 --- a/services/admin/src/apis/points/index.ts +++ b/services/admin/src/apis/points/index.ts @@ -10,6 +10,7 @@ import { instance } from '..'; import { AllPointListResponse, AllPointsOptionResponse, + RecentStudentPointResponse, StudentPointHistoryResponse, } from './response'; import { useToast } from '@/hooks/useToast'; @@ -38,6 +39,22 @@ export const getStudentPointHistory = async ( return data; }; +/** 학생 상/벌점 최근 내역 조회 */ +export const getRecentStudentPointHistory = async ( + student_id: string, + page?: number, + size?: number, +) => { + if (student_id) { + const { data } = await instance.get>( + `${router}/history/students/${student_id}/recent${ + page || size ? `?page=${page}&size=${size}` : '' + }`, + ); + return data; + } +}; + /** 상/벌점 전체 조회 */ export const getAllPoints = async () => { const { data } = await instance.get>( diff --git a/services/admin/src/apis/points/response.ts b/services/admin/src/apis/points/response.ts index 65df288..230dc19 100644 --- a/services/admin/src/apis/points/response.ts +++ b/services/admin/src/apis/points/response.ts @@ -18,6 +18,14 @@ export interface StudentPointHistoryResponse { point_histories: StudentPointHistoryType[]; } +export interface RecentStudentPointResponse { + student_name: string; + student_gcn: string; + point_type: PointType; + point_score: number; + point_name: string; +} + export interface StudentPointHistoryType { point_history_id: string; date?: string; diff --git a/services/admin/src/components/main/DetailBox/PointItem.tsx b/services/admin/src/components/main/DetailBox/PointItem.tsx index d6a6aa4..17c7c8a 100644 --- a/services/admin/src/components/main/DetailBox/PointItem.tsx +++ b/services/admin/src/components/main/DetailBox/PointItem.tsx @@ -7,6 +7,7 @@ import { } from '@/apis/points/response'; import { PointEnum, PointType } from '@/apis/points'; import { usePointHistoryId } from '@/store/usePointHistoryId'; +import { useRecentStudentPointHistory } from '@/hooks/usePointsApi'; import { useState } from 'react'; interface PropsType extends StudentPointHistoryType { @@ -101,6 +102,31 @@ export function PointItem({ ); } +export function RecentPointItem({ studentId }: { studentId: string }) { + const { data: recentStudentPointHistory } = + useRecentStudentPointHistory(studentId); + + return ( + <_Student> + <> + {recentStudentPointHistory?.student_name} + {recentStudentPointHistory?.student_gcn} + + <_Divider /> + <> + <_HollowBox width={80}> + + {recentStudentPointHistory?.point_name || '내역 없음'} + + + {recentStudentPointHistory?.point_score} + + + ); +} + export function StudentPointItem({ isDeleteListOption = false, canDelete = false, @@ -289,3 +315,36 @@ const _Delete = styled.div` background-color: ${({ theme }) => theme.color.gray2}; border-radius: 50%; `; + +const _Student = styled.div` + display: flex; + position: relative; + justify-content: space-between; + align-items: center; + width: 100%; + height: 57px; + background-color: #f9f9f9; + margin-bottom: 8px; + border: 1px solid #eeeeee; + border-radius: 5px; + padding: 0 28px; +`; + +const _HollowBox = styled.div<{ width: number }>` + div { + width: ${({ width }) => width}px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +`; + +const _Divider = styled.div` + position: absolute; + top: 50%; + left: 47%; + transform: translate(-50%, -50%); + width: 1px; + height: 28px; + background-color: ${({ theme }) => theme.color.gray3}; +`; diff --git a/services/admin/src/components/main/Divider.tsx b/services/admin/src/components/main/Divider.tsx index 0cd8fd8..54f4b7e 100644 --- a/services/admin/src/components/main/Divider.tsx +++ b/services/admin/src/components/main/Divider.tsx @@ -4,11 +4,13 @@ interface DividerProps { width?: number; height?: number; margin?: string; + maxWidth?: number; } export function Divider({ height = 500, margin = '0 20px 0 40px', width = 1, + maxWidth = 1300, }: DividerProps) { return <_Divider height={height} width={width} margin={margin} />; } diff --git a/services/admin/src/components/main/StudentList.tsx b/services/admin/src/components/main/StudentList.tsx index e381f90..75d6773 100644 --- a/services/admin/src/components/main/StudentList.tsx +++ b/services/admin/src/components/main/StudentList.tsx @@ -52,6 +52,7 @@ import { import { usePointHistoryId } from '@/store/usePointHistoryId'; import { useQueryClient } from '@tanstack/react-query'; import { useDeleteTagIdStore } from '@/store/useDeleteTagId'; +import StudentSelectModal from '../modals/StudentSelectModal'; import SideBarPortal from '../sidebar/SideBarPortal'; import { PointList } from './PointList'; import { SideBar } from '../sidebar'; @@ -97,9 +98,11 @@ export function StudentList({ state.appendStudentId, state.deleteStudentId, ]); + const [clickedStudentId, setClickedStudentId] = useClickedStudentIdStore( (state) => [state.clickedStudentId, state.setClickedStudentId], ); + const [pointHistoryId] = usePointHistoryId((state) => [state.pointHistoryId]); const [tagId] = useDeleteTagIdStore((state) => [state.deleteTagId]); const { modalState, selectModal, closeModal } = useModal(); @@ -345,6 +348,7 @@ export function StudentList({ tagModal={tagModal} /> )} + {Boolean(selectedStudentId.length) && } {openAllPointHistorySideBar && ( ); } - const _Wrapper = styled.div` width: 1030px; transition: width 0.7s ease-in-out; diff --git a/services/admin/src/components/modals/StudentSelectModal.tsx b/services/admin/src/components/modals/StudentSelectModal.tsx new file mode 100644 index 0000000..8ecaa49 --- /dev/null +++ b/services/admin/src/components/modals/StudentSelectModal.tsx @@ -0,0 +1,127 @@ +import styled from 'styled-components'; +import { useState, useEffect } from 'react'; +import { useSelectedStudentIdStore } from '@/store/useSelectedStudentIdStore'; +import { Text, Button } from '@team-aliens/design-system'; +import { fadeInRight } from '../../components/animation/fade'; +import { Divider } from '../main/Divider'; +import { useModal } from '@/hooks/useModal'; +import OutsideClickHandler from 'react-outside-click-handler'; +import { RecentPointItem } from '../main/DetailBox/PointItem'; + +export default function StudentSelectModal() { + const [selectedStudentId] = useSelectedStudentIdStore((state) => [ + state.selectedStudentId, + state.resetStudentId, + state.appendStudentId, + state.deleteStudentId, + ]); + + const [click, setClick] = useState(false); + const { selectModal } = useModal(); + + return ( + setClick(false)}> + <_Wrapper> + <_Header> + + 기본정보 + + + 최근 부여 항목 + + + <_StudentWrapper> + {selectedStudentId.map((student) => { + return ; + })} + + <_UnderWrapper> + + {selectedStudentId.length}명이 선택되었습니다. + + <_ButtonWrapper> + {click && ( + <_Items> + <_Item onClick={() => selectModal('GIVE_POINT')}> + + 상/벌점 + + + <_Item onClick={() => selectModal('GIVE_TAG_OPTIONS')}> + + 학생 태그 + + + + )} + + + + + + ); +} + +const _Wrapper = styled.div` + display: flex; + flex-direction: column; + position: fixed; + bottom: 32px; + right: 28px; + background-color: white; + width: 418px; + height: 448px; + box-shadow: 0px 3px 20px rgba(0, 0, 0, 0.19); + border-radius: 8px; + z-index: 2; + padding: 36px 40px 23px; + /* animation: ${fadeInRight} 0.3s; */ +`; + +const _Header = styled.div` + display: flex; + justify-content: space-between; +`; + +const _StudentWrapper = styled.div` + width: 338px; + height: 285px; + margin-bottom: 30px; + position: relative; + overflow-y: scroll; + margin-top: 10px; +`; + +const _UnderWrapper = styled.div` + display: flex; + justify-content: space-between; + align-items: center; +`; + +const _ButtonWrapper = styled.div` + display: flex; + position: relative; +`; + +const _Items = styled.div` + position: absolute; + width: 132px; + max-height: 138px; + left: -137px; + top: -46px; + background-color: ${({ theme }) => theme.color.gray1}; + box-shadow: 0px 2px 12px rgba(0, 0, 0, 0.1); + border-radius: 4px; + z-index: 3; + overflow-y: scroll; +`; + +const _Item = styled.div` + display: flex; + justify-content: center; + align-items: center; + gap: 12px; + margin: 0 8px; + height: 46px; + border-bottom: 1px solid ${({ theme }) => theme.color.gray3}; +`; diff --git a/services/admin/src/hooks/usePointsApi.tsx b/services/admin/src/hooks/usePointsApi.tsx index c3504fd..0fac655 100644 --- a/services/admin/src/hooks/usePointsApi.tsx +++ b/services/admin/src/hooks/usePointsApi.tsx @@ -8,12 +8,14 @@ import { cancelPointHistory, getAllPointHistory, getAllPoints, + getRecentStudentPointHistory, getStudentPointHistory, PointType, } from '@/apis/points'; import { usePointHistoryList } from './usePointHistoryList'; import { useToast } from './useToast'; import { useModal } from './useModal'; +import { RecentStudentPointResponse } from '@/apis/points/response'; export const useAllPointHistory = (pointType: PointType) => useQuery( @@ -44,6 +46,22 @@ export const useStudentPointHistory = ( ); }; +export const useRecentStudentPointHistory = ( + student_id: string, + isActive?: boolean, + page?: number, + size?: number, +) => { + return useQuery( + [`getRecentStudentPointHistory`, student_id], + () => getRecentStudentPointHistory(student_id, page, size), + { + refetchOnWindowFocus: true, + enabled: isActive && Boolean(student_id), + }, + ); +}; + export const usePointOptionList = () => useQuery(['usePointList'], () => getAllPoints(), { refetchOnWindowFocus: true,