Skip to content

Commit 92ac900

Browse files
authored
Merge pull request #1732 from coderdojo-japan/enable-to-donwload-dojo-stats-yearly
📥 Enable to download Dojo stats as `CSV` for specific year
2 parents 3d972da + a447999 commit 92ac900

File tree

13 files changed

+1399
-37
lines changed

13 files changed

+1399
-37
lines changed

app/assets/stylesheets/custom.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,3 +1072,10 @@ section {
10721072
line-height: 1.7em;
10731073
color: #909090;
10741074
}
1075+
1076+
/* Stats and Dojos table styling */
1077+
.stats-table {
1078+
td.inactive-item {
1079+
background-color: gainsboro;
1080+
}
1081+
}

app/controllers/dojos_controller.rb

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,95 @@
11
class DojosController < ApplicationController
22

3-
# GET /dojos[.json]
3+
# GET /dojos[.html|.json|.csv]
44
def index
5+
# yearパラメータがある場合の処理
6+
if params[:year].present?
7+
begin
8+
year = params[:year].to_i
9+
# 有効な年の範囲をチェック
10+
unless year.between?(2012, Date.current.year)
11+
flash[:inline_alert] = "指定された年は無効です。2012年から#{Date.current.year}年の間で指定してください。"
12+
return redirect_to dojos_path(anchor: 'table')
13+
end
14+
15+
@selected_year = year
16+
year_end = Time.zone.local(@selected_year).end_of_year
17+
18+
# その年末時点でアクティブだった道場を取得
19+
dojos_scope = Dojo.active_at(year_end)
20+
@page_title = "#{@selected_year}年末時点のCoderDojo一覧"
21+
rescue ArgumentError
22+
flash[:inline_alert] = "無効な年が指定されました"
23+
return redirect_to dojos_path(anchor: 'table')
24+
end
25+
else
26+
# yearパラメータなしの場合(既存の実装そのまま)
27+
dojos_scope = Dojo.all
28+
end
29+
530
@dojos = []
6-
Dojo.includes(:prefecture).order(order: :asc).all.each do |dojo|
31+
dojos_scope.includes(:prefecture).order(is_active: :desc, order: :asc).each do |dojo|
32+
# 年が選択されている場合は、その年末時点でのアクティブ状態を判定
33+
# 選択されていない場合は、現在の is_active を使用
34+
is_active_at_selected_time = if @selected_year
35+
# その年末時点でアクティブだったかを判定
36+
# inactivated_at が nil(まだアクティブ)または選択年より後に非アクティブ化
37+
dojo.inactivated_at.nil? || dojo.inactivated_at > Time.zone.local(@selected_year).end_of_year
38+
else
39+
dojo.is_active
40+
end
41+
742
@dojos << {
843
id: dojo.id,
944
url: dojo.url,
1045
name: dojo.name,
1146
logo: root_url + dojo.logo[1..],
1247
order: dojo.order,
1348
counter: dojo.counter,
14-
is_active: dojo.is_active,
49+
is_active: is_active_at_selected_time,
1550
prefecture: dojo.prefecture.name,
1651
created_at: dojo.created_at,
1752
description: dojo.description,
53+
inactivated_at: dojo.inactivated_at, # CSV用に追加
1854
}
1955
end
56+
57+
# counter合計を計算(/statsとの照合用)
58+
@counter_sum = @dojos.sum { |d| d[:counter] }
59+
60+
# 情報メッセージを設定
61+
if @selected_year
62+
# /statsページと同じ計算方法を使用
63+
# 開設数 = その年に新規開設されたDojoのcounter合計
64+
year_begin = Time.zone.local(@selected_year).beginning_of_year
65+
year_end = Time.zone.local(@selected_year).end_of_year
66+
new_dojos_count = Dojo.where(created_at: year_begin..year_end).sum(:counter)
67+
68+
# 合計数 = その年末時点でアクティブだったDojoのcounter合計
69+
total_dojos_count = Dojo.active_at(year_end).sum(:counter)
70+
71+
# 表示用の日付テキスト
72+
display_date = "#{@selected_year}年末"
73+
display_date = Date.current.strftime('%Y年%-m月%-d日') if @selected_year == Date.current.year
74+
75+
flash.now[:inline_info] = "#{display_date}時点のアクティブな道場を表示中<br>(開設数: #{new_dojos_count} / 合計数: #{total_dojos_count})".html_safe
76+
else
77+
# 全期間表示時の情報メッセージ
78+
flash.now[:inline_info] = "全期間の道場を表示中(非アクティブ含む)"
79+
end
2080

2181
respond_to do |format|
22-
# No corresponding View for now.
23-
# Only for API: GET /dojos.json
24-
format.html # => app/views/dojos/index.html.erb
82+
format.html { render :index } # => app/views/dojos/index.html.erb
2583
format.json { render json: @dojos }
84+
format.csv do
85+
# ファイル名を年に応じて設定
86+
filename = if @selected_year
87+
"dojos_#{@selected_year}.csv"
88+
else
89+
"dojos_all.csv"
90+
end
91+
send_data render_to_string, type: :csv, filename: filename
92+
end
2693
end
2794
end
2895

app/helpers/application_helper.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ def page_lang(lang)
4848
lang.empty? ? 'ja' : lang
4949
end
5050

51+
# 'inline_' プレフィックスがついたflashメッセージをビュー内で表示するヘルパー
52+
# inline_alert → alert, inline_warning → warning のように変換してBootstrapのCSSクラスを適用
53+
def render_inline_flash_messages
54+
flash.select { |type, _| type.to_s.start_with?('inline_') }.map do |type, message|
55+
css_class = type.to_s.gsub('inline_', '')
56+
content_tag(:div, message, class: "alert alert-#{css_class}", style: "margin-bottom: 15px;")
57+
end.join.html_safe
58+
end
59+
5160
def kata_description
5261
"道場で役立つ資料やコンテスト情報、立ち上げ方や各種支援をまとめています。"
5362
end

app/views/dojos/index.csv.ruby

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
require 'csv'
2+
3+
csv_data = CSV.generate do |csv|
4+
# ヘッダー行
5+
# 選択年に応じて状態カラムのヘッダーを変更
6+
status_header = if @selected_year
7+
if @selected_year == Date.current.year
8+
"状態 (#{Date.current.strftime('%Y年%-m月%-d日')}時点)"
9+
else
10+
"状態 (#{@selected_year}年末時点)"
11+
end
12+
else
13+
'状態'
14+
end
15+
16+
# 全期間の場合のみ閉鎖日カラムを追加
17+
if @selected_year
18+
csv << ['ID', '道場名', '道場数', '都道府県', 'URL', '設立日', status_header]
19+
else
20+
csv << ['ID', '道場名', '道場数', '都道府県', 'URL', '設立日', status_header, '閉鎖日']
21+
end
22+
23+
# データ行
24+
@dojos.each do |dojo|
25+
row = [
26+
dojo[:id],
27+
dojo[:name],
28+
dojo[:counter],
29+
dojo[:prefecture],
30+
dojo[:url],
31+
dojo[:created_at].strftime("%F"),
32+
dojo[:is_active] ? 'アクティブ' : '非アクティブ'
33+
]
34+
35+
# 全期間の場合のみ閉鎖日を追加
36+
if !@selected_year
37+
row << (dojo[:inactivated_at] ? dojo[:inactivated_at].strftime("%F") : '')
38+
end
39+
40+
csv << row
41+
end
42+
end

app/views/dojos/index.html.erb

Lines changed: 84 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,49 @@
1818
<br>
1919
<div class='form__terms list'>
2020
<ul style='list-style-type: "\2713\0020"; font-size: smaller;'>
21-
<li>現在は活動停止中 (In-active) の道場も表示されています</li>
22-
<li>道場名をクリックすると個別の統計データが確認できます</li>
23-
<li>下記表は <code><%= link_to dojos_path(format: :json), dojos_path(format: :json) %></code> で JSON に変換できます</li>
21+
<li>道場名をクリックすると、その道場の個別の統計データを確認できます</li>
22+
<li>対象期間を選ぶと、その期間のアクティブな道場の一覧を表示できます</li>
23+
<li>全期間の場合のみ、すべての道場(非アクティブ含む)を表示できます</li>
2424
</ul>
2525
</div>
26+
<div style="text-align: center; margin-top: 10px; margin-bottom: 50px;">
27+
<a href="/stats" style="color: #007bff; text-decoration: none;">
28+
&raquo; 推移グラフで見る
29+
</a>
30+
</div>
2631
</p>
32+
33+
<!-- 年次データを取得する -->
34+
<div style="margin: 30px auto; padding: 20px; background: #f8f9fa; border-radius: 8px; max-width: 600px;">
35+
<h3 id="table">
36+
<a href="#table">📊</a>
37+
年次データを取得する
38+
</h3>
39+
40+
<%= render_inline_flash_messages %>
41+
42+
<div style="display: flex; align-items: center; justify-content: center; gap: 10px; margin-top: 15px;">
43+
<%= label_tag :year, '対象期間:', style: 'font-weight: bold;' %>
44+
<%= select_tag :year,
45+
options_for_select(
46+
(2012..Date.current.year).to_a.reverse.map { |y| [y.to_s + '年', y] },
47+
params[:year]
48+
),
49+
include_blank: '全期間',
50+
onchange: "window.location.href = this.value ? '#{dojos_path}?year=' + this.value + '#table' : '#{dojos_path}#table'",
51+
style: 'padding: 5px; border: 1px solid #ced4da; border-radius: 4px; cursor: pointer;' %>
52+
53+
<%= link_to 'CSV', dojos_path(format: :csv, year: params[:year]),
54+
style: 'padding: 5px 15px; background: #28a745; color: white; text-decoration: none; border-radius: 4px;' %>
55+
56+
<%= link_to 'JSON', dojos_path(format: :json, year: params[:year]),
57+
style: 'padding: 5px 15px; background: #6c757d; color: white; text-decoration: none; border-radius: 4px;' %>
58+
</div>
59+
60+
<p style="font-size: smaller; color: #6c757d; margin-top: 15px;">
61+
対象期間を選択すると、その時点のアクティブな道場の一覧を表示・ダウンロードできます。
62+
</p>
63+
</div>
2764

2865
<style type="text/css">
2966
/* URL 用のセルにクラスを付けておく想定 */
@@ -47,7 +84,7 @@
4784
</style>
4885

4986
<div style="margin-top: 20px;" align="center">
50-
<table border="1">
87+
<table border="1" class="stats-table">
5188
<tr>
5289
<th>
5390
<small>
@@ -73,26 +110,49 @@
73110
</tr>
74111
<% @dojos.each do |dojo| %>
75112
<tr>
76-
<td>
77-
<small>
78-
<%= link_to dojo_path(dojo[:id]) do %>
79-
<%= dojo[:name] %><br>
80-
<small>(ID: <%= dojo[:id] %>)</small>
81-
<% end %>
82-
</small>
83-
</td>
84-
<td>
85-
<small><%= dojo[:created_at].strftime("%F") %></small>
86-
</td>
87-
<td class="url-cell">
88-
<small>
89-
<a href='<%= dojo[:url] %>'>
90-
<span title="<%= dojo[:url] %>">
91-
<%= CGI.unescape dojo[:url].gsub('https://', '').gsub('http://', '').gsub('www.', '').chomp('/') %>
92-
</span>
93-
</a>
94-
</small>
95-
</td>
113+
<% if dojo[:is_active] %>
114+
<td>
115+
<small>
116+
<%= link_to dojo_path(dojo[:id]) do %>
117+
<%= dojo[:name] %><br>
118+
<small>(ID: <%= dojo[:id] %>)</small>
119+
<% end %>
120+
</small>
121+
</td>
122+
<td>
123+
<small><%= dojo[:created_at].strftime("%F") %></small>
124+
</td>
125+
<td class="url-cell">
126+
<small>
127+
<a href='<%= dojo[:url] %>'>
128+
<span title="<%= dojo[:url] %>">
129+
<%= truncate(CGI.unescape(dojo[:url].gsub('https://', '').gsub('http://', '').gsub('www.', '').chomp('/')), length: 30) %>
130+
</span>
131+
</a>
132+
</small>
133+
</td>
134+
<% else %>
135+
<td class="inactive-item">
136+
<small>
137+
<%= link_to dojo_path(dojo[:id]) do %>
138+
<%= dojo[:name] %><br>
139+
<small>(ID: <%= dojo[:id] %>)</small>
140+
<% end %>
141+
</small>
142+
</td>
143+
<td class="inactive-item">
144+
<small><%= dojo[:created_at].strftime("%F") %></small>
145+
</td>
146+
<td class="url-cell inactive-item">
147+
<small>
148+
<a href='<%= dojo[:url] %>'>
149+
<span title="<%= dojo[:url] %>">
150+
<%= truncate(CGI.unescape(dojo[:url].gsub('https://', '').gsub('http://', '').gsub('www.', '').chomp('/')), length: 30) %>
151+
</span>
152+
</a>
153+
</small>
154+
</td>
155+
<% end %>
96156
</tr>
97157
<% end %>
98158
</table>

app/views/layouts/application.html.erb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,11 @@
8888
<body>
8989
<%= render 'shared/header' %>
9090

91+
<%# 'inline_' プレフィックスがついたflashメッセージは、ここでは表示せず、各ビュー内でカスタム表示する %>
9192
<% flash.each do |message_type, message| %>
92-
<div class="alert alert-<%= message_type %>"><%= message %></div>
93+
<% unless message_type.to_s.start_with?('inline_') %>
94+
<div class="alert alert-<%= message_type %>"><%= message %></div>
95+
<% end %>
9396
<% end %>
9497

9598
<%= yield %>

app/views/stats/show.html.erb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323
</div>
2424
<div style="margin-top: 20px;">
2525
<% if @lang == 'en' %>
26-
<a href="/stats">&raquo; Switch to Japanese</a>
26+
<a href="/stats">&raquo; Switch to Japanese</a> / <a href="/dojos">&raquo; View Annual Data</a>
2727
<% else %>
28-
<a href="/english/stats">&raquo; View in English</a>
28+
<a href="/english/stats">&raquo; View in English</a> / <a href="/dojos">&raquo; 年次データを見る</a>
2929
<% end %>
3030
</div>
3131

@@ -361,7 +361,7 @@
361361
</div>
362362
</h2>
363363
<div style="margin-top: 20px;" align="center">
364-
<table border="1">
364+
<table border="1" class="stats-table">
365365
<tr>
366366
<th style="padding: 10px;">
367367
<small><%= @lang == 'en' ? 'Prefecture' : '都道府県名' %></small>
@@ -373,10 +373,10 @@
373373
<% @data_by_prefecture.each_with_index do |(prefecture, count), index| %>
374374
<tr>
375375
<% if count == 0 %>
376-
<td style="background-color: gainsboro; padding: 0px;">
376+
<td class="inactive-item" style="padding: 0px;">
377377
<small><%= prefecture %></small>
378378
</td>
379-
<td style="background-color: gainsboro; padding: 0px;">
379+
<td class="inactive-item" style="padding: 0px;">
380380
<small><%= count %></small>
381381
</td>
382382
<% else %>

db/dojos.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3084,7 +3084,6 @@
30843084
order: '272124'
30853085
created_at: '2022-11-18'
30863086
name: 八尾
3087-
counter: 2
30883087
prefecture_id: 27
30893088
logo: "/img/dojos/yao-yotteco.webp"
30903089
url: https://www.facebook.com/profile.php?id=100087493006122

0 commit comments

Comments
 (0)