Update fullcalendar from 1.6.1 to 5.8.0.

Add calendar to backedend show page.
Add calendar to frontend hire page.
Add error message description when hiring property.
Add backend edit hire page.
This commit is contained in:
BoHung Chiu 2021-07-05 18:05:49 +08:00
parent c9f424b30d
commit 24f93f7630
19 changed files with 23512 additions and 781 deletions

View File

@ -1,8 +1,112 @@
var Calendar = function(dom,property_id){
window.auto_close_popup = false;
$.fn.fullCalendar = function(args){
var self = this[0]
if(!self.calendar_args)
self.calendar_args = args;
else
args = Object.assign(self.calendar_args, args);
var calendar = new FullCalendar.Calendar(self,args);
calendar.render();
$(window).on("load",function(){
calendar.render();
})
this.calendar = calendar;
self.calendar = calendar;
$.fullCalendar = calendar;
return calendar;
};
function correct_date(date){
var new_date = new Date();
new_date.setTime(date.getTime() + date.getTimezoneOffset() * 60 * 1000);
return new_date;
}
FullCalendar.Calendar.prototype.get_all_events = function(){
this.currentData.all_events = [];
var all_events = this.currentData.all_events;
if(this.currentData.eventStore && this.currentData.eventStore.instances){
var instances = this.currentData.eventStore.instances;
Object.keys(instances).forEach(function(k){
var instance = instances[k];
var range = Object.assign({},instance.range);
range.start = correct_date(range.start);
range.end = correct_date(range.end);
all_events.push(range);
})
}
return this.currentData.all_events;
}
FullCalendar.Calendar.prototype.isAnOverlapEvent = function(eventStartDay, eventEndDay){
eventStartDay = eventStartDay || eventEndDay;
eventEndDay = eventEndDay || eventStartDay;
if((typeof(eventStartDay)).toLowerCase() == "string")
eventStartDay = new Date(eventStartDay);
if((typeof(eventEndDay)).toLowerCase() == "string")
eventEndDay = new Date(eventEndDay);
var events = this.get_all_events();
for (var i = 0; i < events.length; i++) {
var eventA = events[i];
// start-time in between any of the events
if (eventStartDay >= eventA.start && eventStartDay <= eventA.end) {
return true;
}
//end-time in between any of the events
if (eventEndDay >= eventA.start && eventEndDay <= eventA.end) {
return true;
}
//any of the events in between/on the start-time and end-time
if (eventStartDay <= eventA.start && eventEndDay >= eventA.end) {
return true;
}
}
return false;
}
window.is_chinese = ( I18n && I18n.locale.indexOf('zh') != -1 );
window.datetime_format = is_chinese ? 'y M d h:m b' : 'd M, y h:m b';
window.date_format = is_chinese ? 'y M d' : 'd M, y';
window.time_format = "h:m b";
window.date_time_str_format = 'y/MM/d H:m';
window.short_day = (is_chinese ? "d (w)" : "w d")
window.short_date = (is_chinese ? "M d (w)" : "w d, M")
window.getDateString = function(date, format,is_chinese) {
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
var week_days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
if(is_chinese){
months = [];
for(var i=0;i<12;i++){
months.push((i+1)+"月");
}
week_days = ["週日","週一","週二","週三","週四","週五","週六"]
}
var getPaddedComp = function(comp) {
return ((parseInt(comp) < 10) ? ('0' + comp) : comp)
},
formattedDate = format,
o = {
"y+": date.getFullYear() + (is_chinese ? "年" : ""), // year
"MM+": getPaddedComp(date.getMonth() + 1), //raw month
"M+": months[date.getMonth()], //month
"d+": (is_chinese ? (date.getDate() + "日") : getPaddedComp(date.getDate())), //day
"w+": week_days[date.getDay()], //weekday
"h+": getPaddedComp((date.getHours() > 12) ? date.getHours() % 12 : date.getHours()), //hour
"H+": getPaddedComp(date.getHours()), //hour
"m+": getPaddedComp(date.getMinutes()), //minute
"s+": getPaddedComp(date.getSeconds()), //second
"S+": getPaddedComp(date.getMilliseconds()), //millisecond,
"b+": (date.getHours() >= 12) ? 'PM' : 'AM'
};
for (var k in o) {
if (new RegExp("(" + k + ")").test(format)) {
formattedDate = formattedDate.replace(RegExp.$1, o[k]);
}
}
return formattedDate;
};
var Calendar = function(dom,property_id,currentView){
c = this;
this.title = $("#current_title");
this.calendar = $(dom);
this.calendar_dom = $(dom);
this.nextBtn = $("#next_month_btn");
this.prevBtn = $("#prev_month_btn");
this.todayBtn = $("#today_btn");
@ -11,7 +115,7 @@ var Calendar = function(dom,property_id){
this.dialog = new EventDialog(c);
this.loading = $('#calendar-loading');
this.agenda_space = $("#calendar_agenda");
this.currentView = "month";
this.currentView = currentView || "dayGridMonth";
this.property_id = property_id;
this.navigation = $("#navigation");
this.rangeSelection = $("#range_selection");
@ -22,43 +126,89 @@ var Calendar = function(dom,property_id){
var d = date.getDate();
var m = date.getMonth();
var y = date.getFullYear();
var dview = (c.currentView == "agenda" ? "month" : c.currentView);
c.calendar.fullCalendar({
var dview = (c.currentView == "agenda" ? "dayGridMonth" : c.currentView);
c.calendar_dom.css("overflow","visible");
c.calendar_dom.fullCalendar({
themeSystem: 'bootstrap',
editable: false,
selectable: false,
events: "/xhr/property_hires/get_bookings?property_id="+c.property_id,
header: false,
default: dview,
height: $("body").height() - 141,
selectable: true,
width: "100%",
events: function(args, success_callback, fail_callback) {
var start = args.start;
var end = args.end;
$.ajax({
url: "/xhr/property_hires/get_bookings?property_id="+c.property_id,
dataType: 'json',
type: 'GET',
data: {
start: Math.round(start.getTime() / 1000),
end: Math.round(end.getTime() / 1000),
_: Date.now()
},
success: function(json) {
// json = json.map(function(obj){
// obj.start = new Date(obj.start).toJSON();
// obj.end = new Date(obj.end).toJSON();
// return obj;
// })
success_callback(json);
}
});
},
// events: 'https://fullcalendar.io/demo-events.json',
headerToolbar: false,
fixedWeekCount: false,
initialView: dview,
loading: function(bool) {
if (bool) c.loading.css("left",($(window).width()/2 - 60) + "px").show();
if (bool) c.loading.show();
else c.loading.hide();
if(this.currentData)
$('#current_title').html(this.currentData.viewTitle);
},
windowResize : function(view){
view.setHeight($("body").height() - 141);
c.calendar.fullCalendar("refetchEvents");
c.calendar_dom.calendar.refetchEvents();
},
viewDisplay: function(view) {
c.title.html(view.title);
},
eventTimeFormat: { hour12: true, hour: '2-digit', minute: '2-digit', omitZeroMinute: true, meridiem: 'narrow' },
eventClick: function(calEvent, e, view) {
if(calEvent.jsEvent && !e){
e = {"originalEvent": calEvent.jsEvent}
}
c.dialog.dismiss();
c.dialog.inflate(calEvent);
c.dialog.show({"x":e.originalEvent.clientX,"y":e.originalEvent.clientY});
}
},
dateClick: function(ev) {
var calendar = this;
var calendar_dom = $(this.el);
if(c.calendar_dom.hasClass("active_picker")){
var date = ev.date,
date_str = getDateString(date,date_time_str_format),
day_element = ev.dayEl,
jsEvent = ev.jsEvent;
var time_str = date_str.split(" ")[1];
var date_str = date_str.split(" ")[0];
calendar_dom.trigger("init_time",[time_str]);
calendar_dom.trigger("select_time",[date_str]);
}
},
views: {
dayGridMonth: {
dayMaxEvents: true
}
}
});
c.nextBtn.click(function(){
c.dialog.dismiss();
c.calendar.fullCalendar('next');
c.calendar_dom.calendar.next();
});
c.prevBtn.click(function(){
c.dialog.dismiss();
c.calendar.fullCalendar('prev');
c.calendar_dom.calendar.prev();
});
c.todayBtn.click(function(){
c.dialog.dismiss();
c.calendar.fullCalendar('today');
c.calendar_dom.calendar.today();
});
c.modeBtns.click(function(){
c.dialog.dismiss();
@ -69,7 +219,7 @@ var Calendar = function(dom,property_id){
if(c.currentView == "agenda")
agendaView.refresh();
else
c.calendar.fullCalendar("refetchEvents");
c.calendar_dom.calendar.refetchEvents();
});
var toggleViews = function(view){
@ -85,7 +235,7 @@ var Calendar = function(dom,property_id){
// $("#sec3").addClass("span4").removeClass("span5");
agendaView.hide();
}
c.calendar.fullCalendar('changeView',view);
c.calendar_dom.calendar.changeView(view);
}else{
// $("#sec1").addClass("span7").removeClass("span3");
$("#sec2").hide();
@ -94,19 +244,25 @@ var Calendar = function(dom,property_id){
}
c.currentView = view;
if(loadeventsonviewchange){
c.calendar.fullCalendar("refetchEvents");
c.calendar_dom.calendar.refetchEvents();
loadeventsonviewchange = false;
}
if(c.calendar_dom.calendar.currentData){
var viewTitle = c.calendar_dom.calendar.currentData.viewTitle;
if(view == "timeGridDay" && $('.fc-col-header-cell-cushion ').text() != "")
viewTitle = $('.fc-col-header-cell-cushion ').text() + ', ' + viewTitle;
$('#current_title').html(viewTitle);
}
c.calendar_dom.calendar.render(); //Rerender to fix layout
};
if(c.currentView == "agenda"){toggleViews("agenda");loadeventsonviewchange = true;}
};
this.renderEvent = function(eventStick){
if(eventStick.recurring === true)
c.calendar.fullCalendar("refetchEvents");
c.calendar_dom.calendar.refetchEvents();
else
c.calendar.fullCalendar("renderEvent",eventStick);
c.calendar_dom.calendar.renderEvent(eventStick);
};
@ -120,31 +276,48 @@ var EventDialog = function(calendar,event){
var event_quick_view = null;
var template = "";
var _this_event = null;
var month_names = ["Jan","Feb","March","April","May","June","July","Aug","Sep","Oct","Nov","Dec"];
this.inflate = function(_event){
if(!_event) throw new UserException("EventStick can't be null!");
_event.allDay = _event.event.allDay;
_event._start = _event.event.start;
_event._end = (_event.event.end ? _event.event.end : _event.event.start);
// var start_date = getDateString(_event._start,date_format);
// var end_date = getDateString(_event._end,date_format);
if(_event._end - _event._start > 86400 * 1000){
_event.allDay = true;
}
_event.title = _event.event.title;
_event.hiring_person_name = _event.event.extendedProps.hiring_person_name;
_event.error_message = _event.event.extendedProps.error_message;
if(!_event.hiring_person_name)
_event.hiring_person_name = "";
_event.note = _event.event.extendedProps.note;
_this_event = _event;
var start_time = "",
end_time = "",
time_string = null;
end_time = "",
time_string = null;
if(_event.allDay) {
start_time = $.fullCalendar.formatDate(_event._start,"MMM dd, yyyy");
start_time = getDateString(_event._start,datetime_format, is_chinese);
if(_event._end)
end_time = $.fullCalendar.formatDate(_event._end,"MMM dd, yyyy");
time_string = (_event._start === _event._end || !_event._end ? "<p class='start-date'><i class='icons-calendar' /> " + start_time + "</p>" : "<i class='icons-calendar' /> " + start_time + " <i class='icons-arrow-right-5' /> " + end_time + "");
end_time = getDateString(_event._end,datetime_format, is_chinese);
time_string = (_event._start === _event._end || !_event._end ? "<p class='start-date'><i class='icons-calendar' /></i>" + start_time + "</p>" : "<i class='icons-calendar' /></i>" + start_time + "<br><i class='icons-arrow-right-5' /></i>" + end_time + "");
} else {
var sh = _event._start.getHours() > 12 ? _event._start.getHours() - 12 : _event._start.getHours(),
eh = _event._end.getHours() > 12 ? _event._end.getHours() - 12 : _event._end.getHours(),
sm = _event._start.getMinutes() < 10 ? '0' + _event._start.getMinutes() : _event._start.getMinutes(),
em = _event._end.getMinutes() < 10 ? '0' + _event._end.getMinutes() : _event._end.getMinutes(),
stime = _event._start.getHours() > 12 ? sh + ':' + sm + " PM" : sh + ':' + sm + " AM",
etime = _event._end.getHours() > 12 ? eh + ':' + em + " PM" : eh + ':' + em + " AM",
same = (_event._start.getDate() == _event._end.getDate() && _event._start.getMonth() == _event._end.getMonth() && _event._start.getFullYear() == _event._end.getFullYear());
start_time = month_names[_event._start.getMonth()] + " " + _event._start.getDate() + ", " + _event._start.getFullYear();
end_time = month_names[_event._end.getMonth()] + " " + _event._end.getDate() + ", " + _event._end.getFullYear();
time_string = (same ? "<p class='date'><i class='icons-calendar' /> " + start_time + "</p><p class='time'><i class='icons-clock' /> " + stime + " <i class='icons-arrow-right-5' /> " + etime : "<p class='start-date'><i class='icons-arrow-right-2' /> " + start_time + "<span class='pull-right'>" + stime + "</span></p><p class='end-date'><i class='icons-arrow-left-2' /> " + end_time + "<span class='pull-right'>" + etime + "</p>");
start_time = getDateString(_event._start,date_format, is_chinese);
end_time = getDateString(_event._end,date_format, is_chinese);
var stime = getDateString(_event._start,time_format, is_chinese),
etime = getDateString(_event._end,time_format, is_chinese),
same = (start_time == end_time);
if( same ){
time_string = "<p class='date'><i class='icons-calendar' /></i> " +
start_time +
"</p><p class='time'><i class='icons-clock' /></i> " + stime +
" <i class='icons-arrow-right-5' /></i> " + etime ;
}else{
time_string = "<i class='icons-calendar' /></i><span class='start-date'>" + start_time + " " + stime +
"</span><br><i class='icons-arrow-right-5' /></i><span class='end-date'>" +
end_time + " " + etime + "</span>"
}
// time_string = (same ? "<p class='date'><i class='icons-calendar' /> " + start_time + "</p><p class='time'><i class='icons-clock' /> " + stime + " <i class='icons-arrow-right-5' /> " + etime : "<p class='start-date'><i class='icons-arrow-right-2' /> " + start_time + "<span class='pull-right'>" + stime + "</span></p><p class='end-date'><i class='icons-arrow-left-2' /> " + end_time + "<span class='pull-right'>" + etime + "</p>");
}
event_quick_view = $('<div class="calendar-modal" style="display:none;"></div>');
template = '<div class="modal-content">' +
@ -154,6 +327,7 @@ var EventDialog = function(calendar,event){
'</div>' +
'<div class="modal-body">' +
'<div class="event_summary">' + time_string + '</br>' + _event.hiring_person_name + '</div>' + _event.note +
(_event.error_message ? ("<br><span style=\"color: #FC4040;\">" + _event.error_message + "</span>") : "")
'</div>' +
'<div class="modal-footer" />' +
'</div>';
@ -161,6 +335,7 @@ var EventDialog = function(calendar,event){
this.show = function(pos){
event_quick_view.css({width: '',height: ''});
if(pos){
var pos = getPosition(pos);
event_quick_view.css({"left":pos.x+"px","top":pos.y+"px"});
@ -169,6 +344,38 @@ var EventDialog = function(calendar,event){
event_quick_view.find(".event-close-btn").one("click",function(){_t.dismiss();});
event_quick_view.find("a.delete").one("click",function(){calendar.deleteEvent(_this_event.delete_url,_this_event._id);return false;});
event_quick_view.find("a.edit").one("click",function(){calendar.editEvent(_this_event.edit_url,_this_event.allDay);return false;});
var window_width = $(window).width(),
window_height = $(window).height();
var offset = event_quick_view.offset();
var dialog_width = event_quick_view.width(),
dialog_height = event_quick_view.height();
var new_offset = Object.assign({},offset);
var need_redisplay = false;
var new_width = null, new_height = null;
var padding = 20;
if(offset.left + dialog_width > window_width){
new_offset.left = window_width - dialog_width - padding;
need_redisplay = true;
}
if(new_offset.left < padding){
new_width = dialog_width - (padding - new_offset.left);
new_offset.left = padding;
need_redisplay = true;
}
if(offset.top + dialog_height > window_height){
new_offset.top = window_height - dialog_height - padding;
need_redisplay = true;
}
if(new_offset.top < padding){
new_height = dialog_height - (padding - new_offset.top);
new_offset.top = padding;
need_redisplay = true;
}
if(need_redisplay){
event_quick_view.offset(new_offset);
event_quick_view.width(new_width);
event_quick_view.height(new_height);
}
}
this.dismiss = function(){
@ -238,6 +445,7 @@ var AgendaView = function(calendar){
'<th>Date</th>' +
'<th>Time</th>' +
'<th>Events</th>' +
'<th>Borrower</th>' +
'</tr>' +
'</thead>' +
'<tbody>' +
@ -250,11 +458,9 @@ var AgendaView = function(calendar){
var head_template = '<div>' +
'<label>From</label>' +
'<select name="start_month" class="form-control input-sm" />' +
'<select name="start_year" class="form-control input-sm" />' +
'<input class="input-large" id="agenda_start" placeholder="YYYY/MM" type="text" value="'+start_year+'/'+('0'+(start_month+1)).substr(-2,2)+'" title="YYYY/MM" autocomplete="off">'+
'<label>To</label>' +
'<select name="end_month" class="form-control input-sm" />' +
'<select name="end_year" class="form-control input-sm" />' +
'<input class="input-large" id="agenda_end" placeholder="YYYY/MM" type="text" value="'+end_year+'/'+('0'+(end_month+1)).substr(-2,2)+'" title="YYYY/MM" autocomplete="off">'+
'<button id="show_events" class="btn btn-sm bt-filter btn-primary">Show Events</button>' +
'</div>';
@ -264,6 +470,7 @@ var AgendaView = function(calendar){
'<td>' +
'<div class="event" />' +
'</td>' +
'<td class="Borrower">'+
'</tr>';
// var month_template = '<div class="span4"><h4></h4><div class="tiny_calendar"><table class="table"><tbody><tr><th class="week_title">Sun</th><th class="week_title">Mon</th><th class="week_title">Tue</th><th class="week_title">Wed</th><th class="week_title">Thu</th><th class="week_title">Fri</th><th class="week_title">Sat</th></tr></tbody></table></div></div>';
@ -282,7 +489,7 @@ var AgendaView = function(calendar){
this.inflate = function(forceInflation){
loading(true);
_calendar.calendar.hide();
_calendar.calendar_dom.hide();
_calendar.navigation.hide();
if(!forceInflation){
@ -299,10 +506,12 @@ var AgendaView = function(calendar){
_calendar.rangeSelection.append(renderHead().html()).show();
_calendar.rangeSelection.find("button#show_events").click(function(){
show_event_clicked = true;
start_month = parseInt($("select[name=start_month]").val());
end_month = parseInt($("select[name=end_month]").val());
start_year = parseInt($("select[name=start_year]").val());
end_year = parseInt($("select[name=end_year]").val());
var starts = $("#agenda_start").val().split("/"),
ends = $("#agenda_end").val().split("/");
start_month = parseInt(starts[1]) - 1;
end_month = parseInt(ends[1]) - 1;
start_year = parseInt(starts[0]);
end_year = parseInt(ends[0]);
av.inflate(true);
})
}
@ -336,7 +545,7 @@ var AgendaView = function(calendar){
_calendar.rangeSelection.hide();
agenda_space.hide();
_calendar.navigation.show();
_calendar.calendar.show();
_calendar.calendar_dom.show();
}
this.show = function(){
@ -347,7 +556,7 @@ var AgendaView = function(calendar){
return x.clone();
}
var eventsManager = function(){
var url = "/xhr/calendars/agenda",
var url = "/xhr/property_hires/get_bookings?property_id="+_calendar.property_id,
sd = new Date(start_year,start_month,1),
ed = new Date(end_year,end_month+1,0),
usd = Math.round(sd/1000),
@ -355,9 +564,39 @@ var AgendaView = function(calendar){
$.ajax({
type : "get",
url : url,
data : {"agenda_start":sd.toLocaleString(),"agenda_end":ed.toLocaleString(),"page_id" : _calendar.page_id,"unix_start":usd,"unix_end":ued},
dataType : "json",
data : {"agenda_start":sd.toLocaleString(),"agenda_end":ed.toLocaleString(),"page_id" : _calendar.page_id,"start":usd,"end":ued},
success : function(data){
$.each(data.events,function(i,e){
$("#agenda_start,#agenda_end").datepicker({
dateFormat: "yy/mm",
onChangeMonthYear: function( year, month, inst ){
$(this).val($.datepicker.formatDate('yy/mm', new Date(year, month-1, 1)));
},
gotoCurrent: true
});
$("#agenda_start,#agenda_end").on("focus",function(){
var input = this;
var inst = $(this).data("datepicker");
var year_month = $(input).val().split("/");
if(year_month.length == 2){
inst.selectedYear = parseInt(year_month[0]);
inst.selectedMonth = parseInt(year_month[1]) - 1;
inst.drawYear = inst.selectedYear;
inst.drawMonth = inst.selectedMonth;
inst.currentYear = inst.selectedYear;
inst.currentMonth = inst.selectedMonth;
}
$.datepicker._updateDatepicker(inst);
})
$("#agenda_start,#agenda_end").focus(function () {
$(".ui-datepicker-calendar").hide();
$("#ui-datepicker-div").position({
my: "center top",
at: "center bottom",
of: $(this)
});
});
$.each(data,function(i,e){
var ed = eventDom(e),
s = new Date(e.start),
e = new Date(e.end),
@ -414,14 +653,16 @@ var AgendaView = function(calendar){
var e_t = $(event_template),
s = new Date(event.start),
e = new Date(event.end),
dateFormat = "";
dateFormat = "",
hiring_person_name = event.hiring_person_name;
if(s.getDate() == e.getDate() && s.getMonth() == s.getMonth() && e.getFullYear() == e.getFullYear())
dateFormat = $.fullCalendar.formatDate(s, "ddd dd");
dateFormat = getDateString(s, short_day,is_chinese);
else
dateFormat = $.fullCalendar.formatDates(s, e, "ddd dd, MMM - {ddd dd, MMM}");
dateFormat = getDateString(s,short_date,is_chinese) + ' - ' + getDateString(e,short_date,is_chinese);
e_t.find("td:first").text(dateFormat);
e_t.find("td.event_time").text((event.allDay ? "All Day" : $.fullCalendar.formatDate(s, "hh:mm")));
e_t.find("td.event_time").text((event.allDay ? "All Day" : (getDateString(s, "h:m b")+"~"+getDateString(e, "h:m b"))));
e_t.find("div.event").text(event.title).css("color",event.color);
e_t.find("td.Borrower").text(hiring_person_name);
return e_t;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,11 @@
#calendar.active_picker .fc-scrollgrid-sync-inner,#calendar.active_picker .fc-timegrid-slot{
cursor: pointer;
}
#calendar.active_picker{
.fc-scrollgrid-sync-inner:hover, .fc-timegrid-slot:hover{
background: var(--fc-highlight-color, rgba(188, 232, 241, 0.3));
}
}
/* orbit calendar */
#orbit_calendar {
transition: all 0.3s ease;
@ -96,7 +104,9 @@
& > * {
margin: 0 5px;
}
& > select {
height: 3em;
}
@media screen and (max-width: 479px) {
margin-right: 0;
@ -253,6 +263,9 @@
.event_list td {
padding: 0;
}
.event_list th {
width: 25%;
}
}
.event_holder {
@ -627,8 +640,9 @@
}
#calendar-loading {
position: absolute;
top: 40%;
position: fixed;
top: 50%;
left: 50%;
z-index: 10;
width: 120px;
height: 120px;
@ -641,6 +655,7 @@
background-position: center 20px;
background-size: 50%;
box-shadow: 0 0 25px 0 rgba(0, 0, 0, 0.2);
transform: translate(-50%, -50%);
&:after {
content: "Loading...";
@ -691,7 +706,7 @@
.calendar-modal {
position: fixed;
z-index: 1050;
z-index: 10000;
width: 300px;
margin: 0;
font-size: 12px;

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,14 @@ class Admin::PropertyHiresController < OrbitAdminController
@locations = PropertyLocation.all.desc(:created_at).collect{|loc| [loc.title, loc.id.to_s]}
@locations << ["Other", "other_location"]
end
def edit_hire
@phire = PHire.find(params[:id])
end
def update_hire
@phire = PHire.find(params[:id])
@phire.update_attributes(phire_params)
redirect_to admin_property_hire_path(:id=>@phire.property)
end
def edit
@property = Property.where(:uid => params[:id].split("-").last).first rescue nil
create_set (true)
@ -178,7 +185,9 @@ class Admin::PropertyHiresController < OrbitAdminController
def location_params
params.require(:property_location).permit!
end
def phire_params
params.require(:p_hire).permit!
end
def property_params
prop = params.require(:property).permit!
prop.delete(:property_location) if prop[:property_location] == "other"

View File

@ -214,7 +214,7 @@ class PropertyHiresController < ApplicationController
end
end
respond_to do |format|
format.html # index.html.erb
format.html { render json: allevents.to_json }# index.html.erb
format.json { render json: allevents.to_json }
end
end

View File

@ -2,32 +2,63 @@ module Admin::PropertyHiresHelper
def check_for_availability(stime, etime, pid, interval=nil, recurring_end_date=nil)
property = Property.find(pid)
return {"success" => false, "msg" => "Values are not ok."} if property.nil? || stime.blank? || etime.blank?
return {"success" => false, "msg" => I18n.t("property_hire.values_are_not_ok",:default=>"Values are not ok.")} if property.nil? || stime.blank? || etime.blank?
timezone = (params[:timezone] rescue nil)
timezone = timezone ? timezone : Time.zone.to_s
if !stime.blank?
stime = DateTime.parse(stime + Time.zone.to_s) rescue nil
stime = DateTime.parse(stime + timezone) rescue nil
else
stime = nil
end
if !etime.blank?
etime = DateTime.parse(etime + Time.zone.to_s) rescue nil
etime = DateTime.parse(etime + timezone) rescue nil
else
etime = nil
end
if !recurring_end_date.blank?
recurring_end_date = DateTime.parse(recurring_end_date + Time.zone.to_s) rescue nil
recurring_end_date = DateTime.parse(recurring_end_date + timezone) rescue nil
begin
interval_time = 1.send(interval)
tmp_date = recurring_end_date - interval_time
if tmp_date < etime
I18n.with_locale(params[:locale]) do
interval_str= I18n.t("property_hire.1_#{interval}","1 #{interval}")
default_msg = "Recurring end date must exceed hire end time than #{interval_str}!"
msg = I18n.t("property_hire.recurring_end_date_must_exceed_time",{:time=>interval_str,:default=>default_msg})
return {"success" => false, "msg" => msg}
end
end
rescue
end
else
recurring_end_date = nil
end
data = {}
return {"success" => false, "msg" => "Starting time cannot be greater than ending time."} if stime > etime
if property.is_available_for_hire?(stime, etime, interval, recurring_end_date)
if property.not_yet_hired?(stime, etime, interval, recurring_end_date)
if stime > etime
I18n.with_locale(params[:locale]) do
return {"success" => false, "msg" => I18n.t("property_hire.starting_time_cannot_be_greater_than_ending_time")}
end
end
available_flag = property.is_available_for_hire?(stime, etime, interval, recurring_end_date)
if available_flag
if property.not_yet_hired?(stime, etime, interval, recurring_end_date,params[:phire_id])
data = {"success" => true}
else
data = {"success" => false, "msg" => "Property is already hired during this time."}
I18n.with_locale(params[:locale]) do
data = {"success" => false, "msg" => I18n.t("property_hire.property_is_already_hired_during_this_time")}
end
end
else
data = {"success" => false, "msg" => "Property is unavailable during this time."}
I18n.with_locale(params[:locale]) do
msg = I18n.t("property_hire.property_is_unavailable_during_this_time")
if available_flag.nil?
can_hire_date = stime - (property.can_hire_before_months).month
msg += ("<br>" + I18n.t("property_hire.please_hire_after_date",{:date=>"{#{can_hire_date.utc.to_json.gsub('"','')}}"}))
else
msg += ("<br>" + property.render_unavailable_message)
end
data = {"success" => false, "msg" => msg}
end
end
return data
end

View File

@ -34,10 +34,11 @@ class PHire
:hiring_person_id => self.hiring_person_id,
:hiring_person_name => self.hiring_person_name,
:note => self.note_for_hire || "",
:start => self.start_time.rfc822,
:end => self.end_time.rfc822,
:allDay => false,
:color => "#FC4040"
:start => self.start_time.to_json.gsub('"',''),
:end => self.end_time.to_json.gsub('"',''),
:allDay => (self.end_time - self.start_time >= 1),
:color => (self.passed ? "#3788d8" : "#FC4040"),
:error_message => (self.passed ? nil : "Not approved")
}
end
@ -87,7 +88,7 @@ class PHire
next if !((startt..endt) & (@start_date..@end_date)).blank?
end
if @start_date < re.recurring_end_date
@recurring << {:id => re.id.to_s, :hiring_person_name => re.hirer_name ,:title=>re.reason_for_hire, :note=>re.reason_for_hire, :start=>@start_date, :end => @end_date, :allDay => false, :recurring => re.recurring, :color => "#FC4040"}
@recurring << {:id => re.id.to_s, :hiring_person_name => re.hirer_name ,:title=>re.reason_for_hire, :note=>re.reason_for_hire, :start=>@start_date, :end => @end_date, :allDay => (re.end_time - re.start_time >= 1), :recurring => re.recurring, :color => (re.passed ? "#3788d8" : "#FC4040"), :error_message => (re.passed ? nil : "Not approved")}
end
end
when "month"

View File

@ -21,7 +21,7 @@ class Property
mount_uploader :image, ImageUploader
# unavailibility fields
field :can_hire_before_months, type: Integer, default: 0
field :set_unavailibility, type: Boolean, default: false
field :start_time
field :end_time
@ -54,7 +54,79 @@ class Property
"Friday",
"Saturday"
]
def render_unavailable_message
message = ""
property = self
weekdays_options = self.class::WEEKDAYS
weekdays_options = weekdays_options.map do |weekday|
trans = I18n.t("property_hire.#{weekday}", :default=>'')
if trans != ""
trans
else
weekday
end
end
if property.set_unavailibility
if property.weekdays.length > 0
translation_missing = (I18n.t('property_hire.unavailable_hint1', {:default => ''}) == "")
str1 = (!property.start_date.nil? ? (I18n.t("property_hire.from",:default=>" from ") + property.start_date.strftime("%Y-%m-%d")) : "")
str2 = (!property.end_date.nil? ? (I18n.t("property_hire.to",:default=>" to ") + property.end_date.strftime("%Y-%m-%d")) : "")
if str1 == "" && str2 != ""
str1 = I18n.t("property_hire.from_now_on")
end
if str1 != "" || str2 != ""
str2 += I18n.t("property_hire.of",:default=>"")
else
str2 += I18n.t("property_hire.at",:default=>"")
end
week_str = ""
if translation_missing
week_str += "every "
else
week_str += I18n.t("property_hire.every")
end
dot_trans = I18n.t("property_hire.dot",:default=>", ")
property.weekdays.each_with_index do |d,i|
if i < (property.weekdays.count - 1)
week_str += (weekdays_options[d.to_i] + dot_trans)
else
week_str += weekdays_options[d.to_i]
end
end
str3 = ""
if property.end_time.blank?
if property.start_time.blank?
str3 = "." if translation_missing
else
str3 = I18n.t("property_hire.from_time",{:time=>property.start_time,:default=>" from #{property.start_time}."})
end
else
if I18n.locale.to_s != "zh_tw"
str3 = " between #{property.start_time} &amp; #{property.end_time}."
else
str3 = I18n.t("property_hire.time1_to_time2",{:time1=>property.start_time,:time2=>property.end_time})
end
end
if str3 != ""
str3 = I18n.t("property_hire.of",:default=>"") + str3
end
hint1 = I18n.t('property_hire.unavailable_hint1', {:str1=>str1,:str2=>str2,:week_str=>week_str,:str3=>str3, :default => ''})
if hint1 == ""
message += "This property is unavaliable#{str1}#{str2} #{week_str}#{str3}"
else
message += hint1
end
end
if property.can_hire_before_months != 0
if message != ""
message += "<br>"
end
default_msg = "This property is unavaliable to hire before #{property.can_hire_before_months} month ago."
message += I18n.t("property_hire.unavailable_hint2",{:month=>property.can_hire_before_months,:default=>default_msg})
end
end
return message.html_safe
end
def get_location_name
return self.property_location.nil? ? self.other_location : self.property_location.title
end
@ -66,11 +138,14 @@ class Property
def is_available_for_hire?(stime, etime, interval = nil, recurring_end_date = nil)
available = false
return true if self.set_unavailibility == false
return true if self.weekdays.empty?
available = true if !self.start_date.nil? && (self.start_date > stime && self.start_date > etime)
available = true if !self.end_date.nil? && self.end_date < stime
startt = self.start_date.nil? ? self.created_at : self.start_date
endt = self.end_date.nil? && !startt.nil? ? (startt + 5.years) : self.end_date
return true if self.weekdays.empty? && self.can_hire_before_months == 0
if self.can_hire_before_months != 0
return nil if (((stime - Time.now.to_datetime) * 1.day) > (self.can_hire_before_months).month)
end
startt = self.start_date.nil? ? stime : self.start_date
endt = self.end_date.nil? ? etime : self.end_date
available = true if (startt > stime && endt > etime)
available = true if (endt < stime)
weekdays = self.weekdays.collect{|w| w.to_i}
if !startt.nil?
if !available
@ -83,8 +158,8 @@ class Property
time_weekdays.uniq!
weekdays = weekdays & time_weekdays
return true if weekdays.blank?
startt = DateTime.parse(stime.strftime("%Y-%m-%d " + self.start_time + Time.zone.to_s))
endt = DateTime.parse(etime.strftime("%Y-%m-%d " + self.end_time + Time.zone.to_s))
startt = DateTime.parse(stime.strftime("%Y-%m-%d " + (self.start_time.blank? ? "00:00" : self.start_time) + Time.zone.to_s))
endt = DateTime.parse(etime.strftime("%Y-%m-%d " + (self.end_time.blank? ? "23:59" : self.end_time) + Time.zone.to_s))
common_dates = (startt..endt) & (stime..etime)
available = common_dates.nil?
end
@ -119,17 +194,21 @@ class Property
end
end
def not_yet_hired?(stime, etime, interval, recurring_end_date)
def not_yet_hired?(stime, etime, interval, recurring_end_date,phire_id=nil)
phires = self.p_hires
bookings_count = phires.where(:start_time.lte => stime,:end_time.gte => stime,:recurring => false).count
+ phires.where(:start_time.gte => stime,:end_time.lte => etime,:recurring => false).count
+phires.where(:start_time.lte => etime,:end_time.gte => etime,:recurring => false).count
stime = stime.utc
etime = etime.utc
bookings_count = phires.where(:start_time.lte => stime,:end_time.gte => stime,:recurring => false,:id.ne=>phire_id).count
+ phires.where(:start_time.gte => stime,:end_time.lte => etime,:recurring => false,:id.ne=>phire_id).count
+phires.where(:start_time.lte => etime,:end_time.gte => etime,:recurring => false,:id.ne=>phire_id).count
available = true
if bookings_count != 0
available = false
end
puts ["phire_id",phire_id]
if available
bookings = phires.where(:recurring_end_date.gte => stime, :recurring => true) + phires.where(:recurring => false,:recurring_end_date => nil) + phires.where(:recurring => false,:recurring_end_date.gte => stime)
recurring_bookings = phires.where(:recurring_end_date.gte => stime, :recurring => true,:id.ne=>phire_id)
bookings = phires.where(:recurring => false,:recurring_end_date => nil)
case interval
when 'week'
d_step = 1.week
@ -138,52 +217,49 @@ class Property
else
d_step = 0
end
bookings.each do |booking|
stime_tp = stime
etime_tp = etime
if true#d_step != 0
bookings += recurring_bookings
end
bookings.each_with_index do |booking,i|
stime_tp = stime.clone
etime_tp = etime.clone
b_interval = booking.recurring_interval
if (b_interval == 'month' || b_interval == 'week') && booking.recurring_end_date.nil?
b_recurring_end_date = booking.recurring_end_date ? booking.recurring_end_date.utc : nil
b_sdata = booking.start_time.utc
b_edata = booking.end_time.utc
b_delta = b_edata - b_sdata
if (b_interval == 'month' || b_interval == 'week') && (booking.recurring_end_date.nil? || !booking.recurring)
b_interval = nil
end
while true
if b_interval == 'month'
diff_month = booking.end_time.month - booking.start_time.month
diff_month = diff_month + 12 if diff_month < 0
e_month = stime_tp.month + diff_month
e_year = stime_tp.year + (booking.end_time.year-booking.start_time.year)
e_month = e_month - 12 if e_month >12
b_sdata = Time.local(stime_tp.year,stime_tp.month,booking.start_time.day,booking.start_time.hour,booking.start_time.minute).to_datetime
b_edata = Time.local(e_year,e_month,booking.end_time.day,booking.end_time.hour,booking.end_time.minute).to_datetime
elsif b_interval == 'week'
diff_day = booking.end_time - booking.start_time
if (booking.end_time.wday <= booking.start_time.wday && diff_day>1) || diff_day > 7
over_one_week = true
b_datas = []
if b_interval.present?
b_interval = (1).send(b_interval)
b_datas = (b_edata..b_recurring_end_date).step(b_interval).to_a
b_datas = b_datas.map{|b_end| [b_end-b_delta,b_end]}
start_index = b_datas.count
b_datas.each_with_index do |(b_start,b_end),i|
if b_start >= stime_tp || b_end <= etime_tp
start_index = i
break
end
b_sdata_tp = stime_tp - stime_tp.wday.day + booking.start_time.wday.day
b_sdata = Time.local(b_sdata_tp.year,b_sdata_tp.month,b_sdata_tp.day,booking.start_time.hour,booking.start_time.minute).to_datetime
if over_one_week && etime_tp.wday <= booking.start_time.wday
b_sdata = b_sdata - 7.day
end
now_diff_day = etime_tp - stime_tp
if (etime_tp.wday <= stime_tp.wday && now_diff_day>1) || now_diff_day > 7
b_sdata = b_sdata + 7.day
end
b_edata_tp = b_sdata + (booking.end_time - booking.start_time).to_i.day
b_edata = Time.local(b_edata_tp.year,b_edata_tp.month,b_edata_tp.day,booking.end_time.hour,booking.end_time.minute).to_datetime
else
b_sdata = booking.start_time
b_edata = booking.end_time
end
b_datas = b_datas[start_index..-1].to_a.map{|b_start,b_end| (b_start..b_end)}
else
b_datas = [b_sdata..b_edata]
end
while b_datas.present?
sdata = stime_tp
edata = etime_tp
if etime_tp > booking.start_time
if (sdata <= b_sdata && edata >= b_sdata) || (sdata >= b_sdata && edata <= b_edata) || (sdata <= b_edata && edata >= b_edata)
available = false
end
end
available = b_datas.find{|b_range| b_range & (stime_tp..etime_tp)}.nil?
stime_tp = stime_tp + d_step
etime_tp = etime_tp + d_step
break if recurring_end_date.blank? || d_step==0 || recurring_end_date < stime_tp
break if !available || recurring_end_date.blank? || d_step==0
start_index = b_datas.find_index{|b_range| b_range.first >= stime_tp}
if start_index
b_datas = b_datas[start_index..-1]
else
b_datas = []
end
end
break if available == false
end

View File

@ -245,42 +245,20 @@
<div id="set_unavailibility_div" style="display: none;">
<% end %>
<div class="control-group">
<%= f.label :start_time, t("property_hire.start_time"), :class => "control-label muted" %>
<%= f.label :can_hire_before_months, t("property_hire.how_many_months_ago_can_be_hired"), :class => "control-label muted" %>
<div class="controls">
<%= f.datetime_picker :start_time, :picker_type => "time", :no_label => true, :new_record => @property.new_record?, :value => (Time.parse(@property.start_time) rescue "") %>
<%= f.select :can_hire_before_months, options_for_select([[t("property_hire.no_limit"),0]] + (1..12).to_a.map{|month| [t("property_hire.month", month: month), month]},f.object.can_hire_before_months) %>
</div>
</div>
<div class="control-group">
<%= f.label :end_time, t("property_hire.end_time"), :class => "control-label muted" %>
<div class="controls">
<%= f.datetime_picker :end_time, :picker_type => "time", :no_label => true, :new_record => @property.new_record?, :value => (Time.parse(@property.end_time) rescue "") %>
</div>
</div>
<div class="control-group">
<label class="control-label muted">Weekdays</label>
<label class="control-label muted"><%=t("property_hire.weekdays").html_safe%></label>
<div class="controls">
<% weekdays = @property.weekdays rescue [] %>
<label for="sunday">
<input id="sunday" type="checkbox" name="property[weekdays][]" value="0" <%= weekdays.include?("0") ? "checked=checked" : "" %> /> Sunday
</label>
<label for="monday">
<input id="monday" type="checkbox" name="property[weekdays][]" value="1" <%= weekdays.include?("1") ? "checked=checked" : "" %> /> Monday
</label>
<label for="tuesday">
<input id="tuesday" type="checkbox" name="property[weekdays][]" value="2" <%= weekdays.include?("2") ? "checked=checked" : "" %> /> Tuesday
</label>
<label for="wednesday">
<input id="wednesday" type="checkbox" name="property[weekdays][]" value="3" <%= weekdays.include?("3") ? "checked=checked" : "" %> /> Wednesday
</label>
<label for="thursday">
<input id="thursday" type="checkbox" name="property[weekdays][]" value="4" <%= weekdays.include?("4") ? "checked=checked" : "" %> /> Thursday
</label>
<label for="friday">
<input id="friday" type="checkbox" name="property[weekdays][]" value="5" <%= weekdays.include?("5") ? "checked=checked" : "" %> /> Friday
</label>
<label for="saturday">
<input id="saturday" type="checkbox" name="property[weekdays][]" value="6" <%= weekdays.include?("6") ? "checked=checked" : "" %> /> Saturday
</label>
<% Property::WEEKDAYS.each_with_index do |weekday,i| %>
<label for="<%=weekday%>">
<input id="<%=weekday%>" type="checkbox" name="property[weekdays][]" value="<%=i%>" <%= weekdays.include?(i.to_s) ? "checked=checked" : "" %> /> <% trans = t("property_hire.#{weekday}") %><%= (trans.class == ActiveSupport::SafeBuffer ? weekday : trans) %>
</label>
<% end %>
</div>
</div>
<div class="control-group">
@ -295,6 +273,18 @@
<%= f.datetime_picker :end_date, :picker_type => "date", :no_label => true, :new_record => @property.new_record?, :data=>{"picker-type" => "range", "range" => "end"}, :format => "yyyy/MM/dd" %>
</div>
</div>
<div class="control-group">
<%= f.label :start_time, t("property_hire.limit_start_time"), :class => "control-label muted" %>
<div class="controls">
<%= f.datetime_picker :start_time, :picker_type => "time", :no_label => true, :new_record => @property.new_record?, :value => (Time.parse(@property.start_time) rescue "") %>
</div>
</div>
<div class="control-group">
<%= f.label :end_time, t("property_hire.limit_end_time"), :class => "control-label muted" %>
<div class="controls">
<%= f.datetime_picker :end_time, :picker_type => "time", :no_label => true, :new_record => @property.new_record?, :value => (Time.parse(@property.end_time) rescue "") %>
</div>
</div>
<% @site_in_use_locales.each do |locale| %>
<%= f.fields_for :description_translations do |f| %>
<div class="control-group">

View File

@ -0,0 +1,496 @@
<style type="text/css">
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.row{
margin-left: 0;
}
.form-group {
margin-bottom: 0.9375em;
clear: both;
}
@media (min-width:768px){
.modal-content {
-webkit-box-shadow: 0 0.3125em 0.9375em rgb(0 0 0 / 50%);
box-shadow: 0 0.3125em 0.9375em rgb(0 0 0 / 50%);
}
.col-sm-offset-2 {
margin-left: 16.66666667%;
}
.form-horizontal .control-label {
padding-top: 0.4375em;
margin-bottom: 0;
width: auto;
text-align: right;
}
[class*="col-sm"],[class*="col-md"]{
float: left;
position: relative;
min-height: 0.0625em;
padding-right: 0.9375em;
padding-left: 0.9375em;
}
.form-horizontal .col-sm-2 {
width: 16.66666667%;
}
.form-horizontal .col-sm-5 {
width: 41.66666667%;
}
.form-horizontal .col-sm-10 {
width: 83.33333333%;
}
.form-horizontal .col-md-4 > * {
padding: 0 0.5em;
}
.form-horizontal .col-md-8 > * {
padding: 0 1em;
}
.form-horizontal .col-md-4{
width: 33.3%;
}
.form-horizontal .col-md-8{
width: 66.6%;
}
}
.modal-content {
position: relative;
background-color: #fff;
-webkit-background-clip: padding-box;
background-clip: padding-box;
border: 0.0625em solid #999;
border: 0.0625em solid rgba(0,0,0,.2);
border-radius: 0.375em;
outline: 0;
-webkit-box-shadow: 0 0.1875em 0.5625em rgb(0 0 0 / 50%);
box-shadow: 0 0.1875em 0.5625em rgb(0 0 0 / 50%);
}
.modal-header {
padding: 0.9375em;
border-bottom: 0.0625em solid #e5e5e5;
}
.modal-body {
position: relative;
padding: 0.9375em;
max-height: 400px;
overflow: initial;
}
</style>
<%= content_for :page_specific_css do %>
<% ["basic/bootstrap-datetimepicker.css","property_hire_fullcalendar.css","property_hire_calendar"].each do |css| %>
<%= stylesheet_link_tag css %>
<% end %>
<% end %>
<script src="/assets/javascripts/validator.js"></script>
<script src="https://polyfill.io/v3/polyfill.min.js?features=Intl.DateTimeFormat,Intl.DateTimeFormat.~locale.en,Intl.NumberFormat.~locale.en"></script>
<script type="text/javascript" src="/assets/property_hire_fullcalendar.min.js"></script>
<script type="text/javascript" src="/assets/property_hire_calendar_frontend.js"></script>
<%
hire = @phire
property = @phire.property
%>
<h3><%= property.title %></h3>
<% if session["hire-save-msg"].present? %>
<div id="property-unavaialable-alert" class="alert alert-danger" role="alert"><b>Sorry! </b><span> <%= session["hire-save-msg"] %></span></div>
<% session.delete("hire-save-msg") %>
<% end %>
<div id="orbit_calendar">
<div id="sec1">
<div class="btn-toolbar" id="navigation">
<div id="calendar-nav">
<div class="btn-group">
<button class="btn btn-default btn-sm" id="prev_month_btn">
<i class="icon-chevron-left"></i>
</button>
<button class="btn btn-default btn-sm" id="next_month_btn">
<i class="icon-chevron-right"></i>
</button>
<button class="btn btn-default btn-sm" id="today_btn">Today</button>
</div>
</div>
</div>
<div class="form-inline" id="range_selection"></div>
</div>
<div id='sec3' class="btn-toolbar">
<div class="btn-group calendar_mode">
<button class="btn btn-default mode_switch btn-sm" data-mode="timeGridDay" >day</button>
<button class="btn btn-default mode_switch btn-sm" data-mode="timeGridWeek" >week</button>
<button class="btn btn-default active mode_switch btn-sm" data-mode="dayGridMonth" >month</button>
</div>
<button id="refresh_btn" class="btn btn-default btn-sm">
<i class="icons-cycle"></i>
</button>
</div>
<div id="view_holder">
<h3 id="current_title" class="current_day_title"></h3>
<div id="calendar"></div>
<div id="calendar_agenda"></div>
</div>
</div>
<div id="event_quick_view" class="modal" style="width: 300px; display:none; margin:0 0 0 0;"></div>
<div id="calendar-loading"></div>
<script type="text/javascript">
var property_id = "<%= property.id.to_s %>";
var calendar = new Calendar("#calendar",property_id);
function change_pick(target){
if( $(target).attr("id") == "pick_recurring_end_date"){
if($('#p_hire_recurring_interval').val() == ""){
alert("<%=t("property_hire.please_select_recurring_interval")%>");
$('#p_hire_recurring_interval').focus();
return;
}
$("#calendar").data("recurring_interval", $('#p_hire_recurring_interval').val());
}
$("#hidden_timepicker").addClass("hide");
$("#calendar").data("target","#"+$(target).attr("id"));
$("#calendar").data("title", $(target).parents(".col-sm-10").prev("label").text().replace("*",""));
$("#calendar").addClass("active_picker");
document.getElementById("orbit_calendar").scrollIntoView();
}
$("#calendar").on("select_time",function(ev,date_str){
$("#hidden_date").text(date_str);
$("#hidden_title").text($("#calendar").data("title"));
$('#hidden_timepicker .time_picker').addClass("pull-left");
$("#hidden_timepicker").removeClass("hide");
var target = $('#timepicker');
var window_width = $(window).width();
var window_height = $(window).height();
var target_offset = target.offset();
scrollTo(target_offset.left - window_width / 2, target_offset.top - window_height / 2);
$('#timepicker').trigger('focus');
})
$("#calendar").on("init_time",function(ev,time_str){
$('#timepicker').val(time_str);
})
function set_datetimepicker(){
var date_time = $("#hidden_date").text() + " " + $('#timepicker').val();
var start_date, end_date, interval = null, recurring_end_date = null;
var target = $("#calendar").data("target");
if(target == "#pick_start_date"){
start_date = date_time;
end_date = $("#p_hire_end_time").val();
}else if(target == "#pick_end_date"){
end_date = date_time;
start_date = $("#p_hire_start_time").val();
}else if(target == "#pick_recurring_end_date"){
start_date = $("#p_hire_start_time").val();
end_date = $("#p_hire_end_time").val();
interval = $("#p_hire_recurring_interval").val();
recurring_end_date = date_time;
}
end_date = (end_date == "" ? null : end_date);
start_date = (start_date == "" ? null : start_date);
if(start_date != null && end_date != null && $("#p_hire_start_time").val() != "" && $("#p_hire_end_time").val() != ""){
if(start_date > end_date){
if(target == "#pick_start_date"){
end_date = start_date.split(" ")[0] + " " + end_date.split(" ")[1];
$("#p_hire_end_time").val(end_date);
}else{
start_date = end_date.split(" ")[0] + " " + start_date.split(" ")[1];
$("#p_hire_start_time").val(start_date);
}
}
}
console.log(start_date)
var check_only = (start_date == null || end_date == null);
if(check_available(start_date,end_date,interval,recurring_end_date,check_only)){
var target = $($("#calendar").data("target"));
target.prev().find("input").val(date_time);
$("#hidden_timepicker").addClass("hide");
$("#calendar").removeClass("active_picker");
var window_width = $(window).width();
var window_height = $(window).height();
var target_offset = target.offset();
scrollTo(target_offset.left - window_width / 2, target_offset.top - window_height / 2);
}else{
if(window.check_message !== ""){
alert(window.check_message.replace(/<br>/g,"\n"));
}else{
alert('<%=t("property_hire.unavailability")%>');
}
}
}
function goto_calendar(){
$("#hidden_timepicker").addClass("hide");
var target = $("#calendar");
var window_width = $(window).width();
var window_height = $(window).height();
var target_offset = target.offset();
scrollTo(target_offset.left - window_width / 2, target_offset.top - window_height / 2);
}
</script>
<div id="hidden_timepicker" class="hide">
<span id="hidden_title" class="pull-left" style="margin-right: 1em;font-weight: bold;"></span>
<div style="display: grid;">
<span id="hidden_date" class="pull-left" style="margin-right: 1em;"></span>
<%= fields_for :timepicker do |f|%>
<%= f.time_picker :timepicker, :no_label => true, :new_record => hire.new_record?,:format=>"HH:mm", :class => "pull-left", :data=>{"picker-type" => "range", "range" => "end", "fv-validation" => "required;", "fv-messages" => "Cannot be empty;"} %>
<% end %>
<div class="pull-left btn-group" style="margin-top: 0.5em;">
<button id="confirm_date" class="btn btn-primary btn-sm" style="margin-right: 0.5em;" onclick="set_datetimepicker()"><%=t("property_hire.confirm")%></button>
<button id="cancel_date" class="btn btn-primary btn-sm" onclick="goto_calendar()"><%=t("property_hire.cancel")%></button>
</div>
</div>
<div style="clear: both;"></div>
<hr>
</div>
<%= form_for hire, :url => update_hire_admin_property_hire_path(hire), html: { class: "form-horizontal" } do |f| %>
<div class="form-group">
<%= f.label :start_time, "*"+t("property_hire.start_time"), :class => "col-sm-2 control-label" %>
<div class="col-sm-10">
<%= f.datetime_picker :start_time, :no_label => true, :new_record => hire.new_record?,:class => "pull-left", :data=>{"picker-type" => "range", "range" => "start", "fv-validation" => "required;", "fv-messages" => "Cannot be empty;"} %>
<button type="button" id="pick_start_date" onclick="change_pick(this)" class="btn btn-primary btn-sm pull-left" style="margin-left: 1em;"><%=t("property_hire.pick_from_calendar")%></button>
</div>
</div>
<div class="form-group">
<%= f.label :end_time, "*"+t("property_hire.end_time"), :class => "col-sm-2 control-label" %>
<div class="col-sm-10">
<%= f.datetime_picker :end_time, :no_label => true, :new_record => hire.new_record?,:class => "pull-left", :data=>{"picker-type" => "range", "range" => "end", "fv-validation" => "required;", "fv-messages" => "Cannot be empty;"} %>
<button type="button" id="pick_end_date" onclick="change_pick(this)" class="btn btn-primary btn-sm pull-left" style="margin-left: 1em;"><%=t("property_hire.pick_from_calendar")%></button>
</div>
</div>
<!-- ############# recurring ############# -->
<div class="form-group">
<%= f.label :recurring, t("property_hire.recurring"), :class => "col-sm-2 control-label" %>
<div class="col-sm-1">
<%= f.check_box :recurring %>
</div>
</div>
<div id="recurring-block" <%= hire.recurring ? "" : "style=display:none;" %>>
<div class="form-group">
<%= f.label :recurring_interval, t("property_hire.recurring_interval"), :class => "col-sm-2 control-label" %>
<div class="col-sm-1">
<%= f.select :recurring_interval, PHire::INTERVALS.collect{|int| [t("property_hire.recurring_interval_types.#{int}"), int] }, {:prompt => t('property_hire.select_interval')}, {:data => {"fv-validation" => "requiredifrecurring;" , "fv-messages" => "Cannot be empty;"}} %>
</div>
</div>
<div class="form-group">
<%= f.label :recurring_end_date, "*"+t("property_hire.recurring_end_date"), :class => "col-sm-2 control-label" %>
<div class="col-sm-10">
<%= f.datetime_picker :recurring_end_date, :no_label => true, :new_record => hire.new_record?, :class=>"pull-left", :data=>{"fv-validation" => "requiredifrecurring;", "fv-messages" => "Cannot be empty;"} %>
<button type="button" id="pick_recurring_end_date" onclick="change_pick(this)" class="btn btn-primary btn-sm pull-left" style="margin-left: 1em;"><%=t("property_hire.pick_from_calendar")%></button>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-5">
<div id="property-avaialable-alert" style="margin-bottom: 5px; padding: 10px; display: none;" class="alert alert-success" role="alert"><b>Hooray! </b>This property is available.</div>
<div id="property-unavaialable-alert" style="margin-bottom: 5px; padding: 10px; display: none;" class="alert alert-danger" role="alert"><b>Sorry! </b><span> This property is available.</span></div>
<div id="values-alert" style="margin-bottom: 5px; padding: 10px; display: none;" class="alert alert-warning" role="alert">
<% hint1 = t("property_hire.please_select_recurring_interval_and_recurring_end_time",:default=>"") %>
<% if hint1 == ""%>
<b>Please! </b><span> Select an interval time and recurring event end date.</span>
<% else %>
<span><b><%=hint1%></b></span>
<% end %>
</div>
</div>
<% if property.set_unavailibility %>
<div class="col-sm-offset-2 col-sm-5">
<b><%= t("property_hire.Unavailibility_Schedule") %></b>
<div>
<%= property.render_unavailable_message%>
</div>
</div>
<% end %>
</div>
<div class="form-group">
<label for="" class="col-sm-2 control-label"></label>
<div class="col-sm-10">
<a href="/xhr/property_hires/check_availability" id="check-avail-btn" class="btn btn-primary"><%= t('property_hire.check_availibility') %></a>
<img style="display: none;" width="40" src="/assets/spin.gif" id="spinner" />
</div>
</div>
<div class="form-group">
<%= f.label :hiring_person_email, "*"+t("property_hire.hiring_person_email"), :class => "col-sm-2 control-label" %>
<div class="col-sm-5">
<%= f.text_field :hiring_person_email, :class => "form-control", :value => current_user.member_profile.email, :data => {"fv-validation" => "required;", "fv-messages" => "Cannot be empty;"} %>
</div>
</div>
<div class="form-group">
<%= f.label :hiring_person_number, "*"+t("property_hire.hiring_person_number"), :class => "col-sm-2 control-label" %>
<div class="col-sm-5">
<%= f.text_field :hiring_person_number, :class => "form-control", :data => {"fv-validation" => "required;", "fv-messages" => "Cannot be empty;"} %>
</div>
</div>
<div class="form-group">
<%= f.label :hiring_person_name, "*"+t("property_hire.hiring_person_name"), :class => "col-sm-2 control-label" %>
<div class="col-sm-5">
<%= f.text_field :hiring_person_name, :class => "form-control", :value => (current_user.name rescue ""), :data => {"fv-validation" => "required;", "fv-messages" => "Cannot be empty;"} %>
<%= f.hidden_field :hiring_person_id, :value => (current_user.member_profile.id.to_s rescue "") %>
</div>
</div>
<div class="form-group">
<%= f.label :reason_for_hire, "*"+t("property_hire.reason_for_hire"), :class => "col-sm-2 control-label" %>
<div class="col-sm-5">
<%= f.text_field :reason_for_hire, :class => "form-control", :data => {"fv-validation" => "required;", "fv-messages" => "Cannot be empty;"} %>
</div>
</div>
<% if(property.enable_notes_selector rescue false) %>
<% property.notes_selector.each do |index,sub_hash| %>
<% name = sub_hash["name"][I18n.locale.to_s] %>
<% name = sub_hash["name"].values.select{|v| v.present?}.first.to_s if name.blank? %>
<% values = sub_hash["value"][I18n.locale.to_s] %>
<% values = sub_hash["value"].values.select{|v| v.present?}.first.to_s if values.blank? %>
<% type = sub_hash["type"] %>
<div class="form-group">
<%= f.label "notes_selector[#{index}]", name, :class => "col-sm-2 control-label" %>
<div class="col-sm-5">
<% values.each_with_index do |v,i| %>
<label class="checkbox-inline">
<input type="<%=type%>" name="p_hire[notes_selector][<%=index.to_s%>][]" value="<%=i%>" <%= (type=="radio" && i == 0) ? "checked=\"checked\"" : "" %>>
<%=v%>
</label>
<% end %>
<% if type == "checkbox" && (values.count > 1) %>
<small class="help-block"><%= t("property_hire.checkbox_hint") %></small>
<% end %>
</div>
</div>
<% end %>
<% else %>
<div class="form-group">
<%= f.label :note_for_hire, t("property_hire.note_for_hire"), :class => "col-sm-2 control-label" %>
<div class="col-sm-5">
<%= f.text_area :note_for_hire, :class => "form-control" %>
</div>
</div>
<% end %>
<% fields_name = ["organization" ,"person_in_charge" , "tel_of_person_in_charge" , "department" , "contact_person" , "tel_of_contact_person" , "mobile_phone_of_contact_person" , "contact_person_Email" , "contact_person_department"] %>
<% fields_name.each do |field_name| %>
<% if(property[field_name]["enable"] == "1" rescue false) %>
<% required = (property[field_name]["required"] == "true" rescue false) %>
<div class="form-group">
<%= f.label field_name, (required ? "*" : "") + t("property_hire.#{field_name}"), :class => "col-sm-2 control-label" %>
<div class="col-sm-5">
<% if required %>
<%= f.text_field field_name, :class => "form-control", :data => {"fv-validation" => "required;", "fv-messages" => "Cannot be empty;"} %>
<% else %>
<%= f.text_field field_name, :class => "form-control" %>
<% end %>
</div>
</div>
<% end %>
<% end %>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<%= f.submit t("property_hire.save"), :class => "btn btn-primary" %>
<%= f.hidden_field :property_id, :value => property.id %>
<input type="hidden" id="dates_validated" name="dates_validated" value="0" data-fv-validation="checkForDates;" data-fv-messages="Please make sure first if dates are available.;">
</div>
</div>
<% end %>
<div style="height: 50px;"></div>
<script type="text/javascript">
var property_id = "<%= property.id.to_s %>";
var check_available = function(stime,etime,interval,recurring_end_date,check_only,property_id){
var el = $("#check-avail-btn"),
url = $("#check-avail-btn").attr("href"),
spinner = $("#spinner");
stime = stime || etime;
etime = etime || stime;
property_id = property_id || window.property_id;
var timezone = new Date().toString().match(/([-\+][0-9]+)\s/)[1];
data = {
"stime": stime,
"etime": etime,
"property_id": property_id,
"locale": "<%=I18n.locale%>",
"timezone": timezone,
"phire_id": "<%=hire.id%>"
}
data["interval"] = interval;
data["recurring_end_date"] = recurring_end_date;
var flag = false;
window.check_message = "";
$.ajax({
"url" : url,
"type" : "get",
"data" : data,
"dataType" : "json",
"async": false
}).done(function(data){
if(data.success){
$("#dates_validated").val("1");
$("#property-unavaialable-alert").hide();
flag = true;
if(!check_only){
$("#property-avaialable-alert").show();
}
}else{
$("#dates_validated").val("0");
$("#property-avaialable-alert").hide();
window.check_message = data.msg;
window.check_message = window.check_message.replace(/{(.*)}/,function(v){
return getDateString(new Date(RegExp.$1),datetime_format,is_chinese);
})
if(!check_only){
$("#property-unavaialable-alert").find("span").html(window.check_message);
$("#property-unavaialable-alert").show();
}
}
spinner.hide();
el.show();
})
return flag;
}
$("#check-avail-btn").on("click",function(){
var el = $(this),
url = $(this).attr("href"),
spinner = $("#spinner");
$(".alert").hide();
var stime = $("#p_hire_start_time").val(),
etime = $("#p_hire_end_time").val();
var interval = null, recurring_end_date = null, is_recurring = false;;
if($("#p_hire_recurring").is(":checked")){
is_recurring = true;
interval = $("#p_hire_recurring_interval").val(),
recurring_end_date = $("#p_hire_recurring_end_date").val();
if(interval == "" || recurring_end_date == ""){
$("#values-alert").show();
return false;
}
}
spinner.show();
el.hide();
check_available(stime,etime,interval,recurring_end_date);
return false;
})
$("#unavailable-schedule").on("click",function(){
})
var hireForm = new FormValidator($("#new_p_hire"));
hireForm.validate_functions.checkForDates = function(value,element){
return value == "1";
}
hireForm.validate_functions.requiredifrecurring = function(value, element){
if($("#p_hire_recurring").is(":checked")){
return value != "";
}else{
return true;
}
}
$("#p_hire_recurring").on("click",function(){
$("#dates_validated").val("0");
$("#property-avaialable-alert").hide();
$("#property-unavaialable-alert").hide();
if($(this).is(":checked")){
$("#recurring-block").slideDown();
}else{
$("#recurring-block").slideUp();
}
})
</script>

View File

@ -1,4 +1,80 @@
<%= csrf_meta_tag %>
<div class="pull-right">
<a href="?type=" class="btn <%= 'active' if params[:type] != 'Calendar' %>">Table</a>
<a href="?type=Calendar" class="btn <%= 'active' if params[:type] == 'Calendar' %>">Calendar</a>
</div>
<hr>
<% if params[:type] == "Calendar" %>
<style type="text/css">
.row{
margin-left: 0;
}
@media (min-width:768px){
.col-md-4 > * {
padding: 0 0.5em;
}
.col-md-8 > * {
padding: 0 1em;
}
.col-md-4{
float: left;
width: 33.3%;
}
.col-md-8{
float: left;
width: 66.6%;
}
}
</style>
<%= content_for :page_specific_css do %>
<% ["basic/bootstrap-datetimepicker.css","property_hire_fullcalendar.css","property_hire_calendar"].each do |css| %>
<%= stylesheet_link_tag css %>
<% end %>
<% end %>
<script src="https://polyfill.io/v3/polyfill.min.js?features=Intl.DateTimeFormat,Intl.DateTimeFormat.~locale.en,Intl.NumberFormat.~locale.en"></script>
<script type="text/javascript" src="/assets/property_hire_fullcalendar.min.js"></script>
<script type="text/javascript" src="/assets/property_hire_calendar_frontend.js"></script>
<div id="orbit_calendar">
<div id="sec1">
<div class="btn-toolbar" id="navigation">
<div id="calendar-nav">
<div class="btn-group">
<button class="btn btn-default btn-sm" id="prev_month_btn">
<i class="icon-chevron-left"></i>
</button>
<button class="btn btn-default btn-sm" id="next_month_btn">
<i class="icon-chevron-right"></i>
</button>
<button class="btn btn-default btn-sm" id="today_btn">Today</button>
</div>
</div>
</div>
<div class="form-inline" id="range_selection"></div>
</div>
<div id='sec3' class="btn-toolbar">
<div class="btn-group calendar_mode">
<button class="btn btn-default mode_switch btn-sm" data-mode="timeGridDay" >day</button>
<button class="btn btn-default mode_switch btn-sm" data-mode="timeGridWeek" >week</button>
<button class="btn btn-default mode_switch btn-sm" data-mode="dayGridMonth" >month</button>
<button class="btn btn-default active mode_switch btn-sm" data-mode="agenda" >agenda</button>
</div>
<button id="refresh_btn" class="btn btn-default btn-sm">
<i class="icons-cycle"></i>
</button>
</div>
<div id="view_holder">
<h3 id="current_title" class="current_day_title"></h3>
<div id="calendar"></div>
<div id="calendar_agenda"></div>
</div>
</div>
<div id="event_quick_view" class="modal" style="width: 300px; display:none; margin:0 0 0 0;"></div>
<div id="calendar-loading"></div>
<script type="text/javascript">
var property_id = "<%= @property.id.to_s %>";
var calendar = new Calendar("#calendar",property_id,"agenda");
</script>
<% else %>
<table class="table main-list">
<thead>
<tr class="sort-header">
@ -31,6 +107,7 @@
</td>
<td>
<% if can_edit_or_delete?(p_hire.property) %>
<a href="<%= edit_hire_admin_property_hire_path(p_hire) %>" class="btn btn-info">Edit</a>
<a href="<%= show_booking_details_admin_property_hire_path(p_hire, :page => params[:page]) %>" class="btn btn-info">View</a>
<% if p_hire.passed %>
<a href="<%= pass_booking_admin_property_hire_path(p_hire, :page => params[:page], :status => "reject", :ref => "index") %>" class="btn btn-warning">Reject</a>
@ -48,3 +125,4 @@
<a href="<%= admin_property_hires_path %>" class="btn btn-warning">Back</a>
<%= content_tag(:div, paginate(@bookings), class: "pagination pagination-centered") %>
</div>
<% end %>

View File

@ -59,6 +59,7 @@
</table>
<a href="" onclick="window.history.back();return false;" class="btn btn-warning">Back</a>
<% if can_edit_or_delete?(@booking.property) %>
<a href="<%= edit_hire_admin_property_hire_path(@booking) %>" class="btn btn-info">Edit</a>
<% if @booking.passed %>
<a href="<%= pass_booking_admin_property_hire_path(@booking, :status => "reject") %>" class="btn btn-warning">Reject</a>
<% else %>

View File

@ -1,7 +1,8 @@
<% OrbitHelper.render_css_in_head(["basic/bootstrap-datetimepicker.css"]) %>
<%= javascript_include_tag "lib/bootstrap-datetimepicker" %>
<%= javascript_include_tag "lib/datetimepicker/datetimepicker.js" %>
<% OrbitHelper.render_css_in_head(["basic/bootstrap-datetimepicker.css","property_hire_fullcalendar.css","property_hire_calendar.scss"]) %>
<%= javascript_include_tag "validator.js" %>
<script src="https://polyfill.io/v3/polyfill.min.js?features=Intl.DateTimeFormat,Intl.DateTimeFormat.~locale.en,Intl.NumberFormat.~locale.en"></script>
<script type="text/javascript" src="/assets/property_hire_fullcalendar.min.js"></script>
<script type="text/javascript" src="/assets/property_hire_calendar_frontend.js"></script>
<%
data = action_data
hire = data["hire"]
@ -26,17 +27,159 @@
<div id="property-unavaialable-alert" class="alert alert-danger" role="alert"><b>Sorry! </b><span> <%= session["hire-save-msg"] %></span></div>
<% session.delete("hire-save-msg") %>
<% end %>
<div id="orbit_calendar">
<div id="sec1">
<div class="btn-toolbar" id="navigation">
<div id="calendar-nav">
<div class="btn-group">
<button class="btn btn-default btn-sm" id="prev_month_btn">
<i class="icon-chevron-left"></i>
</button>
<button class="btn btn-default btn-sm" id="next_month_btn">
<i class="icon-chevron-right"></i>
</button>
<button class="btn btn-default btn-sm" id="today_btn">Today</button>
</div>
</div>
</div>
<div class="form-inline" id="range_selection"></div>
</div>
<div id='sec3' class="btn-toolbar">
<div class="btn-group calendar_mode">
<button class="btn btn-default mode_switch btn-sm" data-mode="timeGridDay" >day</button>
<button class="btn btn-default mode_switch btn-sm" data-mode="timeGridWeek" >week</button>
<button class="btn btn-default active mode_switch btn-sm" data-mode="dayGridMonth" >month</button>
</div>
<button id="refresh_btn" class="btn btn-default btn-sm">
<i class="icons-cycle"></i>
</button>
</div>
<div id="view_holder">
<h3 id="current_title" class="current_day_title"></h3>
<div id="calendar"></div>
<div id="calendar_agenda"></div>
</div>
</div>
<div id="event_quick_view" class="modal" style="width: 300px; display:none; margin:0 0 0 0;"></div>
<div id="calendar-loading"></div>
<script type="text/javascript">
var property_id = "<%= property.id.to_s %>";
var calendar = new Calendar("#calendar",property_id);
function change_pick(target){
if( $(target).attr("id") == "pick_recurring_end_date"){
if($('#p_hire_recurring_interval').val() == ""){
alert("<%=t("property_hire.please_select_recurring_interval")%>");
$('#p_hire_recurring_interval').focus();
return;
}
$("#calendar").data("recurring_interval", $('#p_hire_recurring_interval').val());
}
$("#hidden_timepicker").addClass("hide");
$("#calendar").data("target","#"+$(target).attr("id"));
$("#calendar").data("title", $(target).parents(".col-sm-10").prev("label").text().replace("*",""));
$("#calendar").addClass("active_picker");
document.getElementById("main-content").scrollIntoView();
}
$("#calendar").on("select_time",function(ev,date_str){
$("#hidden_date").text(date_str);
$("#hidden_title").text($("#calendar").data("title"));
$('#hidden_timepicker .time_picker').addClass("pull-left");
$("#hidden_timepicker").removeClass("hide");
var target = $('#timepicker');
var window_width = $(window).width();
var window_height = $(window).height();
var target_offset = target.offset();
scrollTo(target_offset.left - window_width / 2, target_offset.top - window_height / 2);
$('#timepicker').trigger('focus');
})
$("#calendar").on("init_time",function(ev,time_str){
$('#timepicker').val(time_str);
})
function set_datetimepicker(){
var date_time = $("#hidden_date").text() + " " + $('#timepicker').val();
var start_date, end_date, interval = null, recurring_end_date = null;
var target = $("#calendar").data("target");
if(target == "#pick_start_date"){
start_date = date_time;
end_date = $("#p_hire_end_time").val();
}else if(target == "#pick_end_date"){
end_date = date_time;
start_date = $("#p_hire_start_time").val();
}else if(target == "#pick_recurring_end_date"){
start_date = $("#p_hire_start_time").val();
end_date = $("#p_hire_end_time").val();
interval = $("#p_hire_recurring_interval").val();
recurring_end_date = date_time;
}
end_date = (end_date == "" ? null : end_date);
start_date = (start_date == "" ? null : start_date);
if(start_date != null && end_date != null && $("#p_hire_start_time").val() != "" && $("#p_hire_end_time").val() != ""){
if(start_date > end_date){
if(target == "#pick_start_date"){
end_date = start_date.split(" ")[0] + " " + end_date.split(" ")[1];
$("#p_hire_end_time").val(end_date);
}else{
start_date = end_date.split(" ")[0] + " " + start_date.split(" ")[1];
$("#p_hire_start_time").val(start_date);
}
}
}
console.log(start_date)
var check_only = (start_date == null || end_date == null);
if(check_available(start_date,end_date,interval,recurring_end_date,check_only)){
var target = $($("#calendar").data("target"));
target.prev().find("input").val(date_time);
$("#hidden_timepicker").addClass("hide");
$("#calendar").removeClass("active_picker");
var window_width = $(window).width();
var window_height = $(window).height();
var target_offset = target.offset();
scrollTo(target_offset.left - window_width / 2, target_offset.top - window_height / 2);
}else{
if(window.check_message !== ""){
alert(window.check_message.replace(/<br>/g,"\n"));
}else{
alert('<%=t("property_hire.unavailability")%>');
}
}
}
function goto_calendar(){
$("#hidden_timepicker").addClass("hide");
var target = $("#calendar");
var window_width = $(window).width();
var window_height = $(window).height();
var target_offset = target.offset();
scrollTo(target_offset.left - window_width / 2, target_offset.top - window_height / 2);
}
</script>
<div id="hidden_timepicker" class="hide">
<span id="hidden_title" class="pull-left" style="margin-right: 1em;font-weight: bold;"></span>
<div style="display: grid;">
<span id="hidden_date" class="pull-left" style="margin-right: 1em;"></span>
<%= fields_for :timepicker do |f|%>
<%= f.time_picker :timepicker, :no_label => true, :new_record => hire.new_record?,:format=>"HH:mm", :class => "pull-left", :data=>{"picker-type" => "range", "range" => "end", "fv-validation" => "required;", "fv-messages" => "Cannot be empty;"} %>
<% end %>
<div class="pull-left btn-group" style="margin-top: 0.5em;">
<button id="confirm_date" class="btn btn-primary btn-sm" style="margin-right: 0.5em;" onclick="set_datetimepicker()"><%=t("property_hire.confirm")%></button>
<button id="cancel_date" class="btn btn-primary btn-sm" onclick="goto_calendar()"><%=t("property_hire.cancel")%></button>
</div>
</div>
<div style="clear: both;"></div>
<hr>
</div>
<%= form_for hire, :url => "/xhr/property_hires/make_booking", html: { class: "form-horizontal" } do |f| %>
<div class="form-group">
<%= f.label :start_time, "*"+t("property_hire.start_time"), :class => "col-sm-2 control-label" %>
<div class="col-sm-10">
<%= f.datetime_picker :start_time, :no_label => true, :new_record => hire.new_record?, :data=>{"picker-type" => "range", "range" => "start", "fv-validation" => "required;", "fv-messages" => "Cannot be empty;"} %>
<%= f.datetime_picker :start_time, :no_label => true, :new_record => hire.new_record?,:class => "pull-left", :data=>{"picker-type" => "range", "range" => "start", "fv-validation" => "required;", "fv-messages" => "Cannot be empty;"} %>
<button type="button" id="pick_start_date" onclick="change_pick(this)" class="btn btn-primary btn-sm pull-left" style="margin-left: 1em;"><%=t("property_hire.pick_from_calendar")%></button>
</div>
</div>
<div class="form-group">
<%= f.label :end_time, "*"+t("property_hire.end_time"), :class => "col-sm-2 control-label" %>
<div class="col-sm-10">
<%= f.datetime_picker :end_time, :no_label => true, :new_record => hire.new_record?, :data=>{"picker-type" => "range", "range" => "end", "fv-validation" => "required;", "fv-messages" => "Cannot be empty;"} %>
<%= f.datetime_picker :end_time, :no_label => true, :new_record => hire.new_record?,:class => "pull-left", :data=>{"picker-type" => "range", "range" => "end", "fv-validation" => "required;", "fv-messages" => "Cannot be empty;"} %>
<button type="button" id="pick_end_date" onclick="change_pick(this)" class="btn btn-primary btn-sm pull-left" style="margin-left: 1em;"><%=t("property_hire.pick_from_calendar")%></button>
</div>
</div>
<!-- ############# recurring ############# -->
@ -57,7 +200,8 @@
<div class="form-group">
<%= f.label :recurring_end_date, "*"+t("property_hire.recurring_end_date"), :class => "col-sm-2 control-label" %>
<div class="col-sm-10">
<%= f.datetime_picker :recurring_end_date, :no_label => true, :new_record => hire.new_record?, :data=>{"fv-validation" => "requiredifrecurring;", "fv-messages" => "Cannot be empty;"} %>
<%= f.datetime_picker :recurring_end_date, :no_label => true, :new_record => hire.new_record?, :class=>"pull-left", :data=>{"fv-validation" => "requiredifrecurring;", "fv-messages" => "Cannot be empty;"} %>
<button type="button" id="pick_recurring_end_date" onclick="change_pick(this)" class="btn btn-primary btn-sm pull-left" style="margin-left: 1em;"><%=t("property_hire.pick_from_calendar")%></button>
</div>
</div>
</div>
@ -66,21 +210,20 @@
<div class="col-sm-offset-2 col-sm-5">
<div id="property-avaialable-alert" style="margin-bottom: 5px; padding: 10px; display: none;" class="alert alert-success" role="alert"><b>Hooray! </b>This property is available.</div>
<div id="property-unavaialable-alert" style="margin-bottom: 5px; padding: 10px; display: none;" class="alert alert-danger" role="alert"><b>Sorry! </b><span> This property is available.</span></div>
<div id="values-alert" style="margin-bottom: 5px; padding: 10px; display: none;" class="alert alert-warning" role="alert"><b>Please! </b><span> Select an interval time and recurring event end date.</span></div>
<div id="values-alert" style="margin-bottom: 5px; padding: 10px; display: none;" class="alert alert-warning" role="alert">
<% hint1 = t("property_hire.please_select_recurring_interval_and_recurring_end_time",:default=>"") %>
<% if hint1 == ""%>
<b>Please! </b><span> Select an interval time and recurring event end date.</span>
<% else %>
<span><b><%=hint1%></b></span>
<% end %>
</div>
</div>
<% if property.set_unavailibility %>
<div class="col-sm-offset-2 col-sm-5">
<b><%= t("property_hire.Unavailibility_Schedule") %></b>
<div>
This property is unavaliable <%= !property.start_date.nil? ? " from " + property.start_date.strftime("%Y-%m-%d") : "" %> <%= !property.end_date.nil? ? " to " + property.end_date.strftime("%Y-%m-%d") : "" %> every
<% property.weekdays.each_with_index do |d,i| %>
<% if i < (property.weekdays.count - 1) %>
<%= Property::WEEKDAYS[d.to_i] + ", " %>
<% else %>
<%= Property::WEEKDAYS[d.to_i] %>
<% end %>
<% end %>
between <%= property.start_time %> &amp; <%= property.end_time %>.
<%= property.render_unavailable_message%>
</div>
</div>
<% end %>
@ -176,45 +319,76 @@
<div style="height: 50px;"></div>
<script type="text/javascript">
var property_id = "<%= property.id.to_s %>";
var check_available = function(stime,etime,interval,recurring_end_date,check_only,property_id){
var el = $("#check-avail-btn"),
url = $("#check-avail-btn").attr("href"),
spinner = $("#spinner");
stime = stime || etime;
etime = etime || stime;
property_id = property_id || window.property_id;
var timezone = new Date().toString().match(/([-\+][0-9]+)\s/)[1];
data = {
"stime": stime,
"etime": etime,
"property_id": property_id,
"locale": "<%=I18n.locale%>",
"timezone": timezone
}
data["interval"] = interval;
data["recurring_end_date"] = recurring_end_date;
var flag = false;
window.check_message = "";
$.ajax({
"url" : url,
"type" : "get",
"data" : data,
"dataType" : "json",
"async": false
}).done(function(data){
if(data.success){
$("#dates_validated").val("1");
$("#property-unavaialable-alert").hide();
flag = true;
if(!check_only){
$("#property-avaialable-alert").show();
}
}else{
$("#dates_validated").val("0");
$("#property-avaialable-alert").hide();
window.check_message = data.msg;
window.check_message = window.check_message.replace(/{(.*)}/,function(v){
return getDateString(new Date(RegExp.$1),datetime_format,is_chinese);
})
if(!check_only){
$("#property-unavaialable-alert").find("span").html(window.check_message);
$("#property-unavaialable-alert").show();
}
}
spinner.hide();
el.show();
})
return flag;
}
$("#check-avail-btn").on("click",function(){
var el = $(this),
url = $(this).attr("href"),
spinner = $("#spinner");
$(".alert").hide();
data = {
"stime" : $("#p_hire_start_time").val(),
"etime" : $("#p_hire_end_time").val(),
"property_id" : property_id
}
var stime = $("#p_hire_start_time").val(),
etime = $("#p_hire_end_time").val();
var interval = null, recurring_end_date = null, is_recurring = false;;
if($("#p_hire_recurring").is(":checked")){
var interval = $("#p_hire_recurring_interval").val(),
recurring_end_date = $("#p_hire_recurring_end_date").val();
is_recurring = true;
interval = $("#p_hire_recurring_interval").val(),
recurring_end_date = $("#p_hire_recurring_end_date").val();
if(interval == "" || recurring_end_date == ""){
$("#values-alert").show();
return false;
}
data["interval"] = interval;
data["recurring_end_date"] = recurring_end_date;
}
spinner.show();
el.hide();
$.ajax({
"url" : url,
"type" : "get",
"data" : data,
"dataType" : "json"
}).done(function(data){
if(data.success){
$("#property-avaialable-alert").show();
$("#dates_validated").val("1");
}else{
$("#property-unavaialable-alert").find("span").text(data.msg);
$("#property-unavaialable-alert").show();
}
spinner.hide();
el.show();
})
check_available(stime,etime,interval,recurring_end_date);
return false;
})
@ -235,6 +409,10 @@
}
$("#p_hire_recurring").on("click",function(){
$("#dates_validated").val("0");
$("#property-avaialable-alert").hide();
$("#property-unavaialable-alert").hide();
if($(this).is(":checked")){
$("#recurring-block").slideDown();
}else{

View File

@ -1,4 +1,5 @@
<% OrbitHelper.render_css_in_head(["property_hire_fullcalendar.css","property_hire_calendar.scss"]) %>
<script src="https://polyfill.io/v3/polyfill.min.js?features=Intl.DateTimeFormat,Intl.DateTimeFormat.~locale.en,Intl.NumberFormat.~locale.en"></script>
<script type="text/javascript" src="//code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<script type="text/javascript" src="/assets/property_hire_fullcalendar.min.js"></script>
<script type="text/javascript" src="/assets/property_hire_calendar_frontend.js"></script>
@ -33,9 +34,9 @@
</div>
<div id='sec3' class="btn-toolbar">
<div class="btn-group calendar_mode">
<button class="btn btn-default mode_switch btn-sm" data-mode="agendaDay" >day</button>
<button class="btn btn-default mode_switch btn-sm" data-mode="agendaWeek" >week</button>
<button class="btn btn-default active mode_switch btn-sm" data-mode="month" >month</button>
<button class="btn btn-default mode_switch btn-sm" data-mode="timeGridDay" >day</button>
<button class="btn btn-default mode_switch btn-sm" data-mode="timeGridWeek" >week</button>
<button class="btn btn-default active mode_switch btn-sm" data-mode="dayGridMonth" >month</button>
</div>
<button id="refresh_btn" class="btn btn-default btn-sm">
<i class="icons-cycle"></i>

View File

@ -1,5 +1,16 @@
en:
property_hire:
please_hire_after_date: "Please hire after %{date}!"
property_is_unavailable_during_this_time: "Property is unavailable during this time."
property_is_already_hired_during_this_time: "Property is already hired during this time."
starting_time_cannot_be_greater_than_ending_time: "Starting time cannot be greater than ending time."
please_select_recurring_interval: "Please select recurring interval!"
confirm: Confirm
cancel: Cancel
pick_from_calendar: Pick from calendar
how_many_months_ago_can_be_hired: "How many months ago can be hired?"
month: "%{month} month"
no_limit: No limit
none: None
checkbox_hint: You Can Select Mulitple Places
add_choice: Add choice
@ -51,9 +62,11 @@ en:
owner_phone: Owner Phone
price: Price
set_unavailibility: Set Unavailability
limit_start_time: Limit start time
limit_end_time: Limit end time
start_time: Start Time
end_time: End Time
weekdays: Weekdays
weekdays: Unavailability weekdays
start_date: From Date
end_date: To Date
description: Unavailability Description

View File

@ -1,5 +1,39 @@
zh_tw:
property_hire:
recurring_end_date_must_exceed_time: "週期結束時間需超過借用結束時間%{time}!"
1_week: "一週"
1_month: "一個月"
values_are_not_ok: "借用開始時間和結束時間不可為空"
dot: " 、 "
from: " 從 "
to: " 到 "
of: "的"
at: "在"
from_time: "從%{time}開始"
from_now_on: "從現在起"
every: 每個
time1_to_time2: "%{time1}到%{time2}之間"
unavailable_hint1: "此地點或設備%{str1}%{str2}%{week_str}%{str3}為不可借用。"
unavailable_hint2: "此地點或設備在%{month}個月之前不可借用。"
Sunday: 週日
Monday: 週一
Tuesday: 週二
Wednesday: 週三
Thursday: 週四
Friday: 週五
Saturday: 週六
please_hire_after_date: "請在%{date}之後再借用!"
property_is_unavailable_during_this_time: "該時段不可借用。"
property_is_already_hired_during_this_time: "該時段已被借用,請再選擇其他時段!"
starting_time_cannot_be_greater_than_ending_time: "借用開始時間不能超過借用結束時間!"
please_select_recurring_interval: "請選擇借用週期!"
please_select_recurring_interval_and_recurring_end_time: "請選擇借用週期和週期結束時間"
confirm: 確認
cancel: 取消
pick_from_calendar: 從日曆上選擇
how_many_months_ago_can_be_hired: 多少個月內才可被借用
month: "%{month}月"
no_limit: 無限制
none:
checkbox_hint: 可重複勾選
add_choice: 新增選項
@ -53,11 +87,11 @@ zh_tw:
owner_phone: 管理人聯絡電話
price: Price
set_unavailibility: 設定為不可借用
start_time: 借用開始時間
end_time: 借用結束時間
weekdays: Weekdays
start_date: From Date
end_date: To Date
start_time: 可被借用開始時間
end_time: 可被借用結束時間
weekdays: 不可借用工作日
start_date: 套用限制開始日期
end_date: 套用限制結束日期
description: Unavailability Description
unavailibility_note: Unavailability Note
property_location: Property Location
@ -66,6 +100,8 @@ zh_tw:
view_calendar: 查詢目前借用狀況
image: Property Image
actions: 操作
limit_start_time: 限制開始時間
limit_end_time: 限制結束時間
start_time: 借用開始時間
end_time: 借用結束時間
hiring_person_email: 借用人電子信箱

View File

@ -14,6 +14,8 @@ Rails.application.routes.draw do
get "show_booking_details"
get "pass_booking"
delete "delete_booking_details"
get "edit_hire"
patch "update_hire"
end
collection do
get "my_bookings"