fix: Show alerts below stats normally, fix usage cost calculation
Some checks failed
CI / style (push) Has been cancelled
CI / build-client (push) Has been cancelled
CI / build-amd64 (push) Has been cancelled
CI / build-tester (push) Has been cancelled
CI / test (cockroachdb) (push) Has been cancelled
CI / test (mariadb) (push) Has been cancelled
CI / test (postgres) (push) Has been cancelled
CI / test (sqlite) (push) Has been cancelled
CI / build-arm64 (push) Has been cancelled
CI / build-armv7 (push) Has been cancelled
CI / publish-images (push) Has been cancelled
CI / publish-release (push) Has been cancelled
Some checks failed
CI / style (push) Has been cancelled
CI / build-client (push) Has been cancelled
CI / build-amd64 (push) Has been cancelled
CI / build-tester (push) Has been cancelled
CI / test (cockroachdb) (push) Has been cancelled
CI / test (mariadb) (push) Has been cancelled
CI / test (postgres) (push) Has been cancelled
CI / test (sqlite) (push) Has been cancelled
CI / build-arm64 (push) Has been cancelled
CI / build-armv7 (push) Has been cancelled
CI / publish-images (push) Has been cancelled
CI / publish-release (push) Has been cancelled
- Alerts shown below QuickStats without hover reveal - Alerts also listed after usage analytics section - Usage cost now shows cost of material consumed (unit cost * usage) instead of all-time purchase total which was misleading Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
import { ExclamationCircleOutlined, PrinterOutlined, DashboardOutlined, WarningOutlined } from "@ant-design/icons";
|
import { ExclamationCircleOutlined, PrinterOutlined, DashboardOutlined } from "@ant-design/icons";
|
||||||
import { useList, useTranslate } from "@refinedev/core";
|
import { useList, useTranslate } from "@refinedev/core";
|
||||||
import { Badge, Card, Col, Row, theme, Typography } from "antd";
|
import { Badge, Card, Col, Row, theme } from "antd";
|
||||||
import { useState } from "react";
|
|
||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
import { ISpool } from "../../spools/model";
|
import { ISpool } from "../../spools/model";
|
||||||
import { IPrintJob } from "../../spools/model";
|
import { IPrintJob } from "../../spools/model";
|
||||||
@@ -41,13 +40,12 @@ function AlertCard({ icon, label, count, to, color, loading }: AlertCardProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface AlertCardsProps {
|
interface AlertCardsProps {
|
||||||
reveal?: boolean;
|
compact?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AlertCards({ reveal }: AlertCardsProps) {
|
export function AlertCards({ compact }: AlertCardsProps) {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const { token } = useToken();
|
const { token } = useToken();
|
||||||
const [hovered, setHovered] = useState(false);
|
|
||||||
|
|
||||||
// Low stock spools
|
// Low stock spools
|
||||||
const { result: lowStockResult, query: lowStockQuery } = useList<ISpool>({
|
const { result: lowStockResult, query: lowStockQuery } = useList<ISpool>({
|
||||||
@@ -111,42 +109,8 @@ export function AlertCards({ reveal }: AlertCardsProps) {
|
|||||||
|
|
||||||
if (visibleAlerts.length === 0) return null;
|
if (visibleAlerts.length === 0) return null;
|
||||||
|
|
||||||
const totalAlertCount = visibleAlerts.reduce((sum, a) => sum + a.count, 0);
|
|
||||||
|
|
||||||
if (reveal) {
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Row gutter={[compact ? 8 : 12, compact ? 8 : 12]} style={{ marginTop: compact ? 8 : 16 }}>
|
||||||
onMouseEnter={() => setHovered(true)}
|
|
||||||
onMouseLeave={() => setHovered(false)}
|
|
||||||
style={{ marginTop: 8 }}
|
|
||||||
>
|
|
||||||
<Typography.Text
|
|
||||||
type="secondary"
|
|
||||||
style={{ fontSize: 12, cursor: "pointer", display: "flex", alignItems: "center", gap: 4 }}
|
|
||||||
>
|
|
||||||
<WarningOutlined />
|
|
||||||
{totalAlertCount} {totalAlertCount === 1 ? "alert" : "alerts"}
|
|
||||||
</Typography.Text>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
maxHeight: hovered ? 200 : 0,
|
|
||||||
overflow: "hidden",
|
|
||||||
transition: "max-height 0.2s ease",
|
|
||||||
marginTop: hovered ? 8 : 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Row gutter={[8, 8]}>
|
|
||||||
{visibleAlerts.map((alert, i) => (
|
|
||||||
<AlertCard key={i} {...alert} />
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Row gutter={[12, 12]} style={{ marginBottom: 16 }}>
|
|
||||||
{visibleAlerts.map((alert, i) => (
|
{visibleAlerts.map((alert, i) => (
|
||||||
<AlertCard key={i} {...alert} />
|
<AlertCard key={i} {...alert} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -122,18 +122,21 @@ export function UsageAnalytics() {
|
|||||||
<div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
|
<div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
|
||||||
{materialData.map((m) => {
|
{materialData.map((m) => {
|
||||||
const costInfo = costData?.cost_by_material?.find((c) => c.material === m.material);
|
const costInfo = costData?.cost_by_material?.find((c) => c.material === m.material);
|
||||||
|
// Calculate cost of material used based on unit cost (cost per gram)
|
||||||
|
const costPerGram = costInfo && costInfo.total_weight > 0 ? costInfo.total_cost / costInfo.total_weight : null;
|
||||||
|
const usageCost = costPerGram != null ? costPerGram * m.weight_used : null;
|
||||||
return (
|
return (
|
||||||
<Card size="small" key={m.material} style={{ minWidth: 120 }}>
|
<Card size="small" key={m.material} style={{ minWidth: 120 }}>
|
||||||
<Typography.Text strong>{m.material}</Typography.Text>
|
<Typography.Text strong>{m.material}</Typography.Text>
|
||||||
<br />
|
<br />
|
||||||
<Typography.Text type="secondary">
|
<Typography.Text type="secondary">
|
||||||
{t("home.analytics.used")}: {m.weight_used >= 1000 ? `${(m.weight_used / 1000).toFixed(2)} kg` : `${m.weight_used.toFixed(0)} g`}
|
{m.weight_used >= 1000 ? `${(m.weight_used / 1000).toFixed(2)} kg` : `${m.weight_used.toFixed(0)} g`}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
{costInfo && (
|
{usageCost != null && (
|
||||||
<>
|
<>
|
||||||
<br />
|
<br />
|
||||||
<Typography.Text type="secondary">
|
<Typography.Text type="secondary">
|
||||||
{t("home.analytics.spent")}: {currencyFormatter.format(costInfo.total_cost)}
|
{currencyFormatter.format(usageCost)}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export const Home: React.FC<IResourceComponentsProps> = () => {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<QuickStats compact />
|
<QuickStats compact />
|
||||||
<AlertCards reveal />
|
<AlertCards compact />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
@@ -171,6 +171,9 @@ export const Home: React.FC<IResourceComponentsProps> = () => {
|
|||||||
|
|
||||||
{/* Usage analytics & insights */}
|
{/* Usage analytics & insights */}
|
||||||
<UsageAnalytics />
|
<UsageAnalytics />
|
||||||
|
|
||||||
|
{/* Alerts */}
|
||||||
|
<AlertCards />
|
||||||
</Content>
|
</Content>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user