[IMP] web, calendar: make odoo calendar google again

This PR change generic calendar view to make it behave more like
google calendar.

These changes includes :
- Scss rules for small event(<15 minutes) so that the title is always readable.
  On top of that, the top border of a darker color has been removed
  for these even to allow more space.

- Event longer than 24h are now shown in the all day column independent
  of their 'allday' status

- Time is now added in the event description, if the event is
 shorter than 30 minutes, only the start time is displayed at the end
 of the line.
 If the event is longer, the start time and end time are displayed
 on a new line.

task-id : 3114155

closes odoo/odoo#109736

Signed-off-by: Michaël Mattiello <mcm@odoo.com>
This commit is contained in:
Florian Damhaut
2023-02-02 15:02:04 +01:00
parent 1f022636f0
commit 5c3589ebe7
4 changed files with 68 additions and 23 deletions
@@ -5,6 +5,12 @@
<div t-attf-class="#{attendeeStatus != 'accepted' ? 'fw-normal' : ''} o_event_title me-2">
<span t-if="isAlone" class="fa fa-exclamation-circle"/>
<t t-esc="title"/>
<t t-if="!isAllDay">
<t t-if="24 > duration and duration > 0.5">
<br/><t t-esc="startTime"/> - <t t-esc="endTime"/>
</t>
<t t-else="">, <t t-esc="startTime"/></t>
</t>
</div>
<span t-if="!isTimeHidden" class="fc-time"/>
</div>
@@ -115,6 +115,11 @@ export class CalendarCommonRenderer extends Component {
return record.start.toFormat(timeFormat);
}
getEndTime(record) {
const timeFormat = is24HourFormat() ? "HH:mm" : "hh:mm a";
return record.end.toFormat(timeFormat);
}
computeEventSelector(event) {
return `[data-event-id="${event.id}"]`;
}
@@ -132,15 +137,17 @@ export class CalendarCommonRenderer extends Component {
return Object.values(this.props.model.records).map((r) => this.convertRecordToEvent(r));
}
convertRecordToEvent(record) {
const allDay = record.isAllDay || record.end.diff(record.start, "hours").hours >= 24;
return {
id: record.id,
title: record.title,
start: record.start.toISO(),
end:
["week", "month"].includes(this.props.model.scale) && record.isAllDay
["week", "month"].includes(this.props.model.scale) && (record.isAllDay || allDay &&
record.end.toMillis() !== record.end.startOf('day').toMillis())
? record.end.plus({ days: 1 }).toISO()
: record.end.toISO(),
allDay: record.isAllDay,
allDay: allDay ,
};
}
getPopoverProps(record) {
@@ -202,6 +209,7 @@ export class CalendarCommonRenderer extends Component {
const injectedContentStr = renderToString(this.constructor.eventTemplate, {
...record,
startTime: this.getStartTime(record),
endTime: this.getEndTime(record),
});
const domParser = new DOMParser();
const { children } = domParser.parseFromString(injectedContentStr, "text/html").body;
@@ -222,6 +230,9 @@ export class CalendarCommonRenderer extends Component {
if (record.isStriked) {
el.classList.add("o_event_striked");
}
if (record.duration <= 0.25 ) {
el.classList.add("o_event_oneliner");
}
}
if (!el.querySelector(".fc-bg")) {
@@ -193,7 +193,7 @@
// Prevent events with similar color to visually overlap each other
box-shadow: 0 0 0 1px white;
&.fc-event:not(.fc-h-event) {
&.fc-event:not(.fc-h-event):not(.o_event_oneliner) {
border-width: 3px 0 0;
&.fc-not-start {
@@ -204,6 +204,16 @@
}
}
}
&.o_event_oneliner {
padding-top: 0;
padding-bottom: 0;
.fc-content {
margin-top: 0;
font-size: 11px !important;
line-height: 0.9;
}
}
}
// Reset position to keep the "nowIndicator" line visible
@@ -104,6 +104,7 @@ QUnit.module("Views", ({ beforeEach }) => {
start: { string: "start datetime", type: "datetime" },
stop: { string: "stop datetime", type: "datetime" },
delay: { string: "delay", type: "float" },
duration: { string: "Duration", type:"float", default: 1},
allday: { string: "allday", type: "boolean" },
partner_ids: {
string: "attendees",
@@ -306,7 +307,7 @@ QUnit.module("Views", ({ beforeEach }) => {
QUnit.module("CalendarView");
QUnit.test(`simple calendar rendering`, async (assert) => {
assert.expect(24);
assert.expect(25);
serverData.models.event.records.push({
id: 8,
@@ -318,6 +319,17 @@ QUnit.module("Views", ({ beforeEach }) => {
allday: false,
partner_ids: [2],
type: 1,
}, {
id: 9,
user_id: uid,
partner_id: false,
name: "event 8",
start: "2016-12-11 05:15:00",
stop: "2016-12-11 05:30:00",
allday: false,
partner_ids: [1, 2, 3],
duration: 0.25,
type: 1,
});
await makeView({
@@ -325,10 +337,11 @@ QUnit.module("Views", ({ beforeEach }) => {
resModel: "event",
serverData,
arch: `
<calendar event_open_popup="1" date_start="start" date_stop="stop" all_day="allday" mode="week" attendee="partner_ids" color="partner_id">
<calendar event_open_popup="1" date_start="start" date_stop="stop" all_day="allday" mode="week" attendee="partner_ids" color="partner_id" date_delay="duration">
<filter name="user_id" avatar_field="image" />
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" />
<field name="partner_id" filters="1" invisible="1" />
<field name="duration" invisible="1"/>
</calendar>
`,
});
@@ -350,8 +363,8 @@ QUnit.module("Views", ({ beforeEach }) => {
assert.containsN(
target,
".fc-event",
9,
"should display 9 events on the week (4 event + 5 days event)"
6,
"should display 6 events on the week (4 event + 1 allday + 1 >24h allday)"
);
assert.containsN(
target,
@@ -359,6 +372,13 @@ QUnit.module("Views", ({ beforeEach }) => {
7,
"week scale should highlight 7 days in mini calendar"
);
assert.containsOnce(
target,
".o_event_oneliner",
"should contain 1 oneliner event (the one we add)"
);
await click(target, ".scale_button_selection");
await click(target, ".o_scale_button_day"); // display only one day
assert.containsN(target, ".fc-event", 2, "should display 2 events on the day");
@@ -378,8 +398,8 @@ QUnit.module("Views", ({ beforeEach }) => {
assert.containsN(
target,
".fc-event",
7,
"should display 7 events on the month (5 events + 2 week event - 1 'event 6' is filtered + 1 'Undefined event')"
8,
"should display 7 events on the month (6 events + 2 week event - 1 'event 6' is filtered + 1 'Undefined event')"
);
assert.containsN(
target,
@@ -439,11 +459,11 @@ QUnit.module("Views", ({ beforeEach }) => {
assert.containsN(
target,
".fc-event",
7,
8,
"should display 7 events ('event 5' counts for 2 because it spans two weeks and thus generate two fc-event elements)"
);
await click(target.querySelectorAll(".o_calendar_filter input[type=checkbox]")[1]); // click on partner 2
assert.containsN(target, ".fc-event", 4, "should now only display 4 event");
assert.containsN(target, ".fc-event", 5, "should now only display 5 event");
await click(target.querySelectorAll(".o_calendar_filter input[type=checkbox]")[2]);
assert.containsNone(target, ".fc-event", "should not display any event anymore");
// test search bar in filter
@@ -2131,8 +2151,8 @@ QUnit.module("Views", ({ beforeEach }) => {
assert.containsN(
target,
".fc-event",
9,
"should display 9 events on the week (4 event + 5 days event)"
5,
"should display 5 events on the week (4 event + 1 >24h event)"
);
await pickDate(target, "2016-12-19");
@@ -2141,8 +2161,8 @@ QUnit.module("Views", ({ beforeEach }) => {
assert.containsN(
target,
".fc-event",
4,
"should display 4 events on the week (1 event + 3 days event)"
2,
"should display 4 events on the week (1 event + 1 >24h event)"
);
// Clicking on a day in the same week should switch to that particular day view
@@ -2165,8 +2185,8 @@ QUnit.module("Views", ({ beforeEach }) => {
assert.containsN(
target,
".fc-event",
4,
"should display 4 events on the week (1 event + 3 days event)"
2,
"should display 4 events on the week (1 event + 1 >24h event)"
);
await pickDate(target, "2016-12-18");
@@ -2566,11 +2586,11 @@ QUnit.module("Views", ({ beforeEach }) => {
assert.containsN(target, ".fc-event", 4, "should display 4 events on the week");
await toggleFilter(target, "partner_ids", 2);
assert.containsN(target, ".fc-event", 9, "should display 9 events on the week");
assert.containsN(target, ".fc-event", 5, "should display 5 events on the week");
// Click on the "all" filter to reload all events
await toggleFilter(target, "partner_ids", "all");
assert.containsN(target, ".fc-event", 9, "should display 9 events on the week");
assert.containsN(target, ".fc-event", 5, "should display 5 events on the week");
});
QUnit.test(`Add filters and specific color`, async (assert) => {
@@ -2707,7 +2727,6 @@ QUnit.module("Views", ({ beforeEach }) => {
assert.hasClass(findEvent(target, 2), "o_calendar_color_0");
assert.hasClass(findEvent(target, 3), "o_calendar_color_0");
assert.hasClass(findEvent(target, 4), "o_calendar_color_0");
assert.hasClass(findEvent(target, 5), "o_calendar_color_0");
assert.containsOnce(target, ".o_calendar_filter[data-name=user_id]");
assert.containsNone(
findFilterPanelSection(target, "user_id"),
@@ -2806,7 +2825,6 @@ QUnit.module("Views", ({ beforeEach }) => {
assert.hasClass(findEvent(target, 2), "o_calendar_color_-1"); // uid = -1 ...
assert.hasClass(findEvent(target, 3), "o_calendar_color_4");
assert.hasClass(findEvent(target, 4), "o_calendar_color_-1"); // uid = -1 ...
assert.hasClass(findEvent(target, 5), "o_calendar_color_4");
assert.containsNone(
findFilterPanelSection(target, "partner_id"),
"[class*='o_cw_filter_color_']"
@@ -3059,7 +3077,7 @@ QUnit.module("Views", ({ beforeEach }) => {
await toggleFilter(target, "partner_id", 4);
await toggleFilter(target, "partner_ids", 2);
assert.containsN(target, ".fc-event", 11, "should display all records");
assert.containsN(target, ".fc-event", 7, "should display all records");
});
QUnit.test(`create event with filters (no quickCreate)`, async (assert) => {
@@ -4286,7 +4304,7 @@ QUnit.module("Views", ({ beforeEach }) => {
await resizeEventToTime(target, 8, "2016-12-14 08:00:00");
const event = findEvent(target, 8);
assert.strictEqual(event.textContent, "foobar");
assert.notOk(event.closest(".fc-day-grid"), "event should not be in the all day slots");
assert.ok(event.closest(".fc-day-grid"), "event should be in the all day slots");
});
QUnit.test(`correctly display year view`, async (assert) => {