diff --git a/src/Layout/PageLayout.jsx b/src/Layout/PageLayout.jsx
index e96102f..c090cd4 100644
--- a/src/Layout/PageLayout.jsx
+++ b/src/Layout/PageLayout.jsx
@@ -7,10 +7,16 @@ const Container = styled.div`
width: 100%;
`;
+const SideBarLayout = styled.div`
+ @media (max-width: 480px) {
+ display: none;
+ }
+`;
+
export default function PageLayout({ sideBar, children }) {
return (
- {sideBar}
+ {sideBar}
{children}
);
diff --git a/src/components/Modal/Modal.jsx b/src/components/Modal/Modal.jsx
index 988cbad..cebbe7c 100644
--- a/src/components/Modal/Modal.jsx
+++ b/src/components/Modal/Modal.jsx
@@ -1,18 +1,19 @@
import PropTypes from 'prop-types';
import * as S from './Modal.style';
import { useNavigate } from 'react-router-dom';
-import { useState } from 'react';
import Portal from '../Portal/Portal';
-const Modal = ({ name, major, studentId, isOpen, onClose }) => {
+const Modal = ({ isOpen, onClose, attendees }) => {
const navigate = useNavigate();
- const handleCompletedButtonClick = async () => {
+ const handlePersonClick = (studentInfo) => {
onClose();
- navigate('/attendance/sign');
+ navigate('/attendance/sign', {
+ state: { studentInfo },
+ });
};
- const handleCancelButtonClick = async () => {
+ const handleCancelButtonClick = () => {
onClose();
navigate('/attendance/student-id');
};
@@ -24,26 +25,19 @@ const Modal = ({ name, major, studentId, isOpen, onClose }) => {
return (
-
- {name}님이 맞으십니까?
-
+ 출석 체크 할 사람을 선택해주세요.
-
- 학과
- {major}
-
-
- 학번
- {studentId}
-
+ {attendees.map((attendee, index) => (
+ handlePersonClick(attendee)}>
+ {attendee.name}
+ {attendee.major}
+
+ ))}
- 아니요
+ 이전페이지로 돌아가기.
-
- 네, 서명하러 하기
-
@@ -51,11 +45,15 @@ const Modal = ({ name, major, studentId, isOpen, onClose }) => {
};
Modal.propTypes = {
- name: PropTypes.string.isRequired,
- major: PropTypes.string.isRequired,
- studentId: PropTypes.string.isRequired,
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
+ attendees: PropTypes.arrayOf(
+ PropTypes.shape({
+ name: PropTypes.string.isRequired,
+ major: PropTypes.string.isRequired,
+ studentId: PropTypes.string.isRequired,
+ }),
+ ).isRequired,
};
export default Modal;
diff --git a/src/components/Navigator/Navigator.jsx b/src/components/Navigator/Navigator.jsx
index 81612bf..62c99dc 100644
--- a/src/components/Navigator/Navigator.jsx
+++ b/src/components/Navigator/Navigator.jsx
@@ -15,6 +15,14 @@ export default function Navigator() {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const EVENT_ID = useRecoilValue(eventIDState);
+ const handleDimClick = () => {
+ setIsSidebarOpen(false);
+ };
+
+ const toggleSidebar = () => {
+ setIsSidebarOpen(!isSidebarOpen);
+ };
+
useEffect(() => {
console.log('USER_ID:', USER_ID);
console.log('EVENT_ID:', EVENT_ID);
@@ -55,9 +63,19 @@ export default function Navigator() {
};
}, []);
- const toggleSidebar = () => {
- setIsSidebarOpen(!isSidebarOpen);
- };
+ useEffect(() => {
+ const handleScroll = () => {
+ if (window.scrollY > 0) {
+ setIsSidebarOpen(false);
+ }
+ };
+
+ window.addEventListener('scroll', handleScroll);
+
+ return () => {
+ window.removeEventListener('scroll', handleScroll);
+ };
+ }, []);
return (
<>
@@ -93,6 +111,7 @@ export default function Navigator() {
+ {isSidebarOpen && }
>
);
diff --git a/src/components/Navigator/Navigator.style.jsx b/src/components/Navigator/Navigator.style.jsx
index 9baf268..6bb9889 100644
--- a/src/components/Navigator/Navigator.style.jsx
+++ b/src/components/Navigator/Navigator.style.jsx
@@ -175,3 +175,14 @@ export const Sidebar = styled.div`
transform: ${({ isOpen }) => (isOpen ? 'translateX(0)' : 'translateX(100%)')};
transition: transform 0.3s ease-in-out;
`;
+
+export const Dim = styled.div`
+ opacity: 0.2;
+ position: fixed;
+ top: 55px;
+ z-index: 1;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: black;
+`;
diff --git a/src/components/Navigator/Sidebar.jsx b/src/components/Navigator/Sidebar.jsx
index 1c44415..8c49d11 100644
--- a/src/components/Navigator/Sidebar.jsx
+++ b/src/components/Navigator/Sidebar.jsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import * as S from './Sidebar.style';
import {
FaTableList,
@@ -7,8 +7,13 @@ import {
FaUsers,
FaChartPie,
} from 'react-icons/fa6';
-import { useLocation } from 'react-router-dom';
+import { USER_ID } from '../../constants';
+import { useLocation, useNavigate } from 'react-router-dom';
+import { useRecoilValue } from 'recoil';
+import { eventIDState } from '../../recoil/atoms/state';
+import { axiosInstance } from '../../axios';
+// 개별 메뉴
const menuItems = [
{ to: '/event/dashboard', icon: , text: '대시보드' },
{
@@ -40,8 +45,73 @@ function MenuItem({ to, icon, text, isActive }) {
);
}
+// 사이드바 전체
export default function Sidebar() {
+ const [parsedEvents, setParsedEvents] = useState(null);
+ const [isAttendanceButtonActive, setIsAttendanceButtonActive] =
+ useState(false);
const location = useLocation();
+ const EVENT_ID = useRecoilValue(eventIDState);
+
+ useEffect(() => {
+ console.log('USER_ID:', USER_ID);
+ console.log('EVENT_ID:', EVENT_ID);
+ const fetchData = async () => {
+ try {
+ const response = await axiosInstance.get(
+ `/api/v1/events/${USER_ID}/${EVENT_ID}`,
+ );
+ const eventData = response.data;
+ if (eventData) {
+ const now = new Date();
+
+ const schedules = eventData.eventSchedules.map((schedule) => ({
+ date: schedule.eventDate,
+ startTime: schedule.eventStartTime,
+ endTime: schedule.eventEndTime,
+ attendanceList: schedule.attendanceListResponseDtos,
+ }));
+
+ const firstSchedule = schedules[0];
+ const lastSchedule = schedules[schedules.length - 1];
+
+ const firstScheduleStartDateTime = new Date(
+ `${firstSchedule.date}T${firstSchedule.startTime}`,
+ );
+ const lastScheduleEndDateTime = new Date(
+ `${lastSchedule.date}T${lastSchedule.endTime}`,
+ );
+
+ // 현재 시간이 행사 기간 내에 있는지 확인
+ if (
+ now >= firstScheduleStartDateTime &&
+ now <= lastScheduleEndDateTime
+ ) {
+ setIsAttendanceButtonActive(true);
+ } else {
+ setIsAttendanceButtonActive(false);
+ }
+
+ const parsedEvent = {
+ title: eventData.eventTitle,
+ detail: eventData.eventDetail,
+ image: eventData.eventImage,
+ schedules,
+ totalSessions: eventData.eventSchedules.length,
+ totalParticipants: schedules[0].attendanceList.length,
+ eventType: eventData.eventType,
+ eventTarget: eventData.eventTarget,
+ };
+
+ setParsedEvents(parsedEvent);
+ }
+ } catch (error) {
+ console.error('이벤트 데이터를 가져오는 중 오류:', error);
+ }
+ };
+
+ fetchData();
+ }, [EVENT_ID]);
return (
@@ -76,8 +146,10 @@ export default function Sidebar() {
-
-
+
+
출석화면으로 이동
diff --git a/src/components/Navigator/Sidebar.style.jsx b/src/components/Navigator/Sidebar.style.jsx
index 9d4b92b..1e81525 100644
--- a/src/components/Navigator/Sidebar.style.jsx
+++ b/src/components/Navigator/Sidebar.style.jsx
@@ -78,8 +78,15 @@ export const AttendanceBtn = styled.button`
line-height: 100%;
letter-spacing: -0.01em;
color: #ffffff;
+ cursor: pointer;
+ transition: background 0.3s ease;
&:hover {
background: #2b90fc;
}
+
+ &:disabled {
+ background: #cccccc;
+ cursor: not-allowed;
+ }
`;
diff --git a/src/pages/AttendancePage/AttendanceSignPage.jsx b/src/pages/AttendancePage/AttendanceSignPage.jsx
index a2905d4..989f818 100644
--- a/src/pages/AttendancePage/AttendanceSignPage.jsx
+++ b/src/pages/AttendancePage/AttendanceSignPage.jsx
@@ -21,6 +21,7 @@ const AttendanceSignPage = ({ name, major, studentId }) => {
const [isSigned, setIsSigned] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [eventTitle, setEventTitle] = useState('');
+ const [eventTarget, setEventTarget] = useState('INTERNAL');
const signatureRef = useRef(null);
const { getSessionStorage } = useSessionStorages();
@@ -70,17 +71,19 @@ const AttendanceSignPage = ({ name, major, studentId }) => {
};
useEffect(() => {
- const fetchEventTitle = async () => {
+ const fetchEventDetails = async () => {
try {
const response = await axiosInstance.get(
`/api/v1/events/${USER_ID}/${EVENT_ID}`,
);
- setEventTitle(response.data.eventTitle);
+ const eventData = response.data;
+ setEventTitle(eventData.eventTitle);
+ setEventTarget(eventData.eventTarget);
} catch (error) {
console.error('이벤트 타이틀 에러', error);
}
};
- fetchEventTitle();
+ fetchEventDetails();
}, []);
return (
@@ -91,13 +94,15 @@ const AttendanceSignPage = ({ name, major, studentId }) => {
- 학과
+ 소속
{studentInfo.major}
-
- 학번
- {studentInfo.number}
-
+ {eventTarget === 'INTERNAL' && (
+
+ 학번
+ {studentInfo.number}
+
+ )}
{/* 서명 */}
diff --git a/src/pages/AttendancePage/AttendanceSignPage.style.jsx b/src/pages/AttendancePage/AttendanceSignPage.style.jsx
index 8c27831..ba6a4eb 100644
--- a/src/pages/AttendancePage/AttendanceSignPage.style.jsx
+++ b/src/pages/AttendancePage/AttendanceSignPage.style.jsx
@@ -36,7 +36,7 @@ export const ContentContainer = styled.div`
width: 100%;
max-width: 900px;
gap: 16px;
- padding: 20px 0;
+ padding-bottom: 20px;
@media (max-width: ${BREAKPOINTS[0]}px) {
flex-direction: column;
@@ -119,9 +119,9 @@ export const CanvasPlaceholder = styled.p`
export const SignatureCanvasContainer = styled.div`
width: 100%;
- max-width: 900px;
- height: auto;
- aspect-ratio: 900 / 400;
+ max-width: 800px;
+ height: inherit;
+ aspect-ratio: 900 / 300;
canvas {
width: 100%;
diff --git a/src/pages/AttendancePage/AttendanceStudentIdPage.jsx b/src/pages/AttendancePage/AttendanceStudentIdPage.jsx
index a173625..a230ce6 100644
--- a/src/pages/AttendancePage/AttendanceStudentIdPage.jsx
+++ b/src/pages/AttendancePage/AttendanceStudentIdPage.jsx
@@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react';
import * as S from './AttendanceStudentIdPage.style';
import { AttendanceHeader } from '../../components';
+import Modal from '../../components/Modal/Modal';
import { USER_ID } from '../../constants';
import { useSessionStorages } from '../../hooks';
import { axiosInstance } from '../../axios';
@@ -16,26 +17,43 @@ const AttendanceStudentIdPage = () => {
const [eventDate, setEventDate] = useState('');
const [isAlreadyCompleted, setIsAlreadyCompleted] = useState(false);
const [isNoMatch, setIsNoMatch] = useState(false);
+ const [eventTarget, setEventTarget] = useState('INTERNAL');
+ const [attendees, setAttendees] = useState([]);
+ const [isModalOpen, setIsModalOpen] = useState(false);
const EVENT_ID = useRecoilValue(eventIDState);
const { setSessionStorage } = useSessionStorages();
const studentId = Array.from({ length: 7 }, (_, index) => index + 1);
+ const phoneId = Array.from({ length: 4 }, (_, index) => index + 1);
const dialList = Array.from({ length: 9 }, (_, index) => index + 1);
- const isSevenDigits = enteredDials.length === 7;
+ const isSevenDigits =
+ eventTarget === 'INTERNAL'
+ ? enteredDials.length === 7
+ : enteredDials.length === 4;
const isConfirmEnabled = isSevenDigits;
const getAttendanceCheck = async (params) => {
- const { data } = await axiosInstance.get(
- `/api/v1/attendance/check/${USER_ID}/${EVENT_ID}`,
- {
- params: {
- studentNumber: params.studentNumber,
- eventDate: params.eventDate,
- },
+ const url =
+ eventTarget === 'INTERNAL'
+ ? `/api/v1/attendance/check/studentNumber/${USER_ID}/${EVENT_ID}`
+ : `/api/v1/attendance/check/phoneNumber/${USER_ID}/${EVENT_ID}`;
+
+ console.log('API 호출 URL:', url);
+ console.log('API 호출 파라미터:', {
+ [eventTarget === 'INTERNAL' ? 'studentNumber' : 'phoneNumber']:
+ params.number,
+ eventDate: params.eventDate,
+ });
+
+ const { data } = await axiosInstance.get(url, {
+ params: {
+ [eventTarget === 'INTERNAL' ? 'studentNumber' : 'phoneNumber']:
+ params.number,
+ eventDate: params.eventDate,
},
- );
+ });
return data;
};
@@ -44,22 +62,27 @@ const AttendanceStudentIdPage = () => {
setEnteredDials(enteredDials.slice(0, -1));
} else if (dial === '서명하러 가기' && isConfirmEnabled) {
try {
+ const numberString = enteredDials.join('');
const data = await getAttendanceCheck({
- studentNumber: Number(enteredDials.join('')),
+ number: numberString,
eventDate,
});
- if (!data) {
+ if (!data || (Array.isArray(data) && data.length === 0)) {
setIsNoMatch(true);
- alert('일치하는 학번이 없습니다');
+ alert('일치하는 정보가 없습니다');
} else if (data.isAlreadyCompleted) {
setIsAlreadyCompleted(true);
alert('이미 출석을 완료하였습니다');
+ } else if (Array.isArray(data) && data.length > 1) {
+ // 동일한 휴대폰 번호 뒷 4자리를 가진 사람이 여러 명인 경우
+ setAttendees(data);
+ setIsModalOpen(true);
} else {
const parsedStudent = {
- name: data.studentName,
- number: data.studentNumber,
- major: data.major,
+ name: data.studentName || data[0].studentName,
+ number: data.studentNumber || data[0].studentNumber,
+ major: data.major || data[0].major,
};
setAttendanceCheck(data);
@@ -71,13 +94,17 @@ const AttendanceStudentIdPage = () => {
} catch (error) {
setEnteredDials([]);
if (error.response && error.response.status === 404) {
- alert('일치하는 학번이 없습니다');
+ alert('일치하는 정보가 없습니다');
} else {
+ console.error('API 에러 발생:', error.response || error);
alert('API 에러 발생');
}
}
} else {
- if (enteredDials.length < 7 && dial !== '서명하러 가기') {
+ if (
+ enteredDials.length < (eventTarget === 'INTERNAL' ? 7 : 4) &&
+ dial !== '서명하러 가기'
+ ) {
setEnteredDials([...enteredDials, dial]);
}
}
@@ -91,8 +118,23 @@ const AttendanceStudentIdPage = () => {
const response = await axiosInstance.get(
`/api/v1/events/${USER_ID}/${EVENT_ID}`,
);
- setEventTitle(response.data.eventTitle);
- setEventDate(response.data.eventSchedules[0].eventDate); // 첫 번째 스케줄의 날짜를 가져옴
+ const eventData = response.data;
+ setEventTitle(eventData.eventTitle);
+ setEventTarget(eventData.eventTarget);
+
+ const now = new Date();
+ const today = now.toISOString().split('T')[0];
+ const todaySchedule = eventData.eventSchedules.find(
+ (schedule) => schedule.eventDate === today,
+ );
+
+ if (todaySchedule) {
+ setEventDate(todaySchedule.eventDate);
+ console.log('출석 체크가 처리되는 날짜:', todaySchedule.eventDate);
+ } else {
+ setEventDate('');
+ console.log('오늘 날짜에 해당하는 일정이 없습니다.');
+ }
} catch (error) {
console.error('이벤트 정보를 가져오는 중 에러 발생:', error);
}
@@ -104,9 +146,13 @@ const AttendanceStudentIdPage = () => {
return (
- 학번을 입력해주세요.
+
+ {eventTarget === 'INTERNAL'
+ ? '학번을 입력해주세요.'
+ : '휴대폰 번호 뒷자리 4자리를 입력해주세요.'}
+
- {studentId.map((index) => (
+ {(eventTarget === 'INTERNAL' ? studentId : phoneId).map((index) => (
{enteredDials[index - 1] || ''}
))}
@@ -132,6 +178,13 @@ const AttendanceStudentIdPage = () => {
{'서명하러 가기'}
+ {isModalOpen && (
+ setIsModalOpen(false)}
+ attendees={attendees}
+ />
+ )}
);
};
diff --git a/src/pages/DashboardPage/DashboardPage.jsx b/src/pages/DashboardPage/DashboardPage.jsx
index 4abfb15..0af44c3 100644
--- a/src/pages/DashboardPage/DashboardPage.jsx
+++ b/src/pages/DashboardPage/DashboardPage.jsx
@@ -257,7 +257,7 @@ export default function DashboardPage() {
담당자
- 해당 연락처로 참석자들에게 문자와 메일이 발송됩니다.
+ 해당 연락처로 참석자 명단이 발송됩니다.