
import {Layout, Select, Button } from 'antd';
Layout 컴포넌트Layout 컴포넌트는 페이지의 기본 구조를 구성하는 데 사용. 이를 통해 웹 애플리케이션의 레이아웃을 쉽게 정의.Layout 컴포넌트는 여러 서브 컴포넌트(Header, Footer, Sider, Content)로 나뉘어 있어, 페이지의 상단, 하단, 사이드바, 본문 영역을 각각 쉽게 설정.Select 컴포넌트Select 컴포넌트는 사용자가 여러 옵션 중 하나를 선택할 수 있도록 하는 드롭다운 메뉴를 제공.Option 서브 컴포넌트를 사용하여 각각의 선택 항목을 정의.Button 컴포넌트Button 컴포넌트는 사용자가 클릭할 수 있는 버튼을 생성.primary, default, dashed, link 등)의 버튼을 제공하여, 버튼의 스타일을 쉽게 변경.const [selectedYear, setselectedYear ] = useState(new Data().getFullYear());
React의 useState 훅을 사용하여 상태값을 생성하고 초기화.
useState 훅useState는 React에서 상태값을 관리하기 위해 사용하는 훅(Hook). 이 훅은 배열을 반환하며, 배열의 첫 번째 요소는 현재 상태값이고, 두 번째 요소는 그 상태값을 업데이트할 수 있는 함수입니다.
React 훅은 기본적으로 함수 컴포넌트에서 상태를 관리하거나 부수효과(side effect)를 처리하기 위해 사용. 주로 사용되는 두 가지 주요 훅은 useState와 useEffect.
selectedYearselectedYear는 현재 연도를 저장하는 상태값. 이 값은 컴포넌트가 처음 렌더링될 때 new Date().getFullYear()에 의해 초기화.new Date().getFullYear()는 현재 날짜의 연도를 숫자 형식으로 반환. 예를 들어, 2024년이라면 2024가 반환.setSelectedYearsetSelectedYear는 selectedYear 상태를 업데이트하기 위해 사용되는 함수.selectedYear 상태를 업데이트.setSelectedYear(2025)와 같이 호출하여 상태를 2025로 변경.useEffect는 React에서 컴포넌트가 렌더링될 때나 특정 상태나 props가 변경될 때 실행되는 사이드 이펙트를 처리하기 위해 사용되는 훅(Hook).
useEffect의 기본 개념useEffect는 다음과 같은 경우에 코드를 실행할 수 있도록 함.
useEffect의 역할 및 구조useEffect는 두 가지 인수를 받습니다:
1. 첫 번째 인수: 실행할 함수. 이 함수는 지정된 의존성(dependencies)이 변경될 때마다 호출.
2. 두 번째 인수: 의존성 배열. 이 배열 안에 나열된 값들이 변경될 때마다 useEffect 안의 함수가 다시 실행. 이 배열을 생략하면 컴포넌트가 렌더링될 때마다 useEffect가 실행. 빈 배열([])을 전달하면 컴포넌트가 처음 마운트될 때만 실행.
AttendancePage의 useEffect 설명useEffect(() => {
fetchStudents().then(setStudents);
fetchAttendanceData(selectedYear).then(setAttendanceData);
}, [selectedYear]);
이 useEffect는 다음과 같은 역할을 한다.
컴포넌트가 처음 렌더링될 때 실행:
fetchStudents() 함수가 호출되어 학생 데이터를 가져온다. 이 함수가 데이터를 성공적으로 가져오면 setStudents 함수가 호출되어 students 상태가 업데이트.fetchAttendanceData(selectedYear) 함수가 호출되어 선택된 연도의 출석 데이터를 가져온다. 이 함수가 데이터를 성공적으로 가져오면 setAttendanceData 함수가 호출되어 attendanceData 상태가 업데이트.selectedYear 상태가 변경될 때 실행:
selectedYear 상태가 업데이트되며, 이로 인해 useEffect가 다시 실행. fetchAttendanceData(selectedYear) 함수가 다시 호출되고, 해당 연도의 출석 데이터를 가져와 attendanceData 상태를 업데이트.useEffect의 기능 요약AttendancePage 컴포넌트가 처음 렌더링될 때, 학생 목록과 초기 연도의 출석 데이터를 가져온다.selectedYear 의존성 배열에 포함되어 있어, 이 값이 변경될 때마다 useEffect 내부의 코드가 다시 실행됩니다.useEffect의 주의점useEffect의 두 번째 인수로 전달된 의존성 배열에 포함된 값이 변경될 때만 useEffect가 다시 실행. 예를 들어, 이 코드에서는 selectedYear가 변경될 때만 출석 데이터를 새로 가져온다.fetchStudents()와 fetchAttendanceData(selectedYear)는 비동기 함수로, 데이터를 가져오는 동안 컴포넌트는 비동기적으로 동작하며 상태가 업데이트.(axios로 전환, 동기)<Select> 컴포넌트<Select> 컴포넌트는 사용자가 여러 옵션 중에서 하나를 선택할 수 있는 드롭다운 메뉴를 제공. 이 컴포넌트는 Ant Design 라이브러리에서 제공하는 UI 컴포넌트.
defaultValue={selectedYear}: defaultValue 속성은 드롭다운이 처음 렌더링될 때 기본으로 선택된 옵션을 설정. 여기서는 selectedYear 상태 값이 기본 선택으로 설정됩니다.
onChange={(value) => setselectedYear(value)}: onChange 속성은 사용자가 드롭다운에서 다른 옵션을 선택했을 때 실행될 함수를 지정. 여기서는 사용자가 연도를 선택하면 setSelectedYear(value) 함수가 호출되어 selectedYear 상태가 업데이트. 이때 value는 사용자가 선택한 연도.
<Option> 요소들: 이 컴포넌트는 [2022, 2023, 2024] 배열을 순회하며 각각의 연도에 대해 <Option> 요소를 생성. <Option> 요소는 key와 value 속성을 가지며, 사용자가 선택할 수 있는 개별 옵션을 표시. 예를 들어, 사용자가 2023년을 선택하면 2023이 selectedYear로 설정.
selectedYear 상태로 저장..useEffect 훅이 실행되어, 새로운 연도에 대한 출석 데이터를 가져온다.모달 창 크기 조정 필요:
디자인 목표:
모달 크기 및 높이 조정:
.modal-form의 max-height 값을 조정하여 학생 목록의 최대 높이를 400px로 설정하고, 스크롤을 추가..modal-form {
max-height: 400px; /* 학생 목록의 최대 높이 */
overflow-y: auto; /* 학생 목록에만 스크롤 추가 */
margin-bottom: 20px; /* 학생 목록과 하단 버튼 사이의 간격 */
padding-right: 10px; /* 스크롤바와 내용 간의 간격 */
}
버튼 스크롤에서 제외:
position: sticky와 bottom: 0 속성을 추가하여 취소 및 등록 버튼이 항상 하단에 고정되도록 수정.background-color를 추가하여 버튼 영역이 고정되면서 가려지지 않도록 함..modal-footer {
display: flex;
justify-content: space-between;
margin-top: 30px;
position: sticky;
bottom: 0;
background-color: white; /* 고정 시 버튼 영역이 가려지지 않도록 배경색 추가 */
padding-top: 10px;
}
최종 CSS 코드:
.custom-modal {
border-radius: 12px;
padding: 20px;
}
.modal-header {
text-align: center;
margin-bottom: 30px;
}
.modal-header h2 {
font-size: 18px;
font-weight: bold;
}
.modal-subtitle {
font-size: 14px;
color: #757575;
}
.modal-form {
max-height: 400px;
overflow-y: auto;
margin-bottom: 20px;
padding-right: 10px;
}
.form-item {
background-color: #F0F4FF;
border-radius: 8px;
padding: 8px;
margin-bottom: 10px;
}
.checkbox {
width: 100%;
}
.modal-footer {
display: flex;
justify-content: space-between;
margin-top: 30px;
position: sticky;
bottom: 0;
background-color: white;
padding-top: 10px;
}
.cancel-btn {
background-color: #F5F5F5;
border: none;
color: #757575;
width: 100px;
height: 40px;
font-size: 16px;
border-radius: 8px;
}
.submit-btn {
background-color: #1890ff;
border: none;
color: white;
width: 100px;
height: 40px;
font-size: 16px;
border-radius: 8px;
}
출석부 모달 기능 구현:
useState)를 통해 체크된 학생 목록을 동적으로 관리하고, 모달을 열고 닫는 동작을 구현.문제:
해결 과정:
Ant Design Modal 컴포넌트 사용:
Modal 컴포넌트를 사용하여 모달을 구현.useState를 통해 isModalOpen으로 관리하며, 모달을 열고 닫는 함수(openModal, closeModal)를 각각 정의.학생 체크박스 구현:
Checkbox 컴포넌트를 사용하여 학생 목록을 출력하고, 각각의 학생을 체크할 수 있도록 구현.checkedStudents 배열로 체크된 학생 목록을 관리하며, handleCheck 함수로 체크 여부를 동적으로 관리.등록 및 취소 버튼 구현:
onCancel로 모달을 닫는 기능을 수행하고, 등록 버튼은 선택된 데이터를 콘솔에 출력한 후 모달을 닫도록 구현.모달 컴포넌트 분리:
AttendanceModal.js)로 분리하여 유지보수성을 높임.모달 관련 주요 코드:
// AttendancePage.js
const [isModalOpen, setIsModalOpen] = useState(false);
const [checkedStudents, setCheckedStudents] = useState([]);
const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false);
const handleCheck = (studentId) => {
setCheckedStudents(prevChecked =>
prevChecked.includes(studentId)
? prevChecked.filter(id => id !== studentId)
: [...prevChecked, studentId]
);
};
const handleSubmit = () => {
console.log('출석부 등록 완료:', checkedStudents);
setIsModalOpen(false);
};
<AttendanceModal
isOpen={isModalOpen}
onClose={closeModal}
students={students}
checkedStudents={checkedStudents}
handleCheck={handleCheck}
handleSubmit={handleSubmit}
/>
AttendanceModal 컴포넌트:
// AttendanceModal.js
const AttendanceModal = ({ isOpen, onClose, students, checkedStudents, handleCheck, handleSubmit }) => {
return (
<Modal
open={isOpen}
onCancel={onClose}
footer={null}
destroyOnClose={true}
>
<div className="modal-header">
<h2>출석부 등록</h2>
<p className="modal-subtitle">등록일: {new Date().toLocaleDateString()}</p>
</div>
<Form layout="vertical" className="modal-form">
{students.map(student => (
<Form.Item key={student.id} className="form-item">
<Checkbox
checked={checkedStudents.includes(student.id)}
onChange={() => handleCheck(student.id)}
>
{student.name}
</Checkbox>
</Form.Item>
))}
<div className="modal-footer">
<Button onClick={onClose}>취소</Button>
<Button type="primary" onClick={handleSubmit}>등록</Button>
</div>
</Form>
</Modal>
);
};
문제:
해결 과정:
Ant Design Table 컴포넌트 사용:
Table 컴포넌트를 사용.✅ 또는 ❌로 표시.가로 스크롤 추가:
Table의 scroll 속성을 사용하여 가로 스크롤을 추가.scroll={{ x: 'max-content' }} 속성을 사용해 테이블이 화면 크기에 맞지 않으면 가로 스크롤이 생성되도록 설정.학생 목록 고정:
fixed: 'left'와 fixed: 'right' 속성을 추가.AttendanceChart 관련 주요 코드:
// AttendanceChart.js
const columns = useMemo(() => [
{
title: '학생명',
dataIndex: 'name',
key: 'name',
fixed: 'left',
},
...data.map(dateEntry => ({
title: dateEntry.date,
dataIndex: dateEntry.date,
key: dateEntry.date,
render: attendance => (
<span>{attendance ? '✅' : '❌'}</span>
),
})),
{
title: '정보',
key: 'info',
fixed: 'right',
render: () => <a href="#">보기</a>,
}
], [data]);
<Table
columns={columns}
dataSource={dataSource}
pagination={false}
scroll={{ x: 'max-content' }} // 가로 스크롤 추가
sticky
/>