Browse Source

add project and todo views and some state saving

master
Richard Cagle 1 year ago
parent
commit
4579d67500
10 changed files with 139 additions and 9 deletions
  1. 1
    1
      app.rb
  2. 1
    0
      db/migrate/001_schema.rb
  3. 10
    5
      jobs/sync_job.rb
  4. 6
    0
      models/bucket.rb
  5. 7
    0
      models/todo_item.rb
  6. 32
    2
      public/script.js
  7. 8
    0
      public/style.css
  8. 1
    1
      views/layout.erb
  9. 32
    0
      views/projects.erb
  10. 41
    0
      views/todos.erb

+ 1
- 1
app.rb View File

@@ -13,7 +13,7 @@ require "sinatra/json"

SuckerPunch.logger = Logger.new(STDOUT)

ActiveRecord::Base.logger = Logger.new("debug.log")
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.establish_connection(ENV.fetch("DATABASE_URL"))

require_relative "models/bucket.rb"

+ 1
- 0
db/migrate/001_schema.rb View File

@@ -35,6 +35,7 @@ class Schema < ActiveRecord::Migration[6.0]
t.string :title
t.string :list_type
t.string :url
t.string :app_url
t.timestamps
end


+ 10
- 5
jobs/sync_job.rb View File

@@ -17,14 +17,19 @@ class SyncJob
basecamp.request_count = 0

basecamp.todo_items.each do |item|
Bucket.find_or_create_by(id: item["bucket"]["id"], name: item["bucket"]["name"], bucket_type: item["bucket"]["type"]).save!
Bucket.find_or_create_by(
id: item["bucket"]["id"],
name: item["bucket"]["name"],
bucket_type: item["bucket"]["type"]
).save!

TodoList.find_or_create_by(
id: item["parent"]["id"],
bucket_id: item["bucket"]["id"],
title: item["parent"]["title"],
list_type: item["parent"]["type"],
url: item["parent"]["url"]
url: item["parent"]["url"],
app_url: item["parent"]["app_url"]
).save!

Person.find_or_create_by(
@@ -55,7 +60,7 @@ class SyncJob
creator_id: item["creator"]["id"],
status: item["status"],
visible_to_clients: item["visible_to_clients"] ? 1 : 0,
created_at: DateTime.strptime(item["created_at"].to_s, "%s"),
created_at: DateTime.parse(item["created_at"]),
title: item["title"],
item_type: item["type"],
url: item["url"],
@@ -63,8 +68,8 @@ class SyncJob
completed: item["completed"] ? 1 : 0,
description: item["description"],
content: item["content"],
starts_on: item["starts_on"].nil? ? nil : DateTime.strptime(item["starts_on"].to_s, "%s"),
due_on: item["due_on"].nil? ? nil : DateTime.strptime(item["due_on"].to_s, "%s")
starts_on: item["starts_on"].nil? ? nil : DateTime.parse(item["starts_on"]),
due_on: item["due_on"].nil? ? nil : DateTime.parse(item["due_on"])
).save!
end


+ 6
- 0
models/bucket.rb View File

@@ -2,4 +2,10 @@

class Bucket < ActiveRecord::Base
has_many :todo_lists
has_many :todo_items, through: :todo_lists
has_many :people, through: :todo_items, source: :assignees, class_name: 'Person'

def real_people
people.reject{ |person| person.name == "Mr. Backlog" }.uniq
end
end

+ 7
- 0
models/todo_item.rb View File

@@ -3,4 +3,11 @@
class TodoItem < ActiveRecord::Base
belongs_to :todo_list
belongs_to :person, foreign_key: :creator_id

has_many :todo_item_assignees
has_many :assignees, through: :todo_item_assignees, class_name: 'Person', source: :person

def self.active_and_assigned
TodoItem.order(due_on: :asc).all.reject{ |todo_item| todo_item.assignees.count.zero? || todo_item.assignees.map(&:name).include?("Mr. Backlog") }
end
end

+ 32
- 2
public/script.js View File

@@ -1,4 +1,13 @@
var is_syncing = false;
var active_tab = Cookies.get('active_tab');

function ready(fn) {
if (document.readyState != 'loading'){
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}

function poll_for_sync_status(){
var request = new XMLHttpRequest();
@@ -17,16 +26,26 @@ function poll_for_sync_status(){
request.send();
}

ready(function(){
if(active_tab){
var event = document.createEvent('HTMLEvents');
event.initEvent('click', true, false);
document.querySelector('[data-tab-link="'+active_tab+'"]').dispatchEvent(event);
}
});

document.addEventListener('click', function(e) {
for (var target = e.target; target && target != this; target = target.parentNode) {
if (target.matches(".tab-link")) {

Cookies.set('active_tab', target.dataset.tabLink);

var tab_links = document.querySelectorAll('.tab-link');
for (var i = 0; i < tab_links.length; i++) {
tab_links[i].parentNode.classList.remove('is-active');
}

e.target.parentNode.classList.add('is-active');
target.parentNode.classList.add('is-active');

var tab_content_containers = document.querySelectorAll('.tab-content');
for (var i = 0; i < tab_content_containers.length; i++) {
@@ -41,6 +60,17 @@ document.addEventListener('click', function(e) {
target.parentNode.classList.toggle('closed');
}

if (target.matches(".person-checkbox")) {
var todo_person_elements = document.querySelectorAll("." + target.id);
for (var i = 0; i < todo_person_elements.length; i++) {
if(target.checked){
todo_person_elements[i].classList.remove('is-hidden');
} else {
todo_person_elements[i].classList.add('is-hidden');
}
}
}

if(target.matches(".fa-sync")) {
if(is_syncing == false){
is_syncing = true;
@@ -52,7 +82,7 @@ document.addEventListener('click', function(e) {
request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');

request.onload = function() {
poll_for_sync_status();
setTimeout(poll_for_sync_status, 1000);
};

request.onerror = function() {

+ 8
- 0
public/style.css View File

@@ -62,3 +62,11 @@ body {
.accordian-section.closed .accordian-icon .is-closed {
display: inline-block;
}

.user-controls label {
width: 200px;
}

.user-controls label span {
vertical-align: middle;
}

+ 1
- 1
views/layout.erb View File

@@ -13,7 +13,7 @@
<link rel="stylesheet" href="/style.css">

<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
<script src="https://cdn.jsdelivr.net/npm/js-cookie@beta/dist/js.cookie.min.js"></script>
<script type="text/javascript" src="/script.js"></script>
</head>


+ 32
- 0
views/projects.erb View File

@@ -0,0 +1,32 @@
<% Bucket.order(:name).all.each do |project| %>
<div class="accordian-section">
<div class="media accordian-trigger">
<div class="media-left">
<figure class="image is-64x64">
<img class="is-rounded" src="https://source.unsplash.com/random/64x64?<%= ["tree", "building", "clouds", "wolf", "tiger", "pheasant", "computer", "smile", "goat"].sample %>">
</figure>
</div>
<div class="media-content">
<p class="title is-4"><%= project.name %></p>
<p class="subtitle is-6"><%= project.real_people.count %> Team Member(s) Working: (<%= project.real_people.count.zero? ? "NONE" : project.real_people.map(&:name).join(", ") %>) </p>
</div>
<div class="accordian-icon">
<i class="fa fa-angle-up is-closed"></i>
<i class="fa fa-angle-down is-open"></i>
</div>
</div>
<div class="content accordian-content">
<% project.todo_lists.each do |todo_list| %>
<span class="has-text-weight-bold"><u><%= todo_list.title %></u></span>
<ul>
<% todo_list.todo_items.reject{ |todo_item| todo_item.assignees.count.zero? || todo_item.assignees.map(&:name).include?("Mr. Backlog") }.each do |todo_item| %>
<li>
<a href="<%= todo_item.app_url %>"><%= todo_item.title %></a>
<span class="is-size-7">(<%= todo_item.assignees.map(&:name).join(', ') %>)</span>
</li>
<% end %>
</ul>
<% end %>
</div>
</div>
<% end %>

+ 41
- 0
views/todos.erb View File

@@ -0,0 +1,41 @@
<span class="is-size-5"><%= TodoItem.active_and_assigned.count %> Active & Assigned To-Dos</span>
<div class="user-controls">
<% Person.where.not(name: "Mr. Backlog").all.each do |person| %>
<label class="checkbox">
<input type="checkbox" class="person-checkbox" id="person-<%= person.id %>" checked>
<span>
<figure class="image is-16x16 is-inline-block">
<img class="is-rounded" src="<%= person.avatar_url %>">
</figure>
<%= person.name %>
</span>
</label>
<% end %>
</div>
<hr>
<% TodoItem.active_and_assigned.each do |todo_item| %>
<ul>
<li class="<%= todo_item.assignees.map{ |person| "person-#{person.id}" }.join(" ") %>">
<a href="<%= todo_item.app_url %>"><%= todo_item.title %></a><br>
<small>
<em>Todo List:</em> <a href="<%= todo_item.todo_list.app_url %>"><%= todo_item.todo_list.title %></a>
|
<em>Project:</em> <%= todo_item.todo_list.bucket.name %>
<br>
<em>Assigned To:</em>
<% todo_item.assignees.each do |person| %>
<figure class="image is-16x16 is-inline-block">
<img class="is-rounded" src="<%= person.avatar_url %>">
</figure>
<%= person.name %>
<% end %>
<% unless todo_item.due_on.nil? %>
|
Due: <%= todo_item.due_on.strftime("%m/%d/%Y") %>
<strong>(<%= (todo_item.due_on.to_date - DateTime.now.to_date).to_i %> Days)</strong>
<% end %>
</small>
<hr>
</li>
</ul>
<% end %>

Loading…
Cancel
Save