- Add main application files (main.py, models.py, schemas.py, etc.) - Add routers for all features (waiting, attendance, members, etc.) - Add HTML templates for admin and user interfaces - Add migration scripts and utility files - Add Docker configuration - Add documentation files - Add .gitignore to exclude database and cache files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
12 KiB
개발 일지 - 교시 마감 시스템 구현
날짜: 2025-11-28 개발자: Claude (AI Assistant) 작업 시간: 약 2시간
📋 작업 개요
일괄 출석 기능을 교시 마감 시스템으로 전환하여, 마감된 교시에는 더 이상 대기자를 등록할 수 없도록 구현했습니다.
🎯 요구사항
사용자가 요청한 기능:
- 교시별 마감 기능: 일괄 출석 대신 교시 마감으로 동작 변경
- 대기자 등록 차단: 마감된 교시에는 더 이상 대기자 등록 불가
- 대기자 리스트 유지: 마감 후에도 대기자 리스트는 그대로 표시 (비활성화 상태)
- 시각적 표시:
- 탭 색상을 빨강으로 변경
- 인원수 옆에 "마감" 텍스트 표시
- 왼쪽 화살표 비활성화: 마감된 교시 오른쪽의 대기자는 왼쪽 화살표 비활성화
🔨 구현 내용
1. 데이터베이스 변경
새로운 테이블 추가: class_closure
class ClassClosure(Base):
"""교시 마감 정보"""
__tablename__ = "class_closure"
id = Column(Integer, primary_key=True, index=True)
business_date = Column(Date, nullable=False, index=True)
class_id = Column(Integer, ForeignKey("class_info.id"), nullable=False)
closed_at = Column(DateTime, default=datetime.now)
created_at = Column(DateTime, default=datetime.now)
파일: models.py (98-106번 줄)
마이그레이션 스크립트
- 파일:
migrate_add_class_closure.py - 실행 결과: ✅ 마이그레이션 성공
2. 백엔드 API 수정
2.1 교시 마감 엔드포인트 (/api/board/batch-attendance)
파일: routers/waiting_board.py (394-450번 줄)
변경 전: 모든 대기자를 attended 상태로 변경 변경 후: ClassClosure 레코드 생성, 대기자 상태는 waiting 유지
@router.post("/batch-attendance")
async def batch_attendance(batch: BatchAttendance, db: Session = Depends(get_db)):
"""교시 마감 처리"""
# 이미 마감된 교시인지 확인
existing_closure = db.query(ClassClosure).filter(...).first()
if existing_closure:
raise HTTPException(status_code=400, detail="이미 마감된 교시입니다.")
# 교시 마감 레코드 생성
closure = ClassClosure(
business_date=today,
class_id=batch.class_id,
closed_at=datetime.now()
)
db.add(closure)
db.commit()
# SSE 브로드캐스트
await sse_manager.broadcast(
store_id="default",
event_type="class_closed", # 이벤트 타입 변경
data={...}
)
2.2 다음 마감 대상 조회 (/api/board/next-batch-class)
파일: routers/waiting_board.py (452-492번 줄)
마감된 교시를 제외한 첫 번째 대기자 있는 교시를 반환하도록 수정
# 이미 마감된 교시 ID 목록
closed_class_ids = db.query(ClassClosure.class_id).filter(
ClassClosure.business_date == today
).all()
closed_class_ids = set(c.class_id for c in closed_class_ids)
for cls in classes:
if cls.id in closed_class_ids:
continue # 마감된 교시는 건너뜀
# ...
2.3 마감된 교시 목록 조회 (/api/board/closed-classes)
파일: routers/waiting_board.py (494-507번 줄)
새로운 엔드포인트 추가 - 오늘 마감된 교시 ID 목록 반환
@router.get("/closed-classes")
async def get_closed_classes(db: Session = Depends(get_db)):
"""오늘 마감된 교시 목록 조회"""
today = date.today()
closed_classes = db.query(ClassClosure).filter(
ClassClosure.business_date == today
).all()
return {"closed_class_ids": [c.class_id for c in closed_classes]}
2.4 대기자 등록 차단
파일: routers/waiting.py (27-95번 줄)
get_available_class() 함수 수정 - 마감된 교시를 배치 대상에서 제외
def get_available_class(db: Session, business_date: date):
"""배치 가능한 클래스 찾기 - 마감된 교시 제외"""
# 마감된 교시 ID 목록 조회
closed_class_ids = db.query(ClassClosure.class_id).filter(
ClassClosure.business_date == business_date
).all()
closed_class_ids = set(c.class_id for c in closed_class_ids)
# 마감되지 않은 교시만 필터링
available_classes = [c for c in classes if c.id not in closed_class_ids]
if not available_classes:
raise HTTPException(
status_code=400,
detail="모든 교시가 마감되었습니다. 대기 접수를 받을 수 없습니다."
)
# ...
3. 프론트엔드 UI 수정
3.1 CSS 스타일 변경
파일: templates/manage.html (74-136번 줄)
변경 전: 완료된 교시는 회색 (#95a5a6)
변경 후: 마감된 교시는 빨강 (#e74c3c)
/* 마감된 교시 탭 스타일 - 빨강 */
.class-tab.closed {
background: #e74c3c;
color: #fff;
border-color: #c0392b;
opacity: 0.9;
}
/* 마감된 교시의 리스트 스타일 - 비활성화 상태로 표시 */
.waiting-table.closed {
background: #f5f5f5;
opacity: 0.8;
}
.waiting-table.closed::before {
content: '🔒 마감된 교시입니다';
display: block;
padding: 15px;
background: #e74c3c;
color: #fff;
text-align: center;
font-weight: 600;
font-size: 14px;
}
3.2 JavaScript 로직 수정
파일: templates/manage.html
주요 변경 사항:
- 변수명 변경:
completedClasses→closedClasses - 초기 로드 시 마감된 교시 조회 (774-816번 줄):
async function loadClasses() {
// 마감된 교시 목록 조회
const closedResponse = await fetch('/api/board/closed-classes');
const closedData = await closedResponse.json();
closedClasses = new Set(closedData.closed_class_ids);
// ...
}
- 탭 렌더링 수정 (813-833번 줄):
function renderClassTabs() {
classes.forEach(cls => {
const isClosed = closedClasses.has(cls.id);
tab.className = isClosed ? 'class-tab closed' : 'class-tab';
// 마감된 교시는 인원수와 "마감" 배지 표시
tab.innerHTML = `
${cls.class_name}
<span class="count">${cls.current_count || 0}명</span>
${isClosed ? '<span class="badge-closed">마감</span>' : ''}
`;
});
}
- SSE 이벤트 핸들러 수정 (506-515번 줄):
case 'class_closed': // 이벤트 타입 변경
closedClasses.add(message.data.class_id);
updateClassCounts();
loadBatchInfo();
if (currentClassId === message.data.class_id) {
updateWaitingOrder();
}
break;
- 왼쪽 화살표 비활성화 (967-1019번 줄):
// 왼쪽에 마감된 교시가 있는지 확인
let hasClosedClassOnLeft = false;
if (hasPrevClass) {
const prevClass = classes[classIndex - 1];
hasClosedClassOnLeft = closedClasses.has(prevClass.id);
}
// 왼쪽 화살표 비활성화 조건
const leftArrowDisabled = !hasPrevClass || hasClosedClassOnLeft;
div.innerHTML = `
...
<button class="btn-icon btn-left"
${leftArrowDisabled ? 'disabled' : ''}
onclick="moveToClass(${item.id}, ${classIndex - 1})"
title="이전 교시로 이동">
←
</button>
...
`;
- 마감 확인 다이얼로그 (1292-1319번 줄):
async function batchAttendance() {
if (!confirm(`${batchClass.class_name}을(를) 마감하시겠습니까?\n마감 후 해당 교시에는 더 이상 대기자를 등록할 수 없습니다.`)) return;
// ...
}
- 대기자 리스트 표시 로직 (688-722번 줄):
async function updateWaitingOrder() {
// 마감된 교시도 대기 목록 표시 (비활성화 상태로)
const isClosed = closedClasses.has(currentClassId);
const status = 'waiting'; // 마감된 교시도 waiting 상태 유지
const response = await fetch(`/api/waiting/list?status=${status}&class_id=${currentClassId}`);
// ...
}
✅ 테스트 항목
기능 테스트
- 교시 마감 버튼 동작
- 마감된 교시 탭 빨강색 표시
- "마감" 배지 표시
- 마감된 교시의 대기자 리스트 비활성화 표시
- 마감된 교시에 대기자 등록 차단
- 왼쪽 화살표 비활성화 (마감된 교시 우측)
- 이미 마감된 교시 중복 마감 방지
- SSE 실시간 업데이트
- 페이지 새로고침 시 마감 상태 유지
UI/UX 테스트
- 마감 확인 다이얼로그 표시
- 대기자 리스트 그대로 유지 (비활성화 상태)
- 드래그 앤 드롭 비활성화 (마감된 교시)
- 모든 버튼 비활성화 (마감된 교시)
📊 변경 파일 목록
신규 파일
migrate_add_class_closure.py- 마이그레이션 스크립트
수정 파일
models.py- ClassClosure 모델 추가routers/waiting_board.py- 교시 마감 API 구현routers/waiting.py- 대기자 등록 차단 로직templates/manage.html- UI 및 JavaScript 로직 전면 수정
영향받는 파일 (수정 없음)
schemas.py- BatchAttendance 스키마 재사용sse_manager.py- SSE 이벤트 타입만 변경database.py- 변경 없음
🐛 발견된 문제 및 해결
문제 1: 모듈 import 오류
증상: ModuleNotFoundError: No module named 'fastapi'
원인: 가상환경 활성화 없이 서버 실행
해결: source venv/bin/activate 후 실행
문제 2: 없음
초기 설계가 명확했고, 요구사항이 구체적이어서 추가 문제 없이 구현 완료
📝 코드 품질
장점
- ✅ 기존 코드 구조 유지하며 최소한의 변경
- ✅ 명확한 변수명과 함수명 사용
- ✅ 일관된 코딩 스타일 유지
- ✅ 충분한 주석과 docstring 작성
- ✅ 에러 핸들링 적절히 구현
개선 가능한 부분
- ⚠️ 마감 취소 기능 미구현 (향후 필요시 추가)
- ⚠️ 마감 이력 조회 기능 미구현
- ⚠️ 마감 시간 설정 기능 미구현 (현재는 즉시 마감만 가능)
🎓 배운 점
-
상태 관리의 중요성:
- 프론트엔드에서
closedClassesSet을 사용하여 효율적으로 마감 상태 추적 - 초기 로드 시 서버에서 마감 상태 동기화
- 프론트엔드에서
-
점진적 기능 전환:
- 기존 일괄 출석 기능을 완전히 대체하지 않고, 엔드포인트는 유지하며 동작만 변경
- UI 텍스트만 변경하여 사용자 혼란 최소화
-
실시간 동기화:
- SSE 이벤트 타입만 변경하여 실시간 업데이트 유지
- 여러 화면 간 상태 동기화 보장
🚀 향후 개선 방향
- 마감 취소 기능: 실수로 마감한 경우 취소 가능하도록
- 마감 이력: 언제 누가 마감했는지 이력 추적
- 자동 마감: 특정 시간에 자동으로 교시 마감
- 마감 알림: 마감 전 관리자에게 알림 발송
- 마감 통계: 일별/월별 마감 현황 통계
📌 참고사항
- 데이터베이스: SQLite 사용
- 실시간 통신: Server-Sent Events (SSE) 사용
- 프론트엔드: Vanilla JavaScript (프레임워크 없음)
- 백엔드: FastAPI + SQLAlchemy
✨ 결론
교시 마감 시스템이 성공적으로 구현되었습니다. 모든 요구사항이 충족되었으며, 기존 시스템과의 호환성을 유지하면서 새로운 기능이 추가되었습니다. 사용자는 이제 교시별로 마감 처리를 할 수 있으며, 마감된 교시에는 더 이상 대기자가 등록되지 않습니다.