diff --git a/AspNetServer/Data/AppDb.db b/AspNetServer/Data/AppDb.db index 5bf4ec1..353915a 100644 Binary files a/AspNetServer/Data/AppDb.db and b/AspNetServer/Data/AppDb.db differ diff --git a/AspNetServer/bin/Debug/net6.0/Data/AppDb.db b/AspNetServer/bin/Debug/net6.0/Data/AppDb.db index 5bf4ec1..353915a 100644 Binary files a/AspNetServer/bin/Debug/net6.0/Data/AppDb.db and b/AspNetServer/bin/Debug/net6.0/Data/AppDb.db differ diff --git a/react-client/package-lock.json b/react-client/package-lock.json index 63e6f08..d768ef6 100644 --- a/react-client/package-lock.json +++ b/react-client/package-lock.json @@ -8,11 +8,16 @@ "name": "react-client", "version": "0.1.0", "dependencies": { + "@babel/core": "^7.15.8", + "@babel/preset-env": "^7.15.8", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "babel-core": "^7.0.0-bridge.0", "boxicons": "^2.1.4", + "chart.js": "^4.0.0", "react": "^18.2.0", + "react-chartjs-2": "^5.0.0", "react-dom": "^18.2.0", "react-icons": "^4.10.1", "react-router-dom": "^6.15.0", @@ -3125,6 +3130,11 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -5304,6 +5314,14 @@ "dequal": "^2.0.3" } }, + "node_modules/babel-core": { + "version": "7.0.0-bridge.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", + "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/babel-jest": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", @@ -5990,6 +6008,17 @@ "node": ">=10" } }, + "node_modules/chart.js": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.0.tgz", + "integrity": "sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=7" + } + }, "node_modules/check-types": { "version": "11.2.2", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.2.tgz", @@ -14651,6 +14680,15 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, + "node_modules/react-chartjs-2": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz", + "integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==", + "peerDependencies": { + "chart.js": "^4.1.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", diff --git a/react-client/package.json b/react-client/package.json index cdd1ca1..1755a5a 100644 --- a/react-client/package.json +++ b/react-client/package.json @@ -3,11 +3,16 @@ "version": "0.1.0", "private": true, "dependencies": { + "@babel/core": "^7.15.8", + "@babel/preset-env": "^7.15.8", + "babel-core": "^7.0.0-bridge.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "boxicons": "^2.1.4", + "chart.js": "^4.0.0", "react": "^18.2.0", + "react-chartjs-2": "^5.0.0", "react-dom": "^18.2.0", "react-icons": "^4.10.1", "react-router-dom": "^6.15.0", diff --git a/react-client/src/components/BarDiagram.js b/react-client/src/components/BarDiagram.js new file mode 100644 index 0000000..e3778ae --- /dev/null +++ b/react-client/src/components/BarDiagram.js @@ -0,0 +1,57 @@ +import React from "react"; +import { + CategoryScale, + Chart as ChartJS, + Legend, + LinearScale, + LineElement, + PointElement, + Title, + Tooltip, +} from "chart.js"; +import {Line} from "react-chartjs-2"; + +ChartJS.register( + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend +); + +ChartJS.defaults.color = "white"; + +export default function BarDiagram(dataset, labels, titleDates) { + const options = { + responsive: true, + plugins: { + legend: { + position: "none", + }, + title: { + display: true, + text: `Средние сахара за ${titleDates}`, + color: "white", + } + } + }; + + const data = { + labels, + datasets: [ + { + data: dataset, + borderColor: "rgba(116, 33, 72, 1)", + backgroundColor: "rgba(116, 33, 72, 0.5)", + } + ], + }; + + return ( +
+ +
+ ) +} \ No newline at end of file diff --git a/react-client/src/index.css b/react-client/src/index.css index 2b707ae..396fc2e 100644 --- a/react-client/src/index.css +++ b/react-client/src/index.css @@ -85,7 +85,7 @@ main { position: relative; margin-top: 2ex; left: 68px; - width: calc(100% - 56.8px); + width: calc(100% - 68px); display: flex; justify-content: space-evenly; height: 100%; @@ -93,6 +93,7 @@ main { .board-view-centered { align-items: center; + flex-direction: column; } .input-stats-area { @@ -119,6 +120,12 @@ main { margin-top: 2ex; } +.text-stats { + display: flex; + flex-direction: row; + gap: 3ex; +} + .input-single-date { display: flex; flex-direction: column; @@ -322,6 +329,22 @@ ul { list-style-type: none; } +.single-diagram-wrapper { + padding: 2ex; + box-shadow: inset 0 0 0 3000px rgba(150, 150, 150, 0.292); + backdrop-filter: blur(10px); + border-radius: 2ex; +} + +.single-diagram { + width: 450px; + height: 200px; + padding: 1ex; + background: rgba(255, 255, 255, 0.192); + backdrop-filter: blur(10px); + border-radius: 1ex; +} + @media screen and (max-width: 1180px) { .board-view { display: flex; diff --git a/react-client/src/pages/Stats.js b/react-client/src/pages/Stats.js index d5c397a..765d725 100644 --- a/react-client/src/pages/Stats.js +++ b/react-client/src/pages/Stats.js @@ -1,250 +1,298 @@ import React, {useEffect, useState} from "react" import Constants from "../utilities/Constants"; +import BarDiagram from "../components/BarDiagram"; export default function Stats() { - //#region Константы - const initialFormData = Object.freeze({ - date1: null, - date2: null, - }); - - const [sugars, setSugars] = useState(0); - const [daySugars, setDaySugars] = useState(0); - const [weekSugars, setWeekSugars] = useState(0); - const [monthSugars, setMonthSugars] = useState(0); - const [insulins, setInsulins] = useState([]); - const [catheters, setCatheters] = useState([]); - const [dates, setDates] = useState("date1:date2"); - const [formData, setFormData] = useState(initialFormData); - - //#endregion - - //#region Работа с датами - function getToday(plusDays = 0) { - let today = new Date(); - today = new Date(today.getFullYear(), today.getMonth(), today.getDate() + plusDays) - let dd = String(today.getDate()).padStart(2, "0"); - let mm = String(today.getMonth() + 1).padStart(2, "0"); //January is 0! - let yyyy = today.getFullYear(); - - return dd + "." + mm + "." + yyyy; - } - - function toShortDateString(date = getToday()) { - let d = date.split("-") - return d[2] + "." + d[1]; - } + //#region Константы + const initialFormData = Object.freeze({ + date1: null, + date2: null, + }); + + const [sugars, setSugars] = useState(0); + const [diagramSugars, setDiagramSugars] = useState([]) + const [diagramDates, setDiagramDates] = useState([]) + const [daySugars, setDaySugars] = useState(0); + const [weekSugars, setWeekSugars] = useState(0); + const [monthSugars, setMonthSugars] = useState(0); + const [insulins, setInsulins] = useState([]); + const [catheters, setCatheters] = useState([]); + const [dates, setDates] = useState("date1:date2"); + const [formData, setFormData] = useState(initialFormData); + + //#endregion + + //#region Работа с датами + function getToday(plusDays = 0) { + let today = new Date(); + today = new Date(today.getFullYear(), today.getMonth(), today.getDate() + plusDays) + let dd = String(today.getDate()).padStart(2, "0"); + let mm = String(today.getMonth() + 1).padStart(2, "0"); //January is 0! + let yyyy = today.getFullYear(); + + return dd + "." + mm + "." + yyyy; + } + + function toShortDateString(date = getToday()) { + let d = date.split("-") + return d[2] + "." + d[1]; + } + + function toShortDateString2(date) { + let d = date.split(" ") + return d[1]; + } + + + //#endregion + + //#region Загрузка данных + useEffect(() => { + let url = Constants.API_URL_GET_LAST_INSULIN; + + fetch(url, { + method: "GET" + }).then(response => response.json()) + .then(insulinFromServer => { + setInsulins(insulinFromServer); + }) + .catch((error) => { + console.log(error); + alert(error) + }) + + url = Constants.API_URL_GET_LAST_CATHETER; + + fetch(url, { + method: "GET" + }).then(response => response.json()) + .then(cathetersFromServer => { + setCatheters(cathetersFromServer); + }) + .catch((error) => { + console.log(error); + alert(error) + }) + + url = `${Constants.API_URL_GET_AVG_SUGARS}/?date1=${getToday()}`; + + fetch(url, { + method: "GET" + }).then(response => response.json()) + .then(sugarsFromServer => { + setDaySugars(sugarsFromServer); + }) + .catch((error) => { + console.log(error); + alert(error) + }) + + url = `${Constants.API_URL_GET_AVG_SUGARS}/?date1=${getToday(-7)}&date2=${getToday()}`; + + fetch(url, { + method: "GET" + }).then(response => response.json()) + .then(sugarsFromServer => { + setWeekSugars(sugarsFromServer); + }) + .catch((error) => { + console.log(error); + alert(error) + }) + + url = `${Constants.API_URL_GET_AVG_SUGARS}/?date1=${getToday(-30)}&date2=${getToday()}`; + + fetch(url, { + method: "GET" + }).then(response => response.json()) + .then(sugarsFromServer => { + setMonthSugars(sugarsFromServer); + }) + .catch((error) => { + console.log(error); + alert(error) + }) + }, []); + //#endregion + + //#region Отображение данных на странице + function renderSugarTable() { + return ( +
+
+ +
+
+ ) + } + function renderInsulinTable() { + return ( +
+
+
+
{insulins["time"]}
+
+
+
+ ) + } - //#endregion - - //#region Загрузка данных - useEffect(() => { - let url = Constants.API_URL_GET_LAST_INSULIN; - - fetch(url, { - method: "GET" - }).then(response => response.json()) - .then(insulinFromServer => { - setInsulins(insulinFromServer); - }) - .catch((error) => { - console.log(error); - alert(error) - }) - - url = Constants.API_URL_GET_LAST_CATHETER; - - fetch(url, { - method: "GET" - }).then(response => response.json()) - .then(cathetersFromServer => { - setCatheters(cathetersFromServer); - }) - .catch((error) => { - console.log(error); - alert(error) - }) - - url = `${Constants.API_URL_GET_AVG_SUGARS}/?date1=${getToday()}`; - - fetch(url, { - method: "GET" - }).then(response => response.json()) - .then(sugarsFromServer => { - setDaySugars(sugarsFromServer); - }) - .catch((error) => { - console.log(error); - alert(error) - }) - - url = `${Constants.API_URL_GET_AVG_SUGARS}/?date1=${getToday(-7)}&date2=${getToday()}`; - - fetch(url, { - method: "GET" - }).then(response => response.json()) - .then(sugarsFromServer => { - setWeekSugars(sugarsFromServer); - }) - .catch((error) => { - console.log(error); - alert(error) - }) - - url = `${Constants.API_URL_GET_AVG_SUGARS}/?date1=${getToday(-30)}&date2=${getToday()}`; - - fetch(url, { - method: "GET" - }).then(response => response.json()) - .then(sugarsFromServer => { - setMonthSugars(sugarsFromServer); - }) - .catch((error) => { - console.log(error); - alert(error) - }) - }, []); - //#endregion - - //#region Отображение данных на странице - function renderSugarTable() { - return ( -
-
- -
-
- ) + function renderCathetersTable() { + return ( +
+
+
+
{catheters["time"]}
+
+
+
+ ) + } + + //#endregion + + const handleChange = (event) => { + setFormData({ + ...formData, + [event.target.name]: event.target.value, + }) + }; + + const handleSubmit = (event) => { + event.preventDefault(); + + let date1 = formData.date1; + let date2 = formData.date2; + + if (formData.date1 == null && formData.date2 == null) { + date1 = getToday(); + date2 = getToday(); + } else if (formData.date1 == null) { + date1 = date2; + } else if (formData.date2 == null) { + date2 = date1; } - function renderInsulinTable() { - return ( -
-
-
-
{insulins["time"]}
-
-
+ let url = `${Constants.API_URL_GET_AVG_SUGARS}?date1=${date1}&date2=${date2}`; + setDates(`${toShortDateString(date1)} - ${toShortDateString(date2)}`) + + fetch(url, { + method: "GET" + }).then(response => response.json()) + .then(sugarsFromServer => { + setSugars(sugarsFromServer); + }) + .catch((error) => { + console.log(error); + alert(error) + }) + + url = `${Constants.API_URL_GET_SUGARS}?date1=${date1}&date2=${date2}`; + + fetch(url, { + method: "GET" + }).then(response => response.json()) + .then(sugarsFromServer => { + let uniqueDates = sugarsFromServer.map(q => toShortDateString2(q.time)); + uniqueDates = [...new Set(uniqueDates)]; + setDiagramDates(uniqueDates); + + let datesMap = new Map([]); + uniqueDates.forEach(q => datesMap.set(q, [0, 0])) + + // q: дата + // [x, y]: x - сумма сахара за дату, y - количество сложенных сахаров + // + // условно, если 15.08.2023 было занесено два сахара: 3.0 и 4.0, то: + // datesMap = {"15.08.2023": [7.0, 2]} + sugarsFromServer.forEach(q => { + let tempDate = toShortDateString2(q.time); + let sugarSum = datesMap.get(tempDate)[0] + q.sugar; + let sugarCount = datesMap.get(tempDate)[1] + 1; + datesMap.set(tempDate, [sugarSum, sugarCount]) + }); + + setDiagramSugars([...datesMap.values()].map(q => (q[0] / q[1]).toFixed(1))); + }) + .catch((error) => { + console.log(error); + alert(error) + }) + }; + + return ( +
+
+
+
+ Дата 1 + +
+
+ Дата 2 + +
+
+ +
+
+ +
+
+
+

Средний сахар

- ) - } - function renderCathetersTable() { - return ( -
-
-
-
{catheters["time"]}
-
-
+ {renderSugarTable()} +
+ +
+
+

Последний инсулин

- ) - } - //#endregion - - const handleChange = (event) => { - setFormData({ - ...formData, - [event.target.name]: event.target.value, - }) - }; - - const handleSubmit = (event) => { - event.preventDefault(); - - let date1 = formData.date1; - let date2 = formData.date2; - - if (formData.date1 == null && formData.date2 == null) { - date1 = getToday(); - date2 = getToday(); - } else if (formData.date1 == null) { - date1 = date2; - } else if (formData.date2 == null) { - date2 = date1; - } - - let url = `${Constants.API_URL_GET_AVG_SUGARS}?date1=${date1}&date2=${date2}`; - setDates(`${toShortDateString(date1)} - ${toShortDateString(date2)}`) - - fetch(url, { - method: "GET" - }).then(response => response.json()) - .then(sugarsFromServer => { - setSugars(sugarsFromServer); - }) - .catch((error) => { - console.log(error); - alert(error) - }) - }; + {renderInsulinTable()} +
- return ( -
-
-
-
- Дата 1 - -
-
- Дата 2 - -
-
- -
-
- -
-
-

Средний сахар

-
- - {renderSugarTable()} -
- -
-
-

Последний инсулин

-
- - {renderInsulinTable()} -
- -
-
-

Последний катетер

-
- - {renderCathetersTable()} -
+
+
+

Последний катетер

-
- ) + + {renderCathetersTable()} +
+
+ +
+ {diagramSugars !== [] && + BarDiagram(diagramSugars, diagramDates, dates) + } +
+
+
+ ) }