diff --git a/app/assets/javascripts/calendar/custom.js b/app/assets/javascripts/calendar/custom.js index 6e254f5..6f2e3e0 100644 --- a/app/assets/javascripts/calendar/custom.js +++ b/app/assets/javascripts/calendar/custom.js @@ -1,6 +1,7 @@ $(document).ready(function() { $(window).trigger("resize"); var schedule_query_url = "/api/schedules.json"; + var holiday_query_url = "/api/holidays.json"; var MyCalendar = $("#calendar"); var MyMiniCalendar = $("#mini-calendar"); var EventPopup = $("#quick-event-popup"); @@ -168,6 +169,8 @@ $(document).ready(function() { localStorage.setItem("view_type", view.type); + cloneHeaderRow(view); + try { setTimeLine(); setInterval(function(){ setTimeLine(); }, clock*1000); @@ -177,6 +180,8 @@ $(document).ready(function() { setElementBackground(lastSelectedDay, ""); MyCalendar.find(".fc-left").append($("#room_selector")); MyCalendar.find(".fc-right").append($("#other_dropdown")); + + renderHolidays(view); }, loading: function(isLoading, view){ $("label.loading").attr("class", isLoading ? "loading" : "loading hidden"); @@ -337,6 +342,46 @@ $(document).ready(function() { } } + function cloneHeaderRow(view) { + if (view.type != "month" && $(".list-holidays").length == 0){ + MyCalendar.find(".fc-head").clone().addClass("list-holidays").insertAfter(".fc-head"); + $(".list-holidays .fc-day-header").each(function(){ + if (view.type == "agendaDay"){ + var currentDate = new Date(MyCalendar.fullCalendar("getView").start); + $(this).attr("value", dateFormat(currentDate, "mm-dd")).html(" "); + } else { + $(this).attr("value", dateFormat(new Date($(this).text()), "mm-dd")).html(" "); + } + }); + } + } + + function renderHolidays(view) { + var fcStartRange = new Date(MyCalendar.fullCalendar("getView").start); + var fcEndRange = new Date(MyCalendar.fullCalendar("getView").end); + $.ajax({ + url: holiday_query_url, + type: "GET", + data: {start_date_range: fcStartRange, end_date_range: fcEndRange}, + success: function(response) { + if(response.holidays){ + $.map(response.holidays, function(holiday) { + holidayDate = dateFormat(new Date(holiday.date), "mm-dd"); + if (view.type == "month"){ + $(".fc-day-number").filter(function(){ + return dateFormat($(this).data("date"), "mm-dd") == holidayDate; + }).addClass("holiday").prepend("" + holiday.name + ""); + } else { + $(".list-holidays .fc-day-header").filter(function(){ + return $(this).attr("value") == holidayDate; + }).addClass("holiday").text(holiday.name); + } + }); + } + } + }); + } + $(document).on("click", ".fc-body, #quick-event-popup", function(e){ e.stopPropagation(); }); diff --git a/app/assets/javascripts/date_format.js b/app/assets/javascripts/date_format.js new file mode 100644 index 0000000..f5dc4ab --- /dev/null +++ b/app/assets/javascripts/date_format.js @@ -0,0 +1,125 @@ +/* + * Date Format 1.2.3 + * (c) 2007-2009 Steven Levithan + * MIT license + * + * Includes enhancements by Scott Trenda + * and Kris Kowal + * + * Accepts a date, a mask, or a date and a mask. + * Returns a formatted version of the given date. + * The date defaults to the current date/time. + * The mask defaults to dateFormat.masks.default. + */ + +var dateFormat = function () { + var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, + timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, + timezoneClip = /[^-+\dA-Z]/g, + pad = function (val, len) { + val = String(val); + len = len || 2; + while (val.length < len) val = "0" + val; + return val; + }; + + // Regexes and supporting functions are cached through closure + return function (date, mask, utc) { + var dF = dateFormat; + + // You can't provide utc if you skip other args (use the "UTC:" mask prefix) + if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) { + mask = date; + date = undefined; + } + + // Passing date through Date applies Date.parse, if necessary + date = date ? new Date(date) : new Date; + if (isNaN(date)) throw SyntaxError("invalid date"); + + mask = String(dF.masks[mask] || mask || dF.masks["default"]); + + // Allow setting the utc argument via the mask + if (mask.slice(0, 4) == "UTC:") { + mask = mask.slice(4); + utc = true; + } + + var _ = utc ? "getUTC" : "get", + d = date[_ + "Date"](), + D = date[_ + "Day"](), + m = date[_ + "Month"](), + y = date[_ + "FullYear"](), + H = date[_ + "Hours"](), + M = date[_ + "Minutes"](), + s = date[_ + "Seconds"](), + L = date[_ + "Milliseconds"](), + o = utc ? 0 : date.getTimezoneOffset(), + flags = { + d: d, + dd: pad(d), + ddd: dF.i18n.dayNames[D], + dddd: dF.i18n.dayNames[D + 7], + m: m + 1, + mm: pad(m + 1), + mmm: dF.i18n.monthNames[m], + mmmm: dF.i18n.monthNames[m + 12], + yy: String(y).slice(2), + yyyy: y, + h: H % 12 || 12, + hh: pad(H % 12 || 12), + H: H, + HH: pad(H), + M: M, + MM: pad(M), + s: s, + ss: pad(s), + l: pad(L, 3), + L: pad(L > 99 ? Math.round(L / 10) : L), + t: H < 12 ? "a" : "p", + tt: H < 12 ? "am" : "pm", + T: H < 12 ? "A" : "P", + TT: H < 12 ? "AM" : "PM", + Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), + o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), + S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] + }; + + return mask.replace(token, function ($0) { + return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); + }); + }; +}(); + +// Some common format strings +dateFormat.masks = { + "default": "ddd mmm dd yyyy HH:MM:ss", + shortDate: "m/d/yy", + mediumDate: "mmm d, yyyy", + longDate: "mmmm d, yyyy", + fullDate: "dddd, mmmm d, yyyy", + shortTime: "h:MM TT", + mediumTime: "h:MM:ss TT", + longTime: "h:MM:ss TT Z", + isoDate: "yyyy-mm-dd", + isoTime: "HH:MM:ss", + isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", + isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" +}; + +// Internationalization strings +dateFormat.i18n = { + dayNames: [ + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" + ], + monthNames: [ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" + ] +}; + +// For convenience... +Date.prototype.format = function (mask, utc) { + return dateFormat(this, mask, utc); +}; diff --git a/app/assets/javascripts/full_calendar.js b/app/assets/javascripts/full_calendar.js index 94e5125..2102606 100644 --- a/app/assets/javascripts/full_calendar.js +++ b/app/assets/javascripts/full_calendar.js @@ -19,3 +19,4 @@ //= require schedules //= require underscore //= require header-dropdown +//= require date_format diff --git a/app/assets/stylesheets/custom.scss b/app/assets/stylesheets/custom.scss index 023863b..a49f6fe 100644 --- a/app/assets/stylesheets/custom.scss +++ b/app/assets/stylesheets/custom.scss @@ -576,15 +576,33 @@ footer { #load { position: absolute; - margin-left: 275px; + margin-left: 265px; padding: 3.6px; - padding-left: 10px; - padding-right: 10px; + padding-left: 5px; + padding-right: 5px; +} + +.holiday { + border: 1px solid #16A765 !important; + color: #1d1d1d !important; + background-color: #16A765 !important; + margin: 2px; +} + +.holiday-name-month { + position: relative; + right: 40%; } .glyphicon-refresh-animate { -animation: spin .7s infinite linear; -webkit-animation: spin2 .7s infinite linear; + -moz-animation: spin3 .7s infinite linear; +} + +@-moz-keyframes spin3 { + from { -moz-transform: rotate(0deg);} + to { -moz-transform: rotate(360deg);} } @-webkit-keyframes spin2 { diff --git a/app/controllers/api/holidays_controller.rb b/app/controllers/api/holidays_controller.rb new file mode 100644 index 0000000..b03ce75 --- /dev/null +++ b/app/controllers/api/holidays_controller.rb @@ -0,0 +1,16 @@ +class Api::HolidaysController < ApplicationController + before_action :load_holidays, only: :index + + def index + respond_to do |format| + format.json {render json: {holidays: @holidays}, status: :ok} + end + end + + private + def load_holidays + from = Date.parse params[:start_date_range] + to = Date.parse params[:end_date_range] + @holidays = Holiday.in_range from, to + end +end diff --git a/app/models/ability.rb b/app/models/ability.rb index ea14d7f..63668c1 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -8,7 +8,7 @@ def initialize user when user.admin? can :access, :rails_admin can :dashboard - can :manage, [User, Repeat, Room] + can :manage, [User, Repeat, Room, Holiday] can [:create, :read, :destroy], Schedule can :update, Schedule do |schedule| schedule.finish_time > Time.zone.now diff --git a/app/models/concerns/rails_admin_holiday.rb b/app/models/concerns/rails_admin_holiday.rb new file mode 100644 index 0000000..dfa8eb5 --- /dev/null +++ b/app/models/concerns/rails_admin_holiday.rb @@ -0,0 +1,20 @@ +module RailsAdminHoliday + extend ActiveSupport::Concern + + included do + rails_admin do + list do + field :id + field :name + field :date + field :is_annual + end + + edit do + field :name + field :date + field :is_annual + end + end + end +end diff --git a/app/models/holiday.rb b/app/models/holiday.rb new file mode 100644 index 0000000..4ebd0cd --- /dev/null +++ b/app/models/holiday.rb @@ -0,0 +1,12 @@ +class Holiday < ActiveRecord::Base + include RailsAdminHoliday + + QUERY = "(is_annual = 0 AND date BETWEEN :from AND :to) + OR (is_annual = 1 AND DATE_FORMAT(date,'%m-%d') >= DATE_FORMAT(:from,'%m-%d') + AND DATE_FORMAT(date,'%m-%d') <= DATE_FORMAT(:to,'%m-%d'))" + + validates :name, presence: true + validates :date, presence: true + + scope :in_range, -> (from, to){where(QUERY, from: from, to: to)} +end diff --git a/config/routes.rb b/config/routes.rb index dbd03fc..d7dc1d1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -18,5 +18,6 @@ resources :my_schedules, only: :index resources :shared_schedules, only: :index resources :schedules, only: :index + resources :holidays, only: :index end end diff --git a/db/migrate/20160204155953_create_holidays.rb b/db/migrate/20160204155953_create_holidays.rb new file mode 100644 index 0000000..ec2cd87 --- /dev/null +++ b/db/migrate/20160204155953_create_holidays.rb @@ -0,0 +1,9 @@ +class CreateHolidays < ActiveRecord::Migration + def change + create_table :holidays do |t| + t.string :name + t.date :date + t.boolean :is_annual, default: true + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 42f23f0..daa95da 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,13 +11,19 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160201035201) do +ActiveRecord::Schema.define(version: 20160204155953) do create_table "creators", force: :cascade do |t| t.string "email", limit: 255 t.string "display_name", limit: 255 end + create_table "holidays", force: :cascade do |t| + t.string "name", limit: 255 + t.date "date" + t.boolean "is_annual", limit: 1, default: true + end + create_table "repeats", force: :cascade do |t| t.integer "repeat_type", limit: 4 t.datetime "created_at", null: false