Skip to content

Commit

Permalink
Add status, note, project panel
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisvel committed Nov 27, 2023
1 parent 19584ec commit 2398d22
Show file tree
Hide file tree
Showing 24 changed files with 397 additions and 152 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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.
Expand Down
30 changes: 3 additions & 27 deletions app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
require './app/models/note'

require './app/helpers/authentication_helper'
require './app/helpers/task_helper'

require './app/routes/authentication_routes'
require './app/routes/tasks_routes'
Expand Down Expand Up @@ -46,6 +47,8 @@
require_login
end

helpers TaskHelper

helpers do
def current_path
request.path_info
Expand All @@ -55,16 +58,6 @@ def partial(page, options = {})
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
Expand Down Expand Up @@ -92,23 +85,6 @@ def nav_link(path, query_params = {}, project_id = nil)
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)
Expand Down
74 changes: 74 additions & 0 deletions app/helpers/task_helper.rb
Original file line number Diff line number Diff line change
@@ -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
21 changes: 19 additions & 2 deletions app/models/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 5 additions & 2 deletions app/models/task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
11 changes: 10 additions & 1 deletion app/routes/notes_routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,16 @@ def update_note_tags(note, tags_json)
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
Expand Down
3 changes: 2 additions & 1 deletion app/routes/projects_routes.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down
53 changes: 33 additions & 20 deletions app/routes/tasks_routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,36 @@ def update_task_tags(task, tags_json)
end

get '/tasks' do
@tasks = 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

# Base query with necessary joins
base_query = current_user.tasks.includes(:project, :tags)

# 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
Expand All @@ -52,6 +54,8 @@ def update_task_tags(task, tags_json)
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
}

Expand Down Expand Up @@ -79,6 +83,8 @@ def update_task_tags(task, tags_json)
task_attributes = {
name: params[:name],
priority: params[:priority],
status: params[:status] || Task.statuses[:not_started],
note: params[:note],
due_date: params[:due_date]
}

Expand All @@ -101,8 +107,15 @@ def update_task_tags(task, tags_json)
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
Expand Down
2 changes: 1 addition & 1 deletion app/views/inbox.erb
Original file line number Diff line number Diff line change
@@ -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 %>

Expand Down
11 changes: 6 additions & 5 deletions app/views/notes/_note.erb
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
30 changes: 20 additions & 10 deletions app/views/notes/index.erb
Original file line number Diff line number Diff line change
@@ -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">
Expand Down
Loading

0 comments on commit 2398d22

Please sign in to comment.