diff options
| author | Zog | 2018-01-19 08:22:24 +0100 |
|---|---|---|
| committer | Zog | 2018-02-02 14:31:32 +0100 |
| commit | c13c94fe665fdbe2c434c8bfadd44a0988ecea60 (patch) | |
| tree | cbf70b0af9dd0bbbe4129384adcf5db39117fe33 | |
| parent | 9f30b2debe7ece403d9e588df9763c119436d967 (diff) | |
| download | chouette-core-c13c94fe665fdbe2c434c8bfadd44a0988ecea60.tar.bz2 | |
Refs #3542 @4h; First UI
Still missing:
- Pagination
- Filters
| -rw-r--r-- | app/assets/stylesheets/components/_referential_overview.sass | 171 | ||||
| -rw-r--r-- | app/helpers/referentials_helper.rb | 5 | ||||
| -rw-r--r-- | app/services/referential_overview.rb | 218 | ||||
| -rw-r--r-- | app/views/referentials/_overview.html.slim | 31 | ||||
| -rw-r--r-- | app/views/referentials/show.html.slim | 2 | ||||
| -rw-r--r-- | config/locales/referentials.en.yml | 5 | ||||
| -rw-r--r-- | config/locales/referentials.fr.yml | 5 | ||||
| -rw-r--r-- | spec/services/referential_overview_spec.rb | 154 |
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 |
