diff --git a/spp_dashboard_base/__init__.py b/spp_dashboard_base/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/spp_dashboard_base/__manifest__.py b/spp_dashboard_base/__manifest__.py new file mode 100644 index 000000000..282f6ee54 --- /dev/null +++ b/spp_dashboard_base/__manifest__.py @@ -0,0 +1,30 @@ +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. + + +{ + "name": "OpenSPP Dashboard: Base", + "category": "OpenSPP", + "version": "17.0.1.0.0", + "sequence": 1, + "author": "OpenSPP.org", + "website": "https://github.com/OpenSPP/openspp-modules", + "license": "LGPL-3", + "development_status": "Beta", + "maintainers": ["reichie020212"], + "depends": [ + "base", + ], + "data": [], + "assets": { + "web.assets_backend": [ + "spp_dashboard_base/static/src/dashboard/**/*", + "spp_dashboard_base/static/src/chart/**/*", + "spp_dashboard_base/static/src/card_board/**/*", + ], + }, + "demo": [], + "images": [], + "application": False, + "installable": True, + "auto_install": False, +} diff --git a/spp_dashboard_base/pyproject.toml b/spp_dashboard_base/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/spp_dashboard_base/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/spp_dashboard_base/static/description/icon.png b/spp_dashboard_base/static/description/icon.png new file mode 100644 index 000000000..35f8fec26 Binary files /dev/null and b/spp_dashboard_base/static/description/icon.png differ diff --git a/spp_dashboard_base/static/src/card_board/card_board.js b/spp_dashboard_base/static/src/card_board/card_board.js new file mode 100644 index 000000000..915e2e44f --- /dev/null +++ b/spp_dashboard_base/static/src/card_board/card_board.js @@ -0,0 +1,17 @@ +/** @odoo-module **/ + +import {Component} from "@odoo/owl"; + +export class CardBoardComponent extends Component {} + +CardBoardComponent.template = "spp_dashboard_base.CardBoardTemplate"; +CardBoardComponent.props = { + title: {type: String, optional: true}, + data: {type: [String, Number], optional: true}, + size: {type: String, optional: true}, +}; +CardBoardComponent.defaultProps = { + title: "Title", + data: "Data", + size: "col-md-4", +}; diff --git a/spp_dashboard_base/static/src/card_board/card_board.xml b/spp_dashboard_base/static/src/card_board/card_board.xml new file mode 100644 index 000000000..c91d34d9f --- /dev/null +++ b/spp_dashboard_base/static/src/card_board/card_board.xml @@ -0,0 +1,26 @@ + + + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
diff --git a/spp_dashboard_base/static/src/chart/chart.js b/spp_dashboard_base/static/src/chart/chart.js new file mode 100644 index 000000000..d170d4ea6 --- /dev/null +++ b/spp_dashboard_base/static/src/chart/chart.js @@ -0,0 +1,72 @@ +/** @odoo-module **/ + +import {Component, onMounted, onWillStart, useRef} from "@odoo/owl"; +import {loadBundle} from "@web/core/assets"; + +export class ChartComponent extends Component { + setup() { + onMounted(() => this.renderChart()); + this.canvasRef = useRef("canvas"); + + this.chartTitle = ""; + const chartTypesWithTitle = ["pie", "doughnut"]; + if (chartTypesWithTitle.includes(this.props.chart_type)) { + this.chartTitle = this.props.data_label; + } + + onWillStart(async () => { + return Promise.all([ + // Load external JavaScript and CSS libraries. + loadBundle({ + jsLibs: [ + // "/awesome_dashboard/static/lib/chart-js.4.4.4/chart.umd.min.js", + "https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js", + ], + }), + ]); + }); + } + + renderChart() { + const ctx = this.canvasRef.el.getContext("2d"); + + new Chart(ctx, { + type: this.props.chart_type, + data: { + labels: this.props.labels, + datasets: [ + { + label: this.props.data_label, + data: this.props.data, + backgroundColor: this.props.backgroundColor, + hoverOffset: 2, + }, + ], + }, + options: {...this.props.options}, + }); + } +} + +ChartComponent.template = "spp_dashboard_base.ChartComponentTemplate"; +ChartComponent.props = { + chart_type: {type: String, optional: true}, + labels: {type: Array, optional: true}, + data_label: {type: String, optional: true}, + data: {type: Array, optional: true}, + backgroundColor: {type: Array, optional: true}, + options: {type: Object, optional: true}, + size: {type: String, optional: true}, +}; +ChartComponent.defaultProps = { + chart_type: "pie", + labels: ["Red", "Blue", "Yellow"], + data_label: "Number of Colors", + data: [300, 50, 150], + backgroundColor: ["rgb(255, 99, 132)", "rgb(54, 162, 235)", "rgb(255, 205, 86)"], + options: { + maintainAspectRatio: true, + aspectRatio: 2, + }, + size: "col-md-6", +}; diff --git a/spp_dashboard_base/static/src/chart/chart.xml b/spp_dashboard_base/static/src/chart/chart.xml new file mode 100644 index 000000000..97973bfc1 --- /dev/null +++ b/spp_dashboard_base/static/src/chart/chart.xml @@ -0,0 +1,18 @@ + + + + +
+
+

+ +
+
+
+
diff --git a/spp_dashboard_base/static/src/dashboard/dashboard.js b/spp_dashboard_base/static/src/dashboard/dashboard.js new file mode 100644 index 000000000..a23cde99f --- /dev/null +++ b/spp_dashboard_base/static/src/dashboard/dashboard.js @@ -0,0 +1,28 @@ +/** @odoo-module **/ +import {Component, onWillStart, useState} from "@odoo/owl"; +import {CardBoardComponent} from "../card_board/card_board"; +import {ChartComponent} from "../chart/chart"; +import {registry} from "@web/core/registry"; +import {useService} from "@web/core/utils/hooks"; + +export class SppDashboard extends Component { + setup() { + super.setup(); + this.orm = useService("orm"); + this.state = useState({hierarchy: []}); + this.card_board_data = {}; + onWillStart(this.onWillStart); + this.dashboard_title = "Dashboard"; + } + + async onWillStart() { + // Super this function to get data from the server + // sample + // this.dashboard_data = await this.orm.call("res.partner", "get_data", []); + } +} + +SppDashboard.template = "spp_dashboard_base.dashboard_page"; +SppDashboard.components = {ChartComponent, CardBoardComponent}; + +registry.category("actions").add("spp_dashboard_tag", SppDashboard); diff --git a/spp_dashboard_base/static/src/dashboard/dashboard.xml b/spp_dashboard_base/static/src/dashboard/dashboard.xml new file mode 100644 index 000000000..46edcd960 --- /dev/null +++ b/spp_dashboard_base/static/src/dashboard/dashboard.xml @@ -0,0 +1,14 @@ + + + +
+
+
+

+
+
+
+
+
+
+
diff --git a/spp_farmer_registry_base/models/agricultural_activity.py b/spp_farmer_registry_base/models/agricultural_activity.py index eaf6dde3e..abcc3f56f 100644 --- a/spp_farmer_registry_base/models/agricultural_activity.py +++ b/spp_farmer_registry_base/models/agricultural_activity.py @@ -31,7 +31,9 @@ class AgriculturalActivity(models.Model): ], ) - species_id = fields.Many2one("spp.farm.species", string="Species", domain="[('species_type', '=', activity_type)]") + species_id = fields.Many2one( + "spp.farm.species", ondelete="restrict", string="Species", domain="[('species_type', '=', activity_type)]" + ) @api.onchange("crop_farm_id") def _onchange_farm_id(self): diff --git a/spp_farmer_registry_demo/models/generate_farmer_data.py b/spp_farmer_registry_demo/models/generate_farmer_data.py index cfdda75b5..d8a05952a 100644 --- a/spp_farmer_registry_demo/models/generate_farmer_data.py +++ b/spp_farmer_registry_demo/models/generate_farmer_data.py @@ -7,6 +7,7 @@ from odoo import Command, api, fields, models +from odoo.addons.queue_job.delay import group from odoo.addons.spp_base_demo.locale_providers import create_faker from .. import tools @@ -57,16 +58,52 @@ class SPPGenerateFarmerData(models.Model): required=True, ) + locked = fields.Boolean(default=False) + locked_reason = fields.Char(readonly=True) + + GROUPS_PER_BATCH = 100 + def generate_sample_data(self): - batches = math.ceil(self.num_groups / 1000) + batches = math.ceil(self.num_groups / self.GROUPS_PER_BATCH) + + self.locked = True + self.locked_reason = "Generating Sample Data" + num_groups = self.num_groups + + jobs = [] for _ in range(0, batches): + jobs.append(self.delayable()._generate_sample_data(res=self, num_groups=num_groups)) + batch_num_groups = min(num_groups, self.GROUPS_PER_BATCH) + num_groups -= batch_num_groups # self.with_delay()._generate_sample_data(res_id=self.id) - self._generate_sample_data(res=self) + # self._generate_sample_data(res=self) + + main_job = group(*jobs) + main_job.on_done(self.delayable()._mark_done()) + main_job.delay() + + def _mark_done(self): + self.ensure_one() + self.locked = False + self.locked_reason = "" + + def refresh_page(self): + """ + The function `refresh_page` returns a dictionary with the type and tag values to reload the + page. + :return: The code is returning a dictionary with two key-value pairs. The "type" key has the + value "ir.actions.client" and the "tag" key has the value "reload". + """ + return { + "type": "ir.actions.client", + "tag": "reload", + } @api.model def _generate_sample_data(self, **kwargs): res = kwargs.get("res") + num_groups = kwargs.get("num_groups") kind_farm_id = self.env.ref("spp_farmer_registry_base.kind_farm").id @@ -77,7 +114,7 @@ def _generate_sample_data(self, **kwargs): sex_choices = [option.value for option in options] sex_choice_range = sex_choices * 50 - num_groups = min(res.num_groups, 1000) + num_groups = min(num_groups, self.GROUPS_PER_BATCH) for i in range(0, num_groups): group_id = res._generate_group_data(i, fake, sex_choice_range, kind_farm_id) diff --git a/spp_farmer_registry_demo/views/generate_farmer_data_view.xml b/spp_farmer_registry_demo/views/generate_farmer_data_view.xml index 59e6ccf16..077c66e81 100644 --- a/spp_farmer_registry_demo/views/generate_farmer_data_view.xml +++ b/spp_farmer_registry_demo/views/generate_farmer_data_view.xml @@ -24,8 +24,25 @@
+ +
+ Warning: Operation in progress: + +
-
+
+