diff --git a/app/controllers/targets_controller.rb b/app/controllers/targets_controller.rb index 154ed8a..d019136 100644 --- a/app/controllers/targets_controller.rb +++ b/app/controllers/targets_controller.rb @@ -2,17 +2,21 @@ class TargetsController < ApplicationController layout 'body_tracking' menu_item :body_trackers helper :body_trackers + helper_method :current_goal include Concerns::Finders before_action :find_project_by_project_id, only: [:index, :new, :create] + before_action :find_quantity_by_quantity_id, only: [:toggle_exposure] + before_action :find_goal, only: [:toggle_exposure] + before_action :set_view_params def index prepare_targets end def new - target = (@goal || @project.goals.binding).targets.new + target = current_goal.targets.new target.arity.times { target.thresholds.new } @targets = [target] end @@ -22,9 +26,15 @@ class TargetsController < ApplicationController @targets = goal.targets.build(targets_params[:targets]) do |target| target.effective_from = params[:target][:effective_from] end + # FIXME: add goal exposures before save and require (in model) goal.target_exposures to + # be present (same for measurement/food?) # :save only after build, to re-display values in case records are invalid if goal.save && Target.transaction { @targets.all?(&:save) } + if goal.target_exposures.empty? + goal.quantities << @targets.map { |t| t.thresholds.first.quantity }.first(6) + end + flash[:notice] = 'Created new target(s)' prepare_targets else @@ -36,6 +46,27 @@ class TargetsController < ApplicationController end end + def edit + end + + def update + end + + def destroy + end + + def reapply + end + + def toggle_exposure + current_goal.target_exposures.toggle!(@quantity) + prepare_targets + end + + def current_goal + @goal || @project.goals.binding + end + private def goal_params @@ -59,6 +90,22 @@ class TargetsController < ApplicationController end def prepare_targets - @targets = @project.targets.includes(:item, :thresholds) + @quantities = current_goal.quantities.includes(:formula) + + @targets_by_date = Hash.new { |h,k| h[k] = {} } + @project.targets.includes(:item, thresholds: [:quantity]).reject(&:new_record?) + .each { |t| @targets_by_date[t.effective_from][t.thresholds.first.quantity] = t } + end + + def set_view_params + @view_params = case params[:view] + when 'by_effective_from' + {view: :by_effective_from, effective_from: @effective_from} + when 'by_item_quantity' + {view: :by_item_quantity, item: nil, quantity: @quantity} + else + {view: :by_scope, scope: :all} + end + @view_params[goal_id] = @goal.id if @goal end end diff --git a/app/helpers/body_trackers_helper.rb b/app/helpers/body_trackers_helper.rb index a9aa842..4b5f287 100644 --- a/app/helpers/body_trackers_helper.rb +++ b/app/helpers/body_trackers_helper.rb @@ -14,11 +14,15 @@ module BodyTrackersHelper end end - def format_time(t) - t.strftime("%R") if t + def format_date(d) + d&.strftime("%F") end - def toggle_exposure_options(enabled, domain) + def format_time(t) + t&.strftime("%R") + end + + def toggle_exposure_options(enabled, domain = :all) enabled = enabled.map { |q| [q.name, q.id] } enabled_ids = enabled.map(&:last) @@ -49,8 +53,7 @@ module BodyTrackersHelper end end - # TODO: rename to quantities_table_header - def table_header_spec(quantities) + def quantities_table_header(quantities) # spec: table of rows (tr), where each row is a hash of cells (td) (hash keeps items # ordered the way they were added). Hash values determine cell property: # * int > 0 - quantity name-labelled cell with 'int' size colspan diff --git a/app/helpers/targets_helper.rb b/app/helpers/targets_helper.rb index 9365156..9940b2a 100644 --- a/app/helpers/targets_helper.rb +++ b/app/helpers/targets_helper.rb @@ -3,11 +3,11 @@ module TargetsHelper Target::CONDITIONS end - def action_links(m) - link_to(l(:button_retake), retake_measurement_path(m, @view_params), + def action_links(d) + link_to(l(:button_reapply), reapply_project_targets_path(@project, d, @view_params), {remote: true, class: "icon icon-reload"}) + - link_to(l(:button_edit), edit_measurement_path(m, @view_params), + link_to(l(:button_edit), edit_target_path(@project, d, @view_params), {remote: true, class: "icon icon-edit"}) + - delete_link(measurement_path(m), {remote: true, data: {}}) + delete_link(target_path(d), {remote: true, data: {}}) end end diff --git a/app/models/goal.rb b/app/models/goal.rb index 1ad3f58..8b7d481 100644 --- a/app/models/goal.rb +++ b/app/models/goal.rb @@ -1,6 +1,10 @@ class Goal < ActiveRecord::Base belongs_to :project, required: true - has_many :targets, inverse_of: :goal, dependent: :destroy + has_many :targets, -> { order "effective_from DESC" }, inverse_of: :goal, + dependent: :destroy, extend: BodyTracking::ItemsWithQuantities + has_many :target_exposures, as: :view, dependent: :destroy, + class_name: 'Exposure', extend: BodyTracking::TogglableExposures + has_many :quantities, -> { order "lft" }, through: :target_exposures validates :name, presence: true, uniqueness: {scope: :project_id} diff --git a/app/models/target.rb b/app/models/target.rb index 8a639ca..f13c1b1 100644 --- a/app/models/target.rb +++ b/app/models/target.rb @@ -16,7 +16,7 @@ class Target < ActiveRecord::Base validates :condition, inclusion: {in: CONDITIONS} validates :scope, inclusion: {in: [:ingredient, :meal, :day], if: -> { thresholds.first.quantity.domain == :diet }} - validates :effective_from, presence: {unless: :is_binding?}, absence: {if: :is_binding?} + validates :effective_from, presence: {if: :is_binding?}, absence: {unless: :is_binding?} after_initialize do if new_record? @@ -30,4 +30,8 @@ class Target < ActiveRecord::Base def arity BigDecimal.method(condition).arity end + + def to_s + "#{condition} #{thresholds.first.value} [#{thresholds.first.unit.shortname}]" + end end diff --git a/app/views/body_trackers/_quantity_table_header.html.erb b/app/views/body_trackers/_quantity_table_header.html.erb new file mode 100644 index 0000000..7d867f0 --- /dev/null +++ b/app/views/body_trackers/_quantity_table_header.html.erb @@ -0,0 +1,27 @@ +<% total_width = 4 + @quantities.length %> +<% header = quantities_table_header(@quantities) %> +<% header.each_with_index do |row, i| %> + + <% if i == 0 %> + + <%= l(:field_amount) %> + <% end %> + + <% row.each do |q, span| %> + 0 %> + <%= "rowspan=#{-span}" if span && span < 0 %> + style="width: <%= (span && span > 0 ? span : 1) * 100/total_width %>%;" + title="<%= q.description %>"> + <%= q.name if span %> + + <% end %> + + <% if i == 0 %> + + <% end %> + +<% end %> diff --git a/app/views/meals/_index.html.erb b/app/views/meals/_index.html.erb index 14f2fc8..ed4baac 100644 --- a/app/views/meals/_index.html.erb +++ b/app/views/meals/_index.html.erb @@ -8,7 +8,7 @@ <% total_width = 4 + @quantities.length %> - <% header = table_header_spec(@quantities) %> + <% header = quantities_table_header(@quantities) %> <% @meals_by_date.reverse_each do |date, meals| %> <% header.each_with_index do |row, i| %> diff --git a/app/views/targets/_form.html.erb b/app/views/targets/_form.html.erb index 23d9e9d..dc6b237 100644 --- a/app/views/targets/_form.html.erb +++ b/app/views/targets/_form.html.erb @@ -6,7 +6,7 @@ <% if goal.persisted? %>

<%= render partial: 'goals/show_form', locals: {goal: goal} %>

<%#= t '.effective_from' %> -

<%= f.date_field :effective_from, disabled: goal.is_binding? %>

+

<%= f.date_field :effective_from, disabled: !goal.is_binding? %>

<% else %> <%= render partial: 'goals/form', locals: {goal: goal} %> <% end %> diff --git a/app/views/targets/_index.html.erb b/app/views/targets/_index.html.erb index b08d70c..7edd1fb 100644 --- a/app/views/targets/_index.html.erb +++ b/app/views/targets/_index.html.erb @@ -1,32 +1,87 @@ -<% if @targets.any? { |t| t.persisted? } %> -
- - - - - - - - - +<%#= render partial: 'measurements/filters', + locals: {url: filter_project_measurements_path(@project, @view_params)} %> + +<% if @targets_by_date.any? %> + <%= render partial: 'targets/options' %> + + <% formulas = @quantities.map { |q| q.formula } %> + <%# formulas.unshift(@filter_q.formula) if @filter_q %> + <%= error_messages_for *formulas %> + +
<%= l(:field_taken_at_date) %><%= l(:field_name) %><%= l(:field_notes) %><%= l(:field_source) %><%= l(:field_action) %>
- <% @measurements.each do |m| %> - <% next if m.new_record? %> - - - - - - + <% total_width = 3 + @quantities.length %> + <% header = quantities_table_header(@quantities) %> + <% header.each_with_index do |row, i| %> + + <% if i == 0 %> + + <% end %> + + <% row.each do |q, span| %> + + <% end %> + + <% if i == 0 %> + + <% end %> <% end %> + + <% @targets_by_date.each do |date, targets| %> + <% row_class = "date #{cycle('odd', 'even')}" %> + + + <% @quantities.each do |q| %> + + <% end %> + + + + + <% rows = @quantities.empty? ? 1 : (targets.length - 1) / @quantities.length + 1 %> + + <% @quantities.each do |q| %> + + <% end %> + + + + <% targets.each_slice(@quantities.length) do |extras| %> + + <% extras.each do |q, t| %> + + <% end %> + <% if @quantities.length > extras.length %> + + <% end %> + + <% end %> + + <% end %>
<%= format_datetime(m) %> -
- <%= link_to m.routine.name, readouts_measurement_routine_path(m.routine) %> -
-
- <%= " (#{pluralize(m.readouts.size, 'readout')})" %> -
-
<%= m.notes %><%= m.source.name if m.source.present? %><%= action_links(m) %>
<%= l(:field_effective_from) %> 0 %> + <%= "rowspan=#{-span}" if span && span < 0 %> + style="width: <%= (span && span > 0 ? span : 1) * 100/total_width %>%;" + title="<%= q.description %>"> + <%= q.name if span %> + <%= l(:field_action) %>
+ <%= format_date(date) %> + <%= targets[q] %><%= action_links(date) %>
<% else %> diff --git a/app/views/targets/_oldindex.html.erb b/app/views/targets/_oldindex.html.erb new file mode 100644 index 0000000..4b60569 --- /dev/null +++ b/app/views/targets/_oldindex.html.erb @@ -0,0 +1,33 @@ +<% if @targets.any? %> + + + + + + + + + + + + <% @targets.select(&:persisted?).each do |t| %> + + + + + + + + <% end %> + +
<%= l(:field_effective_from) %><%= l(:field_name) %><%= l(:field_notes) %><%= l(:field_source) %><%= l(:field_action) %>
<%= format_date(t.effective_from) %> +
+ <%= link_to m.routine.name, readouts_measurement_routine_path(m.routine) %> +
+
+ <%= " (#{pluralize(m.readouts.size, 'readout')})" %> +
+
<%= m.notes %><%= m.source.name if m.source.present? %><%= action_links(m) %>
+<% else %> +

<%= l(:label_no_data) %>

+<% end %> diff --git a/app/views/targets/_options.html.erb b/app/views/targets/_options.html.erb new file mode 100644 index 0000000..0188b4a --- /dev/null +++ b/app/views/targets/_options.html.erb @@ -0,0 +1,18 @@ +
+ <%= l(:label_options) %> +
+ <%= form_tag toggle_exposure_goal_path(current_goal, @view_params), + id: 'toggle-exposure-form', name: 'toggle-exposure-form', + method: :post, remote: true do %> + + + + + + + +
<%= select_tag 'quantity_id', + toggle_exposure_options(current_goal.quantities) %><%= submit_tag l(:button_add) %>
+ <% end %> +
+
diff --git a/app/views/targets/create.js.erb b/app/views/targets/create.js.erb index 4ad3eae..e484069 100644 --- a/app/views/targets/create.js.erb +++ b/app/views/targets/create.js.erb @@ -1,7 +1,7 @@ $('#new-targets').empty(); <% case @view_params[:view] %> <% when :by_date %> - $('#targets').html('<%= j render partial: 'targets/by_date' %>'); + $('#targets').html('<%= j render partial: 'targets/by_effective_date' %>'); <% else %> $('#targets').html('<%= j render partial: 'targets/index' %>'); <% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index b1168c9..821f53b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -22,6 +22,7 @@ en: field_formula: 'Formula' field_code: 'Formula' field_shortname: 'Short name' + button_reapply: 'Reapply' button_eat: 'Eat' button_notes: 'Notes' button_retake: 'Retake' diff --git a/config/routes.rb b/config/routes.rb index 7b2fabf..fea5548 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -7,8 +7,16 @@ resources :projects, shallow: true do post 'defaults' end end - resources :goals, only: [:show, :edit] - resources :targets, except: [:show] + resources :goals, only: [:show, :edit] do + member do + post 'toggle_exposure', to: 'goals#toggle_exposure' + end + end + resources :targets, except: [:show] do + collection do + post 'reapply/:date', to: 'targets#reapply', as: :reapply + end + end resources :ingredients, only: [] do post 'adjust/:adjustment', to: 'meals#adjust', as: :adjust, on: :member end diff --git a/init.rb b/init.rb index ee80c54..7baa7d7 100644 --- a/init.rb +++ b/init.rb @@ -27,7 +27,7 @@ Redmine::Plugin.register :body_tracking do permission :manage_common, { body_trackers: [:defaults], goals: [:edit], - targets: [:new, :create, :edit, :update, :destroy], + targets: [:new, :create, :edit, :update, :destroy, :reapply, :toggle_exposure], meals: [:new, :create, :edit, :update, :destroy, :edit_notes, :update_notes, :toggle_eaten, :toggle_exposure, :adjust], measurement_routines: [:edit],