diff --git a/README.md b/README.md index c013ae4f41..f957633507 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ As rule of thumb, merge with squash is preferred over merge. Rebase is not advis ### Lerna -This project uses [lerna](https://github.com/lerna/lerna) and [craco](https://github.com/gsoft-inc/craco) +This project uses [lerna](https://github.com/lerna/lerna) and [vitejs](https://vitejs.dev/config/) These tools allow to handle a monorepo with multiple webapps which share common components. The content of monorepo is: @@ -60,8 +60,7 @@ The content of monorepo is: - packages/pn-pa-webapp app for public administration - packages/pn-personafisica-webapp app for citizens - packages/pn-personafisica-login login section for citizen app - -https://medium.com/geekculture/setting-up-monorepo-with-create-react-app-cb2cfa763b96 +- packages/pn-data-viz component-library for statistical data visualization for SEND ### Sonar @@ -73,7 +72,7 @@ You can run a task analysis with sonar-scanner using this script in each package ### Versioning -These scripts use [lerna version](https://github.com/lerna/lerna/blob/main/commands/version/README.md). +These scripts use [lerna version](https://github.com/lerna/lerna/tree/main/libs/commands/version#readme). Release a prepatch: diff --git a/package.json b/package.json index df9545f755..24d40d2997 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { - "name": "send-monorepo", + "name": "pn-frontend", "private": true, + "license": "SEE LICENS IN LICENSE", "devDependencies": { "lerna": "^6.4.1" }, diff --git a/packages/pn-data-viz/.gitignore b/packages/pn-data-viz/.gitignore new file mode 100644 index 0000000000..bb6b5d1104 --- /dev/null +++ b/packages/pn-data-viz/.gitignore @@ -0,0 +1,28 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage +test-report.xml + +# sonar +/.scannerwork + +# production +/build +/dist + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/packages/pn-data-viz/README.md b/packages/pn-data-viz/README.md new file mode 100644 index 0000000000..d3a0abe659 --- /dev/null +++ b/packages/pn-data-viz/README.md @@ -0,0 +1,11 @@ +# `pn-data-viz` + +> TODO: description + +## Usage + +``` +const pnDataViz = require('pn-data-viz'); + +// TODO: DEMONSTRATE API +``` diff --git a/packages/pn-data-viz/package.json b/packages/pn-data-viz/package.json new file mode 100644 index 0000000000..7206eb5ee5 --- /dev/null +++ b/packages/pn-data-viz/package.json @@ -0,0 +1,36 @@ +{ + "name": "@pagopa-pn/pn-data-viz", + "version": "0.0.1", + "private": true, + "description": "SEND component library for data visualization", + "keywords": [ + "data-viz", + "charts", + "send" + ], + "homepage": "https://github.com/pagopa/pn-frontend/pn-data-viz#readme", + "main": "src/pn-data-viz.js", + "dependencies": { + "@types/echarts": "^4.9.22", + "echarts": "^5.5.0", + "typescript": "^5.2.2" + }, + "scripts": { + "test": "node ./__tests__/pn-data-viz.test.js" + }, + "peerDependencies": { + "@mui/material": "^5.14.5", + "react": "^18.2.0" + }, + "devDependencies": { + "eslint": "7.11.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-functional": "^3.7.2", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-react": "^7.27.1", + "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-sonarjs": "^0.10.0", + "vite": "^5.1.4", + "vitest": "1.3.1" + } +} diff --git a/packages/pn-data-viz/src/PnEcharts.tsx b/packages/pn-data-viz/src/PnEcharts.tsx new file mode 100644 index 0000000000..d4c0f60466 --- /dev/null +++ b/packages/pn-data-viz/src/PnEcharts.tsx @@ -0,0 +1,72 @@ +import senderDashboard from "./theme/senderDashboard"; +import { init, getInstanceByDom, registerTheme } from "echarts"; +import type { EChartOption, ECharts, SetOptionOpts } from "echarts"; +import React, { useRef, useEffect } from "react"; +import type { CSSProperties } from "react"; + +export interface PnEChartsProps { + option: EChartOption; + style?: CSSProperties; + settings?: SetOptionOpts; + loading?: boolean; + theme?: "light" | "dark" | object; +} + +export function PnECharts({ + option, + style, + settings, + loading, + theme, +}: PnEChartsProps): JSX.Element { + const chartRef = useRef(null); + + useEffect(() => { + // Initialize chart + let chart: ECharts | undefined; + if (chartRef.current !== null) { + let selectedTheme = "defaultTheme"; + + registerTheme("defaultTheme", senderDashboard); + if (typeof theme === "object") { + registerTheme("customTheme", theme); + selectedTheme = "customTheme"; + } + chart = init(chartRef.current, selectedTheme); + } + + // Add chart resize listener + // ResizeObserver is leading to a bit janky UX + function resizeChart() { + chart?.resize(); + } + window.addEventListener("resize", resizeChart); + + // Return cleanup function + return () => { + chart?.dispose(); + window.removeEventListener("resize", resizeChart); + }; + }, [theme]); + + useEffect(() => { + // Update chart + if (chartRef.current !== null) { + const chart = getInstanceByDom(chartRef.current); + chart?.setOption(option, settings); + } + }, [option, settings, theme]); // Whenever theme changes we need to add option and setting due to it being deleted in cleanup function + + useEffect(() => { + // Update chart + if (chartRef.current !== null) { + const chart = getInstanceByDom(chartRef.current); + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + loading === true ? chart?.showLoading() : chart?.hideLoading(); + } + }, [loading, theme]); + + return ( +
+ ); +} diff --git a/packages/pn-data-viz/src/theme/senderDashboard.ts b/packages/pn-data-viz/src/theme/senderDashboard.ts new file mode 100644 index 0000000000..47f7a1cf8e --- /dev/null +++ b/packages/pn-data-viz/src/theme/senderDashboard.ts @@ -0,0 +1,356 @@ +const senderDashboard: object = { + color: ["#0073e6", "#00c5ca", "#6bcffb", "#fe6666", "#ffcb46", "#6cc66a"], + backgroundColor: "rgba(252,252,252,0)", + textStyle: {}, + title: { + textStyle: { + color: "#17324d", + }, + subtextStyle: { + color: "#5c6f82", + }, + }, + line: { + itemStyle: { + borderWidth: "2", + }, + lineStyle: { + width: "3", + }, + symbolSize: "8", + symbol: "emptyCircle", + smooth: false, + }, + radar: { + itemStyle: { + borderWidth: "2", + }, + lineStyle: { + width: "3", + }, + symbolSize: "8", + symbol: "emptyCircle", + smooth: false, + }, + bar: { + itemStyle: { + barBorderWidth: "1", + barBorderColor: "#e3e7eb", + }, + }, + pie: { + itemStyle: { + borderWidth: "1", + borderColor: "#e3e7eb", + }, + }, + scatter: { + itemStyle: { + borderWidth: "1", + borderColor: "#e3e7eb", + }, + }, + boxplot: { + itemStyle: { + borderWidth: "1", + borderColor: "#e3e7eb", + }, + }, + parallel: { + itemStyle: { + borderWidth: "1", + borderColor: "#e3e7eb", + }, + }, + sankey: { + itemStyle: { + borderWidth: "1", + borderColor: "#e3e7eb", + }, + }, + funnel: { + itemStyle: { + borderWidth: "1", + borderColor: "#e3e7eb", + }, + }, + gauge: { + itemStyle: { + borderWidth: "1", + borderColor: "#e3e7eb", + }, + }, + candlestick: { + itemStyle: { + color: "#00c5ca", + color0: "transparent", + borderColor: "#00c5ca", + borderColor0: "#0073e6", + borderWidth: "2", + }, + }, + graph: { + itemStyle: { + borderWidth: "1", + borderColor: "#e3e7eb", + }, + lineStyle: { + width: "1", + color: "#e3e7eb", + }, + symbolSize: "8", + symbol: "emptyCircle", + smooth: false, + color: ["#0073e6", "#00c5ca", "#6bcffb", "#fe6666", "#ffcb46", "#6cc66a"], + label: { + color: "#ffffff", + }, + }, + map: { + itemStyle: { + areaColor: "#eeeeee", + borderColor: "#aaaaaa", + borderWidth: 0.5, + }, + label: { + color: "#ffffff", + }, + emphasis: { + itemStyle: { + areaColor: "rgba(63,177,227,0.25)", + borderColor: "#3fb1e3", + borderWidth: 1, + }, + label: { + color: "#3fb1e3", + }, + }, + }, + geo: { + itemStyle: { + areaColor: "#eeeeee", + borderColor: "#aaaaaa", + borderWidth: 0.5, + }, + label: { + color: "#ffffff", + }, + emphasis: { + itemStyle: { + areaColor: "rgba(63,177,227,0.25)", + borderColor: "#3fb1e3", + borderWidth: 1, + }, + label: { + color: "#3fb1e3", + }, + }, + }, + categoryAxis: { + axisLine: { + show: true, + lineStyle: { + color: "#e3e7eb", + }, + }, + axisTick: { + show: false, + lineStyle: { + color: "#333", + }, + }, + axisLabel: { + show: true, + color: "#5c6f82", + }, + splitLine: { + show: true, + lineStyle: { + color: ["#f6f7f8"], + }, + }, + splitArea: { + show: false, + areaStyle: { + color: ["rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)"], + }, + }, + }, + valueAxis: { + axisLine: { + show: true, + lineStyle: { + color: "#e3e7eb", + }, + }, + axisTick: { + show: false, + lineStyle: { + color: "#333", + }, + }, + axisLabel: { + show: true, + color: "#5c6f82", + }, + splitLine: { + show: true, + lineStyle: { + color: ["#f6f7f8"], + }, + }, + splitArea: { + show: false, + areaStyle: { + color: ["rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)"], + }, + }, + }, + logAxis: { + axisLine: { + show: true, + lineStyle: { + color: "#e3e7eb", + }, + }, + axisTick: { + show: false, + lineStyle: { + color: "#333", + }, + }, + axisLabel: { + show: true, + color: "#5c6f82", + }, + splitLine: { + show: true, + lineStyle: { + color: ["#f6f7f8"], + }, + }, + splitArea: { + show: false, + areaStyle: { + color: ["rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)"], + }, + }, + }, + timeAxis: { + axisLine: { + show: true, + lineStyle: { + color: "#e3e7eb", + }, + }, + axisTick: { + show: false, + lineStyle: { + color: "#333", + }, + }, + axisLabel: { + show: true, + color: "#5c6f82", + }, + splitLine: { + show: true, + lineStyle: { + color: ["#f6f7f8"], + }, + }, + splitArea: { + show: false, + areaStyle: { + color: ["rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)"], + }, + }, + }, + toolbox: { + iconStyle: { + borderColor: "#a2adb8", + }, + emphasis: { + iconStyle: { + borderColor: "#5c6f82", + }, + }, + }, + legend: { + textStyle: { + color: "#5c6f82", + }, + }, + tooltip: { + axisPointer: { + lineStyle: { + color: "#e3e7eb", + width: 1, + }, + crossStyle: { + color: "#e3e7eb", + width: 1, + }, + }, + }, + timeline: { + lineStyle: { + color: "#5c6f82", + width: 1, + }, + itemStyle: { + color: "#5c6f82", + borderWidth: "1", + }, + controlStyle: { + color: "#5c6f82", + borderColor: "#5c6f82", + borderWidth: "0.5", + }, + checkpointStyle: { + color: "#0073e6", + borderColor: "#5c6f82", + }, + label: { + color: "#5c6f82", + }, + emphasis: { + itemStyle: { + color: "#5c6f82", + }, + controlStyle: { + color: "#5c6f82", + borderColor: "#5c6f82", + borderWidth: "0.5", + }, + label: { + color: "#5c6f82", + }, + }, + }, + visualMap: { + color: ["#0073e6", "#ebf4fd"], + }, + dataZoom: { + backgroundColor: "rgba(255,255,255,0)", + dataBackgroundColor: "rgba(222,222,222,1)", + fillerColor: "rgba(114,230,212,0.25)", + handleColor: "#cccccc", + handleSize: "100%", + textStyle: { + color: "#999999", + }, + }, + markPoint: { + label: { + color: "#ffffff", + }, + emphasis: { + label: { + color: "#ffffff", + }, + }, + }, +}; + +export default senderDashboard; diff --git a/packages/pn-pa-webapp/src/App.tsx b/packages/pn-pa-webapp/src/App.tsx index f8b1c0c7f0..850a88395e 100644 --- a/packages/pn-pa-webapp/src/App.tsx +++ b/packages/pn-pa-webapp/src/App.tsx @@ -6,6 +6,7 @@ import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import Email from '@mui/icons-material/Email'; import ErrorIcon from '@mui/icons-material/Error'; import HelpIcon from '@mui/icons-material/Help'; +import StatisticsIcon from '@mui/icons-material/ShowChart'; import VpnKey from '@mui/icons-material/VpnKey'; import { Box } from '@mui/material'; import { @@ -113,6 +114,7 @@ const ActualApp = () => { const menuItems = useMemo(() => { const basicMenuItems: Array = [ { label: 'menu.notifications', icon: Email, route: routes.DASHBOARD }, + { label: 'Statistiche', icon: StatisticsIcon, route: routes.STATISTICHE }, /** * Refers to PN-1741 * Commented out because beyond MVP scope diff --git a/packages/pn-pa-webapp/src/components/Statistics/FiledNotificationsStatistics.tsx b/packages/pn-pa-webapp/src/components/Statistics/FiledNotificationsStatistics.tsx new file mode 100644 index 0000000000..f06d50103b --- /dev/null +++ b/packages/pn-pa-webapp/src/components/Statistics/FiledNotificationsStatistics.tsx @@ -0,0 +1,79 @@ +import React from 'react'; + +import { Grid, Paper, Stack, Typography } from '@mui/material'; +import { useIsMobile } from '@pagopa-pn/pn-commons'; + +import { PnECharts, PnEChartsProps } from '../../../../pn-data-viz/src/PnEcharts'; + +// import theme from './senderDashboard'; + +const FiledNotificationsStatistics: React.FC = () => { + const title = 'Notifiche depositate'; + const description = + 'Numero di nofifiche che sono state accettate dalla piattaforma SEND sul totale delle notifiche caricate'; + const notificationsAmount = '11.560'; + const notificationsPercent = '69,5%'; + const textPercent = notificationsPercent + ' del totale delle notifihe caricate su SEND'; + + const isMobile = useIsMobile(); + + const option: PnEChartsProps['option'] = { + tooltip: { + trigger: 'item', + }, + /* legend: { + top: '5%', + left: 'center', + }, */ + series: [ + { + // name: 'Access From', + type: 'pie', + radius: ['60%', '100%'], + center: ['50%', '90%'], + // adjust the start and end angle + startAngle: 180, + endAngle: 360, + data: [ + { value: 11560, name: 'Notifiche Depositate' }, + { value: 9073, name: 'Notifiche non Depositate' }, + /* { value: 580, name: 'Email' }, + { value: 484, name: 'Union Ads' }, + { value: 300, name: 'Video Ads' }, */ + ], + }, + ], + color: ['#0073e6', '#cccccc'], // customize color to override autoselection from theme palette + }; + + const direction = isMobile ? 'column' : 'row'; + const spacing = isMobile ? 3 : 0; + return ( + // + + + + + + {title} + + + {description} + + notificationsFiledPercent + + {notificationsAmount} + + {textPercent} + + + + + + + + // + ); +}; + +export default FiledNotificationsStatistics; diff --git a/packages/pn-pa-webapp/src/navigation/routes.const.ts b/packages/pn-pa-webapp/src/navigation/routes.const.ts index 704309430a..94ec2754ff 100644 --- a/packages/pn-pa-webapp/src/navigation/routes.const.ts +++ b/packages/pn-pa-webapp/src/navigation/routes.const.ts @@ -2,6 +2,7 @@ import { PRIVACY_LINK_RELATIVE_PATH as PRIVACY_POLICY, TOS_LINK_RELATIVE_PATH as TERMS_OF_SERVICE, } from '@pagopa-pn/pn-commons'; + import { getConfiguration } from '../services/configuration.service'; export const DASHBOARD = '/dashboard'; diff --git a/packages/pn-pa-webapp/src/navigation/routes.tsx b/packages/pn-pa-webapp/src/navigation/routes.tsx index 3f9f5e73ff..822c03311d 100644 --- a/packages/pn-pa-webapp/src/navigation/routes.tsx +++ b/packages/pn-pa-webapp/src/navigation/routes.tsx @@ -11,6 +11,7 @@ import NewApiKey from '../pages/NewApiKey.page'; import NewNotification from '../pages/NewNotification.page'; import NotificationDetail from '../pages/NotificationDetail.page'; import PrivacyPolicyPage from '../pages/PrivacyPolicy.page'; +import Statistics from '../pages/Statistics.page'; import TermsOfServicePage from '../pages/TermsOfService.page'; import { getConfiguration } from '../services/configuration.service'; import RouteGuard from './RouteGuard'; @@ -31,10 +32,11 @@ function Router() { }> }> } /> + } /> } /> - {getConfiguration().IS_MANUAL_SEND_ENABLED && + {getConfiguration().IS_MANUAL_SEND_ENABLED && ( } /> - } + )} } /> {/** * Refers to PN-1741 diff --git a/packages/pn-pa-webapp/src/pages/Statistics.page.tsx b/packages/pn-pa-webapp/src/pages/Statistics.page.tsx index b9a23e971d..3d4bbbcaec 100644 --- a/packages/pn-pa-webapp/src/pages/Statistics.page.tsx +++ b/packages/pn-pa-webapp/src/pages/Statistics.page.tsx @@ -1,8 +1,10 @@ -import { Box, Button, Grid, Typography } from '@mui/material'; import { useTranslation } from 'react-i18next'; -import { TitleBox } from '@pagopa-pn/pn-commons'; + import { Download } from '@mui/icons-material'; +import { Box, Button, Grid, Typography } from '@mui/material'; +import { TitleBox } from '@pagopa-pn/pn-commons'; +import FiledNotificationsStatistics from '../components/Statistics/FiledNotificationsStatistics'; import { useAppSelector } from '../redux/hooks'; import { RootState } from '../redux/store'; @@ -24,6 +26,11 @@ const Statistics = () => { return ( + + {/* + + + */} ); }; diff --git a/yarn.lock b/yarn.lock index 0619483685..cd9ade09ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2839,6 +2839,13 @@ resolved "https://registry.yarnpkg.com/@types/css-mediaquery/-/css-mediaquery-0.1.1.tgz#0902ee5849b89a45390c2c9dccbb536a75a88511" integrity sha512-JQ+sPiPlRUHmlL4e3DBUNbxVEb6p7dis78/uSDbQpkeCKVoepChZMWGPIVA2JIH0ylfkA9S+TZUdShlgDpFKrw== +"@types/echarts@^4.9.22": + version "4.9.22" + resolved "https://registry.yarnpkg.com/@types/echarts/-/echarts-4.9.22.tgz#b584fd3d312f106dd8cacb53f689573a6953d822" + integrity sha512-7Fo6XdWpoi8jxkwP7BARUOM7riq8bMhmsCtSG8gzUcJmFhLo387tihoBYS/y5j7jl3PENT5RxeWZdN9RiwO7HQ== + dependencies: + "@types/zrender" "*" + "@types/estree@1.0.5", "@types/estree@^1.0.0": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" @@ -3029,6 +3036,11 @@ dependencies: "@types/yargs-parser" "*" +"@types/zrender@*": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/zrender/-/zrender-4.0.6.tgz#742dcf00ee0e9f9683391b3b897fd688559e2cdb" + integrity sha512-1jZ9bJn2BsfmYFPBHtl5o3uV+ILejAtGrDcYSpT4qaVKEI/0YY+arw3XHU04Ebd8Nca3SQ7uNcLaqiL+tTFVMg== + "@typescript-eslint/eslint-plugin@^6.7.3": version "6.7.5" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.5.tgz#f4024b9f63593d0c2b5bd6e4ca027e6f30934d4f" @@ -4513,6 +4525,14 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +echarts@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/echarts/-/echarts-5.5.0.tgz#c13945a7f3acdd67c134d8a9ac67e917830113ac" + integrity sha512-rNYnNCzqDAPCr4m/fqyUFv7fD9qIsd50S6GDFgO1DxZhncCsNsG7IfUlAlvZe5oSEQxtsjnHiUuppzccry93Xw== + dependencies: + tslib "2.3.0" + zrender "5.5.0" + ejs@^3.1.7: version "3.1.8" resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b" @@ -9219,6 +9239,11 @@ tsconfig-paths@^3.12.0, tsconfig-paths@^4.1.2: minimist "^1.2.6" strip-bom "^3.0.0" +tslib@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" + integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== + tslib@^1.10.0, tslib@^1.8.1: version "1.14.1" resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" @@ -9893,3 +9918,10 @@ yup@^0.32.11: nanoclone "^0.2.1" property-expr "^2.0.4" toposort "^2.0.2" + +zrender@5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/zrender/-/zrender-5.5.0.tgz#54d0d6c4eda81a96d9f60a9cd74dc48ea026bc1e" + integrity sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w== + dependencies: + tslib "2.3.0"