Add status, note, project panel

This commit is contained in:
Chris Veleris 2023-11-27 15:09:09 +02:00
parent 19584ecb1a
commit 2398d2278e
24 changed files with 396 additions and 151 deletions

View file

@ -91,7 +91,7 @@ puma -C app/config/puma.rb
Pull the latest image:
```bash
docker pull chrisvel/tududi:0.13
docker pull chrisvel/tududi:0.14
```
In order to start the docker container you need 3 enviromental variables:
@ -117,7 +117,7 @@ TUDUDI_SESSION_SECRET
-e TUDUDI_SESSION_SECRET=3337c138d17ac7acefa412e5db0d7ef6540905b198cc28c5bf0d11e48807a71bdfe48d82ed0a0a6eb667c937cbdd1db3e1e6073b3148bff37f73cc6398a39671 \
-v ~/tududi_db:/usr/src/app/tududi_db \
-p 9292:9292 \
-d chrisvel/tududi:0.13
-d chrisvel/tududi:0.14
```
3. Navigate to https://localhost:9292 and fill in your email and password.

30
app.rb
View file

@ -10,6 +10,7 @@ require './app/models/tag'
require './app/models/note'
require './app/helpers/authentication_helper'
require './app/helpers/task_helper'
require './app/routes/authentication_routes'
require './app/routes/tasks_routes'
@ -46,6 +47,8 @@ before do
require_login
end
helpers TaskHelper
helpers do
def current_path
request.path_info
@ -55,16 +58,6 @@ helpers do
erb page, options.merge!(layout: false)
end
def priority_class(task)
return 'text-success' if task.completed
case task.priority
when 'Medium' then 'text-warning'
when 'High' then 'text-danger'
else 'text-secondary'
end
end
def nav_link_active?(path, query_params = {}, project_id = nil)
current_uri = request.path_info
current_query = request.query_string
@ -92,23 +85,6 @@ helpers do
classes
end
def order_name(order_by)
return 'Select' unless order_by
field, direction = order_by.split(':')
name = case field
when 'due_date' then 'Due Date'
when 'name' then 'Name'
when 'priority' then 'Priority'
when 'created_at' then 'Created At'
else 'Select'
end
direction_icon = direction == 'asc' ? '<i class="bi bi-arrow-up"></i>' : '<i class="bi bi-arrow-down"></i>'
"#{name} #{direction_icon}"
end
def update_query_params(key, value)
uri = URI(request.url)
params = Rack::Utils.parse_nested_query(uri.query)

View file

@ -0,0 +1,74 @@
module TaskHelper
def priority_class(task)
return 'text-success' if task.done?
case task.priority
when 'medium'
'text-warning'
when 'high'
'text-danger'
else
'text-secondary'
end
end
def due_date_badge_class(due_date)
return 'bg-light text-dark' unless due_date
case due_date.to_date
when Date.today
'bg-primary'
when Date.tomorrow
'bg-info'
when Date.yesterday..Date.today
'bg-danger'
else
'bg-light text-dark'
end
end
def format_due_date(due_date)
return '' unless due_date
case due_date.to_date
when Date.today
'TODAY'
when Date.tomorrow
'TOMORROW'
when Date.yesterday
'YESTERDAY'
else
due_date.strftime('%Y-%m-%d')
end
end
def status_badge_class(status)
case status
when 'not_started'
'bg-warning-subtle text-warning'
when 'in_progress'
'bg-primary-subtle text-primary'
when 'done'
'bg-success-subtle text-success'
else
'bg-secondary-subtle text-secondary'
end
end
def order_name(order_by)
return 'Select' unless order_by
field, direction = order_by.split(':')
name = case field
when 'due_date' then 'Due Date'
when 'name' then 'Name'
when 'priority' then 'Priority'
when 'created_at' then 'Created At'
else 'Select'
end
direction_icon = direction == 'asc' ? '<i class="bi bi-arrow-up"></i>' : '<i class="bi bi-arrow-down"></i>'
"#{name} #{direction_icon}"
end
end

View file

@ -4,8 +4,25 @@ class Project < ActiveRecord::Base
has_many :tasks, dependent: :destroy
has_many :notes, dependent: :destroy
scope :with_incomplete_tasks, -> { joins(:tasks).where(tasks: { completed: false }).distinct }
scope :with_complete_tasks, -> { joins(:tasks).where(tasks: { completed: true }).distinct }
scope :with_incomplete_tasks, -> { joins(:tasks).where.not(tasks: { status: Task.statuses[:done] }).distinct }
scope :with_complete_tasks, -> { joins(:tasks).where(tasks: { status: Task.statuses[:done] }).distinct }
validates :name, presence: true
def task_status_counts
{
total: tasks.count,
in_progress: tasks.where(status: Task.statuses[:in_progress]).count,
done: tasks.where(status: Task.statuses[:done]).count,
not_started: tasks.where(status: Task.statuses[:not_started]).count
}
end
def progress_percentage
counts = task_status_counts
return 0 if counts[:total].zero?
completed_tasks = counts[:total] - counts[:not_started]
(completed_tasks.to_f / counts[:total] * 100).round
end
end

View file

@ -3,8 +3,11 @@ class Task < ActiveRecord::Base
belongs_to :project, optional: true
has_and_belongs_to_many :tags
scope :complete, -> { where(completed: true) }
scope :incomplete, -> { where(completed: false) }
enum priority: { low: 0, medium: 1, high: 2 }
enum status: { not_started: 0, in_progress: 1, done: 2, archived: 3 }
scope :complete, -> { where(status: statuses[:done]) }
scope :incomplete, -> { where.not(status: statuses[:done]) }
validates :name, presence: true
end

View file

@ -16,7 +16,16 @@ class Sinatra::Application
get '/notes' do
order_by = params[:order_by] || 'title:asc'
order_column, order_direction = order_by.split(':')
@notes = current_user.notes.includes(:tags).order("#{order_column} #{order_direction}")
@notes = current_user.notes.includes(:tags)
@notes = @notes.joins(:tags).where(tags: { name: params[:tag] }) if params[:tag]
@notes = @notes.order("notes.#{order_column} #{order_direction}")
query_params = Rack::Utils.parse_nested_query(request.query_string)
query_params.delete('tag')
@base_query = query_params.to_query
@base_url = '/notes?'
@base_url += "#{@base_query}&" unless @base_query.empty?
erb :'notes/index'
end

View file

@ -1,6 +1,7 @@
class Sinatra::Application
get '/projects' do
@projects_with_tasks = current_user.projects.includes(:tasks, :area).order('name ASC')
@projects_with_tasks = current_user.projects.includes(:tasks, :area).order('areas.name ASC, projects.name ASC')
@grouped_projects = @projects_with_tasks.group_by(&:area)
erb :'projects/index'
end

View file

@ -15,34 +15,36 @@ module Sinatra
end
get '/tasks' do
@tasks = current_user.tasks.includes(:project, :tags)
# Base query with necessary joins
base_query = current_user.tasks.includes(:project, :tags)
case params[:due_date]
when 'today'
today = Date.today
@tasks = @tasks.incomplete.where('due_date <= ?', today.end_of_day)
when 'upcoming'
one_week_from_today = Date.today + 7.days
@tasks = @tasks.incomplete.where(due_date: Date.today..one_week_from_today)
when 'never'
@tasks = @tasks.incomplete.where(due_date: nil)
else
@tasks = params[:status] == 'completed' ? @tasks.complete : @tasks.incomplete
end
# Apply filters based on due_date and status
@tasks = case params[:due_date]
when 'today'
base_query.incomplete.where('due_date <= ?', Date.today.end_of_day)
when 'upcoming'
base_query.incomplete.where(due_date: Date.today..(Date.today + 7.days))
when 'never'
base_query.incomplete.where(due_date: nil)
else
params[:status] == 'done' ? base_query.complete : base_query.incomplete
end
# Apply ordering
if params[:order_by]
order_column, order_direction = params[:order_by].split(':')
order_direction ||= 'asc'
@tasks = @tasks.order("tasks.#{order_column} #{order_direction}")
end
# Filter by tag if tag parameter is present
# Filter by tag if provided
if params[:tag]
tag = params[:tag]
@tasks = @tasks.joins(:tags).where(tags: { name: tag })
tagged_task_ids = Tag.joins(:tasks).where(name: params[:tag],
tasks: { user_id: current_user.id }).select('tasks.id')
@tasks = @tasks.where(id: tagged_task_ids)
end
@tasks = @tasks.to_a.uniq
@tasks = @tasks.distinct
erb :'tasks/index'
end
@ -52,6 +54,8 @@ module Sinatra
name: params[:name],
priority: params[:priority],
due_date: params[:due_date],
status: params[:status] || Task.statuses[:not_started],
note: params[:note],
user_id: current_user.id
}
@ -79,6 +83,8 @@ module Sinatra
task_attributes = {
name: params[:name],
priority: params[:priority],
status: params[:status] || Task.statuses[:not_started],
note: params[:note],
due_date: params[:due_date]
}
@ -101,8 +107,15 @@ module Sinatra
patch '/task/:id/toggle_completion' do
content_type :json
task = current_user.tasks.find_by(id: params[:id])
if task
task.completed = !task.completed
new_status = if task.done?
task.note.present? ? :in_progress : :not_started
else
:done
end
task.status = new_status
if task.save
task.to_json
else

View file

@ -1,6 +1,6 @@
<h2 class="mb-5"><i class="bi bi-inbox-fill ms-3 me-2"></i> Inbox</h2>
<% unless params[:status] == 'completed' %>
<% unless params[:status] == 'done' %>
<%= partial :'tasks/_minimal_form', locals: { task: Task.new } %>
<% end %>

View file

@ -3,18 +3,19 @@
<div class="row flex-grow-1 align-items-center">
<div class="col-md-4">
<div class="">
<a href="#" class="link-dark  fs-5 text-decoration-none">
<a href="#" class="link-dark text-decoration-none">
<%= note.title %>
</a>
<% if note.tags %>
<% if note.tags.any? %>
<div class="ms-3 opacity-75 d-inline-block">
<% note.tags.each do |tag| %>
<span class="badge bg-primary-subtle text-primary rounded">
<% tag_url = "#{@base_url}tag=#{tag.name}" %>
<a href="<%= tag_url %>" class="badge bg-primary-subtle link-primary text-decoration-none rounded">
<%= tag.name %>
</span>
</a>
<% end %>
</div>
<% end %>
<% end %>
</div>
</div>
<div class="col-md-3">

View file

@ -1,15 +1,25 @@
<h2 class="mb-5"><i class="bi bi-journal-text ms-3 me-2"></i> Notes</h2>
<div class="d-flex justify-content-between align-items-center mb-2 px-3">
<h4 class="mt-2 fw-bold"></h4>
<div class="dropdown">
<button class="btn btn-outline-secondary btn-sm dropdown-toggle" type="button" id="orderNotesByDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-sort-alpha-down me-2"></i> <%= order_name(params[:order_by]) %>
</button>
<ul class="dropdown-menu" aria-labelledby="orderNotesByDropdown">
<% ['title:asc', 'created_at:desc'].each do |order| %>
<li><a class="dropdown-item small" href="<%= "/notes?#{request.query_string}&order_by=#{order}" %>"><%= order.split(':').first.capitalize.gsub('_', ' ') %></a></li>
<% end %>
</ul>
<h4 class="mt-2 fw-bold">Notes</h4>
<div class="d-flex align-items-center">
<% if params[:tag] %>
<span class="badge bg-primary-subtle text-primary me-2" style="font-size: 13px">
<i class="bi bi-tag-fill me-1 opacity-50"></i><%= params[:tag] %>
<a href="<%= url_without_tag %>" class="text-decoration-none text-dark ms-1">
<i class="bi bi-x text-primary"></i>
</a>
</span>
<% end %>
<div class="dropdown">
<button class="btn btn-outline-secondary btn-sm dropdown-toggle" type="button" id="orderNotesByDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-sort-alpha-down me-2"></i> <%= order_name(params[:order_by]) %>
</button>
<ul class="dropdown-menu" aria-labelledby="orderNotesByDropdown">
<% ['title:asc', 'created_at:desc'].each do |order| %>
<li><a class="dropdown-item small" href="<%= "/notes?#{request.query_string}&order_by=#{order}" %>"><%= order.split(':').first.capitalize.gsub('_', ' ') %></a></li>
<% end %>
</ul>
</div>
</div>
</div>
<div class="rounded py-2 px-2 mx-3 d-flex align-items-center border" data-bs-toggle="collapse" data-bs-target="#newNoteForm" aria-expanded="false" aria-controls="newNoteForm" style="cursor: pointer; background: #eee;" data-context="notes">

View file

@ -0,0 +1,32 @@
<% task_counts = @project.task_status_counts %>
<% days_passed = (Date.today - @project.created_at.to_date).to_i %>
<div class="bg-secondary-subtle mb-4 mx-3 p-3 rounded">
<div class="row">
<% task_stats = [['list-check', 'Total', task_counts[:total]],
['arrow-repeat', 'In Progress', task_counts[:in_progress]],
['check-circle', 'Done', task_counts[:done]],
['hourglass-split', 'Not Started', task_counts[:not_started]]] %>
<% task_stats.each do |icon, title, count| %>
<div class="col-md-3 mb-2">
<div class="card bg-light text-center py-3 border-0">
<div class="card-body d-flex align-items-center justify-content-center py-1">
<i class="bi bi-<%= icon %> fs-4 me-2"></i>
<span class="card-title mb-1"><%= title %></span>
</div>
<div class="card-footer py-1 border-0 bg-transparent">
<p class="card-text fs-4"><%= count %></p>
</div>
</div>
</div>
<% end %>
</div>
<div class="mt-2 d-flex align-items-center">
<i class="bi bi-calendar fs-4 me-2"></i>
<span>Project Started: <%= @project.created_at.strftime('%d %b %Y') %>, <%= days_passed %> days ago</span>
</div>
<div class="progress mt-3" style="height: 20px;">
<div class="progress-bar" role="progressbar" style="width: <%= @project.progress_percentage %>%;" aria-valuenow="<%= @project.progress_percentage %>" aria-valuemin="0" aria-valuemax="100">
<%= @project.progress_percentage %>% Complete
</div>
</div>
</div>

View file

@ -1,30 +1,68 @@
<h2 class="mb-5"><i class="bi bi-hexagon ms-3 me-2"></i>Areas & Projects</h2>
<h4 class="mt-5 mb-3 ms-4 fw-bold">Projects</h4>
<div class="row px-3">
<% if @projects_with_tasks.any? %>
<% @projects_with_tasks.each do |project| %>
<div class="col-md-4 mb-3">
<a class="text-decoration-none project-card" href="/project/<%= project.id %>">
<div class="card shadow-sm p-0" style="min-height: 177px;">
<div class="card-body p-0">
<div class="bg-light rounded" style="height: 100px;"></div>
<h5 class="card-title px-3 pt-3 pb-0 mb-1"><%= project.name.upcase %></h5>
<p class="card-text px-3 small text-black-50 opacity-75"><%= project.area.name %></p>
</div>
</div>
<% @grouped_projects.each do |area, projects| %>
<div class="mb-5">
<div class="area-item d-flex align-items-center mb-3">
<a href="#area_<%= area.id %>_projects" class="nav-link link-dark" data-bs-toggle="collapse" aria-expanded="false">
<h4 class="mb-0 pb-0 fw-bold"><%= area.name %></h4>
</a>
</div>
<% end %>
<% end %>
<div class="col-md-4 mb-3">
<a class="text-decoration-none project-card"href="#" data-bs-toggle="modal" data-bs-target="#newProjectModal">
<div class="card shadow-sm border-light p-0" style="min-height: 177px;">
<div class="card-body bg-light rounded px-0 p-0 text-center">
<i class="bi bi-plus" style="font-size: 72px; line-height: 175px; color: #eee"></i>
<div class="dropdown area-options ms-2">
<button class="btn btn-link link-dark p-0" type="button" id="dropdownMenuButton<%= area.id %>" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-three-dots-vertical"></i>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton<%= area.id %>">
<li><a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#editAreaModal<%= area.id %>">Edit</a></li>
<li>
<form action="/area/<%= area.id %>" method="post" onsubmit="return confirm('Are you sure you want to delete this area?');">
<input type="hidden" name="_method" value="delete">
<button type="submit" class="dropdown-item">Delete</button>
</form>
</li>
</ul>
</div>
</div>
</a>
</div>
<div class="row">
<% projects.each do |project| %>
<div class="col-md-4 mb-3">
<a class="text-decoration-none project-card" href="/project/<%= project.id %>">
<div class="card shadow-sm" style="min-height: 177px;">
<div class="d-flex flex-column justify-content-between h-100">
<div>
<div class="bg-light rounded" style="height: 100px;"></div>
<div class="card-body p-0">
<div class="card-footer p-0">
<div class="progress rounded-0" style="height: 2px;">
<div class="progress-bar" role="progressbar" style="width: <%= project.progress_percentage %>%" aria-valuenow="<%= project.progress_percentage %>" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
<h5 class="card-title px-3 pt-3 pb-0 mb-1"><%= project.name.upcase %></h5>
<div class="card-text px-3 small text-black-50 opacity-75">
<%= project.task_status_counts[:total] %> Tasks
<% if project.task_status_counts[:in_progress] > 0 %>
, <i class="bi bi-circle-fill text-success me-1" style="font-size: 0.5em; position: relative; top: -0.3em;"></i> <%= project.task_status_counts[:in_progress] %> in progress
<% end %>
</div>
</div>
</div>
</div>
</div>
</a>
</div>
<% end %>
<div class="col-md-4 mb-3">
<a class="text-decoration-none project-card" href="#" data-bs-toggle="modal" data-bs-target="#newProjectModal">
<div class="card shadow-sm border-light p-0" style="min-height: 177px;">
<div class="card-body bg-light rounded px-0 p-0 text-center">
<i class="bi bi-plus" style="font-size: 72px; line-height: 175px; color: #eee"></i>
</div>
</div>
</a>
</div>
</div>
</div>
<% end %>
</div>
<%= partial :'tasks/_edit_task_modal' %>
<% current_user.areas.each do |area| %>
<%= partial :'areas/_edit_area_modal', locals: { area: area } %>
<% end %>

View file

@ -21,16 +21,20 @@
</div>
</h2>
<% unless @project.description.blank? %>
<div class="bg-secondary-subtle px-4 py-3 mb-4 mx-3 rounded">
<%= @project.description %>
<div class="bg-primary-subtle mb-4 mx-3 rounded d-flex align-items-center">
<i class="bi bi-info-circle-fill fs-1 text-primary ms-3"></i>
<div class="px-4 py-3">
<%= @project.description %>
</div>
</div>
<% end %>
<%= partial :'projects/_panel', locals: { project: @project } %>
<div class="d-flex justify-content-between align-items-center px-3">
<h4 class="mt-2 fw-bold">Tasks</h4>
</div>
<% unless params[:status] == 'completed' %>
<% unless params[:status] == 'done' %>
<%= partial :'tasks/_minimal_form', locals: { task: Task.new(project: @project) } %>
<% end %>
<div class="mx-3 mb-2">
@ -49,7 +53,6 @@
</div>
<% end %>
</div>
<h4 class="mt-5 ms-4 fw-bold">Notes</h4>
<div class="rounded py-2 px-2 mx-3 d-flex align-items-center border" data-bs-toggle="collapse" data-bs-target="#newNoteForm" aria-expanded="false" aria-controls="newNoteForm" style="cursor: pointer; background: #eee;">
<i class=" bi bi-plus-circle-fill text-primary me-2"></i> <span class="">New note</span>
@ -73,7 +76,6 @@
</div>
<% end %>
</div>
<%= partial :'projects/_edit_project_modal', locals: { project: @project } %>
<%= partial :'tasks/_edit_task_modal' %>
<%= partial :'notes/_edit_note_modal' %>

View file

@ -24,7 +24,7 @@
</a>
</li>
<li>
<a href="/tasks?status=completed" class="<%= nav_link('/tasks', 'status' => 'completed') %>">
<a href="/tasks?status=done" class="<%= nav_link('/tasks', 'status' => 'done') %>">
<i class="bi bi-check-circle me-1"></i> Completed
</a>
</li>
@ -78,6 +78,4 @@
<%= partial :'projects/_new_project_modal' %>
<%= partial :'areas/_new_area_modal' %>
<%= partial :'notes/_new_note_modal', locals: {note: Note.new, context: 'sidebar'} %>
<% current_user.areas.each do |area| %>
<%= partial :'areas/_edit_area_modal', locals: { area: area } %>
<% end %>

View file

@ -11,23 +11,40 @@
<input type="text" id="task_name_<%= task.id %>" name="name" value="<%= task.name %>" class="form-control" placeholder="+ Add Task" required>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-12">
<label for="task_tags_<%= task.id %>" class="form-label">Tags:</label>
<input name="tags" id="task_tags_<%= task.id %>" class="form-control" value="<%= task.tags&.map(&:name)&.join(',') %>" placeholder="Add Tags">
</div>
</div>
<div class="row mb-3">
<div class="col-md-4">
<div class="col-md-12">
<label for="task_project" class="form-label">Project (optional):</label>
<select id="task_project_<%= task.id %>" name="project_id" class="form-select">
<option value="">No Project</option>
<% current_user.projects.each do |project| %>
<option value="<%= project.id %>" <%= 'selected' if task.project_id == project.id %>><%= project.name %></option>
<% end %>
</select>
<% end %>
</select>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4">
<label for="task_status" class="form-label">Status:</label>
<select id="task_status_<%= task.id %>" name="status" class="form-select">
<option value="not_started" <%= 'selected' if task.not_started? %>>Not Started</option>
<option value="in_progress" <%= 'selected' if task.in_progress? %>>In Progress</option>
<option value="done" <%= 'selected' if task.done? %>>Done</option>
<option value="archived" <%= 'selected' if task.archived? %>>Archived</option>
</select>
</div>
<div class="col-md-4">
<label for="task_priority" class="form-label">Priority:</label>
<select id="task_priority_<%= task.id %>" name="priority" class="form-select">
<option value="Low" <%= 'selected' if task.priority == 'Low' %>>Low</option>
<option value="Medium" <%= 'selected' if task.priority == 'Medium' %>>Medium</option>
<option value="High" <%= 'selected' if task.priority == 'High' %>>High</option>
<option value="low" <%= 'selected' if task.low? %>>Low</option>
<option value="medium" <%= 'selected' if task.medium? %>>Medium</option>
<option value="high" <%= 'selected' if task.high? %>>High</option>
</select>
</div>
<div class="col-md-4">
@ -37,18 +54,21 @@
</div>
<div class="row mb-3">
<div class="col-md-12">
<input name="tags" id="task_tags_<%= task.id %>" class="form-control" value="<%= task.tags&.map(&:name)&.join(',') %>" placeholder="Add Tags">
<label for="task_note" class="form-label">Note:</label>
<textarea rows="4" id="task_note_<%= task.id %>" name="note" class="form-control" placeholder=""><%= task.note %></textarea>
</div>
</div>
<div class="mt-4">
<button type="submit" class="btn btn-primary">
<%= task.new_record? ? 'Create Task' : 'Update Task' %>
</button>
<% unless task.new_record? %>
<button type="button" class="btn btn-outline-danger" onclick="deleteTask('<%= task.id %>')">
<i class="bi bi-trash"></i>
<div class="row">
<div class="mt-4">
<button type="submit" class="btn btn-primary">
<%= task.new_record? ? 'Create Task' : 'Update Task' %>
</button>
<% end %>
<% unless task.new_record? %>
<button type="button" class="btn btn-outline-danger" onclick="deleteTask('<%= task.id %>')">
<i class="bi bi-trash"></i>
</button>
<% end %>
</div>
</div>
</fieldset>
</form>

View file

@ -4,7 +4,7 @@
<h2 class="mb-5"><i class="bi bi-calendar3 ms-3 me-2"></i> Upcoming</h2>
<% elsif params[:due_date] == 'never' %>
<h2 class="mb-5"><i class="bi bi-moon-stars-fill ms-3 me-2"></i> Someday</h2>
<% elsif params[:status] == 'completed' %>
<% elsif params[:status] == 'done' %>
<h2 class="mb-5"><i class="bi bi-check-circle ms-3 me-2"></i> Completed</h2>
<% else %>
<h2 class="mb-5"><i class="bi bi-layers ms-3 me-2"></i> All</h2>

View file

@ -1,14 +1,14 @@
<div class="border-0 rounded bg-white shadow-sm mb-1 px-2 py-2 d-flex align-items-center task-item <%= 'opacity-50' if task.completed %>" data-task-id="<%= task.id %>">
<div class="border-0 rounded bg-white shadow-sm mb-1 px-2 py-2 d-flex align-items-center task-item <%= 'opacity-50' if task.done? %>" data-task-id="<%= task.id %>">
<div class="row flex-grow-1 align-items-top">
<div class="col-md-7">
<div class="col-md-6">
<span onclick="toggleTaskCompletion(event, <%= task.id %>)" class="toggle-completion">
<i class="fs-6 bi <%= task.completed ? 'bi-check-circle-fill' : 'bi-circle' %> <%= priority_class(task) %> me-2"></i>
<i class="fs-6 bi <%= task.done? ? 'bi-check-circle-fill' : 'bi-circle' %> <%= priority_class(task) %> me-2"></i>
</span>
<%= task.name %>
<% if task.tags.any? %>
<div class="ms-3 opacity-75 d-inline-block">
<% task.tags.each do |tag| %>
<a href="<%= "/tasks?#{request.query_string}&tag=#{tag.name}" %>" class="badge bg-primary-subtle link-primary text-decoration-none">
<a href="<%= "/tasks?#{update_query_params('tag', tag.name)}" %>" class="badge bg-primary-subtle link-primary text-decoration-none">
<i class="bi bi-tag-fill me-1 opacity-50"></i><%= tag.name %>
</a>
<% end %>
@ -22,26 +22,16 @@
</a>
<% end %>
</div>
<div class="col-md-2 text-end">
<div class="col-md-3 text-end">
<% if task.due_date %>
<% if task.due_date.to_date == Date.today %>
<span class="badge bg-primary rounded">
<i class="bi bi-clock me-2"></i> TODAY
</span>
<% elsif task.due_date.to_date < Date.today %>
<span class="badge bg-danger rounded">
<i class="bi bi-clock me-2"></i> <%= task.due_date.strftime("%Y-%m-%d") %>
</span>
<% elsif task.due_date.to_date == Date.today + 1 %>
<span class="badge bg-info rounded">
<i class="bi bi-clock me-2"></i> TOMORROW
</span>
<% else %>
<span class="badge bg-light text-dark rounded">
<i class="bi bi-clock me-2"></i> <%= task.due_date.strftime("%Y-%m-%d") %>
</span>
<% end %>
<span class="badge <%= due_date_badge_class(task.due_date) %>">
<i class="bi bi-clock me-2"></i> <%= format_due_date(task.due_date) %>
</span>
<% end %>
<span class="badge <%= status_badge_class(task.status) %>">
<i class="bi bi-circle-fill me-1" style="font-size: 0.6em; position: relative; top: -0.15em;"></i>
<span class="text-dark"><%= task.status.gsub('_', ' ').capitalize %></span>
</span>
</div>
</div>
</div>

View file

@ -5,7 +5,7 @@
<% if params[:tag] %>
<span class="badge bg-primary-subtle text-primary me-2" style="font-size: 13px">
<i class="bi bi-tag-fill me-1 opacity-50"></i><%= params[:tag] %>
<a href="<%= url_without_tag %>" class="text-decoration-none text-dark ms-1" style="font-size: 13px">
<a href="<%= url_without_tag %>" class="text-decoration-none text-dark ms-1">
<i class="bi bi-x text-primary"></i>
</a>
</span>
@ -15,10 +15,10 @@
<i class="bi bi-sort-alpha-down me-2"></i> <%= order_name(params[:order_by]) %>
</button>
<ul class="dropdown-menu" aria-labelledby="orderByDropdown">
<% ['due_date:desc', 'name:asc', 'priority:asc', 'created_at:desc'].each do |order| %>
<% ['due_date:desc', 'name:asc', 'priority:desc', 'created_at:desc'].each do |order| %>
<li>
<a class="dropdown-item" href="<%= "/tasks?#{update_query_params('order_by', order)}" %>">
<%= order.split(':').first.capitalize %>
<%= order.split(':').first.capitalize.gsub('_', ' ') %>
</a>
</li>
<% end %>
@ -26,7 +26,7 @@
</div>
</div>
</div>
<% unless params[:status] == 'completed' %>
<% unless params[:status] == 'done' %>
<%= partial :'tasks/_minimal_form', locals: { task: Task.new } %>
<% end %>
<div class="mx-3">

View file

@ -0,0 +1,37 @@
class ChangePriorityInTasks < ActiveRecord::Migration[7.1]
def up
add_column :tasks, :new_priority, :integer
Task.reset_column_information
Task.find_each do |task|
new_value = case task.priority
when 'Low' then 0
when 'Medium' then 1
when 'High' then 2
else 0
end
task.update_column(:new_priority, new_value)
end
remove_column :tasks, :priority
rename_column :tasks, :new_priority, :priority
end
def down
add_column :tasks, :old_priority, :string
Task.reset_column_information
Task.find_each do |task|
old_value = case task.priority
when 0 then 'Low'
when 1 then 'Medium'
when 2 then 'High'
else 'Low'
end
task.update_column(:old_priority, old_value)
end
remove_column :tasks, :priority
rename_column :tasks, :old_priority, :priority
end
end

View file

@ -0,0 +1,23 @@
class AddNoteAndStatusToTasks < ActiveRecord::Migration[7.1]
def up
add_column :tasks, :note, :text
add_column :tasks, :status, :integer, default: 0
Task.reset_column_information
Task.find_each do |task|
task.update_column(:status, task.completed ? 2 : 0)
end
remove_column :tasks, :completed
end
def down
add_column :tasks, :completed, :boolean, default: false
Task.reset_column_information
Task.where(status: 2).update_all(completed: true)
remove_column :tasks, :status
remove_column :tasks, :note
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2023_11_17_174412) do
ActiveRecord::Schema[7.1].define(version: 2023_11_27_094906) do
create_table "areas", force: :cascade do |t|
t.string "name"
t.integer "user_id", null: false
@ -65,7 +65,6 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_17_174412) do
create_table "tasks", force: :cascade do |t|
t.string "name"
t.string "priority"
t.datetime "due_date"
t.integer "user_id", null: false
t.integer "project_id"
@ -73,7 +72,9 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_17_174412) do
t.datetime "updated_at", null: false
t.boolean "today", default: false
t.text "description"
t.boolean "completed", default: false
t.integer "priority"
t.text "note"
t.integer "status", default: 0
t.index ["project_id"], name: "index_tasks_on_project_id"
t.index ["user_id"], name: "index_tasks_on_user_id"
end

View file

@ -1,6 +1,6 @@
require 'faker'
user = User.create(email: "myemail@somewhere.com", password: "awes0meHax0Rp4ssword")
user = User.create(email: 'myemail@somewhere.com', password: 'awes0meHax0Rp4ssword')
user_id = user.id
4.times do
@ -28,10 +28,10 @@ projects.each do |project|
8.times do
Task.create(
name: Faker::Lorem.sentence(word_count: 3),
priority: ['Low', 'Medium', 'High'].sample,
priority: %w[Low Medium High].sample,
due_date: [Date.today, Date.today + rand(1..30), nil].sample,
description: Faker::Lorem.sentence(word_count: 15),
completed: [true, false].sample,
status: [0, 1, 2].sample,
user_id: user_id,
project_id: project.id
)

View file

@ -219,7 +219,7 @@ function updateTaskCompletionStatus(taskId, data) {
const taskIcon = iconSpan.querySelector('.bi');
const taskDiv = iconSpan.closest('.task-item');
if (data.completed) {
if (data.done) {
taskIcon.classList.remove('bi-circle', 'text-warning', 'text-danger');
taskIcon.classList.add('bi-check-circle-fill', 'text-success');
taskDiv.classList.add('opacity-50');