From 8a4755489c7c20bf21f481b272f87bc29a2d36c7 Mon Sep 17 00:00:00 2001 From: vincentlu <t1341870251@gmail.com> Date: 星期一, 11 八月 2025 11:17:32 +0800 Subject: [PATCH] # --- rsf-admin/src/page/dashboard/Welcome.jsx | 3 rsf-admin/package.json | 5 rsf-admin/package-lock.json | 284 +++++++++++++++++++++++++++++++++++ rsf-admin/src/page/dashboard/index.jsx | 26 +++ rsf-admin/src/page/dashboard/NbChart.jsx | 117 ++++++++++++++ 5 files changed, 431 insertions(+), 4 deletions(-) diff --git a/rsf-admin/package-lock.json b/rsf-admin/package-lock.json index 2ccac71..fef3d13 100644 --- a/rsf-admin/package-lock.json +++ b/rsf-admin/package-lock.json @@ -39,6 +39,7 @@ "react-router-dom": "^6.26.1", "react-syntax-highlighter": "^15.5.0", "react-to-print": "^2.14.11", + "recharts": "^2.15.0", "svgpath": "^2.6.0", "three": "^0.155.0", "tweedle.js": "^2.1.0" @@ -2539,6 +2540,69 @@ "integrity": "sha512-x2tZZYkSxXqWvTDgveSynfjq/T2HyiZHXb00j/+gy19yp70PHCizM48XFdjBCWH7eHBD0R5i/pw9yMBP/BH5uA==", "license": "MIT" }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, "node_modules/@types/earcut": { "version": "2.1.4", "resolved": "https://registry.npmmirror.com/@types/earcut/-/earcut-2.1.4.tgz", @@ -3498,6 +3562,127 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -3584,6 +3769,12 @@ "optional": true } } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" }, "node_modules/decode-uri-component": { "version": "0.2.2", @@ -4241,6 +4432,15 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", + "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } }, "node_modules/fast-glob": { "version": "3.3.3", @@ -4939,6 +5139,15 @@ }, "engines": { "node": ">= 0.4" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" } }, "node_modules/is-alphabetical": { @@ -6607,6 +6816,21 @@ "react-dom": ">=16.8" } }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-syntax-highlighter": { "version": "15.6.1", "resolved": "https://registry.npmmirror.com/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz", @@ -6652,6 +6876,44 @@ "react": ">=16.6.0", "react-dom": ">=16.6.0" } + }, + "node_modules/recharts": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", + "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/recharts/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" }, "node_modules/redux": { "version": "4.2.1", @@ -7591,6 +7853,28 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vite": { "version": "5.4.19", "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.19.tgz", diff --git a/rsf-admin/package.json b/rsf-admin/package.json index 4e1d8b2..4cc1046 100644 --- a/rsf-admin/package.json +++ b/rsf-admin/package.json @@ -45,7 +45,8 @@ "react-to-print": "^2.14.11", "svgpath": "^2.6.0", "three": "^0.155.0", - "tweedle.js": "^2.1.0" + "tweedle.js": "^2.1.0", + "recharts": "^2.15.0" }, "devDependencies": { "@types/node": "^20.10.7", @@ -63,4 +64,4 @@ "vite": "^5.3.5" }, "name": "rsf" -} +} \ No newline at end of file diff --git a/rsf-admin/src/page/dashboard/NbChart.jsx b/rsf-admin/src/page/dashboard/NbChart.jsx new file mode 100644 index 0000000..2ed9df8 --- /dev/null +++ b/rsf-admin/src/page/dashboard/NbChart.jsx @@ -0,0 +1,117 @@ +import * as React from 'react'; +import { Card, CardHeader, CardContent } from '@mui/material'; +import { + ResponsiveContainer, + AreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + Tooltip, +} from 'recharts'; +import { useTranslate } from 'react-admin'; +import { format, subDays, addDays } from 'date-fns'; + +const lastDay = new Date(); +const lastMonthDays = Array.from({ length: 30 }, (_, i) => subDays(lastDay, i)); +const aMonthAgo = subDays(new Date(), 30); + +const dateFormatter = (date) => + new Date(date).toLocaleDateString(); + +const aggregateOrdersByDay = (orders) => + orders + .filter((order) => order.status !== 'cancelled') + .reduce( + (acc, curr) => { + const day = format(curr.date, 'yyyy-MM-dd'); + if (!acc[day]) { + acc[day] = 0; + } + acc[day] += curr.total; + return acc; + }, + {} + ); + +const getRevenuePerDay = (orders) => { + const daysWithRevenue = aggregateOrdersByDay(orders); + return lastMonthDays.map(date => ({ + date: date.getTime(), + total: daysWithRevenue[format(date, 'yyyy-MM-dd')] || 0, + })); +}; + +const NbChart = (props) => { + const { orders } = props; + const translate = useTranslate(); + if (!orders) return null; + + return ( + <Card> + <CardHeader title={translate('pos.dashboard.month_history')} /> + <CardContent> + <div style={{ width: '100%', height: 300 }}> + <ResponsiveContainer> + <AreaChart data={getRevenuePerDay(orders)}> + <defs> + <linearGradient + id="colorUv" + x1="0" + y1="0" + x2="0" + y2="1" + > + <stop + offset="5%" + stopColor="#8884d8" + stopOpacity={0.8} + /> + <stop + offset="95%" + stopColor="#8884d8" + stopOpacity={0} + /> + </linearGradient> + </defs> + <XAxis + dataKey="date" + name="Date" + type="number" + scale="time" + domain={[ + addDays(aMonthAgo, 1).getTime(), + new Date().getTime(), + ]} + tickFormatter={dateFormatter} + /> + <YAxis dataKey="total" name="Revenue" unit="鈧�" /> + <CartesianGrid strokeDasharray="3 3" /> + <Tooltip + cursor={{ strokeDasharray: '3 3' }} + formatter={(value) => + new Intl.NumberFormat(undefined, { + style: 'currency', + currency: 'USD', + }).format(value) + } + labelFormatter={(label) => + dateFormatter(label) + } + /> + <Area + type="monotone" + dataKey="total" + stroke="#8884d8" + strokeWidth={2} + fill="url(#colorUv)" + /> + </AreaChart> + </ResponsiveContainer> + </div> + </CardContent> + </Card> + ); +}; + +export default NbChart; diff --git a/rsf-admin/src/page/dashboard/Welcome.jsx b/rsf-admin/src/page/dashboard/Welcome.jsx index 460c244..756ad55 100644 --- a/rsf-admin/src/page/dashboard/Welcome.jsx +++ b/rsf-admin/src/page/dashboard/Welcome.jsx @@ -23,10 +23,11 @@ ${theme.palette.primary.dark} 100%)`, color: theme => theme.palette.primary.contrastText, padding: '20px', - paddingBottom: '50px', + // paddingBottom: '50px', marginTop: 2, marginBottom: '1em', width: '100%', + overflow: 'visible' }} > <Box display="flex"> diff --git a/rsf-admin/src/page/dashboard/index.jsx b/rsf-admin/src/page/dashboard/index.jsx index 9a8c629..c02706a 100644 --- a/rsf-admin/src/page/dashboard/index.jsx +++ b/rsf-admin/src/page/dashboard/index.jsx @@ -10,6 +10,7 @@ import WifiIcon from '@mui/icons-material/Wifi'; import request from '@/utils/request'; import { Box, Typography, LinearProgress, Stack } from '@mui/material'; +import NbChart from "./NbChart"; const styles = { flex: { display: 'flex' }, @@ -85,6 +86,29 @@ }) }, []) + const recentOrders = [ + { + date: "2025-08-10T12:23:56.959Z", + total: 138.94 + }, + { + date: "2025-08-03T07:45:00.304Z", + total: 214.66 + }, + { + date: "2025-07-28T00:20:10.968Z", + total: 68.19 + }, + { + date: "2025-07-22T20:39:00.293Z", + total: 36.56 + }, + { + date: "2025-07-16T17:40:24.791Z", + total: 100.82 + }, + ] + return ( <> <div style={styles.flex}> @@ -103,7 +127,7 @@ /> </div> <div style={styles.singleCol}> - {/* <OrderChart orders={recentOrders} /> */} + <NbChart orders={recentOrders} /> </div> <div style={styles.singleCol}> {/* <PendingOrders orders={pendingOrders} /> */} -- Gitblit v1.9.1