[IMP] web,hr_homeworking,calendar: overwrite default fc header to allow customization
In some modules at Odoo we want to have custom headers for calendar. Since fullcalendar accepts a function callback that should render the header, it was decided to have this method exist in the calendar_common_renderer. So any other apps that want to add something to the header, can just extend this method. This commit also changes how homeworking changes the header so that it complies with this change. task-3455051 task-3389317 Part-of: odoo/odoo#138670
This commit is contained in:
@@ -1,156 +1,161 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { stepUtils } from "@web_tour/tour_service/tour_utils";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { stepUtils } from "@web_tour/tour_service/tour_utils";
|
||||
|
||||
const todayDate = function() {
|
||||
let now = new Date();
|
||||
let year = now.getFullYear();
|
||||
let month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
let day = String(now.getDate()).padStart(2, '0');
|
||||
const todayDate = function () {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(now.getDate()).padStart(2, "0");
|
||||
|
||||
return `${month}/${day}/${year} 10:00:00`;
|
||||
};
|
||||
return `${month}/${day}/${year} 10:00:00`;
|
||||
};
|
||||
|
||||
registry.category("web_tour.tours").add('calendar_appointments_hour_tour', {
|
||||
url: '/web',
|
||||
test: true,
|
||||
steps: () => [stepUtils.showAppsMenuItem(),
|
||||
registry.category("web_tour.tours").add("calendar_appointments_hour_tour", {
|
||||
url: "/web",
|
||||
test: true,
|
||||
steps: () => [
|
||||
stepUtils.showAppsMenuItem(),
|
||||
{
|
||||
trigger: '.o_app[data-menu-xmlid="calendar.mail_menu_calendar"]',
|
||||
content: 'Open Calendar',
|
||||
run: 'click',
|
||||
content: "Open Calendar",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: '.o-calendar-button-new',
|
||||
content: 'Create a new event',
|
||||
run: 'click',
|
||||
trigger: ".o-calendar-button-new",
|
||||
content: "Create a new event",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: '#name_0',
|
||||
content: 'Give a name to the new event',
|
||||
run: 'text TEST EVENT',
|
||||
trigger: "#name_0",
|
||||
content: "Give a name to the new event",
|
||||
run: "text TEST EVENT",
|
||||
},
|
||||
{
|
||||
trigger: '#start_0',
|
||||
content: 'Give a date to the new event',
|
||||
trigger: "#start_0",
|
||||
content: "Give a date to the new event",
|
||||
run: `text ${todayDate()}`,
|
||||
},
|
||||
{
|
||||
trigger: '.fa-cloud-upload',
|
||||
content: 'Save the new event',
|
||||
run: 'click',
|
||||
trigger: ".fa-cloud-upload",
|
||||
content: "Save the new event",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: '.o_back_button',
|
||||
content: 'Go back to Calendar view',
|
||||
run: 'click',
|
||||
trigger: ".o_back_button",
|
||||
content: "Go back to Calendar view",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: '.dropdown-toggle:contains("Week")',
|
||||
content: 'Click to change calendar view',
|
||||
run: 'click',
|
||||
content: "Click to change calendar view",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: '.dropdown-item:contains("Month")',
|
||||
content: 'Change the calendar view to Month',
|
||||
run: 'click',
|
||||
content: "Change the calendar view to Month",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: '.fc-day-header:contains("Monday")',
|
||||
content: 'Change the calendar view to week',
|
||||
trigger: '.fc-day-header:contains("Mon")',
|
||||
content: "Check the day is properly displayed",
|
||||
},
|
||||
{
|
||||
trigger: '.fc-time:contains("10:00")',
|
||||
content: 'Check the time is properly displayed',
|
||||
content: "Check the time is properly displayed",
|
||||
},
|
||||
{
|
||||
trigger: '.o_event_title:contains("TEST EVENT")',
|
||||
content: 'Check the event title',
|
||||
content: "Check the event title",
|
||||
isCheck: true,
|
||||
},
|
||||
]});
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add('test_calendar_delete_tour', {
|
||||
test: true,
|
||||
steps: () => [
|
||||
registry.category("web_tour.tours").add("test_calendar_delete_tour", {
|
||||
test: true,
|
||||
steps: () => [
|
||||
{
|
||||
content: 'Select filter (everybody)',
|
||||
content: "Select filter (everybody)",
|
||||
trigger: 'div[data-value="all"] input',
|
||||
},
|
||||
{
|
||||
content: 'Click on the event (focus + waiting)',
|
||||
content: "Click on the event (focus + waiting)",
|
||||
trigger: 'a .fc-content:contains("Test Event")',
|
||||
async run() {
|
||||
$('a .fc-content:contains("Test Event")').click();
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
$('a .fc-content:contains("Test Event")').click();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
content: 'Delete the event',
|
||||
trigger: '.o_cw_popover_delete',
|
||||
content: "Delete the event",
|
||||
trigger: ".o_cw_popover_delete",
|
||||
},
|
||||
{
|
||||
content: 'Validate the deletion',
|
||||
trigger:'button:contains("Delete")',
|
||||
content: "Validate the deletion",
|
||||
trigger: 'button:contains("Delete")',
|
||||
async run() {
|
||||
$('button:contains("Delete")').click();
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
}
|
||||
},
|
||||
},
|
||||
]});
|
||||
|
||||
registry.category("web_tour.tours").add('test_calendar_decline_tour', {
|
||||
test: true,
|
||||
steps: () => [
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_calendar_decline_tour", {
|
||||
test: true,
|
||||
steps: () => [
|
||||
{
|
||||
content: 'Click on the event (focus + waiting)',
|
||||
content: "Click on the event (focus + waiting)",
|
||||
trigger: 'a .fc-content:contains("Test Event")',
|
||||
async run() {
|
||||
$('a .fc-content:contains("Test Event")').click();
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
$('a .fc-content:contains("Test Event")').click();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
content: 'Delete the event',
|
||||
trigger: '.o_cw_popover_delete',
|
||||
content: "Delete the event",
|
||||
trigger: ".o_cw_popover_delete",
|
||||
},
|
||||
{
|
||||
content: 'Wait declined status',
|
||||
trigger: '.o_attendee_status_declined',
|
||||
content: "Wait declined status",
|
||||
trigger: ".o_attendee_status_declined",
|
||||
isCheck: true,
|
||||
},
|
||||
]});
|
||||
|
||||
registry.category("web_tour.tours").add('test_calendar_decline_with_everybody_filter_tour', {
|
||||
test: true,
|
||||
steps: () => [
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_calendar_decline_with_everybody_filter_tour", {
|
||||
test: true,
|
||||
steps: () => [
|
||||
{
|
||||
content: 'Select filter (everybody)',
|
||||
content: "Select filter (everybody)",
|
||||
trigger: 'div[data-value="all"] input',
|
||||
},
|
||||
{
|
||||
content: 'Click on the event (focus + waiting)',
|
||||
content: "Click on the event (focus + waiting)",
|
||||
trigger: 'a .fc-content:contains("Test Event")',
|
||||
async run() {
|
||||
$('a .fc-content:contains("Test Event")').click();
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
$('a .fc-content:contains("Test Event")').click();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
content: 'Delete the event',
|
||||
trigger: '.o_cw_popover_delete',
|
||||
content: "Delete the event",
|
||||
trigger: ".o_cw_popover_delete",
|
||||
},
|
||||
{
|
||||
content: 'Select filter (everybody)',
|
||||
content: "Select filter (everybody)",
|
||||
trigger: 'div[data-value="all"] input',
|
||||
},
|
||||
{
|
||||
content: 'Wait declined status',
|
||||
trigger: '.o_attendee_status_declined',
|
||||
content: "Wait declined status",
|
||||
trigger: ".o_attendee_status_declined",
|
||||
isCheck: true,
|
||||
},
|
||||
]});
|
||||
],
|
||||
});
|
||||
|
||||
@@ -3,39 +3,13 @@
|
||||
import { AttendeeCalendarCommonRenderer } from "@calendar/views/attendee_calendar/common/attendee_calendar_common_renderer";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { renderToString } from "@web/core/utils/render";
|
||||
|
||||
const { DateTime } = luxon;
|
||||
|
||||
patch(AttendeeCalendarCommonRenderer.prototype, {
|
||||
get options(){
|
||||
let a = Object.assign(super.options, {
|
||||
columnHeaderHtml: function(date) {
|
||||
const datetime = DateTime.fromJSDate(date);
|
||||
if (this.props.model.scale === 'week'){
|
||||
return (
|
||||
"<div>" +
|
||||
datetime.toFormat("ccc dd") +
|
||||
"</div>" +
|
||||
renderToString(this.constructor.HeaderCalendarTemplate, {
|
||||
date: date,
|
||||
today: new Date(),
|
||||
})
|
||||
);
|
||||
}
|
||||
if (this.props.model.scale === 'day'){
|
||||
return (
|
||||
"<div>" +
|
||||
datetime.toFormat("LLLL dd, yyyy") +
|
||||
"</div>" +
|
||||
renderToString(this.constructor.HeaderCalendarTemplate, {
|
||||
date: date,
|
||||
today: new Date(),
|
||||
})
|
||||
);
|
||||
}
|
||||
return "<div>" + datetime.toFormat("cccc") + "</div>";
|
||||
}
|
||||
});
|
||||
return Object.assign(a, {
|
||||
return {
|
||||
...super.options,
|
||||
eventOrder: function(event1, event2){
|
||||
if (event1.extendedProps.worklocation){
|
||||
return -1;
|
||||
@@ -46,8 +20,8 @@ patch(AttendeeCalendarCommonRenderer.prototype, {
|
||||
return event1.title.localeCompare(event2.title);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
};
|
||||
},
|
||||
fcEventToRecord(event) {
|
||||
const res = super.fcEventToRecord(...arguments);
|
||||
@@ -133,7 +107,8 @@ patch(AttendeeCalendarCommonRenderer.prototype, {
|
||||
return;
|
||||
const content = renderToString(this.constructor.ButtonWorklocationTemplate, {})
|
||||
const {children } = new DOMParser().parseFromString(content, "text/html").body
|
||||
box.appendChild(...children)
|
||||
box.appendChild(...children);
|
||||
info.homework = true;
|
||||
}
|
||||
super.onDayRender(...arguments);
|
||||
},
|
||||
@@ -205,7 +180,7 @@ patch(AttendeeCalendarCommonRenderer.prototype, {
|
||||
}
|
||||
},
|
||||
onDateClick(info){
|
||||
if (info.jsEvent && info.jsEvent.target.tagName ==="BUTTON") {
|
||||
if (info.jsEvent && info.jsEvent.target.closest(".btnWorklocation")) {
|
||||
info.homework = true
|
||||
this.props.createRecord(this.fcEventToRecord(info));
|
||||
} else {
|
||||
@@ -215,5 +190,5 @@ patch(AttendeeCalendarCommonRenderer.prototype, {
|
||||
});
|
||||
|
||||
AttendeeCalendarCommonRenderer.WorklocationTemplate = "hr.homeworking.CalendarCommonRenderer.worklocation";
|
||||
AttendeeCalendarCommonRenderer.HeaderCalendarTemplate = "hr.homeworking.CalendarCommonRenderer.buttonWorklocation";
|
||||
AttendeeCalendarCommonRenderer.ButtonWorklocationTemplate = "hr.homeworking.CalendarCommonRenderer.buttonWorklocation";
|
||||
AttendeeCalendarCommonRenderer.headerTemplate = "hr_homeworking.CalendarCommonRendererHeader";
|
||||
|
||||
@@ -31,6 +31,12 @@
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-name="hr_homeworking.CalendarCommonRendererHeader" t-inherit="web.CalendarCommonRendererHeader" t-inherit-mode="primary">
|
||||
<xpath expr="//span[hasclass('o_cw_day_number')]" position="after">
|
||||
<t t-if="scale != 'month'" t-call="hr.homeworking.CalendarCommonRenderer.buttonWorklocation"/>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
<t t-name="hr.homeworking.CalendarCommonRenderer.buttonWorklocation">
|
||||
<div class="btnWorklocation w-100">
|
||||
<button class='rounded-pill o_month text-nowrap bg-info-light'>
|
||||
|
||||
@@ -83,6 +83,7 @@ export class CalendarCommonRenderer extends Component {
|
||||
eventDrop: this.onEventDrop,
|
||||
eventLimit: this.props.model.eventLimit,
|
||||
eventLimitClick: this.onEventLimitClick,
|
||||
eventLimitText: this.env.isSmall ? "" : "more",
|
||||
eventMouseEnter: this.onEventMouseEnter,
|
||||
eventMouseLeave: this.onEventMouseLeave,
|
||||
eventRender: this.onEventRender,
|
||||
@@ -113,6 +114,7 @@ export class CalendarCommonRenderer extends Component {
|
||||
weekNumbers: true,
|
||||
weekNumbersWithinDays: !this.env.isSmall,
|
||||
windowResize: this.onWindowResizeDebounced,
|
||||
columnHeaderHtml: this.getHeaderHtml,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -175,7 +177,12 @@ export class CalendarCommonRenderer extends Component {
|
||||
);
|
||||
}
|
||||
updateSize() {
|
||||
const height = window.innerHeight - this.fc.el.getBoundingClientRect().top;
|
||||
let headerHeight = 0;
|
||||
if (!this.env.isSmall) {
|
||||
headerHeight =
|
||||
document.querySelector(".o_calendar_header")?.getBoundingClientRect()?.height ?? 0;
|
||||
}
|
||||
const height = window.innerHeight - this.fc.el.getBoundingClientRect().top - headerHeight;
|
||||
this.fc.el.style.height = `${height}px`;
|
||||
this.fc.api.updateSize();
|
||||
}
|
||||
@@ -205,6 +212,7 @@ export class CalendarCommonRenderer extends Component {
|
||||
}
|
||||
onEventClick(info) {
|
||||
this.click(info);
|
||||
info.el.classList.toggle("o_calendar_popover_open");
|
||||
}
|
||||
onEventRender(info) {
|
||||
const { el, event } = info;
|
||||
@@ -310,9 +318,25 @@ export class CalendarCommonRenderer extends Component {
|
||||
onWindowResize() {
|
||||
this.updateSize();
|
||||
}
|
||||
|
||||
getHeaderHtml(date) {
|
||||
const scale = this.props.model.scale;
|
||||
const {
|
||||
weekdayShort: weekdayShort,
|
||||
weekdayLong: weekdayLong,
|
||||
day,
|
||||
} = DateTime.fromJSDate(date);
|
||||
return renderToString(this.constructor.headerTemplate, {
|
||||
weekdayShort,
|
||||
weekdayLong,
|
||||
day,
|
||||
scale,
|
||||
});
|
||||
}
|
||||
}
|
||||
CalendarCommonRenderer.components = {
|
||||
Popover: CalendarCommonPopover,
|
||||
};
|
||||
CalendarCommonRenderer.template = "web.CalendarCommonRenderer";
|
||||
CalendarCommonRenderer.eventTemplate = "web.CalendarCommonRenderer.event";
|
||||
CalendarCommonRenderer.headerTemplate = "web.CalendarCommonRendererHeader";
|
||||
|
||||
@@ -15,4 +15,8 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="web.CalendarCommonRendererHeader">
|
||||
<span class="o_cw_day_name" t-esc="scale == 'day' ? weekdayLong : weekdayShort "/> <span t-if="scale != 'month'" class="fc-day-number o_cw_day_number" t-esc="day"/>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -67,7 +67,7 @@ QUnit.module("CalendarView - CommonRenderer", ({ beforeEach }) => {
|
||||
QUnit.test("Day: check date", async (assert) => {
|
||||
await start({ model: { scale: "day" } });
|
||||
assert.containsOnce(target, ".fc-day-header");
|
||||
assert.strictEqual(target.querySelector(".fc-day-header").textContent, "July 16, 2021");
|
||||
assert.strictEqual(target.querySelector(".fc-day-header").textContent, "Fri 16");
|
||||
});
|
||||
|
||||
QUnit.test("Day: click all day slot", async (assert) => {
|
||||
|
||||
@@ -3994,7 +3994,7 @@ QUnit.module("Views", ({ beforeEach }) => {
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_calendar_renderer .fc-day-header").textContent,
|
||||
"January 30, 2016",
|
||||
"Sat 30",
|
||||
"should display day passed in the context"
|
||||
);
|
||||
});
|
||||
@@ -4030,12 +4030,12 @@ QUnit.module("Views", ({ beforeEach }) => {
|
||||
const dayHeaders = target.querySelectorAll(".fc-day-header");
|
||||
assert.strictEqual(
|
||||
dayHeaders[0].textContent,
|
||||
"Sunday",
|
||||
"Sun 1",
|
||||
"The first day of the week should be Sunday"
|
||||
);
|
||||
assert.strictEqual(
|
||||
dayHeaders[dayHeaders.length - 1].textContent,
|
||||
"Saturday",
|
||||
"Sat 7",
|
||||
"The last day of the week should be Saturday"
|
||||
);
|
||||
|
||||
@@ -4085,12 +4085,12 @@ QUnit.module("Views", ({ beforeEach }) => {
|
||||
const dayHeaders = target.querySelectorAll(".fc-day-header");
|
||||
assert.strictEqual(
|
||||
dayHeaders[0].textContent,
|
||||
"Monday",
|
||||
"Mon 26",
|
||||
"The first day of the week should be Monday"
|
||||
);
|
||||
assert.strictEqual(
|
||||
dayHeaders[dayHeaders.length - 1].textContent,
|
||||
"Sunday",
|
||||
"Sun 1",
|
||||
"The last day of the week should be Sunday"
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user