diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 95fff8c9a..0d49ed364 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -31,6 +31,7 @@ #= require nested_form_fields #= require modal_create #= require datepicker +#= require header #= require worktimes #= require plannings #= require plannings_panel diff --git a/app/assets/javascripts/header.js.coffee b/app/assets/javascripts/header.js.coffee new file mode 100644 index 000000000..f8c73e3a4 --- /dev/null +++ b/app/assets/javascripts/header.js.coffee @@ -0,0 +1,16 @@ +# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of +# PuzzleTime and licensed under the Affero General Public License version 3 +# or later. See the COPYING file at the top-level directory or at +# https://github.com/puzzle/puzzletime. + +do -> + # are these key codes? :) + kmi_sequence = [38,38,40,40,37,39,37,39,66,65]; + kmi_input = [] + + document.addEventListener 'keydown', (e) -> + kmi_input.push(e.keyCode) + kmi_input.shift() while kmi_input.length > kmi_sequence.length + + if kmi_input.toString() is kmi_sequence.toString() + document.getElementById('navbar-app-title').classList.add('rainbow') diff --git a/app/assets/javascripts/modules/selection_watcher.js.coffee b/app/assets/javascripts/modules/selection_watcher.js.coffee new file mode 100644 index 000000000..4036ed69b --- /dev/null +++ b/app/assets/javascripts/modules/selection_watcher.js.coffee @@ -0,0 +1,73 @@ +# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of +# PuzzleTime and licensed under the Affero General Public License version 3 +# or later. See the COPYING file at the top-level directory or at +# https://github.com/puzzle/puzzletime. + + +app = window.App ||= {} + +class app.SelectionWatcherTrigger + constructor: (event, watchSelectors..., observedClass) -> + @event = event + @watchedElements = watchSelectors.join(', ') + @observedClass = observedClass + +class app.SelectionWatcherAction + constructor: (url) -> + @url = url + +# Update Form by running AJAX request when event fires on watched elements +class app.SelectionWatcher + constructor: (trigger, actions...) -> + @trigger = trigger + @actions = actions + + @_bind() + + _bindClassObserver: -> + observer = new MutationObserver (mutationsList) => + for mutation in mutationsList + return unless mutation.type is 'attributes' and mutation.attributeName is 'class' + + el = mutation.target + if $(el).is(@trigger.watchedElements) + console.log('fire action (class change)') + @_runActionsWithSerializedClass() + break # avoid firing multiple times per batch + + observer.observe document.body, + attributes: true + subtree: true + attributeFilter: ['class'] + + _bind: -> + if @trigger.event in ['classChange'] + @_bindClassObserver() + else + # unbind action before binding, as else we might + # run into problems with turbolinks caching + $(document).off(@trigger.event, @trigger.watchedElements) + + # use a promise chain to sequentially execute actions + $(document).on @trigger.event, @trigger.watchedElements, (event) => + console.log('fire action') + @_runActionsWithSerializedClass() + + _runActionsWithSerializedClass: -> + query = @_serializeClassMatches() + @actions.reduce (promise, action) -> + promise.then -> + new Promise (resolve, reject) -> + $.getScript("#{action.url}?#{query}") + .done(resolve) + .fail(reject) + , Promise.resolve() + + _serializeClassMatches: -> + classSelector = ".#{@trigger.observedClass}" + matching = $(@trigger.watchedElements).filter(classSelector) + + cellIds = matching.map (i, el) -> $(el).data('id') + cellIds = $.makeArray(cellIds).filter (id) -> id? + + $.param({ 'cell_ids[]': cellIds }) \ No newline at end of file diff --git a/app/assets/stylesheets/_header.scss b/app/assets/stylesheets/_header.scss index e70eff587..6b19dc6bb 100644 --- a/app/assets/stylesheets/_header.scss +++ b/app/assets/stylesheets/_header.scss @@ -98,3 +98,16 @@ #breadcrumb { margin-top: $line-height-computed; } + +@keyframes rainbowText { + 0% { color: red; } + 20% { color: orange; } + 40% { color: yellow; } + 60% { color: green; } + 80% { color: blue; } + 100% { color: violet; } +} + +.rainbow { + animation: rainbowText 2s infinite; +} diff --git a/app/controllers/order_plannings_controller.rb b/app/controllers/order_plannings_controller.rb index a2cdb61aa..4efcf7ac4 100644 --- a/app/controllers/order_plannings_controller.rb +++ b/app/controllers/order_plannings_controller.rb @@ -8,6 +8,19 @@ class OrderPlanningsController < Plannings::OrdersController skip_load_and_authorize_resource + # AJAX + def preview_total_selected + cell_ids = params[:cell_ids] || [] + Rails.logger.info("cell_ids: #{cell_ids.inspect}") + Rails.logger.info("params: #{params.inspect}") + @cells = Planning.where(id: cell_ids) + @total_selected_hours = @cells.sum(&:percent) * 8 / 100.0 + + respond_to do |format| + format.js + end + end + private def order diff --git a/app/views/layouts/_headerbar.html.haml b/app/views/layouts/_headerbar.html.haml index 5201ef0e1..f56a639cb 100644 --- a/app/views/layouts/_headerbar.html.haml +++ b/app/views/layouts/_headerbar.html.haml @@ -7,7 +7,7 @@ #headerbar.navbar.navbar-default{role: 'navigation', class: ('lonely' unless @user)} .container-fluid .navbar-header - .navbar-brand + .navbar-brand#navbar-app-title = image_tag 'logo.svg', size: '41x40' PuzzleTime = link_to Puzzletime.version, Puzzletime.changelog_url, class: 'version' diff --git a/app/views/plannings/base/_header.html.haml b/app/views/plannings/base/_header.html.haml index 0e6123a43..818231374 100644 --- a/app/views/plannings/base/_header.html.haml +++ b/app/views/plannings/base/_header.html.haml @@ -7,5 +7,6 @@ %header.planning-board-header %h1.h3= @board.caption .planned{ id: "planned_#{dom_id(@board.subject)}" } - = render 'header_planned' + = render 'header_planned' if !multi + = render 'multi_header_planned' if multi = render 'date_filter' if filter diff --git a/app/views/plannings/base/_header_planned.html.haml b/app/views/plannings/base/_header_planned.html.haml index b0d92e997..cb2039fa5 100644 --- a/app/views/plannings/base/_header_planned.html.haml +++ b/app/views/plannings/base/_header_planned.html.haml @@ -13,8 +13,23 @@ Stunden verplant .header-planned-row.inperiod-sum .header-planned-prefix - Ausgewählter Zeitbereich: + Durch Filter angezeigter Zeitbereich: .header-planned-amount = format_number(@board.total_planned_hours(true), 0) .header-planned-postfix Stunden verplant + .header-planned-row.selected-sum + .header-planned-prefix + Selektierter Zeitbereich: + .header-planned-amount + = render 'preview_total_selected' + .header-planned-postfix + Stunden verplant + + +- content_for(:javascript) do + var recalculate_total_selected_amount_trigger = new window.App.SelectionWatcherTrigger('classChange', '.day', 'ui-selected') + + var calculate_total_action = new window.App.SelectionWatcherAction("#{preview_total_selected_order_order_plannings_path(order_id: @board.order.id, format: :js)}") + + new window.App.SelectionWatcher(recalculate_total_selected_amount_trigger, calculate_total_action); \ No newline at end of file diff --git a/app/views/plannings/base/_multi_header_planned.html.haml b/app/views/plannings/base/_multi_header_planned.html.haml new file mode 100644 index 000000000..d56965b6f --- /dev/null +++ b/app/views/plannings/base/_multi_header_planned.html.haml @@ -0,0 +1,20 @@ +-# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of +-# PuzzleTime and licensed under the Affero General Public License version 3 +-# or later. See the COPYING file at the top-level directory or at +-# https://github.com/puzzle/puzzletime. + +.header-planned-container + .header-planned-row.total-sum + .header-planned-prefix + Total: + .header-planned-amount + = format_number(@board.total_planned_hours, 0) + ' / ' + format_number(@board.total_plannable_hours, 0) + .header-planned-postfix + Stunden verplant + .header-planned-row.inperiod-sum + .header-planned-prefix + Durch Filter angezeigter Zeitbereich: + .header-planned-amount + = format_number(@board.total_planned_hours(true), 0) + .header-planned-postfix + Stunden verplant diff --git a/app/views/plannings/base/_show_multi.html.haml b/app/views/plannings/base/_show_multi.html.haml index 19120dfed..846060a60 100644 --- a/app/views/plannings/base/_show_multi.html.haml +++ b/app/views/plannings/base/_show_multi.html.haml @@ -12,7 +12,7 @@ #boards.layout-column - @boards.each_with_index do |board, i| - @board = board - = render 'header', filter: i == 0 + = render 'header', filter: i == 0, multi: true .planning-board{ id: "board_#{dom_id(board.subject)}" } = render 'board' diff --git a/app/views/plannings/base/show.html.haml b/app/views/plannings/base/show.html.haml index ee43f4a09..3d31ecd7a 100644 --- a/app/views/plannings/base/show.html.haml +++ b/app/views/plannings/base/show.html.haml @@ -5,7 +5,7 @@ - content_for(:content_header) do - = render 'header', filter: true + = render 'header', filter: true, multi: false .planning-board = render 'board' diff --git a/app/views/plannings/employees/_multi_header_planned.html.haml b/app/views/plannings/employees/_multi_header_planned.html.haml new file mode 100644 index 000000000..105918577 --- /dev/null +++ b/app/views/plannings/employees/_multi_header_planned.html.haml @@ -0,0 +1,11 @@ +-# Copyright (c) 2006-2017, Puzzle ITC GmbH. This file is part of +-# PuzzleTime and licensed under the Affero General Public License version 3 +-# or later. See the COPYING file at the top-level directory or at +-# https://github.com/puzzle/puzzletime. + + += with_tooltip("#{format_number(@board.total_planned_hours, 0)}h (inkl. Abwesenheiten) von #{format_number(@board.total_plannable_hours, 0)}h (ohne Feiertage)") do + = format_number(@board.total_planned_hours, 0) + von + = format_number(@board.total_plannable_hours, 0) + Stunden verplant diff --git a/app/views/plannings/orders/_preview_total_selected.html.haml b/app/views/plannings/orders/_preview_total_selected.html.haml new file mode 100644 index 000000000..8cbead9f9 --- /dev/null +++ b/app/views/plannings/orders/_preview_total_selected.html.haml @@ -0,0 +1,2 @@ +#plannings-total-selected-amount + = @total_selected_hours \ No newline at end of file diff --git a/app/views/plannings/orders/preview_total_selected.js.haml b/app/views/plannings/orders/preview_total_selected.js.haml new file mode 100644 index 000000000..a469ad65a --- /dev/null +++ b/app/views/plannings/orders/preview_total_selected.js.haml @@ -0,0 +1 @@ +$("#plannings-total-selected-amount").html('#{j(format_number(@total_selected_hours, 2))}'); \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 81d291375..78eb446f1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -128,6 +128,9 @@ resource :order_plannings, only: %i[index show update destroy] do get 'new', on: :member, as: 'new' + collection do + get :preview_total_selected + end end resource :completed, only: %i[edit update], controller: 'orders/completed' diff --git a/test/integration/plannings_orders_test.rb b/test/integration/plannings_orders_test.rb index e2a80fcdc..23638e3c5 100644 --- a/test/integration/plannings_orders_test.rb +++ b/test/integration/plannings_orders_test.rb @@ -592,6 +592,27 @@ class PlanningsOrdersTest < ActionDispatch::IntegrationTest text: '14') end + test 'dragging over entries correctly sets the worktime in hours of the selected area' do + assert_percents ['50', '', '', '', '', ''], row_mark + assert_percents ['25', '', '', '', '', ''], row_pascal + + page.assert_selector("#planned_order_#{orders(:puzzletime).id} .selected-sum .header-planned-amount", text: '0.00') + + drag(row_mark.all('.day')[0], row_mark.all('.day')[2]) + + page.assert_selector('.-selected', count: 3) + page.assert_selector("#planned_order_#{orders(:puzzletime).id} .selected-sum .header-planned-amount", text: '4.00') + find('.planning-cancel').click + + drag(row_mark.all('.day')[0], row_pascal.all('.day')[0]) + + page.assert_selector('.-selected', count: 2) + page.assert_selector("#planned_order_#{orders(:puzzletime).id} .selected-sum .header-planned-amount", text: '6.00') + find('.planning-cancel').click + + page.assert_selector("#planned_order_#{orders(:puzzletime).id} .selected-sum .header-planned-amount", text: '0.00') + end + private def workdays_next_n_months(n, date = Time.zone.today)