Files
waiting-system/출석조회_신규회원탭_개선완료.md
Jun-dev f699a29a85 Add waiting system application files
- 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>
2025-12-14 00:29:39 +09:00

331 lines
12 KiB
Markdown

# 출석 조회 화면 신규회원 탭 개선 완료
## 요청 사항
1. **신규회원 탭 데이터 표시 개선**
- 신규회원 탭도 출석현황처럼 데이터를 보여주기
- 신규회원 리스트를 출석순으로 나열
2. **기본 탭 변경**
- 현재: 출석현황 탭이 기본 선택
- 변경: 대기 현황 탭이 기본 선택되도록
## 수정 내용
### 1. 백엔드 API 수정 - [routers/attendance.py](routers/attendance.py:310-360)
#### `/api/attendance/new-members` 엔드포인트 개선
**Before (기존):**
```python
# 가입일 기준 정렬
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 (개선):**
```python
# 가입일 필터링만 (정렬은 나중에)
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](templates/attendance.html)
#### 2-1. 통계 카드 추가 (lines 331-344)
**Before:**
```html
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">신규 가입 회원</div>
<div class="stat-value" id="newMemberCount">0명</div>
</div>
</div>
```
**After:**
```html
<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:**
```html
<table class="data-table">
<thead>
<tr>
<th>가입일시</th>
<th>이름</th>
<th>전화번호</th>
<th>최초 출석일</th>
</tr>
</thead>
<tbody id="newMemberList"></tbody>
</table>
```
**After:**
```html
<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:**
```javascript
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:**
```javascript
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):**
```html
<!-- 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):**
```html
<!-- 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 │
└────┴────────┴──────────────┴──────────┴────────────┴──────────────┴──────────────┘
```
### 기본 탭
화면 진입 시 **대기 현황 탭**이 자동으로 선택됩니다.
```
┌────────────────────────────────────────────────────┐
│ [대기현황*] [출석현황] [개인별 출석] [신규회원] [출석순위] │
└────────────────────────────────────────────────────┘
```
## 영향 범위
### 수정된 파일
1.`routers/attendance.py` - `/api/attendance/new-members` 엔드포인트
2.`templates/attendance.html` - 신규회원 탭 UI 및 기본 탭 설정
### 영향받는 기능
- ✅ 출석 조회 > 신규회원 탭
- 통계 데이터 표시
- 출석순 정렬
- 순위 배지 표시
- ✅ 출석 조회 화면 진입 시 기본 탭
### 영향받지 않는 기능
- ✅ 대기 현황 탭
- ✅ 출석현황 탭
- ✅ 개인별 출석 탭
- ✅ 출석순위 탭
## 결론
**신규회원 탭이 출석현황 탭처럼 풍부한 데이터를 보여줍니다:**
1. ✅ 통계 카드 3개 표시 (신규 회원 수, 총 출석, 평균 출석)
2. ✅ 출석순으로 정렬 (출석 횟수가 많은 순)
3. ✅ 순위 표시 (1-3위는 금/은/동 배지)
4. ✅ 상세 정보 표시 (출석 횟수, 가입일, 최초/최근 출석일)
5. ✅ 기본 탭이 "대기 현황"으로 변경
**신규회원의 출석 활동을 한눈에 파악할 수 있습니다!**