Compare commits

..

4 commits

Author SHA1 Message Date
Matt Marcha 41f85f9898 Define module data 2024-09-27 17:01:03 -10:00
Matt Marcha 7a28c70b6e ch2.11 add and remove 2024-09-27 12:22:10 -10:00
Matt Marcha 7f28d797c9 ch2.10 make it extensible 2024-09-27 12:14:22 -10:00
Matt Marcha 089ac45df2 ch2.9 make is generic 2024-09-27 12:10:06 -10:00
13 changed files with 306 additions and 36 deletions

View file

@ -5,11 +5,13 @@ import { registry } from "@web/core/registry";
import { Layout } from "@web/search/layout";
import { useService } from "@web/core/utils/hooks"
import { DashboardItem } from "./dashboard_item/dashboard_item";
import { Piechart } from "./piechart/piechart";
import { Dialog } from "@web/core/dialog/dialog";
import { CheckBox } from "@web/core/checkbox/checkbox";
import { browser } from "@web/core/browser/browser";
class AwesomeDashboard extends Component {
static template = "awesome_dashboard.AwesomeDashboard";
static components = { Layout, DashboardItem, Piechart};
static components = { Layout, DashboardItem};
setup() {
this.display = {
@ -19,6 +21,14 @@ class AwesomeDashboard extends Component {
this.action = useService("action");
this.stats = useState(useService('awesome_dashboard.statistics'));
this.items = registry.category("awesome_dashboard").getAll();
this.dialog = useService("dialog");
this.state = useState({
disabledItems: browser.localStorage.getItem("disabledDashboardItems")?.split(",") || []
});
}
openCustomers() {
@ -33,6 +43,47 @@ class AwesomeDashboard extends Component {
views: [[false, 'tree'],[false, 'form']],
});
}
openConfiguration() {
this.dialog.add(ConfigurationDialog, {
items: this.items,
disabledItems: this.state.disabledItems,
onUpdateConfiguration: this.updateConfiguration.bind(this),
})
}
updateConfiguration(newDisabledItems) {
this.state.disabledItems = newDisabledItems;
}
}
class ConfigurationDialog extends Component {
static template = "awesome_dashboard.ConfigurationDialog";
static components = { Dialog, CheckBox };
static props = ["close", "items", "disabledItems", "onUpdateConfiguration"];
setup() {
this.items = useState(this.props.items.map((item) => {
return {
...item,
enabled: !this.props.disabledItems.includes(item.id),
}
}));
}
done() {
this.props.close();
}
onChange(checked, changedItem) {
changedItem.enabled = checked;
const newDisabledItems = Object.values(this.items).filter(
(item) => !item.enabled
).map((item) => item.id)
browser.localStorage.setItem(
"disabledDashboardItems",
newDisabledItems,
);
this.props.onUpdateConfiguration(newDisabledItems);
}
}
registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard);

View file

@ -7,43 +7,36 @@
<button class="btn btn-primary" t-on-click="openCustomers">Customers</button>
<button class="btn btn-primary" t-on-click="openLeads">Leads</button>
</t>
<t t-set-slot="control-panel-additional-actions">
<button t-on-click="openConfiguration" class="btn p-0 ms-1 border-0">
<i class="fa fa-cog"></i>
</button>
</t>
<div class="d-flex flex-wrap" t-if="stats.isReady">
<DashboardItem>
Number of new orders this month
<div class="fs-1 fw-bold text-success text-center">
<t t-out="stats.nb_new_orders"/>
</div>
</DashboardItem>
<DashboardItem>
Total amount of new orders this month
<div class="fs-1 fw-bold text-success text-center">
<t t-out="stats.total_amount"/>
</div>
</DashboardItem>
<DashboardItem>
Average amount of t-shirt by order this month
<div class="fs-1 fw-bold text-success text-center">
<t t-out="stats.average_quantity"/>
</div>
</DashboardItem>
<DashboardItem>
Number of cancelled orders this month
<div class="fs-1 fw-bold text-success text-center">
<t t-out="stats.nb_cancelled_orders"/>
</div>
</DashboardItem>
<DashboardItem>
Average time for an order to go from new to sent or cancelled
<div class="fs-1 fw-bold text-success text-center">
<t t-out="stats.average_time"/>
</div>
</DashboardItem>
<DashboardItem size='4'>
Shirt orders by size
<Piechart data="stats.orders_by_size" label="'Shirt orders by size'"/>
<t t-foreach="items" t-as="item" t-key="item.id">
<DashboardItem t-if="!state.disabledItems.includes(item.id)" size="item.size || 1">
<t t-set="itemProp" t-value="item.props ? item.props(stats) : {'data': stats}"/>
<t t-component="item.Component" t-props="itemProp" />
</DashboardItem>
</t>
</div>
</Layout>
</t>
<t t-name="awesome_dashboard.ConfigurationDialog">
<Dialog title="'Dashboard items configuration'">
Which cards do you whish to see ?
<t t-foreach="items" t-as="item" t-key="item.id">
<CheckBox value="item.enabled" onChange="(ev) => this.onChange(ev, item)">
<t t-esc="item.description"/>
</CheckBox>
</t>
<t t-set-slot="footer">
<button class="btn btn-primary" t-on-click="done">
Done
</button>
</t>
</Dialog>
</t>
</templates>

View file

@ -0,0 +1,67 @@
/** @odoo-module */
import { NumberCard } from "./number_card/number_card";
import { PieChartCard } from "./pie_chart_card/pie_chart_card";
import { registry } from "@web/core/registry";
const items = [
{
id: "average_quantity",
description: "Average amount of t-shirt",
Component: NumberCard,
props: (data) => ({
title: "Average amount of t-shirt by order this month",
value: data.average_quantity,
})
},
{
id: "average_time",
description: "Average time for an order",
Component: NumberCard,
props: (data) => ({
title: "Average time for an order to go from 'new' to 'sent' or 'cancelled'",
value: data.average_time,
})
},
{
id: "number_new_orders",
description: "New orders this month",
Component: NumberCard,
props: (data) => ({
title: "Number of new orders this month",
value: data.nb_new_orders,
})
},
{
id: "cancelled_orders",
description: "Cancelled orders this month",
Component: NumberCard,
props: (data) => ({
title: "Number of cancelled orders this month",
value: data.nb_cancelled_orders,
})
},
{
id: "amount_new_orders",
description: "amount orders this month",
Component: NumberCard,
props: (data) => ({
title: "Total amount of new orders this month",
value: data.total_amount,
})
},
{
id: "pie_chart",
description: "Shirt orders by size",
Component: PieChartCard,
size: 2,
props: (data) => ({
title: "Shirt orders by size",
values: data.orders_by_size,
})
}
]
items.forEach(item => {
registry.category("awesome_dashboard").add(item.id, item);
});

View file

@ -0,0 +1,13 @@
/** @odoo-module */
import { Component } from "@odoo/owl";
export class NumberCard extends Component {
static template = "awesome_dashboard.NumberCard";
static props = {
title: {
type: String,
},
value: {
type: Number,
}
}
}

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_dashboard.NumberCard" owl="1">
<t t-esc="props.title"/>
<div class="fs-1 fw-bold text-success text-center">
<t t-esc="props.value"/>
</div>
</t>
</templates>

View file

@ -0,0 +1,15 @@
/** @odoo-module */
import { Component } from "@odoo/owl";
import { Piechart } from "../pie_chart/pie_chart";
export class PieChartCard extends Component {
static template = "awesome_dashboard.PieChartCard";
static components = { Piechart }
static props = {
title: {
type: String,
},
values: {
type: Object,
},
}
}

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_dashboard.PieChartCard" owl="1">
<t t-esc="props.title"/>
<Piechart data="props.values" label="''"/>
</t>
</templates>

View file

@ -16,6 +16,11 @@
'views/res_users_views.xml',
'views/estate_menus.xml',
'security/ir.model.access.csv',
'data/estate.property.type.csv',
],
'demo':[
'demo/estate_property.xml',
'demo/estate_property_offer.xml',
],
'license': 'AGPL-3',
}

View file

@ -0,0 +1,5 @@
id,name
property_type_residential,Residential
property_type_commercial,Commercial
property_type_industrial,Industrial
property_type_land,Land
1 id name
2 property_type_residential Residential
3 property_type_commercial Commercial
4 property_type_industrial Industrial
5 property_type_land Land

View file

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="property_villa" model="estate.property">
<field name="name">Big Villa</field>
<field name="property_type_id" ref="property_type_residential" />
<field name="state">new</field>
<field name="description">A nice a big villa</field>
<field name="postcode">12345</field>
<field name="date_availability">2020-02-02</field>
<field name="expected_price">1600000.00</field>
<field name="bedrooms">6</field>
<field name="living_area">100</field>
<field name="facades">4</field>
<field name="garage">True</field>
<field name="garden">True</field>
<field name="garden_area">100000</field>
<field name="garden_orientation">south</field>
</record>
<record id="property_trailer" model="estate.property">
<field name="name">Trailer home</field>
<field name="property_type_id" ref="property_type_residential" />
<field name="state">cancelled</field>
<field name="description">Home in a trailer park</field>
<field name="postcode">54321</field>
<field name="date_availability">1970-01-01</field>
<field name="expected_price">100000.00</field>
<field name="selling_price">120000.00</field>
<field name="bedrooms">1</field>
<field name="living_area">10</field>
<field name="facades">4</field>
<field name="garage">False</field>
</record>
<record id="property_hutt" model="estate.property">
<field name="name">Cute Hutt</field>
<field name="property_type_id" ref="property_type_land" />
<field name="state">new</field>
<field name="description">A few logs stacked in a tree, in the middle of a huge land for sale</field>
<field name="postcode">04700</field>
<field name="date_availability">1988-05-16</field>
<field name="expected_price">80000.00</field>
<field name="bedrooms">1</field>
<field name="living_area">5</field>
<field name="facades">3</field>
<field name="garage">False</field>
<field name="garden">True</field>
<field name="garden_area">500000</field>
<field name="garden_orientation">north</field>
<field name="offer_ids" eval="[
Command.create({
'partner_id': 3,
'price': 150000,
'validity': 7,
}),
Command.create({
'partner_id': 5,
'price': 8000,
'validity': 30,
}),
]"/>
</record>
</odoo>

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="property_offer_villa_1" model="estate.property.offer">
<field name="partner_id" ref="base.res_partner_12" />
<field name="property_id" ref="property_villa" />
<field name="price">10000</field>
<field name="validity">14</field>
<field name="create_date" eval="datetime.now()" />
</record>
<record id="property_offer_villa_2" model="estate.property.offer">
<field name="partner_id" ref="base.res_partner_12" />
<field name="property_id" ref="property_villa" />
<field name="price">1500000</field>
<field name="validity">14</field>
<field name="create_date" eval="datetime.now()" />
</record>
<record id="property_offer_villa_3" model="estate.property.offer">
<field name="partner_id" ref="base.res_partner_2" />
<field name="property_id" ref="property_villa" />
<field name="price">1500001</field>
<field name="validity">14</field>
<field name="create_date" eval="datetime.now()" />
</record>
<function model="estate.property.offer" name="action_accept">
<value eval="[ref('property_offer_villa_2')]"/>
</function>
<function model="estate.property.offer" name="action_reject">
<value eval="[ref('property_offer_villa_1')]"/>
</function>
<function model="estate.property.offer" name="action_reject">
<value eval="[ref('property_offer_villa_3')]"/>
</function>
</odoo>