- 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
12 KiB
출석 조회 화면 신규회원 탭 개선 완료
요청 사항
-
신규회원 탭 데이터 표시 개선
- 신규회원 탭도 출석현황처럼 데이터를 보여주기
- 신규회원 리스트를 출석순으로 나열
-
기본 탭 변경
- 현재: 출석현황 탭이 기본 선택
- 변경: 대기 현황 탭이 기본 선택되도록
수정 내용
1. 백엔드 API 수정 - routers/attendance.py
/api/attendance/new-members 엔드포인트 개선
Before (기존):
# 가입일 기준 정렬
new_members = db.query(Member).filter(...).order_by(desc(Member.created_at)).all()
# 최초 출석일만 조회
for member in new_members:
first_attendance = db.query(WaitingList).filter(...).first()
result.append({
"name": member.name,
"phone": member.phone,
"joined_at": member.created_at.strftime("%Y-%m-%d"),
"first_attendance": first_attendance.attended_at.strftime("%Y-%m-%d") if first_attendance else None
})
return {
"count": len(new_members),
"members": result
}
After (개선):
# 가입일 필터링만 (정렬은 나중에)
new_members = db.query(Member).filter(...).all()
result = []
total_attendance = 0
for member in new_members:
# ✅ 출석 횟수 조회
attendance_count = db.query(func.count(WaitingList.id)).filter(
WaitingList.member_id == member.id,
WaitingList.status == 'attended'
).scalar() or 0
# 최초 출석일 조회
first_attendance = db.query(WaitingList).filter(...).first()
# ✅ 최근 출석일 조회
last_attendance = db.query(WaitingList).filter(
WaitingList.member_id == member.id,
WaitingList.status == 'attended'
).order_by(desc(WaitingList.attended_at)).first()
total_attendance += attendance_count
result.append({
"name": member.name,
"phone": member.phone,
"joined_at": member.created_at.strftime("%Y-%m-%d"),
"first_attendance": first_attendance.attended_at.strftime("%Y-%m-%d") if first_attendance else None,
"last_attendance": last_attendance.attended_at.strftime("%Y-%m-%d") if last_attendance else None,
"attendance_count": attendance_count # ✅ 출석 횟수 추가
})
# ✅ 출석순으로 정렬 (출석 횟수가 많은 순)
result.sort(key=lambda x: x['attendance_count'], reverse=True)
# ✅ 평균 출석 횟수 계산
avg_attendance = round(total_attendance / len(new_members), 1) if new_members else 0
return {
"count": len(new_members),
"total_attendance": total_attendance, # ✅ 총 출석 횟수
"avg_attendance": avg_attendance, # ✅ 평균 출석 횟수
"members": result
}
주요 개선 사항:
- ✅ 각 회원의 출석 횟수 조회 및 반환
- ✅ 각 회원의 최근 출석일 조회 및 반환
- ✅ 출석순으로 정렬 (출석 횟수 내림차순)
- ✅ 총 출석 횟수 계산 및 반환
- ✅ 평균 출석 횟수 계산 및 반환
2. 프론트엔드 수정 - templates/attendance.html
2-1. 통계 카드 추가 (lines 331-344)
Before:
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">신규 가입 회원</div>
<div class="stat-value" id="newMemberCount">0명</div>
</div>
</div>
After:
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">신규 가입 회원</div>
<div class="stat-value" id="newMemberCount">0명</div>
</div>
<!-- ✅ 총 출석 횟수 카드 추가 -->
<div class="stat-card">
<div class="stat-label">총 출석 횟수</div>
<div class="stat-value" style="color: #3498db;" id="newMemberTotalAttendance">0회</div>
</div>
<!-- ✅ 평균 출석 횟수 카드 추가 -->
<div class="stat-card">
<div class="stat-label">평균 출석 횟수</div>
<div class="stat-value" style="color: #9b59b6;" id="newMemberAvgAttendance">0회</div>
</div>
</div>
2-2. 테이블 구조 변경 (lines 346-361)
Before:
<table class="data-table">
<thead>
<tr>
<th>가입일시</th>
<th>이름</th>
<th>전화번호</th>
<th>최초 출석일</th>
</tr>
</thead>
<tbody id="newMemberList"></tbody>
</table>
After:
<table class="data-table">
<thead>
<tr>
<th width="80">순위</th> <!-- ✅ 순위 추가 -->
<th>이름</th>
<th>전화번호</th>
<th>출석 횟수</th> <!-- ✅ 출석 횟수 추가 -->
<th>가입일</th>
<th>최초 출석일</th>
<th>최근 출석일</th> <!-- ✅ 최근 출석일 추가 -->
</tr>
</thead>
<tbody id="newMemberList"></tbody>
</table>
2-3. loadNewMembers() 함수 개선 (lines 658-693)
Before:
async function loadNewMembers() {
const period = document.getElementById('newMemberPeriod').value;
const date = document.getElementById('newMemberDate').value;
const response = await fetch(`/api/attendance/new-members?period=${period}&date=${date}`, { headers: getHeaders() });
const data = await response.json();
document.getElementById('newMemberCount').textContent = `${data.count}명`;
const tbody = document.getElementById('newMemberList');
tbody.innerHTML = data.members.map(m => `
<tr>
<td>${m.joined_at}</td>
<td>${m.name}</td>
<td>${m.phone}</td>
<td>${m.first_attendance || '-'}</td>
</tr>
`).join('');
}
After:
async function loadNewMembers() {
const period = document.getElementById('newMemberPeriod').value;
const date = document.getElementById('newMemberDate').value;
const response = await fetch(`/api/attendance/new-members?period=${period}&date=${date}`, { headers: getHeaders() });
const data = await response.json();
// ✅ 통계 데이터 표시
document.getElementById('newMemberCount').textContent = `${data.count}명`;
document.getElementById('newMemberTotalAttendance').textContent = `${data.total_attendance}회`;
document.getElementById('newMemberAvgAttendance').textContent = `${data.avg_attendance}회`;
const tbody = document.getElementById('newMemberList');
tbody.innerHTML = data.members.map((m, index) => {
// ✅ 순위 배지 스타일 적용
let rankClass = 'rank-other';
if (index === 0) rankClass = 'rank-1';
if (index === 1) rankClass = 'rank-2';
if (index === 2) rankClass = 'rank-3';
return `
<tr>
<td><span class="rank-badge ${rankClass}">${index + 1}</span></td>
<td>${m.name}</td>
<td>${m.phone}</td>
<td><strong>${m.attendance_count}회</strong></td> <!-- ✅ 출석 횟수 -->
<td>${m.joined_at}</td>
<td>${m.first_attendance || '-'}</td>
<td>${m.last_attendance || '-'}</td> <!-- ✅ 최근 출석일 -->
</tr>
`;
}).join('');
}
2-4. 기본 탭 변경 (lines 169, 177, 221)
탭 버튼 (line 169):
<!-- Before -->
<button class="tab-btn" onclick="switchTab('waiting_status')">대기현황</button>
<button class="tab-btn active" onclick="switchTab('status')">출석현황</button>
<!-- After -->
<button class="tab-btn active" onclick="switchTab('waiting_status')">대기현황</button> <!-- ✅ active 추가 -->
<button class="tab-btn" onclick="switchTab('status')">출석현황</button> <!-- ✅ active 제거 -->
탭 컨텐츠 (lines 177, 221):
<!-- Before -->
<div id="waiting_statusTab" class="tab-content"> <!-- 대기현황 탭 -->
<div id="statusTab" class="tab-content active"> <!-- 출석현황 탭 -->
<!-- After -->
<div id="waiting_statusTab" class="tab-content active"> <!-- ✅ active 추가 -->
<div id="statusTab" class="tab-content"> <!-- ✅ active 제거 -->
변경 사항 요약
신규회원 탭 개선
| 항목 | Before | After |
|---|---|---|
| 통계 카드 | 신규 가입 회원 수만 표시 | 신규 가입 회원, 총 출석 횟수, 평균 출석 횟수 표시 |
| 테이블 컬럼 | 가입일시, 이름, 전화번호, 최초 출석일 | 순위, 이름, 전화번호, 출석 횟수, 가입일, 최초 출석일, 최근 출석일 |
| 정렬 기준 | 가입일 기준 (최신순) | 출석 횟수 기준 (많은 순) ✅ |
| 순위 표시 | 없음 | 1-3위는 금/은/동 배지, 나머지는 일반 배지 |
기본 탭 변경
| 항목 | Before | After |
|---|---|---|
| 기본 선택 탭 | 출석현황 | 대기 현황 ✅ |
결과
신규회원 탭 화면
통계 표시:
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ 신규 가입 회원 │ │ 총 출석 횟수 │ │ 평균 출석 횟수 │
│ 15명 │ │ 45회 │ │ 3.0회 │
└────────────────┘ └────────────────┘ └────────────────┘
리스트 표시 (출석순 정렬):
┌────┬────────┬──────────────┬──────────┬────────────┬──────────────┬──────────────┐
│순위│ 이름 │ 전화번호 │출석 횟수 │ 가입일 │ 최초 출석일 │ 최근 출석일 │
├────┼────────┼──────────────┼──────────┼────────────┼──────────────┼──────────────┤
│ 🥇 │ 홍길동 │ 010-1234-5678│ 10회 │ 2025-11-01 │ 2025-11-02 │ 2025-12-03 │
│ 🥈 │ 김철수 │ 010-2345-6789│ 8회 │ 2025-11-05 │ 2025-11-06 │ 2025-12-01 │
│ 🥉 │ 이영희 │ 010-3456-7890│ 7회 │ 2025-11-10 │ 2025-11-11 │ 2025-11-30 │
│ 4 │ 박민수 │ 010-4567-8901│ 5회 │ 2025-11-15 │ 2025-11-16 │ 2025-11-28 │
│ 5 │ 최지은 │ 010-5678-9012│ 3회 │ 2025-11-20 │ 2025-11-21 │ 2025-11-25 │
└────┴────────┴──────────────┴──────────┴────────────┴──────────────┴──────────────┘
기본 탭
화면 진입 시 대기 현황 탭이 자동으로 선택됩니다.
┌────────────────────────────────────────────────────┐
│ [대기현황*] [출석현황] [개인별 출석] [신규회원] [출석순위] │
└────────────────────────────────────────────────────┘
영향 범위
수정된 파일
- ✅
routers/attendance.py-/api/attendance/new-members엔드포인트 - ✅
templates/attendance.html- 신규회원 탭 UI 및 기본 탭 설정
영향받는 기능
- ✅ 출석 조회 > 신규회원 탭
- 통계 데이터 표시
- 출석순 정렬
- 순위 배지 표시
- ✅ 출석 조회 화면 진입 시 기본 탭
영향받지 않는 기능
- ✅ 대기 현황 탭
- ✅ 출석현황 탭
- ✅ 개인별 출석 탭
- ✅ 출석순위 탭
결론
신규회원 탭이 출석현황 탭처럼 풍부한 데이터를 보여줍니다:
- ✅ 통계 카드 3개 표시 (신규 회원 수, 총 출석, 평균 출석)
- ✅ 출석순으로 정렬 (출석 횟수가 많은 순)
- ✅ 순위 표시 (1-3위는 금/은/동 배지)
- ✅ 상세 정보 표시 (출석 횟수, 가입일, 최초/최근 출석일)
- ✅ 기본 탭이 "대기 현황"으로 변경
신규회원의 출석 활동을 한눈에 파악할 수 있습니다!