* first cut * Code refactoring, styling * Added Sorting * Revert query_report to use slickgrid * cleanup * Edit cell working * Add regrid, remove datatable * Add clusterize * Update lib, fix get_checked_items * New ReportView * wip * Enable editing, fix styles * update lib * wip * fix refresh rows and editable cells * Refresh list_view every 3s, decouple refreshing logic * Report editing fixes * Cleanup loading fields, add column then refresh list * [wip] New List View * [working] Render results * ListView is now BaseList, add new ListView and GanttView * Create new page for each ListView * GanttView working * CalendarView working * KanbanView working * Cache list_view based on page_name * Gantt view buttons on mobile * Add ReportView * Refresh datatable on render * Setup like * [start][filters] clean up FilterList * [filters] refactor FilterList * [filters] minor fix * [filters] fix remove filter * filter utils * more utils, remove apply * rewrite as class, remove 'me' references * [filter] implement on_change to decouple parent functions * Integrate new filters with new BaseList * Setup freeze area for ListView * Set breadcrumbs on setup_page * Trigger list update from events * Setup footnote area * Fix Kanban Board filters * Add filters to standard filters, then filter_list * Remove old files * Fix ImageView * Some more fixes for BaseList.init * Fix order_by on load * Report View: remember columns * Fix for hidden filters * Fix for delete items * InboxView * Shift select checkboxes * Fix ESLint errors * More refactoring - Move ListMenu to Listview - New FileView - Ability to add custom breadcrumbs * FileManager working * Tags, set filters from route options * Custom Reports Working * List Sidebar reports * Report Name as title * Fix ESLint errors * Fix UI tests * Fix Kanban test * Format ID column * [fix] Kanban cards title * Checkbox fix * Fix Activity Page * Update rows in Report in place * Child Table columns in Report View
369 lines
9.9 KiB
JavaScript
369 lines
9.9 KiB
JavaScript
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
// MIT License. See license.txt
|
|
|
|
frappe.provide("frappe.views.calendar");
|
|
frappe.provide("frappe.views.calendars");
|
|
|
|
frappe.views.CalendarView = class CalendarView extends frappe.views.ListView {
|
|
static load_last_view() {
|
|
const route = frappe.get_route();
|
|
if (route.length === 3) {
|
|
const doctype = route[1];
|
|
const user_settings = frappe.get_user_settings(doctype)['Calendar'] || {};
|
|
route.push(user_settings.last_calendar_view || 'Default');
|
|
frappe.set_route(route);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
setup_defaults() {
|
|
super.setup_defaults();
|
|
this.page_title = this.page_title + ' ' + __('Calendar');
|
|
this.calendar_settings = frappe.views.calendar[this.doctype] || {};
|
|
this.calendar_name = frappe.get_route()[3];
|
|
}
|
|
|
|
before_render() {
|
|
super.before_render();
|
|
this.save_view_user_settings({
|
|
last_calendar: this.calendar_name
|
|
});
|
|
}
|
|
|
|
render() {
|
|
if (this.calendar) {
|
|
this.calendar.refresh();
|
|
return;
|
|
}
|
|
|
|
this.load_lib
|
|
.then(() => this.get_calendar_options())
|
|
.then(options => {
|
|
this.calendar = new frappe.views.Calendar(options);
|
|
});
|
|
}
|
|
|
|
get_calendar_options() {
|
|
const options = {
|
|
doctype: this.doctype,
|
|
parent: this.$result,
|
|
page: this.page,
|
|
list_view: this
|
|
};
|
|
const calendar_name = this.calendar_name;
|
|
|
|
return new Promise(resolve => {
|
|
if (calendar_name === 'Default') {
|
|
Object.assign(options, frappe.views.calendar[this.doctype]);
|
|
resolve(options);
|
|
} else {
|
|
frappe.model.with_doc('Calendar View', calendar_name, () => {
|
|
const doc = frappe.get_doc('Calendar View', calendar_name);
|
|
Object.assign(options, {
|
|
field_map: {
|
|
id: "name",
|
|
start: doc.start_date_field,
|
|
end: doc.end_date_field,
|
|
title: doc.subject_field
|
|
}
|
|
});
|
|
resolve(options);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
get required_libs() {
|
|
return [
|
|
'assets/frappe/js/lib/fullcalendar/fullcalendar.min.css',
|
|
'assets/frappe/js/lib/fullcalendar/fullcalendar.min.js',
|
|
'assets/frappe/js/lib/fullcalendar/locale-all.js'
|
|
];
|
|
}
|
|
};
|
|
|
|
frappe.views.Calendar = Class.extend({
|
|
init: function(options) {
|
|
$.extend(this, options);
|
|
this.make_page();
|
|
this.setup_options();
|
|
this.make();
|
|
},
|
|
make_page: function() {
|
|
var me = this;
|
|
|
|
// add links to other calendars
|
|
me.page.clear_user_actions();
|
|
$.each(frappe.boot.calendars, function(i, doctype) {
|
|
if(frappe.model.can_read(doctype)) {
|
|
me.page.add_menu_item(__(doctype), function() {
|
|
frappe.set_route("List", doctype, "Calendar");
|
|
});
|
|
}
|
|
});
|
|
|
|
$(this.parent).on("show", function() {
|
|
me.$cal.fullCalendar("refetchEvents");
|
|
});
|
|
},
|
|
|
|
make: function() {
|
|
this.$wrapper = this.parent;
|
|
this.$cal = $("<div>").appendTo(this.$wrapper);
|
|
this.footnote_area = frappe.utils.set_footnote(this.footnote_area, this.$wrapper,
|
|
__("Select or drag across time slots to create a new event."));
|
|
this.footnote_area.css({"border-top": "0px"});
|
|
|
|
this.$cal.fullCalendar(this.cal_options);
|
|
this.set_css();
|
|
},
|
|
set_css: function() {
|
|
// flatify buttons
|
|
this.$wrapper.find("button.fc-state-default")
|
|
.removeClass("fc-state-default")
|
|
.addClass("btn btn-default");
|
|
|
|
this.$wrapper.find(".fc-button-group").addClass("btn-group");
|
|
|
|
this.$wrapper.find('.fc-prev-button span')
|
|
.attr('class', '').addClass('fa fa-chevron-left');
|
|
this.$wrapper.find('.fc-next-button span')
|
|
.attr('class', '').addClass('fa fa-chevron-right');
|
|
|
|
var btn_group = this.$wrapper.find(".fc-button-group");
|
|
btn_group.find(".fc-state-active").addClass("active");
|
|
|
|
btn_group.find(".btn").on("click", function() {
|
|
btn_group.find(".btn").removeClass("active");
|
|
$(this).addClass("active");
|
|
});
|
|
},
|
|
field_map: {
|
|
"id": "name",
|
|
"start": "start",
|
|
"end": "end",
|
|
"allDay": "all_day",
|
|
},
|
|
color_map: {
|
|
"danger": "red",
|
|
"success": "green",
|
|
"warning": "orange",
|
|
"default": "blue"
|
|
},
|
|
get_system_datetime: function(date) {
|
|
date._offset = moment.user_utc_offset;
|
|
return frappe.datetime.convert_to_system_tz(date);
|
|
},
|
|
setup_options: function() {
|
|
var me = this;
|
|
this.cal_options = {
|
|
locale: frappe.boot.user.language || "en",
|
|
header: {
|
|
left: 'title',
|
|
center: '',
|
|
right: 'prev,next month,agendaWeek,agendaDay'
|
|
},
|
|
editable: true,
|
|
selectable: true,
|
|
selectHelper: true,
|
|
forceEventDuration: true,
|
|
events: function(start, end, timezone, callback) {
|
|
return frappe.call({
|
|
method: me.get_events_method || "frappe.desk.calendar.get_events",
|
|
type: "GET",
|
|
args: me.get_args(start, end),
|
|
callback: function(r) {
|
|
var events = r.message;
|
|
events = me.prepare_events(events);
|
|
callback(events);
|
|
}
|
|
});
|
|
},
|
|
eventRender: function(event, element) {
|
|
element.attr('title', event.tooltip);
|
|
},
|
|
eventClick: function(event) {
|
|
// edit event description or delete
|
|
var doctype = event.doctype || me.doctype;
|
|
if(frappe.model.can_read(doctype)) {
|
|
frappe.set_route("Form", doctype, event.name);
|
|
}
|
|
},
|
|
eventDrop: function(event, delta, revertFunc) {
|
|
me.update_event(event, revertFunc);
|
|
},
|
|
eventResize: function(event, delta, revertFunc) {
|
|
me.update_event(event, revertFunc);
|
|
},
|
|
select: function(startDate, endDate, jsEvent, view) {
|
|
if (view.name==="month" && (endDate - startDate)===86400000) {
|
|
// detect single day click in month view
|
|
return;
|
|
}
|
|
|
|
var event = frappe.model.get_new_doc(me.doctype);
|
|
|
|
event[me.field_map.start] = me.get_system_datetime(startDate);
|
|
|
|
if(me.field_map.end)
|
|
event[me.field_map.end] = me.get_system_datetime(endDate);
|
|
|
|
if(me.field_map.allDay) {
|
|
var all_day = (startDate._ambigTime && endDate._ambigTime) ? 1 : 0;
|
|
|
|
event[me.field_map.allDay] = all_day;
|
|
|
|
if (all_day)
|
|
event[me.field_map.end] = me.get_system_datetime(moment(endDate).subtract(1, "s"));
|
|
}
|
|
|
|
frappe.set_route("Form", me.doctype, event.name);
|
|
},
|
|
dayClick: function(date, jsEvent, view) {
|
|
if(view.name === 'month') {
|
|
const $date_cell = $('td[data-date=' + date.format('YYYY-MM-DD') + "]");
|
|
|
|
if($date_cell.hasClass('date-clicked')) {
|
|
me.$cal.fullCalendar('changeView', 'agendaDay');
|
|
me.$cal.fullCalendar('gotoDate', date);
|
|
me.$wrapper.find('.date-clicked').removeClass('date-clicked');
|
|
|
|
// update "active view" btn
|
|
me.$wrapper.find('.fc-month-button').removeClass('active');
|
|
me.$wrapper.find('.fc-agendaDay-button').addClass('active');
|
|
}
|
|
|
|
me.$wrapper.find('.date-clicked').removeClass('date-clicked');
|
|
$date_cell.addClass('date-clicked');
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
if(this.options) {
|
|
$.extend(this.cal_options, this.options);
|
|
}
|
|
},
|
|
get_args: function(start, end) {
|
|
var args = {
|
|
doctype: this.doctype,
|
|
start: this.get_system_datetime(start),
|
|
end: this.get_system_datetime(end),
|
|
filters: this.list_view.filter_area.get(),
|
|
field_map: this.field_map
|
|
};
|
|
return args;
|
|
},
|
|
refresh: function() {
|
|
this.$cal.fullCalendar('refetchEvents');
|
|
},
|
|
prepare_events: function(events) {
|
|
var me = this;
|
|
|
|
return (events || []).map(d => {
|
|
d.id = d.name;
|
|
d.editable = frappe.model.can_write(d.doctype || me.doctype);
|
|
|
|
// do not allow submitted/cancelled events to be moved / extended
|
|
if(d.docstatus && d.docstatus > 0) {
|
|
d.editable = false;
|
|
}
|
|
|
|
$.each(me.field_map, function(target, source) {
|
|
d[target] = d[source];
|
|
});
|
|
|
|
if(!me.field_map.allDay)
|
|
d.allDay = 1;
|
|
|
|
// convert to user tz
|
|
d.start = frappe.datetime.convert_to_user_tz(d.start);
|
|
d.end = frappe.datetime.convert_to_user_tz(d.end);
|
|
|
|
// show event on single day if start or end date is invalid
|
|
if (!frappe.datetime.validate(d.start) && d.end) {
|
|
d.start = frappe.datetime.add_days(d.end, -1);
|
|
}
|
|
|
|
if (d.start && !frappe.datetime.validate(d.end)) {
|
|
d.end = frappe.datetime.add_days(d.start, 1);
|
|
}
|
|
|
|
me.fix_end_date_for_event_render(d);
|
|
me.prepare_colors(d);
|
|
return d;
|
|
});
|
|
},
|
|
prepare_colors: function(d) {
|
|
let color, color_name;
|
|
if(this.get_css_class) {
|
|
color_name = this.color_map[this.get_css_class(d)];
|
|
color_name = frappe.ui.color.validate_hex(color_name) ?
|
|
color_name : 'blue';
|
|
d.backgroundColor = frappe.ui.color.get(color_name, 'extra-light');
|
|
d.textColor = frappe.ui.color.get(color_name, 'dark');
|
|
} else {
|
|
color = d.color;
|
|
if (!frappe.ui.color.validate_hex(color) || !color) {
|
|
color = frappe.ui.color.get('blue', 'extra-light');
|
|
}
|
|
d.backgroundColor = color;
|
|
d.textColor = frappe.ui.color.get_contrast_color(color);
|
|
}
|
|
return d;
|
|
},
|
|
update_event: function(event, revertFunc) {
|
|
var me = this;
|
|
frappe.model.remove_from_locals(me.doctype, event.name);
|
|
return frappe.call({
|
|
method: me.update_event_method || "frappe.desk.calendar.update_event",
|
|
args: me.get_update_args(event),
|
|
callback: function(r) {
|
|
if(r.exc) {
|
|
frappe.show_alert(__("Unable to update event"));
|
|
revertFunc();
|
|
}
|
|
},
|
|
error: function() {
|
|
revertFunc();
|
|
}
|
|
});
|
|
},
|
|
get_update_args: function(event) {
|
|
var me = this;
|
|
var args = {
|
|
name: event[this.field_map.id]
|
|
};
|
|
|
|
args[this.field_map.start] = me.get_system_datetime(event.start);
|
|
|
|
if(this.field_map.allDay)
|
|
args[this.field_map.allDay] = (event.start._ambigTime && event.end._ambigTime) ? 1 : 0;
|
|
|
|
if(this.field_map.end) {
|
|
if (!event.end) {
|
|
event.end = event.start.add(1, "hour");
|
|
}
|
|
|
|
args[this.field_map.end] = me.get_system_datetime(event.end);
|
|
|
|
if (args[this.field_map.allDay]) {
|
|
args[this.field_map.end] = me.get_system_datetime(moment(event.end).subtract(1, "s"));
|
|
}
|
|
}
|
|
|
|
args.doctype = event.doctype || this.doctype;
|
|
|
|
return { args: args, field_map: this.field_map };
|
|
},
|
|
|
|
fix_end_date_for_event_render: function(event) {
|
|
if (event.allDay) {
|
|
// We use inclusive end dates. This workaround fixes the rendering of events
|
|
event.start = event.start ? $.fullCalendar.moment(event.start).stripTime() : null;
|
|
event.end = event.end ? $.fullCalendar.moment(event.end).add(1, "day").stripTime() : null;
|
|
}
|
|
}
|
|
});
|