[IMP] web: move views' buttons into the view itself
Were previously in the ControlPanel. Part-of: odoo/odoo#116641
This commit is contained in:
committed by
Pierre Paridans
parent
caef16ee4e
commit
c8ca9da7bc
@@ -3,7 +3,7 @@
|
||||
<t t-name="ImportAction" owl="1">
|
||||
<div class="h-100 d-flex flex-column">
|
||||
<Layout className="'o_import_action d-flex h-100 overflow-auto bg-white'" display="display">
|
||||
<t t-set-slot="layout-buttons">
|
||||
<t t-set-slot="control-panel-create-button">
|
||||
<t t-if="isPreviewing">
|
||||
<button t-if="!state.isPaused" type="button" class="btn btn-primary m-1" t-on-click="() => this.handleImport(false)">Import</button>
|
||||
<button t-else="" type="button" class="btn btn-primary m-1" t-on-click="() => this.handleImport(false)">Resume</button>
|
||||
|
||||
@@ -20,13 +20,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</DatePicker>
|
||||
</t>
|
||||
|
||||
<t t-name="calendar.AttendeeCalendarController.controlButtons" t-inherit="web.CalendarController.controlButtons" owl="1">
|
||||
<xpath expr="//span[hasclass('o_calendar_navigation_buttons')]" position="before">
|
||||
<span class="o_calendar_create_buttons">
|
||||
<xpath expr="//Layout" position="inside">
|
||||
<t t-set-slot="control-panel-create-button">
|
||||
<button class="btn btn-primary o-calendar-button-new me-1" t-on-click="onClickAddButton">New</button>
|
||||
</span>
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -11,7 +11,6 @@ export const attendeeCalendarView = {
|
||||
Controller: AttendeeCalendarController,
|
||||
Model: AttendeeCalendarModel,
|
||||
Renderer: AttendeeCalendarRenderer,
|
||||
buttonTemplate: "calendar.AttendeeCalendarController.controlButtons",
|
||||
};
|
||||
|
||||
registry.category("views").add("attendee_calendar", attendeeCalendarView);
|
||||
|
||||
@@ -111,7 +111,6 @@ export class TimeOffCalendarController extends CalendarController {
|
||||
});
|
||||
}
|
||||
}
|
||||
TimeOffCalendarController.template = "hr_holidays.CalendarController";
|
||||
TimeOffCalendarController.components = {
|
||||
...TimeOffCalendarController.components,
|
||||
FilterPanel: TimeOffCalendarFilterPanel,
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="hr_holidays.CalendarController" t-inherit="web.CalendarController" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//DatePicker" position="replace"/>
|
||||
</t>
|
||||
|
||||
<t t-name="hr_holidays.CalendarController.controlButtons" t-inherit="web.CalendarController.controlButtons" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//ViewScaleSelector" position="after">
|
||||
<xpath expr="." position="inside">
|
||||
<span class="o_timeoff_buttons">
|
||||
<button class="btn btn-primary btn-time-off mx-1" t-on-click="newTimeOffRequest" type="button">
|
||||
New Time Off
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
<templates>
|
||||
<t t-name="hr_work_entry_contract.calendar.controlButtons" t-inherit="web.CalendarController.controlButtons" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//ViewScaleSelector" position="after">
|
||||
<xpath expr="." position="inside">
|
||||
<span class="btn-regenerate-work-entries">
|
||||
<button class="btn btn-secondary ms-3" t-on-click="onRegenerateWorkEntries">
|
||||
Regenerate Work Entries
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
<div class="o_control_panel d-flex flex-column gap-3 gap-lg-1 px-3 pt-2 pb-3" t-ref="root">
|
||||
<div class="o_control_panel_main d-flex flex-wrap flex-lg-nowrap justify-content-between align-items-lg-start gap-3">
|
||||
<div class="o_control_panel_breadcrumbs d-flex align-items-center w-100 w-md-50 w-lg-auto">
|
||||
<div class="o_control_panel_create_button d-empty-none d-print-none me-2">
|
||||
<div class="o_control_panel_main_buttons d-flex gap-1 d-empty-none d-print-none me-2">
|
||||
<t t-slot="control-panel-create-button"/>
|
||||
<t t-slot="layout-buttons"/>
|
||||
<t t-slot="control-panel-always-buttons"/>
|
||||
</div>
|
||||
<t t-if="env.config.noBreadcrumbs">
|
||||
@@ -43,9 +44,6 @@
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_control_panel_secondary d-flex gap-2">
|
||||
<t t-slot="layout-buttons"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -49,13 +49,6 @@ export class CalendarCommonRenderer extends Component {
|
||||
useEffect(() => {
|
||||
this.updateSize();
|
||||
});
|
||||
|
||||
useEffect(
|
||||
(view) => {
|
||||
this.env.config.setDisplayName(`${this.props.displayName} (${view.title})`);
|
||||
},
|
||||
() => [this.fc.api.view]
|
||||
);
|
||||
}
|
||||
|
||||
get options() {
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||
import { _lt, _t } from "@web/core/l10n/translation";
|
||||
import { _t, _lt } from "@web/core/l10n/translation";
|
||||
import { useOwnedDialogs, useService } from "@web/core/utils/hooks";
|
||||
import { sprintf } from "@web/core/utils/strings";
|
||||
import { Layout } from "@web/search/layout";
|
||||
import { useModel } from "@web/views/model";
|
||||
import { ViewScaleSelector } from "@web/views/view_components/view_scale_selector";
|
||||
import { FormViewDialog } from "@web/views/view_dialogs/form_view_dialog";
|
||||
import { useSetupView } from "@web/views/view_hook";
|
||||
import { CalendarDatePicker } from "./date_picker/calendar_date_picker";
|
||||
@@ -14,6 +13,7 @@ import { CalendarFilterPanel } from "./filter_panel/calendar_filter_panel";
|
||||
import { CalendarMobileFilterPanel } from "./mobile_filter_panel/calendar_mobile_filter_panel";
|
||||
import { CalendarQuickCreate } from "./quick_create/calendar_quick_create";
|
||||
import { SearchBar } from "@web/search/search_bar/search_bar";
|
||||
import { ViewScaleSelector } from "@web/views/view_components/view_scale_selector";
|
||||
|
||||
import { Component, useState } from "@odoo/owl";
|
||||
|
||||
@@ -64,7 +64,6 @@ export class CalendarController extends Component {
|
||||
createRecord: this.createRecord.bind(this),
|
||||
deleteRecord: this.deleteRecord.bind(this),
|
||||
editRecord: this.editRecord.bind(this),
|
||||
setDate: this.setDate.bind(this),
|
||||
displayName: this.displayName,
|
||||
};
|
||||
}
|
||||
@@ -90,11 +89,7 @@ export class CalendarController extends Component {
|
||||
toggleSideBar: () => (this.state.showSideBar = !this.state.showSideBar),
|
||||
};
|
||||
}
|
||||
get scales() {
|
||||
return Object.fromEntries(
|
||||
this.model.scales.map((s) => [s, { description: SCALE_LABELS[s] }])
|
||||
);
|
||||
}
|
||||
|
||||
get showCalendar() {
|
||||
return !this.env.isSmall || !this.state.showSideBar;
|
||||
}
|
||||
@@ -107,29 +102,6 @@ export class CalendarController extends Component {
|
||||
return this.props.className;
|
||||
}
|
||||
|
||||
getTodayDay() {
|
||||
return luxon.DateTime.local().day;
|
||||
}
|
||||
|
||||
async setDate(move) {
|
||||
let date = null;
|
||||
switch (move) {
|
||||
case "next":
|
||||
date = this.model.date.plus({ [`${this.model.scale}s`]: 1 });
|
||||
break;
|
||||
case "previous":
|
||||
date = this.model.date.minus({ [`${this.model.scale}s`]: 1 });
|
||||
break;
|
||||
case "today":
|
||||
date = luxon.DateTime.local().startOf("day");
|
||||
break;
|
||||
}
|
||||
await this.model.load({ date });
|
||||
}
|
||||
async setScale(scale) {
|
||||
await this.model.load({ scale });
|
||||
}
|
||||
|
||||
getQuickCreateProps(record) {
|
||||
return {
|
||||
record,
|
||||
@@ -214,6 +186,31 @@ export class CalendarController extends Component {
|
||||
},
|
||||
});
|
||||
}
|
||||
async setDate(move) {
|
||||
let date = null;
|
||||
switch (move) {
|
||||
case "next":
|
||||
date = this.model.date.plus({ [`${this.model.scale}s`]: 1 });
|
||||
break;
|
||||
case "previous":
|
||||
date = this.model.date.minus({ [`${this.model.scale}s`]: 1 });
|
||||
break;
|
||||
case "today":
|
||||
date = luxon.DateTime.local().startOf("day");
|
||||
break;
|
||||
}
|
||||
await this.model.load({ date });
|
||||
}
|
||||
|
||||
get scales() {
|
||||
return Object.fromEntries(
|
||||
this.model.scales.map((s) => [s, { description: SCALE_LABELS[s] }])
|
||||
);
|
||||
}
|
||||
|
||||
async setScale(scale) {
|
||||
await this.model.load({ scale });
|
||||
}
|
||||
}
|
||||
CalendarController.components = {
|
||||
DatePicker: CalendarDatePicker,
|
||||
@@ -221,7 +218,7 @@ CalendarController.components = {
|
||||
MobileFilterPanel: CalendarMobileFilterPanel,
|
||||
QuickCreate: CalendarQuickCreate,
|
||||
Layout,
|
||||
ViewScaleSelector,
|
||||
SearchBar,
|
||||
ViewScaleSelector,
|
||||
};
|
||||
CalendarController.template = "web.CalendarController";
|
||||
|
||||
@@ -13,6 +13,15 @@
|
||||
<div class="o_calendar_container">
|
||||
<MobileFilterPanel t-if="env.isSmall" t-props="mobileFilterPanelProps" />
|
||||
<div t-if="showSideBar" class="o_calendar_sidebar_container d-md-block">
|
||||
<div class="d-flex justify-content-between">
|
||||
<button
|
||||
class="btn btn-secondary mx-1 o_calendar_button_today"
|
||||
t-on-click.stop="() => this.setDate('today')"
|
||||
>
|
||||
Today
|
||||
</button>
|
||||
<ViewScaleSelector scales="scales" currentScale="model.scale" setScale.bind="setScale" />
|
||||
</div>
|
||||
<div class="o_calendar_sidebar">
|
||||
<DatePicker t-if="!env.isSmall" t-props="datePickerProps" />
|
||||
<FilterPanel t-props="filterPanelProps" />
|
||||
@@ -24,45 +33,6 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="web.CalendarController.controlButtons" owl="1">
|
||||
<div class="o_calendar_buttons d-flex align-items-center mb-2">
|
||||
<ViewScaleSelector scales="scales" currentScale="model.scale" setScale.bind="setScale" />
|
||||
<span t-if="!env.isSmall" class="o_calendar_navigation_buttons mx-1">
|
||||
<button
|
||||
class="btn btn-secondary mx-1 o_calendar_button_today"
|
||||
t-on-click.stop="() => this.setDate('today')"
|
||||
>
|
||||
Today
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-text pe-2 o_calendar_button_prev"
|
||||
title="Previous"
|
||||
aria-label="Previous"
|
||||
t-on-click.stop="() => this.setDate('previous')"
|
||||
>
|
||||
<i class="fa oi-larger fa-angle-left" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-text ps-1 o_calendar_button_next"
|
||||
title="Next"
|
||||
aria-label="Next"
|
||||
t-on-click.stop="() => this.setDate('next')"
|
||||
>
|
||||
<i class="fa oi-larger fa-angle-right" />
|
||||
</button>
|
||||
<t t-log="this"></t>
|
||||
</span>
|
||||
|
||||
<!-- POC -->
|
||||
<span class="fs-3">April 2023</span>
|
||||
|
||||
<button t-if="env.isSmall" class="o_cp_today_button btn btn-sm btn-link" t-on-click="() => this.setDate('today')">
|
||||
<span class="fa-stack o_calendar_button_today">
|
||||
<i class="fa fa-calendar-o fa-stack-2x" role="img" aria-label="Today" title="Today" />
|
||||
<strong class="o_calendar_text fa-stack-1x" t-esc="getTodayDay()" />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="web.CalendarController.controlButtons" owl="1"/>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { formatDate } from "@web/core/l10n/dates";
|
||||
import { localization } from "@web/core/l10n/localization";
|
||||
import { useDebounced } from "@web/core/utils/timing";
|
||||
import { getColor } from "../colors";
|
||||
import { useCalendarPopover, useFullCalendar } from "../hooks";
|
||||
import { CalendarYearPopover } from "./calendar_year_popover";
|
||||
|
||||
import { Component, useEffect, useRef, onRendered } from "@odoo/owl";
|
||||
import { Component, useEffect, useRef } from "@odoo/owl";
|
||||
|
||||
export class CalendarYearRenderer extends Component {
|
||||
setup() {
|
||||
@@ -26,11 +25,6 @@ export class CalendarYearRenderer extends Component {
|
||||
useEffect(() => {
|
||||
this.updateSize();
|
||||
});
|
||||
|
||||
onRendered(() => {
|
||||
const year = formatDate(this.props.model.date, { format: "yyyy" });
|
||||
this.env.config.setDisplayName(`${this.props.displayName} (${year})`);
|
||||
});
|
||||
}
|
||||
|
||||
get options() {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { Layout } from "@web/search/layout";
|
||||
import { GroupByMenu } from "@web/search/group_by_menu/group_by_menu";
|
||||
import { useModel } from "@web/views/model";
|
||||
@@ -14,7 +11,6 @@ import { Component, useRef } from "@odoo/owl";
|
||||
|
||||
export class GraphController extends Component {
|
||||
setup() {
|
||||
this.actionService = useService("action");
|
||||
this.model = useModel(this.props.Model, this.props.modelParams);
|
||||
|
||||
useSetupView({
|
||||
@@ -46,90 +42,10 @@ export class GraphController extends Component {
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the action to open the view on the current model.
|
||||
*
|
||||
* @param {Array} domain
|
||||
* @param {Array} views
|
||||
* @param {Object} context
|
||||
*/
|
||||
openView(domain, views, context) {
|
||||
this.actionService.doAction(
|
||||
{
|
||||
context,
|
||||
domain,
|
||||
name: this.model.metaData.title,
|
||||
res_model: this.model.metaData.resModel,
|
||||
target: "current",
|
||||
type: "ir.actions.act_window",
|
||||
views,
|
||||
},
|
||||
{
|
||||
viewType: "list",
|
||||
}
|
||||
);
|
||||
}
|
||||
/**
|
||||
* @param {string} domain the domain of the clicked area
|
||||
*/
|
||||
onGraphClicked(domain) {
|
||||
const { context } = this.model.metaData;
|
||||
|
||||
Object.keys(context).forEach((x) => {
|
||||
if (x === "group_by" || x.startsWith("search_default_")) {
|
||||
delete context[x];
|
||||
}
|
||||
});
|
||||
|
||||
const views = {};
|
||||
for (const [viewId, viewType] of this.env.config.views || []) {
|
||||
views[viewType] = viewId;
|
||||
}
|
||||
function getView(viewType) {
|
||||
return [views[viewType] || false, viewType];
|
||||
}
|
||||
const actionViews = [getView("list"), getView("form")];
|
||||
this.openView(domain, actionViews, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} param0
|
||||
* @param {string} param0.measure
|
||||
*/
|
||||
onMeasureSelected({ measure }) {
|
||||
this.model.updateMetaData({ measure });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {"bar"|"line"|"pie"} mode
|
||||
*/
|
||||
onModeSelected(mode) {
|
||||
this.model.updateMetaData({ mode });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {"ASC"|"DESC"} order
|
||||
*/
|
||||
toggleOrder(order) {
|
||||
const { order: currentOrder } = this.model.metaData;
|
||||
const nextOrder = currentOrder === order ? null : order;
|
||||
this.model.updateMetaData({ order: nextOrder });
|
||||
}
|
||||
|
||||
toggleStacked() {
|
||||
const { stacked } = this.model.metaData;
|
||||
this.model.updateMetaData({ stacked: !stacked });
|
||||
}
|
||||
|
||||
toggleCumulated() {
|
||||
const { cumulated } = this.model.metaData;
|
||||
this.model.updateMetaData({ cumulated: !cumulated });
|
||||
}
|
||||
}
|
||||
|
||||
GraphController.template = "web.GraphView";
|
||||
GraphController.components = { Dropdown, DropdownItem, Layout, GroupByMenu, SearchBar };
|
||||
GraphController.components = { Layout, GroupByMenu, SearchBar };
|
||||
|
||||
GraphController.props = {
|
||||
...standardViewProps,
|
||||
|
||||
@@ -53,9 +53,6 @@
|
||||
<t t-name="web.GraphView" owl="1">
|
||||
<div t-att-class="props.className" t-ref="root">
|
||||
<Layout className="model.useSampleModel ? 'o_view_sample_data' : ''" display="props.display">
|
||||
<t t-set-slot="layout-buttons">
|
||||
<t t-call="{{ props.buttonTemplate }}"/>
|
||||
</t>
|
||||
<t t-set-slot="layout-actions">
|
||||
<SearchBar/>
|
||||
</t>
|
||||
@@ -63,7 +60,7 @@
|
||||
<t t-if="model.useSampleModel and props.info.noContentHelp" t-call="web.ActionHelper">
|
||||
<t t-set="noContentHelp" t-value="props.info.noContentHelp"/>
|
||||
</t>
|
||||
<t t-component="props.Renderer" model="model" onGraphClicked.bind="onGraphClicked" />
|
||||
<t t-component="props.Renderer" model="model" buttonTemplate="props.buttonTemplate" />
|
||||
</t>
|
||||
<t t-else="" t-call="web.NoContentHelper">
|
||||
<t t-set="title">Invalid data</t>
|
||||
|
||||
@@ -10,6 +10,8 @@ import { renderToString } from "@web/core/utils/render";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
import { Component, onWillUnmount, useEffect, useRef, onWillStart } from "@odoo/owl";
|
||||
import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||
|
||||
const NO_DATA = _lt("No data");
|
||||
|
||||
@@ -49,6 +51,7 @@ export class GraphRenderer extends Component {
|
||||
this.canvasRef = useRef("canvas");
|
||||
this.containerRef = useRef("container");
|
||||
this.cookies = useService("cookie");
|
||||
this.actionService = useService("action");
|
||||
|
||||
this.chart = null;
|
||||
this.tooltip = null;
|
||||
@@ -450,15 +453,8 @@ export class GraphRenderer extends Component {
|
||||
* @returns {Object}
|
||||
*/
|
||||
getScaleOptions() {
|
||||
const {
|
||||
allIntegers,
|
||||
fields,
|
||||
groupBy,
|
||||
measure,
|
||||
measures,
|
||||
mode,
|
||||
stacked,
|
||||
} = this.model.metaData;
|
||||
const { allIntegers, fields, groupBy, measure, measures, mode, stacked } =
|
||||
this.model.metaData;
|
||||
if (mode === "pie") {
|
||||
return {};
|
||||
}
|
||||
@@ -558,7 +554,7 @@ export class GraphRenderer extends Component {
|
||||
const { _datasetIndex, _index } = activeElement;
|
||||
const { domains } = this.chart.data.datasets[_datasetIndex];
|
||||
if (domains) {
|
||||
this.props.onGraphClicked(domains[_index]);
|
||||
this.onGraphClickedFinal(domains[_index]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -674,7 +670,88 @@ export class GraphRenderer extends Component {
|
||||
// advancing the animation service.
|
||||
Chart.animationService.advance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the action to open the view on the current model.
|
||||
*
|
||||
* @param {Array} domain
|
||||
* @param {Array} views
|
||||
* @param {Object} context
|
||||
*/
|
||||
openView(domain, views, context) {
|
||||
this.actionService.doAction(
|
||||
{
|
||||
context,
|
||||
domain,
|
||||
name: this.model.metaData.title,
|
||||
res_model: this.model.metaData.resModel,
|
||||
target: "current",
|
||||
type: "ir.actions.act_window",
|
||||
views,
|
||||
},
|
||||
{
|
||||
viewType: "list",
|
||||
}
|
||||
);
|
||||
}
|
||||
/**
|
||||
* @param {string} domain the domain of the clicked area
|
||||
*/
|
||||
onGraphClickedFinal(domain) {
|
||||
const { context } = this.model.metaData;
|
||||
|
||||
Object.keys(context).forEach((x) => {
|
||||
if (x === "group_by" || x.startsWith("search_default_")) {
|
||||
delete context[x];
|
||||
}
|
||||
});
|
||||
|
||||
const views = {};
|
||||
for (const [viewId, viewType] of this.env.config.views || []) {
|
||||
views[viewType] = viewId;
|
||||
}
|
||||
function getView(viewType) {
|
||||
return [views[viewType] || false, viewType];
|
||||
}
|
||||
const actionViews = [getView("list"), getView("form")];
|
||||
this.openView(domain, actionViews, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} param0
|
||||
* @param {string} param0.measure
|
||||
*/
|
||||
onMeasureSelected({ measure }) {
|
||||
this.model.updateMetaData({ measure });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {"bar"|"line"|"pie"} mode
|
||||
*/
|
||||
onModeSelected(mode) {
|
||||
this.model.updateMetaData({ mode });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {"ASC"|"DESC"} order
|
||||
*/
|
||||
toggleOrder(order) {
|
||||
const { order: currentOrder } = this.model.metaData;
|
||||
const nextOrder = currentOrder === order ? null : order;
|
||||
this.model.updateMetaData({ order: nextOrder });
|
||||
}
|
||||
|
||||
toggleStacked() {
|
||||
const { stacked } = this.model.metaData;
|
||||
this.model.updateMetaData({ stacked: !stacked });
|
||||
}
|
||||
|
||||
toggleCumulated() {
|
||||
const { cumulated } = this.model.metaData;
|
||||
this.model.updateMetaData({ cumulated: !cumulated });
|
||||
}
|
||||
}
|
||||
|
||||
GraphRenderer.template = "web.GraphRenderer";
|
||||
GraphRenderer.props = ["class?", "model", "onGraphClicked"];
|
||||
GraphRenderer.components = { Dropdown, DropdownItem };
|
||||
GraphRenderer.props = ["class?", "model", "buttonTemplate"];
|
||||
|
||||
@@ -28,8 +28,11 @@
|
||||
</t>
|
||||
|
||||
<t t-name="web.GraphRenderer" owl="1">
|
||||
<div t-att-class="'o_graph_renderer o_renderer h-100 ' + props.class" t-ref="root">
|
||||
<div class="o_graph_canvas_container h-100 position-relative" t-ref="container">
|
||||
<div t-att-class="'o_graph_renderer o_renderer h-100 d-flex flex-column border-top ' + props.class" t-ref="root">
|
||||
<div class="d-flex gap-1 mt-2 mx-3 mb-3">
|
||||
<t t-call="{{ props.buttonTemplate }}"/>
|
||||
</div>
|
||||
<div class="o_graph_canvas_container flex-grow-1 position-relative" t-ref="container">
|
||||
<canvas t-ref="canvas" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
New
|
||||
</button>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
<t t-set-slot="layout-buttons">
|
||||
<t t-call="{{ props.buttonTemplate }}"/>
|
||||
</t>
|
||||
<t t-set-slot="control-panel-always-buttons">
|
||||
<t t-foreach="headerButtons" t-as="button" t-key="button.id">
|
||||
<MultiRecordViewButton
|
||||
@@ -26,9 +29,6 @@
|
||||
/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-set-slot="layout-buttons">
|
||||
<t t-call="{{ props.buttonTemplate }}"/>
|
||||
</t>
|
||||
<t t-set-slot="layout-actions">
|
||||
<SearchBar/>
|
||||
</t>
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-set-slot="layout-buttons">
|
||||
<t t-call="{{ props.buttonTemplate }}"/>
|
||||
</t>
|
||||
<t t-set-slot="control-panel-always-buttons">
|
||||
<t t-foreach="archInfo.headerButtons" t-as="button" t-key="button.id">
|
||||
<MultiRecordViewButton
|
||||
@@ -28,12 +31,6 @@
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-set-slot="layout-buttons">
|
||||
<div class="o_cp_buttons" role="toolbar" aria-label="Control panel buttons" t-ref="buttons">
|
||||
<t t-call="{{ props.buttonTemplate }}"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-set-slot="layout-actions">
|
||||
<SearchBar/>
|
||||
</t>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||
import { download } from "@web/core/network/download";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { Layout } from "@web/search/layout";
|
||||
import { useModel } from "@web/views/model";
|
||||
import { standardViewProps } from "@web/views/standard_view_props";
|
||||
@@ -14,7 +10,6 @@ import { Component, useRef } from "@odoo/owl";
|
||||
|
||||
export class PivotController extends Component {
|
||||
setup() {
|
||||
this.actionService = useService("action");
|
||||
this.model = useModel(this.props.Model, this.props.modelParams);
|
||||
|
||||
useSetupView({
|
||||
@@ -36,108 +31,14 @@ export class PivotController extends Component {
|
||||
pivot_row_groupby: this.model.metaData.fullRowGroupBys,
|
||||
};
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Exports the current pivot table data in a xls file. For this, we have to
|
||||
* serialize the current state, then call the server /web/pivot/export_xlsx.
|
||||
* Force a reload before exporting to ensure to export up-to-date data.
|
||||
*/
|
||||
onDownloadButtonClicked() {
|
||||
if (this.model.getTableWidth() > 16384) {
|
||||
throw new Error(
|
||||
this.env._t(
|
||||
"For Excel compatibility, data cannot be exported if there are more than 16384 columns.\n\nTip: try to flip axis, filter further or reduce the number of measures."
|
||||
)
|
||||
);
|
||||
}
|
||||
const table = this.model.exportData();
|
||||
download({
|
||||
url: "/web/pivot/export_xlsx",
|
||||
data: { data: JSON.stringify(table) },
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Expands all groups
|
||||
*/
|
||||
onExpandButtonClicked() {
|
||||
this.model.expandAll();
|
||||
}
|
||||
/**
|
||||
* Flips axis
|
||||
*/
|
||||
onFlipButtonClicked() {
|
||||
this.model.flip();
|
||||
}
|
||||
/**
|
||||
* Toggles the given measure
|
||||
*
|
||||
* @param {Object} param0
|
||||
* @param {string} param0.measure
|
||||
*/
|
||||
onMeasureSelected({ measure }) {
|
||||
this.model.toggleMeasure(measure);
|
||||
}
|
||||
/**
|
||||
* Execute the action to open the view on the current model.
|
||||
*
|
||||
* @param {Array} domain
|
||||
* @param {Array} views
|
||||
* @param {Object} context
|
||||
*/
|
||||
openView(domain, views, context) {
|
||||
this.actionService.doAction({
|
||||
type: "ir.actions.act_window",
|
||||
name: this.model.metaData.title,
|
||||
res_model: this.props.resModel,
|
||||
views: views,
|
||||
view_mode: "list",
|
||||
target: "current",
|
||||
context,
|
||||
domain,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @param {CustomEvent} ev
|
||||
*/
|
||||
onOpenView(cell) {
|
||||
if (cell.value === undefined || this.model.metaData.disableLinking) {
|
||||
return;
|
||||
}
|
||||
|
||||
const context = Object.assign({}, this.model.searchParams.context);
|
||||
Object.keys(context).forEach((x) => {
|
||||
if (x === "group_by" || x.startsWith("search_default_")) {
|
||||
delete context[x];
|
||||
}
|
||||
});
|
||||
|
||||
// retrieve form and list view ids from the action
|
||||
const { views = [] } = this.env.config;
|
||||
this.views = ["list", "form"].map((viewType) => {
|
||||
const view = views.find((view) => view[1] === viewType);
|
||||
return [view ? view[0] : false, viewType];
|
||||
});
|
||||
|
||||
const group = {
|
||||
rowValues: cell.groupId[0],
|
||||
colValues: cell.groupId[1],
|
||||
originIndex: cell.originIndexes[0],
|
||||
};
|
||||
this.openView(this.model.getGroupDomain(group), this.views, context);
|
||||
}
|
||||
}
|
||||
|
||||
PivotController.template = "web.PivotView";
|
||||
PivotController.components = { Dropdown, DropdownItem, Layout, SearchBar };
|
||||
PivotController.components = { Layout, SearchBar };
|
||||
|
||||
PivotController.props = {
|
||||
...standardViewProps,
|
||||
Model: Function,
|
||||
modelParams: Object,
|
||||
Renderer: Function,
|
||||
buttonTemplate: String,
|
||||
};
|
||||
|
||||
@@ -2,26 +2,25 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.PivotView.Buttons" owl="1">
|
||||
<div class="btn-group" role="toolbar" aria-label="Main actions">
|
||||
<t t-call="web.ReportViewMeasures">
|
||||
<t t-set="measures" t-value="model.metaData.measures"/>
|
||||
<t t-set="activeMeasures" t-value="model.metaData.activeMeasures"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="btn-group" role="toolbar" aria-label="Pivot settings">
|
||||
<t t-set="noDataDisplayed" t-value="!model.hasData() || !model.metaData.activeMeasures.length"/>
|
||||
<button class="btn btn-secondary fa fa-exchange o_pivot_flip_button" t-on-click="onFlipButtonClicked" data-tooltip="Flip axis" aria-label="Flip axis" t-attf-disabled="{{noDataDisplayed ? 'disabled' : false}}"/>
|
||||
<button class="btn btn-secondary fa fa-arrows o_pivot_expand_button" t-on-click="onExpandButtonClicked" data-tooltip="Expand all" aria-label="Expand all" t-attf-disabled="{{noDataDisplayed ? 'disabled' : false}}"/>
|
||||
<button class="btn btn-secondary fa fa-download o_pivot_download" t-on-click="onDownloadButtonClicked" data-tooltip="Download xlsx" aria-label="Download xlsx" t-attf-disabled="{{noDataDisplayed ? 'disabled' : false}}"/>
|
||||
<div class="d-flex gap-1 mt-2 mx-3 mb-3">
|
||||
<div class="btn-group" role="toolbar" aria-label="Main actions">
|
||||
<t t-call="web.ReportViewMeasures">
|
||||
<t t-set="measures" t-value="model.metaData.measures"/>
|
||||
<t t-set="activeMeasures" t-value="model.metaData.activeMeasures"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="btn-group" role="toolbar" aria-label="Pivot settings">
|
||||
<t t-set="noDataDisplayed" t-value="!model.hasData() || !model.metaData.activeMeasures.length"/>
|
||||
<button class="btn btn-secondary fa fa-exchange o_pivot_flip_button" t-on-click="onFlipButtonClicked" data-tooltip="Flip axis" aria-label="Flip axis" t-attf-disabled="{{noDataDisplayed ? 'disabled' : false}}"/>
|
||||
<button class="btn btn-secondary fa fa-arrows o_pivot_expand_button" t-on-click="onExpandButtonClicked" data-tooltip="Expand all" aria-label="Expand all" t-attf-disabled="{{noDataDisplayed ? 'disabled' : false}}"/>
|
||||
<button class="btn btn-secondary fa fa-download o_pivot_download" t-on-click="onDownloadButtonClicked" data-tooltip="Download xlsx" aria-label="Download xlsx" t-attf-disabled="{{noDataDisplayed ? 'disabled' : false}}"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="web.PivotView" owl="1">
|
||||
<div t-att-class="props.className" t-ref="root">
|
||||
<Layout className="model.useSampleModel ? 'o_view_sample_data' : ''" display="props.display">
|
||||
<t t-set-slot="layout-buttons">
|
||||
<t t-call="{{ props.buttonTemplate }}"/>
|
||||
</t>
|
||||
<t t-set-slot="layout-actions">
|
||||
<SearchBar/>
|
||||
</t>
|
||||
@@ -38,7 +37,7 @@
|
||||
<t t-else="" t-call="web.NoContentHelper"/>
|
||||
</t>
|
||||
<t t-if="model.hasData() and model.metaData.activeMeasures.length">
|
||||
<t t-component="props.Renderer" model="model" onCellClicked="cell => this.onOpenView(cell)"/>
|
||||
<t t-component="props.Renderer" model="model"/>
|
||||
</t>
|
||||
</Layout>
|
||||
</div>
|
||||
|
||||
@@ -3,15 +3,20 @@
|
||||
import { CheckBox } from "@web/core/checkbox/checkbox";
|
||||
import { localization } from "@web/core/l10n/localization";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||
import { formatPercentage } from "@web/views/fields/formatters";
|
||||
import { PivotGroupByMenu } from "@web/views/pivot/pivot_group_by_menu";
|
||||
import fieldUtils from "web.field_utils";
|
||||
|
||||
import { Component, onWillUpdateProps, useRef } from "@odoo/owl";
|
||||
import { download } from "@web/core/network/download";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
const formatters = registry.category("formatters");
|
||||
|
||||
export class PivotRenderer extends Component {
|
||||
setup() {
|
||||
this.actionService = useService("action");
|
||||
this.model = this.props.model;
|
||||
this.table = this.model.getTable();
|
||||
this.l10n = localization;
|
||||
@@ -71,9 +76,6 @@ export class PivotRenderer extends Component {
|
||||
// Handlers
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
onCellClicked(cell) {
|
||||
this.props.onCellClicked(cell);
|
||||
}
|
||||
/**
|
||||
* Handle the adding of a custom groupby (inside the view, not the searchview).
|
||||
*
|
||||
@@ -148,7 +150,100 @@ export class PivotRenderer extends Component {
|
||||
.querySelectorAll(".o_cell_hover")
|
||||
.forEach((elt) => elt.classList.remove("o_cell_hover"));
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Exports the current pivot table data in a xls file. For this, we have to
|
||||
* serialize the current state, then call the server /web/pivot/export_xlsx.
|
||||
* Force a reload before exporting to ensure to export up-to-date data.
|
||||
*/
|
||||
onDownloadButtonClicked() {
|
||||
if (this.model.getTableWidth() > 16384) {
|
||||
throw new Error(
|
||||
this.env._t(
|
||||
"For Excel compatibility, data cannot be exported if there are more than 16384 columns.\n\nTip: try to flip axis, filter further or reduce the number of measures."
|
||||
)
|
||||
);
|
||||
}
|
||||
const table = this.model.exportData();
|
||||
download({
|
||||
url: "/web/pivot/export_xlsx",
|
||||
data: { data: JSON.stringify(table) },
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Expands all groups
|
||||
*/
|
||||
onExpandButtonClicked() {
|
||||
this.model.expandAll();
|
||||
}
|
||||
/**
|
||||
* Flips axis
|
||||
*/
|
||||
onFlipButtonClicked() {
|
||||
this.model.flip();
|
||||
}
|
||||
/**
|
||||
* Toggles the given measure
|
||||
*
|
||||
* @param {Object} param0
|
||||
* @param {string} param0.measure
|
||||
*/
|
||||
onMeasureSelected({ measure }) {
|
||||
this.model.toggleMeasure(measure);
|
||||
}
|
||||
/**
|
||||
* Execute the action to open the view on the current model.
|
||||
*
|
||||
* @param {Array} domain
|
||||
* @param {Array} views
|
||||
* @param {Object} context
|
||||
*/
|
||||
openView(domain, views, context) {
|
||||
this.actionService.doAction({
|
||||
type: "ir.actions.act_window",
|
||||
name: this.model.metaData.title,
|
||||
res_model: this.model.metaData.resModel,
|
||||
views: views,
|
||||
view_mode: "list",
|
||||
target: "current",
|
||||
context,
|
||||
domain,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @param {CustomEvent} ev
|
||||
*/
|
||||
onOpenView(cell) {
|
||||
if (cell.value === undefined || this.model.metaData.disableLinking) {
|
||||
return;
|
||||
}
|
||||
|
||||
const context = Object.assign({}, this.model.searchParams.context);
|
||||
Object.keys(context).forEach((x) => {
|
||||
if (x === "group_by" || x.startsWith("search_default_")) {
|
||||
delete context[x];
|
||||
}
|
||||
});
|
||||
|
||||
// retrieve form and list view ids from the action
|
||||
const { views = [] } = this.env.config;
|
||||
this.views = ["list", "form"].map((viewType) => {
|
||||
const view = views.find((view) => view[1] === viewType);
|
||||
return [view ? view[0] : false, viewType];
|
||||
});
|
||||
|
||||
const group = {
|
||||
rowValues: cell.groupId[0],
|
||||
colValues: cell.groupId[1],
|
||||
originIndex: cell.originIndexes[0],
|
||||
};
|
||||
this.openView(this.model.getGroupDomain(group), this.views, context);
|
||||
}
|
||||
}
|
||||
PivotRenderer.template = "web.PivotRenderer";
|
||||
PivotRenderer.components = { CheckBox, PivotGroupByMenu };
|
||||
PivotRenderer.props = ["model", "onCellClicked"];
|
||||
PivotRenderer.components = { Dropdown, DropdownItem, CheckBox, PivotGroupByMenu };
|
||||
PivotRenderer.props = ["model"];
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.PivotRenderer" owl="1">
|
||||
<t t-call="web.PivotView.Buttons"/>
|
||||
<div class="o_pivot table-responsive">
|
||||
|
||||
<table
|
||||
@@ -31,7 +32,7 @@
|
||||
o_empty: cell.value === undefined,
|
||||
'cursor-pointer': cell.value !== undefined,
|
||||
'fw-bold': cell.isBold,
|
||||
}" t-on-click="() => this.onCellClicked(cell)"
|
||||
}" t-on-click="() => this.onOpenView(cell)"
|
||||
t-on-mouseover="onMouseEnter" t-on-mouseout="onMouseLeave">
|
||||
<t t-if="cell.value !== undefined">
|
||||
<div t-if="cell.originIndexes.length > 1" class="o_variation" t-att-class="{
|
||||
|
||||
@@ -21,7 +21,6 @@ export const pivotView = {
|
||||
ArchParser: PivotArchParser,
|
||||
SearchModel: PivotSearchModel,
|
||||
searchMenuTypes: ["filter", "groupBy", "comparison", "favorite"],
|
||||
buttonTemplate: "web.PivotView.Buttons",
|
||||
|
||||
props: (genericProps, view) => {
|
||||
const modelParams = {};
|
||||
@@ -57,7 +56,6 @@ export const pivotView = {
|
||||
Model: view.Model,
|
||||
modelParams,
|
||||
Renderer: view.Renderer,
|
||||
buttonTemplate: view.buttonTemplate,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</t>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//Layout/t[@t-set-slot='layout-buttons']" position="replace">
|
||||
<xpath expr="//Layout" position="inside">
|
||||
<t t-set-slot="layout-buttons">
|
||||
<t t-call="{{ props.buttonTemplate }}"/>
|
||||
</t>
|
||||
|
||||
Reference in New Issue
Block a user