diff --git a/.gitignore b/.gitignore index b6e47617de1..61af8745c5f 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,5 @@ dmypy.json # Pyre type checker .pyre/ + +.vscode/launch.js diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000000..cdc9bed8035 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Run (Update)", + "type": "debugpy", + "request": "launch", + "python": "/usr/bin/python3", + "program": "/home/odoo/odoo/odoo-bin", + "console": "integratedTerminal", + "args": [ + "--database", "rd-demo", + "--dev", "xml", + // "-u", "we_guarantee", + "--addons-path", "/home/odoo/enterprise,/home/odoo/odoo/addons,/home/odoo/tutorials", + "--log-level", "info", + "--limit-time-real=0", + "--limit-time-cpu=0", + ], + "variablePresentation": {}, + }, + ] +} diff --git a/awesome_dashboard/__init__.py b/awesome_dashboard/__init__.py index b0f26a9a602..aa4d0fd63a9 100644 --- a/awesome_dashboard/__init__.py +++ b/awesome_dashboard/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- from . import controllers +from . import models diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py index 31406e8addb..2b96c7ca699 100644 --- a/awesome_dashboard/__manifest__.py +++ b/awesome_dashboard/__manifest__.py @@ -16,7 +16,7 @@ 'version': '0.1', 'application': True, 'installable': True, - 'depends': ['base', 'web', 'mail', 'crm'], + 'depends': ['base', 'web', 'mail', 'crm', 'sale'], 'data': [ 'views/views.xml', @@ -24,6 +24,11 @@ 'assets': { 'web.assets_backend': [ 'awesome_dashboard/static/src/**/*', + ('remove', 'awesome_dashboard/static/src/dashboard/**/*'), + + ], + 'awesome_dashboard.dashboard': [ + 'awesome_dashboard/static/src/dashboard/**/*', ], }, 'license': 'AGPL-3' diff --git a/awesome_dashboard/controllers/__init__.py b/awesome_dashboard/controllers/__init__.py index 457bae27e11..b0f26a9a602 100644 --- a/awesome_dashboard/controllers/__init__.py +++ b/awesome_dashboard/controllers/__init__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -from . import controllers \ No newline at end of file +from . import controllers diff --git a/awesome_dashboard/models/__init__.py b/awesome_dashboard/models/__init__.py new file mode 100644 index 00000000000..82fcc4bfe96 --- /dev/null +++ b/awesome_dashboard/models/__init__.py @@ -0,0 +1 @@ +from . import res_users_settings diff --git a/awesome_dashboard/models/res_users_settings.py b/awesome_dashboard/models/res_users_settings.py new file mode 100644 index 00000000000..b6efc3c0c6c --- /dev/null +++ b/awesome_dashboard/models/res_users_settings.py @@ -0,0 +1,10 @@ +# models/res_users.py +from odoo import models, fields + + +class ResUsersSettings(models.Model): + _inherit = ["res.users.settings"] + + disabled_items = fields.Char( + string="Awesome Dashboard Disabled Items", + ) 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/config_dialog/config_dialog.js b/awesome_dashboard/static/src/dashboard/config_dialog/config_dialog.js new file mode 100644 index 00000000000..b6a39eced4d --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/config_dialog/config_dialog.js @@ -0,0 +1,36 @@ +import { Component, useState } from "@odoo/owl"; +import { Dialog } from "@web/core/dialog/dialog"; +import { CheckBox } from "@web/core/checkbox/checkbox"; + + +export class ConfigDialog extends Component { + static template = "awesome_dashboard.ConfigDialog"; + static components = { Dialog, CheckBox }; + static props = ["close", "items", "disabledItems", "onUpdateConfigs"]; + setup() { + + this.items = useState(this.props.items.map((item) => { + return { + ...item, + enabled: !this.props.disabledItems.includes(item.id), + } + })); + + } + + onChange(checkedItems, Item) { + Item.enabled = checkedItems; + + const updatedDisabledItems = Object.values(this.items).filter( + (item) => !item.enabled + ).map((item) => item.id); + + this.props.onUpdateConfigs(updatedDisabledItems); + } + + done() { + this.props.close(); + } + +} + diff --git a/awesome_dashboard/static/src/dashboard/config_dialog/config_dialog.xml b/awesome_dashboard/static/src/dashboard/config_dialog/config_dialog.xml new file mode 100644 index 00000000000..975eba8d94a --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/config_dialog/config_dialog.xml @@ -0,0 +1,21 @@ + + + + + + Which cards do you whish to see ? + + + + + + + + + + + + + diff --git a/awesome_dashboard/static/src/dashboard/dashboard.css b/awesome_dashboard/static/src/dashboard/dashboard.css new file mode 100644 index 00000000000..90a9bb80806 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.css @@ -0,0 +1,11 @@ +.o_dashboard { + background-color: #f0f0f0; +} +@media (max-width: 767.98px) { + .o_dashboard .card { + width: 100% !important; /* force full width */ + max-width: 100% !important; + display: block !important; /* no inline-block on mobile */ + } +} + diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js new file mode 100644 index 00000000000..7432e11252b --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -0,0 +1,52 @@ +import { Component, useState } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { Layout } from "@web/search/layout"; +import { useService } from "@web/core/utils/hooks"; +import { DashboardItem } from "./dashboard_item"; +import { ConfigDialog } from "./config_dialog/config_dialog"; +import { _t } from "@web/core/l10n/translation"; +import { user } from "@web/core/user"; + +class AwesomeDashboard extends Component { + static template = "awesome_dashboard.AwesomeDashboard"; + static components = { Layout, DashboardItem } + setup() { + this.action = useService("action"); + this.result = useState(useService("awesome_dashboard.statistics")); + this.items = registry.category("awesome_dashboard").getAll(); + this.dialog = useService("dialog"); + + this.state = useState({ + disabledItems: user.settings.disabled_items || [], + }); + } + openCustomers() { + this.action.doAction("base.action_partner_form"); + } + openConfigs() { + this.dialog.add(ConfigDialog, { + items: this.items, + disabledItems: this.state.disabledItems, + onUpdateConfigs: this.updateConfigs.bind(this), + }); + } + + updateConfigs(newDisabledItems) { + this.state.disabledItems = newDisabledItems; + user.setUserSettings("disabled_items", this.state.disabledItems) + } + + openLeads() { + + this.action.doAction({ + type: 'ir.actions.act_window', + name: _t("CRM Leads"), + target: 'current', + res_model: 'crm.lead', + views: [[false,'list'],[false, 'form']], + }); + } +} + +registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard); + diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml new file mode 100644 index 00000000000..43ee18533de --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + +
+ + + + + + +
+
+
+
+ +
+ diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item.js b/awesome_dashboard/static/src/dashboard/dashboard_item.js new file mode 100644 index 00000000000..a905545373f --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item.js @@ -0,0 +1,19 @@ +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, + }, + }; + get itemWidth() { + return 18*this.props.size + } +} + diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item.xml b/awesome_dashboard/static/src/dashboard/dashboard_item.xml new file mode 100644 index 00000000000..cc26acdb805 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item.xml @@ -0,0 +1,13 @@ + + + + +
+
+ +
+
+
+ +
+ 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..3ee2f1b5ab1 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -0,0 +1,72 @@ +import { NumberCard } from "./numbercard/number_card"; +import { PieChartCard } from "./piechartcard/piechart_card"; +import { registry } from "@web/core/registry"; +import { _t } from "@web/core/l10n/translation"; + +export const items = [ + { + id: "average_quantity", + description: _t("Average amount of t-shirt"), + Component: NumberCard, + props: (data) => ({ + title: _t("Average amount of tees ordered this month"), + value: data.average_quantity, + }) + }, + + { + id: "average_time", + description: _t("Average time of 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: "nb_new_orders", + description: _t("Number of new orders this month"), + Component: NumberCard, + props: (data) => ({ + title: _t("Number of new orders this month"), + value: data.nb_new_orders, + }) + }, + + { + id: "nb_cancelled_orders", + description: _t("Number of cancelled orders this month"), + Component: NumberCard, + props: (data) => ({ + title: _t("Number of cancelled orders this month"), + value: data.nb_cancelled_orders, + }) + }, + + { + id: "total_amount", + description: _t("Total amount of new orders this month"), + Component: NumberCard, + props: (data) => ({ + title: _t("Total amount of new orders this month"), + value: data.total_amount, + }) + }, + + { + id: "orders_pie_chart", + description: _t("Shirt order by size"), + Component: PieChartCard, + props: (data) => ({ + title: _t("Shirt order by size"), + data: data.orders_by_size, + }) + }, + +] + +items.forEach(item => { + registry.category("awesome_dashboard").add(item.id, item); +}) + diff --git a/awesome_dashboard/static/src/dashboard/numbercard/number_card.js b/awesome_dashboard/static/src/dashboard/numbercard/number_card.js new file mode 100644 index 00000000000..9b52b682dde --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/numbercard/number_card.js @@ -0,0 +1,14 @@ +import { Component } from "@odoo/owl"; + +export class NumberCard extends Component { + static template = "awesome_dashboard.NumberCard"; + static props = { + title: { + type: String, + }, + value: { + type: Number, + }, + }; +} + diff --git a/awesome_dashboard/static/src/dashboard/numbercard/number_card.xml b/awesome_dashboard/static/src/dashboard/numbercard/number_card.xml new file mode 100644 index 00000000000..619c1fba23b --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/numbercard/number_card.xml @@ -0,0 +1,14 @@ + + + + +
+ +
+ +
+
+
+ +
+ diff --git a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js new file mode 100644 index 00000000000..3b442df0cf9 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js @@ -0,0 +1,71 @@ +import { Component, onWillStart,useRef, onMounted, onWillUnmount, useEnv } from "@odoo/owl"; +import { loadJS } from "@web/core/assets"; +import { _t } from "@web/core/l10n/translation"; + + + +export class PieChart extends Component { + static template = "awesome_dashboard.PieChart"; + static props = { + label: { + type: String, + optional: true, + }, + data: Object, + clickview: { + type: Function, + optional: true, + }, + } + setup() { + this.canvasRef = useRef("canvas_pie"); + this.env = useEnv(); + onWillStart(async () => loadJS("/web/static/lib/Chart/Chart.js")); + onMounted(()=> { + this.renderChart(); + }); + + onWillUnmount(()=> { + this.chart?.destroy(); + }); + + } + + + + renderChart() { + this.chart = new Chart(this.canvasRef.el,{ + type: 'pie', + data : { + labels : Object.keys(this.props.data), + datasets:[ + { + label: this.props.label, + data : Object.values(this.props.data), + }, + ], + }, + options:{ + events: ['click'], + onClick: (ev, section) => { + console.log(section); + const index = section[0].index + console.log("heere maybee ?"); + const size = Object.keys(this.props.data)[index]; + this.env.services.action.doAction({ + type:'ir.actions.act_window', + name: _t("Orders - Size " + size), + target: 'current', + res_model: 'sale.order', + views: [[false,'list']], + domain: [['order_line.name', 'ilike', size]], + + }) + + } + }, + }); + + } +} + diff --git a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml new file mode 100644 index 00000000000..5d5030f84cf --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml @@ -0,0 +1,13 @@ + + + + +
+
+ +
+
+
+ +
+ diff --git a/awesome_dashboard/static/src/dashboard/piechartcard/piechart_card.js b/awesome_dashboard/static/src/dashboard/piechartcard/piechart_card.js new file mode 100644 index 00000000000..66d17cf7216 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/piechartcard/piechart_card.js @@ -0,0 +1,17 @@ +import { Component } from "@odoo/owl"; +import { PieChart } from "../pie_chart/pie_chart"; + +export class PieChartCard extends Component { + static components = { PieChart } + static template = "awesome_dashboard.PieChartCard"; + static props = { + title: { + type: String, + }, + data: { + type: Object, + }, + + }; +} + diff --git a/awesome_dashboard/static/src/dashboard/piechartcard/piechart_card.xml b/awesome_dashboard/static/src/dashboard/piechartcard/piechart_card.xml new file mode 100644 index 00000000000..7ea9d414d97 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/piechartcard/piechart_card.xml @@ -0,0 +1,14 @@ + + + + +
+
+ + +
+
+
+ +
+ diff --git a/awesome_dashboard/static/src/dashboard/statistics_service.js b/awesome_dashboard/static/src/dashboard/statistics_service.js new file mode 100644 index 00000000000..39bdcf5dd6a --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/statistics_service.js @@ -0,0 +1,24 @@ +import { registry } from "@web/core/registry"; +import { rpc } from "@web/core/network/rpc"; +import { reactive } from "@odoo/owl" + + +const statisticsService = { + start() { + + const statistics = reactive({isReady : false}); + + async function loadingData() { + const data = await rpc("/awesome_dashboard/statistics"); + Object.assign(statistics,data,{isReady : true}); + + } + + loadingData(); + setInterval(loadingData, 10*60*1000); + return statistics; + }, +}; + +registry.category("services").add("awesome_dashboard.statistics", statisticsService); + diff --git a/awesome_dashboard/static/src/dashboard_action.js b/awesome_dashboard/static/src/dashboard_action.js new file mode 100644 index 00000000000..c00005754a6 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_action.js @@ -0,0 +1,13 @@ +import { Component, xml } from "@odoo/owl"; +import {LazyComponent } from "@web/core/assets"; +import { registry } from "@web/core/registry"; + +export class AwesomeDashboardLoader extends Component { + static components = { LazyComponent }; + static template = xml ` + + `; +} + +registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboardLoader); + diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js new file mode 100644 index 00000000000..bfa08c956e5 --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,24 @@ +import { Component, useState } from "@odoo/owl"; + +export class Card extends Component { + static template = "awesome_owl.Card" + + static props = { + title: String, + content:{ + type: String, + optional: true, + }, + slots: Object, // I added this to allow slots inside cards + }; + + setup(){ + this.state = useState({open : true}); + } + + onToggleVisibility() { + 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..c9594077f40 --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,21 @@ + + + + +
+
+ +
+
+
+

+ +

+
+
+
+
+ +
+ diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js new file mode 100644 index 00000000000..c92194e61d6 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.js @@ -0,0 +1,20 @@ +import { Component , useState} from "@odoo/owl"; + +export class Counter extends Component { + static template = "awesome_owl.Counter"; + static props = { + onChange: { + type: Function, + optional: true, + } + }; + + setup() { + this.state = useState({ value: 0 }); + } + + increment() { + this.state.value++; + 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..15d6d402e58 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,12 @@ + + + + +
+

Counter:

+ +
+
+ +
+ diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 657fb8b07bb..37609ece487 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,7 +1,20 @@ -/** @odoo-module **/ - -import { Component } from "@odoo/owl"; +import { Component, markup , useState} from "@odoo/owl"; +import { Card } from "./card/card"; +import { Counter } from "./counter/counter"; +import { TodoList } from "./todo/todo_list"; export class Playground extends Component { static template = "awesome_owl.playground"; + static components = { Card , Counter, TodoList }; + setup() { + this.htmlContent = markup("
some content
"); + this.state = useState({ sum : 0 }); + this.incrementSum = this.incrementSum.bind(this); + } + + incrementSum() { + this.state.sum++; + } + } + diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..68cc2f03e05 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,10 +1,17 @@ - -
- hello world + + + + + + +
+

The Sum is:

-
+ + + diff --git a/awesome_owl/static/src/todo/todo_item.js b/awesome_owl/static/src/todo/todo_item.js new file mode 100644 index 00000000000..e59997f2475 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_item.js @@ -0,0 +1,33 @@ +import { Component } from "@odoo/owl"; + +export class TodoItem extends Component { + static template = "awesome_owl.todo_item"; + + static props = { + todo: { + type: Object, + shape: { + id : Number, + description : String, + isCompleted : Boolean, + }, + }, + toggleState: { + type: Function, + optional: true, + }, + removeTodo: { + type: Function, + optional: true, + } + }; + + onToggleState() { + this.props.toggleState?.(this.props.todo.id); + } + + onDeleteTodo() { + this.props.removeTodo?.(this.props.todo.id); + } +} + diff --git a/awesome_owl/static/src/todo/todo_item.xml b/awesome_owl/static/src/todo/todo_item.xml new file mode 100644 index 00000000000..0ba2d7c13b6 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_item.xml @@ -0,0 +1,23 @@ + + + + +
+ + + . + + +
+
+ +
+ diff --git a/awesome_owl/static/src/todo/todo_list.js b/awesome_owl/static/src/todo/todo_list.js new file mode 100644 index 00000000000..9e76c5f4f08 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_list.js @@ -0,0 +1,47 @@ +import { Component, useState, useRef, onMounted } from "@odoo/owl"; + +import { TodoItem } from "./todo_item"; + + +export class TodoList extends Component { + static template = "awesome_owl.todo_list"; + static components = { TodoItem }; + + setup() { + this.todos = useState([]); + this.state = useState({ counter_id : 0}); + this.inputRef = useRef('newTodoInput'); + onMounted(() => { + this.inputRef.el.focus(); + }) + } + + addTodo(ev) { + if (ev.keyCode === 13 && ev.target.value){ + const newTodo = { + id : this.state.counter_id, + description : ev.target.value, + isCompleted : false, + } + this.state.counter_id++; + this.todos.push(newTodo); + ev.target.value = ""; + }; + + } + + toggleTodoState(todoId) { + const todo = this.todos.find(t=>t.id === todoId); + if (todo) { + todo.isCompleted = !todo.isCompleted; + } + } + + DeleteTodo(todoId) { + const todoIndex = this.todos.findIndex(t=>t.id === todoId); + if (todoIndex !== -1){ + this.todos.splice(todoIndex, 1) + } + } +} + diff --git a/awesome_owl/static/src/todo/todo_list.xml b/awesome_owl/static/src/todo/todo_list.xml new file mode 100644 index 00000000000..2fc46642adc --- /dev/null +++ b/awesome_owl/static/src/todo/todo_list.xml @@ -0,0 +1,21 @@ + + + + +
+
+
+ + + + +
+
+
+
+ +
+