diff --git a/awesome_clicker/static/src/ClickValue.js b/awesome_clicker/static/src/ClickValue.js new file mode 100644 index 00000000000..dec1e991cff --- /dev/null +++ b/awesome_clicker/static/src/ClickValue.js @@ -0,0 +1,15 @@ +import { Component } from "@odoo/owl"; +import { humanNumber } from "@web/core/utils/numbers" + +export class ClickValue extends Component { + static template = "awesome_clicker.ClickValue"; + static props = { + clicks: Number + } + + setup() { + this.humanize = (clicks) => { + return humanNumber(clicks, {decimals: clicks > 1000 ? 1 : 0}); + } + } +} diff --git a/awesome_clicker/static/src/ClickValue.xml b/awesome_clicker/static/src/ClickValue.xml new file mode 100644 index 00000000000..884f032219d --- /dev/null +++ b/awesome_clicker/static/src/ClickValue.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/awesome_clicker/static/src/click_rewards.js b/awesome_clicker/static/src/click_rewards.js new file mode 100644 index 00000000000..f1381c1b094 --- /dev/null +++ b/awesome_clicker/static/src/click_rewards.js @@ -0,0 +1,24 @@ +export const rewards = [ + { + description: "Get 1 click bot", + apply(clicker) { + clicker.bots[0].amount += 1; + }, + maxLevel: 3, + }, + { + description: "Get 10 click bot", + apply(clicker) { + clicker.bots[0].amount += 10; + }, + minLevel: 3, + maxLevel: 4, + }, + { + description: "Increase bot power!", + apply(clicker) { + clicker.power += 1; + }, + minLevel: 3, + }, + ]; diff --git a/awesome_clicker/static/src/clicker_migration.js b/awesome_clicker/static/src/clicker_migration.js new file mode 100644 index 00000000000..1137d705034 --- /dev/null +++ b/awesome_clicker/static/src/clicker_migration.js @@ -0,0 +1,23 @@ +export const CURRENT_VERSION = 2.0; +export const migrations = [ + { + fromVersion: 1.0, + toVersion: 2.0, + apply: (state) => { + state.trees.push({fruit: "Peach", price: 1000000, amount: 0, fruit_number: 0 }) + }, + }, +]; + +export function migrate(localState) { + if (localState?.version < CURRENT_VERSION) { + for (const migration of migrations) { + if (localState.version === migration.fromVersion) { + migration.apply(localState); + localState.version = migration.toVersion + } + } + localState.version = CURRENT_VERSION; + } + return localState; +} diff --git a/awesome_clicker/static/src/clicker_model.js b/awesome_clicker/static/src/clicker_model.js new file mode 100644 index 00000000000..9e7a631548f --- /dev/null +++ b/awesome_clicker/static/src/clicker_model.js @@ -0,0 +1,94 @@ +import { Reactive } from "@web/core/utils/reactive"; +import { EventBus } from "@odoo/owl"; +import { rewards } from "./click_rewards"; +import { choose } from "./utils"; +import { CURRENT_VERSION } from "./clicker_migration"; + +export class ClickerModel extends Reactive { + constructor() { + super(); + this.version = CURRENT_VERSION; + this.clicks = 0; + this.level = 0; + this.power = 1; + this.bus = new EventBus(); + this.milestones = [ + { clicks: 1000, unlock: "ClickBots" }, + { clicks: 5000, unlock: "BigBots"}, + { clicks: 100000, unlock: "Power multipliers"}, + { clicks: 1000000, unlock: "Trees" } + ] + this.bots = [ + { name: "Click Bot", price: 1000, amount: 0, clicks: 10 }, + { name: "Big Bot", price: 5000, amount: 0, clicks: 100 }, + ] + this.trees = [ + { fruit: "Pear", price: 1000000, amount: 0, fruit_number: 0 }, + { fruit: "Cherry", price: 1000000, amount: 0, fruit_number: 0 }, + { fruit: "Apple", price: 1000000, amount: 0, fruit_number: 0 }, + { fruit: "Orange", price: 1000000, amount: 0, fruit_number: 0 }, + { fruit: "Peach", price: 1000000, amount: 0, fruit_number: 0 }, + ] + } + + increment(inc) { + this.clicks += inc; + if (this.milestones[this.level] &&this.clicks >= this.milestones[this.level].clicks) { + this.bus.trigger("MILESTONE", this.milestones[this.level]); + this.level += 1; + this.getReward() + } + } + + buyBot(bot) { + if (this.clicks >= bot.price) { + bot.amount++; + this.clicks -= bot.price; + } + } + + buyClickBot() { + this.buyBot(this.bots[0]); + } + + buyBigBot() { + this.buyBot(this.bots[1]); + } + + buyPower() { + if (this.clicks >= 50000) { + this.power++; + this.clicks -= 50000; + } + } + + buyTree(tree) { + if (this.clicks >= tree.price) { + tree.amount++; + this.clicks -= tree.price; + } + } + + getReward() { + const reward = choose(this, rewards); + this.bus.trigger("REWARD", reward); + } + + getTotalTrees() { + let total = 0; + this.trees.forEach((tree) => total += tree.amount) + return total; + } + + getTotalFruits() { + let total = 0; + this.trees.forEach((tree) => total += tree.fruit_number) + return total; + } + + toJSON() { + const json = Object.assign({}, this); + delete json["bus"]; + return json; + } +} diff --git a/awesome_clicker/static/src/clicker_provider.js b/awesome_clicker/static/src/clicker_provider.js new file mode 100644 index 00000000000..7a126f86309 --- /dev/null +++ b/awesome_clicker/static/src/clicker_provider.js @@ -0,0 +1,27 @@ +import { registry } from "@web/core/registry"; + +const commandProviderRegistry = registry.category("command_provider"); + +commandProviderRegistry.add("clicker", { + provide: (env, options) => { + return [ + { + name: "Buy 1 click bot", + action() { + const clicker = env.services["awesome_clicker.clicker_service"].buyClickBot(); + }, + }, + { + name: "Open Clicker Game", + action() { + env.services.action.doAction({ + type: "ir.actions.client", + tag: "awesome_clicker.client_action", + target: "new", + name: "Clicker Game", + }); + }, + }, + ]; + }, +}); diff --git a/awesome_clicker/static/src/clicker_service.js b/awesome_clicker/static/src/clicker_service.js new file mode 100644 index 00000000000..b86d30dd5b8 --- /dev/null +++ b/awesome_clicker/static/src/clicker_service.js @@ -0,0 +1,53 @@ +import { registry } from "@web/core/registry"; +import { ClickerModel } from "./clicker_model"; +import { migrate } from "./clicker_migration"; + +const clickerService = { + dependencies: ["effect", "notification"], + start(env, services) { + const localClicker = migrate(JSON.parse(localStorage.getItem("clicker"))) + const clicker = localClicker ? Object.assign(new ClickerModel(), localClicker) : new ClickerModel() + + setInterval(() => { + clicker.bots.forEach((bot) => clicker.increment(clicker.power * bot.amount * bot.clicks)); + }, 10*1000); + + setInterval(() => { + clicker.trees.forEach((tree) => tree.fruit_number += tree.amount) + }, 30*1000) + + setInterval(() => { + localStorage.setItem("clicker", JSON.stringify(clicker)); + }, 10*1000); + + const bus = clicker.bus; + bus.addEventListener("MILESTONE", (ev) => { + services.effect.add({ + type: "rainbow_man", + message: `Milestone reached! You can now buy ${ev.detail.unlock}` + }); + }); + + bus.addEventListener("REWARD", (ev) => { + const reward = ev.detail; + const closeNotification = services.notification.add(`Congrats you won a reward: "${reward.description}"`,{ + type: "success", + sticky: true, + buttons: [ + { + name: "Collect", + onClick: () => { + reward.apply(clicker); + closeNotification(); + }, + }, + ], + } + ); + }) + + return clicker + }, +}; + +registry.category("services").add("awesome_clicker.clicker_service", clickerService); diff --git a/awesome_clicker/static/src/clicker_systray_item.js b/awesome_clicker/static/src/clicker_systray_item.js new file mode 100644 index 00000000000..8a550d2f108 --- /dev/null +++ b/awesome_clicker/static/src/clicker_systray_item.js @@ -0,0 +1,33 @@ +import { Component, useExternalListener } from '@odoo/owl' +import { useService } from "@web/core/utils/hooks"; +import { registry } from "@web/core/registry"; +import { useClicker } from "./hooks"; +import { ClickValue } from './ClickValue'; +import { Dropdown } from "@web/core/dropdown/dropdown"; +import { DropdownItem } from "@web/core/dropdown/dropdown_item"; + +export class ClickerSystray extends Component { + static template = "awesome_clicker.ClickerSystray"; + static components = { ClickValue, Dropdown, DropdownItem } + + setup() { + this.clicker = useClicker(); + this.action = useService("action"); + useExternalListener(document.body, "click", () => this.clicker.increment(1), { capture: true }); + } + + openClicker = () => { + this.action.doAction({ + type: "ir.actions.client", + tag: "awesome_clicker.client_action", + target: "new", + name: "Clicker Game", + }); + } +} + +export const systrayItem = { + Component: ClickerSystray, +}; + +registry.category("systray").add("awesome_clicker.ClickerSystray", systrayItem, { sequence: 10 }); diff --git a/awesome_clicker/static/src/clicker_systray_item.xml b/awesome_clicker/static/src/clicker_systray_item.xml new file mode 100644 index 00000000000..aedb90cfbda --- /dev/null +++ b/awesome_clicker/static/src/clicker_systray_item.xml @@ -0,0 +1,34 @@ + + + + + + + + , + , + + + + + + Open the clicker game + + + Buy a Click Bot + + + + + + + + + + + + + + + + diff --git a/awesome_clicker/static/src/client_action.js b/awesome_clicker/static/src/client_action.js new file mode 100644 index 00000000000..0559102d6ac --- /dev/null +++ b/awesome_clicker/static/src/client_action.js @@ -0,0 +1,16 @@ +import { Component } from "@odoo/owl" +import { registry } from "@web/core/registry"; +import { useClicker } from "./hooks"; +import { ClickValue } from './ClickValue'; +import { Notebook } from "@web/core/notebook/notebook"; + +export class ClientAction extends Component { + static template = "awesome_clicker.ClientAction"; + static components = { ClickValue, Notebook } + + setup() { + this.clicker = useClicker(); + } +} + +registry.category("actions").add("awesome_clicker.client_action", ClientAction); diff --git a/awesome_clicker/static/src/client_action.xml b/awesome_clicker/static/src/client_action.xml new file mode 100644 index 00000000000..c05f7ae3252 --- /dev/null +++ b/awesome_clicker/static/src/client_action.xml @@ -0,0 +1,81 @@ + + + + + + + Clicks: + + Increment + + + + + + Bots + + + + + + + + + + + + + + + + + Power multiplier + + + + + + + + Buy Power multiplier (50000 clicks) + + + + + + + + + Trees + + + + + + + + + + + + + + + + + Fruits + + + + + + + + + + + + + + + diff --git a/awesome_clicker/static/src/form_controller_patch.js b/awesome_clicker/static/src/form_controller_patch.js new file mode 100644 index 00000000000..2a47601eebd --- /dev/null +++ b/awesome_clicker/static/src/form_controller_patch.js @@ -0,0 +1,15 @@ +import { FormController } from "@web/views/form/form_controller"; +import { patch } from "@web/core/utils/patch"; +import { useClicker } from "./hooks"; + +const FormControllerPatch = { + setup() { + super.setup(...arguments); + if (Math.random() < 0.01) { + const clicker = useClicker(); + clicker.giveReward(); + } + } +} + +patch(FormController.prototype, FormControllerPatch); diff --git a/awesome_clicker/static/src/hooks.js b/awesome_clicker/static/src/hooks.js new file mode 100644 index 00000000000..231d86736a1 --- /dev/null +++ b/awesome_clicker/static/src/hooks.js @@ -0,0 +1,6 @@ +import { useService } from "@web/core/utils/hooks"; +import { useState } from "@odoo/owl"; + +export function useClicker() { + return useState(useService("awesome_clicker.clicker_service")); +} diff --git a/awesome_clicker/static/src/utils.js b/awesome_clicker/static/src/utils.js new file mode 100644 index 00000000000..a965ce7f7e2 --- /dev/null +++ b/awesome_clicker/static/src/utils.js @@ -0,0 +1,15 @@ +export function choose(clicker, rewards) { + const list = rewards.filter((reward) => { + if (reward.minLevel && reward.maxLevel) { + return clicker.level >= reward.minLevel && clicker.level <= reward.maxLevel; + } else if (reward.minLevel) { + return clicker.level >= reward.minLevel; + } else if (reward.maxLevel) { + return clicker.level <= reward.maxLevel; + } else { + return true; + } + }); + + return list[Math.floor(Math.random() * list.length)]; +} diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py index 31406e8addb..c5b63b98530 100644 --- a/awesome_dashboard/__manifest__.py +++ b/awesome_dashboard/__manifest__.py @@ -25,6 +25,9 @@ 'web.assets_backend': [ 'awesome_dashboard/static/src/**/*', ], + 'awesome_dashboard.dashboard': [ + 'awesome_dashboard/static/src/dashboard/**/*', + ], }, 'license': 'AGPL-3' } diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js deleted file mode 100644 index 637fa4bb972..00000000000 --- a/awesome_dashboard/static/src/dashboard.js +++ /dev/null @@ -1,10 +0,0 @@ -/** @odoo-module **/ - -import { Component } from "@odoo/owl"; -import { registry } from "@web/core/registry"; - -class AwesomeDashboard extends Component { - static template = "awesome_dashboard.AwesomeDashboard"; -} - -registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml deleted file mode 100644 index 1a2ac9a2fed..00000000000 --- a/awesome_dashboard/static/src/dashboard.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - hello dashboard - - - diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js new file mode 100644 index 00000000000..4b57fda6c9b --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -0,0 +1,56 @@ +/** @odoo-module **/ + +import { _t } from "@web/core/l10n/translation"; +import { Component, useState } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; +import { Layout } from "@web/search/layout"; +import { DashboardItem } from "./dashboard_item/dashboard_item"; +import { PieChart } from "./dashboard_pie_chart/dashboard_pie_chart"; +import { DashboardConfig } from "./dashboard_config/dashboard_config"; + +class AwesomeDashboard extends Component { + static template = "awesome_dashboard.AwesomeDashboard"; + static components = { Layout, DashboardItem, PieChart }; + static props = ['*']; + + setup() { + this.action = useService("action"); + this.title = _t("Awesome Dashboard"); + this.statistics = useState(useService("awesome_dashboard.statistics")); + this.items = registry.category("awesome_dashboard.items").getAll(); + this.dialog = useService("dialog"); + this.state = useState({ + disabledItems: localStorage.getItem("disabledItems") ? localStorage.getItem("disabledItems").split(",") : [] + }); + } + + openCustomers = () => { + this.action.doAction("base.action_partner_form"); + } + + openLeads = () => { + this.action.doAction({ + type: 'ir.actions.act_window', + name: _t('Leads'), + res_model: 'crm.lead', + views: [[false, 'list'], [false, 'form']], + target: 'current', + }) + } + + openDashboardConfig = () => { + this.dialog.add(DashboardConfig, { + items: this.items, + applyFunction: this.onApplyConfiguration.bind(this), + disabledItems: this.state.disabledItems + }); + } + + onApplyConfiguration = (ids) => { + this.state.disabledItems = ids; + localStorage.setItem("disabledItems", ids); + } +} + +registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard/dashboard.scss b/awesome_dashboard/static/src/dashboard/dashboard.scss new file mode 100644 index 00000000000..c3edab6589f --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.scss @@ -0,0 +1,9 @@ +.o_dashboard { + background-color: gray; +} + +.o_mobile_card { + @media (max-width: 768px) { + width: 100% !important; + } +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml new file mode 100644 index 00000000000..d40064ae789 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,26 @@ + + + + + + + Customers + Leads + + + + + + + + + + + + + + + + + + diff --git a/awesome_dashboard/static/src/dashboard/dashboard_config/dashboard_config.js b/awesome_dashboard/static/src/dashboard/dashboard_config/dashboard_config.js new file mode 100644 index 00000000000..815de59e6f2 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_config/dashboard_config.js @@ -0,0 +1,33 @@ +import { Component } from "@odoo/owl"; +import { Dialog } from "@web/core/dialog/dialog"; + +export class DashboardConfig extends Component { + static template = "awesome_dashboard.DashboardConfig"; + static components = { Dialog }; + static props = { + items: Object, + applyFunction: Function, + disabledItems: Array, + close: Function + } + + setup() { + this.disabledItems = this.props.disabledItems.slice(); + + this.onChange = (ev, item_id) => { + if (ev.target.checked) { + const index = this.disabledItems.indexOf(item_id); + if (index >= 0) { + this.disabledItems.splice(index, 1); + } + } else { + this.disabledItems.push(item_id); + } + } + } + + onApply() { + this.props.applyFunction(this.disabledItems); + this.props.close(); + } +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard_config/dashboard_config.xml b/awesome_dashboard/static/src/dashboard/dashboard_config/dashboard_config.xml new file mode 100644 index 00000000000..71e5b8f4a70 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_config/dashboard_config.xml @@ -0,0 +1,21 @@ + + + + + + + Which cards do you wish to see ? + + + + + + + + + Apply + + + + + diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js new file mode 100644 index 00000000000..542d93bb9e8 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js @@ -0,0 +1,10 @@ +import { Component } from "@odoo/owl"; + +export class DashboardItem extends Component { + static template = "awesome_dashboard.DashboardItem"; + static defaultProps = { size: 1 } + static props = { + size: {type: Number, optional: true}, + slots: {type: Object, optional: true} + } +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml new file mode 100644 index 00000000000..83ba0997578 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/awesome_dashboard/static/src/dashboard/dashboard_items.js b/awesome_dashboard/static/src/dashboard/dashboard_items.js new file mode 100644 index 00000000000..a25d9a265e8 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -0,0 +1,66 @@ +import { _t } from "@web/core/l10n/translation"; +import { registry } from "@web/core/registry"; +import { NumberCard } from "./number_card/number_card"; +import { PieChartCard } from "./pie_chart_card/pie_chart_card"; + +const items = [ + { + id: "average_quantity", + description: _t("Average amount of t-shirt"), + Component: NumberCard, + props: (data) => ({ + title: _t("Average amount of t-shirt by order this month"), + value: data.average_quantity, + }) + }, + { + id: "average_time", + description: _t("Average time for an order"), + Component: NumberCard, + props: (data) => ({ + title: _t("Average time for an order to go from 'new' to 'sent' or 'cancelled'"), + value: data.average_time, + }) + }, + { + id: "number_new_orders", + description: _t("New orders this month"), + Component: NumberCard, + props: (data) => ({ + title: _t("Number of new orders this month"), + value: data.nb_new_orders, + }) + }, + { + id: "cancelled_orders", + description: _t("Cancelled orders this month"), + Component: NumberCard, + props: (data) => ({ + title: _t("Number of cancelled orders this month"), + value: data.nb_cancelled_orders, + }) + }, + { + id: "amount_new_orders", + description: _t("Amount orders this month"), + Component: NumberCard, + props: (data) => ({ + title: _t("Total amount of new orders this month"), + value: data.total_amount, + }) + }, + { + id: "pie_chart", + description: _t("Shirt orders by size"), + Component: PieChartCard, + size: 2, + props: (data) => ({ + title: _t("Shirt orders by size"), + value: data.orders_by_size, + }) + } +] + +items.forEach(item => { + registry.category("awesome_dashboard.items").add(item.id, item); +}); diff --git a/awesome_dashboard/static/src/dashboard/dashboard_pie_chart/dashboard_pie_chart.js b/awesome_dashboard/static/src/dashboard/dashboard_pie_chart/dashboard_pie_chart.js new file mode 100644 index 00000000000..22b0e990eae --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_pie_chart/dashboard_pie_chart.js @@ -0,0 +1,70 @@ +import { _t } from "@web/core/l10n/translation"; +import { Component, onWillStart, useEffect, onWillUnmount, useRef } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; +import { loadJS } from "@web/core/assets"; + +export class PieChart extends Component { + static template = "awesome_dashboard.PieChart"; + static props = { + data: Object, + }; + + setup() { + this.action = useService("action"); + this.canvasRef = useRef('canvas'); + onWillStart(() => loadJS("/web/static/lib/Chart/Chart.js")); + useEffect(() => this.renderChart()); + onWillUnmount(() => { + if (this.chart) { + this.chart.destroy(); + } + }); + } + + /** + * Creates and binds the chart on `canvasRef`. + */ + renderChart() { + if (this.chart) { + this.chart.destroy(); + } + const ctx = this.canvasRef.el.getContext('2d'); + this.chart = new Chart(ctx, this.getChartConfig()); + } + + onGraphClicked(ev) { + const points = this.chart.getElementsAtEventForMode(ev, 'nearest', { intersect: true }, false); + + if (points.length) { + const index = points[0].index; + const label = this.chart.data.labels[index]; + + this.action.doAction({ + type: 'ir.actions.act_window', + name: _t('Sales'), + res_model: 'sale.order', + views: [[false, 'list'], [false, 'form']], + target: 'current', + domain: [['order_line.product_id', 'ilike', '' + label]] + }) + } + } + + /** + * @returns {object} Chart config for the current data + */ + getChartConfig() { + return { + type: 'pie', + data: { + labels: Object.keys(this.props.data), + datasets: [{ + data: Object.values(this.props.data) + }] + }, + options: { + onClick: this.onGraphClicked.bind(this) + } + }; + } +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard_pie_chart/dashboard_pie_chart.xml b/awesome_dashboard/static/src/dashboard/dashboard_pie_chart/dashboard_pie_chart.xml new file mode 100644 index 00000000000..906710d16c3 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_pie_chart/dashboard_pie_chart.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/awesome_dashboard/static/src/dashboard/dashboard_statistics/dashboard_statistics_service.js b/awesome_dashboard/static/src/dashboard/dashboard_statistics/dashboard_statistics_service.js new file mode 100644 index 00000000000..4832789149d --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_statistics/dashboard_statistics_service.js @@ -0,0 +1,20 @@ +import { reactive } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { rpc } from "@web/core/network/rpc"; + +const dashboardStatisticsService = { + start(env) { + const statistics = reactive({}); + + async function loadData() { + Object.assign(statistics, await rpc("/awesome_dashboard/statistics")); + } + + setInterval(loadData, 10 * 60 * 1000); + loadData(); + + return statistics + }, +}; + +registry.category("services").add("awesome_dashboard.statistics", dashboardStatisticsService); diff --git a/awesome_dashboard/static/src/dashboard/number_card/number_card.js b/awesome_dashboard/static/src/dashboard/number_card/number_card.js new file mode 100644 index 00000000000..2ea1408a285 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.js @@ -0,0 +1,9 @@ +import { Component } from "@odoo/owl"; + +export class NumberCard extends Component { + static template = "awesome_dashboard.NumberCard"; + static props = { + title: String, + value: Number + } +} diff --git a/awesome_dashboard/static/src/dashboard/number_card/number_card.xml b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml new file mode 100644 index 00000000000..1d3df051f8d --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js new file mode 100644 index 00000000000..a8dc9613c03 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js @@ -0,0 +1,11 @@ +import { Component } from "@odoo/owl"; +import { PieChart } from "../dashboard_pie_chart/dashboard_pie_chart"; + +export class PieChartCard extends Component { + static template = "awesome_dashboard.PieChartCard"; + static components = { PieChart }; + static props = { + title: String, + value: Object + }; +} diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml new file mode 100644 index 00000000000..8981238913c --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/awesome_dashboard/static/src/dashboard_action.js b/awesome_dashboard/static/src/dashboard_action.js new file mode 100644 index 00000000000..a0a399c83ce --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_action.js @@ -0,0 +1,13 @@ +import { Component, xml } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { LazyComponent } from "@web/core/assets" + +export class DashboardLoader extends Component { + static components = { LazyComponent } + static props = ['*']; + static template = xml` + + `; +} + +registry.category("actions").add("awesome_dashboard.dashboard", DashboardLoader); diff --git a/awesome_owl/__manifest__.py b/awesome_owl/__manifest__.py index 77abad510ef..43390a74108 100644 --- a/awesome_owl/__manifest__.py +++ b/awesome_owl/__manifest__.py @@ -32,6 +32,7 @@ 'web/static/src/scss/pre_variables.scss', 'web/static/lib/bootstrap/scss/_variables.scss', 'web/static/lib/bootstrap/scss/_maps.scss', + ('include', 'web._assets_bootstrap_backend'), ('include', 'web._assets_bootstrap'), ('include', 'web._assets_core'), 'web/static/src/libs/fontawesome/css/font-awesome.css', diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js new file mode 100644 index 00000000000..bb84fef81a8 --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,17 @@ +import { Component, useState } from "@odoo/owl"; + +export class Card extends Component { + static template = "awesome_owl.card"; + static props = { + title: String, + slots: Object + }; + + setup() { + this.state = useState({ open: true }); + } + + toggle() { + this.state.open = !this.state.open; + } +} diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml new file mode 100644 index 00000000000..b10597ef5c0 --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,19 @@ + + + + + + + + + + Toggle + + + + + + + + + diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js new file mode 100644 index 00000000000..0fdd5f5799f --- /dev/null +++ b/awesome_owl/static/src/counter/counter.js @@ -0,0 +1,19 @@ +import { Component, useState } from "@odoo/owl"; + +export class Counter extends Component { + static template = "awesome_owl.counter"; + static props = { + onChange: Function + }; + + setup() { + this.state = useState({ value: 0 }); + } + + increment() { + this.state.value++; + if (this.props.onChange) { + this.props.onChange(); + } + } +} diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml new file mode 100644 index 00000000000..8969c30896c --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,12 @@ + + + + + + + Counter: + Increment + + + + diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 657fb8b07bb..4a8e8a76ae9 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,7 +1,21 @@ -/** @odoo-module **/ - -import { Component } from "@odoo/owl"; +import { Component, markup, useState } from "@odoo/owl"; +import { Counter } from "./counter/counter"; +import { Card } from "./card/card"; +import { TodoList } from "./todo_list/todo_list"; export class Playground extends Component { static template = "awesome_owl.playground"; + static components = { Counter, Card, TodoList }; + static props = {}; + + html_value1 = markup("some content"); + html_value2 = "some content"; + + setup() { + this.state = useState({ sum: 0 }); + } + + incrementSum() { + this.state.sum++; + } } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..46479537996 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,9 +1,34 @@ + - hello world + + + + + + The sum is: + + + + + + + + + + + + + + + + + + + diff --git a/awesome_owl/static/src/todo_list/todo_item.js b/awesome_owl/static/src/todo_list/todo_item.js new file mode 100644 index 00000000000..a8491d05e75 --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_item.js @@ -0,0 +1,10 @@ +import { Component } from "@odoo/owl"; + +export class TodoItem extends Component { + static template = "awesome_owl.todo_item"; + static props = { + todo: Object, + toggleState: Function, + removeTodo: Function + }; +} diff --git a/awesome_owl/static/src/todo_list/todo_item.xml b/awesome_owl/static/src/todo_list/todo_item.xml new file mode 100644 index 00000000000..0700ce9fb1c --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_item.xml @@ -0,0 +1,13 @@ + + + + + + + + . + + + + + diff --git a/awesome_owl/static/src/todo_list/todo_list.js b/awesome_owl/static/src/todo_list/todo_list.js new file mode 100644 index 00000000000..5178fb009cc --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.js @@ -0,0 +1,36 @@ +import { Component, useState, useRef, onMounted } from "@odoo/owl"; +import { TodoItem } from "./todo_item"; +import { useAutofocus } from "../utils"; + +export class TodoList extends Component { + static template = "awesome_owl.todo_list"; + static components = { TodoItem }; + static props = {}; + + setup() { + this.todos = useState([]); + useAutofocus("todoInput"); + } + + addTodo(ev) { + if (ev.key === "Enter" && ev.target.value.trim() !== "") { + this.todos.push({ + id: this.todos.length + 1, + description: ev.target.value, + isCompleted: false + }); + ev.target.value = ""; + } + } + + setTodoCompletion(todo, ev) { + todo.isCompleted = ev.target.checked; + } + + deleteTodo(todo, ev) { + const index = this.todos.findIndex((elem) => elem.id === todo.id); + if (index >= 0) { + this.todos.splice(index, 1); + } + } +} diff --git a/awesome_owl/static/src/todo_list/todo_list.xml b/awesome_owl/static/src/todo_list/todo_list.xml new file mode 100644 index 00000000000..db20df7f076 --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js new file mode 100644 index 00000000000..aba121a127b --- /dev/null +++ b/awesome_owl/static/src/utils.js @@ -0,0 +1,8 @@ +import { useRef, onMounted } from "@odoo/owl"; + +export function useAutofocus(name) { + const ref = useRef(name); + onMounted(() => { + ref.el.focus(); + }); +}
Which cards do you wish to see ?