aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/assets/stylesheets/modules/_timetables.sass2
-rw-r--r--app/controllers/calendars_controller.rb29
-rw-r--r--app/helpers/time_tables_helper.rb2
-rw-r--r--app/javascript/packs/calendars/edit.js74
-rw-r--r--app/javascript/time_tables/actions/index.js5
-rw-r--r--app/javascript/time_tables/components/Metas.js12
-rw-r--r--app/models/calendar.rb79
-rw-r--r--app/models/calendar/period.rb5
-rw-r--r--app/models/chouette/time_table.rb96
-rw-r--r--app/models/concerns/application_days_support.rb4
-rw-r--r--app/models/concerns/date_support.rb11
-rw-r--r--app/models/concerns/timetable_support.rb149
-rw-r--r--app/views/calendars/_form.html.slim53
-rw-r--r--app/views/calendars/_form_advanced.html.slim8
-rw-r--r--app/views/calendars/_form_simple.html.slim56
-rw-r--r--app/views/calendars/edit.html.slim7
-rw-r--r--app/views/calendars/month.rabl9
-rw-r--r--app/views/calendars/new.html.slim4
-rw-r--r--app/views/calendars/show.html.slim11
-rw-r--r--app/views/calendars/show.rabl22
-rw-r--r--app/views/time_tables/_show_time_table.html.slim28
-rw-r--r--app/views/time_tables/show.html.slim2
-rw-r--r--config/locales/calendars.en.yml7
-rw-r--r--config/locales/calendars.fr.yml7
-rw-r--r--config/routes.rb6
-rw-r--r--db/migrate/20180124124215_add_excluded_dates_to_calendars.rb5
-rw-r--r--db/schema.rb1
-rw-r--r--spec/models/calendar_spec.rb110
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