diff options
28 files changed, 637 insertions, 167 deletions
| diff --git a/app/assets/stylesheets/modules/_timetables.sass b/app/assets/stylesheets/modules/_timetables.sass index b06972ef9..8999456ec 100644 --- a/app/assets/stylesheets/modules/_timetables.sass +++ b/app/assets/stylesheets/modules/_timetables.sass @@ -30,7 +30,7 @@    .t2e-item      .th -      padding: 6px 0 0 0 +      padding: 4px 0 0 0        border-color: $darkgrey        border-top-width: 2px diff --git a/app/controllers/calendars_controller.rb b/app/controllers/calendars_controller.rb index eaff3b0c6..3b7b6de15 100644 --- a/app/controllers/calendars_controller.rb +++ b/app/controllers/calendars_controller.rb @@ -1,8 +1,11 @@  class CalendarsController < ChouetteController    include PolicyChecker +  include TimeTablesHelper +    defaults resource_class: Calendar    before_action :ransack_contains_date, only: [:index]    respond_to :html +  respond_to :json, only: :show    respond_to :js, only: :index    belongs_to :workgroup @@ -21,6 +24,32 @@ class CalendarsController < ChouetteController      end    end +  def month +    @date = params['date'] ? Date.parse(params['date']) : Date.today +    @calendar = resource +  end + +  def create +    create! do +      if @calendar.valid? && has_feature?('application_days_on_calendars') +        redirect_to([:edit, @calendar]) +        return +      end +    end +  end + +  def update +    if params[:calendar] +      super +    else +      state  = JSON.parse request.raw_post +      resource.state_update state +      respond_to do |format| +        format.json { render json: state, status: state['errors'] ? :unprocessable_entity : :ok } +      end +    end +  end +    private    def decorate_calendars(calendars) diff --git a/app/helpers/time_tables_helper.rb b/app/helpers/time_tables_helper.rb index b380a2b0a..1be1fa5a1 100644 --- a/app/helpers/time_tables_helper.rb +++ b/app/helpers/time_tables_helper.rb @@ -84,7 +84,7 @@ module TimeTablesHelper      end unless first.wday == first_weekday      first.upto(last) do |cur| -      cell_text, cell_attrs = block.call(cur) +      cell_text, cell_attrs = yield cur        cell_text  ||= cur.mday        cell_attrs ||= {}        cell_attrs[:headers] = th_id(cur, options[:table_id]) diff --git a/app/javascript/packs/calendars/edit.js b/app/javascript/packs/calendars/edit.js new file mode 100644 index 000000000..bd09657ec --- /dev/null +++ b/app/javascript/packs/calendars/edit.js @@ -0,0 +1,74 @@ +import React from 'react' +import { render } from 'react-dom' +import { Provider } from 'react-redux' +import { createStore } from 'redux' +import timeTablesApp from '../../time_tables/reducers' +import App from '../../time_tables/containers/App' +import clone from '../../helpers/clone' + +const actionType = clone(window, "actionType", true) + +// logger, DO NOT REMOVE +// var applyMiddleware = require('redux').applyMiddleware +// var createLogger = require('redux-logger') +// var thunkMiddleware = require('redux-thunk').default +// var promise = require('redux-promise') + +let initialState = { +  status: { +    actionType: actionType, +    policy: window.perms, +    fetchSuccess: true, +    isFetching: false +  }, +  timetable: { +    current_month: [], +    current_periode_range: '', +    periode_range: [], +    time_table_periods: [], +    time_table_dates: [] +  }, +  metas: { +    comment: '', +    day_types: [], +    initial_tags: [] +  }, +  pagination: { +    stateChanged: false, +    currentPage: '', +    periode_range: [] +  }, +  modal: { +    type: '', +    modalProps: { +      active: false, +      begin: { +        day: '01', +        month: '01', +        year: String(new Date().getFullYear()) +      }, +      end: { +        day: '01', +        month: '01', +        year: String(new Date().getFullYear()) +      }, +      index: false, +      error: '' +    }, +    confirmModal: {} +  } +} +// const loggerMiddleware = createLogger() + +let store = createStore( +  timeTablesApp, +  initialState, +  // applyMiddleware(thunkMiddleware, promise, loggerMiddleware) +) + +render( +  <Provider store={store}> +    <App /> +  </Provider>, +  document.getElementById('periods') +) diff --git a/app/javascript/time_tables/actions/index.js b/app/javascript/time_tables/actions/index.js index 87c7a3e8d..70f548174 100644 --- a/app/javascript/time_tables/actions/index.js +++ b/app/javascript/time_tables/actions/index.js @@ -246,7 +246,8 @@ const actions = {      return error    },    fetchTimeTables: (dispatch, nextPage) => { -    let urlJSON = window.location.pathname.split('/', 5).join('/') +    let urlJSON = window.timetablesUrl || window.location.pathname.split('/', 5).join('/') +      if(nextPage) {        urlJSON += "/month.json?date=" + nextPage      }else{ @@ -277,7 +278,7 @@ const actions = {      let strDayTypes = actions.arrayToStrDayTypes(metas.day_types)      metas.day_types = strDayTypes      let sentState = assign({}, timetable, metas) -    let urlJSON = window.location.pathname.split('/', 5).join('/') +    let urlJSON = window.timetablesUrl || window.location.pathname.split('/', 5).join('/')      let hasError = false      fetch(urlJSON + '.json', {        credentials: 'same-origin', diff --git a/app/javascript/time_tables/components/Metas.js b/app/javascript/time_tables/components/Metas.js index 4170ba493..3c6848d27 100644 --- a/app/javascript/time_tables/components/Metas.js +++ b/app/javascript/time_tables/components/Metas.js @@ -27,7 +27,7 @@ export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdat            </div>            {/* color */} -          <div className="form-group"> +          {metas.color !== undefined && <div className="form-group">              <label htmlFor="" className="control-label col-sm-4">{I18n.activerecord.attributes.time_table.color}</label>              <div className="col-sm-8">                <div className="dropdown color_selector"> @@ -69,10 +69,10 @@ export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdat                  </div>                </div>              </div> -          </div> +          </div>}            {/* tags */} -          <div className="form-group"> +          {metas.tags !== undefined && <div className="form-group">              <label htmlFor="" className="control-label col-sm-4">{I18n.activerecord.attributes.time_table.tag_list}</label>              <div className="col-sm-8">                <TagsSelect2 @@ -82,15 +82,15 @@ export default function Metas({metas, onUpdateDayTypes, onUpdateComment, onUpdat                  onUnselect2Tags={(e) => onUnselect2Tags(e)}                />              </div> -          </div> +          </div>}            {/* calendar */} -          <div className="form-group"> +          {metas.calendar !== null && <div className="form-group">              <label htmlFor="" className="control-label col-sm-4">{I18n.activerecord.attributes.time_table.calendar}</label>              <div className="col-sm-8">                <span>{metas.calendar ? metas.calendar.name : I18n.time_tables.edit.metas.no_calendar}</span>              </div> -          </div> +          </div>}            {/* day_types */}            <div className="form-group"> diff --git a/app/models/calendar.rb b/app/models/calendar.rb index e0f0f03da..84b569ab4 100644 --- a/app/models/calendar.rb +++ b/app/models/calendar.rb @@ -6,6 +6,7 @@ class Calendar < ActiveRecord::Base    include DateSupport    include PeriodSupport    include ApplicationDaysSupport +  include TimetableSupport    has_paper_trail class_name: 'PublicVersion'    belongs_to :organisation @@ -17,16 +18,29 @@ class Calendar < ActiveRecord::Base    has_many :time_tables    scope :contains_date, ->(date) { where('date ? = any (dates) OR date ? <@ any (date_ranges)', date, date) } -  before_create :set_default_days + +  after_initialize :set_defaults    def self.ransackable_scopes(auth_object = nil)      [:contains_date]    end -  def set_default_days +  def self.state_permited_attributes item +    {name: item["comment"]} +  end + +  def set_defaults +    self.excluded_dates ||= []      self.int_day_types ||= EVERYDAY    end +  def human_attribute_name(*args) +    self.class.human_attribute_name(*args) +  end + +  def shortcuts_update(date=nil) +  end +    def convert_to_time_table      Chouette::TimeTable.new.tap do |tt|        self.dates.each do |d| @@ -39,4 +53,65 @@ class Calendar < ActiveRecord::Base      end    end +  def include_in_dates?(day) +    self.dates.include? day +  end + +  def excluded_date?(day) +    self.excluded_dates.include? day +  end + +  def update_in_out date, in_out +    if in_out +      self.excluded_dates.delete date +      self.dates << date unless include_in_dates?(date) +    else +      self.dates.delete date +      self.excluded_dates << date unless excluded_date?(date) +    end +    date +  end + +  def included_days +    dates +  end + +  def excluded_days +    excluded_dates +  end + +  def saved_dates +    Hash[*self.dates.each_with_index.to_a.map(&:reverse).flatten] +  end + +  def all_dates +    (dates + excluded_dates).sort.each_with_index.map do |d, i| +      OpenStruct.new(id: i, date: d, in_out: include_in_dates?(d)) +    end +  end + +  def find_date_by_id id +    self.dates[id] +  end + +  def destroy_date date +    self.dates -= [date] +  end + +  def create_date in_out:, date: +    update_in_out date, in_out +  end + +  def find_period_by_id id +    self.periods.find{|p| p.id == id} +  end + +  def build_period +    self.periods << Calendar::Period.new(id: self.periods.count + 1) +    self.periods.last +  end + +  def destroy_period period +    @periods = self.periods.select{|p| p.end != period.end || p.begin != period.begin} +  end  end diff --git a/app/models/calendar/period.rb b/app/models/calendar/period.rb index 56ab722fe..8b3e4109b 100644 --- a/app/models/calendar/period.rb +++ b/app/models/calendar/period.rb @@ -10,6 +10,11 @@ class Calendar < ActiveRecord::Base      validates_presence_of :begin, :end      validate :check_end_greather_than_begin +    alias_method :period_start, :begin +    alias_method :period_end, :end +    alias_method :period_start=, :begin= +    alias_method :period_end=, :end= +      def check_end_greather_than_begin        if self.begin && self.end && self.begin >= self.end          errors.add(:base, I18n.t('calendars.errors.short_period')) diff --git a/app/models/chouette/time_table.rb b/app/models/chouette/time_table.rb index 1a1972113..8113457ec 100644 --- a/app/models/chouette/time_table.rb +++ b/app/models/chouette/time_table.rb @@ -5,6 +5,7 @@ module Chouette      include TimeTableRestrictions      include ObjectidSupport      include ApplicationDaysSupport +    include TimetableSupport      # FIXME http://jira.codehaus.org/browse/JRUBY-6358      self.primary_key = "id" @@ -82,72 +83,36 @@ module Chouette        end      end -    def state_update state -      update_attributes(self.class.state_permited_attributes(state)) -      self.tag_list    = state['tags'].collect{|t| t['name']}.join(', ') -      self.calendar_id = nil unless state['calendar'] - -      days = state['day_types'].split(',') -      Date::DAYNAMES.map(&:underscore).each do |name| -        prefix = human_attribute_name(name).first(2) -        send("#{name}=", days.include?(prefix)) -      end - -      saved_dates = Hash[self.dates.collect{ |d| [d.id, d.date]}] -      cmonth = Date.parse(state['current_periode_range']) - -      state['current_month'].each do |d| -        date    = Date.parse(d['date']) -        checked = d['include_date'] || d['excluded_date'] -        in_out  = d['include_date'] ? true : false - -        date_id = saved_dates.key(date) -        time_table_date = self.dates.find(date_id) if date_id +    def find_date_by_id id +      self.dates.find id +    end -        next if !checked && !time_table_date -        # Destroy date if no longer checked -        next if !checked && time_table_date.destroy +    def destroy_date date +      date.destroy +    end -        # Create new date -        unless time_table_date -          time_table_date = self.dates.create({in_out: in_out, date: date}) -        end -        # Update in_out -        if in_out != time_table_date.in_out -          time_table_date.update_attributes({in_out: in_out}) -        end +    def update_in_out date, in_out +      if in_out != date.in_out +        date.update_attributes({in_out: in_out})        end - -      self.state_update_periods state['time_table_periods'] -      self.save      end -    def state_update_periods state_periods -      state_periods.each do |item| -        period = self.periods.find(item['id']) if item['id'] -        next if period && item['deleted'] && period.destroy -        period ||= self.periods.build - -        period.period_start = Date.parse(item['period_start']) -        period.period_end   = Date.parse(item['period_end']) +    def find_period_by_id id +      self.periods.find id +    end -        if period.changed? -          period.save -          item['id'] = period.id -        end -      end +    def build_period +      periods.build +    end -      state_periods.delete_if {|item| item['deleted']} +    def destroy_period period +      period.destroy      end      def self.state_permited_attributes item        item.slice('comment', 'color').to_hash      end -    def presenter -      @presenter ||= ::TimeTablePresenter.new( self) -    end -      def self.start_validity_period        [Chouette::TimeTable.minimum(:start_date)].compact.min      end @@ -168,20 +133,6 @@ module Chouette        self.save      end -    def month_inspect(date) -      (date.beginning_of_month..date.end_of_month).map do |d| -        { -          day: I18n.l(d, format: '%A'), -          date: d.to_s, -          wday: d.wday, -          wnumber: d.strftime("%W").to_s, -          mday: d.mday, -          include_date: include_in_dates?(d), -          excluded_date: excluded_date?(d) -        } -      end -    end -      def save_shortcuts          shortcuts_update          self.update_column(:start_date, start_date) @@ -361,6 +312,17 @@ module Chouette        days.sort      end +    def create_date in_out:, date: +      self.dates.create in_out: in_out, date: date +    end + +    def saved_dates +      Hash[self.dates.collect{ |d| [d.id, d.date]}] +    end + +    def all_dates +      dates +    end      # produce a copy of periods without anyone overlapping or including another      def optimize_overlapping_periods diff --git a/app/models/concerns/application_days_support.rb b/app/models/concerns/application_days_support.rb index 425cba5bf..348436aa4 100644 --- a/app/models/concerns/application_days_support.rb +++ b/app/models/concerns/application_days_support.rb @@ -35,6 +35,10 @@ module ApplicationDaysSupport      end    end +  def valid_day? wday +    valid_days.include?(wday) +  end +    def self.valid_days(int_day_types)      # Build an array with day of calendar week (1-7, Monday is 1).      [].tap do |valid_days| diff --git a/app/models/concerns/date_support.rb b/app/models/concerns/date_support.rb index fbfe19af1..5c66cb1a9 100644 --- a/app/models/concerns/date_support.rb +++ b/app/models/concerns/date_support.rb @@ -38,9 +38,12 @@ module DateSupport            date_values_are_valid = false          end          date_ranges.each do |date_range| -          if date_range.cover? date_value.value -            date_value.errors.add(:base, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_date_ranges')) -            date_values_are_valid = false +          if date_range.cover?(date_value.value) +            excluded_day = self.respond_to?(:valid_day?) && !self.valid_day?(date_value.value.wday) +            unless excluded_day +              date_value.errors.add(:base, I18n.t('activerecord.errors.models.calendar.attributes.dates.date_in_date_ranges')) +              date_values_are_valid = false +            end            end          end        end @@ -77,4 +80,4 @@ module DateSupport      private :clear_date_values    end -end
\ No newline at end of file +end diff --git a/app/models/concerns/timetable_support.rb b/app/models/concerns/timetable_support.rb new file mode 100644 index 000000000..8c49723fe --- /dev/null +++ b/app/models/concerns/timetable_support.rb @@ -0,0 +1,149 @@ +module TimetableSupport +  extend ActiveSupport::Concern + +  def presenter +    @presenter ||= ::TimeTablePresenter.new( self) +  end + +  def periods_max_date +    return nil if self.periods.empty? + +    min_start = self.periods.map(&:period_start).compact.min +    max_end = self.periods.map(&:period_end).compact.max +    result = nil + +    if max_end && min_start +      max_end.downto( min_start) do |date| +        if self.valid_days.include?(date.cwday) && !self.excluded_date?(date) +            result = date +            break +        end +      end +    end +    result +  end + +  def periods_min_date +    return nil if self.periods.empty? + +    min_start = self.periods.map(&:period_start).compact.min +    max_end = self.periods.map(&:period_end).compact.max +    result = nil + +    if max_end && min_start +      min_start.upto(max_end) do |date| +        if self.valid_days.include?(date.cwday) && !self.excluded_date?(date) +            result = date +            break +        end +      end +    end +    result +  end + +  def bounding_dates +    bounding_min = self.all_dates.select{|d| d.in_out}.map(&:date).compact.min +    bounding_max = self.all_dates.select{|d| d.in_out}.map(&:date).compact.max + +    unless self.periods.empty? +      bounding_min = periods_min_date if periods_min_date && +          (bounding_min.nil? || (periods_min_date < bounding_min)) + +      bounding_max = periods_max_date if periods_max_date && +          (bounding_max.nil? || (bounding_max < periods_max_date)) +    end + +    [bounding_min, bounding_max].compact +  end + +  def month_inspect(date) +    (date.beginning_of_month..date.end_of_month).map do |d| +      { +        day: I18n.l(d, format: '%A'), +        date: d.to_s, +        wday: d.wday, +        wnumber: d.strftime("%W").to_s, +        mday: d.mday, +        include_date: include_in_dates?(d), +        excluded_date: excluded_date?(d) +      } +    end +  end + +  def include_in_dates?(day) +    self.dates.any?{ |d| d.date === day && d.in_out == true } +  end + +  def excluded_date?(day) +    self.dates.any?{ |d| d.date === day && d.in_out == false } +  end + +  def include_in_overlap_dates?(day) +    return false if self.excluded_date?(day) + +    self.all_dates.any?{ |d| d.date === day} \ +    && self.periods.any?{ |period| period.period_start <= day && day <= period.period_end && valid_days.include?(day.cwday) } +  end + +  def include_in_periods?(day) +    self.periods.any?{ |period| period.period_start <= day && +                                day <= period.period_end && +                                valid_days.include?(day.cwday) && +                                ! excluded_date?(day) } +  end + +  def state_update_periods state_periods +    state_periods.each do |item| +      period = self.find_period_by_id(item['id']) if item['id'] +      next if period && item['deleted'] && self.destroy_period(period) +      period ||= self.build_period + +      period.period_start = Date.parse(item['period_start']) +      period.period_end   = Date.parse(item['period_end']) + +      period.save if period === ActiveRecord::Base && period.changed? + +      item['id'] = period.id +    end + +    state_periods.delete_if {|item| item['deleted']} +  end + +  def state_update state +    update_attributes(self.class.state_permited_attributes(state)) +    self.tag_list    = state['tags'].collect{|t| t['name']}.join(', ') if state['tags'] +    self.calendar_id = nil if self.respond_to?(:calendar_id) && !state['calendar'] + +    days = state['day_types'].split(',') +    Date::DAYNAMES.map(&:underscore).each do |name| +      prefix = human_attribute_name(name).first(2) +      send("#{name}=", days.include?(prefix)) +    end + +    cmonth = Date.parse(state['current_periode_range']) + +    state['current_month'].each do |d| +      date    = Date.parse(d['date']) +      checked = d['include_date'] || d['excluded_date'] +      in_out  = d['include_date'] ? true : false + +      date_id = saved_dates.key(date) +      time_table_date = self.find_date_by_id(date_id) if date_id + +      next if !checked && !time_table_date +      # Destroy date if no longer checked +      next if !checked && destroy_date(time_table_date) + +      # Create new date +      unless time_table_date +        time_table_date = self.create_date in_out: in_out, date: date +      end +      # Update in_out +      self.update_in_out time_table_date, in_out +    end + +    self.state_update_periods state['time_table_periods'] +    self.save +  end + +end diff --git a/app/views/calendars/_form.html.slim b/app/views/calendars/_form.html.slim deleted file mode 100644 index bf9f4f3a7..000000000 --- a/app/views/calendars/_form.html.slim +++ /dev/null @@ -1,53 +0,0 @@ -= simple_form_for [@workgroup, @calendar], html: { class: 'form-horizontal', id: 'calendar_form' }, wrapper: :horizontal_form do |f| -  .row -    .col-lg-12 -      = f.input :name -      = f.input :short_name - -      - if policy(@calendar).share? -        .form-group.has_switch -          = f.label :shared, class: 'col-sm-4 col-xs-5 control-label' -          = f.input :shared, as: :boolean, checked_value: true, unchecked_value: false, label: content_tag(:span, t("#{@calendar.shared}"), class: 'switch-label', data: {checkedValue: t('true'), uncheckedValue: t('false')}), wrapper_html: { class: 'col-sm-8 col-xs-7'} - -  .separator -   -  .row -    .col-lg-12 -      .subform -        .nested-head -          .wrapper -            div -              .form-group -                label.control-label -                  = Calendar.human_attribute_name(:date) -            div - -        = f.simple_fields_for :date_values do |date_value| -          = render 'date_value_fields', f: date_value - -        .links.nested-linker -          = link_to_add_association t('simple_form.labels.calendar.add_a_date'), f, :date_values, class: 'btn btn-outline-primary' -   -  .separator -   -  .row -    .col-lg-12 -      .subform -        .nested-head -          .wrapper -            div -              .form-group -                label.control-label -                  = t('simple_form.labels.calendar.ranges.begin') -            div -              .form-group -                label.control-label -                  = t('simple_form.labels.calendar.ranges.end') -            div - -        = f.simple_fields_for :periods do |period| -          = render 'period_fields', f: period -        .links.nested-linker -          = link_to_add_association t('simple_form.labels.calendar.add_a_date_range'), f, :periods, class: 'btn btn-outline-primary' - -  = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'calendar_form' diff --git a/app/views/calendars/_form_advanced.html.slim b/app/views/calendars/_form_advanced.html.slim new file mode 100644 index 000000000..b4154166b --- /dev/null +++ b/app/views/calendars/_form_advanced.html.slim @@ -0,0 +1,8 @@ +#periods + += javascript_tag do +  | window.actionType = "#{raw params[:action]}"; +  | window.I18n = #{(I18n.backend.send(:translations)[I18n.locale].to_json).html_safe}; +  | window.timetablesUrl = "#{calendar_url(@calendar).html_safe}"; + += javascript_pack_tag 'calendars/edit.js' diff --git a/app/views/calendars/_form_simple.html.slim b/app/views/calendars/_form_simple.html.slim new file mode 100644 index 000000000..2f469ada7 --- /dev/null +++ b/app/views/calendars/_form_simple.html.slim @@ -0,0 +1,56 @@ +.row +  .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 +    = simple_form_for @calendar, html: { class: 'form-horizontal', id: 'calendar_form' }, wrapper: :horizontal_form do |f| +      .row +        .col-lg-12 +          = f.input :name +          = f.input :short_name + +          - if policy(@calendar).share? +            .form-group.has_switch +              = f.label :shared, class: 'col-sm-4 col-xs-5 control-label' +              = f.input :shared, as: :boolean, checked_value: true, unchecked_value: false, label: content_tag(:span, t("#{@calendar.shared}"), class: 'switch-label', data: {checkedValue: t('true'), uncheckedValue: t('false')}), wrapper_html: { class: 'col-sm-8 col-xs-7'} + +      .separator + +      - unless has_feature?('application_days_on_calendars') +        .row +          .col-lg-12 +            .subform +              .nested-head +                .wrapper +                  div +                    .form-group +                      label.control-label +                        = Calendar.human_attribute_name(:date) +                  div + +              = f.simple_fields_for :date_values do |date_value| +                = render 'date_value_fields', f: date_value + +              .links.nested-linker +                = link_to_add_association t('simple_form.labels.calendar.add_a_date'), f, :date_values, class: 'btn btn-outline-primary' + +        .separator + +      .row +        .col-lg-12 +          .subform +            .nested-head +              .wrapper +                div +                  .form-group +                    label.control-label +                      = t('simple_form.labels.calendar.ranges.begin') +                div +                  .form-group +                    label.control-label +                      = t('simple_form.labels.calendar.ranges.end') +                div + +            = f.simple_fields_for :periods do |period| +              = render 'period_fields', f: period +            .links.nested-linker +              = link_to_add_association t('simple_form.labels.calendar.add_a_date_range'), f, :periods, class: 'btn btn-outline-primary' + +      = f.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'calendar_form' diff --git a/app/views/calendars/edit.html.slim b/app/views/calendars/edit.html.slim index a1af5e257..79ab1f5d0 100644 --- a/app/views/calendars/edit.html.slim +++ b/app/views/calendars/edit.html.slim @@ -2,6 +2,7 @@  - page_header_content_for @calendar  .page_content    .container-fluid -    .row -      .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 -        = render 'form' +    - if has_feature?('application_days_on_calendars') +      = render 'form_advanced' +    - else +      = render 'form_simple' diff --git a/app/views/calendars/month.rabl b/app/views/calendars/month.rabl new file mode 100644 index 000000000..1584db44c --- /dev/null +++ b/app/views/calendars/month.rabl @@ -0,0 +1,9 @@ +object @calendar + +node do |tt| +  { +    name: I18n.l(@date, format: '%B'), +    days: tt.month_inspect(@date), +    day_types: %w(monday tuesday wednesday thursday friday saturday sunday).select{ |d| tt.send(d) }.map{ |d| tt.human_attribute_name(d).first(2)}.join('') +  } +end diff --git a/app/views/calendars/new.html.slim b/app/views/calendars/new.html.slim index c1e084913..5657a0c55 100644 --- a/app/views/calendars/new.html.slim +++ b/app/views/calendars/new.html.slim @@ -1,6 +1,4 @@  - breadcrumb :calendars, @workgroup  .page_content    .container-fluid -    .row -      .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 -        = render 'form' +    = render 'form_simple' diff --git a/app/views/calendars/show.html.slim b/app/views/calendars/show.html.slim index cd2be2bd1..cec4f66a5 100644 --- a/app/views/calendars/show.html.slim +++ b/app/views/calendars/show.html.slim @@ -11,3 +11,14 @@              'Organisation' => resource.organisation.name,              Calendar.human_attribute_name(:dates) =>  resource.dates.collect{|d| l(d, format: :short)}.join(', ').html_safe,              Calendar.human_attribute_name(:date_ranges) => resource.periods.map{|d| t('validity_range', debut: l(d.begin, format: :short), end: l(d.end, format: :short))}.join('<br>').html_safe } + +    - if has_feature?('application_days_on_calendars') +      .row +        .col-lg-12.mb-sm +          .pagination.pull-right +            = @year +            .page_links +              = link_to '', calendar_path(@calendar, year: (@year - 1)), class: 'previous_page' +              = link_to '', calendar_path(@calendar, year: (@year + 1)), class: 'next_page' + +      = render 'time_tables/show_time_table', time_table: @calendar diff --git a/app/views/calendars/show.rabl b/app/views/calendars/show.rabl new file mode 100644 index 000000000..295d97528 --- /dev/null +++ b/app/views/calendars/show.rabl @@ -0,0 +1,22 @@ +object @calendar + +attributes :id +node do |tt| +  { +    comment: tt.name, +    time_table_bounding: tt.presenter.time_table_bounding, +    day_types: %w(monday tuesday wednesday thursday friday saturday sunday).select{ |d| tt.send(d) }.map{ |d| tt.human_attribute_name(d).first(2)}.join(''), +    current_month: tt.month_inspect(Date.today), +    periode_range: month_periode_enum(3), +    current_periode_range: Date.today.beginning_of_month, +    short_id: tt.object_id, +  } +end + +child(:periods, object_root: false, root: :time_table_periods) do +  attributes :id, :period_start, :period_end +end + +child(:all_dates, object_root: false, root: :time_table_dates) do +  attributes :id, :date, :in_out +end diff --git a/app/views/time_tables/_show_time_table.html.slim b/app/views/time_tables/_show_time_table.html.slim index ebfe9d283..102dbfad7 100644 --- a/app/views/time_tables/_show_time_table.html.slim +++ b/app/views/time_tables/_show_time_table.html.slim @@ -2,24 +2,10 @@    - (1..12).each do |month|      .col-lg-3.col-md-4.col-sm-4.col-xs-6        = new_alt_calendar(year: @year, month: month, first_day_of_week: 1, calendar_title: "#{I18n.t("date.month_names")[month]}", show_today: false) do |d| -        / - if @time_table.excluded_date?(d) -        /   - [link_to(d.mday, edit_referential_time_table_path(@referential, @time_table) ), {class: "day excluded_date"}] -        - if @time_table.include_in_overlap_dates?(d) -          - [link_to(d.mday, edit_referential_time_table_path(@referential, @time_table) ), {class: "day overlaped_date", title: 'Voir'}] -        - elsif @time_table.include_in_dates?(d) -          - [link_to(d.mday, edit_referential_time_table_path(@referential, @time_table) ), {class: "day selected_date", title: 'Voir'}] -        - elsif @time_table.include_in_periods?(d) -          - [link_to(d.mday, edit_referential_time_table_path(@referential, @time_table) ), {class: "day selected_period", title: 'Voir'}] - -/ .row -/   .col-lg-12 -/     / wip -/     - if @time_table.dates.where("in_out = true").present? -/       h3.time_table_dates = @time_table.human_attribute_name("dates") -/       .dates.content -/         == render "time_tables/dates" -/  -/     - if @time_table.dates.where("in_out = false").present? -/       h3.time_table_dates = @time_table.human_attribute_name("excluded_dates") -/       .excluded_dates.content -/         == render "time_tables/excluded_dates" +        - edit_url = [:edit, @referential, time_table].compact +        - if time_table.include_in_overlap_dates?(d) +          - [link_to(d.mday, edit_url), {class: "day overlaped_date", title: 'Voir'}] +        - elsif time_table.include_in_dates?(d) +          - [link_to(d.mday, edit_url), {class: "day selected_date", title: 'Voir'}] +        - elsif time_table.include_in_periods?(d) +          - [link_to(d.mday, edit_url), {class: "day selected_period", title: 'Voir'}] diff --git a/app/views/time_tables/show.html.slim b/app/views/time_tables/show.html.slim index 6d15323f3..e038b73ca 100644 --- a/app/views/time_tables/show.html.slim +++ b/app/views/time_tables/show.html.slim @@ -24,4 +24,4 @@              = link_to '', referential_time_table_path(@referential, @time_table, year: (@year - 1)), class: 'previous_page'              = link_to '', referential_time_table_path(@referential, @time_table, year: (@year + 1)), class: 'next_page' -    = render 'show_time_table' +    = render 'show_time_table', time_table: @time_table diff --git a/config/locales/calendars.en.yml b/config/locales/calendars.en.yml index a2110d053..c3df413af 100644 --- a/config/locales/calendars.en.yml +++ b/config/locales/calendars.en.yml @@ -69,6 +69,13 @@ en:          dates: Dates          shared: Shared          organisation: Organisation +        monday: "Monday" +        tuesday: "Tuesday" +        wednesday: "Wednesday" +        thursday: "Thursday" +        friday: "Friday" +        saturday: "Saturday" +        sunday: "Sunday"      errors:        models:          calendar: diff --git a/config/locales/calendars.fr.yml b/config/locales/calendars.fr.yml index f9eaf1be5..6fd265925 100644 --- a/config/locales/calendars.fr.yml +++ b/config/locales/calendars.fr.yml @@ -69,6 +69,13 @@ fr:          dates: Dates          shared: Partagé          organisation: Organisation +        monday: "Lundi" +        tuesday: "Mardi" +        wednesday: "Mercredi" +        thursday: "Jeudi" +        friday: "Vendredi" +        saturday: "Samedi" +        sunday: "Dimanche"      errors:        models:          calendar: diff --git a/config/routes.rb b/config/routes.rb index 07370ee6d..5fc39ba92 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -111,9 +111,13 @@ ChouetteIhm::Application.routes.draw do      resources :companies      resources :networks    end +      resources :workgroups do      resources :calendars do        get :autocomplete, on: :collection, controller: 'autocomplete_calendars' +      member do +        get 'month', defaults: { format: :json } +      end      end    end @@ -121,7 +125,7 @@ ChouetteIhm::Application.routes.draw do      resources :autocomplete_stop_areas, only: [:show, :index] do        get 'around', on: :member      end -    resources :autocomplete_purchase_windows, only: [:index]  +    resources :autocomplete_purchase_windows, only: [:index]      get :select_compliance_control_set      post :validate, on: :member      resources :autocomplete_time_tables, only: [:index] diff --git a/db/migrate/20180124124215_add_excluded_dates_to_calendars.rb b/db/migrate/20180124124215_add_excluded_dates_to_calendars.rb new file mode 100644 index 000000000..b98b50717 --- /dev/null +++ b/db/migrate/20180124124215_add_excluded_dates_to_calendars.rb @@ -0,0 +1,5 @@ +class AddExcludedDatesToCalendars < ActiveRecord::Migration +  def change +    add_column :calendars, :excluded_dates, :date, array: true +  end +end diff --git a/db/schema.rb b/db/schema.rb index 231bea9ba..b696cfc98 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,6 @@  #  # It's strongly recommended that you check this file into your version control system. -  ActiveRecord::Schema.define(version: 20180126134944) do    # These are extensions that must be enabled in order to support this database diff --git a/spec/models/calendar_spec.rb b/spec/models/calendar_spec.rb index 86ce565cd..3cffd0f8a 100644 --- a/spec/models/calendar_spec.rb +++ b/spec/models/calendar_spec.rb @@ -32,7 +32,11 @@ RSpec.describe Calendar, :type => :model do    describe 'validations' do      it 'validates that dates and date_ranges do not overlap' do -      expect(build(:calendar, dates: [Date.today], date_ranges: [Date.today..Date.tomorrow])).to_not be_valid +      expect(build(:calendar, dates: [Date.today.beginning_of_week], date_ranges: [Date.today.beginning_of_week..Date.today])).to_not be_valid +    end + +    it 'validates that dates and date_ranges do not overlap but allow for days not in the list' do +      expect(build(:calendar, dates: [Date.today.beginning_of_week], date_ranges: [Date.today.beginning_of_week..Date.today], int_day_types: Calendar::THURSDAY)).to be_valid      end      it 'validates that there are no duplicates in dates' do @@ -52,4 +56,108 @@ RSpec.describe Calendar, :type => :model do      end    end +  describe "Update state" do +    def calendar_to_state calendar +      calendar.slice('id').tap do |item| +        item['comment'] = calendar.name +        item['day_types'] = "Di,Lu,Ma,Me,Je,Ve,Sa" +        item['current_month'] = calendar.month_inspect(Date.today.beginning_of_month) +        item['current_periode_range'] = Date.today.beginning_of_month.to_s +        item['time_table_periods'] = calendar.periods.map{|p| {'id': p.id, 'period_start': p.period_start.to_s, 'period_end': p.period_end.to_s}} +      end +    end + +    subject(:calendar){ create :calendar } +    let(:state) { calendar_to_state subject } + +    it 'should update time table periods association' do +      period = state['time_table_periods'].first +      period['period_start'] = (Date.today - 1.month).to_s +      period['period_end']   = (Date.today + 1.month).to_s + +      subject.state_update_periods state['time_table_periods'] +      ['period_end', 'period_start'].each do |prop| +        expect(subject.reload.periods.first.send(prop).to_s).to eq(period[prop]) +      end +    end + +    it 'should create time table periods association' do +      state['time_table_periods'] << { +        'id' => false, +        'period_start' => (Date.today + 1.year).to_s, +        'period_end' => (Date.today + 2.year).to_s +      } + +      expect { +        subject.state_update_periods state['time_table_periods'] +      }.to change {subject.periods.count}.by(1) +      expect(state['time_table_periods'].last['id']).to eq subject.reload.periods.last.id +    end + +    it 'should delete time table periods association' do +      state['time_table_periods'].first['deleted'] = true +      expect { +        subject.state_update_periods state['time_table_periods'] +      }.to change {subject.periods.count}.by(-1) +    end + +    it 'should update name' do +      state['comment'] = "Edited timetable name" +      subject.state_update state +      expect(subject.reload.name).to eq state['comment'] +    end + +    it 'should update day_types' do +      state['day_types'] = "Di,Lu,Je,Ma" +      subject.state_update state +      expect(subject.reload.valid_days).to include(7, 1, 4, 2) +      expect(subject.reload.valid_days).not_to include(3, 5, 6) +    end + +    it 'should delete date if date is set to neither include or excluded date' do +      updated = state['current_month'].map do |day| +        day['include_date'] = false if day['include_date'] +      end + +      expect { +        subject.state_update state +      }.to change {subject.dates.count}.by(-updated.compact.count) +    end + +    it 'should update date if date is set to excluded date' do +        updated = state['current_month'].map do |day| +          next unless day['include_date'] +          day['include_date']  = false +          day['excluded_date'] = true +        end + +        subject.state_update state +        expect(subject.reload.excluded_days.count).to eq (updated.compact.count) +    end + +    it 'should create new include date' do +      day  = state['current_month'].find{|d| !d['excluded_date'] && !d['include_date'] } +      date = Date.parse(day['date']) +      day['include_date'] = true +      expect(subject.included_days).not_to include(date) + +      expect { +        subject.state_update state +      }.to change {subject.dates.count}.by(1) +      expect(subject.reload.included_days).to include(date) +    end + +    it 'should create new exclude date' do +      day  = state['current_month'].find{|d| !d['excluded_date'] && !d['include_date']} +      date = Date.parse(day['date']) +      day['excluded_date'] = true +      expect(subject.excluded_days).not_to include(date) + +      expect { +        subject.state_update state +      }.to change {subject.all_dates.count}.by(1) +      expect(subject.reload.excluded_days).to include(date) +    end +  end +  end | 
