diff --git a/fusion-woo-odoo/fusion_woocommerce/static/src/js/dashboard.js b/fusion-woo-odoo/fusion_woocommerce/static/src/js/dashboard.js new file mode 100644 index 00000000..5bec895f --- /dev/null +++ b/fusion-woo-odoo/fusion_woocommerce/static/src/js/dashboard.js @@ -0,0 +1,221 @@ +/** @odoo-module **/ + +import { Component, useState, onWillStart, onMounted } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; +import { rpc } from "@web/core/network/rpc"; + +/** + * WooDashboard — OWL client action for the WooCommerce sync dashboard. + * + * Shows: + * - Orders pending sync + * - Last sync time (relative) + * - Errors in last 24 h + * - Products mapped / unmapped (progress bar) + * - Quick actions: Sync Now, View Conflicts, Open Mapping + */ +export class WooDashboard extends Component { + static template = "fusion_woocommerce.Dashboard"; + static props = ["action", "*"]; + + setup() { + this.actionService = useService("action"); + this.notification = useService("notification"); + + this.state = useState({ + loading: true, + + // Stats + pendingOrders: 0, + lastSync: null, + errors24h: 0, + mappedCount: 0, + totalProducts: 0, + + // Instances + instances: [], + }); + + onWillStart(async () => { + await this._loadDashboard(); + }); + + // Refresh every 60 s while mounted + this._refreshInterval = null; + onMounted(() => { + this._refreshInterval = setInterval(() => { + this._loadDashboard(); + }, 60000); + }); + } + + // ------------------------------------------------------------------------- + // Data + // ------------------------------------------------------------------------- + + async _loadDashboard() { + try { + await Promise.all([ + this._loadInstances(), + this._loadPendingOrders(), + this._loadErrors(), + this._loadProductStats(), + this._loadLastSync(), + ]); + } catch (err) { + console.error("[WooDashboard] _loadDashboard error:", err); + } finally { + this.state.loading = false; + } + } + + async _loadInstances() { + const result = await rpc("/web/dataset/call_kw", { + model: "woo.instance", + method: "search_read", + args: [[]], + kwargs: { fields: ["id", "name", "state", "last_sync"], limit: 20 }, + }); + this.state.instances = result || []; + } + + async _loadPendingOrders() { + const count = await rpc("/web/dataset/call_kw", { + model: "woo.order", + method: "search_count", + args: [[["state", "in", ["new", "confirmed"]]]], + kwargs: {}, + }); + this.state.pendingOrders = count || 0; + } + + async _loadErrors() { + // Errors from woo.sync.log in the last 24 h + const since = new Date(Date.now() - 24 * 3600 * 1000); + const sinceStr = since.toISOString().replace("T", " ").substring(0, 19); + + const count = await rpc("/web/dataset/call_kw", { + model: "woo.sync.log", + method: "search_count", + args: [[ + ["state", "=", "failed"], + ["create_date", ">=", sinceStr], + ]], + kwargs: {}, + }); + this.state.errors24h = count || 0; + } + + async _loadProductStats() { + const [mapped, total] = await Promise.all([ + rpc("/web/dataset/call_kw", { + model: "woo.product.map", + method: "search_count", + args: [[["state", "=", "mapped"]]], + kwargs: {}, + }), + rpc("/web/dataset/call_kw", { + model: "woo.product.map", + method: "search_count", + args: [[]], + kwargs: {}, + }), + ]); + this.state.mappedCount = mapped || 0; + this.state.totalProducts = total || 0; + } + + async _loadLastSync() { + // Get the most recent successful sync log entry + const result = await rpc("/web/dataset/call_kw", { + model: "woo.sync.log", + method: "search_read", + args: [[["state", "=", "success"]]], + kwargs: { + fields: ["create_date"], + limit: 1, + order: "create_date desc", + }, + }); + if (result && result.length) { + this.state.lastSync = result[0].create_date; + } + } + + // ------------------------------------------------------------------------- + // Computed / helpers + // ------------------------------------------------------------------------- + + get mappedPercent() { + if (!this.state.totalProducts) return 0; + return Math.round((this.state.mappedCount / this.state.totalProducts) * 100); + } + + get lastSyncRelative() { + if (!this.state.lastSync) return "Never"; + const past = new Date(this.state.lastSync.replace(" ", "T") + "Z"); + const diffMs = Date.now() - past.getTime(); + const diffMin = Math.floor(diffMs / 60000); + + if (diffMin < 1) return "Just now"; + if (diffMin < 60) return `${diffMin} minute${diffMin > 1 ? "s" : ""} ago`; + const diffHr = Math.floor(diffMin / 60); + if (diffHr < 24) return `${diffHr} hour${diffHr > 1 ? "s" : ""} ago`; + const diffDay = Math.floor(diffHr / 24); + return `${diffDay} day${diffDay > 1 ? "s" : ""} ago`; + } + + // ------------------------------------------------------------------------- + // Actions + // ------------------------------------------------------------------------- + + async syncNow() { + try { + const instanceIds = this.state.instances.map((i) => i.id); + if (!instanceIds.length) { + this.notification.add("No WooCommerce instances configured.", { type: "warning" }); + return; + } + await rpc("/web/dataset/call_kw", { + model: "woo.instance", + method: "action_sync", + args: [instanceIds], + kwargs: {}, + }); + this.notification.add("Sync started for all instances.", { type: "success" }); + } catch (err) { + console.error("[WooDashboard] syncNow error:", err); + this.notification.add("Failed to start sync.", { type: "danger" }); + } + } + + openOrders() { + this.actionService.doAction("fusion_woocommerce.action_woo_order"); + } + + openSyncLogs() { + this.actionService.doAction({ + type: "ir.actions.act_window", + name: "Sync Errors (Last 24 h)", + res_model: "woo.sync.log", + view_mode: "tree,form", + domain: [["state", "=", "failed"]], + target: "current", + }); + } + + openConflicts() { + this.actionService.doAction("fusion_woocommerce.action_woo_conflict"); + } + + openMapping() { + this.actionService.doAction({ + type: "ir.actions.client", + tag: "fusion_woocommerce.product_mapping", + name: "Product Mapping", + }); + } +} + +registry.category("actions").add("fusion_woocommerce.woo_dashboard", WooDashboard); diff --git a/fusion-woo-odoo/fusion_woocommerce/static/src/xml/dashboard.xml b/fusion-woo-odoo/fusion_woocommerce/static/src/xml/dashboard.xml new file mode 100644 index 00000000..371991c5 --- /dev/null +++ b/fusion-woo-odoo/fusion_woocommerce/static/src/xml/dashboard.xml @@ -0,0 +1,142 @@ + + + + +
+ +
WooCommerce Dashboard
+
+ At-a-glance sync status across all WooCommerce instances. +
+ + + +
+
+ Loading dashboard… +
+ + + + + +
+ + +
+
🛒
+
+
Orders Pending Sync
+
Click to view orders
+
+ + +
+
🔄
+
+
Last Sync
+
+ + instances configured + + No instances configured +
+
+ + +
+
⚠️
+
+
Errors (Last 24 h)
+
Click to view sync log
+
+ + +
+
🔗
+
+ % +
+
Products Mapped
+
+
+
+
+ / products +
+
+ +
+ + +
Quick Actions
+
+ + + + + + + + + +
+ + + +
Instances
+
+ + + + + + + + + + + + + + + + + +
InstanceStatusLast Sync
+ + Connected + + + Error + + + Draft + + + + + + + Never + +
+
+
+ + +
+ + +