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; c = this;
this.title = $("#current_title"); this.title = $("#current_title");
this.calendar = $(dom); this.calendar_dom = $(dom);
this.nextBtn = $("#next_month_btn"); this.nextBtn = $("#next_month_btn");
this.prevBtn = $("#prev_month_btn"); this.prevBtn = $("#prev_month_btn");
this.todayBtn = $("#today_btn"); this.todayBtn = $("#today_btn");
@ -11,7 +115,7 @@ var Calendar = function(dom,property_id){
this.dialog = new EventDialog(c); this.dialog = new EventDialog(c);
this.loading = $('#calendar-loading'); this.loading = $('#calendar-loading');
this.agenda_space = $("#calendar_agenda"); this.agenda_space = $("#calendar_agenda");
this.currentView = "month"; this.currentView = currentView || "dayGridMonth";
this.property_id = property_id; this.property_id = property_id;
this.navigation = $("#navigation"); this.navigation = $("#navigation");
this.rangeSelection = $("#range_selection"); this.rangeSelection = $("#range_selection");
@ -22,43 +126,89 @@ var Calendar = function(dom,property_id){
var d = date.getDate(); var d = date.getDate();
var m = date.getMonth(); var m = date.getMonth();
var y = date.getFullYear(); var y = date.getFullYear();
var dview = (c.currentView == "agenda" ? "month" : c.currentView); var dview = (c.currentView == "agenda" ? "dayGridMonth" : c.currentView);
c.calendar.fullCalendar({ c.calendar_dom.css("overflow","visible");
c.calendar_dom.fullCalendar({
themeSystem: 'bootstrap',
editable: false, editable: false,
selectable: false, selectable: true,
events: "/xhr/property_hires/get_bookings?property_id="+c.property_id, width: "100%",
header: false, events: function(args, success_callback, fail_callback) {
default: dview, var start = args.start;
height: $("body").height() - 141, 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) { loading: function(bool) {
if (bool) c.loading.css("left",($(window).width()/2 - 60) + "px").show(); if (bool) c.loading.show();
else c.loading.hide(); else c.loading.hide();
if(this.currentData)
$('#current_title').html(this.currentData.viewTitle);
}, },
windowResize : function(view){ windowResize : function(view){
view.setHeight($("body").height() - 141); c.calendar_dom.calendar.refetchEvents();
c.calendar.fullCalendar("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) { eventClick: function(calEvent, e, view) {
if(calEvent.jsEvent && !e){
e = {"originalEvent": calEvent.jsEvent}
}
c.dialog.dismiss(); c.dialog.dismiss();
c.dialog.inflate(calEvent); c.dialog.inflate(calEvent);
c.dialog.show({"x":e.originalEvent.clientX,"y":e.originalEvent.clientY}); 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.nextBtn.click(function(){
c.dialog.dismiss(); c.dialog.dismiss();
c.calendar.fullCalendar('next'); c.calendar_dom.calendar.next();
}); });
c.prevBtn.click(function(){ c.prevBtn.click(function(){
c.dialog.dismiss(); c.dialog.dismiss();
c.calendar.fullCalendar('prev'); c.calendar_dom.calendar.prev();
}); });
c.todayBtn.click(function(){ c.todayBtn.click(function(){
c.dialog.dismiss(); c.dialog.dismiss();
c.calendar.fullCalendar('today'); c.calendar_dom.calendar.today();
}); });
c.modeBtns.click(function(){ c.modeBtns.click(function(){
c.dialog.dismiss(); c.dialog.dismiss();
@ -69,7 +219,7 @@ var Calendar = function(dom,property_id){
if(c.currentView == "agenda") if(c.currentView == "agenda")
agendaView.refresh(); agendaView.refresh();
else else
c.calendar.fullCalendar("refetchEvents"); c.calendar_dom.calendar.refetchEvents();
}); });
var toggleViews = function(view){ var toggleViews = function(view){
@ -85,7 +235,7 @@ var Calendar = function(dom,property_id){
// $("#sec3").addClass("span4").removeClass("span5"); // $("#sec3").addClass("span4").removeClass("span5");
agendaView.hide(); agendaView.hide();
} }
c.calendar.fullCalendar('changeView',view); c.calendar_dom.calendar.changeView(view);
}else{ }else{
// $("#sec1").addClass("span7").removeClass("span3"); // $("#sec1").addClass("span7").removeClass("span3");
$("#sec2").hide(); $("#sec2").hide();
@ -94,19 +244,25 @@ var Calendar = function(dom,property_id){
} }
c.currentView = view; c.currentView = view;
if(loadeventsonviewchange){ if(loadeventsonviewchange){
c.calendar.fullCalendar("refetchEvents"); c.calendar_dom.calendar.refetchEvents();
loadeventsonviewchange = false; 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;} if(c.currentView == "agenda"){toggleViews("agenda");loadeventsonviewchange = true;}
}; };
this.renderEvent = function(eventStick){ this.renderEvent = function(eventStick){
if(eventStick.recurring === true) if(eventStick.recurring === true)
c.calendar.fullCalendar("refetchEvents"); c.calendar_dom.calendar.refetchEvents();
else 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 event_quick_view = null;
var template = ""; var template = "";
var _this_event = null; var _this_event = null;
var month_names = ["Jan","Feb","March","April","May","June","July","Aug","Sep","Oct","Nov","Dec"];
this.inflate = function(_event){ this.inflate = function(_event){
if(!_event) throw new UserException("EventStick can't be null!"); 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; _this_event = _event;
var start_time = "", var start_time = "",
end_time = "", end_time = "",
time_string = null; time_string = null;
if(_event.allDay) { if(_event.allDay) {
start_time = $.fullCalendar.formatDate(_event._start,"MMM dd, yyyy"); start_time = getDateString(_event._start,datetime_format, is_chinese);
if(_event._end) if(_event._end)
end_time = $.fullCalendar.formatDate(_event._end,"MMM dd, yyyy"); 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' /> " + start_time + "</p>" : "<i class='icons-calendar' /> " + start_time + " <i class='icons-arrow-right-5' /> " + end_time + ""); 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 { } else {
var sh = _event._start.getHours() > 12 ? _event._start.getHours() - 12 : _event._start.getHours(), start_time = getDateString(_event._start,date_format, is_chinese);
eh = _event._end.getHours() > 12 ? _event._end.getHours() - 12 : _event._end.getHours(), end_time = getDateString(_event._end,date_format, is_chinese);
sm = _event._start.getMinutes() < 10 ? '0' + _event._start.getMinutes() : _event._start.getMinutes(), var stime = getDateString(_event._start,time_format, is_chinese),
em = _event._end.getMinutes() < 10 ? '0' + _event._end.getMinutes() : _event._end.getMinutes(), etime = getDateString(_event._end,time_format, is_chinese),
stime = _event._start.getHours() > 12 ? sh + ':' + sm + " PM" : sh + ':' + sm + " AM", same = (start_time == end_time);
etime = _event._end.getHours() > 12 ? eh + ':' + em + " PM" : eh + ':' + em + " AM", if( same ){
same = (_event._start.getDate() == _event._end.getDate() && _event._start.getMonth() == _event._end.getMonth() && _event._start.getFullYear() == _event._end.getFullYear()); time_string = "<p class='date'><i class='icons-calendar' /></i> " +
start_time = month_names[_event._start.getMonth()] + " " + _event._start.getDate() + ", " + _event._start.getFullYear(); start_time +
end_time = month_names[_event._end.getMonth()] + " " + _event._end.getDate() + ", " + _event._end.getFullYear(); "</p><p class='time'><i class='icons-clock' /></i> " + stime +
" <i class='icons-arrow-right-5' /></i> " + etime ;
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>"); }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>'); event_quick_view = $('<div class="calendar-modal" style="display:none;"></div>');
template = '<div class="modal-content">' + template = '<div class="modal-content">' +
@ -154,6 +327,7 @@ var EventDialog = function(calendar,event){
'</div>' + '</div>' +
'<div class="modal-body">' + '<div class="modal-body">' +
'<div class="event_summary">' + time_string + '</br>' + _event.hiring_person_name + '</div>' + _event.note + '<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>' +
'<div class="modal-footer" />' + '<div class="modal-footer" />' +
'</div>'; '</div>';
@ -161,6 +335,7 @@ var EventDialog = function(calendar,event){
this.show = function(pos){ this.show = function(pos){
event_quick_view.css({width: '',height: ''});
if(pos){ if(pos){
var pos = getPosition(pos); var pos = getPosition(pos);
event_quick_view.css({"left":pos.x+"px","top":pos.y+"px"}); 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(".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.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;}); 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(){ this.dismiss = function(){
@ -238,6 +445,7 @@ var AgendaView = function(calendar){
'<th>Date</th>' + '<th>Date</th>' +
'<th>Time</th>' + '<th>Time</th>' +
'<th>Events</th>' + '<th>Events</th>' +
'<th>Borrower</th>' +
'</tr>' + '</tr>' +
'</thead>' + '</thead>' +
'<tbody>' + '<tbody>' +
@ -250,11 +458,9 @@ var AgendaView = function(calendar){
var head_template = '<div>' + var head_template = '<div>' +
'<label>From</label>' + '<label>From</label>' +
'<select name="start_month" 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">'+
'<select name="start_year" class="form-control input-sm" />' +
'<label>To</label>' + '<label>To</label>' +
'<select name="end_month" 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">'+
'<select name="end_year" class="form-control input-sm" />' +
'<button id="show_events" class="btn btn-sm bt-filter btn-primary">Show Events</button>' + '<button id="show_events" class="btn btn-sm bt-filter btn-primary">Show Events</button>' +
'</div>'; '</div>';
@ -264,6 +470,7 @@ var AgendaView = function(calendar){
'<td>' + '<td>' +
'<div class="event" />' + '<div class="event" />' +
'</td>' + '</td>' +
'<td class="Borrower">'+
'</tr>'; '</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>'; // 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){ this.inflate = function(forceInflation){
loading(true); loading(true);
_calendar.calendar.hide(); _calendar.calendar_dom.hide();
_calendar.navigation.hide(); _calendar.navigation.hide();
if(!forceInflation){ if(!forceInflation){
@ -299,10 +506,12 @@ var AgendaView = function(calendar){
_calendar.rangeSelection.append(renderHead().html()).show(); _calendar.rangeSelection.append(renderHead().html()).show();
_calendar.rangeSelection.find("button#show_events").click(function(){ _calendar.rangeSelection.find("button#show_events").click(function(){
show_event_clicked = true; show_event_clicked = true;
start_month = parseInt($("select[name=start_month]").val()); var starts = $("#agenda_start").val().split("/"),
end_month = parseInt($("select[name=end_month]").val()); ends = $("#agenda_end").val().split("/");
start_year = parseInt($("select[name=start_year]").val()); start_month = parseInt(starts[1]) - 1;
end_year = parseInt($("select[name=end_year]").val()); end_month = parseInt(ends[1]) - 1;
start_year = parseInt(starts[0]);
end_year = parseInt(ends[0]);
av.inflate(true); av.inflate(true);
}) })
} }
@ -336,7 +545,7 @@ var AgendaView = function(calendar){
_calendar.rangeSelection.hide(); _calendar.rangeSelection.hide();
agenda_space.hide(); agenda_space.hide();
_calendar.navigation.show(); _calendar.navigation.show();
_calendar.calendar.show(); _calendar.calendar_dom.show();
} }
this.show = function(){ this.show = function(){
@ -347,7 +556,7 @@ var AgendaView = function(calendar){
return x.clone(); return x.clone();
} }
var eventsManager = function(){ 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), sd = new Date(start_year,start_month,1),
ed = new Date(end_year,end_month+1,0), ed = new Date(end_year,end_month+1,0),
usd = Math.round(sd/1000), usd = Math.round(sd/1000),
@ -355,9 +564,39 @@ var AgendaView = function(calendar){
$.ajax({ $.ajax({
type : "get", type : "get",
url : url, 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){ 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), var ed = eventDom(e),
s = new Date(e.start), s = new Date(e.start),
e = new Date(e.end), e = new Date(e.end),
@ -414,14 +653,16 @@ var AgendaView = function(calendar){
var e_t = $(event_template), var e_t = $(event_template),
s = new Date(event.start), s = new Date(event.start),
e = new Date(event.end), 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()) 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 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: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("div.event").text(event.title).css("color",event.color);
e_t.find("td.Borrower").text(hiring_person_name);
return e_t; 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 */
#orbit_calendar { #orbit_calendar {
transition: all 0.3s ease; transition: all 0.3s ease;
@ -96,7 +104,9 @@
& > * { & > * {
margin: 0 5px; margin: 0 5px;
} }
& > select {
height: 3em;
}
@media screen and (max-width: 479px) { @media screen and (max-width: 479px) {
margin-right: 0; margin-right: 0;
@ -253,6 +263,9 @@
.event_list td { .event_list td {
padding: 0; padding: 0;
} }
.event_list th {
width: 25%;
}
} }
.event_holder { .event_holder {
@ -627,8 +640,9 @@
} }
#calendar-loading { #calendar-loading {
position: absolute; position: fixed;
top: 40%; top: 50%;
left: 50%;
z-index: 10; z-index: 10;
width: 120px; width: 120px;
height: 120px; height: 120px;
@ -641,6 +655,7 @@
background-position: center 20px; background-position: center 20px;
background-size: 50%; background-size: 50%;
box-shadow: 0 0 25px 0 rgba(0, 0, 0, 0.2); box-shadow: 0 0 25px 0 rgba(0, 0, 0, 0.2);
transform: translate(-50%, -50%);
&:after { &:after {
content: "Loading..."; content: "Loading...";
@ -691,7 +706,7 @@
.calendar-modal { .calendar-modal {
position: fixed; position: fixed;
z-index: 1050; z-index: 10000;
width: 300px; width: 300px;
margin: 0; margin: 0;
font-size: 12px; 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 = PropertyLocation.all.desc(:created_at).collect{|loc| [loc.title, loc.id.to_s]}
@locations << ["Other", "other_location"] @locations << ["Other", "other_location"]
end 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 def edit
@property = Property.where(:uid => params[:id].split("-").last).first rescue nil @property = Property.where(:uid => params[:id].split("-").last).first rescue nil
create_set (true) create_set (true)
@ -178,7 +185,9 @@ class Admin::PropertyHiresController < OrbitAdminController
def location_params def location_params
params.require(:property_location).permit! params.require(:property_location).permit!
end end
def phire_params
params.require(:p_hire).permit!
end
def property_params def property_params
prop = params.require(:property).permit! prop = params.require(:property).permit!
prop.delete(:property_location) if prop[:property_location] == "other" prop.delete(:property_location) if prop[:property_location] == "other"

View File

@ -214,7 +214,7 @@ class PropertyHiresController < ApplicationController
end end
end end
respond_to do |format| 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 } format.json { render json: allevents.to_json }
end end
end end

View File

@ -2,32 +2,63 @@ module Admin::PropertyHiresHelper
def check_for_availability(stime, etime, pid, interval=nil, recurring_end_date=nil) def check_for_availability(stime, etime, pid, interval=nil, recurring_end_date=nil)
property = Property.find(pid) 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? if !stime.blank?
stime = DateTime.parse(stime + Time.zone.to_s) rescue nil stime = DateTime.parse(stime + timezone) rescue nil
else else
stime = nil stime = nil
end end
if !etime.blank? if !etime.blank?
etime = DateTime.parse(etime + Time.zone.to_s) rescue nil etime = DateTime.parse(etime + timezone) rescue nil
else else
etime = nil etime = nil
end end
if !recurring_end_date.blank? 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 else
recurring_end_date = nil recurring_end_date = nil
end end
data = {} data = {}
return {"success" => false, "msg" => "Starting time cannot be greater than ending time."} if stime > etime if stime > etime
if property.is_available_for_hire?(stime, etime, interval, recurring_end_date) I18n.with_locale(params[:locale]) do
if property.not_yet_hired?(stime, etime, interval, recurring_end_date) 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} data = {"success" => true}
else 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 end
else 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 end
return data return data
end end

View File

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

View File

@ -21,7 +21,7 @@ class Property
mount_uploader :image, ImageUploader mount_uploader :image, ImageUploader
# unavailibility fields # unavailibility fields
field :can_hire_before_months, type: Integer, default: 0
field :set_unavailibility, type: Boolean, default: false field :set_unavailibility, type: Boolean, default: false
field :start_time field :start_time
field :end_time field :end_time
@ -54,7 +54,79 @@ class Property
"Friday", "Friday",
"Saturday" "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 def get_location_name
return self.property_location.nil? ? self.other_location : self.property_location.title return self.property_location.nil? ? self.other_location : self.property_location.title
end end
@ -66,11 +138,14 @@ class Property
def is_available_for_hire?(stime, etime, interval = nil, recurring_end_date = nil) def is_available_for_hire?(stime, etime, interval = nil, recurring_end_date = nil)
available = false available = false
return true if self.set_unavailibility == false return true if self.set_unavailibility == false
return true if self.weekdays.empty? return true if self.weekdays.empty? && self.can_hire_before_months == 0
available = true if !self.start_date.nil? && (self.start_date > stime && self.start_date > etime) if self.can_hire_before_months != 0
available = true if !self.end_date.nil? && self.end_date < stime return nil if (((stime - Time.now.to_datetime) * 1.day) > (self.can_hire_before_months).month)
startt = self.start_date.nil? ? self.created_at : self.start_date end
endt = self.end_date.nil? && !startt.nil? ? (startt + 5.years) : self.end_date 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} weekdays = self.weekdays.collect{|w| w.to_i}
if !startt.nil? if !startt.nil?
if !available if !available
@ -83,8 +158,8 @@ class Property
time_weekdays.uniq! time_weekdays.uniq!
weekdays = weekdays & time_weekdays weekdays = weekdays & time_weekdays
return true if weekdays.blank? return true if weekdays.blank?
startt = DateTime.parse(stime.strftime("%Y-%m-%d " + self.start_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 + 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) common_dates = (startt..endt) & (stime..etime)
available = common_dates.nil? available = common_dates.nil?
end end
@ -119,17 +194,21 @@ class Property
end end
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 phires = self.p_hires
bookings_count = phires.where(:start_time.lte => stime,:end_time.gte => stime,:recurring => false).count stime = stime.utc
+ phires.where(:start_time.gte => stime,:end_time.lte => etime,:recurring => false).count etime = etime.utc
+phires.where(:start_time.lte => etime,:end_time.gte => etime,:recurring => false).count 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 available = true
if bookings_count != 0 if bookings_count != 0
available = false available = false
end end
puts ["phire_id",phire_id]
if available 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 case interval
when 'week' when 'week'
d_step = 1.week d_step = 1.week
@ -138,52 +217,49 @@ class Property
else else
d_step = 0 d_step = 0
end end
bookings.each do |booking| if true#d_step != 0
stime_tp = stime bookings += recurring_bookings
etime_tp = etime end
bookings.each_with_index do |booking,i|
stime_tp = stime.clone
etime_tp = etime.clone
b_interval = booking.recurring_interval 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 b_interval = nil
end end
while true b_datas = []
if b_interval == 'month' if b_interval.present?
diff_month = booking.end_time.month - booking.start_time.month b_interval = (1).send(b_interval)
diff_month = diff_month + 12 if diff_month < 0 b_datas = (b_edata..b_recurring_end_date).step(b_interval).to_a
e_month = stime_tp.month + diff_month b_datas = b_datas.map{|b_end| [b_end-b_delta,b_end]}
e_year = stime_tp.year + (booking.end_time.year-booking.start_time.year) start_index = b_datas.count
e_month = e_month - 12 if e_month >12 b_datas.each_with_index do |(b_start,b_end),i|
b_sdata = Time.local(stime_tp.year,stime_tp.month,booking.start_time.day,booking.start_time.hour,booking.start_time.minute).to_datetime if b_start >= stime_tp || b_end <= etime_tp
b_edata = Time.local(e_year,e_month,booking.end_time.day,booking.end_time.hour,booking.end_time.minute).to_datetime start_index = i
elsif b_interval == 'week' break
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
end 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 end
now_diff_day = etime_tp - stime_tp b_datas = b_datas[start_index..-1].to_a.map{|b_start,b_end| (b_start..b_end)}
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 else
b_sdata = booking.start_time b_datas = [b_sdata..b_edata]
b_edata = booking.end_time
end end
while b_datas.present?
sdata = stime_tp sdata = stime_tp
edata = etime_tp edata = etime_tp
if etime_tp > booking.start_time available = b_datas.find{|b_range| b_range & (stime_tp..etime_tp)}.nil?
if (sdata <= b_sdata && edata >= b_sdata) || (sdata >= b_sdata && edata <= b_edata) || (sdata <= b_edata && edata >= b_edata)
available = false
end
end
stime_tp = stime_tp + d_step stime_tp = stime_tp + d_step
etime_tp = etime_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 end
break if available == false break if available == false
end end

View File

@ -245,42 +245,20 @@
<div id="set_unavailibility_div" style="display: none;"> <div id="set_unavailibility_div" style="display: none;">
<% end %> <% end %>
<div class="control-group"> <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"> <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> </div>
<div class="control-group"> <div class="control-group">
<%= f.label :end_time, t("property_hire.end_time"), :class => "control-label muted" %> <label class="control-label muted"><%=t("property_hire.weekdays").html_safe%></label>
<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>
<div class="controls"> <div class="controls">
<% weekdays = @property.weekdays rescue [] %> <% weekdays = @property.weekdays rescue [] %>
<label for="sunday"> <% Property::WEEKDAYS.each_with_index do |weekday,i| %>
<input id="sunday" type="checkbox" name="property[weekdays][]" value="0" <%= weekdays.include?("0") ? "checked=checked" : "" %> /> Sunday <label for="<%=weekday%>">
</label> <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 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> </label>
<% end %>
</div> </div>
</div> </div>
<div class="control-group"> <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" %> <%= 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> </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| %> <% @site_in_use_locales.each do |locale| %>
<%= f.fields_for :description_translations do |f| %> <%= f.fields_for :description_translations do |f| %>
<div class="control-group"> <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 %> <%= 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"> <table class="table main-list">
<thead> <thead>
<tr class="sort-header"> <tr class="sort-header">
@ -31,6 +107,7 @@
</td> </td>
<td> <td>
<% if can_edit_or_delete?(p_hire.property) %> <% 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> <a href="<%= show_booking_details_admin_property_hire_path(p_hire, :page => params[:page]) %>" class="btn btn-info">View</a>
<% if p_hire.passed %> <% 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> <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> <a href="<%= admin_property_hires_path %>" class="btn btn-warning">Back</a>
<%= content_tag(:div, paginate(@bookings), class: "pagination pagination-centered") %> <%= content_tag(:div, paginate(@bookings), class: "pagination pagination-centered") %>
</div> </div>
<% end %>

View File

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

View File

@ -1,7 +1,8 @@
<% OrbitHelper.render_css_in_head(["basic/bootstrap-datetimepicker.css"]) %> <% OrbitHelper.render_css_in_head(["basic/bootstrap-datetimepicker.css","property_hire_fullcalendar.css","property_hire_calendar.scss"]) %>
<%= javascript_include_tag "lib/bootstrap-datetimepicker" %>
<%= javascript_include_tag "lib/datetimepicker/datetimepicker.js" %>
<%= javascript_include_tag "validator.js" %> <%= 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 data = action_data
hire = data["hire"] 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> <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") %> <% session.delete("hire-save-msg") %>
<% end %> <% 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| %> <%= form_for hire, :url => "/xhr/property_hires/make_booking", html: { class: "form-horizontal" } do |f| %>
<div class="form-group"> <div class="form-group">
<%= f.label :start_time, "*"+t("property_hire.start_time"), :class => "col-sm-2 control-label" %> <%= f.label :start_time, "*"+t("property_hire.start_time"), :class => "col-sm-2 control-label" %>
<div class="col-sm-10"> <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> </div>
<div class="form-group"> <div class="form-group">
<%= f.label :end_time, "*"+t("property_hire.end_time"), :class => "col-sm-2 control-label" %> <%= f.label :end_time, "*"+t("property_hire.end_time"), :class => "col-sm-2 control-label" %>
<div class="col-sm-10"> <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>
</div> </div>
<!-- ############# recurring ############# --> <!-- ############# recurring ############# -->
@ -57,7 +200,8 @@
<div class="form-group"> <div class="form-group">
<%= f.label :recurring_end_date, "*"+t("property_hire.recurring_end_date"), :class => "col-sm-2 control-label" %> <%= f.label :recurring_end_date, "*"+t("property_hire.recurring_end_date"), :class => "col-sm-2 control-label" %>
<div class="col-sm-10"> <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> </div>
</div> </div>
@ -66,21 +210,20 @@
<div class="col-sm-offset-2 col-sm-5"> <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-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="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> </div>
<% if property.set_unavailibility %> <% if property.set_unavailibility %>
<div class="col-sm-offset-2 col-sm-5"> <div class="col-sm-offset-2 col-sm-5">
<b><%= t("property_hire.Unavailibility_Schedule") %></b> <b><%= t("property_hire.Unavailibility_Schedule") %></b>
<div> <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.render_unavailable_message%>
<% 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 %>.
</div> </div>
</div> </div>
<% end %> <% end %>
@ -176,45 +319,76 @@
<div style="height: 50px;"></div> <div style="height: 50px;"></div>
<script type="text/javascript"> <script type="text/javascript">
var property_id = "<%= property.id.to_s %>"; 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(){ $("#check-avail-btn").on("click",function(){
var el = $(this), var el = $(this),
url = $(this).attr("href"), url = $(this).attr("href"),
spinner = $("#spinner"); spinner = $("#spinner");
$(".alert").hide(); $(".alert").hide();
data = { var stime = $("#p_hire_start_time").val(),
"stime" : $("#p_hire_start_time").val(), etime = $("#p_hire_end_time").val();
"etime" : $("#p_hire_end_time").val(), var interval = null, recurring_end_date = null, is_recurring = false;;
"property_id" : property_id
}
if($("#p_hire_recurring").is(":checked")){ if($("#p_hire_recurring").is(":checked")){
var interval = $("#p_hire_recurring_interval").val(), is_recurring = true;
interval = $("#p_hire_recurring_interval").val(),
recurring_end_date = $("#p_hire_recurring_end_date").val(); recurring_end_date = $("#p_hire_recurring_end_date").val();
if(interval == "" || recurring_end_date == ""){ if(interval == "" || recurring_end_date == ""){
$("#values-alert").show(); $("#values-alert").show();
return false; return false;
} }
data["interval"] = interval;
data["recurring_end_date"] = recurring_end_date;
} }
spinner.show(); spinner.show();
el.hide(); el.hide();
$.ajax({ check_available(stime,etime,interval,recurring_end_date);
"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();
})
return false; return false;
}) })
@ -235,6 +409,10 @@
} }
$("#p_hire_recurring").on("click",function(){ $("#p_hire_recurring").on("click",function(){
$("#dates_validated").val("0");
$("#property-avaialable-alert").hide();
$("#property-unavaialable-alert").hide();
if($(this).is(":checked")){ if($(this).is(":checked")){
$("#recurring-block").slideDown(); $("#recurring-block").slideDown();
}else{ }else{

View File

@ -1,4 +1,5 @@
<% OrbitHelper.render_css_in_head(["property_hire_fullcalendar.css","property_hire_calendar.scss"]) %> <% 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="//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_fullcalendar.min.js"></script>
<script type="text/javascript" src="/assets/property_hire_calendar_frontend.js"></script> <script type="text/javascript" src="/assets/property_hire_calendar_frontend.js"></script>
@ -33,9 +34,9 @@
</div> </div>
<div id='sec3' class="btn-toolbar"> <div id='sec3' class="btn-toolbar">
<div class="btn-group calendar_mode"> <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="timeGridDay" >day</button>
<button class="btn btn-default mode_switch btn-sm" data-mode="agendaWeek" >week</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="month" >month</button> <button class="btn btn-default active mode_switch btn-sm" data-mode="dayGridMonth" >month</button>
</div> </div>
<button id="refresh_btn" class="btn btn-default btn-sm"> <button id="refresh_btn" class="btn btn-default btn-sm">
<i class="icons-cycle"></i> <i class="icons-cycle"></i>

View File

@ -1,5 +1,16 @@
en: en:
property_hire: 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 none: None
checkbox_hint: You Can Select Mulitple Places checkbox_hint: You Can Select Mulitple Places
add_choice: Add choice add_choice: Add choice
@ -51,9 +62,11 @@ en:
owner_phone: Owner Phone owner_phone: Owner Phone
price: Price price: Price
set_unavailibility: Set Unavailability set_unavailibility: Set Unavailability
limit_start_time: Limit start time
limit_end_time: Limit end time
start_time: Start Time start_time: Start Time
end_time: End Time end_time: End Time
weekdays: Weekdays weekdays: Unavailability weekdays
start_date: From Date start_date: From Date
end_date: To Date end_date: To Date
description: Unavailability Description description: Unavailability Description

View File

@ -1,5 +1,39 @@
zh_tw: zh_tw:
property_hire: 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: none:
checkbox_hint: 可重複勾選 checkbox_hint: 可重複勾選
add_choice: 新增選項 add_choice: 新增選項
@ -53,11 +87,11 @@ zh_tw:
owner_phone: 管理人聯絡電話 owner_phone: 管理人聯絡電話
price: Price price: Price
set_unavailibility: 設定為不可借用 set_unavailibility: 設定為不可借用
start_time: 借用開始時間 start_time: 可被借用開始時間
end_time: 借用結束時間 end_time: 可被借用結束時間
weekdays: Weekdays weekdays: 不可借用工作日
start_date: From Date start_date: 套用限制開始日期
end_date: To Date end_date: 套用限制結束日期
description: Unavailability Description description: Unavailability Description
unavailibility_note: Unavailability Note unavailibility_note: Unavailability Note
property_location: Property Location property_location: Property Location
@ -66,6 +100,8 @@ zh_tw:
view_calendar: 查詢目前借用狀況 view_calendar: 查詢目前借用狀況
image: Property Image image: Property Image
actions: 操作 actions: 操作
limit_start_time: 限制開始時間
limit_end_time: 限制結束時間
start_time: 借用開始時間 start_time: 借用開始時間
end_time: 借用結束時間 end_time: 借用結束時間
hiring_person_email: 借用人電子信箱 hiring_person_email: 借用人電子信箱

View File

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