mirror of
https://github.com/ajnart/homarr.git
synced 2026-02-26 16:30:57 +01:00
feat: stock price widget (#2579)
* feat: added stock price widget * fix: formatting * fix: broken lock file * fix: requested changes * fix: added parsing schema * fix: improve time range and interval inputs * fix: only return required data * fix: formatting * fix: deepsource tests * fix: moved all time frames into one location * fix: formatting * fix: requested changes * fix: formatting * fix: parse response data * fix: update packages * fix: typescript issues * fix: formatting * fix: broken lockfile --------- Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import { notebookRouter } from "./notebook";
|
||||
import { optionsRouter } from "./options";
|
||||
import { rssFeedRouter } from "./rssFeed";
|
||||
import { smartHomeRouter } from "./smart-home";
|
||||
import { stockPriceRouter } from "./stocks";
|
||||
import { weatherRouter } from "./weather";
|
||||
|
||||
export const widgetRouter = createTRPCRouter({
|
||||
@@ -21,6 +22,7 @@ export const widgetRouter = createTRPCRouter({
|
||||
app: appRouter,
|
||||
dnsHole: dnsHoleRouter,
|
||||
smartHome: smartHomeRouter,
|
||||
stockPrice: stockPriceRouter,
|
||||
mediaServer: mediaServerRouter,
|
||||
calendar: calendarRouter,
|
||||
downloads: downloadsRouter,
|
||||
|
||||
23
packages/api/src/router/widgets/stocks.ts
Normal file
23
packages/api/src/router/widgets/stocks.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { fetchStockPriceHandler } from "@homarr/request-handler/stock-price";
|
||||
|
||||
import { stockPriceTimeFrames } from "../../../../widgets/src/stocks";
|
||||
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
|
||||
const stockPriceInputSchema = z.object({
|
||||
stock: z.string().nonempty(),
|
||||
timeRange: z.enum(stockPriceTimeFrames.range),
|
||||
timeInterval: z.enum(stockPriceTimeFrames.interval),
|
||||
});
|
||||
|
||||
export const stockPriceRouter = createTRPCRouter({
|
||||
getPriceHistory: publicProcedure.input(stockPriceInputSchema).query(async ({ input }) => {
|
||||
const innerHandler = fetchStockPriceHandler.handler({
|
||||
stock: input.stock,
|
||||
timeRange: input.timeRange,
|
||||
timeInterval: input.timeInterval,
|
||||
});
|
||||
return await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
|
||||
}),
|
||||
});
|
||||
@@ -9,6 +9,7 @@ export const widgetKinds = [
|
||||
"dnsHoleControls",
|
||||
"smartHome-entityState",
|
||||
"smartHome-executeAutomation",
|
||||
"stockPrice",
|
||||
"mediaServer",
|
||||
"calendar",
|
||||
"downloads",
|
||||
|
||||
58
packages/request-handler/src/stock-price.ts
Normal file
58
packages/request-handler/src/stock-price.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import dayjs from "dayjs";
|
||||
import { z } from "zod";
|
||||
|
||||
import { fetchWithTimeout } from "@homarr/common";
|
||||
|
||||
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
||||
|
||||
export const fetchStockPriceHandler = createCachedWidgetRequestHandler({
|
||||
queryKey: "fetchStockPriceResult",
|
||||
widgetKind: "stockPrice",
|
||||
async requestAsync(input: { stock: string; timeRange: string; timeInterval: string }) {
|
||||
const response = await fetchWithTimeout(
|
||||
`https://query1.finance.yahoo.com/v8/finance/chart/${input.stock}?range=${input.timeRange}&interval=${input.timeInterval}`,
|
||||
);
|
||||
const data = dataSchema.parse(await response.json());
|
||||
|
||||
if ("error" in data) {
|
||||
throw new Error(data.error.description);
|
||||
}
|
||||
if (data.chart.result.length !== 1) {
|
||||
throw new Error("Received multiple results");
|
||||
}
|
||||
if (!data.chart.result[0]) {
|
||||
throw new Error("Received invalid data");
|
||||
}
|
||||
|
||||
return data.chart.result[0];
|
||||
},
|
||||
cacheDuration: dayjs.duration(5, "minutes"),
|
||||
});
|
||||
|
||||
const dataSchema = z
|
||||
.object({
|
||||
error: z.object({
|
||||
description: z.string(),
|
||||
}),
|
||||
})
|
||||
.or(
|
||||
z.object({
|
||||
chart: z.object({
|
||||
result: z.array(
|
||||
z.object({
|
||||
indicators: z.object({
|
||||
quote: z.array(
|
||||
z.object({
|
||||
close: z.array(z.number()),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
meta: z.object({
|
||||
symbol: z.string(),
|
||||
shortName: z.string(),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
@@ -1424,6 +1424,82 @@
|
||||
"run": "Run {name}"
|
||||
}
|
||||
},
|
||||
"stockPrice": {
|
||||
"name": "Stock Price",
|
||||
"description": "Displays the current stock price of a company",
|
||||
"option": {
|
||||
"stock": {
|
||||
"label": "Stock symbol"
|
||||
},
|
||||
"timeRange": {
|
||||
"label": "Time Range",
|
||||
"option": {
|
||||
"1d": {
|
||||
"label": "1 Day"
|
||||
},
|
||||
"5d": {
|
||||
"label": "5 Day"
|
||||
},
|
||||
"1mo": {
|
||||
"label": "1 Month"
|
||||
},
|
||||
"3mo": {
|
||||
"label": "3 Months"
|
||||
},
|
||||
"6mo": {
|
||||
"label": "6 Months"
|
||||
},
|
||||
"ytd": {
|
||||
"label": "Year to Date"
|
||||
},
|
||||
"1y": {
|
||||
"label": "1 Year"
|
||||
},
|
||||
"2y": {
|
||||
"label": "2 Years"
|
||||
},
|
||||
"5y": {
|
||||
"label": "5 Years"
|
||||
},
|
||||
"10y": {
|
||||
"label": "10 Years"
|
||||
},
|
||||
"max": {
|
||||
"label": "Max"
|
||||
}
|
||||
}
|
||||
},
|
||||
"timeInterval": {
|
||||
"label": "Time Interval",
|
||||
"option": {
|
||||
"5m": {
|
||||
"label": "5 Minutes"
|
||||
},
|
||||
"15m": {
|
||||
"label": "15 Minutes"
|
||||
},
|
||||
"30m": {
|
||||
"label": "30 Minutes"
|
||||
},
|
||||
"1h": {
|
||||
"label": "1 Hour"
|
||||
},
|
||||
"1d": {
|
||||
"label": "1 Day"
|
||||
},
|
||||
"5d": {
|
||||
"label": "5 Days"
|
||||
},
|
||||
"1wk": {
|
||||
"label": "1 Week"
|
||||
},
|
||||
"1mo": {
|
||||
"label": "1 Month"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
"name": "Calendar",
|
||||
"description": "Display events from your integrations in a calendar view within a certain relative time period",
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/charts": "^7.17.2",
|
||||
"@mantine/core": "^7.17.2",
|
||||
"@mantine/hooks": "^7.17.2",
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
@@ -68,6 +69,7 @@
|
||||
"next": "15.1.7",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"recharts": "^2.15.1",
|
||||
"video.js": "^8.22.0",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
|
||||
@@ -29,6 +29,7 @@ import type { WidgetOptionDefinition } from "./options";
|
||||
import * as rssFeed from "./rssFeed";
|
||||
import * as smartHomeEntityState from "./smart-home/entity-state";
|
||||
import * as smartHomeExecuteAutomation from "./smart-home/execute-automation";
|
||||
import * as stockPrice from "./stocks";
|
||||
import * as video from "./video";
|
||||
import * as weather from "./weather";
|
||||
|
||||
@@ -46,6 +47,7 @@ export const widgetImports = {
|
||||
dnsHoleControls,
|
||||
"smartHome-entityState": smartHomeEntityState,
|
||||
"smartHome-executeAutomation": smartHomeExecuteAutomation,
|
||||
stockPrice,
|
||||
mediaServer,
|
||||
calendar,
|
||||
downloads,
|
||||
|
||||
102
packages/widgets/src/stocks/component.tsx
Normal file
102
packages/widgets/src/stocks/component.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
"use client";
|
||||
|
||||
import { Sparkline } from "@mantine/charts";
|
||||
import { Flex, Stack, Text, Title, useMantineTheme } from "@mantine/core";
|
||||
import { IconTrendingDown, IconTrendingUp } from "@tabler/icons-react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
|
||||
function round(value: number) {
|
||||
return Math.round(value * 100) / 100;
|
||||
}
|
||||
|
||||
function calculateChange(valueA: number, valueB: number) {
|
||||
return valueA - valueB;
|
||||
}
|
||||
|
||||
function calculateChangePercentage(valueA: number, valueB: number) {
|
||||
return 100 * ((valueA - valueB) / valueA);
|
||||
}
|
||||
|
||||
export default function StockPriceWidget({ options, width, height }: WidgetComponentProps<"stockPrice">) {
|
||||
const t = useScopedI18n("widget.stockPrice");
|
||||
const theme = useMantineTheme();
|
||||
const [{ data }] = clientApi.widget.stockPrice.getPriceHistory.useSuspenseQuery(options);
|
||||
|
||||
const stockValues = data.indicators.quote[0]?.close ?? [];
|
||||
|
||||
const stockValuesChange = round(calculateChange(stockValues[stockValues.length - 1] ?? 0, stockValues[0] ?? 0));
|
||||
const stockValuesChangePercentage = round(
|
||||
calculateChangePercentage(stockValues[stockValues.length - 1] ?? 0, stockValues[0] ?? 0),
|
||||
);
|
||||
|
||||
const stockValuesMin = Math.min(...stockValues);
|
||||
const stockGraphValues = stockValues.map((value) => value - stockValuesMin + 50);
|
||||
|
||||
return (
|
||||
<Flex h="100%" w="100%">
|
||||
<Sparkline
|
||||
pos="absolute"
|
||||
bottom={10}
|
||||
w="100%"
|
||||
h={height > 280 ? "75%" : "50%"}
|
||||
data={stockGraphValues}
|
||||
curveType="linear"
|
||||
trendColors={{ positive: "green.7", negative: "red.7", neutral: "gray.6" }}
|
||||
fillOpacity={0.6}
|
||||
strokeWidth={2.5}
|
||||
/>
|
||||
|
||||
<Stack pos="absolute" top={10} left={10}>
|
||||
<Text size="xl" fw={700} lh="0.715">
|
||||
{stockValuesChange > 0 ? (
|
||||
<IconTrendingUp size="1.5rem" color={theme.colors.green[7]} />
|
||||
) : (
|
||||
<IconTrendingDown size="1.5rem" color={theme.colors.red[7]} />
|
||||
)}
|
||||
{data.meta.symbol}
|
||||
</Text>
|
||||
{width > 280 && height > 280 && (
|
||||
<Text size="md" lh="1">
|
||||
{data.meta.shortName}
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
<Title pos="absolute" bottom={10} right={10} order={width > 280 ? 1 : 2} fw={700}>
|
||||
{round(stockValues[stockValues.length - 1] ?? 0)}
|
||||
</Title>
|
||||
|
||||
{width > 280 && (
|
||||
<Text pos="absolute" top={10} right={10} size="xl" fw={700}>
|
||||
{Math.abs(stockValuesChange)} ({Math.abs(stockValuesChangePercentage)}%)
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{width > 280 && (
|
||||
<Text pos="absolute" bottom={10} left={10} fw={700}>
|
||||
{t(`option.timeRange.option.${options.timeRange}.label`)}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Stack pos="absolute" top={10} left={10}>
|
||||
<Text size="xl" fw={700} lh="0.715">
|
||||
{stockValuesChange > 0 ? (
|
||||
<IconTrendingUp size="1.5rem" color={theme.colors.green[7]} />
|
||||
) : (
|
||||
<IconTrendingDown size="1.5rem" color={theme.colors.red[7]} />
|
||||
)}
|
||||
{data.meta.symbol}
|
||||
</Text>
|
||||
{width > 280 && height > 280 && (
|
||||
<Text size="md" lh="1">
|
||||
{data.meta.shortName}
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
37
packages/widgets/src/stocks/index.ts
Normal file
37
packages/widgets/src/stocks/index.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { IconBuildingBank } from "@tabler/icons-react";
|
||||
|
||||
import { createWidgetDefinition } from "../definition";
|
||||
import { optionsBuilder } from "../options";
|
||||
|
||||
export const stockPriceTimeFrames = {
|
||||
range: ["1d", "5d", "1mo", "3mo", "6mo", "ytd", "1y", "2y", "5y", "10y", "max"] as const,
|
||||
interval: ["5m", "15m", "30m", "1h", "1d", "5d", "1wk", "1mo"] as const,
|
||||
};
|
||||
|
||||
const timeRangeOptions = stockPriceTimeFrames.range;
|
||||
const timeIntervalOptions = stockPriceTimeFrames.interval;
|
||||
|
||||
export const { definition, componentLoader } = createWidgetDefinition("stockPrice", {
|
||||
icon: IconBuildingBank,
|
||||
createOptions() {
|
||||
return optionsBuilder.from((factory) => ({
|
||||
stock: factory.text({
|
||||
defaultValue: "AAPL",
|
||||
}),
|
||||
timeRange: factory.select({
|
||||
defaultValue: "1mo",
|
||||
options: timeRangeOptions.map((value) => ({
|
||||
value,
|
||||
label: (t) => t(`widget.stockPrice.option.timeRange.option.${value}.label`),
|
||||
})),
|
||||
}),
|
||||
timeInterval: factory.select({
|
||||
defaultValue: "1d",
|
||||
options: timeIntervalOptions.map((value) => ({
|
||||
value,
|
||||
label: (t) => t(`widget.stockPrice.option.timeInterval.option.${value}.label`),
|
||||
})),
|
||||
}),
|
||||
}));
|
||||
},
|
||||
}).withDynamicImport(() => import("./component"));
|
||||
244
pnpm-lock.yaml
generated
244
pnpm-lock.yaml
generated
@@ -2072,6 +2072,9 @@ importers:
|
||||
'@homarr/validation':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../validation
|
||||
'@mantine/charts':
|
||||
specifier: ^7.17.2
|
||||
version: 7.17.2(@mantine/core@7.17.2(@mantine/hooks@7.17.2(react@19.0.0))(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@mantine/hooks@7.17.2(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(recharts@2.15.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))
|
||||
'@mantine/core':
|
||||
specifier: ^7.17.2
|
||||
version: 7.17.2(@mantine/hooks@7.17.2(react@19.0.0))(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
@@ -2144,6 +2147,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: 19.0.0
|
||||
version: 19.0.0(react@19.0.0)
|
||||
recharts:
|
||||
specifier: ^2.15.1
|
||||
version: 2.15.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
video.js:
|
||||
specifier: ^8.22.0
|
||||
version: 8.22.0
|
||||
@@ -3514,6 +3520,15 @@ packages:
|
||||
'@libsql/core@0.14.0':
|
||||
resolution: {integrity: sha512-nhbuXf7GP3PSZgdCY2Ecj8vz187ptHlZQ0VRc751oB2C1W8jQUXKKklvt7t1LJiUTQBVJuadF628eUk+3cRi4Q==}
|
||||
|
||||
'@mantine/charts@7.17.2':
|
||||
resolution: {integrity: sha512-ckB23pIqRjzysUz2EiWZD9AVyf7t0r7o7zfJbl01nzOezFgYq5RGeRoxvpcsfBC+YoSbB/43rjNcXtYhtA7QzA==}
|
||||
peerDependencies:
|
||||
'@mantine/core': 7.17.2
|
||||
'@mantine/hooks': 7.17.2
|
||||
react: ^18.x || ^19.x
|
||||
react-dom: ^18.x || ^19.x
|
||||
recharts: ^2.13.3
|
||||
|
||||
'@mantine/colors-generator@7.17.2':
|
||||
resolution: {integrity: sha512-wn4qmefWyQO9424nenN3k/zYcN4kPut1LVdv1ZyQ0Bz1giKc3PKTb96OOMzWlPCW08WjK/nwa2/VczC7YVKcQQ==}
|
||||
peerDependencies:
|
||||
@@ -4657,6 +4672,33 @@ packages:
|
||||
'@types/css-modules@1.0.5':
|
||||
resolution: {integrity: sha512-oeKafs/df9lwOvtfiXVliZsocFVOexK9PZtLQWuPeuVCFR7jwiqlg60lu80JTe5NFNtH3tnV6Fs/ySR8BUPHAw==}
|
||||
|
||||
'@types/d3-array@3.2.1':
|
||||
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
|
||||
|
||||
'@types/d3-color@3.1.3':
|
||||
resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
|
||||
|
||||
'@types/d3-ease@3.0.2':
|
||||
resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
|
||||
|
||||
'@types/d3-interpolate@3.0.4':
|
||||
resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
|
||||
|
||||
'@types/d3-path@3.1.1':
|
||||
resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==}
|
||||
|
||||
'@types/d3-scale@4.0.9':
|
||||
resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==}
|
||||
|
||||
'@types/d3-shape@3.1.7':
|
||||
resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==}
|
||||
|
||||
'@types/d3-time@3.0.4':
|
||||
resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
|
||||
|
||||
'@types/d3-timer@3.0.2':
|
||||
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
|
||||
|
||||
'@types/docker-modem@3.0.6':
|
||||
resolution: {integrity: sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==}
|
||||
|
||||
@@ -5766,6 +5808,50 @@ packages:
|
||||
csstype@3.1.3:
|
||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||
|
||||
d3-array@3.2.4:
|
||||
resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
d3-color@3.1.0:
|
||||
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
d3-ease@3.0.1:
|
||||
resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
d3-format@3.1.0:
|
||||
resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
d3-interpolate@3.0.1:
|
||||
resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
d3-path@3.1.0:
|
||||
resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
d3-scale@4.0.2:
|
||||
resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
d3-shape@3.2.0:
|
||||
resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
d3-time-format@4.1.0:
|
||||
resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
d3-time@3.1.0:
|
||||
resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
d3-timer@3.0.1:
|
||||
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
damerau-levenshtein@1.0.8:
|
||||
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
|
||||
|
||||
@@ -5830,6 +5916,9 @@ packages:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
decimal.js-light@2.5.1:
|
||||
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
|
||||
|
||||
decimal.js@10.4.3:
|
||||
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
|
||||
|
||||
@@ -6399,6 +6488,9 @@ packages:
|
||||
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
eventemitter3@4.0.7:
|
||||
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
|
||||
|
||||
events@3.3.0:
|
||||
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
||||
engines: {node: '>=0.8.x'}
|
||||
@@ -6437,6 +6529,10 @@ packages:
|
||||
fast-deep-equal@3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
|
||||
fast-equals@5.2.2:
|
||||
resolution: {integrity: sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
fast-fifo@1.3.2:
|
||||
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
|
||||
|
||||
@@ -6999,6 +7095,10 @@ packages:
|
||||
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
internmap@2.0.3:
|
||||
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
intl-messageformat@10.7.1:
|
||||
resolution: {integrity: sha512-xQuJW2WcyzNJZWUu5xTVPOmNSA1Sowuu/NKFdUid5Fxx/Yl6/s4DefTU/y7zy+irZLDmFGmTLtnM8FqpN05wlA==}
|
||||
|
||||
@@ -8711,6 +8811,9 @@ packages:
|
||||
react-is@16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
|
||||
react-is@18.3.1:
|
||||
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
|
||||
|
||||
react-number-format@5.4.3:
|
||||
resolution: {integrity: sha512-VCY5hFg/soBighAoGcdE+GagkJq0230qN6jcS5sp8wQX1qy1fYN/RX7/BXkrs0oyzzwqR8/+eSUrqXbGeywdUQ==}
|
||||
peerDependencies:
|
||||
@@ -8763,6 +8866,12 @@ packages:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
react-smooth@4.0.4:
|
||||
resolution: {integrity: sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==}
|
||||
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
|
||||
|
||||
react-style-singleton@2.2.3:
|
||||
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -8824,6 +8933,16 @@ packages:
|
||||
resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
recharts-scale@0.4.5:
|
||||
resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==}
|
||||
|
||||
recharts@2.15.1:
|
||||
resolution: {integrity: sha512-v8PUTUlyiDe56qUj82w/EDVuzEFXwEHp9/xOowGAZwfLjB9uAy3GllQVIYMWF6nU+qibx85WF75zD7AjqoT54Q==}
|
||||
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
|
||||
|
||||
redis-errors@1.2.0:
|
||||
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -10060,6 +10179,9 @@ packages:
|
||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
victory-vendor@36.9.2:
|
||||
resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
|
||||
|
||||
video.js@8.22.0:
|
||||
resolution: {integrity: sha512-xge2kpjsvC0zgFJ1cqt+wTqsi21+huFswlonPFh7qiplypsb4FN/D2Rz6bWdG/S9eQaPHfWHsarmJL/7D3DHoA==}
|
||||
|
||||
@@ -11420,6 +11542,14 @@ snapshots:
|
||||
js-base64: 3.7.7
|
||||
optional: true
|
||||
|
||||
'@mantine/charts@7.17.2(@mantine/core@7.17.2(@mantine/hooks@7.17.2(react@19.0.0))(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@mantine/hooks@7.17.2(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(recharts@2.15.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))':
|
||||
dependencies:
|
||||
'@mantine/core': 7.17.2(@mantine/hooks@7.17.2(react@19.0.0))(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@mantine/hooks': 7.17.2(react@19.0.0)
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
recharts: 2.15.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
|
||||
'@mantine/colors-generator@7.17.2(chroma-js@3.1.2)':
|
||||
dependencies:
|
||||
chroma-js: 3.1.2
|
||||
@@ -12906,6 +13036,30 @@ snapshots:
|
||||
|
||||
'@types/css-modules@1.0.5': {}
|
||||
|
||||
'@types/d3-array@3.2.1': {}
|
||||
|
||||
'@types/d3-color@3.1.3': {}
|
||||
|
||||
'@types/d3-ease@3.0.2': {}
|
||||
|
||||
'@types/d3-interpolate@3.0.4':
|
||||
dependencies:
|
||||
'@types/d3-color': 3.1.3
|
||||
|
||||
'@types/d3-path@3.1.1': {}
|
||||
|
||||
'@types/d3-scale@4.0.9':
|
||||
dependencies:
|
||||
'@types/d3-time': 3.0.4
|
||||
|
||||
'@types/d3-shape@3.1.7':
|
||||
dependencies:
|
||||
'@types/d3-path': 3.1.1
|
||||
|
||||
'@types/d3-time@3.0.4': {}
|
||||
|
||||
'@types/d3-timer@3.0.2': {}
|
||||
|
||||
'@types/docker-modem@3.0.6':
|
||||
dependencies:
|
||||
'@types/node': 22.13.10
|
||||
@@ -14198,6 +14352,44 @@ snapshots:
|
||||
|
||||
csstype@3.1.3: {}
|
||||
|
||||
d3-array@3.2.4:
|
||||
dependencies:
|
||||
internmap: 2.0.3
|
||||
|
||||
d3-color@3.1.0: {}
|
||||
|
||||
d3-ease@3.0.1: {}
|
||||
|
||||
d3-format@3.1.0: {}
|
||||
|
||||
d3-interpolate@3.0.1:
|
||||
dependencies:
|
||||
d3-color: 3.1.0
|
||||
|
||||
d3-path@3.1.0: {}
|
||||
|
||||
d3-scale@4.0.2:
|
||||
dependencies:
|
||||
d3-array: 3.2.4
|
||||
d3-format: 3.1.0
|
||||
d3-interpolate: 3.0.1
|
||||
d3-time: 3.1.0
|
||||
d3-time-format: 4.1.0
|
||||
|
||||
d3-shape@3.2.0:
|
||||
dependencies:
|
||||
d3-path: 3.1.0
|
||||
|
||||
d3-time-format@4.1.0:
|
||||
dependencies:
|
||||
d3-time: 3.1.0
|
||||
|
||||
d3-time@3.1.0:
|
||||
dependencies:
|
||||
d3-array: 3.2.4
|
||||
|
||||
d3-timer@3.0.1: {}
|
||||
|
||||
damerau-levenshtein@1.0.8: {}
|
||||
|
||||
data-uri-to-buffer@6.0.2: {}
|
||||
@@ -14257,6 +14449,8 @@ snapshots:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
decimal.js-light@2.5.1: {}
|
||||
|
||||
decimal.js@10.4.3: {}
|
||||
|
||||
decompress-response@6.0.0:
|
||||
@@ -15030,6 +15224,8 @@ snapshots:
|
||||
|
||||
event-target-shim@5.0.1: {}
|
||||
|
||||
eventemitter3@4.0.7: {}
|
||||
|
||||
events@3.3.0: {}
|
||||
|
||||
execa@5.1.1:
|
||||
@@ -15089,6 +15285,8 @@ snapshots:
|
||||
|
||||
fast-deep-equal@3.1.3: {}
|
||||
|
||||
fast-equals@5.2.2: {}
|
||||
|
||||
fast-fifo@1.3.2: {}
|
||||
|
||||
fast-glob@3.3.1:
|
||||
@@ -15707,6 +15905,8 @@ snapshots:
|
||||
hasown: 2.0.2
|
||||
side-channel: 1.1.0
|
||||
|
||||
internmap@2.0.3: {}
|
||||
|
||||
intl-messageformat@10.7.1:
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 2.2.0
|
||||
@@ -17404,6 +17604,8 @@ snapshots:
|
||||
|
||||
react-is@16.13.1: {}
|
||||
|
||||
react-is@18.3.1: {}
|
||||
|
||||
react-number-format@5.4.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
dependencies:
|
||||
react: 19.0.0
|
||||
@@ -17452,6 +17654,14 @@ snapshots:
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
|
||||
react-smooth@4.0.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
dependencies:
|
||||
fast-equals: 5.2.2
|
||||
prop-types: 15.8.1
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
react-transition-group: 4.4.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
|
||||
react-style-singleton@2.2.3(@types/react@19.0.12)(react@19.0.0):
|
||||
dependencies:
|
||||
get-nonce: 1.0.1
|
||||
@@ -17542,6 +17752,23 @@ snapshots:
|
||||
tiny-invariant: 1.3.3
|
||||
tslib: 2.8.1
|
||||
|
||||
recharts-scale@0.4.5:
|
||||
dependencies:
|
||||
decimal.js-light: 2.5.1
|
||||
|
||||
recharts@2.15.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
eventemitter3: 4.0.7
|
||||
lodash: 4.17.21
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
react-is: 18.3.1
|
||||
react-smooth: 4.0.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
recharts-scale: 0.4.5
|
||||
tiny-invariant: 1.3.3
|
||||
victory-vendor: 36.9.2
|
||||
|
||||
redis-errors@1.2.0: {}
|
||||
|
||||
redis-parser@3.0.0:
|
||||
@@ -19008,6 +19235,23 @@ snapshots:
|
||||
|
||||
vary@1.1.2: {}
|
||||
|
||||
victory-vendor@36.9.2:
|
||||
dependencies:
|
||||
'@types/d3-array': 3.2.1
|
||||
'@types/d3-ease': 3.0.2
|
||||
'@types/d3-interpolate': 3.0.4
|
||||
'@types/d3-scale': 4.0.9
|
||||
'@types/d3-shape': 3.1.7
|
||||
'@types/d3-time': 3.0.4
|
||||
'@types/d3-timer': 3.0.2
|
||||
d3-array: 3.2.4
|
||||
d3-ease: 3.0.1
|
||||
d3-interpolate: 3.0.1
|
||||
d3-scale: 4.0.2
|
||||
d3-shape: 3.2.0
|
||||
d3-time: 3.1.0
|
||||
d3-timer: 3.0.1
|
||||
|
||||
video.js@8.22.0:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.25.6
|
||||
|
||||
Reference in New Issue
Block a user