백로그
0할 일
0진행 중
0리뷰
0완료 (15일)
1월별 랭킹 미세 조정 구현
## 목표 랭킹 페이지 UI/UX 미세 조정: 월 선택, 뱃지 개선, 본인 하이라이트, 빈 상태 ## 구현 내용 ### 1. app/controllers/qt/sessions_controller.rb - rankings 액션 수정 현재 period="all"/"month" 기반 → month 파라미터 추가: ```ruby def rankings theme = @session.qt_theme participant_user_ids = @session.qt_participants.pluck(:user_id) users = User.where(id: participant_user_ids) @period = params[:period] || "all" @selected_month = params[:month] # "2026-03" 형식 @rankings = users.map do |user| meditations = user.user_meditations.joins(:qt_content).where(qt_contents: { qt_theme_id: theme.id }) if @period == "month" if @selected_month.present? begin date = Date.parse("#{@selected_month}-01") meditations = meditations.where(meditation_date: date.beginning_of_month..date.end_of_month) rescue Date::Error meditations = meditations.where("user_meditations.meditation_date >= ?", Date.current.beginning_of_month) end else meditations = meditations.where("user_meditations.meditation_date >= ?", Date.current.beginning_of_month) end end completed = meditations.where.not(personal_meditation: [nil, ""]) shared = meditations.where(is_personal_meditation_shared: true) tongtok = meditations.where(is_tongtok_completed: true) { user: user, meditation_count: meditations.count, completed_count: completed.count, shared_count: shared.count, tongtok_count: tongtok.count, score: completed.count * 3 + shared.count * 2 + tongtok.count } end.sort_by { |r| -r[:score] } # 월 선택을 위한 가용 월 목록 (세션 시작부터 현재까지) @available_months = if @session.start_date.present? start = @session.start_date.beginning_of_month months = [] current = Date.current.beginning_of_month while start <= current months << current.strftime("%Y-%m") current = current.prev_month.beginning_of_month end months else [Date.current.strftime("%Y-%m")] end end ``` ### 2. app/views/qt/sessions/rankings.html.erb 전체 교체 ```erb <div class="max-w-2xl mx-auto px-4 py-6 space-y-6"> <%# 헤더 %> <div class="flex items-center gap-3"> <%= link_to qt_session_path(@session), class: "text-text-secondary hover:text-text-primary transition-colors" do %> <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/> </svg> <% end %> <h1 class="text-heading font-bold text-text-primary"><%= @session.title %> 랭킹</h1> </div> <%# 기간 필터 %> <div class="flex items-center gap-2 flex-wrap"> <%= link_to "전체", rankings_qt_session_path(@session, period: "all"), class: "px-4 py-2 text-small font-medium rounded-lg transition-colors #{@period == 'all' ? 'bg-brand-primary text-white' : 'bg-surface-subtle text-text-secondary hover:bg-gray-200 dark:hover:bg-gray-600'}" %> <%= link_to "이번 달", rankings_qt_session_path(@session, period: "month"), class: "px-4 py-2 text-small font-medium rounded-lg transition-colors #{@period == 'month' && @selected_month.blank? ? 'bg-brand-primary text-white' : 'bg-surface-subtle text-text-secondary hover:bg-gray-200 dark:hover:bg-gray-600'}" %> <%# 월 선택 드롭다운 %> <% if @available_months.size > 1 %> <select onchange="if(this.value) window.location.href=this.value" class="px-3 py-2 text-small font-medium rounded-lg bg-surface-subtle text-text-secondary border-0 focus:ring-2 focus:ring-brand-primary"> <option value="">월 선택</option> <% @available_months.each do |month| %> <% month_label = Date.parse("#{month}-01").strftime("%Y년 %m월") %> <option value="<%= rankings_qt_session_path(@session, period: "month", month: month) %>" <%= "selected" if @selected_month == month %>> <%= month_label %> </option> <% end %> </select> <% end %> </div> <%# 랭킹 목록 %> <% if @rankings.empty? || @rankings.all? { |r| r[:score] == 0 } %> <%= render "shared/empty_state", title: "아직 랭킹 데이터가 없습니다", description: "묵상을 완료하면 랭킹에 반영됩니다.", action_text: "오늘의 묵상하기", action_path: qt_today_path(session_id: @session.id) %> <% else %> <%= render "shared/card", padding: :md do %> <div class="space-y-1"> <% @rankings.each_with_index do |ranking, index| %> <% rank = index + 1 %> <% is_me = ranking[:user].id == current_user.id %> <div class="flex items-center gap-3 p-3 rounded-lg <%= if is_me 'bg-brand-primary/10 dark:bg-brand-primary/20 ring-1 ring-brand-primary/30' elsif rank <= 3 'bg-amber-50/80 dark:bg-amber-900/20' end %>"> <%# 순위 %> <div class="w-8 text-center shrink-0"> <% if rank == 1 %> <span class="text-xl">🥇</span> <% elsif rank == 2 %> <span class="text-xl">🥈</span> <% elsif rank == 3 %> <span class="text-xl">🥉</span> <% else %> <span class="text-body font-bold text-text-secondary"><%= rank %></span> <% end %> </div> <%# 아바타 + 이름 %> <%= render "shared/avatar", name: ranking[:user].nickname, size: :sm %> <div class="flex-1 min-w-0"> <div class="flex items-center gap-2"> <span class="text-body font-medium text-text-primary truncate"><%= ranking[:user].nickname %></span> <% if is_me %> <%= render "shared/badge", text: "나", variant: :info %> <% end %> </div> <div class="flex gap-3 text-caption text-text-muted mt-0.5"> <span>묵상 <%= ranking[:completed_count] %></span> <span>공유 <%= ranking[:shared_count] %></span> <span>통독 <%= ranking[:tongtok_count] %></span> </div> </div> <%# 점수 %> <div class="text-right shrink-0"> <span class="text-subheading font-bold text-brand-primary"><%= ranking[:score] %></span> <p class="text-caption text-text-muted">점</p> </div> </div> <% end %> </div> <% end %> <%# 점수 계산 안내 %> <%= render "shared/card", padding: :md do %> <h3 class="text-small font-medium text-text-secondary mb-2">점수 계산</h3> <div class="flex gap-4 text-caption text-text-muted"> <span>묵상 완료 ×3</span> <span>공유 ×2</span> <span>통독 ×1</span> </div> <% end %> <% end %> </div> ``` ### 3. 핵심 변경 요약 1. **월 선택**: @available_months + select 드롭다운 (세션 시작월~현재) 2. **본인 하이라이트**: `is_me` 체크 → brand-primary/10 배경 + ring + "나" 뱃지 3. **다크모드 뱃지**: `dark:bg-amber-900/20` 추가 4. **빈 상태**: shared/empty_state 파셜 사용 (score 0인 경우도 포함) ### 4. 테스트 추가 test/controllers/qt/sessions_controller_test.rb에 추가: ```ruby test "should get rankings with specific month" do sign_in @daniel get rankings_qt_session_path(@active_session, period: "month", month: Date.current.strftime("%Y-%m")) assert_response :success end ``` ## 완료 기준 - [ ] 월 선택 드롭다운 동작 - [ ] 본인 순위 하이라이트 (배경색 + "나" 뱃지) - [ ] 다크모드 대응 (amber, brand 배경) - [ ] 빈 데이터 시 empty_state 파셜 - [ ] 기존 테스트 + 새 테스트 통과 (bin/rails test) ## 담당 파일 - app/controllers/qt/sessions_controller.rb (rankings 액션 수정) - app/views/qt/sessions/rankings.html.erb (전체 교체) - test/controllers/qt/sessions_controller_test.rb (테스트 추가)