diff --git a/app/controllers/todos_controller.rb b/app/controllers/todos_controller.rb index 4e946f5c..091fa85a 100644 --- a/app/controllers/todos_controller.rb +++ b/app/controllers/todos_controller.rb @@ -2,9 +2,9 @@ class TodosController < ApplicationController helper :todos - skip_before_filter :login_required, :only => [:index] - prepend_before_filter :login_or_feed_token_required, :only => [:index] - append_before_filter :init, :except => [ :destroy, :completed, :completed_archive, :check_deferred, :toggle_check, :toggle_star, :edit, :update, :create ] + skip_before_filter :login_required, :only => [:index, :calendar] + prepend_before_filter :login_or_feed_token_required, :only => [:index, :calendar] + append_before_filter :init, :except => [ :destroy, :completed, :completed_archive, :check_deferred, :toggle_check, :toggle_star, :edit, :update, :create, :calendar ] append_before_filter :get_todo_from_params, :only => [ :edit, :toggle_check, :toggle_star, :show, :update, :destroy ] session :off, :only => :index, :if => Proc.new { |req| is_feed_request(req) } @@ -128,8 +128,10 @@ class TodosController < ApplicationController # Toggles the 'done' status of the action # def toggle_check + @source_view = params['_source_view'] || 'todo' + @original_item_due = @todo.due @saved = @todo.toggle_completion! - + # check if this todo has a related recurring_todo. If so, create next todo check_for_next_todo if @saved @@ -139,6 +141,8 @@ class TodosController < ApplicationController determine_remaining_in_context_count(@todo.context_id) determine_down_count determine_completed_count if @todo.completed? + @original_item_due_id = get_due_id_for_calendar(@original_item_due) + @old_due_empty = is_old_due_empty(@original_item_due_id) end render end @@ -172,6 +176,9 @@ class TodosController < ApplicationController @original_item_context_id = @todo.context_id @original_item_project_id = @todo.project_id @original_item_was_deferred = @todo.deferred? + @original_item_due = @todo.due + @original_item_due_id = get_due_id_for_calendar(@todo.due) + if params['todo']['project_id'].blank? && !params['project_name'].nil? if params['project_name'] == 'None' project = Project.null_object @@ -222,6 +229,17 @@ class TodosController < ApplicationController @context_changed = @original_item_context_id != @todo.context_id @todo_was_activated_from_deferred_state = @original_item_was_deferred && @todo.active? + @due_date_changed = @original_item_due != @todo.due + if @due_date_changed + @old_due_empty = is_old_due_empty(@original_item_due_id) + if @todo.due.nil? + # do not act further on date change when date is changed to nil + @due_date_changed = false + else + @new_due_id = get_due_id_for_calendar(@todo.due) + end + end + if @context_changed determine_remaining_in_context_count(@original_item_context_id) else @@ -251,6 +269,7 @@ class TodosController < ApplicationController def destroy @todo = get_todo_from_params + @original_item_due = @todo.due @context_id = @todo.context_id @project_id = @todo.project_id @@ -277,6 +296,8 @@ class TodosController < ApplicationController if source_view_is_one_of(:todo, :deferred) determine_remaining_in_context_count(@context_id) end + @original_item_due_id = get_due_id_for_calendar(@original_item_due) + @old_due_empty = is_old_due_empty(@original_item_due_id) end render end @@ -404,7 +425,47 @@ class TodosController < ApplicationController format.js {render :action => 'update'} end end - + + def calendar + @source_view = params['_source_view'] || 'calendar' + @page_title = "TRACKS::Calendar" + + due_today_date = Time.zone.now + due_this_week_date = Time.zone.now.end_of_week + due_next_week_date = due_this_week_date + 7.days + due_this_month_date = Time.zone.now.end_of_month + + @due_today = current_user.todos.find(:all, + :include => [:taggings, :tags], + :conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due <= ?', 'active', 'deferred', due_today_date], + :order => "due") + @due_this_week = current_user.todos.find(:all, + :include => [:taggings, :tags], + :conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ? AND todos.due <= ?', 'active', 'deferred', due_today_date, due_this_week_date], + :order => "due") + @due_next_week = current_user.todos.find(:all, + :include => [:taggings, :tags], + :conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ? AND todos.due <= ?', 'active', 'deferred', due_this_week_date, due_next_week_date], + :order => "due") + @due_this_month = current_user.todos.find(:all, + :include => [:taggings, :tags], + :conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ? AND todos.due <= ?', 'active', 'deferred', due_next_week_date, due_this_month_date], + :order => "due") + @due_after_this_month = current_user.todos.find(:all, + :include => [:taggings, :tags], + :conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ?', 'active', 'deferred', due_this_month_date], + :order => "due") + + respond_to do |format| + format.html + format.ics { + @due_all = current_user.todos.find(:all, + :conditions => ['(todos.state = ? OR todos.state = ?) AND NOT todos.due IS NULL', 'active', 'deferred'], + :order => "due") + render :action => 'calendar', :layout => false, :content_type => Mime::ICS + } + end + end private @@ -718,6 +779,51 @@ class TodosController < ApplicationController end end end + + def get_due_id_for_calendar(due) + due_today_date = Time.zone.now + due_this_week_date = Time.zone.now.end_of_week + due_next_week_date = due_this_week_date + 7.days + due_this_month_date = Time.zone.now.end_of_month + if due <= due_today_date + new_due_id = "due_today" + elsif due <= due_this_week_date + new_due_id = "due_this_week" + elsif due <= due_next_week_date + new_due_id = "due_next_week" + elsif due <= due_this_month_date + new_due_id = "due_this_month" + else + new_due_id = "due_after_this_month" + end + return new_due_id + end + + def is_old_due_empty(id) + due_today_date = Time.zone.now + due_this_week_date = Time.zone.now.end_of_week + due_next_week_date = due_this_week_date + 7.days + due_this_month_date = Time.zone.now.end_of_month + case id + when "due_today" + return 0 == current_user.todos.count(:all, + :conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due <= ?', 'active', 'deferred', due_today_date]) + when "due_this_week" + return 0 == current_user.todos.count(:all, + :conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ? AND todos.due <= ?', 'active', 'deferred', due_today_date, due_this_week_date]) + when "due_next_week" + return 0 == current_user.todos.count(:all, + :conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ? AND todos.due <= ?', 'active', 'deferred', due_this_week_date, due_next_week_date]) + when "due_this_month" + return 0 == current_user.todos.count(:all, + :conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ? AND todos.due <= ?', 'active', 'deferred', due_next_week_date, due_this_month_date]) + when "due_after_this_month" + return 0 == current_user.todos.count(:all, + :conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ?', 'active', 'deferred', due_this_month_date]) + else + raise Exception.new, "unknown due id for calendar: '#{id}'" + end + end class FindConditionBuilder diff --git a/app/views/layouts/standard.html.erb b/app/views/layouts/standard.html.erb index 21c9cdc4..42ea20d2 100644 --- a/app/views/layouts/standard.html.erb +++ b/app/views/layouts/standard.html.erb @@ -54,6 +54,7 @@ window.onload=function(){ <% if current_user.is_admin? -%>
  • <%= navigation_link("Admin", users_path, {:accesskey => "a", :title => "Add or delete users"} ) %>
  • <% end -%> +
  • <%= navigation_link(image_tag("x-office-calendar.png", :size => "16X16", :border => 0), calendar_path, :title => "Calendar of due actions" ) %>
  • <%= navigation_link(image_tag("recurring_menu16x16.png", :size => "16X16", :border => 0), {:controller => "recurring_todos", :action => "index"}, :title => "Manage recurring actions" ) %>
  • <%= navigation_link(image_tag("feed-icon.png", :size => "16X16", :border => 0), {:controller => "feedlist", :action => "index"}, :title => "See a list of available feeds" ) %>
  • <%= navigation_link(image_tag("menustar.gif", :size => "16X16", :border => 0), tag_path("starred"), :title => "See your starred actions" ) %>
  • diff --git a/app/views/todos/calendar.html.erb b/app/views/todos/calendar.html.erb new file mode 100644 index 00000000..056c0cff --- /dev/null +++ b/app/views/todos/calendar.html.erb @@ -0,0 +1,68 @@ +
    + +
    +

    Due today

    +
    > + No actions due today +
    +
    + <%= render :partial => "todos/todo", :collection => @due_today %> +
    +
    + +
    +

    Due in rest of this week

    +
    > + No actions due in rest of this week +
    +
    + <%= render :partial => "todos/todo", :collection => @due_this_week %> +
    +
    + +
    +

    Due next week

    +
    > + No actions due in next week +
    +
    + <%= render :partial => "todos/todo", :collection => @due_next_week %> +
    +
    + +
    +

    Due in rest of this month

    +
    > + No actions due in rest of this month +
    +
    + <%= render :partial => "todos/todo", :collection => @due_this_month %> +
    +
    + +
    +

    Due next month and later

    +
    > + No actions due after this month +
    +
    + <%= render :partial => "todos/todo", :collection => @due_after_this_month %> +
    +
    + +
    +
    + +

    <%= link_to('iCal', {:format => 'ics', :token => current_user.token}, :title => "iCal feed" ) %> + - Get this calendar in iCal format

    +
    + +<% +apply_behavior 'input.hide_tickler:click', :prevent_default => true do |page| + page << "alert('hiding action in tickler from calendar is not yet implemented');" +end +%> \ No newline at end of file diff --git a/app/views/todos/calendar.ics.erb b/app/views/todos/calendar.ics.erb new file mode 100644 index 00000000..af9d7682 --- /dev/null +++ b/app/views/todos/calendar.ics.erb @@ -0,0 +1,25 @@ +BEGIN:VCALENDAR +PRODID:-//TRACKS//<%= TRACKS_VERSION %>//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:Tracks +<% for todo in @due_all -%> +BEGIN:VEVENT +DTSTART;VALUE=DATE:<%= todo.due.strftime("%Y%m%d") %> +DTEND;VALUE=DATE:<%= (todo.due+1.day).strftime("%Y%m%d") %> +DTSTAMP:<%= todo.due.strftime("%Y%m%dT%H%M%SZ") %> +UID:<%= todo_url(todo) %> +CLASS:PUBLIC +CATEGORIES:Tracks +CREATED:<%= todo.created_at.strftime("%Y%m%dT%H%M%SZ") %> +DESCRIPTION:<%= format_ical_notes(todo.notes) %> +LAST-MODIFIED:<%= todo.due.strftime("%Y%m%dT%H%M%SZ") %> +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:<%= todo.description %> +TRANSP:TRANSPARENT +END:VEVENT +<% end -%> +END:VCALENDAR \ No newline at end of file diff --git a/app/views/todos/destroy.js.rjs b/app/views/todos/destroy.js.rjs index 7f37f87d..ac1ba3d4 100644 --- a/app/views/todos/destroy.js.rjs +++ b/app/views/todos/destroy.js.rjs @@ -1,5 +1,6 @@ if @saved page[@todo].remove + page.show "empty_"+@original_item_due_id if @old_due_empty page['badge_count'].replace_html @down_count # remove context if empty diff --git a/app/views/todos/toggle_check.js.rjs b/app/views/todos/toggle_check.js.rjs index 1462162a..d862862c 100644 --- a/app/views/todos/toggle_check.js.rjs +++ b/app/views/todos/toggle_check.js.rjs @@ -1,5 +1,6 @@ if @saved page[@todo].remove + page.show "empty_"+@original_item_due_id if @old_due_empty if @todo.completed? # completed todos move from their context to the completed container @@ -26,7 +27,7 @@ if @saved page.notify :notice, "There is no next action after the recurring action you just finished. The recurrence is completed", 6.0 if @new_recurring_todo.nil? end end - + else # todo is activated from completed container page.call "todoItems.ensureVisibleWithEffectAppear", item_container_id(@todo) diff --git a/app/views/todos/update.js.rjs b/app/views/todos/update.js.rjs index dae71f41..2f263b54 100644 --- a/app/views/todos/update.js.rjs +++ b/app/views/todos/update.js.rjs @@ -101,6 +101,23 @@ if @saved elsif source_view_is :stats page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type } page.visual_effect :highlight, dom_id(@todo), :duration => 3 + elsif source_view_is :calendar + if @due_date_changed + page[@todo].remove + page.show "empty_"+@original_item_due_id if @old_due_empty + page.hide "empty_"+@new_due_id + page.insert_html :bottom, @new_due_id, :partial => 'todos/todo' + page.visual_effect :highlight, dom_id(@todo), :duration => 3 + else + if @todo.due.nil? + # due date removed + page[@todo].remove + page.show "empty_"+@original_item_due_id if @old_due_empty + else + page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type } + page.visual_effect :highlight, dom_id(@todo), :duration => 3 + end + end else logger.error "unexpected source_view '#{params[:_source_view]}'" end diff --git a/config/routes.rb b/config/routes.rb index e0a91a43..50e29ff7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -46,6 +46,9 @@ ActionController::Routing::Routes.draw do |map| todos.tag 'todos/tag/:name.m', :action => "tag", :format => 'm' todos.tag 'todos/tag/:name', :action => "tag", :name => /.*/ + todos.calendar 'calendar.ics', :action => "calendar", :format => 'ics' + todos.calendar 'calendar', :action => "calendar" + todos.mobile 'mobile', :action => "index", :format => 'm' todos.mobile_abbrev 'm', :action => "index", :format => 'm' todos.mobile_abbrev_new 'm/new', :action => "new", :format => 'm' diff --git a/public/images/x-office-calendar.png b/public/images/x-office-calendar.png new file mode 100644 index 00000000..f8607e33 Binary files /dev/null and b/public/images/x-office-calendar.png differ