Implemented holiday selector for calendars.

This commit is contained in:
Martin Edenhofer 2015-09-25 09:21:55 +02:00
parent b44dbe371f
commit fdea787b2f
7 changed files with 178 additions and 90 deletions

View file

@ -1,7 +1,7 @@
# coffeelint: disable=camel_case_classes # coffeelint: disable=camel_case_classes
class App.UiElement.holiday_selector class App.UiElement.holiday_selector
@render: (attribute, params) -> @render: (attribute, params) ->
console.log('aa', attribute)
days = {} days = {}
if attribute.value if attribute.value
days = attribute.value days = attribute.value
@ -11,3 +11,64 @@ class App.UiElement.holiday_selector
days_new[day] = days[day] days_new[day] = days[day]
item = $( App.view('calendar/holiday_selector')( attribute: attribute, days: days_new ) ) 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

View file

@ -1,36 +1,48 @@
# coffeelint: disable=camel_case_classes # coffeelint: disable=camel_case_classes
class App.UiElement.ical_feed extends App.UiElement.ApplicationUiElement class App.UiElement.ical_feed extends App.UiElement.ApplicationUiElement
@render: (attribute, params) -> @render: (attribute, params) ->
console.log('A', attribute)
item = $( '<div>' + App.view('generic/input')( attribute: attribute ) + '</div>' )
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) updateCheckList = ->
attribute_ical = return if item.find('.js-checkList').prop('checked')
options: ical_feeds return if !item.find('.js-list').val()
tag: 'searchable_select' item.find('.js-checkList').prop('checked', true)
placeholder: App.i18n.translateInline('Search public ical feed...') item.find('.js-checkManual').prop('checked', false)
# build options list based on config updateCheckManual = ->
@getConfigOptionList( attribute_ical ) 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 updateShadow = (selected) ->
@addNullOption( attribute_ical ) 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 # set inital state
@sortOptions( attribute_ical ) if ical_feeds[attribute.value]
updateCheckList()
else
updateCheckManual()
item.find('.js-manual').val(attribute.value)
# finde selected/checked item of list item.find('.js-check').bind('change', ->
@selectedOptions( attribute_ical ) updateShadow()
)
templateSelections = App.UiElement.searchable_select.render(attribute_ical)
item.find('.js-list').bind('click change', ->
templateSelections.find('.js-shadow').bind('change', (e) -> updateCheckList()
val = $(e.target).val() updateShadow('list')
if val )
item.find("[name=#{attribute.name}]").val(val)
item.find('.js-manual').bind('keyup focus blur', ->
updateCheckManual()
updateShadow('manual')
) )
item.append(templateSelections)
item item

View file

@ -58,6 +58,7 @@ class Index extends App.ControllerContent
for day in keys for day in keys
itemTime = new Date( Date.parse( "#{day}T00:00:00Z" ) ) itemTime = new Date( Date.parse( "#{day}T00:00:00Z" ) )
if itemTime < till && itemTime > from if itemTime < till && itemTime > from
if calendar.public_holidays[day] && calendar.public_holidays[day].active
public_holidays_preview[day] = calendar.public_holidays[day] public_holidays_preview[day] = calendar.public_holidays[day]
calendar.public_holidays_preview = public_holidays_preview calendar.public_holidays_preview = public_holidays_preview

View file

@ -1,5 +1,5 @@
class App.Calendar extends App.Model 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 @extend Spine.Model.Ajax
@url: @apiPath + '/calendars' @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: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
{ name: 'timezone', display: 'Timezone', tag: 'timezone', null: false } { name: 'timezone', display: 'Timezone', tag: 'timezone', null: false }
{ name: 'business_hours', display: 'Business Hours', tag: 'business_hours', null: true } { 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: 'ical_url', display: 'Holidays iCalendar 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: 'public_holidays',display: 'Holidays', tag: 'holiday_selector', null: true }
{ name: 'note', display: 'Note', tag: 'textarea', limit: 250, 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_by_id', display: 'Created by', relation: 'User', readonly: 1 },
{ name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 }, { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },

View file

@ -8,59 +8,49 @@
</thead> </thead>
<tbody> <tbody>
<% for day, meta of @days: %> <% for day, meta of @days: %>
<tr <% if !meta.active: %>class="is-inactive"<% end %>> <tr <% if !meta.active: %>class="is-inactive"<% end %> data-date="<%= day %>">
<td> <td>
<label class="checkbox-replacement"> <label class="checkbox-replacement">
<input type="checkbox" checked> <input type="checkbox" <% if meta.active: %>checked<% end %> class="js-active" name="{boolean}public_holidays::<%= day %>::active" value="true">
<%- @Icon('checkbox', 'icon-unchecked') %> <%- @Icon('checkbox', 'icon-unchecked') %>
<%- @Icon('checkbox-checked', 'icon-checked') %> <%- @Icon('checkbox-checked', 'icon-checked') %>
</label> </label>
<td><%- @Tdate(day) %> <td><%- @Tdate(day) %>
<td><%= meta.summary %> <td><input class="form-control form-control--small js-description <% if !meta.active: %>is-disabled<% end %>" type="text" name="public_holidays::<%= day %>::summary" value="<%= meta.summary %>" required/>
<td> <td>
<div class="settings-list-rowControls"> <div class="settings-list-rowControls">
<% if !meta.feed: %>
<div class="btn btn--text js-remove"> <div class="btn btn--text js-remove">
<%- @Icon('trash') %> <%- @T('Remove') %> <%- @Icon('trash') %> <%- @T('Remove') %>
</div> </div>
</div>
<% end %> <% end %>
<tr> </div>
<% end %>
<tr class="hidden js-placeholder" data-date="">
<td> <td>
<label class="checkbox-replacement"> <label class="checkbox-replacement">
<input type="checkbox" checked> <input type="checkbox" checked class="js-active" name="" value="true">
<%- @Icon('checkbox', 'icon-unchecked') %> <%- @Icon('checkbox', 'icon-unchecked') %>
<%- @Icon('checkbox-checked', 'icon-checked') %> <%- @Icon('checkbox-checked', 'icon-checked') %>
</label> </label>
<td><%- @Tdate('2015-12-25') %> <td class="js-date">
<td>Some Description <td><input class="form-control form-control--small js-summary" type="text" name="" value="<%= meta.summary %>" required/>
<td>
<div class="settings-list-rowControls">
<div class="btn btn--text js-remove">
<%- @Icon('trash') %> <%- @T('Remove') %>
</div>
</div>
<tr class="is-inactive">
<td>
<label class="checkbox-replacement">
<input type="checkbox">
<%- @Icon('checkbox', 'icon-unchecked') %>
<%- @Icon('checkbox-checked', 'icon-checked') %>
</label>
<td><%- @Tdate('2015-12-26') %>
<td>Some Description
<td> <td>
<div class="settings-list-rowControls"> <div class="settings-list-rowControls">
<div class="btn btn--text js-remove"> <div class="btn btn--text js-remove">
<%- @Icon('trash') %> <%- @T('Remove') %> <%- @Icon('trash') %> <%- @T('Remove') %>
</div> </div>
</div> </div>
<tr class="settings-list-controlRow"> <tr class="settings-list-controlRow">
<td> <td>
<td class="js-datePicker">
<!-- not supported right now by ff
<input class="form-control form-control--small" type="date" placeholder="<%- @T('Date') %>"/>
-->
<td> <td>
<!-- Hallo Martin! Allow to add by pressing enter! --> <input class="form-control form-control--small js-summary" type="text" placeholder="<%- @T('Description') %>"/>
<input class="form-control form-control--small" type="date" name="public_holidays_date" placeholder="<%- @T('Date') %>"/>
<td>
<input class="form-control form-control--small" type="text" name="public_holidays_description" placeholder="<%- @T('Description') %>"/>
<td> <td>
<div class="btn btn--text js-add"> <div class="btn btn--text js-add">
<%- @Icon('plus-small') %> <%- @T('Add') %> <%- @Icon('plus-small') %> <%- @T('Add') %>

View file

@ -1161,7 +1161,7 @@ input.time.time--12 {
display: none; display: none;
} }
.form-control[disabled] { .form-control[disabled], .form-control.is-disabled {
cursor: not-allowed; cursor: not-allowed;
background-color: #fff; background-color: #fff;
color: #d5d5d5; color: #d5d5d5;

View file

@ -4,8 +4,8 @@ class Calendar < ApplicationModel
store :business_hours store :business_hours
store :public_holidays store :public_holidays
before_create :fetch_ical before_create :validate_public_holidays, :fetch_ical
before_update :fetch_ical before_update :validate_public_holidays, :fetch_ical
after_create :sync_default, :min_one_check after_create :sync_default, :min_one_check
after_update :sync_default, :min_one_check after_update :sync_default, :min_one_check
after_destroy :min_one_check after_destroy :min_one_check
@ -71,14 +71,14 @@ returns
def self.ical_feeds def self.ical_feeds
gfeeds = { gfeeds = {
'Australian' => 'en.australian', 'Australia' => 'en.australian',
'Austrian' => 'de.austrian', 'Austria' => 'de.austrian',
'Argentina' => 'en.ar', 'Argentina' => 'en.ar',
'Bahamas' => 'en.bs', 'Bahamas' => 'en.bs',
'Belarus' => 'en.by', 'Belarus' => 'en.by',
'Brazilian' => 'en.brazilian', 'Brazil' => 'en.brazilian',
'Bulgaria' => 'en.bulgarian', 'Bulgaria' => 'en.bulgarian',
'Canadian' => 'en.canadian', 'Canada' => 'en.canadian',
'China' => 'en.china', 'China' => 'en.china',
'Chile' => 'en.cl', 'Chile' => 'en.cl',
'Costa Rica' => 'en.cr', 'Costa Rica' => 'en.cr',
@ -87,47 +87,44 @@ returns
'Cuba' => 'en.cu', 'Cuba' => 'en.cu',
'Cyprus' => 'de.cy', 'Cyprus' => 'de.cy',
'Switzerland' => 'de.ch', 'Switzerland' => 'de.ch',
'Christian' => 'en.christian', 'Denmark' => 'da.danish',
'Danish' => 'da.danish', 'Netherlands' => 'nl.dutch',
'Dutch' => 'nl.dutch',
'Egypt' => 'en.eg', 'Egypt' => 'en.eg',
'Ethiopia' => 'en.et', 'Ethiopia' => 'en.et',
'Ecuador' => 'en.ec', 'Ecuador' => 'en.ec',
'Estonia' => 'en.ee', 'Estonia' => 'en.ee',
'Finnish' => 'en.finnish', 'Finland' => 'en.finnish',
'French' => 'en.french', 'France' => 'en.french',
'German' => 'de.german', 'Germany' => 'de.german',
'Greek' => 'en.greek', 'Greece' => 'en.greek',
'Ghana' => 'en.gh', 'Ghana' => 'en.gh',
'Hong Kong' => 'en.hong_kong', 'Hong Kong' => 'en.hong_kong',
'Haiti' => 'en.ht', 'Haiti' => 'en.ht',
'Hungary' => 'en.hungarian', 'Hungary' => 'en.hungarian',
'Indian' => 'en.indian', 'India' => 'en.indian',
'Indonesian' => 'en.indonesian', 'Indonesia' => 'en.indonesian',
'Iranian' => 'en.ir', 'Iran' => 'en.ir',
'Irish' => 'en.irish', 'Ireland' => 'en.irish',
'Islamic' => 'en.islamic', 'Italy' => 'it.italian',
'Italian' => 'it.italian',
'Israel' => 'en.jewish', 'Israel' => 'en.jewish',
'Japanese' => 'en.japanese', 'Japan' => 'en.japanese',
'Jewish' => 'en.jewish',
'Kuwait' => 'en.kw', 'Kuwait' => 'en.kw',
'Latvia' => 'en.latvian', 'Latvia' => 'en.latvian',
'Liechtenstein' => 'en.li', 'Liechtenstein' => 'en.li',
'Lithuania' => 'en.lithuanian', 'Lithuania' => 'en.lithuanian',
'Luxembourg' => 'en.lu', 'Luxembourg' => 'en.lu',
'Malaysian' => 'en.malaysia', 'Malaysia' => 'en.malaysia',
'Mexican' => 'en.mexican', 'Mexico' => 'en.mexican',
'Morocco' => 'en.ma', 'Morocco' => 'en.ma',
'Mauritius' => 'en.mu', 'Mauritius' => 'en.mu',
'Moldova' => 'en.md', 'Moldova' => 'en.md',
'New Zealand' => 'en.new_zealand', 'New Zealand' => 'en.new_zealand',
'Norwegian' => 'en.norwegian', 'Norway' => 'en.norwegian',
'Philippines' => 'en.philippines', 'Philippines' => 'en.philippines',
'Polish' => 'en.polish', 'Poland' => 'en.polish',
'Portuguese' => 'en.portuguese', 'Portugal' => 'en.portuguese',
'Pakistan' => 'en.pk', 'Pakistan' => 'en.pk',
'Russian' => 'en.russian', 'Russia' => 'en.russian',
'Senegal' => 'en.sn', 'Senegal' => 'en.sn',
'Singapore' => 'en.singapore', 'Singapore' => 'en.singapore',
'South Africa' => 'en.sa', 'South Africa' => 'en.sa',
@ -136,7 +133,7 @@ returns
'Slovakia' => 'en.slovak', 'Slovakia' => 'en.slovak',
'Serbia' => 'en.rs', 'Serbia' => 'en.rs',
'Slovenia' => 'en.slovenian', 'Slovenia' => 'en.slovenian',
'Swedish' => 'en.swedish', 'Sweden' => 'en.swedish',
'Taiwan' => 'en.taiwan', 'Taiwan' => 'en.taiwan',
'Thai' => 'en.th', 'Thai' => 'en.th',
'Turkey' => 'en.turkish', 'Turkey' => 'en.turkish',
@ -144,12 +141,12 @@ returns
'US' => 'en.usa', 'US' => 'en.usa',
'Ukraine' => 'en.ukrainian', 'Ukraine' => 'en.ukrainian',
'Uruguay' => 'en.uy', 'Uruguay' => 'en.uy',
'Vietnamese' => 'en.vietnamese', 'Vietnam' => 'en.vietnamese',
'Venezuela' => 'en.ve', 'Venezuela' => 'en.ve',
} }
all_feeds = {} all_feeds = {}
gfeeds.each {|key, name| 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 all_feeds
end end
@ -218,6 +215,15 @@ returns
if !public_holidays if !public_holidays
self.public_holidays = {} self.public_holidays = {}
end 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| events.each {|day, summary|
if !public_holidays[day] if !public_holidays[day]
public_holidays[day] = {} public_holidays[day] = {}
@ -230,6 +236,7 @@ returns
public_holidays[day] = { public_holidays[day] = {
active: true, active: true,
summary: summary, summary: summary,
feed: Digest::MD5.hexdigest(ical_url)
} }
} }
self.last_log = nil self.last_log = nil
@ -315,4 +322,21 @@ returns
def fetch_ical def fetch_ical
sync(true) sync(true)
end 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 end