aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLuc Donnet2018-02-02 16:00:39 +0100
committerGitHub2018-02-02 16:00:39 +0100
commit5f240b2ceea25c13b054baaea6aa5cdc982793cf (patch)
treefdd66161ec12034b5d0eeed634a80d105e52c94b
parent9f30b2debe7ece403d9e588df9763c119436d967 (diff)
parentc086dded51711e0bd1ec852196cf66ea35a7e2b8 (diff)
downloadchouette-core-5f240b2ceea25c13b054baaea6aa5cdc982793cf.tar.bz2
Merge pull request #250 from af83/3542-referential-overview
3542 referential overview
-rw-r--r--app/assets/stylesheets/base/_config.sass1
-rw-r--r--app/assets/stylesheets/base/_utilities.sass2
-rw-r--r--app/assets/stylesheets/components/_referential_overview.sass346
-rw-r--r--app/helpers/pagination_helper.rb2
-rw-r--r--app/helpers/referentials_helper.rb5
-rw-r--r--app/javascript/packs/referential_overview/overview.js1
-rw-r--r--app/javascript/referential_overview/index.coffee113
-rw-r--r--app/services/referential_overview.rb256
-rw-r--r--app/views/merges/show.html.slim3
-rw-r--r--app/views/referentials/_overview.html.slim75
-rw-r--r--app/views/referentials/show.html.slim2
-rw-r--r--config/locales/referentials.en.yml8
-rw-r--r--config/locales/referentials.fr.yml8
-rw-r--r--spec/services/referential_overview_spec.rb154
14 files changed, 974 insertions, 2 deletions
diff --git a/app/assets/stylesheets/base/_config.sass b/app/assets/stylesheets/base/_config.sass
index 2c226357f..0fff1dd9c 100644
--- a/app/assets/stylesheets/base/_config.sass
+++ b/app/assets/stylesheets/base/_config.sass
@@ -17,6 +17,7 @@ $blue: #007fbb
$darkgrey: #4b4b4b
$grey: #a4a4a4
$lightgrey: lighten($grey, 25)
+$lightergrey: lighten($lightgrey,5)
$green: #70b12b
$red: #da2f36
diff --git a/app/assets/stylesheets/base/_utilities.sass b/app/assets/stylesheets/base/_utilities.sass
index 24f2038f0..fbda5630d 100644
--- a/app/assets/stylesheets/base/_utilities.sass
+++ b/app/assets/stylesheets/base/_utilities.sass
@@ -44,7 +44,7 @@
// Empty zones
=emptyzone($col1, $col2)
- background-image: linear-gradient(-45deg, $col1 25%, $col2 25%, $col2 50%, $col1 50%, $col1 75%, $col2 75%, transparent)
+ background-image: linear-gradient(-45deg, $col1 25%, $col2 25%, $col2 50%, $col1 50%, $col1 75%, $col2 75%, $col2)
background-size: 40px 40px
.cellwrap
diff --git a/app/assets/stylesheets/components/_referential_overview.sass b/app/assets/stylesheets/components/_referential_overview.sass
new file mode 100644
index 000000000..0beb8ab67
--- /dev/null
+++ b/app/assets/stylesheets/components/_referential_overview.sass
@@ -0,0 +1,346 @@
+.referential-overview
+ $left-size: 100px
+ $line-height: 60px
+ margin-top: 50px
+ overflow: hidden
+ .time-travel, .filters
+ background-color: $lightergrey
+ padding: 10px
+ float: right
+ border-top-left-radius: 4px
+ border-top-right-radius: 4px
+ border: 1px solid $lightgrey
+ border-bottom: none
+ position: relative
+ &:after
+ position: absolute
+ content: ""
+ left: 0
+ top: 100%
+ right: 0
+ height: 10px
+ box-shadow: 0 0 10px rgba(0,0,0,0.5)
+ z-index: 1
+ .time-travel
+ padding-top: 4px
+ padding-bottom: 4px
+ a.btn:first-child
+ margin-right: 1px
+ a.btn:last-child
+ margin-right: 1px
+
+ max-width: 33%
+ .btn-group, .form-group
+ position: relative
+ z-index: 2
+ .form-group
+ margin-left: 10px
+ margin-bottom: 0
+ display: inline-block
+ input
+ padding: 6px 5px
+ border: 1px solid $lightgrey
+ outline: none
+ height: 34px
+ border-radius: 4px
+ padding-right: 25px
+ a
+ padding: 4px
+ margin-top: 2px
+ margin-left: -25px
+ .filters
+ float: left
+ max-width: 66%
+ padding: 0
+ form
+ background: transparent
+ display: flex
+ .ffg-row
+ border-color: $grey
+ .form-group
+ border-color: $grey
+ width: auto
+ flex: 1 1
+ padding: 4px 11px 5px
+ .input-group-btn
+ right: 10px
+ &.togglable
+ padding-top: 6px
+ padding-bottom: 5px
+ &:before
+ top: 0px
+
+ .overview-table
+ position: relative
+ z-index: 2
+ border: 1px solid $grey
+ clear: both
+ display: flex
+ +emptyzone($lightgrey, $lightergrey)
+ .head
+
+ height: $left-size
+ .line
+ height: $line-height
+ .left
+ flex: 0 0
+ background: $lightergrey
+ min-width: $left-size
+ overflow: hidden
+ border-right: 1px solid white
+ .head
+ position: relative
+ border-bottom: 1px solid $grey
+ border-right: 1px solid $lightgrey
+ .dates, .lines
+ position: absolute
+ font-size: 0.8em
+ z-index: 2
+ .dates
+ right: 20px
+ top: 20px
+ .lines
+ left: 20px
+ bottom: 20px
+ &:after
+ position: absolute
+ border-left: ($left-size - 2px)/2 solid transparent
+ border-bottom: ($left-size - 2px)/2 solid transparent
+ border-right: ($left-size - 2px)/2 solid white
+ border-top: ($left-size - 2px)/2 solid white
+ z-index: 1
+ top: 0
+ right: 0
+ width: 0
+ content: ""
+ .line
+ padding: 7px 10px
+ border-bottom: 1px solid $grey
+ font-size: 0.8em
+ &:last-child
+ border-bottom: none
+ .number
+ border-radius: 100px
+ display: inline-block
+ min-width: 20px
+ height: 20px
+ text-align: center
+ padding: 1px 4px
+ text-decoration: none
+ color: black
+ border: 1px solid $grey
+ max-width: 100%
+ white-space: nowrap
+ text-overflow: ellipsis
+ overflow: hidden
+ .name
+ display: inline-block
+ width: $left-size - 50px()
+ white-space: nowrap
+ line-height: 20px
+ margin-left: 5px
+ text-overflow: ellipsis
+ overflow: hidden
+ vertical-align: bottom
+ color: black
+ text-decoration: none
+
+ .company, .mode
+ font-size: 0.9em
+ white-space: nowrap
+ text-overflow: ellipsis
+ overflow: hidden
+ margin-top: -2px
+ .mode
+ text-transform: uppercase
+ color: $grey
+ font-weight: bold
+ .right
+ flex: 1 1
+ overflow: hidden
+ .inner .lines
+ transition: margin-left 0.5s
+ .head
+ white-space: nowrap
+ position: relative
+ z-index: 3
+ &:after, &:before
+ opacity: 0
+ // transition: opacity 0.5s
+ content: ""
+ position: absolute
+ left: -1000px
+ right: 100%
+ top: 0px
+ bottom: 0
+ background: $darkblue
+ z-index: 11
+ border-top: 1px solid white
+ .week
+ display: inline-block
+ position: relative
+ height: 100%
+ transition: margin 0.5s
+ background: white
+ &:last-child
+ box-shadow: 0 -10px 10px rgba(0,0,0,0.5)
+ .week-span
+ left: 15px
+ top: 15px
+ right: 30px
+ white-space: nowrap
+ overflow: hidden
+ text-overflow: ellipsis
+ position: absolute
+
+ .week-number
+ background-color: $lightgrey
+ color: $grey
+ position: absolute
+ top: 0
+ right: 0
+ padding: 2px 4px
+
+ &:after
+ position: absolute
+ right: 0
+ top: 0
+ bottom: 0
+ background: $grey
+ width: 1px
+ content: ""
+
+ &:last-child:after
+ display: none
+
+ .days
+ position: relative
+ top: 50%
+ height: 50%
+ border-top: 1px solid $grey
+ border-bottom: 1px solid $grey
+
+ .day
+ float: left
+ border-left: 1px solid $grey
+ box-sizing: border-box
+ padding-left: 5px
+ padding-top: 3px
+ position: relative
+ height: 100%
+ .name, .number
+ position: absolute
+ left: 10px
+ right: 10px
+ top: 50%
+ transform: translateY(-50%)
+ margin-top: 10px
+ .name
+ font-weight: bold
+ font-size: 0.8em
+ margin-top: -10px
+ &:first-child
+ border: none
+ &.weekend
+ background: $lightergrey
+ &.selected, &:hover
+ color: $blue
+ background-color: transparentize($blue, 0.7)
+ &:after
+ content: ""
+ left: -1px
+ right: -1px
+ top: 100%
+ height: 10000px
+ background-color: transparentize($blue, 0.7)
+ position: absolute
+ z-index: 4
+ &:hover
+ background-color: transparentize(white, 0.7)
+ &:after
+ background-color: transparentize(white, 0.7)
+
+ .line
+ border-bottom: 1px solid $grey
+ position: relative
+ overflow: hidden
+ box-shadow: 0 -10px 10px rgba(0,0,0,0.5)
+
+ &:last-child
+ border-bottom: none
+
+ .period
+ height: 100%
+ top: 0
+ background: #aedd8a
+ position: absolute
+ box-shadow: 0 0 10px rgba(0,0,0,0.5)
+ z-index: 2
+ .title
+ position: absolute
+ left: 12px
+ top: 50%
+ margin-top: -6px
+ transform: translateY(-50%)
+ background-color: transparentize(white, 0.25)
+ padding: 5px
+ font-size: 0.7em
+ border-radius: 5px
+ transition: margin-left 0.5s
+ max-width: calc(100% - 24px)
+ margin-right: 12px
+ &:after
+ content: ""
+ position: absolute
+ bottom: 1px
+ left: 0
+ right: 0
+ height: 10px
+ background: white
+ opacity: 0.25
+ z-index: 3
+ &.empty
+ z-index: 1
+ background: rgb(244, 67, 67)
+ background: repeating-linear-gradient(-45deg, #f5e1cf,#f5e1cf 12px,#e49393 12px,#e49393 25px)
+ &.accepted
+ background: #f19039
+ background: repeating-linear-gradient(-45deg, #f5e1cf,#f5e1cf 12px,#f19039 12px,#f19039 25px)
+ &:hover
+ z-index: 3
+ &:after
+ opacity: 0.5
+ .title
+ background-color: transparentize(white, 0.1)
+
+ &.sticky
+ .time-travel
+ position: fixed
+ bottom: 0
+ z-index: 15
+ right: 35px
+ box-shadow: 0 0 10px rgba(0,0,0,0.5)
+
+ .overview-table .right
+ .lines
+ margin-top: $left-size
+ .head
+ position: fixed
+ top: 80px
+ z-index: 10
+ background: white
+ height: 50px
+ right: 51px
+ left: 51px + $left-size
+ // overflow-x: hidden
+ &:after, &:before
+ opacity: 1
+ &:after
+ left: 100%
+ right: -1000px
+ .week-span, .week-number
+ display: none
+ .days
+ height: 100%
+ top: 0
+ border-top: 1px solid white
diff --git a/app/helpers/pagination_helper.rb b/app/helpers/pagination_helper.rb
index 02eec39dc..9b6042377 100644
--- a/app/helpers/pagination_helper.rb
+++ b/app/helpers/pagination_helper.rb
@@ -25,7 +25,7 @@ module PaginationHelper
if collection.total_pages > 1
links = content_tag :div, '', class: 'page_links' do
- will_paginate collection, container: false, page_links: false, previous_label: '', next_label: ''
+ will_paginate collection, container: false, page_links: false, previous_label: '', next_label: '', param_name: collection.try(:pagination_param_name)
end
content_tag :div, pinfos.concat(links).html_safe, class: "pagination #{cls}"
diff --git a/app/helpers/referentials_helper.rb b/app/helpers/referentials_helper.rb
index 01e5a5879..8251377aa 100644
--- a/app/helpers/referentials_helper.rb
+++ b/app/helpers/referentials_helper.rb
@@ -10,4 +10,9 @@ module ReferentialsHelper
t('true')
end
end
+
+ def referential_overview referential
+ service = ReferentialOverview.new referential, self
+ render partial: "referentials/overview", locals: {referential: referential, overview: service}
+ end
end
diff --git a/app/javascript/packs/referential_overview/overview.js b/app/javascript/packs/referential_overview/overview.js
new file mode 100644
index 000000000..59c326e9a
--- /dev/null
+++ b/app/javascript/packs/referential_overview/overview.js
@@ -0,0 +1 @@
+import ReferentialOverview from '../../referential_overview'
diff --git a/app/javascript/referential_overview/index.coffee b/app/javascript/referential_overview/index.coffee
new file mode 100644
index 000000000..0e6541421
--- /dev/null
+++ b/app/javascript/referential_overview/index.coffee
@@ -0,0 +1,113 @@
+class TimeTravel
+ constructor: (@overview)->
+ @container = @overview.container.find('.time-travel')
+ @todayBt = @container.find(".today")
+ @prevBt = @container.find(".prev-page")
+ @nextBt = @container.find(".next-page")
+ @searchDateBt = @container.find("a.search-date")
+ @searchDateInput = @container.find("input.date-search")
+ @initButtons()
+
+ initButtons: ->
+ @prevBt.click (e)=>
+ @overview.prevPage()
+ e.preventDefault()
+ false
+
+ @nextBt.click (e)=>
+ @overview.nextPage()
+ e.preventDefault()
+ false
+
+ @todayBt.click (e)=>
+ today = new Date()
+ month = today.getMonth() + 1
+ month = "0#{month}" if month < 10
+ day = today.getDate()
+ day = "0#{month}" if day < 10
+ @overview.showDay "#{today.getFullYear()}-#{month}-#{day}"
+ e.preventDefault()
+ false
+
+ @searchDateBt.click (e)=>
+ @overview.showDay @searchDateInput.val() if @searchDateInput.val().length > 0
+ e.preventDefault()
+ false
+
+ scrolledTo: (progress)->
+ @prevBt.removeClass 'disabled'
+ @nextBt.removeClass 'disabled'
+ @prevBt.addClass 'disabled' if progress == 0
+ @nextBt.addClass 'disabled' if progress == 1
+
+class window.ReferentialOverview
+ constructor: (selector)->
+ @container = $(selector)
+ @timeTravel = new TimeTravel(this)
+ @currentOffset = 0
+ $(document).scroll (e)=>
+ @documentScroll(e)
+ @documentScroll pageY: $(document).scrollTop()
+
+ showDay: (date)->
+ day = @container.find(".day.#{date}")
+ @container.find(".day.selected").removeClass('selected')
+ day.addClass "selected"
+ offset = day.offset().left
+ parentOffset = @currentOffset + @container.find(".right").offset().left
+ @scrollTo parentOffset - offset
+
+ currentOffset: ->
+ @container.find(".right .inner").offset().left
+
+ top: ->
+ @_top ||= @container.find('.days').offset().top - 80
+ bottom: ->
+ @_bottom ||= @top() + @container.height() - 50
+
+ prevPage: ->
+ @scrollTo @currentOffset + @container.find(".right").width()
+
+ nextPage: ->
+ @scrollTo @currentOffset - @container.find(".right").width()
+
+ minOffset: ->
+ @_minOffset ||= @container.find(".right").width() - @container.find(".right .line").width()
+ @_minOffset
+
+ scrollTo: (offset)->
+ @currentOffset = offset
+ @currentOffset = Math.max(@currentOffset, @minOffset())
+ @currentOffset = Math.min(@currentOffset, 0)
+ @container.find(".right .inner .lines").css "margin-left": "#{@currentOffset}px"
+ @container.find(".head .week:first-child").css "margin-left", "#{@currentOffset}px"
+ @timeTravel.scrolledTo 1 - (@minOffset() - @currentOffset) / @minOffset()
+ setTimeout =>
+ @movePeriodTitles()
+ , 600
+
+ movePeriodTitles: ->
+ @_right_offset ||= @container.find('.right').offset().left
+ @container.find(".shifted").removeClass("shifted").css "margin-left", 0
+ @container.find(".right .line").each (i, l) =>
+ $(l).find(".period").each (i, _p) =>
+ p = $(_p)
+ offset = parseInt(p.css("left")) + @currentOffset
+ if offset < 0 && - offset < p.width()
+ offset = Math.min(-offset, p.width() - 100)
+ p.find(".title").addClass("shifted").css "margin-left", offset + "px"
+ return
+
+ documentScroll: (e)->
+ if @sticky
+ if e.pageY < @top() || e.pageY > @bottom()
+ @container.removeClass "sticky"
+ @sticky = false
+ else
+ if e.pageY > @top() && e.pageY < @bottom()
+ @sticky = true
+ @container.addClass "sticky"
+
+
+
+export default ReferentialOverview
diff --git a/app/services/referential_overview.rb b/app/services/referential_overview.rb
new file mode 100644
index 000000000..ccfe0617a
--- /dev/null
+++ b/app/services/referential_overview.rb
@@ -0,0 +1,256 @@
+class ReferentialOverview
+ attr_reader :h
+ attr_reader :referential
+
+ PER_PAGE = 10
+
+ def initialize referential, h=nil
+ @referential = referential
+ @page = h && h.params[pagination_param_name]&.to_i || 1
+ @h = h
+ end
+
+ def lines
+ filtered_lines.includes(:company).map{|l| Line.new(l, @referential, period.first, h)}
+ end
+
+ def period
+ @period ||= @referential.metadatas_period || []
+ end
+
+ def includes_today?
+ period.include? Time.now.to_date
+ end
+
+ def weeks
+ @weeks = {}
+ period.map do |d|
+ @weeks[Week.key(d)] ||= Week.new(d, period.last, h)
+ end
+ @weeks.values
+ end
+
+ def referential_lines
+ @referential.metadatas_lines
+ end
+
+ def filtered_lines
+ search.result.page(@page).per_page(PER_PAGE)
+ end
+
+ ### Pagination
+
+ delegate :empty?, :first, :total_pages, :size, :total_entries, :offset, :length, to: :filtered_lines
+ def current_page
+ @page
+ end
+
+ ### search
+ def search
+ lines = referential_lines
+ lines = lines.search h.params[search_param_name]
+ lines
+ end
+
+ def pagination_param_name
+ "referential_#{@referential.slug}_overview"
+ end
+
+ def search_param_name
+ "q_#{pagination_param_name}"
+ end
+
+ class Line
+ attr_reader :h
+ attr_reader :referential_line
+
+ delegate :name, :number, :company, :color, :transport_mode, to: :referential_line
+
+ def initialize line, referential, start, h
+ @referential_line = line
+ @referential = referential
+ @start = start
+ @h = h
+ end
+
+ def period
+ @period ||= @referential.metadatas_period || []
+ end
+
+ def referential_periods
+ @referential_periods ||= @referential.metadatas.include_lines([@referential_line.id]).map(&:periodes).flatten.sort{|p1, p2| p1.first <=> p2.first}
+ end
+
+ def periods
+ @periods ||= begin
+ periods = referential_periods.flatten.map{|p| Period.new p, @start, h}
+ periods = fill_periods periods
+ periods = merge_periods periods
+ periods
+ end
+ end
+
+ def fill_periods periods
+ [].tap do |out|
+ previous = OpenStruct.new(end: period.first - 1.day)
+ (periods + [OpenStruct.new(start: period.last + 1.day)]).each do |p|
+ if p.start > previous.end + 1.day
+ out << Period.new((previous.end+1.day..p.start-1.day), @start, h).tap{|p| p.empty = true}
+ end
+ out << p if p.respond_to?(:end)
+ previous = p
+ end
+ end
+ end
+
+ def merge_periods periods
+ [].tap do |out|
+ current = periods.first
+ periods[1..-1].each do |p|
+ if p.start <= current.end
+ current.end = p.end
+ else
+ out << current
+ current = p
+ end
+ end
+ out << current
+ end
+ end
+
+ def width
+ period.count * Day::WIDTH
+ end
+
+ def html_style
+ {
+ width: "#{width}px"
+ }.map{|k, v| "#{k}: #{v}"}.join("; ")
+ end
+
+ def html_class
+ out = []
+ out
+ end
+
+ class Period
+ attr_accessor :empty
+ attr_accessor :h
+
+ def initialize period, start, h
+ @period = period
+ @start = start
+ @empty = false
+ @h = h
+ end
+
+ def start
+ @period.first
+ end
+
+ def end
+ @period.last
+ end
+
+ def end= val
+ @period = (start..val)
+ end
+
+ def width
+ @period.count * Day::WIDTH
+ end
+
+ def left
+ (@period.first - @start).to_i * Day::WIDTH
+ end
+
+ def html_style
+ {
+ width: "#{width}px",
+ left: "#{left}px",
+ }.map{|k, v| "#{k}: #{v}"}.join("; ")
+ end
+
+ def empty?
+ @empty
+ end
+
+ def accepted?
+ @period.count < 7
+ end
+
+ def title
+ h.l(self.start, format: :short) + " - " + h.l(self.end, format: :short)
+ end
+
+ def html_class
+ out = []
+ out << "empty" if empty?
+ out << "accepted" if accepted?
+ out
+ end
+ end
+ end
+
+ class Week
+ attr_reader :h
+ attr_reader :start_date
+ attr_reader :end_date
+
+ def initialize start_date, boundary, h
+ @start_date = start_date.to_date
+ @end_date = [start_date.end_of_week, boundary].min.to_date
+ @h = h
+ end
+
+ def self.key date
+ date.beginning_of_week.to_s
+ end
+
+ def span
+ h.l(@start_date, format: "#{@start_date.day}-#{@end_date.day} %b")
+ end
+
+ def number
+ h.l(@start_date, format: "%W")
+ end
+
+ def period
+ (@start_date..@end_date)
+ end
+
+ def days
+ period.map {|d| Day.new d, h }
+ end
+ end
+
+ class Day
+ attr_reader :h
+
+ WIDTH=50
+
+ def initialize date, h
+ @date = date
+ @h = h
+ end
+
+ def html_style
+ {width: "#{WIDTH}px"}.map{|k, v| "#{k}: #{v}"}.join("; ")
+ end
+
+ def html_class
+ out = [h.l(@date, format: "%Y-%m-%d")]
+ out << "weekend" if [0, 6].include?(@date.wday)
+ out << "today" if @date == Time.now.to_date
+ out
+ end
+
+ def short_name
+ h.l(@date, format: "%a")
+ end
+
+ def number
+ @date.day
+ end
+ end
+end
diff --git a/app/views/merges/show.html.slim b/app/views/merges/show.html.slim
index 47e5aa029..a94552c0d 100644
--- a/app/views/merges/show.html.slim
+++ b/app/views/merges/show.html.slim
@@ -12,3 +12,6 @@
@merge.class.human_attribute_name(:created_at) => @merge.created_at ? l(@merge.created_at) : '-',
@merge.class.human_attribute_name(:started_at) => @merge.started_at ? l(@merge.started_at) : '-',
@merge.class.human_attribute_name(:ended_at) => @merge.ended_at ? l(@merge.ended_at) : '-' }
+
+ - if resource.output.current
+ = referential_overview resource.output.current
diff --git a/app/views/referentials/_overview.html.slim b/app/views/referentials/_overview.html.slim
new file mode 100644
index 000000000..143784800
--- /dev/null
+++ b/app/views/referentials/_overview.html.slim
@@ -0,0 +1,75 @@
+.referential-overview id=overview.pagination_param_name
+ .filters
+ = search_form_for overview.search, as: overview.search_param_name, url: "##{overview.pagination_param_name}", html: {method: :get}, class: 'form form-filter' do |f|
+ .ffg-row
+ .form-group.input-group.search_bar
+ = f.search_field :name_or_number_or_objectid_cont, placeholder: t('lines.index.name_or_number_or_objectid'), class: 'form-control'
+ span.input-group-btn
+ button.btn.btn-default#search-btn type='submit'
+ span.fa.fa-search
+ .form-group.togglable
+ = f.label Chouette::Line.human_attribute_name(:company_id), required: false, class: 'control-label'
+ = f.input :company_id_eq_any, collection: overview.referential_lines.map(&:company).uniq.sort_by(&:name), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + l.name + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'}
+
+ .form-group.togglable
+ = f.label Chouette::Line.human_attribute_name(:transport_mode), required: false, class: 'control-label'
+ = f.input :transport_mode_eq_any, collection: overview.referential_lines.map(&:transport_mode).uniq.sort, as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + t("enumerize.transport_mode.#{l}") + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list'}
+
+ .actions
+ = link_to 'Effacer', url_for() + "##{overview.pagination_param_name}", class: 'btn btn-link'
+ = f.submit 'Filtrer', class: 'btn btn-default'
+
+ .time-travel
+ .btn-group
+ = link_to I18n.t("referentials.overview.head.prev_page"), '#', class: "prev-page btn btn-default disabled"
+ = link_to I18n.t("referentials.overview.head.today"), '#', class: "today btn btn-default #{overview.includes_today? ? '' : 'disabled'}"
+ = link_to I18n.t("referentials.overview.head.next_page"), '#', class: "next-page btn btn-default"
+ .form-group
+ input.date-search type="date" min=overview.period.first max=overview.period.last
+ a.search-date href='#'
+ span.fa.fa-search
+ .overview-table
+ .left
+ .head
+ .dates= I18n.t("referentials.overview.head.dates")
+ .lines= I18n.t("referentials.overview.head.lines")
+ .lines
+ - overview.lines.each do |line|
+ .line
+ a.number style="background-color: #{line.color.present? ? "##{line.color}" : 'whitesmoke'}" title=line.name
+ = line.number
+ .company= line.company&.name
+ .mode= t("enumerize.transport_mode.#{line.transport_mode}")
+ .right
+ .inner
+ .head
+ - overview.weeks.each do |week|
+ .week
+ .week-span= week.span
+ .week-number= week.number
+ .days
+ - week.days.each do |day|
+ .day style=day.html_style class=day.html_class
+ .name= day.short_name
+ .number= day.number
+ .lines
+ - if overview.lines.any?
+ - overview.lines.each do |line|
+ .line style=line.html_style class=line.html_class
+ - line.periods.each do |period|
+ .period style=period.html_style class=period.html_class
+ .title=period.title
+ - else
+ = replacement_msg t('referential_lines.search_no_results')
+
+ = new_pagination overview, 'pull-right'
+
+- content_for :javascript do
+ = javascript_pack_tag 'referential_overview/overview.js'
+
+ javascript:
+ overview_id = "#{overview.pagination_param_name}";
+
+ coffee:
+ $ =>
+ new ReferentialOverview("##{overview_id}")
diff --git a/app/views/referentials/show.html.slim b/app/views/referentials/show.html.slim
index 6c88f5b81..289e802d7 100644
--- a/app/views/referentials/show.html.slim
+++ b/app/views/referentials/show.html.slim
@@ -67,6 +67,8 @@
= replacement_msg t('referential_lines.search_no_results')
+ = referential_overview resource
+
/ Modal(s)
= modalbox 'purgeModal' do
= simple_form_for [@referential, CleanUp.new] do |f|
diff --git a/config/locales/referentials.en.yml b/config/locales/referentials.en.yml
index eb8eae98d..b7483c0aa 100644
--- a/config/locales/referentials.en.yml
+++ b/config/locales/referentials.en.yml
@@ -48,6 +48,14 @@ en:
overlapped_referential: "%{referential} cover the same perimeter"
overlapped_period: "Another period is on the same period"
short_period: Min period length is two days
+ overview:
+ head:
+ dates: Dates
+ lines: Lignes
+ today: Today
+ prev_page: Prev. page
+ next_page: Next page
+
activerecord:
models:
referential:
diff --git a/config/locales/referentials.fr.yml b/config/locales/referentials.fr.yml
index 37af8a4eb..53183a4c2 100644
--- a/config/locales/referentials.fr.yml
+++ b/config/locales/referentials.fr.yml
@@ -48,6 +48,14 @@ fr:
overlapped_referential: "%{referential} couvre le même périmètre d'offre"
overlapped_period: "Une autre période chevauche cette période"
short_period: "La durée minimum d'une période est de deux jours"
+ overview:
+ head:
+ dates: Dates
+ lines: Lignes
+ today: Aujourd'hui
+ prev_page: ←
+ next_page: →
+
activerecord:
models:
referential:
diff --git a/spec/services/referential_overview_spec.rb b/spec/services/referential_overview_spec.rb
new file mode 100644
index 000000000..0ce29643c
--- /dev/null
+++ b/spec/services/referential_overview_spec.rb
@@ -0,0 +1,154 @@
+RSpec.describe ReferentialOverview do
+
+ subject{ described_class }
+
+end
+
+RSpec.describe ReferentialOverview::Week do
+
+ describe "#initialize" do
+ it "should respect the boundary" do
+ week = ReferentialOverview::Week.new(Time.now.beginning_of_week, 1.week.from_now, {})
+ expect(week.start_date).to eq Time.now.beginning_of_week.to_date
+ expect(week.end_date).to eq Time.now.end_of_week.to_date
+
+ week = ReferentialOverview::Week.new(Time.now.beginning_of_week, Time.now, {})
+ expect(week.start_date).to eq Time.now.beginning_of_week.to_date
+ expect(week.end_date).to eq Time.now.to_date
+ end
+ end
+end
+
+RSpec.describe ReferentialOverview::Line do
+
+ let(:ref_line){create(:line)}
+ let(:referential){create(:referential)}
+ let(:start){2.days.ago}
+ let(:period_1){(10.days.ago..8.days.ago)}
+ let(:period_2){(5.days.ago..1.days.ago)}
+
+ subject(:line){ReferentialOverview::Line.new ref_line, referential, start, {}}
+
+ before(:each) do
+ create(:referential_metadata, referential: referential, line_ids: [ref_line.id], periodes: [period_1, period_2].compact)
+ end
+
+ describe "#width" do
+ it "should have the right value" do
+ expect(line.width).to eq ReferentialOverview::Day::WIDTH * referential.metadatas_period.count
+ end
+ end
+
+ describe "#periods" do
+ context "when the periodes are split into several metadatas" do
+ let(:period_2){nil}
+ before do
+ create(:referential_metadata, referential: referential, line_ids: [ref_line.id], periodes: [(17.days.ago..11.days.ago)])
+ end
+ it "should find them all" do
+ expect(line.periods.count).to eq 2
+ expect(line.periods[0].empty?).to be_falsy
+ expect(line.periods[1].empty?).to be_falsy
+ end
+
+ context "when the periodes overlap" do
+ let(:period_2){nil}
+ before do
+ create(:referential_metadata, referential: referential, line_ids: [ref_line.id], periodes: [(17.days.ago..9.days.ago)])
+ end
+ it "should merge them" do
+ expect(line.periods.count).to eq 1
+ expect(line.periods[0].empty?).to be_falsy
+ expect(line.periods[0].start).to eq 17.days.ago.to_date
+ expect(line.periods[0].end).to eq 8.days.ago.to_date
+ end
+ end
+ end
+ end
+
+ describe "#fill_periods" do
+ it "should fill the voids" do
+ expect(line.periods.count).to eq 3
+ expect(line.periods[0].empty?).to be_falsy
+ expect(line.periods[1].empty?).to be_truthy
+ expect(line.periods[1].accepted?).to be_truthy
+ expect(line.periods[2].empty?).to be_falsy
+ end
+
+ context "with no void" do
+ let(:period_1){(10.days.ago..8.days.ago)}
+ let(:period_2){(7.days.ago..1.days.ago)}
+
+ it "should find no void" do
+ expect(line.periods.count).to eq 2
+ expect(line.periods[0].empty?).to be_falsy
+ expect(line.periods[1].empty?).to be_falsy
+ end
+ end
+
+ context "with a large void" do
+ let(:period_1){(20.days.ago..19.days.ago)}
+ let(:period_2){(2.days.ago..1.days.ago)}
+
+ it "should fill the void" do
+ expect(line.periods.count).to eq 3
+ expect(line.periods[0].empty?).to be_falsy
+ expect(line.periods[1].empty?).to be_truthy
+ expect(line.periods[1].accepted?).to be_falsy
+ expect(line.periods[2].empty?).to be_falsy
+ end
+ end
+
+ context "with a void at the end" do
+ before do
+ create(:referential_metadata, referential: referential, periodes: [(2.days.ago..1.days.ago)])
+ end
+ let(:period_1){(20.days.ago..19.days.ago)}
+ let(:period_2){nil}
+
+ it "should fill the void" do
+ expect(line.periods.count).to eq 2
+ expect(line.periods[0].empty?).to be_falsy
+ expect(line.periods[1].empty?).to be_truthy
+ expect(line.periods[1].accepted?).to be_falsy
+ expect(line.periods[1].end).to eq 1.days.ago.to_date
+ end
+ end
+
+ context "with a void at the beginning" do
+ before do
+ create(:referential_metadata, referential: referential, periodes: [(200.days.ago..199.days.ago)])
+ end
+ let(:period_1){(20.days.ago..19.days.ago)}
+ let(:period_2){nil}
+
+ it "should fill the void" do
+ expect(line.periods.count).to eq 2
+ expect(line.periods[0].start).to eq 200.days.ago.to_date
+ expect(line.periods[0].empty?).to be_truthy
+ expect(line.periods[0].accepted?).to be_falsy
+ expect(line.periods[1].empty?).to be_falsy
+ end
+ end
+ end
+end
+
+RSpec.describe ReferentialOverview::Line::Period do
+
+ let(:period){(1.day.ago.to_date..Time.now.to_date)}
+ let(:start){2.days.ago.to_date}
+
+ subject(:line_period){ReferentialOverview::Line::Period.new period, start, nil}
+
+ describe "#width" do
+ it "should have the right value" do
+ expect(line_period.width).to eq ReferentialOverview::Day::WIDTH * 2
+ end
+ end
+
+ describe "#left" do
+ it "should have the right value" do
+ expect(line_period.left).to eq ReferentialOverview::Day::WIDTH * 1
+ end
+ end
+end