From 8c9e74e008a1ded5578666bdb5452593cafc239f Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Mon, 24 Apr 2023 20:37:07 -0400 Subject: [PATCH] better graphs - group modal fix - system list --- admin/server/controllers/systems.js | 60 +++ admin/server/index.js | 15 +- admin/server/models/system.js | 5 +- admin/src/AllSystems/AllSystems.js | 58 +++ admin/src/App/App.js | 3 +- admin/src/App/Navigation.js | 7 +- admin/src/App/Restricted.js | 21 +- admin/src/Group/GroupModal.js | 2 +- admin/src/features/api/apiSlice.js | 6 +- backend/models/system.js | 1 - frontend/package.json | 7 + frontend/src/Call/Activity.js | 2 +- frontend/src/Call/BetterActivityChart.js | 524 +++++++++++++++++++++++ frontend/src/Call/Example.js | 212 +++++++++ frontend/src/app.js | 2 + frontend/yarn.lock | 93 +++- 16 files changed, 1005 insertions(+), 13 deletions(-) create mode 100644 admin/src/AllSystems/AllSystems.js create mode 100644 frontend/src/Call/BetterActivityChart.js create mode 100644 frontend/src/Call/Example.js diff --git a/admin/server/controllers/systems.js b/admin/server/controllers/systems.js index f8ee979..4973fa5 100644 --- a/admin/server/controllers/systems.js +++ b/admin/server/controllers/systems.js @@ -12,6 +12,66 @@ exports.isLoggedIn = function (req, res, next) { // ------------------------------------------- +exports.listAllSystems = async function (req, res, next) { + if (!req.user.admin) { + res.status(401); + res.json({ success: false, message: "Not Authorized" }); + return; + } + + const systems = await System.find().exec(); + if (systems == null) { + res.status(400); + res.json({ success: false, message: err }); + return; + } + + let returnSys = systems.map(obj => { + var rObj = (({ + name, + shortName, + description, + systemType, + city, + state, + county, + country, + userId, + key, + showScreenName, + ignoreUnknownTalkgroup, + lastActive, + active + }) => ({ + name, + shortName, + description, + systemType, + city, + state, + county, + country, + userId, + key, + showScreenName, + ignoreUnknownTalkgroup, + lastActive, + active + }))(obj); + + + rObj.id = obj._id; + return rObj; + }); + + + res.json({ + success: true, + systems: returnSys + }); + return; +}; + exports.listSystems = async function (req, res, next) { const userId = new mongoose.Types.ObjectId(req.user._id); const systems = await System.find({ userId: userId }).exec(); diff --git a/admin/server/index.js b/admin/server/index.js index 08a53db..5473a93 100644 --- a/admin/server/index.js +++ b/admin/server/index.js @@ -79,7 +79,7 @@ function isOwner(req, res, next) { } } - +/* function isAdmin(req, res, next) { if (req.params.shortName && req.user) { var short_name = req.params.shortName.toLowerCase(); @@ -99,7 +99,7 @@ function isAdmin(req, res, next) { } }); } -} +}*/ function isLoggedIn(req, res, next) { if (req.isAuthenticated()) return next(); @@ -109,6 +109,16 @@ function isLoggedIn(req, res, next) { }); }; + +function isAdmin(req, res, next) { + if (req.isAuthenticated() && req.user.admin) return next(); + res.status(401).send({ + success: false, + message: "Not Authenticated." + }); +}; + + function uploadFile(req, res, next) { const upload = multer({dest: 'uploads/'}).single('file'); @@ -142,6 +152,7 @@ app.post("/groups/:shortName/:groupId?", isLoggedIn, groups.upsertGroup); app.get("/groups/:shortName/:groupId?", isLoggedIn, groups.getGroups); app.delete("/groups/:shortName/:groupId", isLoggedIn, groups.deleteGroup); app.get("/systems", isLoggedIn, systems.listSystems) +app.get("/admin/systems", isAdmin, systems.listAllSystems) app.delete("/systems/:shortName", isLoggedIn, systems.deleteSystem) app.post("/systems/:shortName", [isLoggedIn, systems.ownSystem, systems.validateSystem, systems.updateSystem]) app.post("/systems", [isLoggedIn, systems.uniqueShortName, systems.validateSystem, systems.createSystem]) diff --git a/admin/server/models/system.js b/admin/server/models/system.js index d9f92ef..dc9fc8f 100644 --- a/admin/server/models/system.js +++ b/admin/server/models/system.js @@ -1,5 +1,6 @@ var mongoose = require('mongoose'); var bcrypt = require('bcrypt'); +var User = require('./user'); var systemSchema = mongoose.Schema({ name: String, @@ -12,7 +13,9 @@ var systemSchema = mongoose.Schema({ description: String, showScreenName: Boolean, ignoreUnknownTalkgroup : Boolean, - userId: mongoose.Schema.Types.ObjectId, + active: {type: Boolean, default: false}, + lastActive: Date, + userId: {type: mongoose.Schema.Types.ObjectId, ref: 'User'}, key: String }); diff --git a/admin/src/AllSystems/AllSystems.js b/admin/src/AllSystems/AllSystems.js new file mode 100644 index 0000000..ec5df2a --- /dev/null +++ b/admin/src/AllSystems/AllSystems.js @@ -0,0 +1,58 @@ +import { + Container, + Divider, + Header, + Table +} from "semantic-ui-react"; +import { useGetAllSystemsQuery, } from '../features/api/apiSlice' + + +// ---------------------------------------------------- +const ListSystems = () => { + const { data, isSuccess } = useGetAllSystemsQuery(); + let systems = false; + if (isSuccess) { + systems = data.systems; + } + +//https://stackoverflow.com/questions/36559661/how-can-i-dispatch-from-child-components-in-react-redux +//https://stackoverflow.com/questions/42597602/react-onclick-pass-event-with-parameter + + return ( +
+ +
Radio Systems
+ + + {systems && + systems.map((system) => + + + + ShortName + Name + Last Active + Owner + Email + + + + + + {system.shortName} + {system.name} + {system.lastActive} + {system.userId.firstName} {system.userId.lastName} + {system.userId.email} + + +
+ )} + +
+
+ ); + +} + +export default ListSystems; diff --git a/admin/src/App/App.js b/admin/src/App/App.js index 8b7ef2d..3fad512 100644 --- a/admin/src/App/App.js +++ b/admin/src/App/App.js @@ -1,7 +1,7 @@ import Navigation from "./Navigation" import { Routes, Route } from 'react-router-dom'; -import Restricted from "./Restricted" +import {AdminRestricted, Restricted} from "./Restricted" import About from "../Components/About" import System from "../System/System" @@ -20,6 +20,7 @@ const App = (params) => { } /> } /> } /> + } /> } /> (
Miss
)} /> diff --git a/admin/src/App/Navigation.js b/admin/src/App/Navigation.js index 65abe0e..e58fc72 100644 --- a/admin/src/App/Navigation.js +++ b/admin/src/App/Navigation.js @@ -9,7 +9,7 @@ const navStyle = { const Navigation = (props) => { const dispatch = useDispatch(); - const { email } = useSelector((state) => state.user); + const { email, admin } = useSelector((state) => state.user); const logout = event => { event.preventDefault(); @@ -24,6 +24,11 @@ const Navigation = (props) => { {process.env.REACT_APP_SITE_NAME} + {admin && + + All Systems + + } Systems diff --git a/admin/src/App/Restricted.js b/admin/src/App/Restricted.js index 39c0df3..0729af2 100644 --- a/admin/src/App/Restricted.js +++ b/admin/src/App/Restricted.js @@ -7,7 +7,7 @@ import { authenticateUser } from "../features/user/userSlice"; * Higher-order component (HOC) to wrap restricted pages */ -const Restricted = ({children }) => { +export const Restricted = ({children }) => { const { authenticated, hasAuthenticated } = useSelector((state) => state.user); const dispatch = useDispatch(); useEffect(() => { @@ -26,4 +26,23 @@ const Restricted = ({children }) => { }; +export const AdminRestricted = ({children }) => { + const { authenticated, hasAuthenticated, admin } = useSelector((state) => state.user); + const dispatch = useDispatch(); + useEffect(() => { + dispatch(authenticateUser({})); + },[]); + + if (hasAuthenticated) { + if (authenticated && admin) { + return children;; + } + else { + window.location = process.env.REACT_APP_ACCOUNT_SERVER + "/login?nextLocation=admin"; + } +} + return
+ +}; + export default Restricted \ No newline at end of file diff --git a/admin/src/Group/GroupModal.js b/admin/src/Group/GroupModal.js index 8e63bab..b260477 100644 --- a/admin/src/Group/GroupModal.js +++ b/admin/src/Group/GroupModal.js @@ -77,7 +77,7 @@ const GroupModal = (props) => { } },[props.editGroupId, props.talkgroups]); - const removeTalkgroup = (index) => { + const removeTalkgroup = (event, index) => { const item = { num: group.items[index].num, description: group.items[index].description diff --git a/admin/src/features/api/apiSlice.js b/admin/src/features/api/apiSlice.js index 5b17307..6c11272 100644 --- a/admin/src/features/api/apiSlice.js +++ b/admin/src/features/api/apiSlice.js @@ -136,6 +136,10 @@ export const apiSlice = createApi({ // The URL for the request is '/fakeApi/posts' query: () => ({ url: `/systems`, credentials: "include" }) }), + getAllSystems: builder.query({ + // The URL for the request is '/fakeApi/posts' + query: () => ({ url: `/admin/systems`, credentials: "include" }) + }), createSystem: builder.mutation({ query: (system) => ({ url: `/systems`, @@ -198,4 +202,4 @@ export const apiSlice = createApi({ }) // Export the auto-generated hook for the `getPosts` query endpoint -export const { useGetGroupsQuery, useGetSystemsQuery, useGetTalkgroupsQuery, useGetErrorsQuery, useDeleteGroupMutation, useCreateGroupMutation, useCreateSystemMutation, useUpdateSystemMutation, useDeleteSystemMutation, useSaveGroupOrderMutation, useUpdateGroupMutation, useImportTalkgroupsMutation } = apiSlice +export const { useGetGroupsQuery, useGetSystemsQuery, useGetAllSystemsQuery, useGetTalkgroupsQuery, useGetErrorsQuery, useDeleteGroupMutation, useCreateGroupMutation, useCreateSystemMutation, useUpdateSystemMutation, useDeleteSystemMutation, useSaveGroupOrderMutation, useUpdateGroupMutation, useImportTalkgroupsMutation } = apiSlice diff --git a/backend/models/system.js b/backend/models/system.js index 980e264..1ebaed4 100644 --- a/backend/models/system.js +++ b/backend/models/system.js @@ -17,7 +17,6 @@ var systemSchema = mongoose.Schema({ ignoreUnknownTalkgroup : Boolean, active: {type: Boolean, default: false}, lastActive: Date, - planType: {type: Number, default: 0}, userId: {type: mongoose.Schema.Types.ObjectId, ref: 'User'}, key: String }); diff --git a/frontend/package.json b/frontend/package.json index 6564cad..b435a5d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,10 +11,17 @@ "@testing-library/user-event": "^14.4.3", "@visx/axis": "^3.1.0", "@visx/curve": "^3.0.0", + "@visx/event": "^3.0.1", + "@visx/gradient": "^3.0.0", + "@visx/grid": "^3.0.1", "@visx/group": "^3.0.0", + "@visx/mock-data": "^3.0.0", "@visx/scale": "^3.0.0", "@visx/shape": "^3.0.0", + "@visx/tooltip": "^3.1.2", "axios": "1.2.1", + "d3-array": "^3.2.3", + "d3-time-format": "^4.1.0", "date-fns": "2.29.3", "ejs": "3.1.8", "history": "^5.3.0", diff --git a/frontend/src/Call/Activity.js b/frontend/src/Call/Activity.js index a000c06..989bb5f 100644 --- a/frontend/src/Call/Activity.js +++ b/frontend/src/Call/Activity.js @@ -9,7 +9,7 @@ import { useSelector, useDispatch } from 'react-redux' import { setFilter, setDateFilter } from "../features/callPlayer/callPlayerSlice"; import { useGetStatsQuery,useGetTalkgroupsQuery } from '../features/api/apiSlice' -import ActivityChart from "./ActivityChart"; +import ActivityChart from "./BetterActivityChart"; import { selectSystem } from "../features/systems/systemsSlice"; import { Container, diff --git a/frontend/src/Call/BetterActivityChart.js b/frontend/src/Call/BetterActivityChart.js new file mode 100644 index 0000000..0dbd2cf --- /dev/null +++ b/frontend/src/Call/BetterActivityChart.js @@ -0,0 +1,524 @@ +import React, { useMemo, useCallback } from 'react'; +import { AreaClosed, Line, Bar } from '@visx/shape'; +import appleStock, { AppleStock } from '@visx/mock-data/lib/mocks/appleStock'; +import { curveMonotoneX } from '@visx/curve'; +import { GridRows, GridColumns } from '@visx/grid'; +import { scaleTime, scaleLinear } from '@visx/scale'; +import { withTooltip, Tooltip, TooltipWithBounds, defaultStyles } from '@visx/tooltip'; +import { useTooltip, useTooltipInPortal } from '@visx/tooltip'; +import { Loader } from "semantic-ui-react"; +import { WithTooltipProvidedProps } from '@visx/tooltip/lib/enhancers/withTooltip'; +import { localPoint } from '@visx/event'; +import { LinearGradient } from '@visx/gradient'; +import { max, extent, bisector } from 'd3-array'; +import { timeFormat } from 'd3-time-format'; + +const data = [ + 0, + 3, + 1, + 0, + 0, + 0, + 0, + 6, + 5, + 2, + 7, + 0, + 0, + 2, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 +]; + +export const background = '#3b6978'; +export const background2 = '#204051'; +export const accentColor = '#edffea'; +export const accentColorDark = '#75daad'; +const tooltipStyles = { + ...defaultStyles, + background, + border: '1px solid white', + color: 'white', +}; + +// util +const formatDate = timeFormat("%b %d, %H:%m"); + +// accessors +const getDate = (d) => { + return d.x; +} +const getCallActivity = (d) => { + return d.y; +} + + + + + + + +const ChartWithTooltip = () => { + const props = {data}; + const { + tooltipData, + tooltipLeft, + tooltipTop, + tooltipOpen, + showTooltip, + hideTooltip, + } = useTooltip(); + const width = 500; + const height = 250; + // If you don't want to use a Portal, simply replace `TooltipInPortal` below with + // `Tooltip` or `TooltipWithBounds` and remove `containerRef` + const { containerRef, TooltipInPortal } = useTooltipInPortal({ + // use TooltipWithBounds + detectBounds: true, + // when tooltip containers are scrolled, this will correctly update the Tooltip position + scroll: true, + }) + const bisectDate = bisector((d) => d.x).left; + + const getMouseData = (event) => { + const coords = localPoint(event.target.ownerSVGElement, event); + const { x } = coords; + + const x0 = dateScale.invert(x); + const index = bisectDate(calls, x0, 1); + const d0 = calls[index - 1]; + const d1 = calls[index]; + let d = d0; + if (d1 && getDate(d1)) { + d = x0.valueOf() - getDate(d0).valueOf() > getDate(d1).valueOf() - x0.valueOf() ? d1 : d0; + } + + return { data: d, coords } + } + const handleMouseClick = (event) => { + const { data } = getMouseData(event); + console.log(data); + } + const handleMouseOver = (event, datum) => { + const { coords, data } = getMouseData(event); + showTooltip({ + tooltipLeft: coords.x, + tooltipTop: callActivityScale(getCallActivity(data)), + tooltipData: data + }); + }; + // bounds + const margin = { top: 0, right: 0, bottom: 0, left: 0 }; + const innerWidth = width - margin.left - margin.right; + const innerHeight = height - margin.top - margin.bottom; + + const calls = useMemo( + () => { + let callTotals = []; + const now = new Date(); + var MS_PER_MINUTE = 60000; + + for (let j = 0; j < props.data.length; j++) { + let spotsBack = props.data.length - j; + let time = new Date(now - spotsBack * 15 * MS_PER_MINUTE); + callTotals.push({ y: props.data[j], x: time }); + } + return callTotals + + + + + }, [props.data] + ); + const dateScale = useMemo( + () => + scaleTime({ + range: [margin.left, innerWidth + margin.left], + domain: extent(calls, getDate), + }), + [innerWidth, margin.left], + ); + + const callActivityScale = useMemo( + () => { + return scaleLinear({ + range: [innerHeight + margin.top, margin.top], + domain: [0, (max(calls, getCallActivity) || 0)], // + innerHeight / 3], + nice: true, + }) + }, + [margin.top, innerHeight], + ); + + return ( + // Set `ref={containerRef}` on the element corresponding to the coordinate system that + // `left/top` (passed to `TooltipInPortal`) are relative to. + <> + + + + + + + dateScale(getDate(d)) ?? 0} + y={(d) => callActivityScale(getCallActivity(d)) ?? 0} + yScale={callActivityScale} + strokeWidth={1} + stroke="url(#area-gradient)" + fill="url(#area-gradient)" + curve={curveMonotoneX} + + /> + + {tooltipData && ( + + + + + + )} + + {tooltipOpen && ( +
+ + {`$${getCallActivity(tooltipData)}`} + + + {formatDate(getDate(tooltipData))} + +
+ )} + + ) +}; + +export default ChartWithTooltip; +/* +export default withTooltip(({ + width = 250, + height = 250, + margin = { top: 0, right: 0, bottom: 0, left: 0 }, + showTooltip, + hideTooltip, + data, + tooltipTop = 0, + tooltipLeft = 0, +}) => { + //if (width < 10) return null; + + // bounds + const innerWidth = width - margin.left - margin.right; + const innerHeight = height - margin.top - margin.bottom; + const stock = buildData(); + const tooltipData = stock; + // scales + const dateScale = useMemo( + () => + scaleTime({ + range: [margin.left, innerWidth + margin.left], + domain: extent(stock, getDate), + }), + [innerWidth, margin.left], + ); + const stockValueScale = useMemo( + () => + scaleLinear({ + range: [innerHeight + margin.top, margin.top], + domain: [0, (max(stock, getStockValue) || 0) + innerHeight / 3], + nice: true, + }), + [margin.top, innerHeight], + ); + + // tooltip handler + const handleTooltip = useCallback( + (event) => { + const { x } = localPoint(event) || { x: 0 }; + const x0 = dateScale.invert(x); + const index = bisectDate(stock, x0, 1); + const d0 = stock[index - 1]; + const d1 = stock[index]; + let d = d0; + if (d1 && getDate(d1)) { + d = x0.valueOf() - getDate(d0).valueOf() > getDate(d1).valueOf() - x0.valueOf() ? d1 : d0; + } + showTooltip({ + tooltipData: d, + tooltipLeft: x, + tooltipTop: stockValueScale(getStockValue(d)), + }); + }, + [showTooltip, stockValueScale, dateScale], + ); + + return ( +
+ + + + + + + dateScale(getDate(d)) ?? 0} + y={(d) => stockValueScale(getStockValue(d)) ?? 0} + yScale={stockValueScale} + strokeWidth={1} + stroke="url(#area-gradient)" + fill="url(#area-gradient)" + curve={curveMonotoneX} + /> + hideTooltip()} + /> + + + + + + + + + +
+ + {getStockValue(tooltipData)} + + + {formatDate(getDate(tooltipData))} + +
+ +
+ ); +} +); +*/ diff --git a/frontend/src/Call/Example.js b/frontend/src/Call/Example.js new file mode 100644 index 0000000..d9d3342 --- /dev/null +++ b/frontend/src/Call/Example.js @@ -0,0 +1,212 @@ +import React, { useMemo, useCallback } from 'react'; +import { AreaClosed, Line, Bar } from '@visx/shape'; +import appleStock, { AppleStock } from '@visx/mock-data/lib/mocks/appleStock'; +import { curveMonotoneX } from '@visx/curve'; +import { GridRows, GridColumns } from '@visx/grid'; +import { scaleTime, scaleLinear } from '@visx/scale'; +import { withTooltip, Tooltip, TooltipWithBounds, defaultStyles } from '@visx/tooltip'; +import { WithTooltipProvidedProps } from '@visx/tooltip/lib/enhancers/withTooltip'; +import { localPoint } from '@visx/event'; +import { LinearGradient } from '@visx/gradient'; +import { max, extent, bisector } from 'd3-array'; +import { timeFormat } from 'd3-time-format'; + +type TooltipData = AppleStock; + +const stock = appleStock.slice(800); +export const background = '#3b6978'; +export const background2 = '#204051'; +export const accentColor = '#edffea'; +export const accentColorDark = '#75daad'; +const tooltipStyles = { + ...defaultStyles, + background, + border: '1px solid white', + color: 'white', +}; + +// util +const formatDate = timeFormat("%b %d, '%y"); + +// accessors +const getDate = (d: AppleStock) => new Date(d.date); +const getStockValue = (d: AppleStock) => d.close; +const bisectDate = bisector((d) => new Date(d.date)).left; + +export type AreaProps = { + width: number; + height: number; + margin?: { top: number; right: number; bottom: number; left: number }; +}; + +export default withTooltip( + ({ + width, + height, + margin = { top: 0, right: 0, bottom: 0, left: 0 }, + showTooltip, + hideTooltip, + tooltipData, + tooltipTop = 0, + tooltipLeft = 0, + }: AreaProps & WithTooltipProvidedProps) => { + if (width < 10) return null; + + // bounds + const innerWidth = width - margin.left - margin.right; + const innerHeight = height - margin.top - margin.bottom; + + // scales + const dateScale = useMemo( + () => + scaleTime({ + range: [margin.left, innerWidth + margin.left], + domain: extent(stock, getDate) as [Date, Date], + }), + [innerWidth, margin.left], + ); + const stockValueScale = useMemo( + () => + scaleLinear({ + range: [innerHeight + margin.top, margin.top], + domain: [0, (max(stock, getStockValue) || 0) + innerHeight / 3], + nice: true, + }), + [margin.top, innerHeight], + ); + + // tooltip handler + const handleTooltip = useCallback( + (event: React.TouchEvent | React.MouseEvent) => { + const { x } = localPoint(event) || { x: 0 }; + const x0 = dateScale.invert(x); + const index = bisectDate(stock, x0, 1); + const d0 = stock[index - 1]; + const d1 = stock[index]; + let d = d0; + if (d1 && getDate(d1)) { + d = x0.valueOf() - getDate(d0).valueOf() > getDate(d1).valueOf() - x0.valueOf() ? d1 : d0; + } + showTooltip({ + tooltipData: d, + tooltipLeft: x, + tooltipTop: stockValueScale(getStockValue(d)), + }); + }, + [showTooltip, stockValueScale, dateScale], + ); + + return ( +
+ + + + + + + + data={stock} + x={(d) => dateScale(getDate(d)) ?? 0} + y={(d) => stockValueScale(getStockValue(d)) ?? 0} + yScale={stockValueScale} + strokeWidth={1} + stroke="url(#area-gradient)" + fill="url(#area-gradient)" + curve={curveMonotoneX} + /> + hideTooltip()} + /> + {tooltipData && ( + + + + + + )} + + {tooltipData && ( +
+ + {`$${getStockValue(tooltipData)}`} + + + {formatDate(getDate(tooltipData))} + +
+ )} +
+ ); + }, +); \ No newline at end of file diff --git a/frontend/src/app.js b/frontend/src/app.js index 99bd2fc..8fcb605 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -19,6 +19,7 @@ import AboutComponent from "./About/AboutComponent" // Event import ListEvents from "./Event/ListEvents" import ViewEvent from "./Event/ViewEvent" +import ActivityChart from './Call/BetterActivityChart'; const App = () => { @@ -32,6 +33,7 @@ return ( } /> } /> } /> + } /> ) } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index e5986fa..aeae5e8 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2691,6 +2691,11 @@ resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-1.0.9.tgz#73526b150d14cd96e701597cbf346cfd1fd4a58c" integrity sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ== +"@types/d3-random@^2.2.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-2.2.1.tgz#551edbb71cb317dea2cf9c76ebe059d311eefacb" + integrity sha512-5vvxn6//poNeOxt1ZwC7QU//dG9QqABjy1T7fP/xmFHY95GnaOw3yABf29hiu5SR1Oo34XcpyHFbzod+vemQjA== + "@types/d3-scale@^4.0.2": version "4.0.3" resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.3.tgz#7a5780e934e52b6f63ad9c24b105e33dd58102b5" @@ -2874,6 +2879,13 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== +"@types/react-dom@*": + version "18.0.11" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.11.tgz#321351c1459bc9ca3d216aefc8a167beec334e33" + integrity sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw== + dependencies: + "@types/react" "*" + "@types/react-dom@^18.0.0": version "18.0.10" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.10.tgz#3b66dec56aa0f16a6cc26da9e9ca96c35c0b4352" @@ -3099,6 +3111,15 @@ classnames "^2.3.1" prop-types "^15.6.0" +"@visx/bounds@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@visx/bounds/-/bounds-3.0.0.tgz#cf357cbff90a1fe5f95eb9d9288dd8794b744a7f" + integrity sha512-YQaSSER9erxlhppzRms6cvYdKqcIwk6eksrGdbJkBoHobhPo1JCIUXlmrA4qgrEnXInPJpueGE+PE5F+Dk12DA== + dependencies: + "@types/react" "*" + "@types/react-dom" "*" + prop-types "^15.5.10" + "@visx/curve@3.0.0", "@visx/curve@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@visx/curve/-/curve-3.0.0.tgz#c54568472e00a38483c58cf52e4a6ddb2887c2d4" @@ -3107,6 +3128,36 @@ "@types/d3-shape" "^1.3.1" d3-shape "^1.0.6" +"@visx/event@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@visx/event/-/event-3.0.1.tgz#d5358f52ff5ef30036d955bd2b68b96472ff2d6f" + integrity sha512-tK1EUYQLLStBuoCMbm8LJ3VbDyCVI8HjT0pMRQxm+C75FSIVWvrThgrfrC9sWOFnEMEYWspZO7hI5zjsPKjLQA== + dependencies: + "@types/react" "*" + "@visx/point" "3.0.1" + +"@visx/gradient@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@visx/gradient/-/gradient-3.0.0.tgz#39b55dd5a0b34feb219fcb2ea5525388c5ae0c1b" + integrity sha512-UoM9R9PIPLO/w7hCW9gFncrLdpKNqh13sLS9/0Iy6b75uP2l05FLG2HX4kXljeyOrj4/XyzRCMYm0HHk/p5iMA== + dependencies: + "@types/react" "*" + prop-types "^15.5.7" + +"@visx/grid@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@visx/grid/-/grid-3.0.1.tgz#d91085ed92e7e1c0c2e58710bc33b2f0f33b8e74" + integrity sha512-cln5CVvFG58C5Uz1Uf0KRBFmGmgD1NALOQdYDu5yPsTuY2yLzVYPvCIlYBMdUtE0uzfNq972SmkZHfZYs03jxQ== + dependencies: + "@types/react" "*" + "@visx/curve" "3.0.0" + "@visx/group" "3.0.0" + "@visx/point" "3.0.1" + "@visx/scale" "3.0.0" + "@visx/shape" "3.0.0" + classnames "^2.3.1" + prop-types "^15.6.2" + "@visx/group@3.0.0", "@visx/group@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@visx/group/-/group-3.0.0.tgz#e7f9752599bcc7e141ff5317a2a9a502577ab8df" @@ -3116,6 +3167,14 @@ classnames "^2.3.1" prop-types "^15.6.2" +"@visx/mock-data@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@visx/mock-data/-/mock-data-3.0.0.tgz#d8ac030e8dc58b338cba87e6b65276e7f9ac63ad" + integrity sha512-qi+G413zX1zIsyKDMvfKDTa8R9n+AOZygKq8qLGB9Uddx0ogXqjypDYN+gxvqSjzjxleuQ9Ml6PNTOdj9V9a1A== + dependencies: + "@types/d3-random" "^2.2.0" + d3-random "^2.2.2" + "@visx/point@3.0.1": version "3.0.1" resolved "https://registry.yarnpkg.com/@visx/point/-/point-3.0.1.tgz#77587ddaabf6f3023f09f8a0ce33a2c27c9d64c8" @@ -3163,6 +3222,17 @@ prop-types "^15.7.2" reduce-css-calc "^1.3.0" +"@visx/tooltip@^3.1.2": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@visx/tooltip/-/tooltip-3.1.2.tgz#6c7bb36a296f4501adb99b59487412e39fe06f44" + integrity sha512-p46qztGRNkEDbxzc3V1virahvz3UQ29TzddUjA0oaTIBCrOd9UJuLvv1Tq9OpeUYPdbrO/ZRwaEeri2pbwv04Q== + dependencies: + "@types/react" "*" + "@visx/bounds" "3.0.0" + classnames "^2.3.1" + prop-types "^15.5.10" + react-use-measure "^2.0.4" + "@webassemblyjs/ast@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" @@ -4480,7 +4550,7 @@ d3-array@2: dependencies: internmap "^1.0.0" -"d3-array@2 - 3", "d3-array@2.10.0 - 3": +"d3-array@2 - 3", "d3-array@2.10.0 - 3", d3-array@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.3.tgz#39f1f4954e4a09ff69ac597c2d61906b04e84740" integrity sha512-JRHwbQQ84XuAESWhvIPaUV4/1UYTBOLiOPGWqgFDHZS1D5QN9c57FbH3QpEnQMYiOXNzKUQyGTZf+EVO7RT5TQ== @@ -4509,6 +4579,11 @@ d3-path@1, d3-path@^1.0.5: resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf" integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg== +d3-random@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-2.2.2.tgz#5eebd209ef4e45a2b362b019c1fb21c2c98cbb6e" + integrity sha512-0D9P8TRj6qDAtHhRQn6EfdOtHMfsUWanl3yb/84C4DqpZ+VsgfI5iTVRNRbELCfNvRfpMr8OrqqUTQ6ANGCijw== + d3-scale@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" @@ -4527,7 +4602,7 @@ d3-shape@^1.0.6, d3-shape@^1.2.0: dependencies: d3-path "1" -"d3-time-format@2 - 4": +"d3-time-format@2 - 4", d3-time-format@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== @@ -4567,6 +4642,11 @@ date-fns@2.29.3, date-fns@^2.24.0: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== +debounce@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" + integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== + debug@2.6.9, debug@^2.6.0, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -8507,7 +8587,7 @@ prompts@^2.0.1, prompts@^2.4.2: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -8799,6 +8879,13 @@ react-scripts@5.0.1: optionalDependencies: fsevents "^2.3.2" +react-use-measure@^2.0.4: + version "2.1.1" + resolved "https://registry.yarnpkg.com/react-use-measure/-/react-use-measure-2.1.1.tgz#5824537f4ee01c9469c45d5f7a8446177c6cc4ba" + integrity sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig== + dependencies: + debounce "^1.2.1" + react@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"