diff --git a/app/assets/javascripts/app/controllers/_ui_element/holiday_selector.js.coffee b/app/assets/javascripts/app/controllers/_ui_element/holiday_selector.js.coffee index b13309cf6..6c85538eb 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/holiday_selector.js.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/holiday_selector.js.coffee @@ -1,7 +1,7 @@ # coffeelint: disable=camel_case_classes class App.UiElement.holiday_selector @render: (attribute, params) -> - console.log('aa', attribute) + days = {} if attribute.value days = attribute.value @@ -10,4 +10,65 @@ class App.UiElement.holiday_selector for day in days_sorted days_new[day] = days[day] - item = $( App.view('calendar/holiday_selector')( attribute: attribute, days: days_new ) ) \ No newline at end of file + item = $( App.view('calendar/holiday_selector')( attribute: attribute, days: days_new ) ) + + # add date picker + attributeDatepicket = + name: "#{attribute.name}_date" + disable_feature: true + datePicker = App.UiElement.date.render(attributeDatepicket) + item.find('.js-datePicker').html(datePicker) + + # set active/inactive of date + item.find('.js-active').bind('click', (e) -> + active = $(e.target).prop('checked') + row = $(e.target).closest('tr') + input = $(e.target).closest('tr').find('.js-description') + if !active + row.addClass('is-inactive') + input.prop('readonly', true) + input.addClass('is-disabled') + else + row.removeClass('is-inactive') + input.prop('readonly', false) + input.removeClass('is-disabled') + ) + + # remove date + item.find('.js-remove').bind('click', (e) -> + $(e.target).closest('tr').remove() + ) + + # catch enter / apply add + item.find('.js-summary').bind( 'keydown', (e) -> + return if e.which isnt 13 + e.preventDefault() + item.find('.js-add').click() + ) + + # add date + item.find('.js-add').bind('click', (e) -> + date = $(e.target).closest('tr').find('[name="{date}public_holidays_date"]').val() + return if !date + summary = $(e.target).closest('tr').find('.js-summary').val() + return if !summary + + # check if entry already exists + exists = item.find("[data-date=#{date}]").get(0) + return if exists + + # reset form input + $(e.target).closest('tr').find('.js-summary').val('') + + # place new element + template = item.find('.js-placeholder').clone() + template.removeClass('hidden').removeClass('js-placeholder') + template.attr('data-date', date) + template.find('.js-date').html(App.i18n.translateDate(date)) + template.find('.js-active').attr('name', "{boolean}public_holidays::#{date}::active") + template.find('.js-summary').attr('name', "public_holidays::#{date}::summary") + template.find('.js-summary').val(summary) + item.find('.js-placeholder').before(template) + ) + + item diff --git a/app/assets/javascripts/app/controllers/_ui_element/ical_feed.js.coffee b/app/assets/javascripts/app/controllers/_ui_element/ical_feed.js.coffee index 95e137e8e..861bc9e99 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/ical_feed.js.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/ical_feed.js.coffee @@ -1,36 +1,48 @@ # coffeelint: disable=camel_case_classes class App.UiElement.ical_feed extends App.UiElement.ApplicationUiElement @render: (attribute, params) -> - console.log('A', attribute) - item = $( '
' + App.view('generic/input')( attribute: attribute ) + '
' ) - ical_feeds = App.Config.get('ical_feeds') + ical_feeds = App.Config.get('ical_feeds') || {} + item = $( App.view('generic/ical_feed')( attribute: attribute, ical_feeds: ical_feeds ) ) - if !_.isEmpty(ical_feeds) - attribute_ical = - options: ical_feeds - tag: 'searchable_select' - placeholder: App.i18n.translateInline('Search public ical feed...') + updateCheckList = -> + return if item.find('.js-checkList').prop('checked') + return if !item.find('.js-list').val() + item.find('.js-checkList').prop('checked', true) + item.find('.js-checkManual').prop('checked', false) - # build options list based on config - @getConfigOptionList( attribute_ical ) + updateCheckManual = -> + return if item.find('.js-checkManual').prop('checked') + item.find('.js-checkList').prop('checked', false) + item.find('.js-checkManual').prop('checked', true) - # add null selection if needed - @addNullOption( attribute_ical ) + updateShadow = (selected) -> + if !selected + selected = item.find('.js-check:checked').attr('value') + if selected is 'manual' + item.find('.js-shadow').val( item.find('.js-manual').val() ) + else + item.find('.js-shadow').val( item.find('.js-list').val() ) - # sort attribute.options - @sortOptions( attribute_ical ) + # set inital state + if ical_feeds[attribute.value] + updateCheckList() + else + updateCheckManual() + item.find('.js-manual').val(attribute.value) - # finde selected/checked item of list - @selectedOptions( attribute_ical ) + item.find('.js-check').bind('change', -> + updateShadow() + ) - templateSelections = App.UiElement.searchable_select.render(attribute_ical) + item.find('.js-list').bind('click change', -> + updateCheckList() + updateShadow('list') + ) - templateSelections.find('.js-shadow').bind('change', (e) -> - val = $(e.target).val() - if val - item.find("[name=#{attribute.name}]").val(val) - ) - item.append(templateSelections) + item.find('.js-manual').bind('keyup focus blur', -> + updateCheckManual() + updateShadow('manual') + ) - item \ No newline at end of file + item diff --git a/app/assets/javascripts/app/controllers/calendar.js.coffee b/app/assets/javascripts/app/controllers/calendar.js.coffee index 4d45c3d92..30a16400b 100644 --- a/app/assets/javascripts/app/controllers/calendar.js.coffee +++ b/app/assets/javascripts/app/controllers/calendar.js.coffee @@ -58,7 +58,8 @@ class Index extends App.ControllerContent for day in keys itemTime = new Date( Date.parse( "#{day}T00:00:00Z" ) ) if itemTime < till && itemTime > from - public_holidays_preview[day] = calendar.public_holidays[day] + if calendar.public_holidays[day] && calendar.public_holidays[day].active + public_holidays_preview[day] = calendar.public_holidays[day] calendar.public_holidays_preview = public_holidays_preview # show description button, only if content exists diff --git a/app/assets/javascripts/app/models/calendar.js.coffee b/app/assets/javascripts/app/models/calendar.js.coffee index 9ac906c1c..add3b4131 100644 --- a/app/assets/javascripts/app/models/calendar.js.coffee +++ b/app/assets/javascripts/app/models/calendar.js.coffee @@ -1,5 +1,5 @@ class App.Calendar extends App.Model - @configure 'Calendar', 'name', 'timezone', 'default', 'business_hours', 'ical_url', 'public_holidays' + @configure 'Calendar', 'name', 'timezone', 'default', 'business_hours', 'ical_url', 'public_holidays', 'note' @extend Spine.Model.Ajax @url: @apiPath + '/calendars' @@ -7,8 +7,8 @@ class App.Calendar extends App.Model { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false }, { name: 'timezone', display: 'Timezone', tag: 'timezone', null: false } { name: 'business_hours', display: 'Business Hours', tag: 'business_hours', null: true } - { name: 'ical_url', display: 'Public Holidays iCal Feed', tag: 'ical_feed', placeholder: 'http://example.com/public_holidays.ical', null: true } - { name: 'public_holidays',display: 'Public Holidays', tag: 'holiday_selector', null: true } + { name: 'ical_url', display: 'Holidays iCalendar Feed', tag: 'ical_feed', placeholder: 'http://example.com/public_holidays.ical', null: true } + { name: 'public_holidays',display: 'Holidays', tag: 'holiday_selector', null: true } { name: 'note', display: 'Note', tag: 'textarea', limit: 250, null: true }, { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 }, diff --git a/app/assets/javascripts/app/views/calendar/holiday_selector.jst.eco b/app/assets/javascripts/app/views/calendar/holiday_selector.jst.eco index 3646010f7..572bae413 100644 --- a/app/assets/javascripts/app/views/calendar/holiday_selector.jst.eco +++ b/app/assets/javascripts/app/views/calendar/holiday_selector.jst.eco @@ -8,59 +8,49 @@ <% for day, meta of @days: %> - class="is-inactive"<% end %>> + class="is-inactive"<% end %> data-date="<%= day %>"> <%- @Tdate(day) %> - <%= meta.summary %> +
-
- <%- @Icon('trash') %> <%- @T('Remove') %> -
+ <% if !meta.feed: %> +
+ <%- @Icon('trash') %> <%- @T('Remove') %> +
+ <% end %>
<% end %> - + + - <%- @Tdate('2015-12-25') %> - Some Description - -
-
- <%- @Icon('trash') %> <%- @T('Remove') %> -
-
- - - - <%- @Tdate('2015-12-26') %> - Some Description + +
<%- @Icon('trash') %> <%- @T('Remove') %>
+ + + - - - - +
<%- @Icon('plus-small') %> <%- @T('Add') %> diff --git a/app/assets/stylesheets/zammad.css.scss b/app/assets/stylesheets/zammad.css.scss index 67ede89c5..2e1278b97 100644 --- a/app/assets/stylesheets/zammad.css.scss +++ b/app/assets/stylesheets/zammad.css.scss @@ -1161,7 +1161,7 @@ input.time.time--12 { display: none; } - .form-control[disabled] { + .form-control[disabled], .form-control.is-disabled { cursor: not-allowed; background-color: #fff; color: #d5d5d5; diff --git a/app/models/calendar.rb b/app/models/calendar.rb index 071b94f09..464e9af9d 100644 --- a/app/models/calendar.rb +++ b/app/models/calendar.rb @@ -4,8 +4,8 @@ class Calendar < ApplicationModel store :business_hours store :public_holidays - before_create :fetch_ical - before_update :fetch_ical + before_create :validate_public_holidays, :fetch_ical + before_update :validate_public_holidays, :fetch_ical after_create :sync_default, :min_one_check after_update :sync_default, :min_one_check after_destroy :min_one_check @@ -71,14 +71,14 @@ returns def self.ical_feeds gfeeds = { - 'Australian' => 'en.australian', - 'Austrian' => 'de.austrian', + 'Australia' => 'en.australian', + 'Austria' => 'de.austrian', 'Argentina' => 'en.ar', 'Bahamas' => 'en.bs', 'Belarus' => 'en.by', - 'Brazilian' => 'en.brazilian', + 'Brazil' => 'en.brazilian', 'Bulgaria' => 'en.bulgarian', - 'Canadian' => 'en.canadian', + 'Canada' => 'en.canadian', 'China' => 'en.china', 'Chile' => 'en.cl', 'Costa Rica' => 'en.cr', @@ -87,47 +87,44 @@ returns 'Cuba' => 'en.cu', 'Cyprus' => 'de.cy', 'Switzerland' => 'de.ch', - 'Christian' => 'en.christian', - 'Danish' => 'da.danish', - 'Dutch' => 'nl.dutch', + 'Denmark' => 'da.danish', + 'Netherlands' => 'nl.dutch', 'Egypt' => 'en.eg', 'Ethiopia' => 'en.et', 'Ecuador' => 'en.ec', 'Estonia' => 'en.ee', - 'Finnish' => 'en.finnish', - 'French' => 'en.french', - 'German' => 'de.german', - 'Greek' => 'en.greek', + 'Finland' => 'en.finnish', + 'France' => 'en.french', + 'Germany' => 'de.german', + 'Greece' => 'en.greek', 'Ghana' => 'en.gh', 'Hong Kong' => 'en.hong_kong', 'Haiti' => 'en.ht', 'Hungary' => 'en.hungarian', - 'Indian' => 'en.indian', - 'Indonesian' => 'en.indonesian', - 'Iranian' => 'en.ir', - 'Irish' => 'en.irish', - 'Islamic' => 'en.islamic', - 'Italian' => 'it.italian', + 'India' => 'en.indian', + 'Indonesia' => 'en.indonesian', + 'Iran' => 'en.ir', + 'Ireland' => 'en.irish', + 'Italy' => 'it.italian', 'Israel' => 'en.jewish', - 'Japanese' => 'en.japanese', - 'Jewish' => 'en.jewish', + 'Japan' => 'en.japanese', 'Kuwait' => 'en.kw', 'Latvia' => 'en.latvian', 'Liechtenstein' => 'en.li', 'Lithuania' => 'en.lithuanian', 'Luxembourg' => 'en.lu', - 'Malaysian' => 'en.malaysia', - 'Mexican' => 'en.mexican', + 'Malaysia' => 'en.malaysia', + 'Mexico' => 'en.mexican', 'Morocco' => 'en.ma', 'Mauritius' => 'en.mu', 'Moldova' => 'en.md', 'New Zealand' => 'en.new_zealand', - 'Norwegian' => 'en.norwegian', + 'Norway' => 'en.norwegian', 'Philippines' => 'en.philippines', - 'Polish' => 'en.polish', - 'Portuguese' => 'en.portuguese', + 'Poland' => 'en.polish', + 'Portugal' => 'en.portuguese', 'Pakistan' => 'en.pk', - 'Russian' => 'en.russian', + 'Russia' => 'en.russian', 'Senegal' => 'en.sn', 'Singapore' => 'en.singapore', 'South Africa' => 'en.sa', @@ -136,7 +133,7 @@ returns 'Slovakia' => 'en.slovak', 'Serbia' => 'en.rs', 'Slovenia' => 'en.slovenian', - 'Swedish' => 'en.swedish', + 'Sweden' => 'en.swedish', 'Taiwan' => 'en.taiwan', 'Thai' => 'en.th', 'Turkey' => 'en.turkish', @@ -144,12 +141,12 @@ returns 'US' => 'en.usa', 'Ukraine' => 'en.ukrainian', 'Uruguay' => 'en.uy', - 'Vietnamese' => 'en.vietnamese', + 'Vietnam' => 'en.vietnamese', 'Venezuela' => 'en.ve', } all_feeds = {} gfeeds.each {|key, name| - all_feeds["http://www.google.com/calendar/ical/#{name}%23holiday%40group.v.calendar.google.com/public/basic.ics"] = "#{key} - Holidays" + all_feeds["http://www.google.com/calendar/ical/#{name}%23holiday%40group.v.calendar.google.com/public/basic.ics"] = key } all_feeds end @@ -218,6 +215,15 @@ returns if !public_holidays self.public_holidays = {} end + + # remove old ical entries if feed has changed + public_holidays.each {|day, meta| + next if !public_holidays[day]['feed'] + next if meta['feed'] == Digest::MD5.hexdigest(ical_url) + public_holidays.delete(day) + } + + # sync new ical feed dates events.each {|day, summary| if !public_holidays[day] public_holidays[day] = {} @@ -230,6 +236,7 @@ returns public_holidays[day] = { active: true, summary: summary, + feed: Digest::MD5.hexdigest(ical_url) } } self.last_log = nil @@ -315,4 +322,21 @@ returns def fetch_ical sync(true) end + + # validate format of public holidays + def validate_public_holidays + + # fillup feed info + public_holidays.each {|day, meta| + if public_holidays_was && public_holidays_was[day] && public_holidays_was[day]['feed'] + meta['feed'] = public_holidays_was[day]['feed'] + end + if meta['active'] + meta['active'] = true + else + meta['active'] = false + end + } + + end end