diff --git a/bun.lock b/bun.lock index 4de55e8..75a02f1 100644 --- a/bun.lock +++ b/bun.lock @@ -5,31 +5,29 @@ "": { "name": "localgreenchain", "dependencies": { - "@aws-sdk/client-s3": "^3.937.0", - "@aws-sdk/s3-request-presigner": "^3.937.0", "@tailwindcss/forms": "^0.4.0", "@tailwindcss/typography": "^0.5.1", "@tanstack/react-query": "^4.0.10", "classnames": "^2.3.1", + "d3": "^7.9.0", + "date-fns": "^4.1.0", "drupal-jsonapi-params": "^1.2.2", "html-react-parser": "^1.2.7", - "multer": "^2.0.2", "next": "^12.2.3", "next-drupal": "^1.6.0", "nprogress": "^0.2.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-hook-form": "^7.8.6", - "sharp": "^0.34.5", + "recharts": "^3.4.1", "socks-proxy-agent": "^8.0.2", }, "devDependencies": { "@babel/core": "^7.12.9", + "@types/d3": "^7.4.3", "@types/jest": "^29.5.0", - "@types/multer": "^2.0.0", "@types/node": "^17.0.21", "@types/react": "^17.0.0", - "@types/sharp": "^0.32.0", "autoprefixer": "^10.4.2", "eslint-config-next": "^12.0.10", "jest": "^29.5.0", @@ -43,90 +41,6 @@ "packages": { "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], - "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], - - "@aws-crypto/crc32c": ["@aws-crypto/crc32c@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag=="], - - "@aws-crypto/sha1-browser": ["@aws-crypto/sha1-browser@5.2.0", "", { "dependencies": { "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg=="], - - "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], - - "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], - - "@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="], - - "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], - - "@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.937.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.936.0", "@aws-sdk/credential-provider-node": "3.936.0", "@aws-sdk/middleware-bucket-endpoint": "3.936.0", "@aws-sdk/middleware-expect-continue": "3.936.0", "@aws-sdk/middleware-flexible-checksums": "3.936.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-location-constraint": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-sdk-s3": "3.936.0", "@aws-sdk/middleware-ssec": "3.936.0", "@aws-sdk/middleware-user-agent": "3.936.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/signature-v4-multi-region": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.936.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", "@smithy/eventstream-serde-browser": "^4.2.5", "@smithy/eventstream-serde-config-resolver": "^4.3.5", "@smithy/eventstream-serde-node": "^4.2.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-blob-browser": "^4.2.6", "@smithy/hash-node": "^4.2.5", "@smithy/hash-stream-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/md5-js": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-retry": "^4.4.12", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.11", "@smithy/util-defaults-mode-node": "^4.2.14", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-ioeNe6HSc7PxjsUQY7foSHmgesxM5KwAeUtPhIHgKx99nrM+7xYCfW4FMvHypUzz7ZOvqlCdH7CEAZ8ParBvVg=="], - - "@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.936.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.936.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.936.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.936.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-retry": "^4.4.12", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.11", "@smithy/util-defaults-mode-node": "^4.2.14", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-0G73S2cDqYwJVvqL08eakj79MZG2QRaB56Ul8/Ps9oQxllr7DMI1IQ/N3j3xjxgpq/U36pkoFZ8aK1n7Sbr3IQ=="], - - "@aws-sdk/core": ["@aws-sdk/core@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws-sdk/xml-builder": "3.930.0", "@smithy/core": "^3.18.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-eGJ2ySUMvgtOziHhDRDLCrj473RJoL4J1vPjVM3NrKC/fF3/LoHjkut8AAnKmrW6a2uTzNKubigw8dEnpmpERw=="], - - "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.936.0", "", { "dependencies": { "@aws-sdk/core": "3.936.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-dKajFuaugEA5i9gCKzOaVy9uTeZcApE+7Z5wdcZ6j40523fY1a56khDAUYkCfwqa7sHci4ccmxBkAo+fW1RChA=="], - - "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.936.0", "", { "dependencies": { "@aws-sdk/core": "3.936.0", "@aws-sdk/types": "3.936.0", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-5FguODLXG1tWx/x8fBxH+GVrk7Hey2LbXV5h9SFzYCx/2h50URBm0+9hndg0Rd23+xzYe14F6SI9HA9c1sPnjg=="], - - "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.936.0", "", { "dependencies": { "@aws-sdk/core": "3.936.0", "@aws-sdk/credential-provider-env": "3.936.0", "@aws-sdk/credential-provider-http": "3.936.0", "@aws-sdk/credential-provider-login": "3.936.0", "@aws-sdk/credential-provider-process": "3.936.0", "@aws-sdk/credential-provider-sso": "3.936.0", "@aws-sdk/credential-provider-web-identity": "3.936.0", "@aws-sdk/nested-clients": "3.936.0", "@aws-sdk/types": "3.936.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-TbUv56ERQQujoHcLMcfL0Q6bVZfYF83gu/TjHkVkdSlHPOIKaG/mhE2XZSQzXv1cud6LlgeBbfzVAxJ+HPpffg=="], - - "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.936.0", "", { "dependencies": { "@aws-sdk/core": "3.936.0", "@aws-sdk/nested-clients": "3.936.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-8DVrdRqPyUU66gfV7VZNToh56ZuO5D6agWrkLQE/xbLJOm2RbeRgh6buz7CqV8ipRd6m+zCl9mM4F3osQLZn8Q=="], - - "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.936.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.936.0", "@aws-sdk/credential-provider-http": "3.936.0", "@aws-sdk/credential-provider-ini": "3.936.0", "@aws-sdk/credential-provider-process": "3.936.0", "@aws-sdk/credential-provider-sso": "3.936.0", "@aws-sdk/credential-provider-web-identity": "3.936.0", "@aws-sdk/types": "3.936.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-rk/2PCtxX9xDsQW8p5Yjoca3StqmQcSfkmD7nQ61AqAHL1YgpSQWqHE+HjfGGiHDYKG7PvE33Ku2GyA7lEIJAw=="], - - "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.936.0", "", { "dependencies": { "@aws-sdk/core": "3.936.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-GpA4AcHb96KQK2PSPUyvChvrsEKiLhQ5NWjeef2IZ3Jc8JoosiedYqp6yhZR+S8cTysuvx56WyJIJc8y8OTrLA=="], - - "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.936.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.936.0", "@aws-sdk/core": "3.936.0", "@aws-sdk/token-providers": "3.936.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-wHlEAJJvtnSyxTfNhN98JcU4taA1ED2JvuI2eePgawqBwS/Tzi0mhED1lvNIaWOkjfLd+nHALwszGrtJwEq4yQ=="], - - "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.936.0", "", { "dependencies": { "@aws-sdk/core": "3.936.0", "@aws-sdk/nested-clients": "3.936.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-v3qHAuoODkoRXsAF4RG+ZVO6q2P9yYBT4GMpMEfU9wXVNn7AIfwZgTwzSUfnjNiGva5BKleWVpRpJ9DeuLFbUg=="], - - "@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws-sdk/util-arn-parser": "3.893.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-XLSVVfAorUxZh6dzF+HTOp4R1B5EQcdpGcPliWr0KUj2jukgjZEcqbBmjyMF/p9bmyQsONX80iURF1HLAlW0qg=="], - - "@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Eb4ELAC23bEQLJmUMYnPWcjD3FZIsmz2svDiXEcxRkQU9r7NRID7pM7C5NPH94wOfiCk0b2Y8rVyFXW0lGQwbA=="], - - "@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.936.0", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "3.936.0", "@aws-sdk/types": "3.936.0", "@smithy/is-array-buffer": "^4.2.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-l3GG6CrSQtMCM6fWY7foV3JQv0WJWT+3G6PSP3Ceb/KEE/5Lz5PrYFXTBf+bVoYL1b0bGjGajcgAXpstBmtHtQ=="], - - "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw=="], - - "@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-SCMPenDtQMd9o5da9JzkHz838w3327iqXk3cbNnXWqnNRx6unyW8FL0DZ84gIY12kAyVHz5WEqlWuekc15ehfw=="], - - "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw=="], - - "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws/lambda-invoke-store": "^0.2.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA=="], - - "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.936.0", "", { "dependencies": { "@aws-sdk/core": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-arn-parser": "3.893.0", "@smithy/core": "^3.18.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-UQs/pVq4cOygsnKON0pOdSKIWkfgY0dzq4h+fR+xHi/Ng3XzxPJhWeAE6tDsKrcyQc1X8UdSbS70XkfGYr5hng=="], - - "@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-/GLC9lZdVp05ozRik5KsuODR/N7j+W+2TbfdFL3iS+7un+gnP6hC8RDOZd6WhpZp7drXQ9guKiTAxkZQwzS8DA=="], - - "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.936.0", "", { "dependencies": { "@aws-sdk/core": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@smithy/core": "^3.18.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-YB40IPa7K3iaYX0lSnV9easDOLPLh+fJyUDF3BH8doX4i1AOSsYn86L4lVldmOaSX+DwiaqKHpvk4wPBdcIPWw=="], - - "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.936.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.936.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.936.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.936.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-retry": "^4.4.12", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.11", "@smithy/util-defaults-mode-node": "^4.2.14", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-eyj2tz1XmDSLSZQ5xnB7cLTVKkSJnYAEoNDSUNhzWPxrBDYeJzIbatecOKceKCU8NBf8gWWZCK/CSY0mDxMO0A=="], - - "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/config-resolver": "^4.4.3", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw=="], - - "@aws-sdk/s3-request-presigner": ["@aws-sdk/s3-request-presigner@3.937.0", "", { "dependencies": { "@aws-sdk/signature-v4-multi-region": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-format-url": "3.936.0", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-AvsCt6FnnKTpkmzDA1pFzmXPyxbGBdtllOIY0mL1iNSVZ3d7SoJKZH4NaqlcgUtbYG9zVh6QfLWememj1yEAmw=="], - - "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.936.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.936.0", "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-8qS0GFUqkmwO7JZ0P8tdluBmt1UTfYUah8qJXGzNh9n1Pcb0AIeT117cCSiCUtwk+gDbJvd4hhRIhJCNr5wgjg=="], - - "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.936.0", "", { "dependencies": { "@aws-sdk/core": "3.936.0", "@aws-sdk/nested-clients": "3.936.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-vvw8+VXk0I+IsoxZw0mX9TMJawUJvEsg3EF7zcCSetwhNPAU8Xmlhv7E/sN/FgSmm7b7DsqKoW6rVtQiCs1PWQ=="], - - "@aws-sdk/types": ["@aws-sdk/types@3.936.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg=="], - - "@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.893.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA=="], - - "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-endpoints": "^3.2.5", "tslib": "^2.6.2" } }, "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w=="], - - "@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-MS5eSEtDUFIAMHrJaMERiHAvDPdfxc/T869ZjDNFAIiZhyc037REw0aoTNeimNXDNy2txRNZJaAUn/kE4RwN+g=="], - - "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.893.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg=="], - - "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw=="], - - "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.936.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.936.0", "@aws-sdk/types": "3.936.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-XOEc7PF9Op00pWV2AYCGDSu5iHgYjIO53Py2VUQTIvP7SRCaCsXmA33mjBvC2Ms6FhSyWNa4aK4naUGIz0hQcw=="], - - "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA=="], - - "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.1", "", {}, "sha512-sIyFcoPZkTtNu9xFeEoynMef3bPJIAbOfUh+ueYcfhVl6xm2VRtMcMclSxmZCMnHHd4hlYKJeq/aggmBEWynww=="], - "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], "@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], @@ -197,8 +111,6 @@ "@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="], - "@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], @@ -213,56 +125,6 @@ "@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="], - "@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="], - - "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], - - "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], - - "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], - - "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], - - "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], - - "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], - - "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], - - "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], - - "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], - - "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], - - "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], - - "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], - - "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], - - "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], - - "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], - - "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], - - "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], - - "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], - - "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], - - "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], - - "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], - - "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], - - "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], - - "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], - "@istanbuljs/load-nyc-config": ["@istanbuljs/load-nyc-config@1.1.0", "", { "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" } }, "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ=="], "@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="], @@ -341,6 +203,8 @@ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + "@reduxjs/toolkit": ["@reduxjs/toolkit@2.10.1", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^10.2.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-/U17EXQ9Do9Yx4DlNGU6eVNfZvFJfYpUtRRdLf19PbPjdWBxNlxGZXywQZ1p1Nz8nMkWplTI7iD/23m07nolDA=="], + "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], "@rushstack/eslint-patch": ["@rushstack/eslint-patch@1.15.0", "", {}, "sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw=="], @@ -351,107 +215,9 @@ "@sinonjs/fake-timers": ["@sinonjs/fake-timers@10.3.0", "", { "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA=="], - "@smithy/abort-controller": ["@smithy/abort-controller@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA=="], + "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], - "@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA=="], - - "@smithy/chunked-blob-reader-native": ["@smithy/chunked-blob-reader-native@4.2.1", "", { "dependencies": { "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ=="], - - "@smithy/config-resolver": ["@smithy/config-resolver@4.4.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw=="], - - "@smithy/core": ["@smithy/core@3.18.5", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.6", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-6gnIz3h+PEPQGDj8MnRSjDvKBah042jEoPgjFGJ4iJLBE78L4lY/n98x14XyPF4u3lN179Ub/ZKFY5za9GeLQw=="], - - "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ=="], - - "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.5", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA=="], - - "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.5", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-HohfmCQZjppVnKX2PnXlf47CW3j92Ki6T/vkAT2DhBR47e89pen3s4fIa7otGTtrVxmj7q+IhH0RnC5kpR8wtw=="], - - "@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-ibjQjM7wEXtECiT6my1xfiMH9IcEczMOS6xiCQXoUIYSj5b1CpBbJ3VYbdwDy8Vcg5JHN7eFpOCGk8nyZAltNQ=="], - - "@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.5", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-+elOuaYx6F2H6x1/5BQP5ugv12nfJl66GhxON8+dWVUEDJ9jah/A0tayVdkLRP0AeSac0inYkDz5qBFKfVp2Gg=="], - - "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.5", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-G9WSqbST45bmIFaeNuP/EnC19Rhp54CcVdX9PDL1zyEB514WsDVXhlyihKlGXnRycmHNmVv88Bvvt4EYxWef/Q=="], - - "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg=="], - - "@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.6", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.0", "@smithy/chunked-blob-reader-native": "^4.2.1", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-8P//tA8DVPk+3XURk2rwcKgYwFvwGwmJH/wJqQiSKwXZtf/LiZK+hbUZmPj/9KzM+OVSwe4o85KTp5x9DUZTjw=="], - - "@smithy/hash-node": ["@smithy/hash-node@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA=="], - - "@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6+do24VnEyvWcGdHXomlpd0m8bfZePpUKBy7m311n+JuRwug8J4dCanJdTymx//8mi0nlkflZBvJe+dEO/O12Q=="], - - "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A=="], - - "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], - - "@smithy/md5-js": ["@smithy/md5-js@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Bt6jpSTMWfjCtC0s79gZ/WZ1w90grfmopVOWqkI2ovhjpD5Q2XRXuecIPB9689L2+cCySMbaXDhBPU56FKNDNg=="], - - "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.5", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A=="], - - "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.12", "", { "dependencies": { "@smithy/core": "^3.18.5", "@smithy/middleware-serde": "^4.2.6", "@smithy/node-config-provider": "^4.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-9pAX/H+VQPzNbouhDhkW723igBMLgrI8OtX+++M7iKJgg/zY/Ig3i1e6seCcx22FWhE6Q/S61BRdi2wXBORT+A=="], - - "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/service-error-classification": "^4.2.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-S4kWNKFowYd0lID7/DBqWHOQxmxlsf0jBaos9chQZUWTVOjSW1Ogyh8/ib5tM+agFDJ/TCxuCTvrnlc+9cIBcQ=="], - - "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ=="], - - "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ=="], - - "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.5", "", { "dependencies": { "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg=="], - - "@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw=="], - - "@smithy/property-provider": ["@smithy/property-provider@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg=="], - - "@smithy/protocol-http": ["@smithy/protocol-http@5.3.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ=="], - - "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg=="], - - "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ=="], - - "@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0" } }, "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ=="], - - "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA=="], - - "@smithy/signature-v4": ["@smithy/signature-v4@5.3.5", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w=="], - - "@smithy/smithy-client": ["@smithy/smithy-client@4.9.8", "", { "dependencies": { "@smithy/core": "^3.18.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-stack": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-8xgq3LgKDEFoIrLWBho/oYKyWByw9/corz7vuh1upv7ZBm0ZMjGYBhbn6v643WoIqA9UTcx5A5htEp/YatUwMA=="], - - "@smithy/types": ["@smithy/types@4.9.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA=="], - - "@smithy/url-parser": ["@smithy/url-parser@4.2.5", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ=="], - - "@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], - - "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], - - "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA=="], - - "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], - - "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="], - - "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.11", "", { "dependencies": { "@smithy/property-provider": "^4.2.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-yHv+r6wSQXEXTPVCIQTNmXVWs7ekBTpMVErjqZoWkYN75HIFN5y9+/+sYOejfAuvxWGvgzgxbTHa/oz61YTbKw=="], - - "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.14", "", { "dependencies": { "@smithy/config-resolver": "^4.4.3", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-ljZN3iRvaJUgulfvobIuG97q1iUuCMrvXAlkZ4msY+ZuVHQHDIqn7FKZCEj+bx8omz6kF5yQXms/xhzjIO5XiA=="], - - "@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A=="], - - "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], - - "@smithy/util-middleware": ["@smithy/util-middleware@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA=="], - - "@smithy/util-retry": ["@smithy/util-retry@4.2.5", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg=="], - - "@smithy/util-stream": ["@smithy/util-stream@4.5.6", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ=="], - - "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], - - "@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], - - "@smithy/util-waiter": ["@smithy/util-waiter@4.2.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g=="], - - "@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], + "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], "@swc/helpers": ["@swc/helpers@0.4.11", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-rEUrBSGIoSFuYxwBYtlUFMlE2CwGhmW+w9355/5oduSw8e5h2+Tj4UrAGNNgP9915++wj5vkQo0UuOBqOAq4nw=="], @@ -471,18 +237,72 @@ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], - "@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="], + "@types/d3": ["@types/d3@7.4.3", "", { "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", "@types/d3-brush": "*", "@types/d3-chord": "*", "@types/d3-color": "*", "@types/d3-contour": "*", "@types/d3-delaunay": "*", "@types/d3-dispatch": "*", "@types/d3-drag": "*", "@types/d3-dsv": "*", "@types/d3-ease": "*", "@types/d3-fetch": "*", "@types/d3-force": "*", "@types/d3-format": "*", "@types/d3-geo": "*", "@types/d3-hierarchy": "*", "@types/d3-interpolate": "*", "@types/d3-path": "*", "@types/d3-polygon": "*", "@types/d3-quadtree": "*", "@types/d3-random": "*", "@types/d3-scale": "*", "@types/d3-scale-chromatic": "*", "@types/d3-selection": "*", "@types/d3-shape": "*", "@types/d3-time": "*", "@types/d3-time-format": "*", "@types/d3-timer": "*", "@types/d3-transition": "*", "@types/d3-zoom": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="], - "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], + "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], - "@types/express": ["@types/express@5.0.5", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", "@types/serve-static": "^1" } }, "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ=="], + "@types/d3-axis": ["@types/d3-axis@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw=="], - "@types/express-serve-static-core": ["@types/express-serve-static-core@5.1.0", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA=="], + "@types/d3-brush": ["@types/d3-brush@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A=="], + + "@types/d3-chord": ["@types/d3-chord@3.0.6", "", {}, "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg=="], + + "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], + + "@types/d3-contour": ["@types/d3-contour@3.0.6", "", { "dependencies": { "@types/d3-array": "*", "@types/geojson": "*" } }, "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg=="], + + "@types/d3-delaunay": ["@types/d3-delaunay@6.0.4", "", {}, "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw=="], + + "@types/d3-dispatch": ["@types/d3-dispatch@3.0.7", "", {}, "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA=="], + + "@types/d3-drag": ["@types/d3-drag@3.0.7", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ=="], + + "@types/d3-dsv": ["@types/d3-dsv@3.0.7", "", {}, "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g=="], + + "@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="], + + "@types/d3-fetch": ["@types/d3-fetch@3.0.7", "", { "dependencies": { "@types/d3-dsv": "*" } }, "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA=="], + + "@types/d3-force": ["@types/d3-force@3.0.10", "", {}, "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw=="], + + "@types/d3-format": ["@types/d3-format@3.0.4", "", {}, "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g=="], + + "@types/d3-geo": ["@types/d3-geo@3.1.0", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ=="], + + "@types/d3-hierarchy": ["@types/d3-hierarchy@3.1.7", "", {}, "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg=="], + + "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="], + + "@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], + + "@types/d3-polygon": ["@types/d3-polygon@3.0.2", "", {}, "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA=="], + + "@types/d3-quadtree": ["@types/d3-quadtree@3.0.6", "", {}, "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg=="], + + "@types/d3-random": ["@types/d3-random@3.0.3", "", {}, "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ=="], + + "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="], + + "@types/d3-scale-chromatic": ["@types/d3-scale-chromatic@3.1.0", "", {}, "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ=="], + + "@types/d3-selection": ["@types/d3-selection@3.0.11", "", {}, "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w=="], + + "@types/d3-shape": ["@types/d3-shape@3.1.7", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg=="], + + "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="], + + "@types/d3-time-format": ["@types/d3-time-format@4.0.3", "", {}, "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg=="], + + "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="], + + "@types/d3-transition": ["@types/d3-transition@3.0.9", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg=="], + + "@types/d3-zoom": ["@types/d3-zoom@3.0.8", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw=="], + + "@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="], "@types/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="], - "@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="], - "@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="], "@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="], @@ -493,30 +313,18 @@ "@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="], - "@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="], - - "@types/multer": ["@types/multer@2.0.0", "", { "dependencies": { "@types/express": "*" } }, "sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw=="], - "@types/node": ["@types/node@17.0.45", "", {}, "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="], "@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="], - "@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="], - - "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="], - "@types/react": ["@types/react@17.0.90", "", { "dependencies": { "@types/prop-types": "*", "@types/scheduler": "^0.16", "csstype": "^3.2.2" } }, "sha512-P9beVR/x06U9rCJzSxtENnOr4BrbJ6VrsrDTc+73TtHv9XHhryXKbjGRB+6oooB2r0G/pQkD/S4dHo/7jUfwFw=="], "@types/scheduler": ["@types/scheduler@0.16.8", "", {}, "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A=="], - "@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="], - - "@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="], - - "@types/sharp": ["@types/sharp@0.32.0", "", { "dependencies": { "sharp": "*" } }, "sha512-OOi3kL+FZDnPhVzsfD37J88FNeZh6gQsGcLc95NbeURRGvmSjeXiDcyWzF2o3yh/gQAUn2uhh/e+CPCa5nwAxw=="], - "@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="], + "@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="], + "@types/yargs": ["@types/yargs@17.0.35", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg=="], "@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="], @@ -551,8 +359,6 @@ "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], - "append-field": ["append-field@1.0.0", "", {}, "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="], - "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="], "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], @@ -605,8 +411,6 @@ "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], - "bowser": ["bowser@2.12.1", "", {}, "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw=="], - "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], @@ -619,8 +423,6 @@ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], - "busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="], - "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], @@ -651,6 +453,8 @@ "clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="], + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + "co": ["co@4.6.0", "", {}, "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ=="], "collect-v8-coverage": ["collect-v8-coverage@1.0.3", "", {}, "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw=="], @@ -663,8 +467,6 @@ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - "concat-stream": ["concat-stream@2.0.0", "", { "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.0.2", "typedarray": "^0.0.6" } }, "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A=="], - "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], "create-jest": ["create-jest@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "prompts": "^2.0.1" }, "bin": { "create-jest": "bin/create-jest.js" } }, "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q=="], @@ -675,6 +477,68 @@ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + "d3": ["d3@7.9.0", "", { "dependencies": { "d3-array": "3", "d3-axis": "3", "d3-brush": "3", "d3-chord": "3", "d3-color": "3", "d3-contour": "4", "d3-delaunay": "6", "d3-dispatch": "3", "d3-drag": "3", "d3-dsv": "3", "d3-ease": "3", "d3-fetch": "3", "d3-force": "3", "d3-format": "3", "d3-geo": "3", "d3-hierarchy": "3", "d3-interpolate": "3", "d3-path": "3", "d3-polygon": "3", "d3-quadtree": "3", "d3-random": "3", "d3-scale": "4", "d3-scale-chromatic": "3", "d3-selection": "3", "d3-shape": "3", "d3-time": "3", "d3-time-format": "4", "d3-timer": "3", "d3-transition": "3", "d3-zoom": "3" } }, "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA=="], + + "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], + + "d3-axis": ["d3-axis@3.0.0", "", {}, "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw=="], + + "d3-brush": ["d3-brush@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "3", "d3-transition": "3" } }, "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ=="], + + "d3-chord": ["d3-chord@3.0.1", "", { "dependencies": { "d3-path": "1 - 3" } }, "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g=="], + + "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], + + "d3-contour": ["d3-contour@4.0.2", "", { "dependencies": { "d3-array": "^3.2.0" } }, "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA=="], + + "d3-delaunay": ["d3-delaunay@6.0.4", "", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="], + + "d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="], + + "d3-drag": ["d3-drag@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" } }, "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg=="], + + "d3-dsv": ["d3-dsv@3.0.1", "", { "dependencies": { "commander": "7", "iconv-lite": "0.6", "rw": "1" }, "bin": { "csv2json": "bin/dsv2json.js", "csv2tsv": "bin/dsv2dsv.js", "dsv2dsv": "bin/dsv2dsv.js", "dsv2json": "bin/dsv2json.js", "json2csv": "bin/json2dsv.js", "json2dsv": "bin/json2dsv.js", "json2tsv": "bin/json2dsv.js", "tsv2csv": "bin/dsv2dsv.js", "tsv2json": "bin/dsv2json.js" } }, "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q=="], + + "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], + + "d3-fetch": ["d3-fetch@3.0.1", "", { "dependencies": { "d3-dsv": "1 - 3" } }, "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw=="], + + "d3-force": ["d3-force@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", "d3-timer": "1 - 3" } }, "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg=="], + + "d3-format": ["d3-format@3.1.0", "", {}, "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA=="], + + "d3-geo": ["d3-geo@3.1.1", "", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="], + + "d3-hierarchy": ["d3-hierarchy@3.1.2", "", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="], + + "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], + + "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], + + "d3-polygon": ["d3-polygon@3.0.1", "", {}, "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg=="], + + "d3-quadtree": ["d3-quadtree@3.0.1", "", {}, "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw=="], + + "d3-random": ["d3-random@3.0.1", "", {}, "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="], + + "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], + + "d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="], + + "d3-selection": ["d3-selection@3.0.0", "", {}, "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="], + + "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], + + "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], + + "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="], + + "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], + + "d3-transition": ["d3-transition@3.0.1", "", { "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "peerDependencies": { "d3-selection": "2 - 3" } }, "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w=="], + + "d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="], + "damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="], "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="], @@ -683,8 +547,12 @@ "data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="], + "date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="], + "dedent": ["dedent@1.7.0", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ=="], "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], @@ -695,7 +563,7 @@ "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], - "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "delaunator": ["delaunator@5.0.1", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw=="], "detect-newline": ["detect-newline@3.1.0", "", {}, "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA=="], @@ -747,6 +615,8 @@ "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], + "es-toolkit": ["es-toolkit@1.42.0", "", {}, "sha512-SLHIyY7VfDJBM8clz4+T2oquwTQxEzu263AyhVK4jREOAwJ+8eebaa4wM3nlvnAqhDrMm2EsA6hWHaQsMPQ1nA=="], + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], @@ -785,6 +655,8 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], + "execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], "exit": ["exit@0.1.2", "", {}, "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ=="], @@ -799,8 +671,6 @@ "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - "fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], - "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], "fb-watchman": ["fb-watchman@2.0.2", "", { "dependencies": { "bser": "2.1.1" } }, "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA=="], @@ -889,8 +759,12 @@ "human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "immer": ["immer@10.2.0", "", {}, "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw=="], + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], "import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="], @@ -905,6 +779,8 @@ "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], + "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], + "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="], @@ -1093,18 +969,12 @@ "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], - "media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="], - "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - - "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], "mini-svg-data-uri": ["mini-svg-data-uri@1.4.4", "", { "bin": { "mini-svg-data-uri": "cli.js" } }, "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg=="], @@ -1113,12 +983,8 @@ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], - "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "multer": ["multer@2.0.2", "", { "dependencies": { "append-field": "^1.0.0", "busboy": "^1.6.0", "concat-stream": "^2.0.0", "mkdirp": "^0.5.6", "object-assign": "^4.1.1", "type-is": "^1.6.18", "xtend": "^4.0.2" } }, "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw=="], - "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], @@ -1243,18 +1109,26 @@ "react-property": ["react-property@2.0.0", "", {}, "sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw=="], + "react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="], + "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="], - "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], - "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], + "recharts": ["recharts@3.4.1", "", { "dependencies": { "@reduxjs/toolkit": "1.x.x || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-35kYg6JoOgwq8sE4rhYkVWwa6aAIgOtT+Ob0gitnShjwUwZmhrmy7Jco/5kJNF4PnLXgt9Hwq+geEMS+WrjU1g=="], + + "redux": ["redux@5.0.1", "", {}, "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="], + + "redux-thunk": ["redux-thunk@3.1.0", "", { "peerDependencies": { "redux": "^5.0.0" } }, "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw=="], + "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + "reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="], + "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], "resolve-cwd": ["resolve-cwd@3.0.0", "", { "dependencies": { "resolve-from": "^5.0.0" } }, "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg=="], @@ -1267,16 +1141,20 @@ "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], + "robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="], + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], - "safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="], + "rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="], - "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="], "safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="], "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "scheduler": ["scheduler@0.20.2", "", { "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" } }, "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ=="], "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -1287,8 +1165,6 @@ "set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="], - "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], @@ -1325,8 +1201,6 @@ "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], - "streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="], - "string-length": ["string-length@4.0.2", "", { "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" } }, "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ=="], "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -1343,8 +1217,6 @@ "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="], - "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], - "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], @@ -1353,8 +1225,6 @@ "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], - "strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="], - "style-to-js": ["style-to-js@1.1.1", "", { "dependencies": { "style-to-object": "0.3.0" } }, "sha512-RJ18Z9t2B02sYhZtfWKQq5uplVctgvjTfLWT7+Eb1zjUjIrWzX5SdlkwLGQozrqarTmEzJJ/YmdNJCUNI47elg=="], "style-to-object": ["style-to-object@0.3.0", "", { "dependencies": { "inline-style-parser": "0.1.1" } }, "sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA=="], @@ -1377,6 +1247,8 @@ "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], "tmpl": ["tmpl@1.0.5", "", {}, "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="], @@ -1399,8 +1271,6 @@ "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], - "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], - "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], "typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="], @@ -1409,8 +1279,6 @@ "typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="], - "typedarray": ["typedarray@0.0.6", "", {}, "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="], - "typescript": ["typescript@4.9.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g=="], "uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="], @@ -1427,6 +1295,8 @@ "v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="], + "victory-vendor": ["victory-vendor@37.3.6", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ=="], + "walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], @@ -1449,8 +1319,6 @@ "write-file-atomic": ["write-file-atomic@4.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" } }, "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg=="], - "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], - "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], @@ -1461,12 +1329,6 @@ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - "@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], - - "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], - - "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], - "@istanbuljs/load-nyc-config/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], "@istanbuljs/load-nyc-config/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], @@ -1477,8 +1339,6 @@ "@tailwindcss/typography/postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="], - "@types/serve-static/@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="], - "@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], @@ -1489,6 +1349,8 @@ "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], + "dom-serializer/entities": ["entities@2.2.0", "", {}, "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="], "eslint/doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], @@ -1533,8 +1395,6 @@ "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - "sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], "string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -1551,24 +1411,12 @@ "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], - - "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], - - "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], - "@istanbuljs/load-nyc-config/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], "@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], "pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], - "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], - - "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], - - "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], - "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], "pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], diff --git a/components/EnvironmentalForm.tsx b/components/EnvironmentalForm.tsx index 3e8e269..da0a3d1 100644 --- a/components/EnvironmentalForm.tsx +++ b/components/EnvironmentalForm.tsx @@ -28,10 +28,11 @@ export default function EnvironmentalForm({ section: K, updates: Partial ) => { + const currentSection = value[section] || {}; onChange({ ...value, [section]: { - ...value[section], + ...currentSection, ...updates, }, }); diff --git a/components/analytics/DataTable.tsx b/components/analytics/DataTable.tsx new file mode 100644 index 0000000..f82b0ed --- /dev/null +++ b/components/analytics/DataTable.tsx @@ -0,0 +1,229 @@ +/** + * Data Table Component + * Sortable and filterable data table for analytics + */ + +import { useState, useMemo } from 'react'; + +interface Column { + key: string; + header: string; + sortable?: boolean; + render?: (value: any, row: any) => React.ReactNode; + width?: string; + align?: 'left' | 'center' | 'right'; +} + +interface DataTableProps { + data: any[]; + columns: Column[]; + title?: string; + pageSize?: number; + showSearch?: boolean; + searchPlaceholder?: string; +} + +type SortDirection = 'asc' | 'desc' | null; + +export default function DataTable({ + data, + columns, + title, + pageSize = 10, + showSearch = true, + searchPlaceholder = 'Search...', +}: DataTableProps) { + const [sortKey, setSortKey] = useState(null); + const [sortDir, setSortDir] = useState(null); + const [search, setSearch] = useState(''); + const [page, setPage] = useState(0); + + const filteredData = useMemo(() => { + if (!search) return data; + + const searchLower = search.toLowerCase(); + return data.filter((row) => + columns.some((col) => { + const value = row[col.key]; + return String(value).toLowerCase().includes(searchLower); + }) + ); + }, [data, columns, search]); + + const sortedData = useMemo(() => { + if (!sortKey || !sortDir) return filteredData; + + return [...filteredData].sort((a, b) => { + const aVal = a[sortKey]; + const bVal = b[sortKey]; + + if (aVal === bVal) return 0; + if (aVal === null || aVal === undefined) return 1; + if (bVal === null || bVal === undefined) return -1; + + const comparison = aVal < bVal ? -1 : 1; + return sortDir === 'asc' ? comparison : -comparison; + }); + }, [filteredData, sortKey, sortDir]); + + const paginatedData = useMemo(() => { + const start = page * pageSize; + return sortedData.slice(start, start + pageSize); + }, [sortedData, page, pageSize]); + + const totalPages = Math.ceil(sortedData.length / pageSize); + + const handleSort = (key: string) => { + if (sortKey === key) { + if (sortDir === 'asc') setSortDir('desc'); + else if (sortDir === 'desc') { + setSortKey(null); + setSortDir(null); + } + } else { + setSortKey(key); + setSortDir('asc'); + } + }; + + const getSortIcon = (key: string) => { + if (sortKey !== key) { + return ( + + + + ); + } + if (sortDir === 'asc') { + return ( + + + + ); + } + return ( + + + + ); + }; + + const alignClasses = { + left: 'text-left', + center: 'text-center', + right: 'text-right', + }; + + return ( +
+ {/* Header */} +
+
+ {title &&

{title}

} + {showSearch && ( +
+ { + setSearch(e.target.value); + setPage(0); + }} + className="pl-10 pr-4 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-green-500" + /> + + + +
+ )} +
+
+ + {/* Table */} +
+ + + + {columns.map((col) => ( + + ))} + + + + {paginatedData.length === 0 ? ( + + + + ) : ( + paginatedData.map((row, rowIndex) => ( + + {columns.map((col) => ( + + ))} + + )) + )} + +
col.sortable !== false && handleSort(col.key)} + > +
+ {col.header} + {col.sortable !== false && getSortIcon(col.key)} +
+
+ No data available +
+ {col.render ? col.render(row[col.key], row) : row[col.key]} +
+
+ + {/* Pagination */} + {totalPages > 1 && ( +
+ + Showing {page * pageSize + 1} to {Math.min((page + 1) * pageSize, sortedData.length)} of{' '} + {sortedData.length} results + +
+ + +
+
+ )} +
+ ); +} diff --git a/components/analytics/DateRangePicker.tsx b/components/analytics/DateRangePicker.tsx new file mode 100644 index 0000000..a3e8cf0 --- /dev/null +++ b/components/analytics/DateRangePicker.tsx @@ -0,0 +1,47 @@ +/** + * Date Range Picker Component + * Allows selection of time range for analytics + */ + +import { TimeRange } from '../../lib/analytics/types'; + +interface DateRangePickerProps { + value: TimeRange; + onChange: (range: TimeRange) => void; + showCustom?: boolean; +} + +const timeRangeOptions: { value: TimeRange; label: string }[] = [ + { value: '7d', label: 'Last 7 days' }, + { value: '30d', label: 'Last 30 days' }, + { value: '90d', label: 'Last 90 days' }, + { value: '365d', label: 'Last year' }, + { value: 'all', label: 'All time' }, +]; + +export default function DateRangePicker({ + value, + onChange, + showCustom = false, +}: DateRangePickerProps) { + return ( +
+ Time range: +
+ {timeRangeOptions.map((option) => ( + + ))} +
+
+ ); +} diff --git a/components/analytics/FilterPanel.tsx b/components/analytics/FilterPanel.tsx new file mode 100644 index 0000000..fed04bd --- /dev/null +++ b/components/analytics/FilterPanel.tsx @@ -0,0 +1,165 @@ +/** + * Filter Panel Component + * Provides filtering options for analytics data + */ + +import { useState } from 'react'; + +interface FilterOption { + value: string; + label: string; +} + +interface FilterConfig { + key: string; + label: string; + type: 'select' | 'multiselect' | 'search'; + options?: FilterOption[]; +} + +interface FilterPanelProps { + filters: FilterConfig[]; + values: Record; + onChange: (values: Record) => void; + onReset?: () => void; +} + +export default function FilterPanel({ + filters, + values, + onChange, + onReset, +}: FilterPanelProps) { + const [isExpanded, setIsExpanded] = useState(false); + + const handleChange = (key: string, value: any) => { + onChange({ ...values, [key]: value }); + }; + + const handleMultiSelect = (key: string, value: string) => { + const current = values[key] || []; + const updated = current.includes(value) + ? current.filter((v: string) => v !== value) + : [...current, value]; + handleChange(key, updated); + }; + + const activeFilterCount = Object.values(values).filter( + (v) => v && (Array.isArray(v) ? v.length > 0 : true) + ).length; + + return ( +
+ {/* Header */} + + + {/* Filter content */} + {isExpanded && ( +
+
+ {filters.map((filter) => ( +
+ + {filter.type === 'select' && filter.options && ( + + )} + {filter.type === 'multiselect' && filter.options && ( +
+ {filter.options.map((opt) => ( + + ))} +
+ )} + {filter.type === 'search' && ( + handleChange(filter.key, e.target.value || null)} + placeholder={`Search ${filter.label.toLowerCase()}...`} + className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-green-500" + /> + )} +
+ ))} +
+ + {/* Actions */} +
+ {onReset && ( + + )} + +
+
+ )} +
+ ); +} diff --git a/components/analytics/KPICard.tsx b/components/analytics/KPICard.tsx new file mode 100644 index 0000000..b8d5e1f --- /dev/null +++ b/components/analytics/KPICard.tsx @@ -0,0 +1,129 @@ +/** + * KPI Card Component + * Displays key performance indicators with trend indicators + */ + +import { TrendDirection } from '../../lib/analytics/types'; + +interface KPICardProps { + title: string; + value: number | string; + unit?: string; + change?: number; + changePercent?: number; + trend?: TrendDirection; + color?: 'green' | 'blue' | 'purple' | 'orange' | 'red' | 'teal'; + icon?: React.ReactNode; + loading?: boolean; +} + +const colorClasses = { + green: { + bg: 'bg-green-50', + text: 'text-green-600', + icon: 'text-green-500', + }, + blue: { + bg: 'bg-blue-50', + text: 'text-blue-600', + icon: 'text-blue-500', + }, + purple: { + bg: 'bg-purple-50', + text: 'text-purple-600', + icon: 'text-purple-500', + }, + orange: { + bg: 'bg-orange-50', + text: 'text-orange-600', + icon: 'text-orange-500', + }, + red: { + bg: 'bg-red-50', + text: 'text-red-600', + icon: 'text-red-500', + }, + teal: { + bg: 'bg-teal-50', + text: 'text-teal-600', + icon: 'text-teal-500', + }, +}; + +export default function KPICard({ + title, + value, + unit, + change, + changePercent, + trend = 'stable', + color = 'green', + icon, + loading = false, +}: KPICardProps) { + const classes = colorClasses[color]; + + const getTrendIcon = () => { + if (trend === 'up') { + return ( + + + + ); + } + if (trend === 'down') { + return ( + + + + ); + } + return ( + + + + ); + }; + + const getTrendColor = () => { + if (trend === 'up') return 'text-green-600'; + if (trend === 'down') return 'text-red-600'; + return 'text-gray-500'; + }; + + if (loading) { + return ( +
+
+
+
+
+ ); + } + + return ( +
+
+

{title}

+ {icon && {icon}} +
+
+

{value}

+ {unit && {unit}} +
+ {(change !== undefined || changePercent !== undefined) && ( +
+ {getTrendIcon()} + + {changePercent !== undefined + ? `${changePercent > 0 ? '+' : ''}${changePercent.toFixed(1)}%` + : change !== undefined + ? `${change > 0 ? '+' : ''}${change}` + : ''} + + vs prev period +
+ )} +
+ ); +} diff --git a/components/analytics/TrendIndicator.tsx b/components/analytics/TrendIndicator.tsx new file mode 100644 index 0000000..c32977d --- /dev/null +++ b/components/analytics/TrendIndicator.tsx @@ -0,0 +1,105 @@ +/** + * Trend Indicator Component + * Shows trend direction with visual indicators + */ + +import { TrendDirection } from '../../lib/analytics/types'; + +interface TrendIndicatorProps { + direction: TrendDirection; + value?: number; + showLabel?: boolean; + size?: 'sm' | 'md' | 'lg'; +} + +const sizeClasses = { + sm: 'w-3 h-3', + md: 'w-4 h-4', + lg: 'w-5 h-5', +}; + +const textSizeClasses = { + sm: 'text-xs', + md: 'text-sm', + lg: 'text-base', +}; + +export default function TrendIndicator({ + direction, + value, + showLabel = false, + size = 'md', +}: TrendIndicatorProps) { + const iconSize = sizeClasses[size]; + const textSize = textSizeClasses[size]; + + const getColor = () => { + switch (direction) { + case 'up': + return 'text-green-500'; + case 'down': + return 'text-red-500'; + default: + return 'text-gray-400'; + } + }; + + const getBgColor = () => { + switch (direction) { + case 'up': + return 'bg-green-100'; + case 'down': + return 'bg-red-100'; + default: + return 'bg-gray-100'; + } + }; + + const getIcon = () => { + switch (direction) { + case 'up': + return ( + + + + ); + case 'down': + return ( + + + + ); + default: + return ( + + + + ); + } + }; + + const getLabel = () => { + switch (direction) { + case 'up': + return 'Increasing'; + case 'down': + return 'Decreasing'; + default: + return 'Stable'; + } + }; + + return ( +
+ {getIcon()} + {value !== undefined && ( + + {value > 0 ? '+' : ''}{value.toFixed(1)}% + + )} + {showLabel && ( + {getLabel()} + )} +
+ ); +} diff --git a/components/analytics/charts/AreaChart.tsx b/components/analytics/charts/AreaChart.tsx new file mode 100644 index 0000000..4f20b78 --- /dev/null +++ b/components/analytics/charts/AreaChart.tsx @@ -0,0 +1,98 @@ +/** + * Area Chart Component + * Displays time series data as a filled area chart + */ + +import { + AreaChart as RechartsAreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, +} from 'recharts'; + +interface AreaChartProps { + data: any[]; + xKey: string; + yKey: string | string[]; + title?: string; + colors?: string[]; + height?: number; + showGrid?: boolean; + showLegend?: boolean; + stacked?: boolean; + gradient?: boolean; + formatter?: (value: number) => string; +} + +const DEFAULT_COLORS = ['#10b981', '#3b82f6', '#8b5cf6', '#f59e0b', '#ef4444']; + +export default function AreaChart({ + data, + xKey, + yKey, + title, + colors = DEFAULT_COLORS, + height = 300, + showGrid = true, + showLegend = true, + stacked = false, + gradient = true, + formatter = (value) => value.toLocaleString(), +}: AreaChartProps) { + const yKeys = Array.isArray(yKey) ? yKey : [yKey]; + + return ( +
+ {title &&

{title}

} + + + + {yKeys.map((key, index) => ( + + + + + ))} + + {showGrid && } + + + [formatter(value), '']} + /> + {showLegend && } + {yKeys.map((key, index) => ( + + ))} + + +
+ ); +} diff --git a/components/analytics/charts/BarChart.tsx b/components/analytics/charts/BarChart.tsx new file mode 100644 index 0000000..60848fc --- /dev/null +++ b/components/analytics/charts/BarChart.tsx @@ -0,0 +1,99 @@ +/** + * Bar Chart Component + * Displays categorical data as bars + */ + +import { + BarChart as RechartsBarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, + Cell, +} from 'recharts'; + +interface BarChartProps { + data: any[]; + xKey: string; + yKey: string | string[]; + title?: string; + colors?: string[]; + height?: number; + showGrid?: boolean; + showLegend?: boolean; + stacked?: boolean; + horizontal?: boolean; + formatter?: (value: number) => string; +} + +const DEFAULT_COLORS = ['#10b981', '#3b82f6', '#8b5cf6', '#f59e0b', '#ef4444', '#06b6d4']; + +export default function BarChart({ + data, + xKey, + yKey, + title, + colors = DEFAULT_COLORS, + height = 300, + showGrid = true, + showLegend = true, + stacked = false, + horizontal = false, + formatter = (value) => value.toLocaleString(), +}: BarChartProps) { + const yKeys = Array.isArray(yKey) ? yKey : [yKey]; + const layout = horizontal ? 'vertical' : 'horizontal'; + + return ( +
+ {title &&

{title}

} + + + {showGrid && } + {horizontal ? ( + <> + + + + ) : ( + <> + + + + )} + [formatter(value), '']} + /> + {showLegend && yKeys.length > 1 && } + {yKeys.map((key, index) => ( + + {yKeys.length === 1 && + data.map((entry, i) => ( + + ))} + + ))} + + +
+ ); +} diff --git a/components/analytics/charts/Gauge.tsx b/components/analytics/charts/Gauge.tsx new file mode 100644 index 0000000..98162ff --- /dev/null +++ b/components/analytics/charts/Gauge.tsx @@ -0,0 +1,91 @@ +/** + * Gauge Chart Component + * Displays a single value as a gauge/meter + */ + +import { PieChart, Pie, Cell, ResponsiveContainer } from 'recharts'; + +interface GaugeProps { + value: number; + max?: number; + title?: string; + unit?: string; + size?: number; + colors?: { low: string; medium: string; high: string }; + thresholds?: { low: number; high: number }; +} + +const DEFAULT_COLORS = { + low: '#ef4444', + medium: '#f59e0b', + high: '#10b981', +}; + +export default function Gauge({ + value, + max = 100, + title, + unit = '%', + size = 200, + colors = DEFAULT_COLORS, + thresholds = { low: 33, high: 66 }, +}: GaugeProps) { + const percentage = Math.min((value / max) * 100, 100); + + // Determine color based on thresholds + let color: string; + if (percentage < thresholds.low) { + color = colors.low; + } else if (percentage < thresholds.high) { + color = colors.medium; + } else { + color = colors.high; + } + + // Data for semi-circle gauge + const gaugeData = [ + { value: percentage, color }, + { value: 100 - percentage, color: '#e5e7eb' }, + ]; + + return ( +
+ {title &&

{title}

} +
+ + + + {gaugeData.map((entry, index) => ( + + ))} + + + +
+ + {value.toFixed(1)} + + {unit} +
+
+
+ 0 + {max / 2} + {max} +
+
+ ); +} diff --git a/components/analytics/charts/Heatmap.tsx b/components/analytics/charts/Heatmap.tsx new file mode 100644 index 0000000..f1d5077 --- /dev/null +++ b/components/analytics/charts/Heatmap.tsx @@ -0,0 +1,134 @@ +/** + * Heatmap Component + * Displays data intensity across a grid + */ + +interface HeatmapCell { + x: string; + y: string; + value: number; +} + +interface HeatmapProps { + data: HeatmapCell[]; + title?: string; + xLabels: string[]; + yLabels: string[]; + colorRange?: { min: string; max: string }; + height?: number; + showValues?: boolean; +} + +function interpolateColor(color1: string, color2: string, factor: number): string { + const hex = (c: string) => parseInt(c, 16); + const r1 = hex(color1.slice(1, 3)); + const g1 = hex(color1.slice(3, 5)); + const b1 = hex(color1.slice(5, 7)); + const r2 = hex(color2.slice(1, 3)); + const g2 = hex(color2.slice(3, 5)); + const b2 = hex(color2.slice(5, 7)); + + const r = Math.round(r1 + (r2 - r1) * factor); + const g = Math.round(g1 + (g2 - g1) * factor); + const b = Math.round(b1 + (b2 - b1) * factor); + + return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`; +} + +export default function Heatmap({ + data, + title, + xLabels, + yLabels, + colorRange = { min: '#fee2e2', max: '#10b981' }, + height = 300, + showValues = true, +}: HeatmapProps) { + const maxValue = Math.max(...data.map((d) => d.value)); + const minValue = Math.min(...data.map((d) => d.value)); + const range = maxValue - minValue || 1; + + const getColor = (value: number): string => { + const factor = (value - minValue) / range; + return interpolateColor(colorRange.min, colorRange.max, factor); + }; + + const getValue = (x: string, y: string): number | undefined => { + const cell = data.find((d) => d.x === x && d.y === y); + return cell?.value; + }; + + const cellWidth = `${100 / xLabels.length}%`; + const cellHeight = (height - 40) / yLabels.length; + + return ( +
+ {title &&

{title}

} +
+ {/* X Labels */} +
+ {xLabels.map((label) => ( +
+ {label} +
+ ))} +
+ + {/* Grid */} + {yLabels.map((yLabel) => ( +
+
+ {yLabel} +
+ {xLabels.map((xLabel) => { + const value = getValue(xLabel, yLabel); + const bgColor = value !== undefined ? getColor(value) : '#f3f4f6'; + const textColor = + value !== undefined && (value - minValue) / range > 0.5 + ? '#fff' + : '#374151'; + + return ( +
+ {showValues && value !== undefined && ( + + {value.toFixed(0)} + + )} +
+ ); + })} +
+ ))} + + {/* Legend */} +
+ Low +
+ High +
+
+
+ ); +} diff --git a/components/analytics/charts/LineChart.tsx b/components/analytics/charts/LineChart.tsx new file mode 100644 index 0000000..492ab56 --- /dev/null +++ b/components/analytics/charts/LineChart.tsx @@ -0,0 +1,85 @@ +/** + * Line Chart Component + * Displays time series data as a line chart + */ + +import { + LineChart as RechartsLineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, +} from 'recharts'; + +interface LineChartProps { + data: any[]; + xKey: string; + yKey: string | string[]; + title?: string; + colors?: string[]; + height?: number; + showGrid?: boolean; + showLegend?: boolean; + formatter?: (value: number) => string; +} + +const DEFAULT_COLORS = ['#10b981', '#3b82f6', '#8b5cf6', '#f59e0b', '#ef4444']; + +export default function LineChart({ + data, + xKey, + yKey, + title, + colors = DEFAULT_COLORS, + height = 300, + showGrid = true, + showLegend = true, + formatter = (value) => value.toLocaleString(), +}: LineChartProps) { + const yKeys = Array.isArray(yKey) ? yKey : [yKey]; + + return ( +
+ {title &&

{title}

} + + + {showGrid && } + + + [formatter(value), '']} + /> + {showLegend && } + {yKeys.map((key, index) => ( + + ))} + + +
+ ); +} diff --git a/components/analytics/charts/PieChart.tsx b/components/analytics/charts/PieChart.tsx new file mode 100644 index 0000000..4c14fb6 --- /dev/null +++ b/components/analytics/charts/PieChart.tsx @@ -0,0 +1,123 @@ +/** + * Pie Chart Component + * Displays distribution data as a pie chart + */ + +import { + PieChart as RechartsPieChart, + Pie, + Cell, + Tooltip, + Legend, + ResponsiveContainer, +} from 'recharts'; + +interface PieChartProps { + data: any[]; + dataKey: string; + nameKey: string; + title?: string; + colors?: string[]; + height?: number; + showLegend?: boolean; + innerRadius?: number; + outerRadius?: number; + formatter?: (value: number) => string; +} + +const DEFAULT_COLORS = [ + '#10b981', '#3b82f6', '#8b5cf6', '#f59e0b', '#ef4444', + '#06b6d4', '#ec4899', '#84cc16', '#f97316', '#6366f1', +]; + +export default function PieChart({ + data, + dataKey, + nameKey, + title, + colors = DEFAULT_COLORS, + height = 300, + showLegend = true, + innerRadius = 0, + outerRadius = 80, + formatter = (value) => value.toLocaleString(), +}: PieChartProps) { + const RADIAN = Math.PI / 180; + + const renderCustomizedLabel = ({ + cx, + cy, + midAngle, + innerRadius, + outerRadius, + percent, + }: any) => { + if (percent < 0.05) return null; + + const radius = innerRadius + (outerRadius - innerRadius) * 0.5; + const x = cx + radius * Math.cos(-midAngle * RADIAN); + const y = cy + radius * Math.sin(-midAngle * RADIAN); + + return ( + cx ? 'start' : 'end'} + dominantBaseline="central" + fontSize="12" + fontWeight="bold" + > + {`${(percent * 100).toFixed(0)}%`} + + ); + }; + + return ( +
+ {title &&

{title}

} + + + + {data.map((entry, index) => ( + + ))} + + [formatter(value), '']} + /> + {showLegend && ( + + )} + + +
+ ); +} diff --git a/components/analytics/charts/index.ts b/components/analytics/charts/index.ts new file mode 100644 index 0000000..00af66d --- /dev/null +++ b/components/analytics/charts/index.ts @@ -0,0 +1,11 @@ +/** + * Chart Components Index + * Export all chart components + */ + +export { default as LineChart } from './LineChart'; +export { default as BarChart } from './BarChart'; +export { default as PieChart } from './PieChart'; +export { default as AreaChart } from './AreaChart'; +export { default as Gauge } from './Gauge'; +export { default as Heatmap } from './Heatmap'; diff --git a/components/analytics/index.ts b/components/analytics/index.ts index 6c36352..d02950e 100644 --- a/components/analytics/index.ts +++ b/components/analytics/index.ts @@ -1,3 +1,19 @@ +/** + * Analytics Components Index + * Export all analytics components + */ + +// Charts +export * from './charts'; + +// Widgets +export { default as KPICard } from './KPICard'; +export { default as TrendIndicator } from './TrendIndicator'; +export { default as DataTable } from './DataTable'; +export { default as DateRangePicker } from './DateRangePicker'; +export { default as FilterPanel } from './FilterPanel'; + +// Existing components export { default as EnvironmentalImpact } from './EnvironmentalImpact'; export { default as FoodMilesTracker } from './FoodMilesTracker'; export { default as SavingsCalculator } from './SavingsCalculator'; diff --git a/lib/agents/AgentOrchestrator.ts b/lib/agents/AgentOrchestrator.ts index 68566b3..66d6f15 100644 --- a/lib/agents/AgentOrchestrator.ts +++ b/lib/agents/AgentOrchestrator.ts @@ -201,7 +201,7 @@ export class AgentOrchestrator { } // Stop all agents - for (const agent of this.agents.values()) { + for (const agent of Array.from(this.agents.values())) { try { await agent.stop(); console.log(`[Orchestrator] Stopped: ${agent.config.name}`); @@ -275,7 +275,7 @@ export class AgentOrchestrator { * Perform health check on all agents */ private performHealthCheck(): void { - for (const [agentId, agent] of this.agents) { + for (const [agentId, agent] of Array.from(this.agents.entries())) { const health = this.getAgentHealth(agentId); if (!health.isHealthy) { @@ -296,7 +296,7 @@ export class AgentOrchestrator { private aggregateAlerts(): void { this.aggregatedAlerts = []; - for (const agent of this.agents.values()) { + for (const agent of Array.from(this.agents.values())) { const alerts = agent.getAlerts() .filter(a => !a.acknowledged) .slice(-this.config.maxAlertsPerAgent); diff --git a/lib/analytics/aggregator.ts b/lib/analytics/aggregator.ts new file mode 100644 index 0000000..3c3d98a --- /dev/null +++ b/lib/analytics/aggregator.ts @@ -0,0 +1,406 @@ +/** + * Data Aggregator for Analytics + * Aggregates data from various sources for analytics dashboards + */ + +import { + AnalyticsOverview, + PlantAnalytics, + TransportAnalytics, + FarmAnalytics, + SustainabilityAnalytics, + TimeRange, + DateRange, + TimeSeriesDataPoint, + AnalyticsFilters, + AggregationConfig, + GroupByPeriod, +} from './types'; +import { subDays, subMonths, startOfDay, endOfDay, format, eachDayOfInterval, parseISO } from 'date-fns'; + +// Mock data generators for demonstration - in production these would query actual databases + +/** + * Get date range from TimeRange enum + */ +export function getDateRangeFromTimeRange(timeRange: TimeRange): DateRange { + const end = endOfDay(new Date()); + let start: Date; + + switch (timeRange) { + case '7d': + start = startOfDay(subDays(new Date(), 7)); + break; + case '30d': + start = startOfDay(subDays(new Date(), 30)); + break; + case '90d': + start = startOfDay(subDays(new Date(), 90)); + break; + case '365d': + start = startOfDay(subDays(new Date(), 365)); + break; + case 'all': + default: + start = startOfDay(subMonths(new Date(), 24)); // Default to 2 years + } + + return { start, end }; +} + +/** + * Generate time series data points for a date range + */ +export function generateTimeSeriesPoints( + dateRange: DateRange, + valueGenerator: (date: Date, index: number) => number +): TimeSeriesDataPoint[] { + const days = eachDayOfInterval({ start: dateRange.start, end: dateRange.end }); + return days.map((day, index) => ({ + timestamp: format(day, 'yyyy-MM-dd'), + value: valueGenerator(day, index), + label: format(day, 'MMM d'), + })); +} + +/** + * Aggregate data by time period + */ +export function aggregateByPeriod( + data: T[], + dateField: keyof T, + valueField: keyof T, + period: GroupByPeriod +): Record { + const aggregated: Record = {}; + + data.forEach((item) => { + const date = parseISO(item[dateField] as string); + let key: string; + + switch (period) { + case 'hour': + key = format(date, 'yyyy-MM-dd HH:00'); + break; + case 'day': + key = format(date, 'yyyy-MM-dd'); + break; + case 'week': + key = format(date, "yyyy-'W'ww"); + break; + case 'month': + key = format(date, 'yyyy-MM'); + break; + case 'year': + key = format(date, 'yyyy'); + break; + } + + aggregated[key] = (aggregated[key] || 0) + (item[valueField] as number); + }); + + return aggregated; +} + +/** + * Calculate percentage change between two values + */ +export function calculateChange(current: number, previous: number): { change: number; percent: number } { + const change = current - previous; + const percent = previous !== 0 ? (change / previous) * 100 : current > 0 ? 100 : 0; + return { change, percent }; +} + +/** + * Get analytics overview with aggregated metrics + */ +export async function getAnalyticsOverview( + filters: AnalyticsFilters = { timeRange: '30d' } +): Promise { + const dateRange = filters.dateRange || getDateRangeFromTimeRange(filters.timeRange); + + // In production, these would be actual database queries + // For now, generate realistic mock data + const baseValue = 1000 + Math.random() * 500; + + return { + totalPlants: Math.floor(baseValue * 1.5), + plantsRegisteredToday: Math.floor(Math.random() * 15 + 5), + plantsRegisteredThisWeek: Math.floor(Math.random() * 80 + 40), + plantsRegisteredThisMonth: Math.floor(Math.random() * 250 + 150), + totalTransportEvents: Math.floor(baseValue * 2.3), + totalCarbonKg: Math.round((Math.random() * 500 + 200) * 100) / 100, + totalFoodMiles: Math.round((Math.random() * 10000 + 5000) * 10) / 10, + activeUsers: Math.floor(Math.random() * 200 + 100), + growthRate: Math.round((Math.random() * 20 + 5) * 10) / 10, + trendsData: [ + { + metric: 'Plants', + currentValue: Math.floor(baseValue * 1.5), + previousValue: Math.floor(baseValue * 1.35), + change: Math.floor(baseValue * 0.15), + changePercent: 11.1, + direction: 'up', + period: filters.timeRange, + }, + { + metric: 'Carbon Saved', + currentValue: Math.round((Math.random() * 200 + 100) * 10) / 10, + previousValue: Math.round((Math.random() * 180 + 90) * 10) / 10, + change: Math.round((Math.random() * 20 + 10) * 10) / 10, + changePercent: 12.5, + direction: 'up', + period: filters.timeRange, + }, + { + metric: 'Active Users', + currentValue: Math.floor(Math.random() * 200 + 100), + previousValue: Math.floor(Math.random() * 180 + 90), + change: Math.floor(Math.random() * 30 + 10), + changePercent: 8.3, + direction: 'up', + period: filters.timeRange, + }, + { + metric: 'Food Miles', + currentValue: Math.round((Math.random() * 5000 + 2500) * 10) / 10, + previousValue: Math.round((Math.random() * 5500 + 2800) * 10) / 10, + change: -Math.round((Math.random() * 500 + 200) * 10) / 10, + changePercent: -8.7, + direction: 'down', + period: filters.timeRange, + }, + ], + }; +} + +/** + * Get plant-specific analytics + */ +export async function getPlantAnalytics( + filters: AnalyticsFilters = { timeRange: '30d' } +): Promise { + const dateRange = filters.dateRange || getDateRangeFromTimeRange(filters.timeRange); + + const speciesData = [ + { species: 'Tomato', count: 245, percentage: 28.5, trend: 'up' as const }, + { species: 'Lettuce', count: 198, percentage: 23.0, trend: 'up' as const }, + { species: 'Pepper', count: 156, percentage: 18.1, trend: 'stable' as const }, + { species: 'Basil', count: 134, percentage: 15.6, trend: 'up' as const }, + { species: 'Cucumber', count: 87, percentage: 10.1, trend: 'down' as const }, + { species: 'Other', count: 41, percentage: 4.7, trend: 'stable' as const }, + ]; + + return { + totalPlants: speciesData.reduce((sum, s) => sum + s.count, 0), + plantsBySpecies: speciesData, + plantsByGeneration: [ + { generation: 1, count: 340, percentage: 39.5 }, + { generation: 2, count: 280, percentage: 32.5 }, + { generation: 3, count: 156, percentage: 18.1 }, + { generation: 4, count: 68, percentage: 7.9 }, + { generation: 5, count: 17, percentage: 2.0 }, + ], + registrationsTrend: generateTimeSeriesPoints(dateRange, (_, i) => + Math.floor(Math.random() * 15 + 5 + Math.sin(i / 7) * 5) + ), + averageLineageDepth: 2.3, + topGrowers: [ + { userId: 'user-1', name: 'Green Gardens Co', totalPlants: 145, totalSpecies: 12, averageGeneration: 2.1 }, + { userId: 'user-2', name: 'Urban Farm LLC', totalPlants: 98, totalSpecies: 8, averageGeneration: 1.8 }, + { userId: 'user-3', name: 'Local Seeds Inc', totalPlants: 76, totalSpecies: 15, averageGeneration: 3.2 }, + ], + recentRegistrations: [ + { id: 'plant-1', name: 'Cherry Tomato #245', species: 'Tomato', registeredAt: new Date().toISOString(), generation: 3 }, + { id: 'plant-2', name: 'Butterhead Lettuce', species: 'Lettuce', registeredAt: new Date().toISOString(), generation: 2 }, + { id: 'plant-3', name: 'Sweet Basil', species: 'Basil', registeredAt: new Date().toISOString(), generation: 1 }, + ], + }; +} + +/** + * Get transport analytics + */ +export async function getTransportAnalytics( + filters: AnalyticsFilters = { timeRange: '30d' } +): Promise { + const dateRange = filters.dateRange || getDateRangeFromTimeRange(filters.timeRange); + + return { + totalEvents: 2847, + totalDistanceKm: 15234.5, + totalCarbonKg: 487.3, + carbonSavedKg: 1256.8, + eventsByType: [ + { eventType: 'seed_acquisition', count: 423, percentage: 14.9, carbonKg: 52.3 }, + { eventType: 'growing_transport', count: 687, percentage: 24.1, carbonKg: 112.4 }, + { eventType: 'harvest', count: 534, percentage: 18.8, carbonKg: 45.2 }, + { eventType: 'distribution', count: 756, percentage: 26.6, carbonKg: 178.9 }, + { eventType: 'consumer_delivery', count: 447, percentage: 15.7, carbonKg: 98.5 }, + ], + eventsByMethod: [ + { method: 'walking', count: 312, percentage: 11.0, distanceKm: 156, carbonKg: 0, efficiency: 100 }, + { method: 'bicycle', count: 534, percentage: 18.8, distanceKm: 1602, carbonKg: 0, efficiency: 100 }, + { method: 'electric_vehicle', count: 687, percentage: 24.1, distanceKm: 4806, carbonKg: 72.1, efficiency: 85 }, + { method: 'gasoline_vehicle', count: 756, percentage: 26.6, distanceKm: 5292, carbonKg: 264.6, efficiency: 45 }, + { method: 'local_delivery', count: 558, percentage: 19.6, distanceKm: 3378, carbonKg: 150.6, efficiency: 60 }, + ], + dailyStats: generateTimeSeriesPoints(dateRange, (_, i) => ({ + date: format(dateRange.start, 'yyyy-MM-dd'), + eventCount: Math.floor(Math.random() * 80 + 40), + distanceKm: Math.round((Math.random() * 500 + 200) * 10) / 10, + carbonKg: Math.round((Math.random() * 20 + 5) * 100) / 100, + })).map(p => ({ + date: p.timestamp, + eventCount: p.value, + distanceKm: Math.round((Math.random() * 500 + 200) * 10) / 10, + carbonKg: Math.round((Math.random() * 20 + 5) * 100) / 100, + })), + averageDistancePerEvent: 5.35, + mostEfficientRoutes: [ + { from: 'Local Farm A', to: 'Community Center', method: 'bicycle', distanceKm: 2.3, carbonKg: 0, frequency: 45 }, + { from: 'Urban Garden', to: 'Farmers Market', method: 'walking', distanceKm: 0.8, carbonKg: 0, frequency: 38 }, + { from: 'Rooftop Farm', to: 'Restaurant Row', method: 'electric_vehicle', distanceKm: 4.5, carbonKg: 0.07, frequency: 32 }, + ], + carbonTrend: generateTimeSeriesPoints(dateRange, (_, i) => + Math.round((Math.random() * 15 + 10 - i * 0.1) * 100) / 100 + ), + }; +} + +/** + * Get farm analytics + */ +export async function getFarmAnalytics( + filters: AnalyticsFilters = { timeRange: '30d' } +): Promise { + const dateRange = filters.dateRange || getDateRangeFromTimeRange(filters.timeRange); + + return { + totalFarms: 24, + totalZones: 156, + activeBatches: 89, + completedBatches: 234, + averageYieldKg: 45.6, + resourceUsage: { + waterLiters: 125000, + energyKwh: 8500, + nutrientsKg: 450, + waterEfficiency: 87.5, + energyEfficiency: 92.3, + }, + performanceByZone: [ + { zoneId: 'zone-1', zoneName: 'Zone A - Leafy Greens', currentCrop: 'Lettuce', healthScore: 94, yieldKg: 52.3, efficiency: 91 }, + { zoneId: 'zone-2', zoneName: 'Zone B - Herbs', currentCrop: 'Basil', healthScore: 88, yieldKg: 38.7, efficiency: 85 }, + { zoneId: 'zone-3', zoneName: 'Zone C - Tomatoes', currentCrop: 'Cherry Tomato', healthScore: 92, yieldKg: 67.4, efficiency: 89 }, + { zoneId: 'zone-4', zoneName: 'Zone D - Microgreens', currentCrop: 'Mixed Micro', healthScore: 96, yieldKg: 24.1, efficiency: 94 }, + ], + batchCompletionTrend: generateTimeSeriesPoints(dateRange, (_, i) => + Math.floor(Math.random() * 5 + 2) + ), + yieldPredictions: [ + { cropType: 'Lettuce', predictedYieldKg: 156.5, confidence: 0.92, harvestDate: format(subDays(new Date(), -7), 'yyyy-MM-dd') }, + { cropType: 'Tomato', predictedYieldKg: 234.8, confidence: 0.87, harvestDate: format(subDays(new Date(), -14), 'yyyy-MM-dd') }, + { cropType: 'Basil', predictedYieldKg: 45.2, confidence: 0.94, harvestDate: format(subDays(new Date(), -5), 'yyyy-MM-dd') }, + ], + topPerformingCrops: [ + { cropType: 'Lettuce', averageYieldKg: 48.3, growthDays: 28, successRate: 94.5, batches: 45 }, + { cropType: 'Basil', averageYieldKg: 12.4, growthDays: 21, successRate: 91.2, batches: 38 }, + { cropType: 'Cherry Tomato', averageYieldKg: 67.8, growthDays: 65, successRate: 88.7, batches: 22 }, + { cropType: 'Microgreens', averageYieldKg: 5.6, growthDays: 14, successRate: 96.8, batches: 67 }, + ], + }; +} + +/** + * Get sustainability analytics + */ +export async function getSustainabilityAnalytics( + filters: AnalyticsFilters = { timeRange: '30d' } +): Promise { + const dateRange = filters.dateRange || getDateRangeFromTimeRange(filters.timeRange); + + return { + overallScore: 82.5, + carbonFootprint: { + totalEmittedKg: 487.3, + totalSavedKg: 1256.8, + netImpactKg: -769.5, + reductionPercentage: 72.1, + equivalentTrees: 38.4, + monthlyTrend: generateTimeSeriesPoints(dateRange, (_, i) => + Math.round((50 - i * 0.5 + Math.random() * 10) * 10) / 10 + ), + }, + foodMiles: { + totalMiles: 15234.5, + averageMilesPerPlant: 17.7, + savedMiles: 48672.3, + localPercentage: 76.2, + monthlyTrend: generateTimeSeriesPoints(dateRange, (_, i) => + Math.round((600 - i * 5 + Math.random() * 100) * 10) / 10 + ), + }, + waterUsage: { + totalUsedLiters: 125000, + savedLiters: 87500, + efficiencyScore: 87.5, + perKgProduce: 2.8, + }, + localProduction: { + localCount: 654, + totalCount: 861, + percentage: 76.0, + trend: 'up', + }, + goals: [ + { id: 'goal-1', name: 'Carbon Neutral by 2025', target: 0, current: 487.3, unit: 'kg CO2', progress: 72, deadline: '2025-12-31', status: 'on_track' }, + { id: 'goal-2', name: '80% Local Production', target: 80, current: 76, unit: '%', progress: 95, deadline: '2024-12-31', status: 'on_track' }, + { id: 'goal-3', name: 'Reduce Food Miles 50%', target: 50, current: 38, unit: '%', progress: 76, deadline: '2024-06-30', status: 'at_risk' }, + { id: 'goal-4', name: 'Water Efficiency 90%', target: 90, current: 87.5, unit: '%', progress: 97, deadline: '2024-12-31', status: 'on_track' }, + ], + trends: [ + { + metric: 'Carbon Reduction', + values: generateTimeSeriesPoints(dateRange, (_, i) => + Math.round((65 + i * 0.3 + Math.random() * 5) * 10) / 10 + ), + }, + { + metric: 'Local Production', + values: generateTimeSeriesPoints(dateRange, (_, i) => + Math.round((70 + i * 0.2 + Math.random() * 3) * 10) / 10 + ), + }, + ], + }; +} + +/** + * Cache management for analytics data + */ +const analyticsCache = new Map(); +const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes + +export function getCachedData(key: string): T | null { + const cached = analyticsCache.get(key); + if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) { + return cached.data as T; + } + return null; +} + +export function setCachedData(key: string, data: T): void { + analyticsCache.set(key, { data, timestamp: Date.now() }); +} + +export function clearCache(): void { + analyticsCache.clear(); +} + +/** + * Generate cache key from filters + */ +export function generateCacheKey(prefix: string, filters: AnalyticsFilters): string { + return `${prefix}-${JSON.stringify(filters)}`; +} diff --git a/lib/analytics/cache.ts b/lib/analytics/cache.ts new file mode 100644 index 0000000..87ad97c --- /dev/null +++ b/lib/analytics/cache.ts @@ -0,0 +1,189 @@ +/** + * Cache Management for Analytics + * Provides caching for expensive analytics calculations + */ + +interface CacheEntry { + data: T; + timestamp: number; + expiresAt: number; +} + +class AnalyticsCache { + private cache: Map> = new Map(); + private defaultTTL: number = 5 * 60 * 1000; // 5 minutes + + /** + * Get cached data + */ + get(key: string): T | null { + const entry = this.cache.get(key); + if (!entry) return null; + + if (Date.now() > entry.expiresAt) { + this.cache.delete(key); + return null; + } + + return entry.data as T; + } + + /** + * Set cached data + */ + set(key: string, data: T, ttlMs: number = this.defaultTTL): void { + const now = Date.now(); + this.cache.set(key, { + data, + timestamp: now, + expiresAt: now + ttlMs, + }); + } + + /** + * Check if key exists and is valid + */ + has(key: string): boolean { + const entry = this.cache.get(key); + if (!entry) return false; + + if (Date.now() > entry.expiresAt) { + this.cache.delete(key); + return false; + } + + return true; + } + + /** + * Delete a specific key + */ + delete(key: string): boolean { + return this.cache.delete(key); + } + + /** + * Clear all cached data + */ + clear(): void { + this.cache.clear(); + } + + /** + * Clear expired entries + */ + cleanup(): number { + const now = Date.now(); + let cleaned = 0; + + for (const [key, entry] of this.cache.entries()) { + if (now > entry.expiresAt) { + this.cache.delete(key); + cleaned++; + } + } + + return cleaned; + } + + /** + * Get cache statistics + */ + getStats(): { + size: number; + validEntries: number; + expiredEntries: number; + } { + const now = Date.now(); + let valid = 0; + let expired = 0; + + for (const entry of this.cache.values()) { + if (now > entry.expiresAt) { + expired++; + } else { + valid++; + } + } + + return { + size: this.cache.size, + validEntries: valid, + expiredEntries: expired, + }; + } + + /** + * Generate cache key from object + */ + static generateKey(prefix: string, params: Record): string { + const sortedParams = Object.keys(params) + .sort() + .map((key) => `${key}:${JSON.stringify(params[key])}`) + .join('|'); + return `${prefix}:${sortedParams}`; + } +} + +// Singleton instance +export const analyticsCache = new AnalyticsCache(); + +/** + * Cache decorator for async functions + */ +export function cached( + keyGenerator: (...args: any[]) => string, + ttlMs: number = 5 * 60 * 1000 +) { + return function ( + target: any, + propertyKey: string, + descriptor: PropertyDescriptor + ) { + const originalMethod = descriptor.value; + + descriptor.value = async function (...args: any[]) { + const cacheKey = keyGenerator(...args); + const cached = analyticsCache.get(cacheKey); + + if (cached !== null) { + return cached; + } + + const result = await originalMethod.apply(this, args); + analyticsCache.set(cacheKey, result, ttlMs); + return result; + }; + + return descriptor; + }; +} + +/** + * Higher-order function for caching async functions + */ +export function withCache( + fn: (...args: A) => Promise, + keyGenerator: (...args: A) => string, + ttlMs: number = 5 * 60 * 1000 +): (...args: A) => Promise { + return async (...args: A): Promise => { + const cacheKey = keyGenerator(...args); + const cached = analyticsCache.get(cacheKey); + + if (cached !== null) { + return cached; + } + + const result = await fn(...args); + analyticsCache.set(cacheKey, result, ttlMs); + return result; + }; +} + +// Schedule periodic cleanup +if (typeof setInterval !== 'undefined') { + setInterval(() => { + analyticsCache.cleanup(); + }, 60 * 1000); // Run cleanup every minute +} diff --git a/lib/analytics/index.ts b/lib/analytics/index.ts new file mode 100644 index 0000000..3a17f95 --- /dev/null +++ b/lib/analytics/index.ts @@ -0,0 +1,70 @@ +/** + * Analytics Module Index + * Exports all analytics functionality + */ + +// Types +export * from './types'; + +// Data aggregation +export { + getDateRangeFromTimeRange, + generateTimeSeriesPoints, + aggregateByPeriod, + calculateChange, + getAnalyticsOverview, + getPlantAnalytics, + getTransportAnalytics, + getFarmAnalytics, + getSustainabilityAnalytics, + getCachedData, + setCachedData, + clearCache, + generateCacheKey, +} from './aggregator'; + +// Metrics calculations +export { + mean, + median, + standardDeviation, + percentile, + minMax, + getTrendDirection, + percentageChange, + movingAverage, + rateOfChange, + normalize, + cagr, + efficiencyScore, + carbonIntensity, + foodMilesScore, + sustainabilityScore, + generateKPICards, + calculateGrowthMetrics, + detectAnomalies, + correlationCoefficient, + formatNumber, + formatPercentage, +} from './metrics'; + +// Trend analysis +export { + analyzeTrend, + linearRegression, + forecast, + detectSeasonality, + findPeaksAndValleys, + calculateMomentum, + exponentialSmoothing, + generateTrendSummary, + compareTimeSeries, + getTrendConfidence, + yearOverYearComparison, +} from './trends'; + +// Cache management +export { + analyticsCache, + withCache, +} from './cache'; diff --git a/lib/analytics/metrics.ts b/lib/analytics/metrics.ts new file mode 100644 index 0000000..da176e5 --- /dev/null +++ b/lib/analytics/metrics.ts @@ -0,0 +1,326 @@ +/** + * Metrics Calculations for Analytics + * Provides metric calculations and statistical functions + */ + +import { TrendDirection, TimeSeriesDataPoint, KPICardData } from './types'; + +/** + * Calculate mean of an array of numbers + */ +export function mean(values: number[]): number { + if (values.length === 0) return 0; + return values.reduce((sum, v) => sum + v, 0) / values.length; +} + +/** + * Calculate median of an array of numbers + */ +export function median(values: number[]): number { + if (values.length === 0) return 0; + const sorted = [...values].sort((a, b) => a - b); + const mid = Math.floor(sorted.length / 2); + return sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2; +} + +/** + * Calculate standard deviation + */ +export function standardDeviation(values: number[]): number { + if (values.length === 0) return 0; + const avg = mean(values); + const squareDiffs = values.map(v => Math.pow(v - avg, 2)); + return Math.sqrt(mean(squareDiffs)); +} + +/** + * Calculate percentile + */ +export function percentile(values: number[], p: number): number { + if (values.length === 0) return 0; + const sorted = [...values].sort((a, b) => a - b); + const index = (p / 100) * (sorted.length - 1); + const lower = Math.floor(index); + const upper = Math.ceil(index); + const weight = index - lower; + return sorted[lower] * (1 - weight) + sorted[upper] * weight; +} + +/** + * Calculate min and max + */ +export function minMax(values: number[]): { min: number; max: number } { + if (values.length === 0) return { min: 0, max: 0 }; + return { + min: Math.min(...values), + max: Math.max(...values), + }; +} + +/** + * Determine trend direction from two values + */ +export function getTrendDirection(current: number, previous: number, threshold: number = 0.5): TrendDirection { + const change = ((current - previous) / Math.abs(previous || 1)) * 100; + if (Math.abs(change) < threshold) return 'stable'; + return change > 0 ? 'up' : 'down'; +} + +/** + * Calculate percentage change + */ +export function percentageChange(current: number, previous: number): number { + if (previous === 0) return current > 0 ? 100 : 0; + return ((current - previous) / Math.abs(previous)) * 100; +} + +/** + * Calculate moving average + */ +export function movingAverage(data: TimeSeriesDataPoint[], windowSize: number): TimeSeriesDataPoint[] { + return data.map((point, index) => { + const start = Math.max(0, index - windowSize + 1); + const window = data.slice(start, index + 1); + const avg = mean(window.map(p => p.value)); + return { + ...point, + value: Math.round(avg * 100) / 100, + }; + }); +} + +/** + * Calculate rate of change (derivative) + */ +export function rateOfChange(data: TimeSeriesDataPoint[]): TimeSeriesDataPoint[] { + return data.slice(1).map((point, index) => ({ + ...point, + value: point.value - data[index].value, + })); +} + +/** + * Normalize values to 0-100 range + */ +export function normalize(values: number[]): number[] { + const { min, max } = minMax(values); + const range = max - min; + if (range === 0) return values.map(() => 50); + return values.map(v => ((v - min) / range) * 100); +} + +/** + * Calculate compound annual growth rate (CAGR) + */ +export function cagr(startValue: number, endValue: number, years: number): number { + if (startValue <= 0 || years <= 0) return 0; + return (Math.pow(endValue / startValue, 1 / years) - 1) * 100; +} + +/** + * Calculate efficiency score + */ +export function efficiencyScore(actual: number, optimal: number): number { + if (optimal === 0) return actual === 0 ? 100 : 0; + return Math.min(100, (optimal / actual) * 100); +} + +/** + * Calculate carbon intensity (kg CO2 per km) + */ +export function carbonIntensity(carbonKg: number, distanceKm: number): number { + if (distanceKm === 0) return 0; + return carbonKg / distanceKm; +} + +/** + * Calculate food miles score (0-100, lower is better) + */ +export function foodMilesScore(miles: number, maxMiles: number = 5000): number { + if (miles >= maxMiles) return 0; + return Math.round((1 - miles / maxMiles) * 100); +} + +/** + * Calculate sustainability composite score + */ +export function sustainabilityScore( + carbonReduction: number, + localPercentage: number, + waterEfficiency: number, + wasteReduction: number +): number { + const weights = { + carbon: 0.35, + local: 0.25, + water: 0.25, + waste: 0.15, + }; + + return Math.round( + carbonReduction * weights.carbon + + localPercentage * weights.local + + waterEfficiency * weights.water + + wasteReduction * weights.waste + ); +} + +/** + * Generate KPI card data from metrics + */ +export function generateKPICards(metrics: { + plants: { current: number; previous: number }; + carbon: { current: number; previous: number }; + foodMiles: { current: number; previous: number }; + users: { current: number; previous: number }; + sustainability: { current: number; previous: number }; +}): KPICardData[] { + return [ + { + id: 'total-plants', + title: 'Total Plants', + value: metrics.plants.current, + change: metrics.plants.current - metrics.plants.previous, + changePercent: percentageChange(metrics.plants.current, metrics.plants.previous), + trend: getTrendDirection(metrics.plants.current, metrics.plants.previous), + color: 'green', + }, + { + id: 'carbon-saved', + title: 'Carbon Saved', + value: metrics.carbon.current.toFixed(1), + unit: 'kg CO2', + change: metrics.carbon.current - metrics.carbon.previous, + changePercent: percentageChange(metrics.carbon.current, metrics.carbon.previous), + trend: getTrendDirection(metrics.carbon.current, metrics.carbon.previous), + color: 'teal', + }, + { + id: 'food-miles', + title: 'Food Miles', + value: metrics.foodMiles.current.toFixed(0), + unit: 'km', + change: metrics.foodMiles.current - metrics.foodMiles.previous, + changePercent: percentageChange(metrics.foodMiles.current, metrics.foodMiles.previous), + trend: getTrendDirection(metrics.foodMiles.previous, metrics.foodMiles.current), // Inverted: lower is better + color: 'blue', + }, + { + id: 'active-users', + title: 'Active Users', + value: metrics.users.current, + change: metrics.users.current - metrics.users.previous, + changePercent: percentageChange(metrics.users.current, metrics.users.previous), + trend: getTrendDirection(metrics.users.current, metrics.users.previous), + color: 'purple', + }, + { + id: 'sustainability', + title: 'Sustainability Score', + value: metrics.sustainability.current.toFixed(0), + unit: '%', + change: metrics.sustainability.current - metrics.sustainability.previous, + changePercent: percentageChange(metrics.sustainability.current, metrics.sustainability.previous), + trend: getTrendDirection(metrics.sustainability.current, metrics.sustainability.previous), + color: 'green', + }, + ]; +} + +/** + * Calculate growth metrics + */ +export function calculateGrowthMetrics(data: TimeSeriesDataPoint[]): { + totalGrowth: number; + averageDaily: number; + peakValue: number; + peakDate: string; + trend: TrendDirection; +} { + if (data.length === 0) { + return { totalGrowth: 0, averageDaily: 0, peakValue: 0, peakDate: '', trend: 'stable' }; + } + + const values = data.map(d => d.value); + const total = values.reduce((sum, v) => sum + v, 0); + const avgDaily = total / data.length; + const maxIndex = values.indexOf(Math.max(...values)); + + const firstHalf = mean(values.slice(0, Math.floor(values.length / 2))); + const secondHalf = mean(values.slice(Math.floor(values.length / 2))); + + return { + totalGrowth: total, + averageDaily: Math.round(avgDaily * 100) / 100, + peakValue: values[maxIndex], + peakDate: data[maxIndex].timestamp, + trend: getTrendDirection(secondHalf, firstHalf), + }; +} + +/** + * Detect anomalies using z-score + */ +export function detectAnomalies( + data: TimeSeriesDataPoint[], + threshold: number = 2 +): TimeSeriesDataPoint[] { + const values = data.map(d => d.value); + const avg = mean(values); + const std = standardDeviation(values); + + if (std === 0) return []; + + return data.filter(point => { + const zScore = Math.abs((point.value - avg) / std); + return zScore > threshold; + }); +} + +/** + * Calculate correlation coefficient between two datasets + */ +export function correlationCoefficient(x: number[], y: number[]): number { + if (x.length !== y.length || x.length === 0) return 0; + + const n = x.length; + const meanX = mean(x); + const meanY = mean(y); + + let numerator = 0; + let denomX = 0; + let denomY = 0; + + for (let i = 0; i < n; i++) { + const dx = x[i] - meanX; + const dy = y[i] - meanY; + numerator += dx * dy; + denomX += dx * dx; + denomY += dy * dy; + } + + const denominator = Math.sqrt(denomX * denomY); + return denominator === 0 ? 0 : numerator / denominator; +} + +/** + * Format large numbers for display + */ +export function formatNumber(value: number, decimals: number = 1): string { + if (Math.abs(value) >= 1000000) { + return (value / 1000000).toFixed(decimals) + 'M'; + } + if (Math.abs(value) >= 1000) { + return (value / 1000).toFixed(decimals) + 'K'; + } + return value.toFixed(decimals); +} + +/** + * Format percentage for display + */ +export function formatPercentage(value: number, showSign: boolean = false): string { + const formatted = value.toFixed(1); + if (showSign && value > 0) return '+' + formatted + '%'; + return formatted + '%'; +} diff --git a/lib/analytics/trends.ts b/lib/analytics/trends.ts new file mode 100644 index 0000000..e4f712a --- /dev/null +++ b/lib/analytics/trends.ts @@ -0,0 +1,411 @@ +/** + * Trend Analysis for Analytics + * Provides trend detection, forecasting, and pattern analysis + */ + +import { TimeSeriesDataPoint, TrendDirection, TrendData, TimeRange } from './types'; +import { mean, standardDeviation, percentageChange, movingAverage } from './metrics'; +import { format, parseISO, differenceInDays } from 'date-fns'; + +/** + * Analyze trend from time series data + */ +export function analyzeTrend(data: TimeSeriesDataPoint[]): TrendData { + if (data.length < 2) { + return { + metric: 'Unknown', + currentValue: data[0]?.value || 0, + previousValue: 0, + change: 0, + changePercent: 0, + direction: 'stable', + period: 'N/A', + }; + } + + const midpoint = Math.floor(data.length / 2); + const firstHalf = data.slice(0, midpoint); + const secondHalf = data.slice(midpoint); + + const firstAvg = mean(firstHalf.map(d => d.value)); + const secondAvg = mean(secondHalf.map(d => d.value)); + const change = secondAvg - firstAvg; + const changePercent = percentageChange(secondAvg, firstAvg); + + let direction: TrendDirection = 'stable'; + if (changePercent > 5) direction = 'up'; + else if (changePercent < -5) direction = 'down'; + + return { + metric: '', + currentValue: secondAvg, + previousValue: firstAvg, + change, + changePercent: Math.round(changePercent * 10) / 10, + direction, + period: `${data[0].timestamp} - ${data[data.length - 1].timestamp}`, + }; +} + +/** + * Calculate linear regression for forecasting + */ +export function linearRegression(data: TimeSeriesDataPoint[]): { + slope: number; + intercept: number; + rSquared: number; +} { + const n = data.length; + if (n < 2) return { slope: 0, intercept: 0, rSquared: 0 }; + + let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0, sumY2 = 0; + + data.forEach((point, i) => { + const x = i; + const y = point.value; + sumX += x; + sumY += y; + sumXY += x * y; + sumX2 += x * x; + sumY2 += y * y; + }); + + const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX); + const intercept = (sumY - slope * sumX) / n; + + // Calculate R-squared + const yMean = sumY / n; + let ssRes = 0, ssTot = 0; + data.forEach((point, i) => { + const predicted = slope * i + intercept; + ssRes += Math.pow(point.value - predicted, 2); + ssTot += Math.pow(point.value - yMean, 2); + }); + const rSquared = ssTot === 0 ? 1 : 1 - ssRes / ssTot; + + return { + slope: Math.round(slope * 1000) / 1000, + intercept: Math.round(intercept * 1000) / 1000, + rSquared: Math.round(rSquared * 1000) / 1000, + }; +} + +/** + * Forecast future values using linear regression + */ +export function forecast( + data: TimeSeriesDataPoint[], + periodsAhead: number +): TimeSeriesDataPoint[] { + const regression = linearRegression(data); + const predictions: TimeSeriesDataPoint[] = []; + const lastIndex = data.length - 1; + + for (let i = 1; i <= periodsAhead; i++) { + const predictedValue = regression.slope * (lastIndex + i) + regression.intercept; + predictions.push({ + timestamp: `+${i}`, + value: Math.max(0, Math.round(predictedValue * 100) / 100), + label: `Forecast ${i}`, + }); + } + + return predictions; +} + +/** + * Detect seasonality in data + */ +export function detectSeasonality(data: TimeSeriesDataPoint[]): { + hasSeasonality: boolean; + period: number; + strength: number; +} { + if (data.length < 14) { + return { hasSeasonality: false, period: 0, strength: 0 }; + } + + // Try common periods: 7 days (weekly), 30 days (monthly) + const periods = [7, 14, 30]; + let bestPeriod = 0; + let bestCorrelation = 0; + + for (const period of periods) { + if (data.length < period * 2) continue; + + let correlation = 0; + let count = 0; + + for (let i = period; i < data.length; i++) { + correlation += Math.abs(data[i].value - data[i - period].value); + count++; + } + + const avgCorr = count > 0 ? 1 - correlation / (count * mean(data.map(d => d.value))) : 0; + if (avgCorr > bestCorrelation) { + bestCorrelation = avgCorr; + bestPeriod = period; + } + } + + return { + hasSeasonality: bestCorrelation > 0.5, + period: bestPeriod, + strength: Math.round(bestCorrelation * 100) / 100, + }; +} + +/** + * Identify peaks and valleys in data + */ +export function findPeaksAndValleys( + data: TimeSeriesDataPoint[], + sensitivity: number = 1.5 +): { + peaks: TimeSeriesDataPoint[]; + valleys: TimeSeriesDataPoint[]; +} { + const peaks: TimeSeriesDataPoint[] = []; + const valleys: TimeSeriesDataPoint[] = []; + + if (data.length < 3) return { peaks, valleys }; + + const avg = mean(data.map(d => d.value)); + const std = standardDeviation(data.map(d => d.value)); + const threshold = std * sensitivity; + + for (let i = 1; i < data.length - 1; i++) { + const prev = data[i - 1].value; + const curr = data[i].value; + const next = data[i + 1].value; + + // Peak detection + if (curr > prev && curr > next && curr > avg + threshold) { + peaks.push(data[i]); + } + + // Valley detection + if (curr < prev && curr < next && curr < avg - threshold) { + valleys.push(data[i]); + } + } + + return { peaks, valleys }; +} + +/** + * Calculate trend momentum + */ +export function calculateMomentum(data: TimeSeriesDataPoint[], lookback: number = 5): number { + if (data.length < lookback) return 0; + + const recent = data.slice(-lookback); + const older = data.slice(-lookback * 2, -lookback); + + if (older.length === 0) return 0; + + const recentAvg = mean(recent.map(d => d.value)); + const olderAvg = mean(older.map(d => d.value)); + + return Math.round(((recentAvg - olderAvg) / olderAvg) * 100 * 10) / 10; +} + +/** + * Smooth data using exponential smoothing + */ +export function exponentialSmoothing( + data: TimeSeriesDataPoint[], + alpha: number = 0.3 +): TimeSeriesDataPoint[] { + if (data.length === 0) return []; + + const smoothed: TimeSeriesDataPoint[] = [data[0]]; + + for (let i = 1; i < data.length; i++) { + const smoothedValue = alpha * data[i].value + (1 - alpha) * smoothed[i - 1].value; + smoothed.push({ + ...data[i], + value: Math.round(smoothedValue * 100) / 100, + }); + } + + return smoothed; +} + +/** + * Generate trend summary text + */ +export function generateTrendSummary(data: TimeSeriesDataPoint[], metricName: string): string { + if (data.length < 2) return `Insufficient data for ${metricName} analysis.`; + + const trend = analyzeTrend(data); + const regression = linearRegression(data); + const { peaks, valleys } = findPeaksAndValleys(data); + const momentum = calculateMomentum(data); + + let summary = ''; + + // Overall direction + if (trend.direction === 'up') { + summary += `${metricName} is trending upward with a ${Math.abs(trend.changePercent).toFixed(1)}% increase. `; + } else if (trend.direction === 'down') { + summary += `${metricName} is trending downward with a ${Math.abs(trend.changePercent).toFixed(1)}% decrease. `; + } else { + summary += `${metricName} remains relatively stable. `; + } + + // Trend strength + if (regression.rSquared > 0.8) { + summary += 'The trend is strong and consistent. '; + } else if (regression.rSquared > 0.5) { + summary += 'The trend is moderate with some variability. '; + } else { + summary += 'Data shows high variability. '; + } + + // Momentum + if (momentum > 10) { + summary += 'Recent acceleration detected. '; + } else if (momentum < -10) { + summary += 'Recent deceleration detected. '; + } + + // Notable events + if (peaks.length > 0) { + summary += `${peaks.length} notable peak(s) observed. `; + } + if (valleys.length > 0) { + summary += `${valleys.length} notable dip(s) observed. `; + } + + return summary.trim(); +} + +/** + * Compare two time series for similarity + */ +export function compareTimeSeries( + series1: TimeSeriesDataPoint[], + series2: TimeSeriesDataPoint[] +): { + correlation: number; + leadLag: number; + divergence: number; +} { + // Ensure same length + const minLength = Math.min(series1.length, series2.length); + const s1 = series1.slice(0, minLength).map(d => d.value); + const s2 = series2.slice(0, minLength).map(d => d.value); + + // Calculate correlation + const mean1 = mean(s1); + const mean2 = mean(s2); + let numerator = 0, denom1 = 0, denom2 = 0; + + for (let i = 0; i < minLength; i++) { + const d1 = s1[i] - mean1; + const d2 = s2[i] - mean2; + numerator += d1 * d2; + denom1 += d1 * d1; + denom2 += d2 * d2; + } + + const correlation = denom1 * denom2 === 0 ? 0 : numerator / Math.sqrt(denom1 * denom2); + + // Calculate divergence (normalized difference) + const divergence = mean(s1.map((v, i) => Math.abs(v - s2[i]))) / ((mean1 + mean2) / 2); + + return { + correlation: Math.round(correlation * 1000) / 1000, + leadLag: 0, // Simplified - full cross-correlation would require more computation + divergence: Math.round(divergence * 1000) / 1000, + }; +} + +/** + * Get trend confidence level + */ +export function getTrendConfidence(data: TimeSeriesDataPoint[]): { + level: 'high' | 'medium' | 'low'; + score: number; + factors: string[]; +} { + const factors: string[] = []; + let score = 50; + + // Data quantity factor + if (data.length >= 30) { + score += 15; + factors.push('Sufficient data points'); + } else if (data.length >= 14) { + score += 10; + factors.push('Moderate data points'); + } else { + score -= 10; + factors.push('Limited data points'); + } + + // Consistency factor + const std = standardDeviation(data.map(d => d.value)); + const avg = mean(data.map(d => d.value)); + const cv = avg !== 0 ? std / avg : 0; + + if (cv < 0.2) { + score += 15; + factors.push('Low variability'); + } else if (cv < 0.5) { + score += 5; + factors.push('Moderate variability'); + } else { + score -= 10; + factors.push('High variability'); + } + + // Trend strength + const regression = linearRegression(data); + if (regression.rSquared > 0.7) { + score += 20; + factors.push('Strong trend fit'); + } else if (regression.rSquared > 0.4) { + score += 10; + factors.push('Moderate trend fit'); + } else { + score -= 5; + factors.push('Weak trend fit'); + } + + const level = score >= 70 ? 'high' : score >= 50 ? 'medium' : 'low'; + + return { + level, + score: Math.min(100, Math.max(0, score)), + factors, + }; +} + +/** + * Calculate year-over-year comparison + */ +export function yearOverYearComparison( + currentPeriod: TimeSeriesDataPoint[], + previousPeriod: TimeSeriesDataPoint[] +): { + currentTotal: number; + previousTotal: number; + change: number; + changePercent: number; + trend: TrendDirection; +} { + const currentTotal = currentPeriod.reduce((sum, d) => sum + d.value, 0); + const previousTotal = previousPeriod.reduce((sum, d) => sum + d.value, 0); + const change = currentTotal - previousTotal; + const changePercent = percentageChange(currentTotal, previousTotal); + + return { + currentTotal: Math.round(currentTotal * 100) / 100, + previousTotal: Math.round(previousTotal * 100) / 100, + change: Math.round(change * 100) / 100, + changePercent: Math.round(changePercent * 10) / 10, + trend: changePercent > 5 ? 'up' : changePercent < -5 ? 'down' : 'stable', + }; +} diff --git a/lib/analytics/types.ts b/lib/analytics/types.ts new file mode 100644 index 0000000..2e4ef04 --- /dev/null +++ b/lib/analytics/types.ts @@ -0,0 +1,306 @@ +/** + * Analytics Types for LocalGreenChain + * Defines all types for the Advanced Analytics Dashboard + */ + +// Time range options for analytics queries +export type TimeRange = '7d' | '30d' | '90d' | '365d' | 'all'; + +export interface DateRange { + start: Date; + end: Date; +} + +// Generic data point for time series +export interface TimeSeriesDataPoint { + timestamp: string; + value: number; + label?: string; +} + +// Analytics overview metrics +export interface AnalyticsOverview { + totalPlants: number; + plantsRegisteredToday: number; + plantsRegisteredThisWeek: number; + plantsRegisteredThisMonth: number; + totalTransportEvents: number; + totalCarbonKg: number; + totalFoodMiles: number; + activeUsers: number; + growthRate: number; + trendsData: TrendData[]; +} + +// Trend direction indicator +export type TrendDirection = 'up' | 'down' | 'stable'; + +export interface TrendData { + metric: string; + currentValue: number; + previousValue: number; + change: number; + changePercent: number; + direction: TrendDirection; + period: string; +} + +// Plant analytics +export interface PlantAnalytics { + totalPlants: number; + plantsBySpecies: SpeciesDistribution[]; + plantsByGeneration: GenerationDistribution[]; + registrationsTrend: TimeSeriesDataPoint[]; + averageLineageDepth: number; + topGrowers: GrowerStats[]; + recentRegistrations: PlantRegistration[]; +} + +export interface SpeciesDistribution { + species: string; + count: number; + percentage: number; + trend: TrendDirection; +} + +export interface GenerationDistribution { + generation: number; + count: number; + percentage: number; +} + +export interface GrowerStats { + userId: string; + name: string; + totalPlants: number; + totalSpecies: number; + averageGeneration: number; +} + +export interface PlantRegistration { + id: string; + name: string; + species: string; + registeredAt: string; + generation: number; +} + +// Transport analytics +export interface TransportAnalytics { + totalEvents: number; + totalDistanceKm: number; + totalCarbonKg: number; + carbonSavedKg: number; + eventsByType: TransportEventDistribution[]; + eventsByMethod: TransportMethodDistribution[]; + dailyStats: DailyTransportStats[]; + averageDistancePerEvent: number; + mostEfficientRoutes: EfficientRoute[]; + carbonTrend: TimeSeriesDataPoint[]; +} + +export interface TransportEventDistribution { + eventType: string; + count: number; + percentage: number; + carbonKg: number; +} + +export interface TransportMethodDistribution { + method: string; + count: number; + percentage: number; + distanceKm: number; + carbonKg: number; + efficiency: number; +} + +export interface DailyTransportStats { + date: string; + eventCount: number; + distanceKm: number; + carbonKg: number; +} + +export interface EfficientRoute { + from: string; + to: string; + method: string; + distanceKm: number; + carbonKg: number; + frequency: number; +} + +// Vertical farm analytics +export interface FarmAnalytics { + totalFarms: number; + totalZones: number; + activeBatches: number; + completedBatches: number; + averageYieldKg: number; + resourceUsage: ResourceUsageStats; + performanceByZone: ZonePerformance[]; + batchCompletionTrend: TimeSeriesDataPoint[]; + yieldPredictions: YieldPrediction[]; + topPerformingCrops: CropPerformance[]; +} + +export interface ResourceUsageStats { + waterLiters: number; + energyKwh: number; + nutrientsKg: number; + waterEfficiency: number; + energyEfficiency: number; +} + +export interface ZonePerformance { + zoneId: string; + zoneName: string; + currentCrop: string; + healthScore: number; + yieldKg: number; + efficiency: number; +} + +export interface YieldPrediction { + cropType: string; + predictedYieldKg: number; + confidence: number; + harvestDate: string; +} + +export interface CropPerformance { + cropType: string; + averageYieldKg: number; + growthDays: number; + successRate: number; + batches: number; +} + +// Sustainability analytics +export interface SustainabilityAnalytics { + overallScore: number; + carbonFootprint: CarbonMetrics; + foodMiles: FoodMilesMetrics; + waterUsage: WaterMetrics; + localProduction: LocalProductionMetrics; + goals: SustainabilityGoal[]; + trends: SustainabilityTrend[]; +} + +export interface CarbonMetrics { + totalEmittedKg: number; + totalSavedKg: number; + netImpactKg: number; + reductionPercentage: number; + equivalentTrees: number; + monthlyTrend: TimeSeriesDataPoint[]; +} + +export interface FoodMilesMetrics { + totalMiles: number; + averageMilesPerPlant: number; + savedMiles: number; + localPercentage: number; + monthlyTrend: TimeSeriesDataPoint[]; +} + +export interface WaterMetrics { + totalUsedLiters: number; + savedLiters: number; + efficiencyScore: number; + perKgProduce: number; +} + +export interface LocalProductionMetrics { + localCount: number; + totalCount: number; + percentage: number; + trend: TrendDirection; +} + +export interface SustainabilityGoal { + id: string; + name: string; + target: number; + current: number; + unit: string; + progress: number; + deadline: string; + status: 'on_track' | 'at_risk' | 'behind'; +} + +export interface SustainabilityTrend { + metric: string; + values: TimeSeriesDataPoint[]; +} + +// Export options +export interface ExportOptions { + format: 'csv' | 'pdf' | 'json'; + dateRange: DateRange; + sections: string[]; + includeCharts: boolean; +} + +export interface ExportResult { + filename: string; + mimeType: string; + data: string; + generatedAt: string; +} + +// Dashboard configuration +export interface DashboardConfig { + refreshInterval: number; + defaultTimeRange: TimeRange; + visibleWidgets: string[]; + layout: 'grid' | 'list'; +} + +// KPI Card configuration +export interface KPICardData { + id: string; + title: string; + value: number | string; + unit?: string; + change?: number; + changePercent?: number; + trend: TrendDirection; + color: 'green' | 'blue' | 'purple' | 'orange' | 'red' | 'teal'; + icon?: string; +} + +// Chart configuration +export interface ChartConfig { + title: string; + type: 'line' | 'bar' | 'pie' | 'area' | 'heatmap' | 'gauge'; + data: any[]; + xKey?: string; + yKey?: string; + colors?: string[]; + showLegend?: boolean; + showGrid?: boolean; + height?: number; +} + +// Filter state for analytics +export interface AnalyticsFilters { + timeRange: TimeRange; + dateRange?: DateRange; + species?: string[]; + regions?: string[]; + transportMethods?: string[]; + farmIds?: string[]; +} + +// Aggregation types +export type AggregationType = 'sum' | 'avg' | 'min' | 'max' | 'count'; +export type GroupByPeriod = 'hour' | 'day' | 'week' | 'month' | 'year'; + +export interface AggregationConfig { + metric: string; + aggregation: AggregationType; + groupBy?: GroupByPeriod; + filters?: Record; +} diff --git a/next.config.js b/next.config.js index 600c711..a2a543f 100644 --- a/next.config.js +++ b/next.config.js @@ -5,7 +5,7 @@ module.exports = { defaultLocale: "en", }, images: { - domains: [process.env.NEXT_IMAGE_DOMAIN], + domains: process.env.NEXT_IMAGE_DOMAIN ? [process.env.NEXT_IMAGE_DOMAIN] : [], }, async rewrites() { return [ diff --git a/package.json b/package.json index 5fc2e14..6118a29 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,8 @@ "@tanstack/react-query": "^4.0.10", "bcryptjs": "^3.0.3", "classnames": "^2.3.1", + "d3": "^7.9.0", + "date-fns": "^4.1.0", "drupal-jsonapi-params": "^1.2.2", "html-react-parser": "^1.2.7", "multer": "^2.0.2", @@ -50,6 +52,7 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "react-hook-form": "^7.8.6", + "recharts": "^3.4.1", "sharp": "^0.34.5", "socket.io": "^4.7.2", "socket.io-client": "^4.7.2", @@ -60,6 +63,7 @@ "@commitlint/cli": "^18.4.3", "@commitlint/config-conventional": "^18.4.3", "@types/bcryptjs": "^3.0.0", + "@types/d3": "^7.4.3", "@types/jest": "^29.5.0", "@types/multer": "^2.0.0", "@types/node": "^17.0.21", diff --git a/pages/analytics/farms.tsx b/pages/analytics/farms.tsx new file mode 100644 index 0000000..d4098ae --- /dev/null +++ b/pages/analytics/farms.tsx @@ -0,0 +1,253 @@ +/** + * Farm Analytics Page + * Vertical farm performance and resource analytics + */ + +import { useState, useEffect } from 'react'; +import Link from 'next/link'; +import { + KPICard, + DateRangePicker, + LineChart, + BarChart, + Gauge, + DataTable, +} from '../../components/analytics'; +import { TimeRange, FarmAnalytics } from '../../lib/analytics/types'; + +export default function FarmAnalyticsPage() { + const [timeRange, setTimeRange] = useState('30d'); + const [loading, setLoading] = useState(true); + const [data, setData] = useState(null); + + useEffect(() => { + fetchData(); + }, [timeRange]); + + const fetchData = async () => { + setLoading(true); + try { + const response = await fetch(`/api/analytics/farms?timeRange=${timeRange}`); + const result = await response.json(); + setData(result.data); + } catch (error) { + console.error('Failed to fetch farm analytics:', error); + } finally { + setLoading(false); + } + }; + + const zoneColumns = [ + { key: 'zoneName', header: 'Zone' }, + { key: 'currentCrop', header: 'Crop' }, + { + key: 'healthScore', + header: 'Health', + align: 'right' as const, + render: (v: number) => ( + = 90 ? 'text-green-600' : v >= 70 ? 'text-yellow-600' : 'text-red-600'}`}> + {v}% + + ), + }, + { key: 'yieldKg', header: 'Yield (kg)', align: 'right' as const, render: (v: number) => v.toFixed(1) }, + { key: 'efficiency', header: 'Efficiency', align: 'right' as const, render: (v: number) => `${v}%` }, + ]; + + const cropColumns = [ + { key: 'cropType', header: 'Crop' }, + { key: 'batches', header: 'Batches', align: 'right' as const }, + { key: 'averageYieldKg', header: 'Avg Yield (kg)', align: 'right' as const, render: (v: number) => v.toFixed(1) }, + { key: 'growthDays', header: 'Growth Days', align: 'right' as const }, + { + key: 'successRate', + header: 'Success Rate', + align: 'right' as const, + render: (v: number) => ( + = 90 ? 'text-green-600' : v >= 75 ? 'text-yellow-600' : 'text-red-600'}`}> + {v.toFixed(1)}% + + ), + }, + ]; + + const predictionColumns = [ + { key: 'cropType', header: 'Crop' }, + { key: 'predictedYieldKg', header: 'Predicted Yield', align: 'right' as const, render: (v: number) => `${v.toFixed(1)} kg` }, + { key: 'confidence', header: 'Confidence', align: 'right' as const, render: (v: number) => `${(v * 100).toFixed(0)}%` }, + { key: 'harvestDate', header: 'Harvest Date' }, + ]; + + return ( +
+ {/* Header */} +
+
+

Farm Analytics

+

Vertical farm performance and resource optimization

+
+
+ +
+ {/* Time Range Selector */} +
+ +
+ + {/* Navigation Tabs */} +
+ + Overview + + + Plants + + + Transport + + + Farms + + + Sustainability + +
+ + {/* KPI Cards */} +
+ + + + +
+ + {/* Resource Gauges */} +
+ + + + +
+ + {/* Resource Usage Stats */} +
+

Resource Usage Summary

+
+
+

+ {data?.resourceUsage?.waterLiters?.toLocaleString() || 0} +

+

Water Used (L)

+
+
+

+ {data?.resourceUsage?.energyKwh?.toLocaleString() || 0} +

+

Energy Used (kWh)

+
+
+

+ {data?.resourceUsage?.nutrientsKg?.toLocaleString() || 0} +

+

Nutrients Used (kg)

+
+
+
+ + {/* Charts */} +
+ {data?.batchCompletionTrend && ( + + )} + + {data?.topPerformingCrops && ( + + )} +
+ + {/* Tables */} +
+ {data?.performanceByZone && ( + + )} + + {data?.topPerformingCrops && ( + + )} + + {data?.yieldPredictions && ( + + )} +
+
+
+ ); +} diff --git a/pages/analytics/index.tsx b/pages/analytics/index.tsx new file mode 100644 index 0000000..14bda92 --- /dev/null +++ b/pages/analytics/index.tsx @@ -0,0 +1,225 @@ +/** + * Analytics Dashboard - Main Page + * Comprehensive overview of all analytics data + */ + +import { useState, useEffect } from 'react'; +import Link from 'next/link'; +import { + KPICard, + DateRangePicker, + LineChart, + BarChart, + PieChart, +} from '../../components/analytics'; +import { TimeRange, AnalyticsOverview, PlantAnalytics, TransportAnalytics } from '../../lib/analytics/types'; + +export default function AnalyticsDashboard() { + const [timeRange, setTimeRange] = useState('30d'); + const [loading, setLoading] = useState(true); + const [overview, setOverview] = useState(null); + const [plantData, setPlantData] = useState(null); + const [transportData, setTransportData] = useState(null); + + useEffect(() => { + fetchData(); + }, [timeRange]); + + const fetchData = async () => { + setLoading(true); + try { + const [overviewRes, plantRes, transportRes] = await Promise.all([ + fetch(`/api/analytics/overview?timeRange=${timeRange}`), + fetch(`/api/analytics/plants?timeRange=${timeRange}`), + fetch(`/api/analytics/transport?timeRange=${timeRange}`), + ]); + + const overviewData = await overviewRes.json(); + const plantDataRes = await plantRes.json(); + const transportDataRes = await transportRes.json(); + + setOverview(overviewData.data); + setPlantData(plantDataRes.data); + setTransportData(transportDataRes.data); + } catch (error) { + console.error('Failed to fetch analytics data:', error); + } finally { + setLoading(false); + } + }; + + const handleExport = (format: 'csv' | 'json') => { + window.open(`/api/analytics/export?format=${format}&timeRange=${timeRange}`, '_blank'); + }; + + return ( +
+ {/* Header */} +
+
+
+
+

Analytics Dashboard

+

+ Comprehensive insights into your LocalGreenChain network +

+
+
+ + +
+
+
+
+ +
+ {/* Time Range Selector */} +
+ +
+ + {/* Navigation Tabs */} +
+ + Overview + + + Plants + + + Transport + + + Farms + + + Sustainability + +
+ + {/* KPI Cards */} +
+ + + + +
+ + {/* Charts Row */} +
+ {/* Plant Registrations Trend */} + {plantData?.registrationsTrend && ( + + )} + + {/* Species Distribution */} + {plantData?.plantsBySpecies && ( + + )} +
+ + {/* Transport Charts */} +
+ {/* Transport Methods */} + {transportData?.eventsByMethod && ( + + )} + + {/* Carbon Trend */} + {transportData?.carbonTrend && ( + + )} +
+ + {/* Summary Stats */} +
+

Network Summary

+
+
+

{overview?.plantsRegisteredToday || 0}

+

Registered Today

+
+
+

{overview?.plantsRegisteredThisWeek || 0}

+

This Week

+
+
+

{overview?.plantsRegisteredThisMonth || 0}

+

This Month

+
+
+

{overview?.growthRate?.toFixed(1) || 0}%

+

Growth Rate

+
+
+
+
+
+ ); +} diff --git a/pages/analytics/plants.tsx b/pages/analytics/plants.tsx new file mode 100644 index 0000000..81d3a22 --- /dev/null +++ b/pages/analytics/plants.tsx @@ -0,0 +1,182 @@ +/** + * Plant Analytics Page + * Detailed analytics for plant registrations and lineage + */ + +import { useState, useEffect } from 'react'; +import Link from 'next/link'; +import { + KPICard, + DateRangePicker, + LineChart, + BarChart, + PieChart, + DataTable, + TrendIndicator, +} from '../../components/analytics'; +import { TimeRange, PlantAnalytics } from '../../lib/analytics/types'; + +export default function PlantAnalyticsPage() { + const [timeRange, setTimeRange] = useState('30d'); + const [loading, setLoading] = useState(true); + const [data, setData] = useState(null); + + useEffect(() => { + fetchData(); + }, [timeRange]); + + const fetchData = async () => { + setLoading(true); + try { + const response = await fetch(`/api/analytics/plants?timeRange=${timeRange}`); + const result = await response.json(); + setData(result.data); + } catch (error) { + console.error('Failed to fetch plant analytics:', error); + } finally { + setLoading(false); + } + }; + + const speciesColumns = [ + { key: 'species', header: 'Species' }, + { key: 'count', header: 'Count', align: 'right' as const }, + { key: 'percentage', header: '% Share', align: 'right' as const, render: (v: number) => `${v.toFixed(1)}%` }, + { key: 'trend', header: 'Trend', render: (v: string) => }, + ]; + + const growerColumns = [ + { key: 'name', header: 'Grower' }, + { key: 'totalPlants', header: 'Plants', align: 'right' as const }, + { key: 'totalSpecies', header: 'Species', align: 'right' as const }, + { key: 'averageGeneration', header: 'Avg Gen', align: 'right' as const, render: (v: number) => v.toFixed(1) }, + ]; + + return ( +
+ {/* Header */} +
+
+

Plant Analytics

+

Detailed insights into plant registrations and lineage

+
+
+ +
+ {/* Time Range Selector */} +
+ +
+ + {/* Navigation Tabs */} +
+ + Overview + + + Plants + + + Transport + + + Farms + + + Sustainability + +
+ + {/* KPI Cards */} +
+ + + + +
+ + {/* Charts */} +
+ {data?.registrationsTrend && ( + + )} + + {data?.plantsBySpecies && ( + + )} +
+ + {/* Generation Distribution */} + {data?.plantsByGeneration && ( +
+ +
+ )} + + {/* Tables */} +
+ {data?.plantsBySpecies && ( + + )} + + {data?.topGrowers && ( + + )} +
+
+
+ ); +} diff --git a/pages/analytics/sustainability.tsx b/pages/analytics/sustainability.tsx new file mode 100644 index 0000000..0507af4 --- /dev/null +++ b/pages/analytics/sustainability.tsx @@ -0,0 +1,315 @@ +/** + * Sustainability Analytics Page + * Environmental impact and sustainability metrics + */ + +import { useState, useEffect } from 'react'; +import Link from 'next/link'; +import { + KPICard, + DateRangePicker, + LineChart, + AreaChart, + Gauge, + DataTable, + TrendIndicator, +} from '../../components/analytics'; +import { TimeRange, SustainabilityAnalytics } from '../../lib/analytics/types'; + +export default function SustainabilityAnalyticsPage() { + const [timeRange, setTimeRange] = useState('30d'); + const [loading, setLoading] = useState(true); + const [data, setData] = useState(null); + + useEffect(() => { + fetchData(); + }, [timeRange]); + + const fetchData = async () => { + setLoading(true); + try { + const response = await fetch(`/api/analytics/sustainability?timeRange=${timeRange}`); + const result = await response.json(); + setData(result.data); + } catch (error) { + console.error('Failed to fetch sustainability analytics:', error); + } finally { + setLoading(false); + } + }; + + const goalColumns = [ + { key: 'name', header: 'Goal' }, + { key: 'current', header: 'Current', align: 'right' as const, render: (v: number, row: any) => `${v} ${row.unit}` }, + { key: 'target', header: 'Target', align: 'right' as const, render: (v: number, row: any) => `${v} ${row.unit}` }, + { + key: 'progress', + header: 'Progress', + align: 'right' as const, + render: (v: number) => ( +
+
+
= 90 ? 'bg-green-500' : v >= 70 ? 'bg-yellow-500' : 'bg-red-500'}`} + style={{ width: `${Math.min(v, 100)}%` }} + /> +
+ {v}% +
+ ), + }, + { + key: 'status', + header: 'Status', + render: (v: string) => ( + + {v.replace('_', ' ')} + + ), + }, + ]; + + return ( +
+ {/* Header */} +
+
+

Sustainability Analytics

+

Environmental impact and sustainability metrics

+
+
+ +
+ {/* Time Range Selector */} +
+ +
+ + {/* Navigation Tabs */} +
+ + Overview + + + Plants + + + Transport + + + Farms + + + Sustainability + +
+ + {/* Overall Score */} +
+
+
+

Overall Sustainability Score

+

Based on carbon, local production, water, and waste metrics

+
+
+
{data?.overallScore?.toFixed(0) || 0}
+
out of 100
+
+
+
+ + {/* KPI Cards */} +
+ + + + +
+ + {/* Gauges */} +
+ + + + +
+ + {/* Impact Metrics */} +
+

Environmental Impact

+
+
+

+ {data?.carbonFootprint?.equivalentTrees?.toFixed(1) || 0} +

+

Trees Equivalent

+

CO2 absorption per year

+
+
+

+ {data?.foodMiles?.savedMiles?.toLocaleString() || 0} +

+

Miles Saved

+

vs conventional transport

+
+
+

+ {data?.waterUsage?.savedLiters?.toLocaleString() || 0} +

+

Liters Saved

+

Water conservation

+
+
+

+ {data?.localProduction?.localCount || 0} +

+

Local Plants

+

Within 50km radius

+
+
+
+ + {/* Charts */} +
+ {data?.carbonFootprint?.monthlyTrend && ( + + )} + + {data?.foodMiles?.monthlyTrend && ( + + )} +
+ + {/* Sustainability Trends */} + {data?.trends && data.trends.length > 0 && ( +
+ +
+ )} + + {/* Goals Table */} + {data?.goals && ( + + )} + + {/* Tips Section */} +
+

Sustainability Recommendations

+
+
+ + + + + +

Increase local sourcing to reduce transport emissions

+
+
+ + + + + +

Use electric or bicycle delivery for short distances

+
+
+ + + + + +

Optimize water usage in vertical farming operations

+
+
+ + + + + +

Consolidate deliveries to reduce carbon footprint

+
+
+
+
+
+ ); +} diff --git a/pages/analytics/transport.tsx b/pages/analytics/transport.tsx new file mode 100644 index 0000000..f0b562c --- /dev/null +++ b/pages/analytics/transport.tsx @@ -0,0 +1,232 @@ +/** + * Transport Analytics Page + * Carbon footprint and food miles analysis + */ + +import { useState, useEffect } from 'react'; +import Link from 'next/link'; +import { + KPICard, + DateRangePicker, + LineChart, + BarChart, + PieChart, + AreaChart, + Gauge, + DataTable, +} from '../../components/analytics'; +import { TimeRange, TransportAnalytics } from '../../lib/analytics/types'; + +export default function TransportAnalyticsPage() { + const [timeRange, setTimeRange] = useState('30d'); + const [loading, setLoading] = useState(true); + const [data, setData] = useState(null); + + useEffect(() => { + fetchData(); + }, [timeRange]); + + const fetchData = async () => { + setLoading(true); + try { + const response = await fetch(`/api/analytics/transport?timeRange=${timeRange}`); + const result = await response.json(); + setData(result.data); + } catch (error) { + console.error('Failed to fetch transport analytics:', error); + } finally { + setLoading(false); + } + }; + + const methodColumns = [ + { key: 'method', header: 'Method' }, + { key: 'count', header: 'Events', align: 'right' as const }, + { key: 'distanceKm', header: 'Distance (km)', align: 'right' as const, render: (v: number) => v.toLocaleString() }, + { key: 'carbonKg', header: 'Carbon (kg)', align: 'right' as const, render: (v: number) => v.toFixed(2) }, + { + key: 'efficiency', + header: 'Efficiency', + align: 'right' as const, + render: (v: number) => ( + = 80 ? 'text-green-600' : v >= 50 ? 'text-yellow-600' : 'text-red-600'}`}> + {v}% + + ), + }, + ]; + + const routeColumns = [ + { key: 'from', header: 'From' }, + { key: 'to', header: 'To' }, + { key: 'method', header: 'Method' }, + { key: 'distanceKm', header: 'Distance', align: 'right' as const, render: (v: number) => `${v} km` }, + { key: 'frequency', header: 'Frequency', align: 'right' as const }, + ]; + + return ( +
+ {/* Header */} +
+
+

Transport Analytics

+

Carbon footprint and food miles analysis

+
+
+ +
+ {/* Time Range Selector */} +
+ +
+ + {/* Navigation Tabs */} +
+ + Overview + + + Plants + + + Transport + + + Farms + + + Sustainability + +
+ + {/* KPI Cards */} +
+ + + + +
+ + {/* Gauges */} +
+ + + m.efficiency >= 80).length + ? (data.eventsByMethod.filter(m => m.efficiency >= 80).reduce((s, m) => s + m.count, 0) / data.totalEvents) * 100 + : 0} + title="Green Transport" + unit="%" + /> + +
+ + {/* Charts */} +
+ {data?.carbonTrend && ( + + )} + + {data?.eventsByMethod && ( + + )} +
+ + {/* Event Types Pie Chart */} + {data?.eventsByType && ( +
+
+ + +
+
+ )} + + {/* Tables */} +
+ {data?.eventsByMethod && ( + + )} + + {data?.mostEfficientRoutes && ( + + )} +
+
+
+ ); +} diff --git a/pages/api/analytics/export.ts b/pages/api/analytics/export.ts new file mode 100644 index 0000000..79162bb --- /dev/null +++ b/pages/api/analytics/export.ts @@ -0,0 +1,155 @@ +/** + * Analytics Export API + * Exports analytics data in various formats (CSV, JSON) + */ + +import type { NextApiRequest, NextApiResponse } from 'next'; +import { + getAnalyticsOverview, + getPlantAnalytics, + getTransportAnalytics, + getFarmAnalytics, + getSustainabilityAnalytics, + TimeRange, + AnalyticsFilters, +} from '../../../lib/analytics'; +import { format } from 'date-fns'; + +interface ExportData { + overview?: any; + plants?: any; + transport?: any; + farms?: any; + sustainability?: any; +} + +function convertToCSV(data: any[], headers: string[]): string { + const headerRow = headers.join(','); + const rows = data.map(item => + headers.map(header => { + const value = item[header]; + if (value === null || value === undefined) return ''; + if (typeof value === 'string' && value.includes(',')) { + return `"${value.replace(/"/g, '""')}"`; + } + return String(value); + }).join(',') + ); + return [headerRow, ...rows].join('\n'); +} + +function generatePlantCSV(plantData: any): string { + const speciesData = plantData.plantsBySpecies.map((s: any) => ({ + species: s.species, + count: s.count, + percentage: s.percentage, + trend: s.trend, + })); + return convertToCSV(speciesData, ['species', 'count', 'percentage', 'trend']); +} + +function generateTransportCSV(transportData: any): string { + const methodData = transportData.eventsByMethod.map((m: any) => ({ + method: m.method, + count: m.count, + distanceKm: m.distanceKm, + carbonKg: m.carbonKg, + efficiency: m.efficiency, + })); + return convertToCSV(methodData, ['method', 'count', 'distanceKm', 'carbonKg', 'efficiency']); +} + +function generateSustainabilityCSV(sustainData: any): string { + const goalsData = sustainData.goals.map((g: any) => ({ + name: g.name, + target: g.target, + current: g.current, + unit: g.unit, + progress: g.progress, + status: g.status, + })); + return convertToCSV(goalsData, ['name', 'target', 'current', 'unit', 'progress', 'status']); +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + try { + const exportFormat = (req.query.format as string) || 'json'; + const timeRange = (req.query.timeRange as TimeRange) || '30d'; + const sections = req.query.sections + ? (req.query.sections as string).split(',') + : ['overview', 'plants', 'transport', 'farms', 'sustainability']; + + const filters: AnalyticsFilters = { timeRange }; + const exportData: ExportData = {}; + + // Fetch requested sections + if (sections.includes('overview')) { + exportData.overview = await getAnalyticsOverview(filters); + } + if (sections.includes('plants')) { + exportData.plants = await getPlantAnalytics(filters); + } + if (sections.includes('transport')) { + exportData.transport = await getTransportAnalytics(filters); + } + if (sections.includes('farms')) { + exportData.farms = await getFarmAnalytics(filters); + } + if (sections.includes('sustainability')) { + exportData.sustainability = await getSustainabilityAnalytics(filters); + } + + const timestamp = format(new Date(), 'yyyy-MM-dd_HH-mm-ss'); + + if (exportFormat === 'csv') { + // Generate combined CSV + let csvContent = ''; + + if (exportData.plants) { + csvContent += '# Plant Analytics - Species Distribution\n'; + csvContent += generatePlantCSV(exportData.plants); + csvContent += '\n\n'; + } + + if (exportData.transport) { + csvContent += '# Transport Analytics - By Method\n'; + csvContent += generateTransportCSV(exportData.transport); + csvContent += '\n\n'; + } + + if (exportData.sustainability) { + csvContent += '# Sustainability Goals\n'; + csvContent += generateSustainabilityCSV(exportData.sustainability); + } + + res.setHeader('Content-Type', 'text/csv'); + res.setHeader('Content-Disposition', `attachment; filename=analytics_export_${timestamp}.csv`); + return res.status(200).send(csvContent); + } + + // Default: JSON format + res.setHeader('Content-Type', 'application/json'); + res.setHeader('Content-Disposition', `attachment; filename=analytics_export_${timestamp}.json`); + + return res.status(200).json({ + exportedAt: new Date().toISOString(), + timeRange, + sections, + data: exportData, + }); + + } catch (error) { + console.error('Analytics export error:', error); + res.status(500).json({ + success: false, + error: 'Failed to export analytics data', + }); + } +} diff --git a/pages/api/analytics/farms.ts b/pages/api/analytics/farms.ts new file mode 100644 index 0000000..a253eb6 --- /dev/null +++ b/pages/api/analytics/farms.ts @@ -0,0 +1,46 @@ +/** + * Farm Analytics API + * Returns vertical farm analytics data + */ + +import type { NextApiRequest, NextApiResponse } from 'next'; +import { getFarmAnalytics, AnalyticsFilters, TimeRange } from '../../../lib/analytics'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + try { + const timeRange = (req.query.timeRange as TimeRange) || '30d'; + const farmIds = req.query.farmIds + ? (req.query.farmIds as string).split(',') + : undefined; + + const filters: AnalyticsFilters = { + timeRange, + farmIds, + }; + + const farmAnalytics = await getFarmAnalytics(filters); + + res.status(200).json({ + success: true, + data: farmAnalytics, + meta: { + timeRange, + filters: { farmIds }, + generatedAt: new Date().toISOString(), + }, + }); + } catch (error) { + console.error('Farm analytics error:', error); + res.status(500).json({ + success: false, + error: 'Failed to fetch farm analytics', + }); + } +} diff --git a/pages/api/analytics/overview.ts b/pages/api/analytics/overview.ts new file mode 100644 index 0000000..c6fe177 --- /dev/null +++ b/pages/api/analytics/overview.ts @@ -0,0 +1,41 @@ +/** + * Analytics Overview API + * Returns aggregated overview metrics for the dashboard + */ + +import type { NextApiRequest, NextApiResponse } from 'next'; +import { getAnalyticsOverview, AnalyticsFilters, TimeRange } from '../../../lib/analytics'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + try { + const timeRange = (req.query.timeRange as TimeRange) || '30d'; + + const filters: AnalyticsFilters = { + timeRange, + }; + + const overview = await getAnalyticsOverview(filters); + + res.status(200).json({ + success: true, + data: overview, + meta: { + timeRange, + generatedAt: new Date().toISOString(), + }, + }); + } catch (error) { + console.error('Analytics overview error:', error); + res.status(500).json({ + success: false, + error: 'Failed to fetch analytics overview', + }); + } +} diff --git a/pages/api/analytics/plants.ts b/pages/api/analytics/plants.ts new file mode 100644 index 0000000..8270267 --- /dev/null +++ b/pages/api/analytics/plants.ts @@ -0,0 +1,44 @@ +/** + * Plant Analytics API + * Returns plant-specific analytics data + */ + +import type { NextApiRequest, NextApiResponse } from 'next'; +import { getPlantAnalytics, AnalyticsFilters, TimeRange } from '../../../lib/analytics'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + try { + const timeRange = (req.query.timeRange as TimeRange) || '30d'; + const species = req.query.species ? (req.query.species as string).split(',') : undefined; + + const filters: AnalyticsFilters = { + timeRange, + species, + }; + + const plantAnalytics = await getPlantAnalytics(filters); + + res.status(200).json({ + success: true, + data: plantAnalytics, + meta: { + timeRange, + filters: { species }, + generatedAt: new Date().toISOString(), + }, + }); + } catch (error) { + console.error('Plant analytics error:', error); + res.status(500).json({ + success: false, + error: 'Failed to fetch plant analytics', + }); + } +} diff --git a/pages/api/analytics/sustainability.ts b/pages/api/analytics/sustainability.ts new file mode 100644 index 0000000..8cea4ab --- /dev/null +++ b/pages/api/analytics/sustainability.ts @@ -0,0 +1,41 @@ +/** + * Sustainability Analytics API + * Returns environmental impact and sustainability metrics + */ + +import type { NextApiRequest, NextApiResponse } from 'next'; +import { getSustainabilityAnalytics, AnalyticsFilters, TimeRange } from '../../../lib/analytics'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + try { + const timeRange = (req.query.timeRange as TimeRange) || '30d'; + + const filters: AnalyticsFilters = { + timeRange, + }; + + const sustainabilityAnalytics = await getSustainabilityAnalytics(filters); + + res.status(200).json({ + success: true, + data: sustainabilityAnalytics, + meta: { + timeRange, + generatedAt: new Date().toISOString(), + }, + }); + } catch (error) { + console.error('Sustainability analytics error:', error); + res.status(500).json({ + success: false, + error: 'Failed to fetch sustainability analytics', + }); + } +} diff --git a/pages/api/analytics/transport.ts b/pages/api/analytics/transport.ts new file mode 100644 index 0000000..e00509d --- /dev/null +++ b/pages/api/analytics/transport.ts @@ -0,0 +1,46 @@ +/** + * Transport Analytics API + * Returns transport and carbon footprint analytics + */ + +import type { NextApiRequest, NextApiResponse } from 'next'; +import { getTransportAnalytics, AnalyticsFilters, TimeRange } from '../../../lib/analytics'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + try { + const timeRange = (req.query.timeRange as TimeRange) || '30d'; + const transportMethods = req.query.methods + ? (req.query.methods as string).split(',') + : undefined; + + const filters: AnalyticsFilters = { + timeRange, + transportMethods, + }; + + const transportAnalytics = await getTransportAnalytics(filters); + + res.status(200).json({ + success: true, + data: transportAnalytics, + meta: { + timeRange, + filters: { transportMethods }, + generatedAt: new Date().toISOString(), + }, + }); + } catch (error) { + console.error('Transport analytics error:', error); + res.status(500).json({ + success: false, + error: 'Failed to fetch transport analytics', + }); + } +} diff --git a/tsconfig.json b/tsconfig.json index 7d7287a..9ed7459 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "allowJs": true, "skipLibCheck": true, "strict": false, + "downlevelIteration": true, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true,