Add status, note, project panel
This commit is contained in:
parent
19584ecb1a
commit
2398d2278e
24 changed files with 396 additions and 151 deletions
|
|
@ -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
30
app.rb
|
|
@ -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)
|
||||
|
|
|
|||
74
app/helpers/task_helper.rb
Normal file
74
app/helpers/task_helper.rb
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 %>
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
32
app/views/projects/_panel.erb
Normal file
32
app/views/projects/_panel.erb
Normal 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>
|
||||
|
|
@ -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 %>
|
||||
|
|
|
|||
|
|
@ -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' %>
|
||||
|
|
|
|||
|
|
@ -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 %>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
37
db/migrate/20231127092131_change_priority_in_tasks.rb
Normal file
37
db/migrate/20231127092131_change_priority_in_tasks.rb
Normal 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
|
||||
23
db/migrate/20231127094906_add_note_and_status_to_tasks.rb
Normal file
23
db/migrate/20231127094906_add_note_and_status_to_tasks.rb
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue