aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorZog2018-01-19 08:22:24 +0100
committerZog2018-02-02 14:31:32 +0100
commitc13c94fe665fdbe2c434c8bfadd44a0988ecea60 (patch)
treecbf70b0af9dd0bbbe4129384adcf5db39117fe33
parent9f30b2debe7ece403d9e588df9763c119436d967 (diff)
downloadchouette-core-c13c94fe665fdbe2c434c8bfadd44a0988ecea60.tar.bz2
Refs #3542 @4h; First UI
Still missing: - Pagination - Filters
-rw-r--r--app/assets/stylesheets/components/_referential_overview.sass171
-rw-r--r--app/helpers/referentials_helper.rb5
-rw-r--r--app/services/referential_overview.rb218
-rw-r--r--app/views/referentials/_overview.html.slim31
-rw-r--r--app/views/referentials/show.html.slim2
-rw-r--r--config/locales/referentials.en.yml5
-rw-r--r--config/locales/referentials.fr.yml5
-rw-r--r--spec/services/referential_overview_spec.rb154
8 files changed, 591 insertions, 0 deletions
diff --git a/app/assets/stylesheets/components/_referential_overview.sass b/app/assets/stylesheets/components/_referential_overview.sass
new file mode 100644
index 000000000..0af5a99f7
--- /dev/null
+++ b/app/assets/stylesheets/components/_referential_overview.sass
@@ -0,0 +1,171 @@
+.referential-overview
+ display: flex
+ margin-top: 50px
+ $left-size: 100px
+ .head
+ height: $left-size
+ border-top: 1px solid $lightgrey
+ .line
+ height: 80px
+ .left
+ flex: 0 0
+ background: $lightgrey
+ min-width: $left-size
+ overflow: hidden
+ border-right: 1px solid white
+ .head
+ position: relative
+ border-bottom: 1px solid white
+ 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: 15px 10px 10px
+ border-bottom: 1px solid white
+ font-size: 0.8em
+ &:last-child
+ border-bottom: 1px solid $grey
+ .number
+ border-radius: 100px
+ display: inline-block
+ width: 20px
+ height: 20px
+ text-align: center
+ padding-top: 1px
+ text-decoration: none
+ color: black
+ border: 1px solid $grey
+ .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
+ margin-top: 5px
+ white-space: nowrap
+ text-overflow: ellipsis
+ overflow: hidden
+
+ .mode
+ margin-top: 0
+ text-transform: uppercase
+ color: $grey
+ font-weight: bold
+ .right
+ flex: 1 1
+ overflow-x: scroll
+ overflow-y: hidden
+ .head
+ white-space: nowrap
+ .week
+ display: inline-block
+ position: relative
+ height: 100%
+ .week-span
+ left: 5px
+ top: 10px
+ right: 30px
+ white-space: nowrap
+ overflow: hidden
+ text-overflow: ellipsis
+ position: absolute
+
+ .week-number
+ background-color: lightgrey
+ color: white
+ position: absolute
+ top: 0
+ right: 0
+ padding: 2px 4px
+
+ &:after
+ position: absolute
+ right: 0
+ top: 0
+ bottom: 0
+ background: $grey
+ width: 1px
+ content: ""
+
+ .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: $lightgrey
+
+ .line
+ border-bottom: 1px solid $grey
+ position: relative
+ overflow: hidden
+
+ .period
+ height: 100%
+ top: 0
+ background: rgb(219, 249, 196)
+ position: absolute
+ &:before
+ content: ""
+ border-right: 1px dashed $grey
+ top: -100%
+ bottom: -100%
+ position: absolute
+ right: -1px
+ z-index: 10
+ &.empty
+ background: rgb(244, 67, 67)
+ background: repeating-linear-gradient(-45deg, #f5e1cf,#f5e1cf 10px,#e49393 10px,#e49393 20px)
+ &.accepted
+ background: #f19039
+ background: repeating-linear-gradient(-45deg, #f5e1cf,#f5e1cf 10px,#f19039 10px,#f19039 20px)
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/services/referential_overview.rb b/app/services/referential_overview.rb
new file mode 100644
index 000000000..5b0e144db
--- /dev/null
+++ b/app/services/referential_overview.rb
@@ -0,0 +1,218 @@
+class ReferentialOverview
+ attr_reader :h
+
+ def initialize referential, h
+ @referential = referential
+ @page = 1
+ @h = h
+ end
+
+ def lines
+ @referential.metadatas_lines.includes(:company).page(@page).map{|l| Line.new(l, @referential, period.first, h)}
+ end
+
+ def period
+ @period ||= @referential.metadatas_period || []
+ end
+
+ def weeks
+ @weeks = {}
+ period.map do |d|
+ @weeks[Week.key(d)] ||= Week.new(d, period.last, h)
+ end
+ @weeks.values
+ 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 = []
+ out << "weekend" if [0, 6].include?(@date.wday)
+ out
+ end
+
+ def short_name
+ h.l(@date, format: "%a")
+ end
+
+ def number
+ @date.day
+ end
+ end
+end
diff --git a/app/views/referentials/_overview.html.slim b/app/views/referentials/_overview.html.slim
new file mode 100644
index 000000000..03c72752e
--- /dev/null
+++ b/app/views/referentials/_overview.html.slim
@@ -0,0 +1,31 @@
+.referential-overview
+ .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
+ - unless line.number == line.name
+ a.name title=line.name
+ = line.name
+ .company= line.company&.name
+ .mode= line.transport_mode
+ .right
+ .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
+ - 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
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..dd7f776fd 100644
--- a/config/locales/referentials.en.yml
+++ b/config/locales/referentials.en.yml
@@ -48,6 +48,11 @@ 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
+
activerecord:
models:
referential:
diff --git a/config/locales/referentials.fr.yml b/config/locales/referentials.fr.yml
index 37af8a4eb..48b98214e 100644
--- a/config/locales/referentials.fr.yml
+++ b/config/locales/referentials.fr.yml
@@ -48,6 +48,11 @@ 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
+
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..39a90360a
--- /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}
+
+ 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.width).to eq ReferentialOverview::Day::WIDTH * 1
+ end
+ end
+end