changes
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Component, useState, onWillStart } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
import { FusionHealthCard } from "./health_card";
|
||||
import { FusionChatPanel } from "../chat/chat_panel";
|
||||
|
||||
export class FusionDashboard extends Component {
|
||||
static template = "fusion_accounting.Dashboard";
|
||||
static components = { FusionHealthCard, FusionChatPanel };
|
||||
static props = ["*"];
|
||||
|
||||
setup() {
|
||||
this.action = useService("action");
|
||||
this.state = useState({
|
||||
data: null,
|
||||
loading: true,
|
||||
chatSessionId: null,
|
||||
});
|
||||
|
||||
onWillStart(async () => {
|
||||
await this.loadDashboard();
|
||||
});
|
||||
}
|
||||
|
||||
async loadDashboard() {
|
||||
this.state.loading = true;
|
||||
try {
|
||||
this.state.data = await rpc("/fusion_accounting/dashboard/data");
|
||||
} catch (e) {
|
||||
console.error("Dashboard load error:", e);
|
||||
this.state.data = null;
|
||||
}
|
||||
this.state.loading = false;
|
||||
}
|
||||
|
||||
async onCardClick(domain) {
|
||||
if (!this.state.chatSessionId) {
|
||||
const session = await rpc("/fusion_accounting/session/create", {
|
||||
context_domain: domain,
|
||||
});
|
||||
this.state.chatSessionId = session.session_id;
|
||||
}
|
||||
}
|
||||
|
||||
get cards() {
|
||||
if (!this.state.data) return [];
|
||||
const d = this.state.data;
|
||||
return [
|
||||
{
|
||||
title: "Bank Reconciliation",
|
||||
metric: `${d.bank_recon.count} unmatched`,
|
||||
subtext: `$${(d.bank_recon.amount || 0).toFixed(2)} total`,
|
||||
domain: "bank_reconciliation",
|
||||
status: d.bank_recon.count === 0 ? "green" : d.bank_recon.count < 10 ? "yellow" : "red",
|
||||
},
|
||||
{
|
||||
title: "AR Outstanding",
|
||||
metric: `$${(d.ar.total || 0).toFixed(2)}`,
|
||||
subtext: `${d.ar.overdue_count} overdue`,
|
||||
domain: "accounts_receivable",
|
||||
status: d.ar.overdue_count === 0 ? "green" : d.ar.overdue_count < 5 ? "yellow" : "red",
|
||||
},
|
||||
{
|
||||
title: "AP Due",
|
||||
metric: `$${(d.ap.total || 0).toFixed(2)}`,
|
||||
subtext: `${d.ap.due_this_week} due this week`,
|
||||
domain: "accounts_payable",
|
||||
status: d.ap.due_this_week === 0 ? "green" : "yellow",
|
||||
},
|
||||
{
|
||||
title: "HST Balance",
|
||||
metric: `$${(d.hst.balance || 0).toFixed(2)}`,
|
||||
subtext: d.hst.balance > 0 ? "Owing to CRA" : "Refund expected",
|
||||
domain: "hst_management",
|
||||
status: "blue",
|
||||
},
|
||||
{
|
||||
title: "Audit Score",
|
||||
metric: `${d.audit.score}/100`,
|
||||
subtext: `${d.audit.flags} flags`,
|
||||
domain: "audit",
|
||||
status: d.audit.score >= 80 ? "green" : d.audit.score >= 60 ? "yellow" : "red",
|
||||
},
|
||||
{
|
||||
title: "Month-End",
|
||||
metric: d.month_end.status,
|
||||
subtext: `${d.month_end.open_items} open items`,
|
||||
domain: "month_end",
|
||||
status: d.month_end.open_items === 0 ? "green" : "yellow",
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("actions").add("fusion_accounting.dashboard", FusionDashboard);
|
||||
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="fusion_accounting.Dashboard">
|
||||
<div class="o_action fusion_accounting_dashboard">
|
||||
<div class="fusion_dashboard_header d-flex justify-content-between align-items-center p-3">
|
||||
<h2 class="mb-0">Fusion AI Dashboard</h2>
|
||||
<button class="btn btn-outline-primary btn-sm" t-on-click="loadDashboard">
|
||||
<i class="fa fa-refresh"/> Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<t t-if="state.loading">
|
||||
<div class="text-center p-5">
|
||||
<i class="fa fa-spinner fa-spin fa-2x"/>
|
||||
<p class="mt-2">Loading dashboard...</p>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-else="">
|
||||
<!-- Health Cards -->
|
||||
<div class="fusion_health_cards d-flex flex-wrap gap-3 p-3">
|
||||
<t t-foreach="cards" t-as="card" t-key="card.domain">
|
||||
<FusionHealthCard
|
||||
title="card.title"
|
||||
metric="card.metric"
|
||||
subtext="card.subtext"
|
||||
status="card.status"
|
||||
domain="card.domain"
|
||||
onCardClick.bind="onCardClick"/>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!-- Action Centre + Chat -->
|
||||
<div class="d-flex gap-3 p-3" style="min-height: 500px;">
|
||||
<!-- Action Centre -->
|
||||
<div class="flex-grow-1">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Needs Attention</h5>
|
||||
</div>
|
||||
<div class="card-body overflow-auto">
|
||||
<p class="text-muted">AI-prioritised items will appear here after the first audit scan.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chat Panel (720px = original 400 + 80%) -->
|
||||
<div style="width: 720px; min-width: 600px;">
|
||||
<FusionChatPanel sessionId="state.chatSessionId"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,22 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class FusionHealthCard extends Component {
|
||||
static template = "fusion_accounting.HealthCard";
|
||||
static props = ["title", "metric", "subtext", "status", "domain", "onCardClick"];
|
||||
|
||||
get statusClass() {
|
||||
const map = {
|
||||
green: "bg-success-subtle border-success",
|
||||
yellow: "bg-warning-subtle border-warning",
|
||||
red: "bg-danger-subtle border-danger",
|
||||
blue: "bg-info-subtle border-info",
|
||||
};
|
||||
return map[this.props.status] || "bg-light";
|
||||
}
|
||||
|
||||
onClick() {
|
||||
this.props.onCardClick(this.props.domain);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="fusion_accounting.HealthCard">
|
||||
<div class="fusion_health_card card border-2 cursor-pointer"
|
||||
t-attf-class="{{statusClass}}"
|
||||
style="min-width: 180px; flex: 1;"
|
||||
t-on-click="onClick">
|
||||
<div class="card-body text-center p-3">
|
||||
<h6 class="card-title text-muted mb-1" t-esc="props.title"/>
|
||||
<h3 class="mb-1" t-esc="props.metric"/>
|
||||
<small class="text-muted" t-esc="props.subtext"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
Reference in New Issue
Block a user