はじめに
各カテゴリごとにタスクを分け、それらの進捗度を円形プログレスバーで表示する機能と、カレンダー機能を融合させたタスク管理アプリを作成していきます。
開発環境
・windows11
・ruby3.1.6
・rails7.2.2.1
基本的なアプリケーション構造
保存するカラム
・タスク名
・タスク詳細
・期限
・完了状態
・カテゴリ(今回seedに保存)→今回は大学、サークル、バイト の3つのカテゴリで分けて作成していきます。
実装手順
任意のアプリを作成
rails new アプリ名
cd アプリ名
モデルの作成
今回、Categoryモデル/Taskモデルを使用
rails g model Category name:string
rails g model Task title:string description:text due_date:date completed:boolean category:references
rails db:migrate
シードデータの作成(カテゴリの作成)
Category.create([
{ name: '大学' },
{ name: 'サークル' },
{ name: 'バイト' }
])
その後コマンドプロンプトで下記を実行
rails db:seed
モデル間のアソシエーションの設定
class Category < ApplicationRecord
has_many :tasks
end
class Task < ApplicationRecord
belongs_to :category
validates :title, presence: true
end
コントローラーの作成
rails g controller Tasks index new create edit update destroy
ルーティングの設定
Rails.application.routes.draw do
root 'tasks#index'
resources :tasks
end
タスク一覧表示とカテゴリ別達成度の計算
class TasksController < ApplicationController
def index
@tasks = Task.includes(:category).all
end
def new
@task = Task.new
@categories = Category.all
end
def create
@task = Task.new(task_params)
if @task.save
redirect_to tasks_path, notice: 'タスクが作成されました。'
else
@categories = Category.all
render :new
end
end
def edit
@task = Task.find(params[:id])
@categories = Category.all
end
def update
@task = Task.find(params[:id])
if @task.update(task_params)
redirect_to tasks_path, notice: 'タスクが更新されました。'
else
@categories = Category.all
render :edit
end
end
def destroy
@task = Task.find(params[:id])
@task.destroy
redirect_to tasks_path, notice: 'タスクが削除されました。'
end
private
def task_params
params.require(:task).permit(:title, :description, :due_date, :completed, :category_id)
end
end
ビューの作成
<h1>タスク一覧</h1>
<%= link_to '新規タスク作成', new_task_path %>
<% @tasks.group_by(&:category).each do |category, tasks| %>
<h2><%= category.name %></h2>
<ul>
<% tasks.each do |task| %>
<li>
<%= task.title %> -
<%= task.completed ? '完了' : '未完了' %> -
期限: <%= task.due_date %>
<%= link_to '編集', edit_task_path(task) %>
<%= button_to '削除', task_path(task), method: :delete, data: { confirm: '本当に削除しますか?' } %>
</li>
<% end %>
</ul>
<% end %>
<h1><%= @task.new_record? ? '新規タスク作成' : 'タスク編集' %></h1>
<%= form_with(model: @task, local: true) do |form| %>
<div>
<%= form.label :タスク名 %>
<%= form.text_field :title %>
</div>
<div>
<%= form.label :詳細 %>
<%= form.text_area :description %>
</div>
<div>
<%= form.label :締切日 %>
<%= form.date_field :due_date %>
</div>
<div>
<%= form.label :タスクのカテゴリ %>
<%= form.collection_select :category_id, @categories, :id, :name %>
</div>
<%= form.submit %>
<% end %>
<%= link_to '戻る', tasks_path %>
<h1><%= @task.new_record? ? '新規タスク作成' : 'タスク編集' %></h1>
<%= form_with(model: @task, local: true) do |form| %>
<div>
<%= form.label :タスク名 %>
<%= form.text_field :title %>
</div>
<div>
<%= form.label :詳細 %>
<%= form.text_area :description %>
</div>
<div>
<%= form.label :締切日 %>
<%= form.date_field :due_date %>
</div>
<div>
<%= form.label :カテゴリ %>
<%= form.collection_select :category_id, @categories, :id, :name %>
</div>
<div>
<%= form.label :タスクが完了したらチェック! %>
<%= form.check_box :completed %>
</div>
<%= form.submit %>
<% end %>
<%= link_to '戻る', tasks_path %>
ここまで無事に実装ができると簡易的なタスク管理アプリが出来上がります。
現状できることはこちらです。確認してみてください。
・新規タスク作成ができる
・タスクを追加すると、indexに ”タスク名 - 未完了 - 期限: 2025-〇-✕ 編集 削除” この形式でカテゴリごとにタスクが追加される
・編集を押してタスク編集へ遷移し、”タスクが完了したらチェック!” にチェックを付け、Update Taskを押す。一覧へ戻へ再び遷移、チェックを付けたことで、表示が “未完了→完了” に変化する
無事にできていればここまでのコーディングはクリアです!
次は、ここまでの機能を応用し、それぞれのタスクの進捗度を追加する機能を実装していきます。
Taskモデルに進捗度を計算するメソッドを追加する
class Task < ApplicationRecord
belongs_to :category
validates :title, presence: true
def progress
completed ? 100 : 0
end
end
class ApplicationController < ActionController::Base
#下の記述を追記
helper_method :calculate_progress
private
def calculate_progress(tasks)
return 0 if tasks.empty?
(tasks.count(&:completed).to_f / tasks.count * 100).round
end
end
進捗度計算機能をビューに追加
<h1>タスク一覧</h1>
<%= link_to '新規タスク作成', new_task_path %>
<h2>全体の進捗度</h2>
<p><%= number_to_percentage(@overall_progress, precision: 0) %></p>
<% @category_progress.each do |category_name, progress| %>
<h2><%= category_name %></h2>
<p>カテゴリー進捗度: <%= number_to_percentage(progress, precision: 0) %></p>
<ul>
<% @tasks.select { |task| task.category.name == category_name }.each do |task| %>
<li>
<%= task.title %> -
進捗度: <%= number_to_percentage(task.progress, precision: 0) %> -
<%= task.completed ? '完了' : '未完了' %> -
期限: <%= task.due_date %>
<%= link_to '編集', edit_task_path(task) %>
<%= button_to '削除', task_path(task), method: :delete, data: { confirm: '本当に削除しますか?' } %>
</li>
<% end %>
</ul>
<% end %>
これによりタスク管理アプリに全体/各カテゴリ の進捗度が追加されました
・タスクを追加し、編集からチェックボックスにチェックをつけると、indexの進捗度が変化する
・各カテゴリの進捗度が増減することで、全体の進捗度も自動的に更新される
次は、この真直度表示を円形プログレスバーで表示する仕様にするため、デザインを施していきます。
※なお、円形プログレスバーでの表示にはjsを使用しますが、記述がうまくできずhtmlファイルにjsの記述をしています。
円形プログレスバーの実装
<h1>タスク一覧</h1>
<%= link_to '新規タスク作成', new_task_path %>
<div class="pro_all">
<h2>全体の進捗度</h2>
<p><%= number_to_percentage(@overall_progress, precision: 0) %></p>
<svg class="progress-circle" width="100" height="100" data-progress="<%= @overall_progress %>">
<circle class="background" cx="50" cy="50" r="40"></circle>
<circle class="progress" cx="50" cy="50" r="40"></circle>
</svg>
</div>
<div class="pro_contents">
<% @category_progress.each do |category_name, progress| %>
<div class="pro_content">
<h2><%= category_name %></h2>
<p>カテゴリー進捗度: <%= number_to_percentage(progress, precision: 0) %></p>
<svg class="progress-circle" width="100" height="100" data-progress="<%= progress %>">
<circle class="background" cx="50" cy="50" r="40"></circle>
<circle class="progress" cx="50" cy="50" r="40"></circle>
</svg>
<br>
<%= link_to 'タスクを表示', new_task_path %>
</div>
<% end %>
</div>
<script>
function updateProgressCircles() {
document.querySelectorAll(".progress-circle").forEach(circle => {
let progress = circle.dataset.progress;
let progressCircle = circle.querySelector(".progress");
let radius = progressCircle.r.baseVal.value;
let circumference = 2 * Math.PI * radius;
progressCircle.style.strokeDasharray = circumference;
progressCircle.style.strokeDashoffset = circumference * (1 - progress / 100);
});
}
// Turbo Drive (TurboLinks) に対応
document.addEventListener("turbo:load", updateProgressCircles);
document.addEventListener("DOMContentLoaded", updateProgressCircles);
</script>
<style>
.progress-circle {
transform: rotate(-90deg);
}
circle {
fill: none;
stroke-width: 8;
}
.background {
stroke: #ddd;
}
.progress {
stroke: #4CAF50;
stroke-linecap: round;
transition: stroke-dashoffset 1s ease-in-out;
}
</style>
これにより円形プログレスバーで%表示が行えるようになりました。
続いては、カレンダー機能を導入し、カレンダーを用いたタスク管理アプリに仕上げていきます。
なお、カレンダー機能は https://qiita.com/isaatsu0131/items/ad1d0a6130fe4fd339d0 こちらを参考に作成していきます。
カレンダー機能を導入するためのGemを追加
gem 'simple_calendar', '~> 2.0'
その後コマンドプロンプトに
bundle install
コントローラーのindexアクションを修正
def index
@tasks = Task.includes(:category).all
@overall_progress = calculate_progress(@tasks)
@category_progress = Category.all.map { |category| [category.name, calculate_progress(category.tasks)] }.to_h
end
ビューにカレンダーを追加
かなり長いので、追記する場合はよーく見てね!
<p id="notice"><%= notice %></p>
<div class="index_h1">
<h1><span>Task</span>eru!</h1>
</div>
<div class="pro_all">
<h2>全体の進捗度</h2>
<p><%= number_to_percentage(@overall_progress, precision: 0) %></p>
<svg class="progress-circle" width="100" height="100" data-progress="<%= @overall_progress %>">
<circle class="background" cx="50" cy="50" r="40"></circle>
<circle class="progress" cx="50" cy="50" r="40"></circle>
</svg>
</div>
<div class="pro_contents">
<% @category_progress.each do |category_name, progress| %>
<div class="pro_content">
<h2><%= category_name %></h2>
<p>カテゴリー進捗度: <%= number_to_percentage(progress, precision: 0) %></p>
<svg class="progress-circle" width="100" height="100" data-progress="<%= progress %>">
<circle class="background" cx="50" cy="50" r="40"></circle>
<circle class="progress" cx="50" cy="50" r="40"></circle>
</svg>
<br>
<%= link_to 'タスクを表示', new_task_path %> #こちらは(仮)リンクで現状newに繋がっています
</div>
<% end %>
</div>
<script>
function updateProgressCircles() {
document.querySelectorAll(".progress-circle").forEach(circle => {
let progress = circle.dataset.progress;
let progressCircle = circle.querySelector(".progress");
let radius = progressCircle.r.baseVal.value;
let circumference = 2 * Math.PI * radius;
progressCircle.style.strokeDasharray = circumference;
progressCircle.style.strokeDashoffset = circumference * (1 - progress / 100);
});
}
// Turbo Drive (TurboLinks) に対応
document.addEventListener("turbo:load", updateProgressCircles);
document.addEventListener("DOMContentLoaded", updateProgressCircles);
</script>
<style>
.progress-circle {
transform: rotate(-90deg);
}
circle {
fill: none;
stroke-width: 8;
}
.background {
stroke: #ddd;
}
.progress {
stroke: #4CAF50;
stroke-linecap: round;
transition: stroke-dashoffset 1s ease-in-out;
}
</style>
<div class="index_h1">
<h1>カレンダー</h1>
<h2><%= link_to 'タスクを追加', new_task_path %></h2>
</div>
<% require 'ostruct' %>
<%= month_calendar events: @tasks.map { |task| OpenStruct.new(id: task.id, start_time: task.due_date, title: task.title, completed: task.completed) } do |date, tasks| %>
<%= date.day %>
<% tasks.each do |task| %>
<div>
<%= link_to task.title, edit_task_path(task.id) %> -
<%= task.completed ? '完了' : '未完了' %>
</div>
<% end %>
<% end %>
<br>
<br>
<br>
<br>
カレンダーの見た目を整えるためちょっと手入れ
rails g simple_calendar:views
/*
*= require simple_calendar #ここに追記
*= require_tree .
*= require_self
*/
機能自体は完了ですが、ここでrials s & リロードすると、LoadErrorが発生する場合があるので、こちらで対策します
bundle add sassc
bundle install
サイトを見てみましょう!実際にサイトを使用してみながら、細かい点は修正してみてね!
※おまけ
軽くcssを施して、円形プログレスバーの配置を整える
.progress-bar {
width: 100%;
background-color: #f0f0f0;
border-radius: 10px;
}
.progress {
width: 50%; /* 進捗率に応じて変更 */
background-color: #3498db;
height: 20px;
border-radius: 10px;
}
h1 span{
color: red;
}
.index_h1 {
text-align: center;
}
.pro_all{
text-align: center;
}
.pro_contents{
display: flex;
justify-content: center;
}
.pro_content{
margin: 0px 40px;
text-align: center;
}
無事に実装できたでしょうか?ぜひいろいろアレンジして自分なりのアプリにしてみてください!!
おわりに
がんばってみてね~^^