feat: Implement stock price component and api
This commit is contained in:
@@ -5,6 +5,63 @@ import type ComponentMap from "../../uis/index";
|
||||
import { z } from "zod";
|
||||
import { LangGraphRunnableConfig } from "@langchain/langgraph";
|
||||
import { findToolCall } from "../../find-tool-call";
|
||||
import { format, subDays } from "date-fns";
|
||||
import { Price } from "../../types";
|
||||
|
||||
async function getPricesForTicker(ticker: string): Promise<{
|
||||
oneDayPrices: Price[];
|
||||
thirtyDayPrices: Price[];
|
||||
}> {
|
||||
if (!process.env.FINANCIAL_DATASETS_API_KEY) {
|
||||
throw new Error("Financial datasets API key not set");
|
||||
}
|
||||
|
||||
const options = {
|
||||
method: "GET",
|
||||
headers: { "X-API-KEY": process.env.FINANCIAL_DATASETS_API_KEY },
|
||||
};
|
||||
|
||||
const url = "https://api.financialdatasets.ai/prices";
|
||||
|
||||
const oneMonthAgo = format(subDays(new Date(), 30), "yyyy-MM-dd");
|
||||
const now = format(new Date(), "yyyy-MM-dd");
|
||||
|
||||
const queryParamsOneDay = new URLSearchParams({
|
||||
ticker,
|
||||
interval: "minute",
|
||||
interval_multiplier: "5",
|
||||
start_date: now,
|
||||
end_date: now,
|
||||
limit: "5000",
|
||||
});
|
||||
|
||||
const queryParamsThirtyDays = new URLSearchParams({
|
||||
ticker,
|
||||
interval: "minute",
|
||||
interval_multiplier: "30",
|
||||
start_date: oneMonthAgo,
|
||||
end_date: now,
|
||||
limit: "5000",
|
||||
});
|
||||
|
||||
const [resOneDay, resThirtyDays] = await Promise.all([
|
||||
fetch(`${url}?${queryParamsOneDay.toString()}`, options),
|
||||
fetch(`${url}?${queryParamsThirtyDays.toString()}`, options),
|
||||
]);
|
||||
if (!resOneDay.ok || !resThirtyDays.ok) {
|
||||
throw new Error("Failed to fetch prices");
|
||||
}
|
||||
const { prices: pricesOneDay } = await resOneDay.json();
|
||||
const { prices: pricesThirtyDays } = await resThirtyDays.json();
|
||||
|
||||
console.log("pricesOneDay", pricesOneDay.length);
|
||||
console.log("pricesThirtyDays", pricesThirtyDays.length);
|
||||
|
||||
return {
|
||||
oneDayPrices: pricesOneDay,
|
||||
thirtyDayPrices: pricesThirtyDays,
|
||||
};
|
||||
}
|
||||
|
||||
const llm = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0 });
|
||||
|
||||
@@ -48,15 +105,15 @@ export async function callTools(
|
||||
findToolCall("stock-price")<typeof getStockPriceSchema>,
|
||||
);
|
||||
const portfolioToolCall = message.tool_calls?.find(
|
||||
findToolCall("portfolio")<typeof getStockPriceSchema>,
|
||||
findToolCall("portfolio")<typeof getPortfolioSchema>,
|
||||
);
|
||||
|
||||
if (stockbrokerToolCall) {
|
||||
const instruction = `The stock price of ${
|
||||
stockbrokerToolCall.args.ticker
|
||||
} is ${Math.random() * 100}`;
|
||||
|
||||
ui.write("stock-price", { instruction, logo: "hey" });
|
||||
const prices = await getPricesForTicker(stockbrokerToolCall.args.ticker);
|
||||
ui.write("stock-price", {
|
||||
ticker: stockbrokerToolCall.args.ticker,
|
||||
...prices,
|
||||
});
|
||||
}
|
||||
|
||||
if (portfolioToolCall) {
|
||||
|
||||
@@ -25,3 +25,13 @@ export type Accommodation = {
|
||||
city: string;
|
||||
image: string;
|
||||
};
|
||||
|
||||
export type Price = {
|
||||
ticker: string;
|
||||
open: number;
|
||||
close: number;
|
||||
high: number;
|
||||
low: number;
|
||||
volume: number;
|
||||
time: string;
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -46,6 +46,7 @@
|
||||
"react-markdown": "^10.0.1",
|
||||
"react-router-dom": "^6.17.0",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"recharts": "^2.15.1",
|
||||
"rehype-katex": "^7.0.1",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-math": "^6.0.0",
|
||||
@@ -70,6 +71,7 @@
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"dotenv": "^16.4.7",
|
||||
"eslint": "^9.19.0",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.18",
|
||||
@@ -80,5 +82,8 @@
|
||||
"typescript-eslint": "^8.22.0",
|
||||
"vite": "^6.1.0"
|
||||
},
|
||||
"overrides": {
|
||||
"react-is": "^19.0.0-rc-69d4b800-20241021"
|
||||
},
|
||||
"packageManager": "pnpm@10.5.1+sha512.c424c076bd25c1a5b188c37bb1ca56cc1e136fbf530d98bcb3289982a08fd25527b8c9c4ec113be5e3393c39af04521dd647bcf1d0801eaf8ac6a7b14da313af"
|
||||
}
|
||||
|
||||
391
pnpm-lock.yaml
generated
391
pnpm-lock.yaml
generated
@@ -114,6 +114,9 @@ importers:
|
||||
react-syntax-highlighter:
|
||||
specifier: ^15.5.0
|
||||
version: 15.6.1(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)
|
||||
rehype-katex:
|
||||
specifier: ^7.0.1
|
||||
version: 7.0.1
|
||||
@@ -166,6 +169,9 @@ importers:
|
||||
autoprefixer:
|
||||
specifier: ^10.4.20
|
||||
version: 10.4.20(postcss@8.5.3)
|
||||
dotenv:
|
||||
specifier: ^16.4.7
|
||||
version: 16.4.7
|
||||
eslint:
|
||||
specifier: ^9.19.0
|
||||
version: 9.21.0(jiti@2.4.2)
|
||||
@@ -1850,6 +1856,60 @@ packages:
|
||||
integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==,
|
||||
}
|
||||
|
||||
"@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/debug@4.1.12":
|
||||
resolution:
|
||||
{
|
||||
@@ -2499,6 +2559,83 @@ packages:
|
||||
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" }
|
||||
|
||||
date-fns@4.1.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -2524,6 +2661,12 @@ packages:
|
||||
}
|
||||
engines: { node: ">=0.10.0" }
|
||||
|
||||
decimal.js-light@2.5.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==,
|
||||
}
|
||||
|
||||
decode-named-character-reference@1.0.2:
|
||||
resolution:
|
||||
{
|
||||
@@ -2602,6 +2745,12 @@ packages:
|
||||
integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==,
|
||||
}
|
||||
|
||||
dom-helpers@5.2.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==,
|
||||
}
|
||||
|
||||
dotenv@16.4.7:
|
||||
resolution:
|
||||
{
|
||||
@@ -2898,6 +3047,13 @@ packages:
|
||||
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-glob@3.3.3:
|
||||
resolution:
|
||||
{
|
||||
@@ -3354,6 +3510,13 @@ packages:
|
||||
integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==,
|
||||
}
|
||||
|
||||
internmap@2.0.3:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==,
|
||||
}
|
||||
engines: { node: ">=12" }
|
||||
|
||||
is-alphabetical@1.0.4:
|
||||
resolution:
|
||||
{
|
||||
@@ -3743,6 +3906,12 @@ packages:
|
||||
integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==,
|
||||
}
|
||||
|
||||
lodash@4.17.21:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==,
|
||||
}
|
||||
|
||||
logform@2.7.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -3756,6 +3925,13 @@ packages:
|
||||
integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==,
|
||||
}
|
||||
|
||||
loose-envify@1.4.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==,
|
||||
}
|
||||
hasBin: true
|
||||
|
||||
lowlight@1.20.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -4215,6 +4391,13 @@ packages:
|
||||
}
|
||||
engines: { node: ">=18" }
|
||||
|
||||
object-assign@4.1.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==,
|
||||
}
|
||||
engines: { node: ">=0.10.0" }
|
||||
|
||||
once@1.4.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -4491,6 +4674,12 @@ packages:
|
||||
}
|
||||
engines: { node: ">=6" }
|
||||
|
||||
prop-types@15.8.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==,
|
||||
}
|
||||
|
||||
property-information@5.6.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -4530,6 +4719,18 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^19.0.0
|
||||
|
||||
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-markdown@10.0.1:
|
||||
resolution:
|
||||
{
|
||||
@@ -4600,6 +4801,15 @@ packages:
|
||||
peerDependencies:
|
||||
react: ">=16.8"
|
||||
|
||||
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:
|
||||
{
|
||||
@@ -4630,6 +4840,15 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
react-transition-group@4.4.5:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==,
|
||||
}
|
||||
peerDependencies:
|
||||
react: ">=16.6.0"
|
||||
react-dom: ">=16.6.0"
|
||||
|
||||
react@19.0.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -4651,6 +4870,22 @@ packages:
|
||||
}
|
||||
engines: { node: ">= 14.18.0" }
|
||||
|
||||
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
|
||||
|
||||
refractor@3.6.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -5003,6 +5238,12 @@ packages:
|
||||
integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==,
|
||||
}
|
||||
|
||||
tiny-invariant@1.3.3:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==,
|
||||
}
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
resolution:
|
||||
{
|
||||
@@ -5303,6 +5544,12 @@ packages:
|
||||
integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==,
|
||||
}
|
||||
|
||||
victory-vendor@36.9.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==,
|
||||
}
|
||||
|
||||
vite@6.2.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -6512,6 +6759,30 @@ snapshots:
|
||||
dependencies:
|
||||
"@babel/types": 7.26.9
|
||||
|
||||
"@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/debug@4.1.12":
|
||||
dependencies:
|
||||
"@types/ms": 2.1.0
|
||||
@@ -6880,6 +7151,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: {}
|
||||
|
||||
date-fns@4.1.0: {}
|
||||
|
||||
debug@4.4.0:
|
||||
@@ -6888,6 +7197,8 @@ snapshots:
|
||||
|
||||
decamelize@1.2.0: {}
|
||||
|
||||
decimal.js-light@2.5.1: {}
|
||||
|
||||
decode-named-character-reference@1.0.2:
|
||||
dependencies:
|
||||
character-entities: 2.0.2
|
||||
@@ -6917,6 +7228,11 @@ snapshots:
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
|
||||
dom-helpers@5.2.1:
|
||||
dependencies:
|
||||
"@babel/runtime": 7.26.9
|
||||
csstype: 3.1.3
|
||||
|
||||
dotenv@16.4.7: {}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
@@ -7154,6 +7470,8 @@ snapshots:
|
||||
|
||||
fast-deep-equal@3.1.3: {}
|
||||
|
||||
fast-equals@5.2.2: {}
|
||||
|
||||
fast-glob@3.3.3:
|
||||
dependencies:
|
||||
"@nodelib/fs.stat": 2.0.5
|
||||
@@ -7444,6 +7762,8 @@ snapshots:
|
||||
|
||||
inline-style-parser@0.2.4: {}
|
||||
|
||||
internmap@2.0.3: {}
|
||||
|
||||
is-alphabetical@1.0.4: {}
|
||||
|
||||
is-alphabetical@2.0.1: {}
|
||||
@@ -7623,6 +7943,8 @@ snapshots:
|
||||
|
||||
lodash.merge@4.6.2: {}
|
||||
|
||||
lodash@4.17.21: {}
|
||||
|
||||
logform@2.7.0:
|
||||
dependencies:
|
||||
"@colors/colors": 1.6.0
|
||||
@@ -7634,6 +7956,10 @@ snapshots:
|
||||
|
||||
longest-streak@3.1.0: {}
|
||||
|
||||
loose-envify@1.4.0:
|
||||
dependencies:
|
||||
js-tokens: 4.0.0
|
||||
|
||||
lowlight@1.20.0:
|
||||
dependencies:
|
||||
fault: 1.0.4
|
||||
@@ -8083,6 +8409,8 @@ snapshots:
|
||||
path-key: 4.0.0
|
||||
unicorn-magic: 0.3.0
|
||||
|
||||
object-assign@4.1.1: {}
|
||||
|
||||
once@1.4.0:
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
@@ -8257,6 +8585,12 @@ snapshots:
|
||||
|
||||
prismjs@1.29.0: {}
|
||||
|
||||
prop-types@15.8.1:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
object-assign: 4.1.1
|
||||
react-is: 16.13.1
|
||||
|
||||
property-information@5.6.0:
|
||||
dependencies:
|
||||
xtend: 4.0.2
|
||||
@@ -8277,6 +8611,10 @@ snapshots:
|
||||
react: 19.0.0
|
||||
scheduler: 0.25.0
|
||||
|
||||
react-is@16.13.1: {}
|
||||
|
||||
react-is@18.3.1: {}
|
||||
|
||||
react-markdown@10.0.1(@types/react@19.0.10)(react@19.0.0):
|
||||
dependencies:
|
||||
"@types/hast": 3.0.4
|
||||
@@ -8346,6 +8684,14 @@ snapshots:
|
||||
"@remix-run/router": 1.23.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.10)(react@19.0.0):
|
||||
dependencies:
|
||||
get-nonce: 1.0.1
|
||||
@@ -8373,6 +8719,15 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- "@types/react"
|
||||
|
||||
react-transition-group@4.4.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
dependencies:
|
||||
"@babel/runtime": 7.26.9
|
||||
dom-helpers: 5.2.1
|
||||
loose-envify: 1.4.0
|
||||
prop-types: 15.8.1
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
|
||||
react@19.0.0: {}
|
||||
|
||||
readable-stream@3.6.2:
|
||||
@@ -8383,6 +8738,23 @@ snapshots:
|
||||
|
||||
readdirp@4.1.2: {}
|
||||
|
||||
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
|
||||
|
||||
refractor@3.6.0:
|
||||
dependencies:
|
||||
hastscript: 6.0.0
|
||||
@@ -8607,6 +8979,8 @@ snapshots:
|
||||
|
||||
text-hex@1.0.0: {}
|
||||
|
||||
tiny-invariant@1.3.3: {}
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
@@ -8778,6 +9152,23 @@ snapshots:
|
||||
"@types/unist": 3.0.3
|
||||
vfile-message: 4.0.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
|
||||
|
||||
vite@6.2.0(@types/node@22.13.5)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.3)(yaml@2.7.0):
|
||||
dependencies:
|
||||
esbuild: 0.25.0
|
||||
|
||||
75
src/components/ui/card.tsx
Normal file
75
src/components/ui/card.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card"
|
||||
className={cn(
|
||||
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-header"
|
||||
className={cn("flex flex-col gap-1.5 px-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-title"
|
||||
className={cn("leading-none font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-description"
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-content"
|
||||
className={cn("px-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-footer"
|
||||
className={cn("flex items-center px-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardFooter,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
};
|
||||
353
src/components/ui/chart.tsx
Normal file
353
src/components/ui/chart.tsx
Normal file
@@ -0,0 +1,353 @@
|
||||
import * as React from "react";
|
||||
import * as RechartsPrimitive from "recharts";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
// Format: { THEME_NAME: CSS_SELECTOR }
|
||||
const THEMES = { light: "", dark: ".dark" } as const;
|
||||
|
||||
export type ChartConfig = {
|
||||
[k in string]: {
|
||||
label?: React.ReactNode;
|
||||
icon?: React.ComponentType;
|
||||
} & (
|
||||
| { color?: string; theme?: never }
|
||||
| { color?: never; theme: Record<keyof typeof THEMES, string> }
|
||||
);
|
||||
};
|
||||
|
||||
type ChartContextProps = {
|
||||
config: ChartConfig;
|
||||
};
|
||||
|
||||
const ChartContext = React.createContext<ChartContextProps | null>(null);
|
||||
|
||||
function useChart() {
|
||||
const context = React.useContext(ChartContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useChart must be used within a <ChartContainer />");
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
function ChartContainer({
|
||||
id,
|
||||
className,
|
||||
children,
|
||||
config,
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & {
|
||||
config: ChartConfig;
|
||||
children: React.ComponentProps<
|
||||
typeof RechartsPrimitive.ResponsiveContainer
|
||||
>["children"];
|
||||
}) {
|
||||
const uniqueId = React.useId();
|
||||
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
|
||||
|
||||
return (
|
||||
<ChartContext.Provider value={{ config }}>
|
||||
<div
|
||||
data-slot="chart"
|
||||
data-chart={chartId}
|
||||
className={cn(
|
||||
"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChartStyle id={chartId} config={config} />
|
||||
<RechartsPrimitive.ResponsiveContainer>
|
||||
{children}
|
||||
</RechartsPrimitive.ResponsiveContainer>
|
||||
</div>
|
||||
</ChartContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
||||
const colorConfig = Object.entries(config).filter(
|
||||
([, config]) => config.theme || config.color,
|
||||
);
|
||||
|
||||
if (!colorConfig.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: Object.entries(THEMES)
|
||||
.map(
|
||||
([theme, prefix]) => `
|
||||
${prefix} [data-chart=${id}] {
|
||||
${colorConfig
|
||||
.map(([key, itemConfig]) => {
|
||||
const color =
|
||||
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
|
||||
itemConfig.color;
|
||||
return color ? ` --color-${key}: ${color};` : null;
|
||||
})
|
||||
.join("\n")}
|
||||
}
|
||||
`,
|
||||
)
|
||||
.join("\n"),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ChartTooltip = RechartsPrimitive.Tooltip;
|
||||
|
||||
function ChartTooltipContent({
|
||||
active,
|
||||
payload,
|
||||
className,
|
||||
indicator = "dot",
|
||||
hideLabel = false,
|
||||
hideIndicator = false,
|
||||
label,
|
||||
labelFormatter,
|
||||
labelClassName,
|
||||
formatter,
|
||||
color,
|
||||
nameKey,
|
||||
labelKey,
|
||||
}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
||||
React.ComponentProps<"div"> & {
|
||||
hideLabel?: boolean;
|
||||
hideIndicator?: boolean;
|
||||
indicator?: "line" | "dot" | "dashed";
|
||||
nameKey?: string;
|
||||
labelKey?: string;
|
||||
}) {
|
||||
const { config } = useChart();
|
||||
|
||||
const tooltipLabel = React.useMemo(() => {
|
||||
if (hideLabel || !payload?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [item] = payload;
|
||||
const key = `${labelKey || item?.dataKey || item?.name || "value"}`;
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||
const value =
|
||||
!labelKey && typeof label === "string"
|
||||
? config[label as keyof typeof config]?.label || label
|
||||
: itemConfig?.label;
|
||||
|
||||
if (labelFormatter) {
|
||||
return (
|
||||
<div className={cn("font-medium", labelClassName)}>
|
||||
{labelFormatter(value, payload)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("font-medium bg-white", labelClassName)}>{value}</div>
|
||||
);
|
||||
}, [
|
||||
label,
|
||||
labelFormatter,
|
||||
payload,
|
||||
hideLabel,
|
||||
labelClassName,
|
||||
config,
|
||||
labelKey,
|
||||
]);
|
||||
|
||||
if (!active || !payload?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nestLabel = payload.length === 1 && indicator !== "dot";
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{!nestLabel ? tooltipLabel : null}
|
||||
<div className="grid gap-1.5">
|
||||
{payload.map((item, index) => {
|
||||
const key = `${nameKey || item.name || item.dataKey || "value"}`;
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||
const indicatorColor = color || item.payload.fill || item.color;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.dataKey}
|
||||
className={cn(
|
||||
"[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
|
||||
indicator === "dot" && "items-center",
|
||||
)}
|
||||
>
|
||||
{formatter && item?.value !== undefined && item.name ? (
|
||||
formatter(item.value, item.name, item, index, item.payload)
|
||||
) : (
|
||||
<>
|
||||
{itemConfig?.icon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
!hideIndicator && (
|
||||
<div
|
||||
className={cn(
|
||||
"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
|
||||
{
|
||||
"h-2.5 w-2.5": indicator === "dot",
|
||||
"w-1": indicator === "line",
|
||||
"w-0 border-[1.5px] border-dashed bg-transparent":
|
||||
indicator === "dashed",
|
||||
"my-0.5": nestLabel && indicator === "dashed",
|
||||
},
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--color-bg": indicatorColor,
|
||||
"--color-border": indicatorColor,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-1 justify-between leading-none",
|
||||
nestLabel ? "items-end" : "items-center",
|
||||
)}
|
||||
>
|
||||
<div className="grid gap-1.5">
|
||||
{nestLabel ? tooltipLabel : null}
|
||||
<span className="text-muted-foreground">
|
||||
{itemConfig?.label || item.name}
|
||||
</span>
|
||||
</div>
|
||||
{item.value && (
|
||||
<span className="text-foreground font-mono font-medium tabular-nums">
|
||||
{item.value.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const ChartLegend = RechartsPrimitive.Legend;
|
||||
|
||||
function ChartLegendContent({
|
||||
className,
|
||||
hideIcon = false,
|
||||
payload,
|
||||
verticalAlign = "bottom",
|
||||
nameKey,
|
||||
}: React.ComponentProps<"div"> &
|
||||
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
|
||||
hideIcon?: boolean;
|
||||
nameKey?: string;
|
||||
}) {
|
||||
const { config } = useChart();
|
||||
|
||||
if (!payload?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-center gap-4",
|
||||
verticalAlign === "top" ? "pb-3" : "pt-3",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{payload.map((item) => {
|
||||
const key = `${nameKey || item.dataKey || "value"}`;
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.value}
|
||||
className={cn(
|
||||
"[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3",
|
||||
)}
|
||||
>
|
||||
{itemConfig?.icon && !hideIcon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
<div
|
||||
className="h-2 w-2 shrink-0 rounded-[2px]"
|
||||
style={{
|
||||
backgroundColor: item.color,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{itemConfig?.label}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Helper to extract item config from a payload.
|
||||
function getPayloadConfigFromPayload(
|
||||
config: ChartConfig,
|
||||
payload: unknown,
|
||||
key: string,
|
||||
) {
|
||||
if (typeof payload !== "object" || payload === null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const payloadPayload =
|
||||
"payload" in payload &&
|
||||
typeof payload.payload === "object" &&
|
||||
payload.payload !== null
|
||||
? payload.payload
|
||||
: undefined;
|
||||
|
||||
let configLabelKey: string = key;
|
||||
|
||||
if (
|
||||
key in payload &&
|
||||
typeof payload[key as keyof typeof payload] === "string"
|
||||
) {
|
||||
configLabelKey = payload[key as keyof typeof payload] as string;
|
||||
} else if (
|
||||
payloadPayload &&
|
||||
key in payloadPayload &&
|
||||
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
|
||||
) {
|
||||
configLabelKey = payloadPayload[
|
||||
key as keyof typeof payloadPayload
|
||||
] as string;
|
||||
}
|
||||
|
||||
return configLabelKey in config
|
||||
? config[configLabelKey]
|
||||
: config[key as keyof typeof config];
|
||||
}
|
||||
|
||||
export {
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
ChartLegend,
|
||||
ChartLegendContent,
|
||||
ChartStyle,
|
||||
};
|
||||
@@ -121,6 +121,21 @@
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
:root {
|
||||
--chart-1: 12 76% 61%;
|
||||
--chart-2: 173 58% 39%;
|
||||
--chart-3: 197 37% 24%;
|
||||
--chart-4: 43 74% 66%;
|
||||
--chart-5: 27 87% 67%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--chart-1: 220 70% 50%;
|
||||
--chart-2: 160 60% 45%;
|
||||
--chart-3: 30 80% 55%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
|
||||
Reference in New Issue
Block a user