소스 검색

Removing the UI code, the new repository is opencost/opencost-ui (#2741)

* Removing the UI code, the new repository is https://github.com/opencost/opencost-ui

Do not merge until we have working packages from that repository.

Signed-off-by: Matt Ray <github@mattray.dev>

* Removed UI settings

Signed-off-by: Matt Ray <github@mattray.dev>

* Updated for 1.110.0 and UI moving

Signed-off-by: Matt Ray <github@mattray.dev>

* Updated where to find versions and where to open UI issues

Signed-off-by: Matt Ray <github@mattray.dev>

* Remove UI-related testing and release code

Signed-off-by: Matt Ray <github@mattray.dev>

---------

Signed-off-by: Matt Ray <github@mattray.dev>
Matt Ray 2 년 전
부모
커밋
c87b36f9bc
59개의 변경된 파일6개의 추가작업 그리고 10339개의 파일을 삭제
  1. 2 2
      .github/ISSUE_TEMPLATE/opencost-bug-report.md
  2. 0 6
      .github/dependabot.yml
  3. 0 16
      .github/workflows/build-and-publish-release.yml
  4. 1 37
      .github/workflows/build-test.yaml
  5. 0 5
      .gitignore
  6. 3 1
      README.md
  7. 0 3
      ui/.babelrc
  8. 0 2
      ui/.dockerignore
  9. 0 1
      ui/.nvmrc
  10. 0 48
      ui/Dockerfile
  11. 0 38
      ui/Dockerfile.cross
  12. 0 40
      ui/Dockerfile.debug
  13. 0 65
      ui/README.md
  14. 0 79
      ui/default.nginx.conf.template
  15. 0 26
      ui/docker-entrypoint.sh
  16. 0 41
      ui/justfile
  17. 0 36
      ui/nginx.conf
  18. 0 6093
      ui/package-lock.json
  19. 0 47
      ui/package.json
  20. 0 314
      ui/src/Reports.js
  21. 0 5
      ui/src/app.js
  22. 0 217
      ui/src/cloudCost/cloudCost.js
  23. 0 14
      ui/src/cloudCost/cloudCostChart/index.js
  24. 0 275
      ui/src/cloudCost/cloudCostChart/rangeChart.js
  25. 0 178
      ui/src/cloudCost/cloudCostDetails.js
  26. 0 48
      ui/src/cloudCost/cloudCostRow.js
  27. 0 91
      ui/src/cloudCost/controls/cloudCostEditControls.js
  28. 0 49
      ui/src/cloudCost/tokens.js
  29. 0 337
      ui/src/cloudCostReports.js
  30. 0 149
      ui/src/components/AllocationChart/RangeChart.js
  31. 0 96
      ui/src/components/AllocationChart/SummaryChart.js
  32. 0 81
      ui/src/components/AllocationChart/index.js
  33. 0 89
      ui/src/components/Controls/Download.js
  34. 0 74
      ui/src/components/Controls/Edit.js
  35. 0 48
      ui/src/components/Controls/index.js
  36. 0 227
      ui/src/components/Details.js
  37. 0 13
      ui/src/components/Footer.js
  38. 0 60
      ui/src/components/Header.js
  39. 0 78
      ui/src/components/Nav/NavItem.js
  40. 0 70
      ui/src/components/Nav/SidebarNav.js
  41. 0 3
      ui/src/components/Nav/index.js
  42. 0 46
      ui/src/components/Page.js
  43. 0 190
      ui/src/components/SelectWindow.js
  44. 0 44
      ui/src/components/Subtitle.js
  45. 0 37
      ui/src/components/Warnings.js
  46. 0 241
      ui/src/components/allocationReport.js
  47. 0 38
      ui/src/constants/colors.js
  48. 0 1
      ui/src/constants/currencyCodes.js
  49. 0 20
      ui/src/css/index.css
  50. BIN
      ui/src/images/favicon.ico
  51. BIN
      ui/src/images/logo.png
  52. 0 16
      ui/src/index.html
  53. BIN
      ui/src/opencost-ui.png
  54. 0 25
      ui/src/route.js
  55. 0 28
      ui/src/services/allocation.js
  56. 0 42
      ui/src/services/cloudCostDayTotals.js
  57. 0 57
      ui/src/services/cloudCostTop.js
  58. BIN
      ui/src/thumbnail.png
  59. 0 452
      ui/src/util.js

+ 2 - 2
.github/ISSUE_TEMPLATE/opencost-bug-report.md

@@ -8,7 +8,7 @@ assignees: ''
 ---
 
 **Describe the bug**
-A clear and concise description of what the OpenCost bug is. Please ensure this is an issue related to the OpenCost cost model, API, UI or specification. Public Kubecost bugs may be opened at https://github.com/kubecost/features-bugs
+A clear and concise description of what the OpenCost bug is. Please ensure this is an issue related to the OpenCost cost model, API or specification. UI issues may be opened in the [OpenCost UI repository](https://github.com/opencost/opencost-ui).
 
 **To Reproduce**
 Steps to reproduce the behavior:
@@ -24,7 +24,7 @@ A clear and concise description of what you expected to happen.
 If applicable, add screenshots to help explain your problem.
 
 **Which version of OpenCost are you using?**
-This may be the Kubecost release.
+You can find the version from the container's startup logging or from the bottom of the page in the UI.
 
 **Additional context**
 Add any other context about the problem here. Kubernetes versions and which public clouds you are working with are especially important.

+ 0 - 6
.github/dependabot.yml

@@ -11,9 +11,3 @@ updates:
     directory: "/"
     schedule:
       interval: "weekly"
-
-  # Dependencies listed in ui/package.json
-  - package-ecosystem: "npm"
-    directory: "/ui"
-    schedule:
-      interval: "weekly"

+ 0 - 16
.github/workflows/build-and-publish-release.yml

@@ -88,15 +88,9 @@ jobs:
           echo "IMAGE_TAG=ghcr.io/opencost/opencost:${{ steps.sha.outputs.OC_SHORTHASH }}" >> $GITHUB_OUTPUT
           echo "IMAGE_TAG_LATEST=ghcr.io/opencost/opencost:latest" >> $GITHUB_OUTPUT
           echo "IMAGE_TAG_VERSION=ghcr.io/opencost/opencost:${{ steps.version_number.outputs.RELEASE_VERSION }}" >> $GITHUB_OUTPUT
-          echo "IMAGE_TAG_UI=ghcr.io/opencost/opencost-ui:${{ steps.sha.outputs.OC_SHORTHASH }}" >> $GITHUB_OUTPUT
-          echo "IMAGE_TAG_UI_LATEST=ghcr.io/opencost/opencost-ui:latest" >> $GITHUB_OUTPUT
-          echo "IMAGE_TAG_UI_VERSION=ghcr.io/opencost/opencost-ui:${{ steps.version_number.outputs.RELEASE_VERSION }}" >> $GITHUB_OUTPUT
         #  echo "IMAGE_TAG_QUAY=quay.io/kubecost1/kubecost-cost-model:${{ steps.sha.outputs.OC_SHORTHASH }}" >> $GITHUB_OUTPUT
         #  echo "IMAGE_TAG_LATEST_QUAY=quay.io/kubecost1/kubecost-cost-model:latest" >> $GITHUB_OUTPUT
         #  echo "IMAGE_TAG_VERSION_QUAY=quay.io/kubecost1/kubecost-cost-model:prod-${{ steps.version_number.outputs.RELEASE_VERSION }}" >> $GITHUB_OUTPUT
-        #  echo "IMAGE_TAG_UI_QUAY=quay.io/kubecost1/opencost-ui:${{ steps.sha.outputs.OC_SHORTHASH }}" >> $GITHUB_OUTPUT
-        #  echo "IMAGE_TAG_UI_LATEST_QUAY=quay.io/kubecost1/opencost-ui:latest" >> $GITHUB_OUTPUT
-        #  echo "IMAGE_TAG_UI_VERSION_QUAY=quay.io/kubecost1/opencost-ui:prod-${{ inputs.release_version }}" >> $GITHUB_OUTPUT
 
       - name: Set up Docker Buildx
         uses: docker/setup-buildx-action@v3
@@ -141,13 +135,3 @@ jobs:
         #  crane copy '${{ steps.tags.outputs.IMAGE_TAG }}' '${steps.tags.outputs.IMAGE_TAG_QUAY}'
         #  crane copy '${{ steps.tags.outputs.IMAGE_TAG }}' '${steps.tags.outputs.IMAGE_TAG_LATEST_QUAY}'
         #  crane copy '${{ steps.tags.outputs.IMAGE_TAG }}' '${steps.tags.outputs.IMAGE_TAG_VERSION_QUAY}'
-
-      - name: Build and push (multiarch) OpenCost UI
-        working-directory: ./opencost/ui
-        run: |
-          just build '${{ steps.tags.outputs.IMAGE_TAG_UI }}' '${{ steps.version_number.outputs.RELEASE_VERSION }}'
-          crane copy '${{ steps.tags.outputs.IMAGE_TAG_UI }}' '${{ steps.tags.outputs.IMAGE_TAG_UI_LATEST }}'
-          crane copy '${{ steps.tags.outputs.IMAGE_TAG_UI }}' '${{ steps.tags.outputs.IMAGE_TAG_UI_VERSION }}'
-        #  crane copy '${steps.tags.outputs.IMAGE_TAG_UI}' '${steps.tags.outputs.IMAGE_TAG_UI_QUAY}'
-        #  crane copy '${steps.tags.outputs.IMAGE_TAG_UI}' '${steps.tags.outputs.IMAGE_TAG_UI_LATEST_QUAY}'
-        #  crane copy '${steps.tags.outputs.IMAGE_TAG_UI}' '${steps.tags.outputs.IMAGE_TAG_UI_VERSION_QUAY}'

+ 1 - 37
.github/workflows/build-test.yaml

@@ -21,7 +21,7 @@ jobs:
         uses: actions/setup-go@v5
         with:
           go-version: 'stable'
-          
+
       -
         name: Install protoc
         uses: arduino/setup-protoc@v3
@@ -93,39 +93,3 @@ jobs:
            pr_num.txt
            base.txt
            head.txt
-
-  frontend:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v4
-        with:
-          path: ./
-
-      -
-        name: Install just
-        uses: extractions/setup-just@v1
-
-      -
-        name: Install node
-        uses: actions/setup-node@v4
-        with:
-          node-version: '18.3.0'
-
-      - name: Get npm cache directory
-        id: npm-cache-dir
-        shell: bash
-        run: echo "dir=$(npm config get cache)" >> ${GITHUB_OUTPUT}
-
-      - uses: actions/cache@v4
-        id: npm-cache # use this to check for `cache-hit` ==> if: steps.npm-cache.outputs.cache-hit != 'true'
-        with:
-          path: ${{ steps.npm-cache-dir.outputs.dir }}
-          key: ${{ runner.os }}-node-${{ hashFiles('./ui/**/package-lock.json') }}
-          restore-keys: |
-            ${{ runner.os }}-node-
-
-      -
-        name: Build
-        working-directory: ./ui
-        run: |
-          just build-local

+ 0 - 5
.gitignore

@@ -2,11 +2,6 @@
 .idea
 *.iml
 
-ui/.parcel-cache
-ui/.cache
-ui/dist
-ui/.env
-ui/node_modules/
 cmd/costmodel/costmodel
 cmd/costmodel/costmodel-amd64
 cmd/costmodel/costmodel-arm64

+ 3 - 1
README.md

@@ -9,7 +9,7 @@ OpenCost give teams visibility into current and historical Kubernetes and cloud
 These models provide cost transparency in Kubernetes environments that support multiple applications, teams, departments, etc.
 It also provides visibility into the cloud costs across multiple providers.
 
-OpenCost was originally developed and open sourced by [Kubecost](https://kubecost.com). This project combines a [specification](/spec/) as well as a Golang implementation of these detailed requirements.
+OpenCost was originally developed and open sourced by [Kubecost](https://kubecost.com). This project combines a [specification](/spec/) as well as a Golang implementation of these detailed requirements. The web UI is available in the [opencost/opencost-ui](http://github.com/opencost/opencost-ui) repository.
 
 [![OpenCost UI Walkthrough](./ui/src/thumbnail.png)](https://youtu.be/lCP4Ci9Kcdg)
 *OpenCost UI Walkthrough*
@@ -22,6 +22,8 @@ To see the full functionality of OpenCost you can view [OpenCost features](https
 - Supports on-prem k8s clusters with custom CSV pricing
 - Allocation for in-cluster K8s resources like CPU, GPU, memory, and persistent volumes
 - Easily export pricing data to Prometheus with /metrics endpoint ([learn more](https://www.opencost.io/docs/installation/prometheus))
+- Carbon costs for cloud resources
+- Support for external costs like Datadog through [OpenCost Plugins](https://github.com/opencost/opencost-plugins)
 - Free and open source distribution ([Apache2 license](LICENSE))
 
 ## Getting Started

+ 0 - 3
ui/.babelrc

@@ -1,3 +0,0 @@
-{
-  "plugins": ["@babel/plugin-transform-runtime", "@babel/plugin-proposal-class-properties"]
-}

+ 0 - 2
ui/.dockerignore

@@ -1,2 +0,0 @@
-.parcel-cache/
-node_modules/

+ 0 - 1
ui/.nvmrc

@@ -1 +0,0 @@
-18.3.0

+ 0 - 48
ui/Dockerfile

@@ -1,48 +0,0 @@
-FROM node:18.3.0 as builder
-ADD package*.json /opt/ui/
-WORKDIR /opt/ui
-RUN npm install
-ADD src /opt/ui/src
-RUN npx parcel build src/index.html
-
-FROM nginx:alpine
-
-LABEL org.opencontainers.image.description="Cross-cloud cost allocation models for Kubernetes workloads"
-LABEL org.opencontainers.image.documentation=https://opencost.io/docs/
-LABEL org.opencontainers.image.licenses=Apache-2.0
-LABEL org.opencontainers.image.source=https://github.com/opencost/opencost
-LABEL org.opencontainers.image.title=opencost-ui
-LABEL org.opencontainers.image.url=https://opencost.io
-
-ARG version=dev
-ARG	commit=HEAD
-ENV VERSION=${version}
-ENV HEAD=${commit}
-
-ENV API_PORT=9003
-ENV API_SERVER=0.0.0.0
-ENV UI_PORT=9090
-
-COPY --from=builder /opt/ui/dist /opt/ui/dist
-RUN mkdir -p /var/www
-
-COPY THIRD_PARTY_LICENSES.txt /THIRD_PARTY_LICENSES.txt
-COPY --from=builder /opt/ui/dist /var/www
-
-COPY default.nginx.conf.template /etc/nginx/conf.d/default.nginx.conf.template
-COPY nginx.conf /etc/nginx/
-COPY ./docker-entrypoint.sh /usr/local/bin/
-
-RUN rm -rf /etc/nginx/conf.d/default.conf
-
-RUN adduser 1001 -g 1000 -D
-RUN chown 1001:1000 -R /var/www
-RUN chown 1001:1000 -R /etc/nginx
-RUN chown 1001:1000 -R /usr/local/bin/docker-entrypoint.sh
-
-ENV BASE_URL=/model
-
-USER 1001
-
-ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
-CMD ["nginx", "-g", "daemon off;"]

+ 0 - 38
ui/Dockerfile.cross

@@ -1,38 +0,0 @@
-FROM nginx:alpine
-
-LABEL org.opencontainers.image.description="Cross-cloud cost allocation models for Kubernetes workloads"
-LABEL org.opencontainers.image.documentation=https://opencost.io/docs/
-LABEL org.opencontainers.image.licenses=Apache-2.0
-LABEL org.opencontainers.image.source=https://github.com/opencost/opencost
-LABEL org.opencontainers.image.title=opencost-ui
-LABEL org.opencontainers.image.url=https://opencost.io
-
-ARG version=dev
-ARG	commit=HEAD
-ENV VERSION=${version}
-ENV HEAD=${commit}
-
-ENV API_PORT=9003
-ENV API_SERVER=0.0.0.0
-ENV UI_PORT=9090
-
-COPY ./dist /opt/ui/dist
-COPY THIRD_PARTY_LICENSES.txt /THIRD_PARTY_LICENSES.txt
-COPY default.nginx.conf.template /etc/nginx/conf.d/default.nginx.conf.template
-COPY nginx.conf /etc/nginx/
-COPY ./docker-entrypoint.sh /usr/local/bin/
-RUN mkdir -p /var/www
-
-RUN rm -rf /etc/nginx/conf.d/default.conf
-
-RUN adduser 1001 -g 1000 -D
-RUN chown 1001:1000 -R /var/www
-RUN chown 1001:1000 -R /etc/nginx
-RUN chown 1001:1000 -R /usr/local/bin/docker-entrypoint.sh
-
-ENV BASE_URL=/model
-
-USER 1001
-
-ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
-CMD ["nginx", "-g", "daemon off;"]

+ 0 - 40
ui/Dockerfile.debug

@@ -1,40 +0,0 @@
-# This dockerfile is for development purposes only; do not use this for production deployments
-# This file exists due to changes introduced in https://github.com/opencost/opencost/pull/2502
-# Tilt cannot reference files that exist outside of this ./ui folder so the reference to THIRD_PARTY_LICENSES.txt is removed
-FROM nginx:alpine
-
-LABEL org.opencontainers.image.description="Cross-cloud cost allocation models for Kubernetes workloads"
-LABEL org.opencontainers.image.documentation=https://opencost.io/docs/
-LABEL org.opencontainers.image.licenses=Apache-2.0
-LABEL org.opencontainers.image.source=https://github.com/opencost/opencost
-LABEL org.opencontainers.image.title=opencost-ui
-LABEL org.opencontainers.image.url=https://opencost.io
-
-ARG version=dev
-ARG	commit=HEAD
-ENV VERSION=${version}
-ENV HEAD=${commit}
-
-ENV API_PORT=9003
-ENV API_SERVER=0.0.0.0
-ENV UI_PORT=9090
-
-COPY ./dist /opt/ui/dist
-COPY default.nginx.conf.template /etc/nginx/conf.d/default.nginx.conf.template
-COPY nginx.conf /etc/nginx/
-COPY ./docker-entrypoint.sh /usr/local/bin/
-RUN mkdir -p /var/www
-
-RUN rm -rf /etc/nginx/conf.d/default.conf
-
-RUN adduser 1001 -g 1000 -D
-RUN chown 1001:1000 -R /var/www
-RUN chown 1001:1000 -R /etc/nginx
-RUN chown 1001:1000 -R /usr/local/bin/docker-entrypoint.sh
-
-ENV BASE_URL=/model
-
-USER 1001
-
-ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
-CMD ["nginx", "-g", "daemon off;"]

+ 0 - 65
ui/README.md

@@ -1,65 +0,0 @@
-# OpenCost UI
-
-## Installing
-
-See https://www.opencost.io/docs/install for the full instructions.
-
-```
-helm install prometheus --repo https://prometheus-community.github.io/helm-charts prometheus \
-  --namespace prometheus-system --create-namespace \
-  --set pushgateway.enabled=false \
-  --set alertmanager.enabled=false \
-  -f https://raw.githubusercontent.com/opencost/opencost/develop/kubernetes/prometheus/extraScrapeConfigs.yaml
-
-kubectl apply --namespace opencost -f https://raw.githubusercontent.com/opencost/opencost/develop/kubernetes/opencost.yaml
-```
-
-## Using
-
-After following the installation instructions, access the UI by port forwarding:
-```
-kubectl port-forward --namespace opencost service/opencost 9090
-```
-
-## Running Locally
-
-The UI can be run locally using the `npm run serve` command.
-
-```sh
-$ npm install
-...
-$ npm run serve
-> opencost-ui@0.1.0 serve
-> npx parcel serve src/index.html
-
-Server running at http://localhost:1234
-✨ Built in 1.96s
-```
-
-And can have a custom URL backend prefix.
-
-```sh
-BASE_URL=http://localhost:9090/test npm run serve
-
-> opencost-ui@0.1.0 serve
-> npx parcel serve src/index.html
-
-Server running at http://localhost:1234
-✨ Built in 772ms
-```
-
-In addition, similar behavior can be replicated with the docker container:
-
-```sh
-$ docker run -e BASE_URL_OVERRIDE=test -p 9091:9090 -d opencost-ui:latest
-$ curl localhost:9091
-<html gibberish>
-```
-
-## Overriding the Base API URL
-
-For some use cases such as the case of [OpenCost deployed behind an ingress controller](https://github.com/opencost/opencost/issues/1677), it is useful to override the `BASE_URL` variable responsible for requests sent from the UI to the API.  This means that instead of sending requests to `<domain>/model/allocation/compute/etc`, requests can be sent to `<domain>/{BASE_URL_OVERRIDE}/allocation/compute/etc`.  To do this, supply the environment variable `BASE_URL_OVERRIDE` to the docker image.
-
-```sh
-$ docker run -p 9091:9090 -e BASE_URL_OVERRIDE=anything -d opencost-ui:latest
-```

+ 0 - 79
ui/default.nginx.conf.template

@@ -1,79 +0,0 @@
-gzip_static  on;
-gzip on;
-gzip_min_length 50000;
-gzip_proxied expired no-cache no-store private auth;
-gzip_types
-    application/atom+xml
-    application/geo+json
-    application/javascript
-    application/x-javascript
-    application/json
-    application/ld+json
-    application/manifest+json
-    application/rdf+xml
-    application/rss+xml
-    application/vnd.ms-fontobject
-    application/wasm
-    application/x-web-app-manifest+json
-    application/xhtml+xml
-    application/xml
-    font/eot
-    font/otf
-    font/ttf
-    image/bmp
-    image/svg+xml
-    text/cache-manifest
-    text/calendar
-    text/css
-    text/javascript
-    text/markdown
-    text/plain
-    text/xml
-    text/x-component
-    text/x-cross-domain-policy;
-
-upstream model {
-    # Update to the cost model endpoint
-    # Example: host.docker.internal:9003;
-    server ${API_SERVER}:${API_PORT};
-}
-
-server {
-    server_name _;
-    root /var/www;
-    index index.html;
-    large_client_header_buffers 4 32k;
-    add_header Cache-Control "must-revalidate";
-
-    error_page 504 /custom_504.html;
-    location = /custom_504.html {
-        internal;
-    }
-
-    add_header Cache-Control "max-age=300";
-    location / {
-        root /var/www;
-        index index.html index.htm;
-        try_files $uri /index.html;
-    }
-
-    add_header ETag "1.96.0";
-    listen ${UI_PORT};
-    listen [::]:${UI_PORT};
-    resolver 127.0.0.1 valid=5s;
-    location /healthz {
-        access_log /dev/null;
-        return 200 'OK';
-    }
-    location /model/ {
-        proxy_connect_timeout       180;
-        proxy_send_timeout          180;
-        proxy_read_timeout          180;
-        proxy_pass http://model/;
-        proxy_redirect off;
-        proxy_http_version 1.1;
-        proxy_set_header Connection "";
-        proxy_set_header  X-Real-IP  $remote_addr;
-        proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
-    }
-}

+ 0 - 26
ui/docker-entrypoint.sh

@@ -1,26 +0,0 @@
-#!/bin/sh
-set -e
-
-cp -rv /opt/ui/dist/* /var/www
-
-if [[ ! -z "$BASE_URL_OVERRIDE" ]]; then
-    echo "running with BASE_URL=${BASE_URL_OVERRIDE}"
-    sed -i "s^{PLACEHOLDER_BASE_URL}^$BASE_URL_OVERRIDE^g" /var/www/*.js
-else
-    echo "running with BASE_URL=${BASE_URL}"
-    sed -i "s^{PLACEHOLDER_BASE_URL}^$BASE_URL^g" /var/www/*.js
-fi
-
-# export your OPENCOST_FOOTER_CONTENT='<a href="https://opencost.io">OpenCost</a>' in your Dockerfile to set
-if [[ ! -z "$OPENCOST_FOOTER_CONTENT" ]]; then
-    sed -i "s^PLACEHOLDER_FOOTER_CONTENT^$OPENCOST_FOOTER_CONTENT^g" /var/www/*.js
-else
-    sed -i "s^PLACEHOLDER_FOOTER_CONTENT^OpenCost version: $VERSION ($HEAD)^g" /var/www/*.js
-fi
-
-envsubst '$API_PORT $API_SERVER $UI_PORT' < /etc/nginx/conf.d/default.nginx.conf.template > /etc/nginx/conf.d/default.nginx.conf
-
-echo "Starting OpenCost UI version $VERSION ($HEAD)"
-
-# Run the parent (nginx) container's entrypoint script
-exec /docker-entrypoint.sh "$@"

+ 0 - 41
ui/justfile

@@ -1,41 +0,0 @@
-commit := `git rev-parse --short HEAD`
-thirdPartyLicenseFile := "THIRD_PARTY_LICENSES.txt"
-
-default:
-    just --list
-
-build-local:
-    npm install
-
-    npx parcel build src/index.html
-
-build IMAGE_TAG RELEASE_VERSION: build-local
-    cp ../{{thirdPartyLicenseFile}} .
-    docker buildx build \
-        --rm \
-        --platform "linux/amd64" \
-        -f 'Dockerfile.cross' \
-        --provenance=false \
-        -t {{IMAGE_TAG}}-amd64 \
-        --build-arg version={{RELEASE_VERSION}} \
-        --build-arg commit={{commit}} \
-        --push \
-        .
-
-    docker buildx build \
-        --rm \
-        --platform "linux/arm64" \
-        -f 'Dockerfile.cross' \
-        --provenance=false \
-        -t {{IMAGE_TAG}}-arm64 \
-        --build-arg version={{RELEASE_VERSION}} \
-        --build-arg commit={{commit}} \
-        --push \
-        .
-
-    manifest-tool push from-args \
-        --platforms "linux/amd64,linux/arm64" \
-        --template {{IMAGE_TAG}}-ARCH \
-        --target {{IMAGE_TAG}}
-
-    rm -f {{thirdPartyLicenseFile}}

+ 0 - 36
ui/nginx.conf

@@ -1,36 +0,0 @@
-worker_processes  auto;
-
-error_log  /var/log/nginx/error.log notice;
-pid        /tmp/nginx.pid;
-
-
-events {
-    worker_connections  1024;
-}
-
-
-http {
-    proxy_temp_path /tmp/proxy_temp;
-    client_body_temp_path /tmp/client_temp;
-    fastcgi_temp_path /tmp/fastcgi_temp;
-    uwsgi_temp_path /tmp/uwsgi_temp;
-    scgi_temp_path /tmp/scgi_temp;
-
-    include       /etc/nginx/mime.types;
-    default_type  application/octet-stream;
-
-    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
-                      '$status $body_bytes_sent "$http_referer" '
-                      '"$http_user_agent" "$http_x_forwarded_for"';
-
-    access_log  /var/log/nginx/access.log  main;
-
-    sendfile        on;
-    #tcp_nopush     on;
-
-    keepalive_timeout  65;
-
-    #gzip  on;
-
-    include /etc/nginx/conf.d/*.conf;
-}

+ 0 - 6093
ui/package-lock.json

@@ -1,6093 +0,0 @@
-{
-  "name": "opencost-ui",
-  "version": "0.1.0",
-  "lockfileVersion": 3,
-  "requires": true,
-  "packages": {
-    "": {
-      "name": "opencost-ui",
-      "version": "0.1.0",
-      "hasInstallScript": true,
-      "license": "Apache-2.0",
-      "dependencies": {
-        "material-design-icons-iconfont": "^6.1.0",
-        "axios": "^1.6.0",
-        "@material-ui/icons": "^4.11.2",
-        "@material-ui/pickers": "^3.3.10",
-        "html-to-react": "^1.7.0",
-        "@babel/runtime": "^7.23.9",
-        "date-fns": "^2.30.0",
-        "react-dom": "^17.0.1",
-        "@material-ui/core": "^4.11.3",
-        "recharts": "^2.2.0",
-        "react-router-dom": "^5.2.0",
-        "@date-io/core": "^1.3.13",
-        "@material-ui/styles": "^4.11.5",
-        "react": "^17.0.1",
-        "@date-io/date-fns": "^1.3.13",
-        "prop-types": "^15.7.2"
-      },
-      "devDependencies": {
-        "@babel/core": "^7.13.10",
-        "@babel/plugin-proposal-class-properties": "^7.13.0",
-        "@babel/plugin-transform-runtime": "^7.23.9",
-        "@babel/preset-react": "^7.12.13",
-        "buffer": "^6.0.3",
-        "parcel": "^2.11.0",
-        "process": "^0.11.10",
-        "set-value": "4.1.0"
-      }
-    },
-    "node_modules/argparse": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
-      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
-      "dev": true
-    },
-    "node_modules/d3-scale": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
-      "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
-      "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"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@emotion/hash": {
-      "version": "0.8.0",
-      "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
-      "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
-    },
-    "node_modules/convert-source-map": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
-      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
-      "dev": true
-    },
-    "node_modules/detect-libc": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
-      "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
-      "dev": true,
-      "bin": {
-        "detect-libc": "bin/detect-libc.js"
-      },
-      "engines": {
-        "node": ">=0.10"
-      }
-    },
-    "node_modules/hyphenate-style-name": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
-      "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ=="
-    },
-    "node_modules/react-router-dom": {
-      "version": "5.3.4",
-      "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz",
-      "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==",
-      "dependencies": {
-        "@babel/runtime": "^7.12.13",
-        "history": "^4.9.0",
-        "loose-envify": "^1.3.1",
-        "prop-types": "^15.6.2",
-        "react-router": "5.3.4",
-        "tiny-invariant": "^1.0.2",
-        "tiny-warning": "^1.0.0"
-      },
-      "peerDependencies": {
-        "react": ">=15"
-      }
-    },
-    "node_modules/@parcel/transformer-react-refresh-wrap": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.11.0.tgz",
-      "integrity": "sha512-6pY0CdIgIpXC6XpsDWizf+zLgiuEsJ106HjWLwF7/R72BrvDhLPZ6jRu4UTrnd6bM89KahPw9fZZzjKoA5Efcw==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/plugin": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "react-refresh": "^0.9.0"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/d3-time": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
-      "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
-      "dependencies": {
-        "d3-array": "2 - 3"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/parse-json": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
-      "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
-      "dev": true,
-      "dependencies": {
-        "@babel/code-frame": "^7.0.0",
-        "error-ex": "^1.3.1",
-        "json-parse-even-better-errors": "^2.3.0",
-        "lines-and-columns": "^1.1.6"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/@parcel/codeframe/node_modules/color-name": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-      "dev": true
-    },
-    "node_modules/srcset": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz",
-      "integrity": "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==",
-      "dev": true,
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/@parcel/watcher-linux-arm64-glibc": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.4.0.tgz",
-      "integrity": "sha512-QuJTAQdsd7PFW9jNGaV9Pw+ZMWV9wKThEzzlY3Lhnnwy7iW23qtQFPql8iEaSFMCVI5StNNmONUopk+MFKpiKg==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">= 10.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/get-port": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/get-port/-/get-port-4.2.0.tgz",
-      "integrity": "sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==",
-      "dev": true,
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/@parcel/runtime-js": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/runtime-js/-/runtime-js-2.11.0.tgz",
-      "integrity": "sha512-fH3nJoexINz7s4cDzp0Vjsx0k1pMYSa5ch38LbbNqCKTermy0pS0zZuvgfLfHFFP+AMRpFQenrF7h7N3bgDmHw==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/plugin": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "nullthrows": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@date-io/date-fns": {
-      "version": "1.3.13",
-      "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-1.3.13.tgz",
-      "integrity": "sha512-yXxGzcRUPcogiMj58wVgFjc9qUYrCnnU9eLcyNbsQCmae4jPuZCDoIBR21j8ZURsM7GRtU62VOw5yNd4dDHunA==",
-      "dependencies": {
-        "@date-io/core": "^1.3.13"
-      },
-      "peerDependencies": {
-        "date-fns": "^2.0.0"
-      }
-    },
-    "node_modules/resolve-from": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
-      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
-      "dev": true,
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/@lmdb/lmdb-linux-arm64": {
-      "version": "2.8.5",
-      "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-2.8.5.tgz",
-      "integrity": "sha512-vtbZRHH5UDlL01TT5jB576Zox3+hdyogvpcbvVJlmU5PdL3c5V7cj1EODdh1CHPksRl+cws/58ugEHi8bcj4Ww==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@lezer/lr": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
-      "integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
-      "dev": true,
-      "dependencies": {
-        "@lezer/common": "^1.0.0"
-      }
-    },
-    "node_modules/@parcel/utils": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/utils/-/utils-2.11.0.tgz",
-      "integrity": "sha512-AcL70cXlIyE7eQdvjQbYxegN5l+skqvlJllxTWg4YkIZe9p8Gmv74jLAeLWh5F+IGl5WRn0TSy9JhNJjIMQGwQ==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/codeframe": "2.11.0",
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/logger": "2.11.0",
-        "@parcel/markdown-ansi": "2.11.0",
-        "@parcel/rust": "2.11.0",
-        "@parcel/source-map": "^2.1.1",
-        "chalk": "^4.1.0",
-        "nullthrows": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@parcel/bundler-default": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/bundler-default/-/bundler-default-2.11.0.tgz",
-      "integrity": "sha512-ZIs0865Lp871ZK83k5I9L4DeeE26muNMrHa7j8bvls6fKBJKAn8djrhfU4XOLyziU4aAOobcPwXU0+npWqs52g==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/graph": "3.1.0",
-        "@parcel/plugin": "2.11.0",
-        "@parcel/rust": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "nullthrows": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/react-resize-detector": {
-      "version": "8.1.0",
-      "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-8.1.0.tgz",
-      "integrity": "sha512-S7szxlaIuiy5UqLhLL1KY3aoyGHbZzsTpYal9eYMwCyKqoqoVLCmIgAgNyIM1FhnP2KyBygASJxdhejrzjMb+w==",
-      "dependencies": {
-        "lodash": "^4.17.21"
-      },
-      "peerDependencies": {
-        "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
-        "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
-      }
-    },
-    "node_modules/@material-ui/utils": {
-      "version": "4.11.3",
-      "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.3.tgz",
-      "integrity": "sha512-ZuQPV4rBK/V1j2dIkSSEcH5uT6AaHuKWFfotADHsC0wVL1NLd2WkFCm4ZZbX33iO4ydl6V0GPngKm8HZQ2oujg==",
-      "dependencies": {
-        "@babel/runtime": "^7.4.4",
-        "prop-types": "^15.7.2",
-        "react-is": "^16.8.0 || ^17.0.0"
-      },
-      "engines": {
-        "node": ">=8.0.0"
-      },
-      "peerDependencies": {
-        "react": "^16.8.0 || ^17.0.0",
-        "react-dom": "^16.8.0 || ^17.0.0"
-      }
-    },
-    "node_modules/@parcel/package-manager/node_modules/semver": {
-      "version": "7.5.4",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
-      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
-      "dev": true,
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@jridgewell/set-array": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
-      "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
-      "dev": true,
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
-    "node_modules/jss-plugin-camel-case": {
-      "version": "10.10.0",
-      "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.10.0.tgz",
-      "integrity": "sha512-z+HETfj5IYgFxh1wJnUAU8jByI48ED+v0fuTuhKrPR+pRBYS2EDwbusU8aFOpCdYhtRc9zhN+PJ7iNE8pAWyPw==",
-      "dependencies": {
-        "@babel/runtime": "^7.3.1",
-        "hyphenate-style-name": "^1.0.3",
-        "jss": "10.10.0"
-      }
-    },
-    "node_modules/css-what": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
-      "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
-      "dev": true,
-      "engines": {
-        "node": ">= 6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/fb55"
-      }
-    },
-    "node_modules/@parcel/utils/node_modules/color-name": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-      "dev": true
-    },
-    "node_modules/jss": {
-      "version": "10.10.0",
-      "resolved": "https://registry.npmjs.org/jss/-/jss-10.10.0.tgz",
-      "integrity": "sha512-cqsOTS7jqPsPMjtKYDUpdFC0AbhYFLTcuGRqymgmdJIeQ8cH7+AgX7YSgQy79wXloZq2VvATYxUOUQEvS1V/Zw==",
-      "dependencies": {
-        "@babel/runtime": "^7.3.1",
-        "csstype": "^3.0.2",
-        "is-in-browser": "^1.1.3",
-        "tiny-warning": "^1.0.2"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/jss"
-      }
-    },
-    "node_modules/@material-ui/types": {
-      "version": "5.1.0",
-      "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz",
-      "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==",
-      "peerDependencies": {
-        "@types/react": "*"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@parcel/watcher-darwin-arm64": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.4.0.tgz",
-      "integrity": "sha512-T/At5pansFuQ8VJLRx0C6C87cgfqIYhW2N/kBfLCUvDhCah0EnLLwaD/6MW3ux+rpgkpQAnMELOCTKlbwncwiA==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">= 10.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/csso/node_modules/mdn-data": {
-      "version": "2.0.28",
-      "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz",
-      "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==",
-      "dev": true,
-      "optional": true,
-      "peer": true
-    },
-    "node_modules/@types/prop-types": {
-      "version": "15.7.9",
-      "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.9.tgz",
-      "integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g=="
-    },
-    "node_modules/nullthrows": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz",
-      "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==",
-      "dev": true
-    },
-    "node_modules/dotenv": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-7.0.0.tgz",
-      "integrity": "sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==",
-      "dev": true,
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/msgpackr-extract/node_modules/node-gyp-build-optional-packages": {
-      "version": "5.0.7",
-      "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.7.tgz",
-      "integrity": "sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==",
-      "dev": true,
-      "optional": true,
-      "bin": {
-        "node-gyp-build-optional-packages": "bin.js",
-        "node-gyp-build-optional-packages-optional": "optional.js",
-        "node-gyp-build-optional-packages-test": "build-test.js"
-      }
-    },
-    "node_modules/caniuse-lite": {
-      "version": "1.0.30001581",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001581.tgz",
-      "integrity": "sha512-whlTkwhqV2tUmP3oYhtNfaWGYHDdS3JYFQBKXxcUR9qqPWsRhFHhoISO2Xnl/g0xyKzht9mI1LZpiNWfMzHixQ==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "opencollective",
-          "url": "https://opencollective.com/browserslist"
-        },
-        {
-          "type": "tidelift",
-          "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
-        },
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/ai"
-        }
-      ]
-    },
-    "node_modules/babel-plugin-polyfill-corejs3": {
-      "version": "0.9.0",
-      "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz",
-      "integrity": "sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg==",
-      "dev": true,
-      "dependencies": {
-        "@babel/helper-define-polyfill-provider": "^0.5.0",
-        "core-js-compat": "^3.34.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
-      }
-    },
-    "node_modules/dotenv-expand": {
-      "version": "5.1.0",
-      "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
-      "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==",
-      "dev": true
-    },
-    "node_modules/@swc/helpers": {
-      "version": "0.5.3",
-      "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.3.tgz",
-      "integrity": "sha512-FaruWX6KdudYloq1AHD/4nU+UsMTdNE8CKyrseXWEcgjDAbvkwJg2QGPAnfIJLIWsjZOSPLOAykK6fuYp4vp4A==",
-      "dev": true,
-      "dependencies": {
-        "tslib": "^2.4.0"
-      }
-    },
-    "node_modules/@parcel/packager-js/node_modules/globals": {
-      "version": "13.24.0",
-      "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
-      "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
-      "dev": true,
-      "dependencies": {
-        "type-fest": "^0.20.2"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/@parcel/codeframe/node_modules/color-convert": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-      "dev": true,
-      "dependencies": {
-        "color-name": "~1.1.4"
-      },
-      "engines": {
-        "node": ">=7.0.0"
-      }
-    },
-    "node_modules/@babel/helper-validator-identifier": {
-      "version": "7.22.20",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
-      "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
-      "dev": true,
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/json-parse-even-better-errors": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
-      "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
-      "dev": true
-    },
-    "node_modules/@parcel/node-resolver-core": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/@parcel/node-resolver-core/-/node-resolver-core-3.2.0.tgz",
-      "integrity": "sha512-XJRSxCkNbGFWjfmwFdcQZ/qlzWZd35qLtvLz2va8euGL7M5OMEQOv7dsvEhl0R+CC2zcnfFzZwxk78q6ezs8AQ==",
-      "dev": true,
-      "dependencies": {
-        "@mischnic/json-sourcemap": "^0.1.0",
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/fs": "2.11.0",
-        "@parcel/rust": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "nullthrows": "^1.1.1",
-        "semver": "^7.5.2"
-      },
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@parcel/transformer-css": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/transformer-css/-/transformer-css-2.11.0.tgz",
-      "integrity": "sha512-nFmBulF/ErNoafO87JbVrBavjBMNwE/kahbCRVxc2Mvlphz4F4lBW4eDRS5l4xBqFJaNkHr9R55ehLBBilF4Jw==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/plugin": "2.11.0",
-        "@parcel/source-map": "^2.1.1",
-        "@parcel/utils": "2.11.0",
-        "browserslist": "^4.6.6",
-        "lightningcss": "^1.22.1",
-        "nullthrows": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/buffer": {
-      "version": "6.0.3",
-      "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
-      "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/feross"
-        },
-        {
-          "type": "patreon",
-          "url": "https://www.patreon.com/feross"
-        },
-        {
-          "type": "consulting",
-          "url": "https://feross.org/support"
-        }
-      ],
-      "dependencies": {
-        "base64-js": "^1.3.1",
-        "ieee754": "^1.2.1"
-      }
-    },
-    "node_modules/@parcel/rust": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/rust/-/rust-2.11.0.tgz",
-      "integrity": "sha512-UkLWdHOD8Md2YmJDPsqd3yIs9chhdl/ATfV/B/xdPKGmqtNouYpDCRlq+WxMt3mLoYgHEg9UwrWLTebo2rr2iQ==",
-      "dev": true,
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/callsites": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
-      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/@parcel/package-manager/node_modules/lru-cache": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-      "dev": true,
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@parcel/utils/node_modules/chalk": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-      "dev": true,
-      "dependencies": {
-        "ansi-styles": "^4.1.0",
-        "supports-color": "^7.1.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/chalk?sponsor=1"
-      }
-    },
-    "node_modules/@parcel/packager-wasm": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/packager-wasm/-/packager-wasm-2.11.0.tgz",
-      "integrity": "sha512-tTy4EbDXeeiZ0oB7L2FWaHSD1mbmYZP6R5HXqkvc5dECGUKPU5Jz6ek2C5AM+HfQdQLKXPQ/Xw3eJnI/AmctVg==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/plugin": "2.11.0"
-      },
-      "engines": {
-        "node": ">=12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/entities": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz",
-      "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.12"
-      },
-      "funding": {
-        "url": "https://github.com/fb55/entities?sponsor=1"
-      }
-    },
-    "node_modules/@types/scheduler": {
-      "version": "0.16.5",
-      "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.5.tgz",
-      "integrity": "sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw=="
-    },
-    "node_modules/@parcel/watcher-win32-x64": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.4.0.tgz",
-      "integrity": "sha512-pAUyUVjfFjWaf/pShmJpJmNxZhbMvJASUpdes9jL6bTEJ+gDxPRSpXTIemNyNsb9AtbiGXs9XduP1reThmd+dA==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">= 10.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/domutils": {
-      "version": "2.8.0",
-      "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
-      "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
-      "dev": true,
-      "dependencies": {
-        "dom-serializer": "^1.0.1",
-        "domelementtype": "^2.2.0",
-        "domhandler": "^4.2.0"
-      },
-      "funding": {
-        "url": "https://github.com/fb55/domutils?sponsor=1"
-      }
-    },
-    "node_modules/@swc/core-darwin-arm64": {
-      "version": "1.3.107",
-      "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.107.tgz",
-      "integrity": "sha512-47tD/5vSXWxPd0j/ZllyQUg4bqalbQTsmqSw0J4dDdS82MWqCAwUErUrAZPRjBkjNQ6Kmrf5rpCWaGTtPw+ngw==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@swc/core-linux-arm64-gnu": {
-      "version": "1.3.107",
-      "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.107.tgz",
-      "integrity": "sha512-HWgnn7JORYlOYnGsdunpSF8A+BCZKPLzLtEUA27/M/ZuANcMZabKL9Zurt7XQXq888uJFAt98Gy+59PU90aHKg==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@parcel/codeframe/node_modules/ansi-styles": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-      "dev": true,
-      "dependencies": {
-        "color-convert": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
-      }
-    },
-    "node_modules/@parcel/node-resolver-core/node_modules/lru-cache": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-      "dev": true,
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/domhandler": {
-      "version": "4.3.1",
-      "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz",
-      "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
-      "dev": true,
-      "dependencies": {
-        "domelementtype": "^2.2.0"
-      },
-      "engines": {
-        "node": ">= 4"
-      },
-      "funding": {
-        "url": "https://github.com/fb55/domhandler?sponsor=1"
-      }
-    },
-    "node_modules/tiny-warning": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
-      "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
-    },
-    "node_modules/dom-helpers/node_modules/csstype": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
-      "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
-    },
-    "node_modules/@parcel/package-manager": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/package-manager/-/package-manager-2.11.0.tgz",
-      "integrity": "sha512-QzdsrUYlAwIzb8by7WJjqYnbR1MoMKWbtE1MXUeYsZbFusV8B6pOH+lwqNJKS/BFtddZMRPYFueZS2N2fwzjig==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/fs": "2.11.0",
-        "@parcel/logger": "2.11.0",
-        "@parcel/node-resolver-core": "3.2.0",
-        "@parcel/types": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "@parcel/workers": "2.11.0",
-        "semver": "^7.5.2"
-      },
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      },
-      "peerDependencies": {
-        "@parcel/core": "^2.11.0"
-      }
-    },
-    "node_modules/parcel/node_modules/color-convert": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-      "dev": true,
-      "dependencies": {
-        "color-name": "~1.1.4"
-      },
-      "engines": {
-        "node": ">=7.0.0"
-      }
-    },
-    "node_modules/babel-plugin-polyfill-regenerator": {
-      "version": "0.5.5",
-      "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz",
-      "integrity": "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==",
-      "dev": true,
-      "dependencies": {
-        "@babel/helper-define-polyfill-provider": "^0.5.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
-      }
-    },
-    "node_modules/@parcel/core/node_modules/semver": {
-      "version": "7.5.4",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
-      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
-      "dev": true,
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@parcel/graph": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/@parcel/graph/-/graph-3.1.0.tgz",
-      "integrity": "sha512-d1dTW5C7A52HgDtoXlyvlET1ypSlmIxSIZOJ1xp3R9L9hgo3h1u3jHNyaoTe/WPkGVe2QnFxh0h+UibVJhu9vg==",
-      "dev": true,
-      "dependencies": {
-        "nullthrows": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/mime-types": {
-      "version": "2.1.35",
-      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
-      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
-      "dependencies": {
-        "mime-db": "1.52.0"
-      },
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/date-fns": {
-      "version": "2.30.0",
-      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
-      "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
-      "dependencies": {
-        "@babel/runtime": "^7.21.0"
-      },
-      "engines": {
-        "node": ">=0.11"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/date-fns"
-      }
-    },
-    "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz",
-      "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==",
-      "dev": true,
-      "dependencies": {
-        "@babel/types": "^7.22.5"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/lodash.debounce": {
-      "version": "4.0.8",
-      "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
-      "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
-      "dev": true
-    },
-    "node_modules/react-smooth/node_modules/dom-helpers": {
-      "version": "3.4.0",
-      "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
-      "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
-      "dependencies": {
-        "@babel/runtime": "^7.1.2"
-      }
-    },
-    "node_modules/axios": {
-      "version": "1.6.0",
-      "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz",
-      "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==",
-      "dependencies": {
-        "follow-redirects": "^1.15.0",
-        "form-data": "^4.0.0",
-        "proxy-from-env": "^1.1.0"
-      }
-    },
-    "node_modules/abortcontroller-polyfill": {
-      "version": "1.7.5",
-      "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz",
-      "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==",
-      "dev": true
-    },
-    "node_modules/@jridgewell/gen-mapping": {
-      "version": "0.3.3",
-      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
-      "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
-      "dev": true,
-      "dependencies": {
-        "@jridgewell/set-array": "^1.0.1",
-        "@jridgewell/sourcemap-codec": "^1.4.10",
-        "@jridgewell/trace-mapping": "^0.3.9"
-      },
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
-    "node_modules/posthtml/node_modules/posthtml-parser": {
-      "version": "0.11.0",
-      "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz",
-      "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==",
-      "dev": true,
-      "dependencies": {
-        "htmlparser2": "^7.1.1"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/postcss-value-parser": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
-      "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
-      "dev": true
-    },
-    "node_modules/@swc/core-linux-arm-gnueabihf": {
-      "version": "1.3.107",
-      "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.107.tgz",
-      "integrity": "sha512-I2wzcC0KXqh0OwymCmYwNRgZ9nxX7DWnOOStJXV3pS0uB83TXAkmqd7wvMBuIl9qu4Hfomi9aDM7IlEEn9tumQ==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/html-to-react/node_modules/dom-serializer": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
-      "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
-      "dependencies": {
-        "domelementtype": "^2.3.0",
-        "domhandler": "^5.0.2",
-        "entities": "^4.2.0"
-      },
-      "funding": {
-        "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
-      }
-    },
-    "node_modules/@parcel/reporter-cli": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/reporter-cli/-/reporter-cli-2.11.0.tgz",
-      "integrity": "sha512-hY0iO0f+LifgJHDUIjGQJnxLFSkk2jlbfy+kIaft5oI3/IM+UljecfGO+14XH8mYlqRXXPsT09TJe8ZKQzp4ZQ==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/plugin": "2.11.0",
-        "@parcel/types": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "chalk": "^4.1.0",
-        "cli-progress": "^3.12.0",
-        "term-size": "^2.2.1"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/react-refresh": {
-      "version": "0.9.0",
-      "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.9.0.tgz",
-      "integrity": "sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/@parcel/reporter-cli/node_modules/has-flag": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/@babel/types": {
-      "version": "7.23.0",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
-      "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
-      "dev": true,
-      "dependencies": {
-        "@babel/helper-string-parser": "^7.22.5",
-        "@babel/helper-validator-identifier": "^7.22.20",
-        "to-fast-properties": "^2.0.0"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/template": {
-      "version": "7.22.15",
-      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
-      "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
-      "dev": true,
-      "dependencies": {
-        "@babel/code-frame": "^7.22.13",
-        "@babel/parser": "^7.22.15",
-        "@babel/types": "^7.22.15"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/compat-data": {
-      "version": "7.23.2",
-      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz",
-      "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/html-to-react/node_modules/domhandler": {
-      "version": "5.0.3",
-      "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
-      "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
-      "dependencies": {
-        "domelementtype": "^2.3.0"
-      },
-      "engines": {
-        "node": ">= 4"
-      },
-      "funding": {
-        "url": "https://github.com/fb55/domhandler?sponsor=1"
-      }
-    },
-    "node_modules/@parcel/workers": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/workers/-/workers-2.11.0.tgz",
-      "integrity": "sha512-wjybqdSy6Nk0N9iBGsFcp7739W2zvx0WGfVxPVShqhz46pIkPOiFF/iSn+kFu5EmMKTRWeUif42+a6rRZ7pCnQ==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/logger": "2.11.0",
-        "@parcel/profiler": "2.11.0",
-        "@parcel/types": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "nullthrows": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      },
-      "peerDependencies": {
-        "@parcel/core": "^2.11.0"
-      }
-    },
-    "node_modules/@parcel/transformer-babel/node_modules/lru-cache": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-      "dev": true,
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@parcel/optimizer-htmlnano": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.11.0.tgz",
-      "integrity": "sha512-c20pz4EFF5DNFmqYgptlIj49eT6xjGLkDTdHH3RRzxKovuSXWfYSPs3GED3ZsjVuQyjNQif+/MAk9547F7hrdQ==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/plugin": "2.11.0",
-        "htmlnano": "^2.0.0",
-        "nullthrows": "^1.1.1",
-        "posthtml": "^0.16.5",
-        "svgo": "^2.4.0"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/lightningcss-freebsd-x64": {
-      "version": "1.23.0",
-      "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.23.0.tgz",
-      "integrity": "sha512-xhnhf0bWPuZxcqknvMDRFFo2TInrmQRWZGB0f6YoAsZX8Y+epfjHeeOIGCfAmgF0DgZxHwYc8mIR5tQU9/+ROA==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "freebsd"
-      ],
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/update-browserslist-db": {
-      "version": "1.0.13",
-      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
-      "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "opencollective",
-          "url": "https://opencollective.com/browserslist"
-        },
-        {
-          "type": "tidelift",
-          "url": "https://tidelift.com/funding/github/npm/browserslist"
-        },
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/ai"
-        }
-      ],
-      "dependencies": {
-        "escalade": "^3.1.1",
-        "picocolors": "^1.0.0"
-      },
-      "bin": {
-        "update-browserslist-db": "cli.js"
-      },
-      "peerDependencies": {
-        "browserslist": ">= 4.21.0"
-      }
-    },
-    "node_modules/@trysound/sax": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
-      "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
-      "dev": true,
-      "engines": {
-        "node": ">=10.13.0"
-      }
-    },
-    "node_modules/@parcel/optimizer-css": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/optimizer-css/-/optimizer-css-2.11.0.tgz",
-      "integrity": "sha512-bV97PRxshHV3dMwOpLRgcP1QNhrVWh6VVDfm2gmWULpvsjoykcPS6vrCFksY5CpQsSvNHqJBzQjWS8FubUI76w==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/plugin": "2.11.0",
-        "@parcel/source-map": "^2.1.1",
-        "@parcel/utils": "2.11.0",
-        "browserslist": "^4.6.6",
-        "lightningcss": "^1.22.1",
-        "nullthrows": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@lmdb/lmdb-linux-x64": {
-      "version": "2.8.5",
-      "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-2.8.5.tgz",
-      "integrity": "sha512-Xkc8IUx9aEhP0zvgeKy7IQ3ReX2N8N1L0WPcQwnZweWmOuKfwpS3GRIYqLtK5za/w3E60zhFfNdS+3pBZPytqQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/babel-plugin-polyfill-corejs2": {
-      "version": "0.4.8",
-      "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz",
-      "integrity": "sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg==",
-      "dev": true,
-      "dependencies": {
-        "@babel/compat-data": "^7.22.6",
-        "@babel/helper-define-polyfill-provider": "^0.5.0",
-        "semver": "^6.3.1"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
-      }
-    },
-    "node_modules/electron-to-chromium": {
-      "version": "1.4.650",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.650.tgz",
-      "integrity": "sha512-sYSQhJCJa4aGA1wYol5cMQgekDBlbVfTRavlGZVr3WZpDdOPcp6a6xUnFfrt8TqZhsBYYbDxJZCjGfHuGupCRQ==",
-      "dev": true
-    },
-    "node_modules/@babel/parser": {
-      "version": "7.23.0",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
-      "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
-      "dev": true,
-      "bin": {
-        "parser": "bin/babel-parser.js"
-      },
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
-    "node_modules/lines-and-columns": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
-      "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
-      "dev": true
-    },
-    "node_modules/lmdb": {
-      "version": "2.8.5",
-      "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-2.8.5.tgz",
-      "integrity": "sha512-9bMdFfc80S+vSldBmG3HOuLVHnxRdNTlpzR6QDnzqCQtCzGUEAGTzBKYMeIM+I/sU4oZfgbcbS7X7F65/z/oxQ==",
-      "dev": true,
-      "hasInstallScript": true,
-      "dependencies": {
-        "msgpackr": "^1.9.5",
-        "node-addon-api": "^6.1.0",
-        "node-gyp-build-optional-packages": "5.1.1",
-        "ordered-binary": "^1.4.1",
-        "weak-lru-cache": "^1.2.2"
-      },
-      "bin": {
-        "download-lmdb-prebuilds": "bin/download-prebuilds.js"
-      },
-      "optionalDependencies": {
-        "@lmdb/lmdb-darwin-arm64": "2.8.5",
-        "@lmdb/lmdb-darwin-x64": "2.8.5",
-        "@lmdb/lmdb-linux-arm": "2.8.5",
-        "@lmdb/lmdb-linux-arm64": "2.8.5",
-        "@lmdb/lmdb-linux-x64": "2.8.5",
-        "@lmdb/lmdb-win32-x64": "2.8.5"
-      }
-    },
-    "node_modules/@babel/helper-replace-supers": {
-      "version": "7.22.20",
-      "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz",
-      "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==",
-      "dev": true,
-      "dependencies": {
-        "@babel/helper-environment-visitor": "^7.22.20",
-        "@babel/helper-member-expression-to-functions": "^7.22.15",
-        "@babel/helper-optimise-call-expression": "^7.22.5"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0"
-      }
-    },
-    "node_modules/is-fullwidth-code-point": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
-      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.2.tgz",
-      "integrity": "sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ]
-    },
-    "node_modules/@material-ui/core": {
-      "version": "4.12.4",
-      "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.12.4.tgz",
-      "integrity": "sha512-tr7xekNlM9LjA6pagJmL8QCgZXaubWUwkJnoYcMKd4gw/t4XiyvnTkjdGrUVicyB2BsdaAv1tvow45bPM4sSwQ==",
-      "deprecated": "Material UI v4 doesn't receive active development since September 2021. See the guide https://mui.com/material-ui/migration/migration-v4/ to upgrade to v5.",
-      "dependencies": {
-        "@babel/runtime": "^7.4.4",
-        "@material-ui/styles": "^4.11.5",
-        "@material-ui/system": "^4.12.2",
-        "@material-ui/types": "5.1.0",
-        "@material-ui/utils": "^4.11.3",
-        "@types/react-transition-group": "^4.2.0",
-        "clsx": "^1.0.4",
-        "hoist-non-react-statics": "^3.3.2",
-        "popper.js": "1.16.1-lts",
-        "prop-types": "^15.7.2",
-        "react-is": "^16.8.0 || ^17.0.0",
-        "react-transition-group": "^4.4.0"
-      },
-      "engines": {
-        "node": ">=8.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/material-ui"
-      },
-      "peerDependencies": {
-        "@types/react": "^16.8.6 || ^17.0.0",
-        "react": "^16.8.0 || ^17.0.0",
-        "react-dom": "^16.8.0 || ^17.0.0"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@parcel/reporter-dev-server": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/reporter-dev-server/-/reporter-dev-server-2.11.0.tgz",
-      "integrity": "sha512-T4ue1+oLFNdcd9maw8QWQuxzOS2kX2jOrSvYKwYd9oGnqiAr1rpiHYYKJhHng+PF5ybwWkj8dUJfGh2NoQysJA==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/plugin": "2.11.0",
-        "@parcel/utils": "2.11.0"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/prop-types/node_modules/react-is": {
-      "version": "16.13.1",
-      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
-      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
-    },
-    "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.2.tgz",
-      "integrity": "sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@parcel/markdown-ansi/node_modules/chalk": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-      "dev": true,
-      "dependencies": {
-        "ansi-styles": "^4.1.0",
-        "supports-color": "^7.1.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/chalk?sponsor=1"
-      }
-    },
-    "node_modules/d3-time-format": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
-      "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
-      "dependencies": {
-        "d3-time": "1 - 3"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@parcel/optimizer-svgo/node_modules/mdn-data": {
-      "version": "2.0.14",
-      "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
-      "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==",
-      "dev": true
-    },
-    "node_modules/posthtml-parser": {
-      "version": "0.10.2",
-      "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.10.2.tgz",
-      "integrity": "sha512-PId6zZ/2lyJi9LiKfe+i2xv57oEjJgWbsHGGANwos5AvdQp98i6AtamAl8gzSVFGfQ43Glb5D614cvZf012VKg==",
-      "dev": true,
-      "dependencies": {
-        "htmlparser2": "^7.1.1"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/debug": {
-      "version": "4.3.4",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
-      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
-      "dev": true,
-      "dependencies": {
-        "ms": "2.1.2"
-      },
-      "engines": {
-        "node": ">=6.0"
-      },
-      "peerDependenciesMeta": {
-        "supports-color": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@babel/code-frame": {
-      "version": "7.22.13",
-      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
-      "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
-      "dev": true,
-      "dependencies": {
-        "@babel/highlight": "^7.22.13",
-        "chalk": "^2.4.2"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/highlight": {
-      "version": "7.22.20",
-      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
-      "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
-      "dev": true,
-      "dependencies": {
-        "@babel/helper-validator-identifier": "^7.22.20",
-        "chalk": "^2.4.2",
-        "js-tokens": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/jss/node_modules/csstype": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
-      "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
-    },
-    "node_modules/commander": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
-      "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
-      "dev": true,
-      "engines": {
-        "node": ">= 10"
-      }
-    },
-    "node_modules/@types/d3-ease": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.1.tgz",
-      "integrity": "sha512-VZofjpEt8HWv3nxUAosj5o/+4JflnJ7Bbv07k17VO3T2WRuzGdZeookfaF60iVh5RdhVG49LE5w6LIshVUC6rg=="
-    },
-    "node_modules/@parcel/watcher-linux-arm64-musl": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.4.0.tgz",
-      "integrity": "sha512-oyN+uA9xcTDo/45bwsd6TFHa7Lc7hKujyMlvwrCLvSckvWogndCEoVYFNfZ6JJ2KNL/6fFiGPcbjp8jJmEh5Ng==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">= 10.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/react-smooth": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.5.tgz",
-      "integrity": "sha512-BMP2Ad42tD60h0JW6BFaib+RJuV5dsXJK9Baxiv/HlNFjvRLqA9xrNKxVWnUIZPQfzUwGXIlU/dSYLU+54YGQA==",
-      "dependencies": {
-        "fast-equals": "^5.0.0",
-        "react-transition-group": "2.9.0"
-      },
-      "peerDependencies": {
-        "prop-types": "^15.6.0",
-        "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
-        "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
-      }
-    },
-    "node_modules/is-plain-object": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
-      "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
-      "dev": true,
-      "dependencies": {
-        "isobject": "^3.0.1"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/@babel/helper-define-polyfill-provider": {
-      "version": "0.5.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz",
-      "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==",
-      "dev": true,
-      "dependencies": {
-        "@babel/helper-compilation-targets": "^7.22.6",
-        "@babel/helper-plugin-utils": "^7.22.5",
-        "debug": "^4.1.1",
-        "lodash.debounce": "^4.0.8",
-        "resolve": "^1.14.2"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
-      }
-    },
-    "node_modules/@swc/types": {
-      "version": "0.1.5",
-      "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz",
-      "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==",
-      "dev": true
-    },
-    "node_modules/isobject": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
-      "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/@babel/helper-annotate-as-pure": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz",
-      "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==",
-      "dev": true,
-      "dependencies": {
-        "@babel/types": "^7.22.5"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/lightningcss-darwin-arm64": {
-      "version": "1.23.0",
-      "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.23.0.tgz",
-      "integrity": "sha512-kl4Pk3Q2lnE6AJ7Qaij47KNEfY2/UXRZBT/zqGA24B8qwkgllr/j7rclKOf1axcslNXvvUdztjo4Xqh39Yq1aA==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/svgo": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.2.0.tgz",
-      "integrity": "sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==",
-      "dev": true,
-      "optional": true,
-      "peer": true,
-      "dependencies": {
-        "@trysound/sax": "0.2.0",
-        "commander": "^7.2.0",
-        "css-select": "^5.1.0",
-        "css-tree": "^2.3.1",
-        "css-what": "^6.1.0",
-        "csso": "^5.0.5",
-        "picocolors": "^1.0.0"
-      },
-      "bin": {
-        "svgo": "bin/svgo"
-      },
-      "engines": {
-        "node": ">=14.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/svgo"
-      }
-    },
-    "node_modules/node-gyp-build-optional-packages": {
-      "version": "5.1.1",
-      "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz",
-      "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==",
-      "dev": true,
-      "dependencies": {
-        "detect-libc": "^2.0.1"
-      },
-      "bin": {
-        "node-gyp-build-optional-packages": "bin.js",
-        "node-gyp-build-optional-packages-optional": "optional.js",
-        "node-gyp-build-optional-packages-test": "build-test.js"
-      }
-    },
-    "node_modules/parent-module": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
-      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
-      "dev": true,
-      "dependencies": {
-        "callsites": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/color-name": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-      "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
-      "dev": true
-    },
-    "node_modules/@lmdb/lmdb-linux-arm": {
-      "version": "2.8.5",
-      "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-2.8.5.tgz",
-      "integrity": "sha512-c0TGMbm2M55pwTDIfkDLB6BpIsgxV4PjYck2HiOX+cy/JWiBXz32lYbarPqejKs9Flm7YVAKSILUducU9g2RVg==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/posthtml": {
-      "version": "0.16.6",
-      "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz",
-      "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==",
-      "dev": true,
-      "dependencies": {
-        "posthtml-parser": "^0.11.0",
-        "posthtml-render": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=12.0.0"
-      }
-    },
-    "node_modules/@parcel/transformer-raw": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/transformer-raw/-/transformer-raw-2.11.0.tgz",
-      "integrity": "sha512-2ltp3TgS+cxEqSM1vk5gDtJrYx4KMuRRtbSgSvkdldyOgPhflnLU3/HRz72hXSNGqYOV0/JN0+ocsfPnqR00ug==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/plugin": "2.11.0"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/parcel/node_modules/ansi-styles": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-      "dev": true,
-      "dependencies": {
-        "color-convert": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
-      }
-    },
-    "node_modules/@ampproject/remapping": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
-      "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
-      "dev": true,
-      "dependencies": {
-        "@jridgewell/gen-mapping": "^0.3.0",
-        "@jridgewell/trace-mapping": "^0.3.9"
-      },
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
-    "node_modules/@babel/helper-simple-access": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
-      "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
-      "dev": true,
-      "dependencies": {
-        "@babel/types": "^7.22.5"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/jss-plugin-rule-value-function": {
-      "version": "10.10.0",
-      "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.10.0.tgz",
-      "integrity": "sha512-uEFJFgaCtkXeIPgki8ICw3Y7VMkL9GEan6SqmT9tqpwM+/t+hxfMUdU4wQ0MtOiMNWhwnckBV0IebrKcZM9C0g==",
-      "dependencies": {
-        "@babel/runtime": "^7.3.1",
-        "jss": "10.10.0",
-        "tiny-warning": "^1.0.2"
-      }
-    },
-    "node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/chalk": {
-      "version": "2.4.2",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
-      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
-      "dev": true,
-      "dependencies": {
-        "ansi-styles": "^3.2.1",
-        "escape-string-regexp": "^1.0.5",
-        "supports-color": "^5.3.0"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/@babel/helper-module-transforms": {
-      "version": "7.23.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz",
-      "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==",
-      "dev": true,
-      "dependencies": {
-        "@babel/helper-environment-visitor": "^7.22.20",
-        "@babel/helper-module-imports": "^7.22.15",
-        "@babel/helper-simple-access": "^7.22.5",
-        "@babel/helper-split-export-declaration": "^7.22.6",
-        "@babel/helper-validator-identifier": "^7.22.20"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0"
-      }
-    },
-    "node_modules/@babel/plugin-transform-runtime": {
-      "version": "7.23.9",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.9.tgz",
-      "integrity": "sha512-A7clW3a0aSjm3ONU9o2HAILSegJCYlEZmOhmBRReVtIpY/Z/p7yIZ+wR41Z+UipwdGuqwtID/V/dOdZXjwi9gQ==",
-      "dev": true,
-      "dependencies": {
-        "@babel/helper-module-imports": "^7.22.15",
-        "@babel/helper-plugin-utils": "^7.22.5",
-        "babel-plugin-polyfill-corejs2": "^0.4.8",
-        "babel-plugin-polyfill-corejs3": "^0.9.0",
-        "babel-plugin-polyfill-regenerator": "^0.5.5",
-        "semver": "^6.3.1"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0-0"
-      }
-    },
-    "node_modules/value-equal": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
-      "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
-    },
-    "node_modules/@parcel/watcher-darwin-x64": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.4.0.tgz",
-      "integrity": "sha512-vZMv9jl+szz5YLsSqEGCMSllBl1gU1snfbRL5ysJU03MEa6gkVy9OMcvXV1j4g0++jHEcvzhs3Z3LpeEbVmY6Q==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">= 10.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/msgpackr-extract": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.2.tgz",
-      "integrity": "sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A==",
-      "dev": true,
-      "hasInstallScript": true,
-      "optional": true,
-      "dependencies": {
-        "node-gyp-build-optional-packages": "5.0.7"
-      },
-      "bin": {
-        "download-msgpackr-prebuilds": "bin/download-prebuilds.js"
-      },
-      "optionalDependencies": {
-        "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.2",
-        "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.2",
-        "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.2",
-        "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.2",
-        "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.2",
-        "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.2"
-      }
-    },
-    "node_modules/stable": {
-      "version": "0.1.8",
-      "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz",
-      "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==",
-      "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility",
-      "dev": true
-    },
-    "node_modules/@parcel/transformer-babel/node_modules/semver": {
-      "version": "7.5.4",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
-      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
-      "dev": true,
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.2.tgz",
-      "integrity": "sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ]
-    },
-    "node_modules/@swc/core-win32-x64-msvc": {
-      "version": "1.3.107",
-      "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.107.tgz",
-      "integrity": "sha512-Eyzo2XRqWOxqhE1gk9h7LWmUf4Bp4Xn2Ttb0ayAXFp6YSTxQIThXcT9kipXZqcpxcmDwoq8iWbbf2P8XL743EA==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@material-ui/system": {
-      "version": "4.12.2",
-      "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.12.2.tgz",
-      "integrity": "sha512-6CSKu2MtmiJgcCGf6nBQpM8fLkuB9F55EKfbdTC80NND5wpTmKzwdhLYLH3zL4cLlK0gVaaltW7/wMuyTnN0Lw==",
-      "dependencies": {
-        "@babel/runtime": "^7.4.4",
-        "@material-ui/utils": "^4.11.3",
-        "csstype": "^2.5.2",
-        "prop-types": "^15.7.2"
-      },
-      "engines": {
-        "node": ">=8.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/material-ui"
-      },
-      "peerDependencies": {
-        "@types/react": "^16.8.6 || ^17.0.0",
-        "react": "^16.8.0 || ^17.0.0",
-        "react-dom": "^16.8.0 || ^17.0.0"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@parcel/packager-js": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/packager-js/-/packager-js-2.11.0.tgz",
-      "integrity": "sha512-SxjCsd0xQfg5H73YtVJj9VOpr9s0rwMsSoeykjkatbkEla9NsZajsUkd/bfYf+/0WvEKOrB8oUBo15HkGOgKug==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/plugin": "2.11.0",
-        "@parcel/rust": "2.11.0",
-        "@parcel/source-map": "^2.1.1",
-        "@parcel/types": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "globals": "^13.2.0",
-        "nullthrows": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@types/d3-scale": {
-      "version": "4.0.6",
-      "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.6.tgz",
-      "integrity": "sha512-lo3oMLSiqsQUovv8j15X4BNEDOsnHuGjeVg7GRbAuB2PUa1prK5BNSOu6xixgNf3nqxPl4I1BqJWrPvFGlQoGQ==",
-      "dependencies": {
-        "@types/d3-time": "*"
-      }
-    },
-    "node_modules/@lmdb/lmdb-win32-x64": {
-      "version": "2.8.5",
-      "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-2.8.5.tgz",
-      "integrity": "sha512-4wvrf5BgnR8RpogHhtpCPJMKBmvyZPhhUtEwMJbXh0ni2BucpfF07jlmyM11zRqQ2XIq6PbC2j7W7UCCcm1rRQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ]
-    },
-    "node_modules/node-releases": {
-      "version": "2.0.14",
-      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
-      "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
-      "dev": true
-    },
-    "node_modules/@jridgewell/trace-mapping": {
-      "version": "0.3.20",
-      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz",
-      "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==",
-      "dev": true,
-      "dependencies": {
-        "@jridgewell/resolve-uri": "^3.1.0",
-        "@jridgewell/sourcemap-codec": "^1.4.14"
-      }
-    },
-    "node_modules/@parcel/transformer-postcss/node_modules/semver": {
-      "version": "7.5.4",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
-      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
-      "dev": true,
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/react-dom": {
-      "version": "17.0.2",
-      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
-      "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
-      "dependencies": {
-        "loose-envify": "^1.1.0",
-        "object-assign": "^4.1.1",
-        "scheduler": "^0.20.2"
-      },
-      "peerDependencies": {
-        "react": "17.0.2"
-      }
-    },
-    "node_modules/picomatch": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
-      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
-      "dev": true,
-      "engines": {
-        "node": ">=8.6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/jonschlinkert"
-      }
-    },
-    "node_modules/parcel/node_modules/color-name": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-      "dev": true
-    },
-    "node_modules/lodash.camelcase": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
-      "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="
-    },
-    "node_modules/to-fast-properties": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
-      "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
-      "dev": true,
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/base64-js": {
-      "version": "1.5.1",
-      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
-      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/feross"
-        },
-        {
-          "type": "patreon",
-          "url": "https://www.patreon.com/feross"
-        },
-        {
-          "type": "consulting",
-          "url": "https://feross.org/support"
-        }
-      ]
-    },
-    "node_modules/react": {
-      "version": "17.0.2",
-      "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
-      "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
-      "dependencies": {
-        "loose-envify": "^1.1.0",
-        "object-assign": "^4.1.1"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/@lmdb/lmdb-darwin-x64": {
-      "version": "2.8.5",
-      "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-2.8.5.tgz",
-      "integrity": "sha512-w/sLhN4T7MW1nB3R/U8WK5BgQLz904wh+/SmA2jD8NnF7BLLoUgflCNxOeSPOWp8geP6nP/+VjWzZVip7rZ1ug==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ]
-    },
-    "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.2.tgz",
-      "integrity": "sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/weak-lru-cache": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz",
-      "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==",
-      "dev": true
-    },
-    "node_modules/@babel/helper-compilation-targets": {
-      "version": "7.22.15",
-      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz",
-      "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==",
-      "dev": true,
-      "dependencies": {
-        "@babel/compat-data": "^7.22.9",
-        "@babel/helper-validator-option": "^7.22.15",
-        "browserslist": "^4.21.9",
-        "lru-cache": "^5.1.1",
-        "semver": "^6.3.1"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/function-bind": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
-      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
-      "dev": true,
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/chrome-trace-event": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
-      "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==",
-      "dev": true,
-      "engines": {
-        "node": ">=6.0"
-      }
-    },
-    "node_modules/@parcel/transformer-babel": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/transformer-babel/-/transformer-babel-2.11.0.tgz",
-      "integrity": "sha512-WKGblnp7r426VG+cpeQzc6dj/30EoUaYwyl4OEaigQSJizyuPWTBWTz6FUw+ih1/sg37h+D1BIh9C2FsVzpzbw==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/plugin": "2.11.0",
-        "@parcel/source-map": "^2.1.1",
-        "@parcel/utils": "2.11.0",
-        "browserslist": "^4.6.6",
-        "json5": "^2.2.0",
-        "nullthrows": "^1.1.1",
-        "semver": "^7.5.2"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@parcel/transformer-html": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/transformer-html/-/transformer-html-2.11.0.tgz",
-      "integrity": "sha512-90vp7mbvvfqPr9XIINpMcELtywj56f1bxfOkLQgWU1bm22H0FT3i5dqdac++2My0IGDvMwhAEjQfbn4pA579NQ==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/plugin": "2.11.0",
-        "@parcel/rust": "2.11.0",
-        "nullthrows": "^1.1.1",
-        "posthtml": "^0.16.5",
-        "posthtml-parser": "^0.10.1",
-        "posthtml-render": "^3.0.0",
-        "semver": "^7.5.2",
-        "srcset": "4"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@babel/preset-react": {
-      "version": "7.22.15",
-      "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.22.15.tgz",
-      "integrity": "sha512-Csy1IJ2uEh/PecCBXXoZGAZBeCATTuePzCSB7dLYWS0vOEj6CNpjxIhW4duWwZodBNueH7QO14WbGn8YyeuN9w==",
-      "dev": true,
-      "dependencies": {
-        "@babel/helper-plugin-utils": "^7.22.5",
-        "@babel/helper-validator-option": "^7.22.15",
-        "@babel/plugin-transform-react-display-name": "^7.22.5",
-        "@babel/plugin-transform-react-jsx": "^7.22.15",
-        "@babel/plugin-transform-react-jsx-development": "^7.22.5",
-        "@babel/plugin-transform-react-pure-annotations": "^7.22.5"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0-0"
-      }
-    },
-    "node_modules/@parcel/reporter-tracer": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/reporter-tracer/-/reporter-tracer-2.11.0.tgz",
-      "integrity": "sha512-33q4ftO26OPWHkUpEm0bzzSjW2kHEh6q/JFePwf8W6APTQVruj4mV46+Fh6rxX42ixs92K/QoiE0gYgWZQVDHA==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/plugin": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "chrome-trace-event": "^1.0.3",
-        "nullthrows": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/css-select/node_modules/dom-serializer": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
-      "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
-      "dev": true,
-      "optional": true,
-      "peer": true,
-      "dependencies": {
-        "domelementtype": "^2.3.0",
-        "domhandler": "^5.0.2",
-        "entities": "^4.2.0"
-      },
-      "funding": {
-        "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
-      }
-    },
-    "node_modules/@parcel/profiler": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/profiler/-/profiler-2.11.0.tgz",
-      "integrity": "sha512-s10SS09prOdwnaAcjK8M5zO8o+zPJJW5oOqXPNdf6KH4NGD/ue7iOk2xM8QLw6ulSwxE7NDt++lyfW3AXgCZwg==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/events": "2.11.0",
-        "chrome-trace-event": "^1.0.2"
-      },
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/css-select": {
-      "version": "5.1.0",
-      "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
-      "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
-      "dev": true,
-      "optional": true,
-      "peer": true,
-      "dependencies": {
-        "boolbase": "^1.0.0",
-        "css-what": "^6.1.0",
-        "domhandler": "^5.0.2",
-        "domutils": "^3.0.1",
-        "nth-check": "^2.0.1"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/fb55"
-      }
-    },
-    "node_modules/source-map": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/is-arrayish": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
-      "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
-      "dev": true
-    },
-    "node_modules/react-smooth/node_modules/react-transition-group": {
-      "version": "2.9.0",
-      "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
-      "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
-      "dependencies": {
-        "dom-helpers": "^3.4.0",
-        "loose-envify": "^1.4.0",
-        "prop-types": "^15.6.2",
-        "react-lifecycles-compat": "^3.0.4"
-      },
-      "peerDependencies": {
-        "react": ">=15.0.0",
-        "react-dom": ">=15.0.0"
-      }
-    },
-    "node_modules/error-ex": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
-      "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
-      "dev": true,
-      "dependencies": {
-        "is-arrayish": "^0.2.1"
-      }
-    },
-    "node_modules/utility-types": {
-      "version": "3.11.0",
-      "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz",
-      "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==",
-      "dev": true,
-      "engines": {
-        "node": ">= 4"
-      }
-    },
-    "node_modules/@swc/core": {
-      "version": "1.3.107",
-      "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.107.tgz",
-      "integrity": "sha512-zKhqDyFcTsyLIYK1iEmavljZnf4CCor5pF52UzLAz4B6Nu/4GLU+2LQVAf+oRHjusG39PTPjd2AlRT3f3QWfsQ==",
-      "dev": true,
-      "hasInstallScript": true,
-      "dependencies": {
-        "@swc/counter": "^0.1.1",
-        "@swc/types": "^0.1.5"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/swc"
-      },
-      "optionalDependencies": {
-        "@swc/core-darwin-arm64": "1.3.107",
-        "@swc/core-darwin-x64": "1.3.107",
-        "@swc/core-linux-arm-gnueabihf": "1.3.107",
-        "@swc/core-linux-arm64-gnu": "1.3.107",
-        "@swc/core-linux-arm64-musl": "1.3.107",
-        "@swc/core-linux-x64-gnu": "1.3.107",
-        "@swc/core-linux-x64-musl": "1.3.107",
-        "@swc/core-win32-arm64-msvc": "1.3.107",
-        "@swc/core-win32-ia32-msvc": "1.3.107",
-        "@swc/core-win32-x64-msvc": "1.3.107"
-      },
-      "peerDependencies": {
-        "@swc/helpers": "^0.5.0"
-      },
-      "peerDependenciesMeta": {
-        "@swc/helpers": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/msgpackr": {
-      "version": "1.10.1",
-      "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.10.1.tgz",
-      "integrity": "sha512-r5VRLv9qouXuLiIBrLpl2d5ZvPt8svdQTl5/vMvE4nzDMyEX4sgW5yWhuBBj5UmgwOTWj8CIdSXn5sAfsHAWIQ==",
-      "dev": true,
-      "optionalDependencies": {
-        "msgpackr-extract": "^3.0.2"
-      }
-    },
-    "node_modules/@parcel/transformer-js/node_modules/semver": {
-      "version": "7.5.4",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
-      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
-      "dev": true,
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/mime-db": {
-      "version": "1.52.0",
-      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
-      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/jss-plugin-vendor-prefixer": {
-      "version": "10.10.0",
-      "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.10.0.tgz",
-      "integrity": "sha512-UY/41WumgjW8r1qMCO8l1ARg7NHnfRVWRhZ2E2m0DMYsr2DD91qIXLyNhiX83hHswR7Wm4D+oDYNC1zWCJWtqg==",
-      "dependencies": {
-        "@babel/runtime": "^7.3.1",
-        "css-vendor": "^2.0.8",
-        "jss": "10.10.0"
-      }
-    },
-    "node_modules/htmlnano": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.0.tgz",
-      "integrity": "sha512-jVGRE0Ep9byMBKEu0Vxgl8dhXYOUk0iNQ2pjsG+BcRB0u0oDF5A9p/iBGMg/PGKYUyMD0OAGu8dVT5Lzj8S58g==",
-      "dev": true,
-      "dependencies": {
-        "cosmiconfig": "^8.0.0",
-        "posthtml": "^0.16.5",
-        "timsort": "^0.3.0"
-      },
-      "peerDependencies": {
-        "cssnano": "^6.0.0",
-        "postcss": "^8.3.11",
-        "purgecss": "^5.0.0",
-        "relateurl": "^0.2.7",
-        "srcset": "4.0.0",
-        "svgo": "^3.0.2",
-        "terser": "^5.10.0",
-        "uncss": "^0.17.3"
-      },
-      "peerDependenciesMeta": {
-        "cssnano": {
-          "optional": true
-        },
-        "postcss": {
-          "optional": true
-        },
-        "purgecss": {
-          "optional": true
-        },
-        "relateurl": {
-          "optional": true
-        },
-        "srcset": {
-          "optional": true
-        },
-        "svgo": {
-          "optional": true
-        },
-        "terser": {
-          "optional": true
-        },
-        "uncss": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/recharts-scale": {
-      "version": "0.4.5",
-      "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
-      "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
-      "dependencies": {
-        "decimal.js-light": "^2.4.1"
-      }
-    },
-    "node_modules/@parcel/codeframe/node_modules/chalk": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-      "dev": true,
-      "dependencies": {
-        "ansi-styles": "^4.1.0",
-        "supports-color": "^7.1.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/chalk?sponsor=1"
-      }
-    },
-    "node_modules/@parcel/reporter-cli/node_modules/color-name": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-      "dev": true
-    },
-    "node_modules/resolve": {
-      "version": "1.22.8",
-      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
-      "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
-      "dev": true,
-      "dependencies": {
-        "is-core-module": "^2.13.0",
-        "path-parse": "^1.0.7",
-        "supports-preserve-symlinks-flag": "^1.0.0"
-      },
-      "bin": {
-        "resolve": "bin/resolve"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/@parcel/fs": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/fs/-/fs-2.11.0.tgz",
-      "integrity": "sha512-zWckdnnovdrgdFX4QYuQV4bbKCsh6IYCkmwaB4yp47rhw1MP0lkBINLt4yFPHBxWXOpElCfxjL+z69c9xJQRBQ==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/rust": "2.11.0",
-        "@parcel/types": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "@parcel/watcher": "^2.0.7",
-        "@parcel/workers": "2.11.0"
-      },
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      },
-      "peerDependencies": {
-        "@parcel/core": "^2.11.0"
-      }
-    },
-    "node_modules/@lezer/common": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz",
-      "integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==",
-      "dev": true
-    },
-    "node_modules/@parcel/optimizer-htmlnano/node_modules/mdn-data": {
-      "version": "2.0.14",
-      "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
-      "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==",
-      "dev": true
-    },
-    "node_modules/@parcel/runtime-browser-hmr": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.11.0.tgz",
-      "integrity": "sha512-uVwNBtoLMrlPHLvRS05BVhLseduMOpZT36yiIjS0YSBJcC6/otI9AY7ZiDPYmrB5xTqM0R+D554JhPaJHCuocw==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/plugin": "2.11.0",
-        "@parcel/utils": "2.11.0"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/is-in-browser": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
-      "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g=="
-    },
-    "node_modules/css-select/node_modules/domutils": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
-      "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
-      "dev": true,
-      "optional": true,
-      "peer": true,
-      "dependencies": {
-        "dom-serializer": "^2.0.0",
-        "domelementtype": "^2.3.0",
-        "domhandler": "^5.0.3"
-      },
-      "funding": {
-        "url": "https://github.com/fb55/domutils?sponsor=1"
-      }
-    },
-    "node_modules/@parcel/watcher": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.0.tgz",
-      "integrity": "sha512-XJLGVL0DEclX5pcWa2N9SX1jCGTDd8l972biNooLFtjneuGqodupPQh6XseXIBBeVIMaaJ7bTcs3qGvXwsp4vg==",
-      "dev": true,
-      "hasInstallScript": true,
-      "dependencies": {
-        "detect-libc": "^1.0.3",
-        "is-glob": "^4.0.3",
-        "micromatch": "^4.0.5",
-        "node-addon-api": "^7.0.0"
-      },
-      "engines": {
-        "node": ">= 10.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      },
-      "optionalDependencies": {
-        "@parcel/watcher-android-arm64": "2.4.0",
-        "@parcel/watcher-darwin-arm64": "2.4.0",
-        "@parcel/watcher-darwin-x64": "2.4.0",
-        "@parcel/watcher-freebsd-x64": "2.4.0",
-        "@parcel/watcher-linux-arm-glibc": "2.4.0",
-        "@parcel/watcher-linux-arm64-glibc": "2.4.0",
-        "@parcel/watcher-linux-arm64-musl": "2.4.0",
-        "@parcel/watcher-linux-x64-glibc": "2.4.0",
-        "@parcel/watcher-linux-x64-musl": "2.4.0",
-        "@parcel/watcher-win32-arm64": "2.4.0",
-        "@parcel/watcher-win32-ia32": "2.4.0",
-        "@parcel/watcher-win32-x64": "2.4.0"
-      }
-    },
-    "node_modules/string-width": {
-      "version": "4.2.3",
-      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-      "dev": true,
-      "dependencies": {
-        "emoji-regex": "^8.0.0",
-        "is-fullwidth-code-point": "^3.0.0",
-        "strip-ansi": "^6.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/@babel/generator": {
-      "version": "7.23.0",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
-      "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
-      "dev": true,
-      "dependencies": {
-        "@babel/types": "^7.23.0",
-        "@jridgewell/gen-mapping": "^0.3.2",
-        "@jridgewell/trace-mapping": "^0.3.17",
-        "jsesc": "^2.5.1"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/dom-helpers": {
-      "version": "5.2.1",
-      "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
-      "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
-      "dependencies": {
-        "@babel/runtime": "^7.8.7",
-        "csstype": "^3.0.2"
-      }
-    },
-    "node_modules/@parcel/reporter-cli/node_modules/chalk": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-      "dev": true,
-      "dependencies": {
-        "ansi-styles": "^4.1.0",
-        "supports-color": "^7.1.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/chalk?sponsor=1"
-      }
-    },
-    "node_modules/@babel/helpers": {
-      "version": "7.23.2",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz",
-      "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==",
-      "dev": true,
-      "dependencies": {
-        "@babel/template": "^7.22.15",
-        "@babel/traverse": "^7.23.2",
-        "@babel/types": "^7.23.0"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/jss-plugin-props-sort": {
-      "version": "10.10.0",
-      "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.10.0.tgz",
-      "integrity": "sha512-5VNJvQJbnq/vRfje6uZLe/FyaOpzP/IH1LP+0fr88QamVrGJa0hpRRyAa0ea4U/3LcorJfBFVyC4yN2QC73lJg==",
-      "dependencies": {
-        "@babel/runtime": "^7.3.1",
-        "jss": "10.10.0"
-      }
-    },
-    "node_modules/@parcel/watcher-linux-x64-glibc": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.4.0.tgz",
-      "integrity": "sha512-KphV8awJmxU3q52JQvJot0QMu07CIyEjV+2Tb2ZtbucEgqyRcxOBDMsqp1JNq5nuDXtcCC0uHQICeiEz38dPBQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">= 10.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@parcel/reporter-cli/node_modules/color-convert": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-      "dev": true,
-      "dependencies": {
-        "color-name": "~1.1.4"
-      },
-      "engines": {
-        "node": ">=7.0.0"
-      }
-    },
-    "node_modules/@parcel/cache": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/cache/-/cache-2.11.0.tgz",
-      "integrity": "sha512-RSSkGNjO00lJPyftzaC9eaNVs4jMjPSAm0VJNWQ9JSm2n4A9BzQtTFAt1vhJOzzW1UsQvvBge9DdfkB7a2gIOw==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/fs": "2.11.0",
-        "@parcel/logger": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "lmdb": "2.8.5"
-      },
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      },
-      "peerDependencies": {
-        "@parcel/core": "^2.11.0"
-      }
-    },
-    "node_modules/@parcel/transformer-html/node_modules/lru-cache": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-      "dev": true,
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/to-regex-range": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
-      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
-      "dev": true,
-      "dependencies": {
-        "is-number": "^7.0.0"
-      },
-      "engines": {
-        "node": ">=8.0"
-      }
-    },
-    "node_modules/ieee754": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
-      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/feross"
-        },
-        {
-          "type": "patreon",
-          "url": "https://www.patreon.com/feross"
-        },
-        {
-          "type": "consulting",
-          "url": "https://feross.org/support"
-        }
-      ]
-    },
-    "node_modules/tiny-invariant": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
-      "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="
-    },
-    "node_modules/node-addon-api": {
-      "version": "7.1.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz",
-      "integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==",
-      "dev": true,
-      "engines": {
-        "node": "^16 || ^18 || >= 20"
-      }
-    },
-    "node_modules/escalade": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
-      "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
-      "dev": true,
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/popper.js": {
-      "version": "1.16.1-lts",
-      "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz",
-      "integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA=="
-    },
-    "node_modules/gensync": {
-      "version": "1.0.0-beta.2",
-      "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
-      "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
-      "dev": true,
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@parcel/optimizer-htmlnano/node_modules/css-select": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz",
-      "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==",
-      "dev": true,
-      "dependencies": {
-        "boolbase": "^1.0.0",
-        "css-what": "^6.0.1",
-        "domhandler": "^4.3.1",
-        "domutils": "^2.8.0",
-        "nth-check": "^2.0.1"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/fb55"
-      }
-    },
-    "node_modules/is-primitive": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-3.0.1.tgz",
-      "integrity": "sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/ms": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-      "dev": true
-    },
-    "node_modules/material-design-icons-iconfont": {
-      "version": "6.7.0",
-      "resolved": "https://registry.npmjs.org/material-design-icons-iconfont/-/material-design-icons-iconfont-6.7.0.tgz",
-      "integrity": "sha512-lSj71DgVv20kO0kGbs42icDzbRot61gEDBLQACzkUuznRQBUYmbxzEkGU6dNBb5fRWHMaScYlAXX96HQ4/cJWA=="
-    },
-    "node_modules/@types/d3-shape": {
-      "version": "3.1.4",
-      "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.4.tgz",
-      "integrity": "sha512-M2/xsWPsjaZc5ifMKp1EBp0gqJG0eO/zlldJNOC85Y/5DGsBQ49gDkRJ2h5GY7ZVD6KUumvZWsylSbvTaJTqKg==",
-      "dependencies": {
-        "@types/d3-path": "*"
-      }
-    },
-    "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz",
-      "integrity": "sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ]
-    },
-    "node_modules/lightningcss-linux-arm64-musl": {
-      "version": "1.23.0",
-      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.23.0.tgz",
-      "integrity": "sha512-cU00LGb6GUXCwof6ACgSMKo3q7XYbsyTj0WsKHLi1nw7pV0NCq8nFTn6ZRBYLoKiV8t+jWl0Hv8KkgymmK5L5g==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@parcel/watcher-win32-ia32": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.4.0.tgz",
-      "integrity": "sha512-IO/nM+K2YD/iwjWAfHFMBPz4Zqn6qBDqZxY4j2n9s+4+OuTSRM/y/irksnuqcspom5DjkSeF9d0YbO+qpys+JA==",
-      "cpu": [
-        "ia32"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">= 10.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@parcel/utils/node_modules/color-convert": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-      "dev": true,
-      "dependencies": {
-        "color-name": "~1.1.4"
-      },
-      "engines": {
-        "node": ">=7.0.0"
-      }
-    },
-    "node_modules/color-convert": {
-      "version": "1.9.3",
-      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
-      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
-      "dev": true,
-      "dependencies": {
-        "color-name": "1.1.3"
-      }
-    },
-    "node_modules/posthtml-render": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz",
-      "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==",
-      "dev": true,
-      "dependencies": {
-        "is-json": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@parcel/watcher-freebsd-x64": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.4.0.tgz",
-      "integrity": "sha512-dHTRMIplPDT1M0+BkXjtMN+qLtqq24sLDUhmU+UxxLP2TEY2k8GIoqIJiVrGWGomdWsy5IO27aDV1vWyQ6gfHA==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "freebsd"
-      ],
-      "engines": {
-        "node": ">= 10.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@parcel/transformer-svg/node_modules/yallist": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
-      "dev": true
-    },
-    "node_modules/lodash": {
-      "version": "4.17.21",
-      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
-      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
-    },
-    "node_modules/@parcel/transformer-posthtml": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/transformer-posthtml/-/transformer-posthtml-2.11.0.tgz",
-      "integrity": "sha512-dMK4p1RRAoIJEjK/Wz9GOLqwHqdD/VQDhMPk+6sUKp5zf2MhSohUstpp5gKsSZivCM3PS2f8k9rgroacJ/ReuA==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/plugin": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "nullthrows": "^1.1.1",
-        "posthtml": "^0.16.5",
-        "posthtml-parser": "^0.10.1",
-        "posthtml-render": "^3.0.0",
-        "semver": "^7.5.2"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@parcel/transformer-postcss/node_modules/lru-cache": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-      "dev": true,
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/recharts": {
-      "version": "2.9.0",
-      "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.9.0.tgz",
-      "integrity": "sha512-cVgiAU3W5UrA8nRRV/N0JrudgZzY/vjkzrlShbH+EFo1vs4nMlXgshZWLI0DfDLmn4/p4pF7Lq7F5PU+K94Ipg==",
-      "dependencies": {
-        "classnames": "^2.2.5",
-        "eventemitter3": "^4.0.1",
-        "lodash": "^4.17.19",
-        "react-is": "^16.10.2",
-        "react-resize-detector": "^8.0.4",
-        "react-smooth": "^2.0.4",
-        "recharts-scale": "^0.4.4",
-        "tiny-invariant": "^1.3.1",
-        "victory-vendor": "^36.6.8"
-      },
-      "engines": {
-        "node": ">=12"
-      },
-      "peerDependencies": {
-        "prop-types": "^15.6.0",
-        "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
-        "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
-      }
-    },
-    "node_modules/@material-ui/styles": {
-      "version": "4.11.5",
-      "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.5.tgz",
-      "integrity": "sha512-o/41ot5JJiUsIETME9wVLAJrmIWL3j0R0Bj2kCOLbSfqEkKf0fmaPt+5vtblUh5eXr2S+J/8J3DaCb10+CzPGA==",
-      "deprecated": "Material UI v4 doesn't receive active development since September 2021. See the guide https://mui.com/material-ui/migration/migration-v4/ to upgrade to v5.",
-      "dependencies": {
-        "@material-ui/types": "5.1.0",
-        "jss-plugin-props-sort": "^10.5.1",
-        "jss-plugin-default-unit": "^10.5.1",
-        "@babel/runtime": "^7.4.4",
-        "jss-plugin-nested": "^10.5.1",
-        "jss-plugin-rule-value-function": "^10.5.1",
-        "@material-ui/utils": "^4.11.3",
-        "jss-plugin-camel-case": "^10.5.1",
-        "clsx": "^1.0.4",
-        "@emotion/hash": "^0.8.0",
-        "csstype": "^2.5.2",
-        "jss-plugin-vendor-prefixer": "^10.5.1",
-        "jss": "^10.5.1",
-        "jss-plugin-global": "^10.5.1",
-        "hoist-non-react-statics": "^3.3.2",
-        "prop-types": "^15.7.2"
-      },
-      "engines": {
-        "node": ">=8.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/material-ui"
-      },
-      "peerDependencies": {
-        "@types/react": "^16.8.6 || ^17.0.0",
-        "react": "^16.8.0 || ^17.0.0",
-        "react-dom": "^16.8.0 || ^17.0.0"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/js-tokens": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
-      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
-    },
-    "node_modules/@parcel/transformer-html/node_modules/yallist": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
-      "dev": true
-    },
-    "node_modules/@types/d3-timer": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.1.tgz",
-      "integrity": "sha512-GGTvzKccVEhxmRfJEB6zhY9ieT4UhGVUIQaBzFpUO9OXy2ycAlnPCSJLzmGGgqt3KVjqN3QCQB4g1rsZnHsWhg=="
-    },
-    "node_modules/asynckit": {
-      "version": "0.4.0",
-      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
-      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
-    },
-    "node_modules/@parcel/transformer-postcss": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/transformer-postcss/-/transformer-postcss-2.11.0.tgz",
-      "integrity": "sha512-Ugy8XHBaUptGotsvwzq7gPCvkCopTIqqZ0JZ40Jmy9slGms8wnx06pNHA1Be/RcJwkJ2TbSu+7ncZdgmP5x5GQ==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/plugin": "2.11.0",
-        "@parcel/rust": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "clone": "^2.1.1",
-        "nullthrows": "^1.1.1",
-        "postcss-value-parser": "^4.2.0",
-        "semver": "^7.5.2"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@parcel/logger": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/logger/-/logger-2.11.0.tgz",
-      "integrity": "sha512-HtMEdCq3LKnvv4T2CIskcqlf2gpBvHMm3pkeUFB/hc/7hW/hE1k6/HA2VOQvc0tBsaMpmEx7PCrfrH56usQSyA==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/events": "2.11.0"
-      },
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@parcel/plugin": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/plugin/-/plugin-2.11.0.tgz",
-      "integrity": "sha512-9npuKBlhnPn7oeUpLJGecceg16GkXbvzbr6MNSZiHhkx3IBeITHQXlZnp2zAjUOFreNsYOfifwEF2S4KsARfBQ==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/types": "2.11.0"
-      },
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/rifm": {
-      "version": "0.7.0",
-      "resolved": "https://registry.npmjs.org/rifm/-/rifm-0.7.0.tgz",
-      "integrity": "sha512-DSOJTWHD67860I5ojetXdEQRIBvF6YcpNe53j0vn1vp9EUb9N80EiZTxgP+FkDKorWC8PZw052kTF4C1GOivCQ==",
-      "dependencies": {
-        "@babel/runtime": "^7.3.1"
-      },
-      "peerDependencies": {
-        "react": ">=16.8"
-      }
-    },
-    "node_modules/json5": {
-      "version": "2.2.3",
-      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
-      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
-      "dev": true,
-      "bin": {
-        "json5": "lib/cli.js"
-      },
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/decimal.js-light": {
-      "version": "2.5.1",
-      "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
-      "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
-    },
-    "node_modules/@parcel/transformer-posthtml/node_modules/semver": {
-      "version": "7.5.4",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
-      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
-      "dev": true,
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/eventemitter3": {
-      "version": "4.0.7",
-      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
-      "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
-    },
-    "node_modules/@types/react/node_modules/csstype": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
-      "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
-    },
-    "node_modules/@parcel/runtime-react-refresh": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.11.0.tgz",
-      "integrity": "sha512-Kfnc7gLjhoephLMnjABrkIkzVfzPrpJlxiJFIleY2Fm57YhmCfKsEYxm3lHOutNaYl1VArW0LKClPH/VHG9vfQ==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/plugin": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "react-error-overlay": "6.0.9",
-        "react-refresh": "^0.9.0"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@parcel/transformer-js": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/transformer-js/-/transformer-js-2.11.0.tgz",
-      "integrity": "sha512-G1sv0n8/fJqHqwUs0iVnVdmRY0Kh8kWaDkuWcU/GJBHMGhUnLXKdNwxX2Av9UdBL14bU1nTINfr9qOfnQotXWg==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/plugin": "2.11.0",
-        "@parcel/rust": "2.11.0",
-        "@parcel/source-map": "^2.1.1",
-        "@parcel/utils": "2.11.0",
-        "@parcel/workers": "2.11.0",
-        "@swc/helpers": "^0.5.0",
-        "browserslist": "^4.6.6",
-        "nullthrows": "^1.1.1",
-        "regenerator-runtime": "^0.13.7",
-        "semver": "^7.5.2"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      },
-      "peerDependencies": {
-        "@parcel/core": "^2.11.0"
-      }
-    },
-    "node_modules/@parcel/core/node_modules/lru-cache": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-      "dev": true,
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/node-gyp-build-optional-packages/node_modules/detect-libc": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
-      "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/@babel/helper-environment-visitor": {
-      "version": "7.22.20",
-      "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
-      "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
-      "dev": true,
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@parcel/optimizer-htmlnano/node_modules/svgo": {
-      "version": "2.8.0",
-      "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz",
-      "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==",
-      "dev": true,
-      "dependencies": {
-        "@trysound/sax": "0.2.0",
-        "commander": "^7.2.0",
-        "css-select": "^4.1.3",
-        "css-tree": "^1.1.3",
-        "csso": "^4.2.0",
-        "picocolors": "^1.0.0",
-        "stable": "^0.1.8"
-      },
-      "bin": {
-        "svgo": "bin/svgo"
-      },
-      "engines": {
-        "node": ">=10.13.0"
-      }
-    },
-    "node_modules/@parcel/optimizer-swc": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/optimizer-swc/-/optimizer-swc-2.11.0.tgz",
-      "integrity": "sha512-ftf42F3JyZxJb6nnLlgNGyNQ273YOla4dFGH/tWC8iTwObHUpWe7cMbCGcrSJBvAlsLkZfLpFNAXFxUgxdKyHQ==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/plugin": "2.11.0",
-        "@parcel/source-map": "^2.1.1",
-        "@parcel/utils": "2.11.0",
-        "@swc/core": "^1.3.36",
-        "nullthrows": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@babel/traverse": {
-      "version": "7.23.2",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
-      "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
-      "dev": true,
-      "dependencies": {
-        "@babel/code-frame": "^7.22.13",
-        "@babel/generator": "^7.23.0",
-        "@babel/helper-environment-visitor": "^7.22.20",
-        "@babel/helper-function-name": "^7.23.0",
-        "@babel/helper-hoist-variables": "^7.22.5",
-        "@babel/helper-split-export-declaration": "^7.22.6",
-        "@babel/parser": "^7.23.0",
-        "@babel/types": "^7.23.0",
-        "debug": "^4.1.0",
-        "globals": "^11.1.0"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/lightningcss-linux-x64-gnu": {
-      "version": "1.23.0",
-      "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.23.0.tgz",
-      "integrity": "sha512-q4jdx5+5NfB0/qMbXbOmuC6oo7caPnFghJbIAV90cXZqgV8Am3miZhC4p+sQVdacqxfd+3nrle4C8icR3p1AYw==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@parcel/markdown-ansi/node_modules/color-convert": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-      "dev": true,
-      "dependencies": {
-        "color-name": "~1.1.4"
-      },
-      "engines": {
-        "node": ">=7.0.0"
-      }
-    },
-    "node_modules/@parcel/transformer-posthtml/node_modules/yallist": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
-      "dev": true
-    },
-    "node_modules/csstype": {
-      "version": "2.6.21",
-      "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz",
-      "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
-    },
-    "node_modules/lru-cache": {
-      "version": "5.1.1",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
-      "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
-      "dev": true,
-      "dependencies": {
-        "yallist": "^3.0.2"
-      }
-    },
-    "node_modules/globals": {
-      "version": "11.12.0",
-      "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
-      "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
-      "dev": true,
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/supports-preserve-symlinks-flag": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
-      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/classnames": {
-      "version": "2.3.2",
-      "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
-      "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
-    },
-    "node_modules/cosmiconfig": {
-      "version": "8.3.6",
-      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
-      "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
-      "dev": true,
-      "dependencies": {
-        "import-fresh": "^3.3.0",
-        "js-yaml": "^4.1.0",
-        "parse-json": "^5.2.0",
-        "path-type": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=14"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/d-fischer"
-      },
-      "peerDependencies": {
-        "typescript": ">=4.9.5"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/react-is": {
-      "version": "17.0.2",
-      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
-      "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
-    },
-    "node_modules/@parcel/utils/node_modules/ansi-styles": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-      "dev": true,
-      "dependencies": {
-        "color-convert": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
-      }
-    },
-    "node_modules/regenerator-runtime": {
-      "version": "0.14.0",
-      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
-      "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
-    },
-    "node_modules/@material-ui/icons": {
-      "version": "4.11.3",
-      "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.3.tgz",
-      "integrity": "sha512-IKHlyx6LDh8n19vzwH5RtHIOHl9Tu90aAAxcbWME6kp4dmvODM3UvOHJeMIDzUbd4muuJKHmlNoBN+mDY4XkBA==",
-      "dependencies": {
-        "@babel/runtime": "^7.4.4"
-      },
-      "engines": {
-        "node": ">=8.0.0"
-      },
-      "peerDependencies": {
-        "@material-ui/core": "^4.0.0",
-        "@types/react": "^16.8.6 || ^17.0.0",
-        "react": "^16.8.0 || ^17.0.0",
-        "react-dom": "^16.8.0 || ^17.0.0"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/internmap": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
-      "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/combined-stream": {
-      "version": "1.0.8",
-      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
-      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
-      "dependencies": {
-        "delayed-stream": "~1.0.0"
-      },
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/@types/d3-array": {
-      "version": "3.0.9",
-      "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.9.tgz",
-      "integrity": "sha512-mZowFN3p64ajCJJ4riVYlOjNlBJv3hctgAY01pjw3qTnJePD8s9DZmYDzhHKvzfCYvdjwylkU38+Vdt7Cu2FDA=="
-    },
-    "node_modules/history": {
-      "version": "4.10.1",
-      "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
-      "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
-      "dependencies": {
-        "@babel/runtime": "^7.1.2",
-        "loose-envify": "^1.2.0",
-        "resolve-pathname": "^3.0.0",
-        "tiny-invariant": "^1.0.2",
-        "tiny-warning": "^1.0.0",
-        "value-equal": "^1.0.1"
-      }
-    },
-    "node_modules/@parcel/codeframe/node_modules/supports-color": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
-      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-      "dev": true,
-      "dependencies": {
-        "has-flag": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/@parcel/packager-svg": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/packager-svg/-/packager-svg-2.11.0.tgz",
-      "integrity": "sha512-2wQBkzLwcaWFGWz8TP+bgsXgiueWPzrjKsWugWdDfq0FbXh8XVeR/599qnus3RFHZy4cH6L6yq/7zxcljtxK8A==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/plugin": "2.11.0",
-        "@parcel/types": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "posthtml": "^0.16.4"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@jridgewell/sourcemap-codec": {
-      "version": "1.4.15",
-      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
-      "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
-      "dev": true
-    },
-    "node_modules/hasown": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
-      "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
-      "dev": true,
-      "dependencies": {
-        "function-bind": "^1.1.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/parcel/node_modules/has-flag": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/semver": {
-      "version": "6.3.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
-      "dev": true,
-      "bin": {
-        "semver": "bin/semver.js"
-      }
-    },
-    "node_modules/parcel": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/parcel/-/parcel-2.11.0.tgz",
-      "integrity": "sha512-H/RI1/DmuOkL8RuG/EpNPvtzrbF+7jA/R56ydEEm+lqFbYktKB4COR7JXdHkZXRgbSJyimrFB8d0r9+SaRnj0Q==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/config-default": "2.11.0",
-        "@parcel/core": "2.11.0",
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/events": "2.11.0",
-        "@parcel/fs": "2.11.0",
-        "@parcel/logger": "2.11.0",
-        "@parcel/package-manager": "2.11.0",
-        "@parcel/reporter-cli": "2.11.0",
-        "@parcel/reporter-dev-server": "2.11.0",
-        "@parcel/reporter-tracer": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "chalk": "^4.1.0",
-        "commander": "^7.0.0",
-        "get-port": "^4.2.0"
-      },
-      "bin": {
-        "parcel": "lib/bin.js"
-      },
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/react-error-overlay": {
-      "version": "6.0.9",
-      "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz",
-      "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==",
-      "dev": true
-    },
-    "node_modules/htmlparser2": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz",
-      "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==",
-      "dev": true,
-      "funding": [
-        "https://github.com/fb55/htmlparser2?sponsor=1",
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/fb55"
-        }
-      ],
-      "dependencies": {
-        "domelementtype": "^2.0.1",
-        "domhandler": "^4.2.2",
-        "domutils": "^2.8.0",
-        "entities": "^3.0.1"
-      }
-    },
-    "node_modules/d3-array": {
-      "version": "3.2.4",
-      "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
-      "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
-      "dependencies": {
-        "internmap": "1 - 2"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/js-yaml": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
-      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
-      "dev": true,
-      "dependencies": {
-        "argparse": "^2.0.1"
-      },
-      "bin": {
-        "js-yaml": "bin/js-yaml.js"
-      }
-    },
-    "node_modules/react-lifecycles-compat": {
-      "version": "3.0.4",
-      "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
-      "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
-    },
-    "node_modules/object-assign": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/@swc/core-linux-x64-gnu": {
-      "version": "1.3.107",
-      "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.107.tgz",
-      "integrity": "sha512-uBVNhIg0ip8rH9OnOsCARUFZ3Mq3tbPHxtmWk9uAa5u8jQwGWeBx5+nTHpDOVd3YxKb6+5xDEI/edeeLpha/9g==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/path-to-regexp": {
-      "version": "1.8.0",
-      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
-      "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
-      "dependencies": {
-        "isarray": "0.0.1"
-      }
-    },
-    "node_modules/delayed-stream": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
-      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
-      "engines": {
-        "node": ">=0.4.0"
-      }
-    },
-    "node_modules/@babel/runtime": {
-      "version": "7.23.9",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz",
-      "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==",
-      "dependencies": {
-        "regenerator-runtime": "^0.14.0"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/lightningcss-linux-arm-gnueabihf": {
-      "version": "1.23.0",
-      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.23.0.tgz",
-      "integrity": "sha512-fBamf/bULvmWft9uuX+bZske236pUZEoUlaHNBjnueaCTJ/xd8eXgb0cEc7S5o0Nn6kxlauMBnqJpF70Bgq3zg==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/browserslist": {
-      "version": "4.22.3",
-      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz",
-      "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "opencollective",
-          "url": "https://opencollective.com/browserslist"
-        },
-        {
-          "type": "tidelift",
-          "url": "https://tidelift.com/funding/github/npm/browserslist"
-        },
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/ai"
-        }
-      ],
-      "dependencies": {
-        "caniuse-lite": "^1.0.30001580",
-        "electron-to-chromium": "^1.4.648",
-        "node-releases": "^2.0.14",
-        "update-browserslist-db": "^1.0.13"
-      },
-      "bin": {
-        "browserslist": "cli.js"
-      },
-      "engines": {
-        "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
-      }
-    },
-    "node_modules/loose-envify": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
-      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
-      "dependencies": {
-        "js-tokens": "^3.0.0 || ^4.0.0"
-      },
-      "bin": {
-        "loose-envify": "cli.js"
-      }
-    },
-    "node_modules/@parcel/transformer-js/node_modules/lru-cache": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-      "dev": true,
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/css-vendor": {
-      "version": "2.0.8",
-      "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz",
-      "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==",
-      "dependencies": {
-        "@babel/runtime": "^7.8.3",
-        "is-in-browser": "^1.0.2"
-      }
-    },
-    "node_modules/jsesc": {
-      "version": "2.5.2",
-      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
-      "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
-      "dev": true,
-      "bin": {
-        "jsesc": "bin/jsesc"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/@babel/helper-module-imports": {
-      "version": "7.22.15",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
-      "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
-      "dev": true,
-      "dependencies": {
-        "@babel/types": "^7.22.15"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/react-router": {
-      "version": "5.3.4",
-      "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz",
-      "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==",
-      "dependencies": {
-        "@babel/runtime": "^7.12.13",
-        "history": "^4.9.0",
-        "hoist-non-react-statics": "^3.1.0",
-        "loose-envify": "^1.3.1",
-        "path-to-regexp": "^1.7.0",
-        "prop-types": "^15.6.2",
-        "react-is": "^16.6.0",
-        "tiny-invariant": "^1.0.2",
-        "tiny-warning": "^1.0.0"
-      },
-      "peerDependencies": {
-        "react": ">=15"
-      }
-    },
-    "node_modules/html-to-react/node_modules/domutils": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
-      "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
-      "dependencies": {
-        "dom-serializer": "^2.0.0",
-        "domelementtype": "^2.3.0",
-        "domhandler": "^5.0.3"
-      },
-      "funding": {
-        "url": "https://github.com/fb55/domutils?sponsor=1"
-      }
-    },
-    "node_modules/@babel/helper-hoist-variables": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
-      "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
-      "dev": true,
-      "dependencies": {
-        "@babel/types": "^7.22.5"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@types/d3-path": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.1.tgz",
-      "integrity": "sha512-blRhp7ki7pVznM8k6lk5iUU9paDbVRVq+/xpf0RRgSJn5gr6SE7RcFtxooYGMBOc1RZiGyqRpVdu5AD0z0ooMA=="
-    },
-    "node_modules/is-number": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
-      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.12.0"
-      }
-    },
-    "node_modules/@parcel/optimizer-htmlnano/node_modules/css-tree": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
-      "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==",
-      "dev": true,
-      "dependencies": {
-        "mdn-data": "2.0.14",
-        "source-map": "^0.6.1"
-      },
-      "engines": {
-        "node": ">=8.0.0"
-      }
-    },
-    "node_modules/is-json": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz",
-      "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==",
-      "dev": true
-    },
-    "node_modules/@lmdb/lmdb-darwin-arm64": {
-      "version": "2.8.5",
-      "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-2.8.5.tgz",
-      "integrity": "sha512-KPDeVScZgA1oq0CiPBcOa3kHIqU+pTOwRFDIhxvmf8CTNvqdZQYp5cCKW0bUk69VygB2PuTiINFWbY78aR2pQw==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ]
-    },
-    "node_modules/@parcel/core/node_modules/yallist": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
-      "dev": true
-    },
-    "node_modules/jss-plugin-nested": {
-      "version": "10.10.0",
-      "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.10.0.tgz",
-      "integrity": "sha512-9R4JHxxGgiZhurDo3q7LdIiDEgtA1bTGzAbhSPyIOWb7ZubrjQe8acwhEQ6OEKydzpl8XHMtTnEwHXCARLYqYA==",
-      "dependencies": {
-        "@babel/runtime": "^7.3.1",
-        "jss": "10.10.0",
-        "tiny-warning": "^1.0.2"
-      }
-    },
-    "node_modules/parcel/node_modules/supports-color": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
-      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-      "dev": true,
-      "dependencies": {
-        "has-flag": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/nth-check": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
-      "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
-      "dev": true,
-      "dependencies": {
-        "boolbase": "^1.0.0"
-      },
-      "funding": {
-        "url": "https://github.com/fb55/nth-check?sponsor=1"
-      }
-    },
-    "node_modules/csso": {
-      "version": "5.0.5",
-      "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz",
-      "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==",
-      "dev": true,
-      "optional": true,
-      "peer": true,
-      "dependencies": {
-        "css-tree": "~2.2.0"
-      },
-      "engines": {
-        "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
-        "npm": ">=7.0.0"
-      }
-    },
-    "node_modules/lightningcss-linux-arm64-gnu": {
-      "version": "1.23.0",
-      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.23.0.tgz",
-      "integrity": "sha512-RS7sY77yVLOmZD6xW2uEHByYHhQi5JYWmgVumYY85BfNoVI3DupXSlzbw+b45A9NnVKq45+oXkiN6ouMMtTwfg==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@parcel/source-map": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/@parcel/source-map/-/source-map-2.1.1.tgz",
-      "integrity": "sha512-Ejx1P/mj+kMjQb8/y5XxDUn4reGdr+WyKYloBljpppUy8gs42T+BNoEOuRYqDVdgPc6NxduzIDoJS9pOFfV5Ew==",
-      "dev": true,
-      "dependencies": {
-        "detect-libc": "^1.0.3"
-      },
-      "engines": {
-        "node": "^12.18.3 || >=14"
-      }
-    },
-    "node_modules/@parcel/watcher-win32-arm64": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.4.0.tgz",
-      "integrity": "sha512-NOej2lqlq8bQNYhUMnOD0nwvNql8ToQF+1Zhi9ULZoG+XTtJ9hNnCFfyICxoZLXor4bBPTOnzs/aVVoefYnjIg==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">= 10.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/react-router/node_modules/react-is": {
-      "version": "16.13.1",
-      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
-      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
-    },
-    "node_modules/@swc/core-win32-ia32-msvc": {
-      "version": "1.3.107",
-      "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.107.tgz",
-      "integrity": "sha512-ZBUtgyjTHlz8TPJh7kfwwwFma+ktr6OccB1oXC8fMSopD0AxVnQasgun3l3099wIsAB9eEsJDQ/3lDkOLs1gBA==",
-      "cpu": [
-        "ia32"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@parcel/markdown-ansi/node_modules/color-name": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-      "dev": true
-    },
-    "node_modules/proxy-from-env": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
-      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
-    },
-    "node_modules/@parcel/resolver-default": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/resolver-default/-/resolver-default-2.11.0.tgz",
-      "integrity": "sha512-suZNN2lE5W48LPTwAbG7gnj1IeubkCVEm0XspWXcXUtCzglimNJ8PVVBGx171o5CqDpdbGF3AqHjG9N3uOwXag==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/node-resolver-core": "3.2.0",
-        "@parcel/plugin": "2.11.0"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/parcel/node_modules/chalk": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-      "dev": true,
-      "dependencies": {
-        "ansi-styles": "^4.1.0",
-        "supports-color": "^7.1.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/chalk?sponsor=1"
-      }
-    },
-    "node_modules/resolve-pathname": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
-      "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
-    },
-    "node_modules/@parcel/transformer-js/node_modules/yallist": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
-      "dev": true
-    },
-    "node_modules/@parcel/diagnostic": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/diagnostic/-/diagnostic-2.11.0.tgz",
-      "integrity": "sha512-4dJmOXVL5YGGQRRsQosQbSRONBcboB71mSwaeaEgz3pPdq9QXVPLACkGe/jTXSqa3OnAHu3g5vQLpE1g5xqBqw==",
-      "dev": true,
-      "dependencies": {
-        "@mischnic/json-sourcemap": "^0.1.0",
-        "nullthrows": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@parcel/transformer-image": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/transformer-image/-/transformer-image-2.11.0.tgz",
-      "integrity": "sha512-QiZj18UHf3lVFsi65Vz8YbS3ydx9Pe9x8ktMxE1oh9qpznN8lD7gE/Z9DxuTZB84EZ9pKytKwcv5WGXP25xIFg==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/plugin": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "@parcel/workers": "2.11.0",
-        "nullthrows": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "peerDependencies": {
-        "@parcel/core": "^2.11.0"
-      }
-    },
-    "node_modules/@parcel/transformer-html/node_modules/semver": {
-      "version": "7.5.4",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
-      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
-      "dev": true,
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/ordered-binary": {
-      "version": "1.5.1",
-      "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.1.tgz",
-      "integrity": "sha512-5VyHfHY3cd0iza71JepYG50My+YUbrFtGoUz2ooEydPyPM7Aai/JW098juLr+RG6+rDJuzNNTsEQu2DZa1A41A==",
-      "dev": true
-    },
-    "node_modules/@parcel/node-resolver-core/node_modules/semver": {
-      "version": "7.5.4",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
-      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
-      "dev": true,
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@types/d3-time": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.2.tgz",
-      "integrity": "sha512-kbdRXTmUgNfw5OTE3KZnFQn6XdIc4QGroN5UixgdrXATmYsdlPQS6pEut9tVlIojtzuFD4txs/L+Rq41AHtLpg=="
-    },
-    "node_modules/@types/react-transition-group": {
-      "version": "4.4.8",
-      "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.8.tgz",
-      "integrity": "sha512-QmQ22q+Pb+HQSn04NL3HtrqHwYMf4h3QKArOy5F8U5nEVMaihBs3SR10WiOM1iwPz5jIo8x/u11al+iEGZZrvg==",
-      "dependencies": {
-        "@types/react": "*"
-      }
-    },
-    "node_modules/term-size": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz",
-      "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/@parcel/optimizer-svgo": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/optimizer-svgo/-/optimizer-svgo-2.11.0.tgz",
-      "integrity": "sha512-TQpvfBhjV2IsuFHXUolbDS6XWB3DDR2rYTlqlA8LMmuOY7jQd9Bnkl4JnapzWm/bRuzRlzdGjjVCPGL8iShFvA==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/plugin": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "svgo": "^2.4.0"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/d3-color": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
-      "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@babel/plugin-syntax-jsx": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz",
-      "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==",
-      "dev": true,
-      "dependencies": {
-        "@babel/helper-plugin-utils": "^7.22.5"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0-0"
-      }
-    },
-    "node_modules/fill-range": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
-      "dev": true,
-      "dependencies": {
-        "to-regex-range": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/ansi-styles": {
-      "version": "3.2.1",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
-      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
-      "dev": true,
-      "dependencies": {
-        "color-convert": "^1.9.0"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/core-js-compat": {
-      "version": "3.35.1",
-      "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.1.tgz",
-      "integrity": "sha512-sftHa5qUJY3rs9Zht1WEnmkvXputCyDBczPnr7QDgL8n3qrF3CMXY4VPSYtOLLiOUJcah2WNXREd48iOl6mQIw==",
-      "dev": true,
-      "dependencies": {
-        "browserslist": "^4.22.2"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/core-js"
-      }
-    },
-    "node_modules/@babel/helper-string-parser": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
-      "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
-      "dev": true,
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/jss-plugin-default-unit": {
-      "version": "10.10.0",
-      "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.10.0.tgz",
-      "integrity": "sha512-SvpajxIECi4JDUbGLefvNckmI+c2VWmP43qnEy/0eiwzRUsafg5DVSIWSzZe4d2vFX1u9nRDP46WCFV/PXVBGQ==",
-      "dependencies": {
-        "@babel/runtime": "^7.3.1",
-        "jss": "10.10.0"
-      }
-    },
-    "node_modules/clone": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
-      "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.8"
-      }
-    },
-    "node_modules/fast-equals": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz",
-      "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==",
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
-    "node_modules/process": {
-      "version": "0.11.10",
-      "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
-      "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.6.0"
-      }
-    },
-    "node_modules/@parcel/reporter-cli/node_modules/supports-color": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
-      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-      "dev": true,
-      "dependencies": {
-        "has-flag": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/lightningcss-linux-x64-musl": {
-      "version": "1.23.0",
-      "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.23.0.tgz",
-      "integrity": "sha512-G9Ri3qpmF4qef2CV/80dADHKXRAQeQXpQTLx7AiQrBYQHqBjB75oxqj06FCIe5g4hNCqLPnM9fsO4CyiT1sFSQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@parcel/transformer-svg/node_modules/lru-cache": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-      "dev": true,
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@parcel/optimizer-svgo/node_modules/svgo": {
-      "version": "2.8.0",
-      "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz",
-      "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==",
-      "dev": true,
-      "dependencies": {
-        "@trysound/sax": "0.2.0",
-        "commander": "^7.2.0",
-        "css-select": "^4.1.3",
-        "css-tree": "^1.1.3",
-        "csso": "^4.2.0",
-        "picocolors": "^1.0.0",
-        "stable": "^0.1.8"
-      },
-      "bin": {
-        "svgo": "bin/svgo"
-      },
-      "engines": {
-        "node": ">=10.13.0"
-      }
-    },
-    "node_modules/source-map-js": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
-      "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
-      "dev": true,
-      "optional": true,
-      "peer": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/@parcel/optimizer-svgo/node_modules/css-tree": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
-      "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==",
-      "dev": true,
-      "dependencies": {
-        "mdn-data": "2.0.14",
-        "source-map": "^0.6.1"
-      },
-      "engines": {
-        "node": ">=8.0.0"
-      }
-    },
-    "node_modules/d3-timer": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
-      "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/is-extglob": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
-      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/@swc/core-win32-arm64-msvc": {
-      "version": "1.3.107",
-      "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.107.tgz",
-      "integrity": "sha512-J3P14Ngy/1qtapzbguEH41kY109t6DFxfbK4Ntz9dOWNuVY3o9/RTB841ctnJk0ZHEG+BjfCJjsD2n8H5HcaOA==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@parcel/optimizer-svgo/node_modules/csso": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz",
-      "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==",
-      "dev": true,
-      "dependencies": {
-        "css-tree": "^1.1.2"
-      },
-      "engines": {
-        "node": ">=8.0.0"
-      }
-    },
-    "node_modules/html-to-react": {
-      "version": "1.7.0",
-      "resolved": "https://registry.npmjs.org/html-to-react/-/html-to-react-1.7.0.tgz",
-      "integrity": "sha512-b5HTNaTGyOj5GGIMiWVr1k57egAZ/vGy0GGefnCQ1VW5hu9+eku8AXHtf2/DeD95cj/FKBKYa1J7SWBOX41yUQ==",
-      "dependencies": {
-        "domhandler": "^5.0",
-        "htmlparser2": "^9.0",
-        "lodash.camelcase": "^4.3.0"
-      },
-      "peerDependencies": {
-        "react": "^0.13.0 || ^0.14.0 || >=15"
-      }
-    },
-    "node_modules/@swc/core-darwin-x64": {
-      "version": "1.3.107",
-      "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.107.tgz",
-      "integrity": "sha512-hwiLJ2ulNkBGAh1m1eTfeY1417OAYbRGcb/iGsJ+LuVLvKAhU/itzsl535CvcwAlt2LayeCFfcI8gdeOLeZa9A==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@parcel/codeframe/node_modules/has-flag": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/@types/d3-interpolate": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.3.tgz",
-      "integrity": "sha512-6OZ2EIB4lLj+8cUY7I/Cgn9Q+hLdA4DjJHYOQDiHL0SzqS1K9DL5xIOVBSIHgF+tiuO9MU1D36qvdIvRDRPh+Q==",
-      "dependencies": {
-        "@types/d3-color": "*"
-      }
-    },
-    "node_modules/@types/styled-jsx": {
-      "version": "2.2.9",
-      "resolved": "https://registry.npmjs.org/@types/styled-jsx/-/styled-jsx-2.2.9.tgz",
-      "integrity": "sha512-W/iTlIkGEyTBGTEvZCey8EgQlQ5l0DwMqi3iOXlLs2kyBwYTXHKEiU6IZ5EwoRwngL8/dGYuzezSup89ttVHLw==",
-      "dependencies": {
-        "@types/react": "*"
-      }
-    },
-    "node_modules/@swc/counter": {
-      "version": "0.1.2",
-      "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz",
-      "integrity": "sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==",
-      "dev": true
-    },
-    "node_modules/emoji-regex": {
-      "version": "8.0.0",
-      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
-      "dev": true
-    },
-    "node_modules/hoist-non-react-statics": {
-      "version": "3.3.2",
-      "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
-      "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
-      "dependencies": {
-        "react-is": "^16.7.0"
-      }
-    },
-    "node_modules/@babel/helper-validator-option": {
-      "version": "7.22.15",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz",
-      "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==",
-      "dev": true,
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@parcel/node-resolver-core/node_modules/yallist": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
-      "dev": true
-    },
-    "node_modules/@babel/helper-optimise-call-expression": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz",
-      "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==",
-      "dev": true,
-      "dependencies": {
-        "@babel/types": "^7.22.5"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/set-value": {
-      "version": "4.1.0",
-      "funding": [
-        "https://github.com/sponsors/jonschlinkert",
-        "https://paypal.me/jonathanschlinkert",
-        "https://jonschlinkert.dev/sponsor"
-      ],
-      "resolved": "https://registry.npmjs.org/set-value/-/set-value-4.1.0.tgz",
-      "integrity": "sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw==",
-      "dev": true,
-      "dependencies": {
-        "is-plain-object": "^2.0.4",
-        "is-primitive": "^3.0.1"
-      },
-      "engines": {
-        "node": ">=11.0"
-      }
-    },
-    "node_modules/@material-ui/pickers": {
-      "version": "3.3.11",
-      "resolved": "https://registry.npmjs.org/@material-ui/pickers/-/pickers-3.3.11.tgz",
-      "integrity": "sha512-pDYjbjUeabapijS2FpSwK/ruJdk7IGeAshpLbKDa3PRRKRy7Nv6sXxAvUg2F+lID/NwUKgBmCYS5bzrl7Xxqzw==",
-      "deprecated": "This package no longer supported. It has been relaced by @mui/x-date-pickers",
-      "dependencies": {
-        "@babel/runtime": "^7.6.0",
-        "@date-io/core": "1.x",
-        "@types/styled-jsx": "^2.2.8",
-        "clsx": "^1.0.2",
-        "react-transition-group": "^4.0.0",
-        "rifm": "^0.7.0"
-      },
-      "peerDependencies": {
-        "@date-io/core": "^1.3.6",
-        "@material-ui/core": "^4.0.0",
-        "prop-types": "^15.6.0",
-        "react": "^16.8.0 || ^17.0.0",
-        "react-dom": "^16.8.0 || ^17.0.0"
-      }
-    },
-    "node_modules/follow-redirects": {
-      "version": "1.15.4",
-      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
-      "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
-      "funding": [
-        {
-          "type": "individual",
-          "url": "https://github.com/sponsors/RubenVerborgh"
-        }
-      ],
-      "engines": {
-        "node": ">=4.0"
-      },
-      "peerDependenciesMeta": {
-        "debug": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@babel/plugin-transform-react-jsx": {
-      "version": "7.22.15",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.15.tgz",
-      "integrity": "sha512-oKckg2eZFa8771O/5vi7XeTvmM6+O9cxZu+kanTU7tD4sin5nO/G8jGJhq8Hvt2Z0kUoEDRayuZLaUlYl8QuGA==",
-      "dev": true,
-      "dependencies": {
-        "@babel/helper-annotate-as-pure": "^7.22.5",
-        "@babel/helper-module-imports": "^7.22.15",
-        "@babel/helper-plugin-utils": "^7.22.5",
-        "@babel/plugin-syntax-jsx": "^7.22.5",
-        "@babel/types": "^7.22.15"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0-0"
-      }
-    },
-    "node_modules/domelementtype": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
-      "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/fb55"
-        }
-      ]
-    },
-    "node_modules/@parcel/markdown-ansi/node_modules/ansi-styles": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-      "dev": true,
-      "dependencies": {
-        "color-convert": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
-      }
-    },
-    "node_modules/@parcel/transformer-js/node_modules/regenerator-runtime": {
-      "version": "0.13.11",
-      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
-      "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
-      "dev": true
-    },
-    "node_modules/@parcel/codeframe": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/codeframe/-/codeframe-2.11.0.tgz",
-      "integrity": "sha512-YHs9g/i5af/sd/JrWAojU9YFbKffcJ3Tx2EJaK0ME8OJsye91UaI/3lxSUYLmJG9e4WLNJtqci8V5FBMz//ZPg==",
-      "dev": true,
-      "dependencies": {
-        "chalk": "^4.1.0"
-      },
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@babel/helper-create-class-features-plugin": {
-      "version": "7.22.15",
-      "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz",
-      "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==",
-      "dev": true,
-      "dependencies": {
-        "@babel/helper-annotate-as-pure": "^7.22.5",
-        "@babel/helper-environment-visitor": "^7.22.5",
-        "@babel/helper-function-name": "^7.22.5",
-        "@babel/helper-member-expression-to-functions": "^7.22.15",
-        "@babel/helper-optimise-call-expression": "^7.22.5",
-        "@babel/helper-replace-supers": "^7.22.9",
-        "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
-        "@babel/helper-split-export-declaration": "^7.22.6",
-        "semver": "^6.3.1"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0"
-      }
-    },
-    "node_modules/micromatch": {
-      "version": "4.0.5",
-      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
-      "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
-      "dev": true,
-      "dependencies": {
-        "braces": "^3.0.2",
-        "picomatch": "^2.3.1"
-      },
-      "engines": {
-        "node": ">=8.6"
-      }
-    },
-    "node_modules/@parcel/package-manager/node_modules/yallist": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
-      "dev": true
-    },
-    "node_modules/lightningcss-win32-x64-msvc": {
-      "version": "1.23.0",
-      "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.23.0.tgz",
-      "integrity": "sha512-1rcBDJLU+obPPJM6qR5fgBUiCdZwZLafZM5f9kwjFLkb/UBNIzmae39uCSmh71nzPCTXZqHbvwu23OWnWEz+eg==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/type-fest": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
-      "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/d3-ease": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
-      "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@jridgewell/resolve-uri": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
-      "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
-      "dev": true,
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
-    "node_modules/@parcel/utils/node_modules/supports-color": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
-      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-      "dev": true,
-      "dependencies": {
-        "has-flag": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.2.tgz",
-      "integrity": "sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@parcel/namer-default": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/namer-default/-/namer-default-2.11.0.tgz",
-      "integrity": "sha512-DEwBSKSClg4DA2xAWimYkw9bFi7MFb9TdT7/TYZStMTsfYHPWOyyjGR7aVr3Ra4wNb+XX6g4rR41yp3HD6KO7A==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/plugin": "2.11.0",
-        "nullthrows": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/d3-shape": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
-      "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
-      "dependencies": {
-        "d3-path": "^3.1.0"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@parcel/transformer-svg/node_modules/semver": {
-      "version": "7.5.4",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
-      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
-      "dev": true,
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/lightningcss-darwin-x64": {
-      "version": "1.23.0",
-      "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.23.0.tgz",
-      "integrity": "sha512-KeRFCNoYfDdcolcFXvokVw+PXCapd2yHS1Diko1z1BhRz/nQuD5XyZmxjWdhmhN/zj5sH8YvWsp0/lPLVzqKpg==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/clsx": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
-      "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/dom-serializer/node_modules/entities": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
-      "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
-      "dev": true,
-      "funding": {
-        "url": "https://github.com/fb55/entities?sponsor=1"
-      }
-    },
-    "node_modules/@types/d3-color": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.2.tgz",
-      "integrity": "sha512-At+Ski7dL8Bs58E8g8vPcFJc8tGcaC12Z4m07+p41+DRqnZQcAlp3NfYjLrhNYv+zEyQitU1CUxXNjqUyf+c0g=="
-    },
-    "node_modules/@parcel/packager-html": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/packager-html/-/packager-html-2.11.0.tgz",
-      "integrity": "sha512-ho5AQ70naTV8IqkKIbKtK+jsXQ5TJfFgtBvmJlyB3YydRMbIc+3g4G0xgIvf15V4uCMw9Md0Sv1W65nQXHPQoA==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/plugin": "2.11.0",
-        "@parcel/types": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "nullthrows": "^1.1.1",
-        "posthtml": "^0.16.5"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/supports-color": {
-      "version": "5.5.0",
-      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
-      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
-      "dev": true,
-      "dependencies": {
-        "has-flag": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/lmdb/node_modules/node-addon-api": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
-      "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==",
-      "dev": true
-    },
-    "node_modules/yallist": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
-      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
-      "dev": true
-    },
-    "node_modules/safe-buffer": {
-      "version": "5.2.1",
-      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
-      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/feross"
-        },
-        {
-          "type": "patreon",
-          "url": "https://www.patreon.com/feross"
-        },
-        {
-          "type": "consulting",
-          "url": "https://feross.org/support"
-        }
-      ]
-    },
-    "node_modules/@parcel/transformer-json": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/transformer-json/-/transformer-json-2.11.0.tgz",
-      "integrity": "sha512-Wt/wgSBaRWmPL4gpvjkV0bCBRxFOtsuLNzsm8vYA5poxTFhuLY+AoyQ8S2+xXU4VxwBfdppfIr2Ny3SwGs8xbQ==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/plugin": "2.11.0",
-        "json5": "^2.2.0"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/d3-format": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
-      "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@parcel/transformer-posthtml/node_modules/lru-cache": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-      "dev": true,
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/import-fresh": {
-      "version": "3.3.0",
-      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
-      "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
-      "dev": true,
-      "dependencies": {
-        "parent-module": "^1.0.0",
-        "resolve-from": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/@parcel/optimizer-htmlnano/node_modules/csso": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz",
-      "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==",
-      "dev": true,
-      "dependencies": {
-        "css-tree": "^1.1.2"
-      },
-      "engines": {
-        "node": ">=8.0.0"
-      }
-    },
-    "node_modules/@mischnic/json-sourcemap": {
-      "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/@mischnic/json-sourcemap/-/json-sourcemap-0.1.1.tgz",
-      "integrity": "sha512-iA7+tyVqfrATAIsIRWQG+a7ZLLD0VaOCKV2Wd/v4mqIU3J9c4jx9p7S0nw1XH3gJCKNBOOwACOPYYSUu9pgT+w==",
-      "dev": true,
-      "dependencies": {
-        "@lezer/common": "^1.0.0",
-        "@lezer/lr": "^1.0.0",
-        "json5": "^2.2.1"
-      },
-      "engines": {
-        "node": ">=12.0.0"
-      }
-    },
-    "node_modules/@babel/helper-plugin-utils": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
-      "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==",
-      "dev": true,
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/escape-string-regexp": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-      "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.8.0"
-      }
-    },
-    "node_modules/@date-io/core": {
-      "version": "1.3.13",
-      "resolved": "https://registry.npmjs.org/@date-io/core/-/core-1.3.13.tgz",
-      "integrity": "sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA=="
-    },
-    "node_modules/@parcel/compressor-raw": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/compressor-raw/-/compressor-raw-2.11.0.tgz",
-      "integrity": "sha512-RArhBPRTCfz77soX2IECH09NUd76UBWujXiPRcXGPIHK+C3L1cRuzsNcA39QeSb3thz3b99JcozMJ1nkC2Bsgw==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/plugin": "2.11.0"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/timsort": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
-      "integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==",
-      "dev": true
-    },
-    "node_modules/base-x": {
-      "version": "3.0.9",
-      "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz",
-      "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==",
-      "dev": true,
-      "dependencies": {
-        "safe-buffer": "^5.0.1"
-      }
-    },
-    "node_modules/@parcel/types": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/types/-/types-2.11.0.tgz",
-      "integrity": "sha512-lN5XlfV9b1s2rli8q1LqsLtu+D4ZwNI3sKmNcL/3tohSfQcF2EgF+MaiANGo9VzXOzoWFHt4dqWjO4OcdyC5tg==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/cache": "2.11.0",
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/fs": "2.11.0",
-        "@parcel/package-manager": "2.11.0",
-        "@parcel/source-map": "^2.1.1",
-        "@parcel/workers": "2.11.0",
-        "utility-types": "^3.10.0"
-      }
-    },
-    "node_modules/braces": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
-      "dev": true,
-      "dependencies": {
-        "fill-range": "^7.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/@parcel/packager-css": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/packager-css/-/packager-css-2.11.0.tgz",
-      "integrity": "sha512-AyIxsp4eL8c22vp2oO2hSRnr3hSVNkARNZc9DG6uXxCc2Is5tUEX0I4PwxWnAx0EI44l+3zX/o414zT8yV9wwQ==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/plugin": "2.11.0",
-        "@parcel/source-map": "^2.1.1",
-        "@parcel/utils": "2.11.0",
-        "nullthrows": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/css-select/node_modules/entities": {
-      "version": "4.5.0",
-      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
-      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
-      "dev": true,
-      "optional": true,
-      "peer": true,
-      "engines": {
-        "node": ">=0.12"
-      },
-      "funding": {
-        "url": "https://github.com/fb55/entities?sponsor=1"
-      }
-    },
-    "node_modules/@parcel/utils/node_modules/has-flag": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/prop-types": {
-      "version": "15.8.1",
-      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
-      "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
-      "dependencies": {
-        "loose-envify": "^1.4.0",
-        "object-assign": "^4.1.1",
-        "react-is": "^16.13.1"
-      }
-    },
-    "node_modules/d3-interpolate": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
-      "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
-      "dependencies": {
-        "d3-color": "1 - 3"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/is-core-module": {
-      "version": "2.13.1",
-      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
-      "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
-      "dev": true,
-      "dependencies": {
-        "hasown": "^2.0.0"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/@parcel/packager-raw": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/packager-raw/-/packager-raw-2.11.0.tgz",
-      "integrity": "sha512-2/0JQ8DZrz7cVNXwD6OYoUUtSSnlr4dsz8ZkpFDKsBJhvMHtC78Sq+1EDixDGOMiUcalSEjNsoHtkpq9uNh+Xw==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/plugin": "2.11.0"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@babel/plugin-transform-react-display-name": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz",
-      "integrity": "sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw==",
-      "dev": true,
-      "dependencies": {
-        "@babel/helper-plugin-utils": "^7.22.5"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0-0"
-      }
-    },
-    "node_modules/path-parse": {
-      "version": "1.0.7",
-      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
-      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
-      "dev": true
-    },
-    "node_modules/@babel/helper-split-export-declaration": {
-      "version": "7.22.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
-      "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
-      "dev": true,
-      "dependencies": {
-        "@babel/types": "^7.22.5"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@parcel/optimizer-image": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/optimizer-image/-/optimizer-image-2.11.0.tgz",
-      "integrity": "sha512-jCaJww5QFG2GuNzYW8nlSW+Ea+Cv47TRnOPJNquFIajgfTLJ5ddsWbaNal0GQsL8yNiCBKWd1AV4W0RH9tG0Jg==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/plugin": "2.11.0",
-        "@parcel/rust": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "@parcel/workers": "2.11.0"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      },
-      "peerDependencies": {
-        "@parcel/core": "^2.11.0"
-      }
-    },
-    "node_modules/csso/node_modules/css-tree": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz",
-      "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==",
-      "dev": true,
-      "optional": true,
-      "peer": true,
-      "dependencies": {
-        "mdn-data": "2.0.28",
-        "source-map-js": "^1.0.1"
-      },
-      "engines": {
-        "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
-        "npm": ">=7.0.0"
-      }
-    },
-    "node_modules/is-glob": {
-      "version": "4.0.3",
-      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
-      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
-      "dev": true,
-      "dependencies": {
-        "is-extglob": "^2.1.1"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/hoist-non-react-statics/node_modules/react-is": {
-      "version": "16.13.1",
-      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
-      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
-    },
-    "node_modules/picocolors": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
-      "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
-      "dev": true
-    },
-    "node_modules/@babel/plugin-transform-react-jsx-development": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz",
-      "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==",
-      "dev": true,
-      "dependencies": {
-        "@babel/plugin-transform-react-jsx": "^7.22.5"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0-0"
-      }
-    },
-    "node_modules/tslib": {
-      "version": "2.6.2",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
-      "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
-      "dev": true
-    },
-    "node_modules/form-data": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
-      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
-      "dependencies": {
-        "asynckit": "^0.4.0",
-        "combined-stream": "^1.0.8",
-        "mime-types": "^2.1.12"
-      },
-      "engines": {
-        "node": ">= 6"
-      }
-    },
-    "node_modules/css-tree": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
-      "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
-      "dev": true,
-      "optional": true,
-      "peer": true,
-      "dependencies": {
-        "mdn-data": "2.0.30",
-        "source-map-js": "^1.0.1"
-      },
-      "engines": {
-        "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
-      }
-    },
-    "node_modules/@parcel/watcher-linux-x64-musl": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.4.0.tgz",
-      "integrity": "sha512-7jzcOonpXNWcSijPpKD5IbC6xC7yTibjJw9jviVzZostYLGxbz8LDJLUnLzLzhASPlPGgpeKLtFUMjAAzM+gSA==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">= 10.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/isarray": {
-      "version": "0.0.1",
-      "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
-      "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
-    },
-    "node_modules/@babel/plugin-proposal-class-properties": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz",
-      "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==",
-      "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.",
-      "dev": true,
-      "dependencies": {
-        "@babel/helper-create-class-features-plugin": "^7.18.6",
-        "@babel/helper-plugin-utils": "^7.18.6"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0-0"
-      }
-    },
-    "node_modules/scheduler": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
-      "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
-      "dependencies": {
-        "loose-envify": "^1.1.0",
-        "object-assign": "^4.1.1"
-      }
-    },
-    "node_modules/@babel/core": {
-      "version": "7.23.2",
-      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz",
-      "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==",
-      "dev": true,
-      "dependencies": {
-        "@ampproject/remapping": "^2.2.0",
-        "@babel/code-frame": "^7.22.13",
-        "@babel/generator": "^7.23.0",
-        "@babel/helper-compilation-targets": "^7.22.15",
-        "@babel/helper-module-transforms": "^7.23.0",
-        "@babel/helpers": "^7.23.2",
-        "@babel/parser": "^7.23.0",
-        "@babel/template": "^7.22.15",
-        "@babel/traverse": "^7.23.2",
-        "@babel/types": "^7.23.0",
-        "convert-source-map": "^2.0.0",
-        "debug": "^4.1.0",
-        "gensync": "^1.0.0-beta.2",
-        "json5": "^2.2.3",
-        "semver": "^6.3.1"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/babel"
-      }
-    },
-    "node_modules/d3-path": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
-      "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@parcel/watcher-linux-arm-glibc": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.4.0.tgz",
-      "integrity": "sha512-9NQXD+qk46RwATNC3/UB7HWurscY18CnAPMTFcI9Y8CTbtm63/eex1SNt+BHFinEQuLBjaZwR2Lp+n7pmEJPpQ==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">= 10.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/html-to-react/node_modules/htmlparser2": {
-      "version": "9.1.0",
-      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
-      "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==",
-      "funding": [
-        "https://github.com/fb55/htmlparser2?sponsor=1",
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/fb55"
-        }
-      ],
-      "dependencies": {
-        "domelementtype": "^2.3.0",
-        "domhandler": "^5.0.3",
-        "domutils": "^3.1.0",
-        "entities": "^4.5.0"
-      }
-    },
-    "node_modules/@parcel/transformer-svg": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/transformer-svg/-/transformer-svg-2.11.0.tgz",
-      "integrity": "sha512-GrTNi04OoQSXsyrB7FqQPeYREscEXFhIBPkyQ0q7WDG/yYynWljiA0kwITCtMjPfv2EDVks292dvM3EcnERRIA==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/diagnostic": "2.11.0",
-        "@parcel/plugin": "2.11.0",
-        "@parcel/rust": "2.11.0",
-        "nullthrows": "^1.1.1",
-        "posthtml": "^0.16.5",
-        "posthtml-parser": "^0.10.1",
-        "posthtml-render": "^3.0.0",
-        "semver": "^7.5.2"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/path-type": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
-      "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/victory-vendor": {
-      "version": "36.6.11",
-      "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.6.11.tgz",
-      "integrity": "sha512-nT8kCiJp8dQh8g991J/R5w5eE2KnO8EAIP0xocWlh9l2okngMWglOPoMZzJvek8Q1KUc4XE/mJxTZnvOB1sTYg==",
-      "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"
-      }
-    },
-    "node_modules/boolbase": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
-      "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
-      "dev": true
-    },
-    "node_modules/lightningcss": {
-      "version": "1.23.0",
-      "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.23.0.tgz",
-      "integrity": "sha512-SEArWKMHhqn/0QzOtclIwH5pXIYQOUEkF8DgICd/105O+GCgd7jxjNod/QPnBCSWvpRHQBGVz5fQ9uScby03zA==",
-      "dev": true,
-      "dependencies": {
-        "detect-libc": "^1.0.3"
-      },
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      },
-      "optionalDependencies": {
-        "lightningcss-darwin-arm64": "1.23.0",
-        "lightningcss-darwin-x64": "1.23.0",
-        "lightningcss-freebsd-x64": "1.23.0",
-        "lightningcss-linux-arm-gnueabihf": "1.23.0",
-        "lightningcss-linux-arm64-gnu": "1.23.0",
-        "lightningcss-linux-arm64-musl": "1.23.0",
-        "lightningcss-linux-x64-gnu": "1.23.0",
-        "lightningcss-linux-x64-musl": "1.23.0",
-        "lightningcss-win32-x64-msvc": "1.23.0"
-      }
-    },
-    "node_modules/has-flag": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-      "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
-      "dev": true,
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/@parcel/watcher-android-arm64": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.0.tgz",
-      "integrity": "sha512-+fPtO/GsbYX1LJnCYCaDVT3EOBjvSFdQN9Mrzh9zWAOOfvidPWyScTrHIZHHfJBvlHzNA0Gy0U3NXFA/M7PHUA==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "android"
-      ],
-      "engines": {
-        "node": ">= 10.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/react-transition-group": {
-      "version": "4.4.5",
-      "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
-      "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
-      "dependencies": {
-        "@babel/runtime": "^7.5.5",
-        "dom-helpers": "^5.0.1",
-        "loose-envify": "^1.4.0",
-        "prop-types": "^15.6.2"
-      },
-      "peerDependencies": {
-        "react": ">=16.6.0",
-        "react-dom": ">=16.6.0"
-      }
-    },
-    "node_modules/@swc/core-linux-x64-musl": {
-      "version": "1.3.107",
-      "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.107.tgz",
-      "integrity": "sha512-mvACkUvzSIB12q1H5JtabWATbk3AG+pQgXEN95AmEX2ZA5gbP9+B+mijsg7Sd/3tboHr7ZHLz/q3SHTvdFJrEw==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/cli-progress": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz",
-      "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==",
-      "dev": true,
-      "dependencies": {
-        "string-width": "^4.2.3"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/@types/react": {
-      "version": "17.0.69",
-      "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.69.tgz",
-      "integrity": "sha512-klEeru//GhiQvXUBayz0Q4l3rKHWsBR/EUOhOeow6hK2jV7MlO44+8yEk6+OtPeOlRfnpUnrLXzGK+iGph5aeg==",
-      "dependencies": {
-        "@types/prop-types": "*",
-        "@types/scheduler": "*",
-        "csstype": "^3.0.2"
-      }
-    },
-    "node_modules/css-select/node_modules/domhandler": {
-      "version": "5.0.3",
-      "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
-      "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
-      "dev": true,
-      "optional": true,
-      "peer": true,
-      "dependencies": {
-        "domelementtype": "^2.3.0"
-      },
-      "engines": {
-        "node": ">= 4"
-      },
-      "funding": {
-        "url": "https://github.com/fb55/domhandler?sponsor=1"
-      }
-    },
-    "node_modules/@parcel/markdown-ansi": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/markdown-ansi/-/markdown-ansi-2.11.0.tgz",
-      "integrity": "sha512-YA60EWbXi6cLOIzcwRC2wijotPauOGQbUi0vSbu0O6/mjQ68kWCMGz0hwZjDRQcPypQVJEIvTgMymLbvumxwhg==",
-      "dev": true,
-      "dependencies": {
-        "chalk": "^4.1.0"
-      },
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@babel/helper-function-name": {
-      "version": "7.23.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
-      "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
-      "dev": true,
-      "dependencies": {
-        "@babel/template": "^7.22.15",
-        "@babel/types": "^7.23.0"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@parcel/transformer-postcss/node_modules/yallist": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
-      "dev": true
-    },
-    "node_modules/@swc/core-linux-arm64-musl": {
-      "version": "1.3.107",
-      "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.107.tgz",
-      "integrity": "sha512-vfPF74cWfAm8hyhS8yvYI94ucMHIo8xIYU+oFOW9uvDlGQRgnUf/6DEVbLyt/3yfX5723Ln57U8uiMALbX5Pyw==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/html-to-react/node_modules/entities": {
-      "version": "4.5.0",
-      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
-      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
-      "engines": {
-        "node": ">=0.12"
-      },
-      "funding": {
-        "url": "https://github.com/fb55/entities?sponsor=1"
-      }
-    },
-    "node_modules/@parcel/optimizer-svgo/node_modules/css-select": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz",
-      "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==",
-      "dev": true,
-      "dependencies": {
-        "boolbase": "^1.0.0",
-        "css-what": "^6.0.1",
-        "domhandler": "^4.3.1",
-        "domutils": "^2.8.0",
-        "nth-check": "^2.0.1"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/fb55"
-      }
-    },
-    "node_modules/@parcel/core": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/core/-/core-2.11.0.tgz",
-      "integrity": "sha512-Npe0S6hVaqWEwRL+HI7gtOYOaoE5bJQZTgUDhsDoppWbau51jOlRYOZTXuvRK/jxXnze4/S1sdM24xBYAQ5qkw==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/rust": "2.11.0",
-        "@mischnic/json-sourcemap": "^0.1.0",
-        "@parcel/utils": "2.11.0",
-        "@parcel/package-manager": "2.11.0",
-        "@parcel/fs": "2.11.0",
-        "@parcel/diagnostic": "2.11.0",
-        "semver": "^7.5.2",
-        "@parcel/graph": "3.1.0",
-        "@parcel/source-map": "^2.1.1",
-        "@parcel/types": "2.11.0",
-        "abortcontroller-polyfill": "^1.1.9",
-        "dotenv": "^7.0.0",
-        "@parcel/events": "2.11.0",
-        "msgpackr": "^1.9.9",
-        "@parcel/profiler": "2.11.0",
-        "base-x": "^3.0.8",
-        "@parcel/plugin": "2.11.0",
-        "@parcel/cache": "2.11.0",
-        "@parcel/logger": "2.11.0",
-        "@parcel/workers": "2.11.0",
-        "dotenv-expand": "^5.1.0",
-        "json5": "^2.2.0",
-        "browserslist": "^4.6.6",
-        "clone": "^2.1.1",
-        "nullthrows": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/recharts/node_modules/react-is": {
-      "version": "16.13.1",
-      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
-      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
-    },
-    "node_modules/@babel/helper-member-expression-to-functions": {
-      "version": "7.23.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz",
-      "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==",
-      "dev": true,
-      "dependencies": {
-        "@babel/types": "^7.23.0"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/plugin-transform-react-pure-annotations": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz",
-      "integrity": "sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA==",
-      "dev": true,
-      "dependencies": {
-        "@babel/helper-annotate-as-pure": "^7.22.5",
-        "@babel/helper-plugin-utils": "^7.22.5"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0-0"
-      }
-    },
-    "node_modules/dom-serializer": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
-      "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
-      "dev": true,
-      "dependencies": {
-        "domelementtype": "^2.0.1",
-        "domhandler": "^4.2.0",
-        "entities": "^2.0.0"
-      },
-      "funding": {
-        "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
-      }
-    },
-    "node_modules/@parcel/reporter-cli/node_modules/ansi-styles": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-      "dev": true,
-      "dependencies": {
-        "color-convert": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
-      }
-    },
-    "node_modules/@parcel/transformer-babel/node_modules/yallist": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
-      "dev": true
-    },
-    "node_modules/jss-plugin-global": {
-      "version": "10.10.0",
-      "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.10.0.tgz",
-      "integrity": "sha512-icXEYbMufiNuWfuazLeN+BNJO16Ge88OcXU5ZDC2vLqElmMybA31Wi7lZ3lf+vgufRocvPj8443irhYRgWxP+A==",
-      "dependencies": {
-        "@babel/runtime": "^7.3.1",
-        "jss": "10.10.0"
-      }
-    },
-    "node_modules/mdn-data": {
-      "version": "2.0.30",
-      "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
-      "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
-      "dev": true,
-      "optional": true,
-      "peer": true
-    },
-    "node_modules/@parcel/events": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/events/-/events-2.11.0.tgz",
-      "integrity": "sha512-K6SOjOrQsz1GdNl2qKBktq7KJ3Q3yxK8WXdmQYo10wG39dr051xtMb38aqieTp4eVhL8Yaq2iJgGkdr11fuBnA==",
-      "dev": true,
-      "engines": {
-        "node": ">= 12.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@parcel/runtime-service-worker": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/runtime-service-worker/-/runtime-service-worker-2.11.0.tgz",
-      "integrity": "sha512-c8MaSpSbXIKuN5sA/g4UsrsH1BtBZ6Em+eSxt9AYbdPtWrW+qwCioNVZj9lugBRUzDMjVfJz0yK59nS42hABvw==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/plugin": "2.11.0",
-        "@parcel/utils": "2.11.0",
-        "nullthrows": "^1.1.1"
-      },
-      "engines": {
-        "node": ">= 12.0.0",
-        "parcel": "^2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      }
-    },
-    "node_modules/@parcel/config-default": {
-      "version": "2.11.0",
-      "resolved": "https://registry.npmjs.org/@parcel/config-default/-/config-default-2.11.0.tgz",
-      "integrity": "sha512-1e2+qcZkm5/0f4eI20p/DemcYiSxq9d/eyjpTXA7PulJaHbL1wonwUAuy3mvnAvDnLOJmAk/obDVgX1ZfxMGtg==",
-      "dev": true,
-      "dependencies": {
-        "@parcel/transformer-react-refresh-wrap": "2.11.0",
-        "@parcel/transformer-css": "2.11.0",
-        "@parcel/transformer-svg": "2.11.0",
-        "@parcel/transformer-babel": "2.11.0",
-        "@parcel/transformer-image": "2.11.0",
-        "@parcel/transformer-js": "2.11.0",
-        "@parcel/bundler-default": "2.11.0",
-        "@parcel/packager-html": "2.11.0",
-        "@parcel/transformer-json": "2.11.0",
-        "@parcel/packager-wasm": "2.11.0",
-        "@parcel/optimizer-svgo": "2.11.0",
-        "@parcel/optimizer-css": "2.11.0",
-        "@parcel/packager-js": "2.11.0",
-        "@parcel/packager-svg": "2.11.0",
-        "@parcel/runtime-react-refresh": "2.11.0",
-        "@parcel/compressor-raw": "2.11.0",
-        "@parcel/reporter-dev-server": "2.11.0",
-        "@parcel/packager-css": "2.11.0",
-        "@parcel/transformer-html": "2.11.0",
-        "@parcel/namer-default": "2.11.0",
-        "@parcel/resolver-default": "2.11.0",
-        "@parcel/transformer-raw": "2.11.0",
-        "@parcel/transformer-posthtml": "2.11.0",
-        "@parcel/optimizer-htmlnano": "2.11.0",
-        "@parcel/runtime-service-worker": "2.11.0",
-        "@parcel/runtime-js": "2.11.0",
-        "@parcel/optimizer-swc": "2.11.0",
-        "@parcel/packager-raw": "2.11.0",
-        "@parcel/runtime-browser-hmr": "2.11.0",
-        "@parcel/transformer-postcss": "2.11.0",
-        "@parcel/optimizer-image": "2.11.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/parcel"
-      },
-      "peerDependencies": {
-        "@parcel/core": "^2.11.0"
-      }
-    }
-  }
-}

+ 0 - 47
ui/package.json

@@ -1,47 +0,0 @@
-{
-  "name": "opencost-ui",
-  "description": "Open source UI for OpenCost",
-  "version": "0.1.0",
-  "license": "Apache-2.0",
-  "scripts": {
-    "build": "npx parcel build src/index.html",
-    "serve": "npx parcel serve src/index.html --no-cache",
-    "clean": "rm -rf dist/*",
-    "test": "echo \"Error: no test specified\" && exit 1",
-    "preinstall": "npx npm-force-resolutions"
-  },
-  "browserslist": [
-    "defaults"
-  ],
-  "dependencies": {
-    "@babel/runtime": "^7.23.9",
-    "@date-io/core": "^1.3.13",
-    "@date-io/date-fns": "^1.3.13",
-    "@material-ui/core": "^4.11.3",
-    "@material-ui/icons": "^4.11.2",
-    "@material-ui/pickers": "^3.3.10",
-    "@material-ui/styles": "^4.11.5",
-    "axios": "^1.6.0",
-    "date-fns": "^2.30.0",
-    "html-to-react": "^1.7.0",
-    "material-design-icons-iconfont": "^6.1.0",
-    "prop-types": "^15.7.2",
-    "react": "^17.0.1",
-    "react-dom": "^17.0.1",
-    "react-router-dom": "^5.2.0",
-    "recharts": "^2.2.0"
-  },
-  "devDependencies": {
-    "@babel/core": "^7.13.10",
-    "@babel/plugin-proposal-class-properties": "^7.13.0",
-    "@babel/plugin-transform-runtime": "^7.23.9",
-    "@babel/preset-react": "^7.12.13",
-    "buffer": "^6.0.3",
-    "parcel": "^2.11.0",
-    "process": "^0.11.10",
-    "set-value": "4.1.0"
-  },
-  "resolutions": {
-    "set-value": "4.1.0"
-  }
-}

+ 0 - 314
ui/src/Reports.js

@@ -1,314 +0,0 @@
-import CircularProgress from "@material-ui/core/CircularProgress";
-import IconButton from "@material-ui/core/IconButton";
-import Paper from "@material-ui/core/Paper";
-import Typography from "@material-ui/core/Typography";
-import RefreshIcon from "@material-ui/icons/Refresh";
-import { makeStyles } from "@material-ui/styles";
-import {
-  filter,
-  find,
-  forEach,
-  get,
-  isArray,
-  sortBy,
-  toArray,
-  trim,
-} from "lodash";
-import React, { useEffect, useState } from "react";
-import ReactDOM from "react-dom";
-import { useLocation, useHistory } from "react-router";
-
-import AllocationReport from "./components/allocationReport";
-import Controls from "./components/Controls";
-import Header from "./components/Header";
-import Page from "./components/Page";
-import Footer from "./components/Footer";
-import Subtitle from "./components/Subtitle";
-import Warnings from "./components/Warnings";
-import AllocationService from "./services/allocation";
-import {
-  checkCustomWindow,
-  cumulativeToTotals,
-  rangeToCumulative,
-  toVerboseTimeRange,
-} from "./util";
-import { currencyCodes } from "./constants/currencyCodes";
-
-const windowOptions = [
-  { name: "Today", value: "today" },
-  { name: "Yesterday", value: "yesterday" },
-  { name: "Last 24h", value: "24h" },
-  { name: "Last 48h", value: "48h" },
-  { name: "Week-to-date", value: "week" },
-  { name: "Last week", value: "lastweek" },
-  { name: "Last 7 days", value: "7d" },
-  { name: "Last 14 days", value: "14d" },
-];
-
-const aggregationOptions = [
-  { name: "Cluster", value: "cluster" },
-  { name: "Node", value: "node" },
-  { name: "Namespace", value: "namespace" },
-  { name: "Controller Kind", value: "controllerKind" },
-  { name: "Controller", value: "controller" },
-  { name: "DaemonSet", value: "daemonset" },
-  { name: "Deployment", value: "deployment" },
-  { name: "Job", value: "job" },
-  { name: "Service", value: "service" },
-  { name: "StatefulSet", value: "statefulset" },
-  { name: "Pod", value: "pod" },
-  { name: "Container", value: "container" },
-];
-
-const accumulateOptions = [
-  { name: "Entire window", value: true },
-  { name: "Daily", value: false },
-];
-
-const useStyles = makeStyles({
-  reportHeader: {
-    display: "flex",
-    flexFlow: "row",
-    padding: 24,
-  },
-  titles: {
-    flexGrow: 1,
-  },
-});
-
-// generateTitle generates a string title from a report object
-function generateTitle({ window, aggregateBy, accumulate }) {
-  let windowName = get(find(windowOptions, { value: window }), "name", "");
-  if (windowName === "") {
-    if (checkCustomWindow(window)) {
-      windowName = toVerboseTimeRange(window);
-    } else {
-      console.warn(`unknown window: ${window}`);
-    }
-  }
-
-  let aggregationName = get(
-    find(aggregationOptions, { value: aggregateBy }),
-    "name",
-    ""
-  ).toLowerCase();
-  if (aggregationName === "") {
-    console.warn(`unknown aggregation: ${aggregateBy}`);
-  }
-
-  let str = `${windowName} by ${aggregationName}`;
-
-  if (!accumulate) {
-    str = `${str} daily`;
-  }
-
-  return str;
-}
-
-const ReportsPage = () => {
-  const classes = useStyles();
-
-  // Allocation data state
-  const [allocationData, setAllocationData] = useState([]);
-  const [cumulativeData, setCumulativeData] = useState({});
-  const [totalData, setTotalData] = useState({});
-
-  // When allocation data changes, create a cumulative version of it
-  useEffect(() => {
-    const cumulative = rangeToCumulative(allocationData, aggregateBy);
-    setCumulativeData(toArray(cumulative));
-    setTotalData(cumulativeToTotals(cumulative));
-  }, [allocationData]);
-
-  // Form state, which controls form elements, but not the report itself. On
-  // certain actions, the form state may flow into the report state.
-  const [window, setWindow] = useState(windowOptions[0].value);
-  const [aggregateBy, setAggregateBy] = useState(aggregationOptions[0].value);
-  const [accumulate, setAccumulate] = useState(accumulateOptions[0].value);
-  const [currency, setCurrency] = useState("USD");
-
-  // Report state, including current report and saved options
-  const [title, setTitle] = useState("Last 7 days by namespace daily");
-
-  // When parameters changes, fetch data. This should be the
-  // only mechanism used to fetch data. Also generate a sensible title from the paramters.
-  useEffect(() => {
-    setFetch(true);
-    setTitle(generateTitle({ window, aggregateBy, accumulate }));
-  }, [window, aggregateBy, accumulate]);
-
-  // page and settings state
-  const [init, setInit] = useState(false);
-  const [fetch, setFetch] = useState(false);
-  const [loading, setLoading] = useState(true);
-  const [errors, setErrors] = useState([]);
-
-  // Initialize once, then fetch report each time setFetch(true) is called
-  useEffect(() => {
-    if (!init) {
-      initialize();
-    }
-    if (init || fetch) {
-      fetchData();
-    }
-  }, [init, fetch]);
-
-  // parse any context information from the URL
-  const routerLocation = useLocation();
-  const searchParams = new URLSearchParams(routerLocation.search);
-  const routerHistory = useHistory();
-  useEffect(() => {
-    setWindow(searchParams.get("window") || "7d");
-    setAggregateBy(searchParams.get("agg") || "namespace");
-    setAccumulate(searchParams.get("acc") === "true" || false);
-    setCurrency(searchParams.get("currency") || "USD");
-  }, [routerLocation]);
-
-  async function initialize() {
-    setInit(true);
-  }
-
-  async function fetchData() {
-    setLoading(true);
-    setErrors([]);
-
-    try {
-      const resp = await AllocationService.fetchAllocation(
-        window,
-        aggregateBy,
-        { accumulate }
-      );
-      if (resp.data && resp.data.length > 0) {
-        const allocationRange = resp.data;
-        for (const i in allocationRange) {
-          // update cluster aggregations to use clusterName/clusterId names
-          allocationRange[i] = sortBy(allocationRange[i], (a) => a.totalCost);
-        }
-        setAllocationData(allocationRange);
-      } else {
-        if (resp.message && resp.message.indexOf("boundary error") >= 0) {
-          let match = resp.message.match(/(ETL is \d+\.\d+% complete)/);
-          let secondary = "Try again after ETL build is complete";
-          if (match.length > 0) {
-            secondary = `${match[1]}. ${secondary}`;
-          }
-          setErrors([
-            {
-              primary: "Data unavailable while ETL is building",
-              secondary: secondary,
-            },
-          ]);
-        }
-        setAllocationData([]);
-      }
-    } catch (err) {
-      if (err.message.indexOf("404") === 0) {
-        setErrors([
-          {
-            primary: "Failed to load report data",
-            secondary:
-              "Please update OpenCost to the latest version, then open an Issue on GitHub if problems persist.",
-          },
-        ]);
-      } else {
-        let secondary =
-          "Please open an Issue on GitHub if problems persist.";
-        if (err.message.length > 0) {
-          secondary = err.message;
-        }
-        setErrors([
-          {
-            primary: "Failed to load report data",
-            secondary: secondary,
-          },
-        ]);
-      }
-      setAllocationData([]);
-    }
-
-    setLoading(false);
-    setFetch(false);
-  }
-  return (
-    <Page active="reports.html">
-      <Header>
-        <IconButton aria-label="refresh" onClick={() => setFetch(true)}>
-          <RefreshIcon />
-        </IconButton>
-      </Header>
-
-      {!loading && errors.length > 0 && (
-        <div style={{ marginBottom: 20 }}>
-          <Warnings warnings={errors} />
-        </div>
-      )}
-
-      {init && (
-        <Paper id="report">
-          <div className={classes.reportHeader}>
-            <div className={classes.titles}>
-              <Typography variant="h5">{title}</Typography>
-              <Subtitle report={{ window, aggregateBy, accumulate }} />
-            </div>
-
-            <Controls
-              windowOptions={windowOptions}
-              window={window}
-              setWindow={(win) => {
-                searchParams.set("window", win);
-                routerHistory.push({
-                  search: `?${searchParams.toString()}`,
-                });
-              }}
-              aggregationOptions={aggregationOptions}
-              aggregateBy={aggregateBy}
-              setAggregateBy={(agg) => {
-                searchParams.set("agg", agg);
-                routerHistory.push({
-                  search: `?${searchParams.toString()}`,
-                });
-              }}
-              accumulateOptions={accumulateOptions}
-              accumulate={accumulate}
-              setAccumulate={(acc) => {
-                searchParams.set("acc", acc);
-                routerHistory.push({
-                  search: `?${searchParams.toString()}`,
-                });
-              }}
-              title={title}
-              cumulativeData={cumulativeData}
-              currency={currency}
-              currencyOptions={currencyCodes}
-              setCurrency={(curr) => {
-                searchParams.set("currency", curr);
-                routerHistory.push({
-                  search: `?${searchParams.toString()}`,
-                });
-              }}
-            />
-          </div>
-
-          {loading && (
-            <div style={{ display: "flex", justifyContent: "center" }}>
-              <div style={{ paddingTop: 100, paddingBottom: 100 }}>
-                <CircularProgress />
-              </div>
-            </div>
-          )}
-          {!loading && (
-            <AllocationReport
-              allocationData={allocationData}
-              cumulativeData={cumulativeData}
-              totalData={totalData}
-              currency={currency}
-            />
-          )}
-        </Paper>
-      )}
-      <Footer/>
-    </Page>
-  );
-};
-
-export default React.memo(ReportsPage);

+ 0 - 5
ui/src/app.js

@@ -1,5 +0,0 @@
-import * as React from "react";
-import ReactDOM from "react-dom";
-import Routes from "./route";
-
-ReactDOM.render(<Routes />, document.getElementById("app"));

+ 0 - 217
ui/src/cloudCost/cloudCost.js

@@ -1,217 +0,0 @@
-import * as React from "react";
-import { get } from "lodash";
-import { makeStyles } from "@material-ui/styles";
-import {
-  Typography,
-  TableContainer,
-  TableCell,
-  TableHead,
-  TablePagination,
-  TableRow,
-  TableSortLabel,
-  Table,
-  TableBody,
-} from "@material-ui/core";
-
-import { toCurrency } from "../util";
-import CloudCostChart from "./cloudCostChart";
-import { CloudCostRow } from "./cloudCostRow";
-
-const CloudCost = ({
-  cumulativeData = [],
-  totalData: totalsRow = {},
-  graphData = [],
-  currency = "USD",
-  drilldown,
-  sampleData = false,
-}) => {
-  const useStyles = makeStyles({
-    noResults: {
-      padding: 24,
-    },
-  });
-
- 
-
-  const classes = useStyles();
-
-  function descendingComparator(a, b, orderBy) {
-    if (get(b, orderBy) < get(a, orderBy)) {
-      return -1;
-    }
-    if (get(b, orderBy) > get(a, orderBy)) {
-      return 1;
-    }
-    return 0;
-  }
-
-  function getComparator(order, orderBy) {
-    return order === "desc"
-      ? (a, b) => descendingComparator(a, b, orderBy)
-      : (a, b) => -descendingComparator(a, b, orderBy);
-  }
-
-  function stableSort(array, comparator) {
-    const stabilizedThis = array.map((el, index) => [el, index]);
-    stabilizedThis.sort((a, b) => {
-      const order = comparator(a[0], b[0]);
-      if (order !== 0) return order;
-      return a[1] - b[1];
-    });
-    return stabilizedThis.map((el) => el[0]);
-  }
-
-  const headCells = [
-    {
-      id: "name",
-      numeric: false,
-      label: "Name",
-      width: "auto",
-    },
-    {
-      id: "kubernetesPercent",
-      numeric: true,
-      label: "K8s Utilization",
-      width: 160,
-    },
-    sampleData
-      ? {
-          id: "cost",
-          numeric: true,
-          label: "Sum of Sample Data",
-          width: 200,
-        }
-      : {
-          id: "cost",
-          numeric: true,
-          label: "Total cost",
-          width: 155,
-        },
-  ];
-
-  const [order, setOrder] = React.useState("desc");
-  const [orderBy, setOrderBy] = React.useState("totalCost");
-  const [page, setPage] = React.useState(0);
-  const [rowsPerPage, setRowsPerPage] = React.useState(25);
-  const numData = cumulativeData?.length;
-
-  const lastPage = Math.floor(numData / rowsPerPage);
-
-  const handleChangePage = (event, newPage) => setPage(newPage);
-
-  const handleChangeRowsPerPage = (event) => {
-    setRowsPerPage(parseInt(event.target.value, 10));
-    setPage(0);
-  };
-
-  const orderedRows = stableSort(cumulativeData, getComparator(order, orderBy));
-  const pageRows = orderedRows.slice(
-    page * rowsPerPage,
-    page * rowsPerPage + rowsPerPage
-  );
-
-  React.useEffect(() => {
-    setPage(0);
-  }, [numData]);
-
-  if (cumulativeData.length === 0) {
-    return (
-      <Typography variant="body2" className={classes.noResults}>
-        No results
-      </Typography>
-    );
-  }
-
-  function dataToCloudCostRow(row) {
-    const suffix =
-      { hourly: "/hr", monthly: "/mo", daily: "/day" }["cumulative"] || "";
-    return (
-      <CloudCostRow
-        costSuffix={suffix}
-        cost={row.cost}
-        drilldown={drilldown}
-        key={row.name}
-        kubernetesPercent={row.kubernetesPercent}
-        name={
-          sampleData && row.labelName ? row.labelName ?? "" : row.name ?? ""
-        }
-        row={row}
-        sampleData={sampleData}
-      />
-    );
-  }
-
-  return (
-    <div id="cloud-cost">
-      <div id="cloud-graph-">
-        <CloudCostChart
-          currency={currency}
-          graphData={graphData}
-          height={300}
-          n={10}
-        />
-      </div>
-      <div id="cloud-cost-table">
-        <TableContainer>
-          <Table>
-            <TableHead>
-              <TableRow>
-                {headCells.map((cell) => (
-                  <TableCell
-                    key={cell.id}
-                    colSpan={cell.colspan}
-                    align={cell.numeric ? "right" : "left"}
-                    sortDirection={orderBy === cell.id ? order : false}
-                    style={{ width: cell.width }}
-                  >
-                    <TableSortLabel
-                      active={orderBy === cell.id}
-                      direction={orderBy === cell.id ? order : "asc"}
-                      onClick={() => {
-                        const isDesc = orderBy === cell.id && order === "desc";
-                        setOrder(isDesc ? "asc" : "desc");
-                        setOrderBy(cell.id);
-                      }}
-                    >
-                      {cell.label}
-                    </TableSortLabel>
-                  </TableCell>
-                ))}
-              </TableRow>
-            </TableHead>
-            <TableBody>
-              <TableRow>
-                <TableCell align={"left"} style={{ fontWeight: 500 }}>
-                  {totalsRow?.name || "Totals"}
-                </TableCell>
-
-                <TableCell align={"right"} style={{ fontWeight: 500 }}>
-                  {Math.round(totalsRow?.kubernetesPercent * 100)}%
-                </TableCell>
-
-                <TableCell
-                  align={"right"}
-                  style={{ fontWeight: 500, paddingRight: "2em" }}
-                >
-                  {toCurrency(totalsRow?.cost || 0, currency)}
-                </TableCell>
-              </TableRow>
-              {pageRows.map(dataToCloudCostRow)}
-            </TableBody>
-          </Table>
-        </TableContainer>
-        <TablePagination
-          component="div"
-          count={numData}
-          rowsPerPage={rowsPerPage}
-          rowsPerPageOptions={[10, 25, 50]}
-          page={Math.min(page, lastPage)}
-          onChangePage={handleChangePage}
-          onChangeRowsPerPage={handleChangeRowsPerPage}
-        />
-      </div>
-    </div>
-  );
-};
-
-export default React.memo(CloudCost);

+ 0 - 14
ui/src/cloudCost/cloudCostChart/index.js

@@ -1,14 +0,0 @@
-import * as React from "react";
-
-import Typography from "@material-ui/core/Typography";
-
-import RangeChart from "./rangeChart";
-
-const CloudCostChart = ({ graphData, currency, n, height }) => {
-  if (graphData.length === 0) {
-    return <Typography variant="body2">No data</Typography>;
-  }
-  return <RangeChart data={graphData} currency={currency} height={height} />;
-};
-
-export default React.memo(CloudCostChart);

+ 0 - 275
ui/src/cloudCost/cloudCostChart/rangeChart.js

@@ -1,275 +0,0 @@
-import * as React from "react";
-import { makeStyles } from "@material-ui/styles";
-import {
-  BarChart,
-  Bar,
-  XAxis,
-  YAxis,
-  CartesianGrid,
-  Tooltip,
-  ResponsiveContainer,
-  Cell,
-} from "recharts";
-import { primary, greyscale, browns } from "../../constants/colors";
-import { toCurrency } from "../../util";
-
-const RangeChart = ({ data, currency, height }) => {
-  const useStyles = makeStyles({
-    tooltip: {
-      borderRadius: 2,
-      background: "rgba(255, 255, 255, 0.95)",
-      padding: 12,
-    },
-    tooltipLineItem: {
-      fontSize: "1rem",
-      margin: 0,
-      marginBottom: 4,
-      padding: 0,
-    },
-  });
-
-  const accents = [...primary, ...greyscale, ...browns];
-
-  const _IDLE_ = "__idle__";
-  const _OTHER_ = "others";
-
-  const getItemCost = (item) => {
-    return item.value;
-  };
-
-  function toBar({ end, graph, start }) {
-    const points = graph.map((item) => ({
-      ...item,
-      window: { end, start },
-    }));
-
-    const dateFormatter = Intl.DateTimeFormat(navigator.language, {
-      year: "numeric",
-      month: "numeric",
-      day: "numeric",
-      timeZone: "UTC",
-    });
-
-    const timeFormatter = Intl.DateTimeFormat(navigator.language, {
-      hour: "numeric",
-      minute: "numeric",
-      timeZone: "UTC",
-    });
-
-    const s = new Date(start);
-    const e = new Date(end);
-    const interval = (e.valueOf() - s.valueOf()) / 1000 / 60 / 60;
-
-    const bar = {
-      end: new Date(end),
-      key: interval >= 24 ? dateFormatter.format(s) : timeFormatter.format(s),
-      items: {},
-      start: new Date(start),
-    };
-
-    points.forEach((item) => {
-      const windowStart = new Date(item.window.start);
-      const windowEnd = new Date(item.window.end);
-      const windowHours =
-        (windowEnd.valueOf() - windowStart.valueOf()) / 1000 / 60 / 60;
-
-      if (windowHours >= 24) {
-        bar.key = dateFormatter.format(bar.start);
-      } else {
-        bar.key = timeFormatter.format(bar.start);
-      }
-
-      bar.items[item.name] = getItemCost(item);
-    });
-
-    return bar;
-  }
-
-  const getDataForCloudDay = (dayData) => {
-    const { end, start } = dayData;
-    const copy = [...dayData.items];
-
-    // find items for idle and other
-    const idleIndex = copy.findIndex((item) => item.name === _IDLE_);
-    let idle = undefined;
-    if (idleIndex > -1) {
-      idle = copy[idleIndex];
-      copy.splice(idleIndex, 1);
-    }
-    const otherIndex = copy.findIndex(
-      (i) => i.name === _OTHER_ || i.name === "other"
-    );
-    let other = undefined;
-    if (otherIndex > -1) {
-      other = { ...copy[otherIndex], name: "other" };
-      copy.splice(otherIndex, 1);
-    }
-
-    // sort and remove any items < top 8
-    const sortedItems = copy.slice().sort((a, b) => {
-      return a.value > b.value ? -1 : 1;
-    });
-
-    const top8 = sortedItems.slice(0, 8);
-    // get items that didn't make the cut and shove into other
-    const lefovers = sortedItems.slice(8);
-    if (lefovers.length > 0) {
-      const othersTotal = lefovers.reduce((a, b) => a.value + b.value);
-      if (other) {
-        other.value += othersTotal;
-      } else if (othersTotal) {
-        other = {
-          name: "other",
-          value: othersTotal,
-        };
-      }
-    }
-    // add in idle and other
-    if (idle) {
-      top8.unshift(idle);
-    }
-    if (other) {
-      top8.unshift(other);
-    }
-
-    return { end, start, graph: top8 };
-  };
-
-  const getDataForGraph = (dataPoints) => {
-    // for each day, we want top 8 + Idle and Other
-    const orderedDataPoints = dataPoints.map(getDataForCloudDay);
-    const bars = orderedDataPoints.map(toBar);
-
-    const keyToFill = {};
-    // we want to keep track of the order of fill assignment
-    const assignmentOrder = [];
-    let p = 0;
-
-    orderedDataPoints.forEach(({ graph, start, end }) => {
-      graph.forEach(({ name }) => {
-        const key = name;
-        if (keyToFill[key] === undefined) {
-          assignmentOrder.push(key);
-          if (key === _IDLE_) {
-            keyToFill[key] = browns;
-          } else if (key === _OTHER_ || key === "other") {
-            keyToFill[key] = greyscale;
-          } else {
-            // non-idle/other allocations get the next available color
-            keyToFill[key] = accents[p];
-            p = (p + 1) % accents.length;
-          }
-        }
-      });
-    });
-    // list of dataKeys and fillColors in order of importance (price w/ 'others' last)
-    const labels = assignmentOrder.map((dataKey) => ({
-      dataKey,
-      fill: keyToFill[dataKey],
-    }));
-
-    return { bars, labels, keyToFill };
-  };
-
-  const { bars: barData, labels: barLabels, keyToFill } = getDataForGraph(data);
-
-  const classes = useStyles();
-
-  const CustomTooltip = (params) => {
-    const { active, payload } = params;
-
-    if (!payload || payload.length == 0) {
-      return null;
-    }
-
-    const total = payload.reduce((sum, item) => sum + item.value, 0.0);
-    if (active) {
-      return (
-        <div className={classes.tooltip}>
-          <p
-            className={classes.tooltipLineItem}
-            style={{ color: "#000000" }}
-          >{`Total: ${toCurrency(total, currency)}`}</p>
-
-          {payload
-            .slice()
-            .map((item, i) => (
-              <div
-                key={item.name}
-                style={{
-                  display: "grid",
-                  gridTemplateColumns: "20px 1fr",
-                  gap: ".5em",
-                  margin: ".25em",
-                }}
-              >
-                <div>
-                  <div
-                    style={{
-                      backgroundColor: keyToFill[item.payload.items[i][0]],
-                      width: 18,
-                      height: 18,
-                    }}
-                  />
-                </div>
-                <div>
-                  <p className={classes.tooltipLineItem}>{`${
-                    item.payload.items[i][0]
-                  }: ${toCurrency(item.value, currency)}`}</p>
-                </div>
-              </div>
-            ))
-            .reverse()}
-        </div>
-      );
-    }
-
-    return null;
-  };
-
-  const orderedBars = barData.map((bar) => {
-    return {
-      ...bar,
-      items: Object.entries(bar.items).sort((a, b) => {
-        if (a[0] === "other") {
-          return -1;
-        }
-        if (b[0] === "other") {
-          return 1;
-        }
-        return a[1] > b[1] ? -1 : 1;
-      }),
-    };
-  });
-
-  return (
-    <ResponsiveContainer height={height} width={"100%"}>
-      <BarChart
-        data={orderedBars}
-        margin={{ top: 30, right: 35, left: 30, bottom: 45 }}
-      >
-        <CartesianGrid strokeDasharray={"3 3"} vertical={false} />
-        <XAxis dataKey={"key"} />
-        <YAxis tickFormatter={(val) => toCurrency(val, currency, 2, true)} />
-        <Tooltip content={<CustomTooltip />} wrapperStyle={{ zIndex: 1000 }} />
-
-        {new Array(10).fill(0).map((item, idx) => (
-          <Bar
-            dataKey={(entry) => (entry.items[idx] ? entry.items[idx][1] : null)}
-            stackId="x"
-          >
-            {orderedBars.map((bar) =>
-              bar.items[idx] ? (
-                <Cell fill={keyToFill[bar.items[idx][0]]} />
-              ) : (
-                <Cell />
-              )
-            )}
-          </Bar>
-        ))}
-      </BarChart>
-    </ResponsiveContainer>
-  );
-};
-
-export default RangeChart;

+ 0 - 178
ui/src/cloudCost/cloudCostDetails.js

@@ -1,178 +0,0 @@
-import * as React from "react";
-import { Modal, Paper, Typography } from "@material-ui/core";
-import Warnings from "../components/Warnings";
-import CircularProgress from "@material-ui/core/CircularProgress";
-
-import {
-  ResponsiveContainer,
-  CartesianGrid,
-  Legend,
-  XAxis,
-  YAxis,
-  Tooltip,
-  BarChart,
-  Bar,
-} from "recharts";
-import { toCurrency } from "../util";
-import cloudCostDayTotals from "../services/cloudCostDayTotals";
-
-const CloudCostDetails = ({
-  onClose,
-  selectedProviderId,
-  selectedItem,
-  agg,
-  filters,
-  costMetric,
-  window,
-  currency,
-}) => {
-  const [data, setData] = React.useState([]);
-  const [loading, setLoading] = React.useState(false);
-  const [errors, setErrors] = React.useState([]);
-  const [fetch, setFetch] = React.useState(true);
-
-  const nextFilters = [
-    ...(filters ?? []),
-    { property: "providerID", value: selectedProviderId },
-  ];
-
-  async function fetchData() {
-    setLoading(true);
-    setErrors([]);
-
-    try {
-      const resp = await cloudCostDayTotals.fetchCloudCostData(
-        window,
-        agg,
-        costMetric,
-        nextFilters
-      );
-
-      if (resp.data) {
-        setData(resp.data);
-      } else {
-        if (resp.message && resp.message.indexOf("boundary error") >= 0) {
-          let match = resp.message.match(/(ETL is \d+\.\d+% complete)/);
-          let secondary = "Try again after ETL build is complete";
-          if (match.length > 0) {
-            secondary = `${match[1]}. ${secondary}`;
-          }
-          setErrors([
-            {
-              primary: "Data unavailable while ETL is building",
-              secondary: secondary,
-            },
-          ]);
-        }
-        setData([]);
-      }
-    } catch (err) {
-      console.log(err);
-      if (err.message.indexOf("404") === 0) {
-        setErrors([
-          {
-            primary: "Failed to load report data",
-            secondary:
-              "Please update OpenCost to the latest version, then open an Issue on GitHub if problems persist.",
-          },
-        ]);
-      } else {
-        let secondary =
-          "Please open an Issue on GitHub if problems persist.";
-        if (err.message.length > 0) {
-          secondary = err.message;
-        }
-        setErrors([
-          {
-            primary: "Failed to load report data",
-            secondary: secondary,
-          },
-        ]);
-      }
-      setData([]);
-    }
-    setLoading(false);
-    setFetch(false);
-  }
-
-  React.useEffect(() => {
-    if (fetch) {
-      fetchData();
-    }
-  }, [fetch]);
-
-  const drilldownData = data.sort(
-    (a, b) =>
-      new Date(a.date ?? "").getTime() - new Date(b.date ?? "").getTime()
-  );
-
-  const itemData = drilldownData.map((items) => {
-    const dataPoint = {
-      time: new Date(items.date),
-      cost: items.cost,
-    };
-    return dataPoint;
-  });
-
-  return (
-    <div>
-      <Modal
-        open={true}
-        onClose={onClose}
-        title={`Costs over the last ${window}`}
-        style={{ margin: "10%" }}
-      >
-        <Paper style={{ padding: 20 }}>
-          <Typography style={{ marginTop: "1rem" }} variant="body1">
-            {selectedItem}
-          </Typography>
-
-          {loading && (
-            <div style={{ display: "flex", justifyContent: "center" }}>
-              <div style={{ paddingTop: 100, paddingBottom: 100 }}>
-                <CircularProgress />
-              </div>
-            </div>
-          )}
-          {!loading && errors.length > 0 && (
-            <div style={{ marginBottom: 20 }}>
-              <Warnings warnings={errors} />
-            </div>
-          )}
-          {data && (
-            <div style={{ display: "flex", marginTop: "2.5rem" }}>
-              <ResponsiveContainer
-                height={250}
-                id={"cloud-cost-drilldown"}
-                width={"100%"}
-              >
-                <BarChart
-                  data={itemData}
-                  margin={{
-                    top: 0,
-                    bottom: 10,
-                    left: 20,
-                    right: 0,
-                  }}
-                >
-                  <CartesianGrid vertical={false} />
-                  <Legend verticalAlign={"bottom"} />
-                  <XAxis dataKey={"time"} />
-                  <YAxis tickFormatter={(tick) => `${toCurrency(tick)}`} />
-                  <Bar dataKey={"cost"} fill={"#2196f3"} name={"Item Cost"} />
-                  <Tooltip
-                    formatter={(value) =>
-                      `${toCurrency(value ?? 0, currency, 4, true)}`
-                    }
-                  />
-                </BarChart>
-              </ResponsiveContainer>
-            </div>
-          )}
-        </Paper>
-      </Modal>
-    </div>
-  );
-};
-
-export { CloudCostDetails };

+ 0 - 48
ui/src/cloudCost/cloudCostRow.js

@@ -1,48 +0,0 @@
-import * as React from "react";
-
-import { TableCell, TableRow } from "@material-ui/core";
-
-import { toCurrency } from "../util";
-import { primary } from "../constants/colors";
-
-const displayCurrencyAsLessThanPenny = (amount, currency) =>
-  amount > 0 && amount < 0.01
-    ? `<${toCurrency(0.01, currency)}`
-    : toCurrency(amount, currency);
-
-const CloudCostRow = ({
-  cost,
-  costSuffix,
-  currency,
-  drilldown,
-  kubernetesPercent,
-  name,
-  row,
-  sampleData,
-}) => {
-  function calculatePercent() {
-    const totalPercent = (kubernetesPercent * 100).toFixed();
-    return `${totalPercent}%`;
-  }
-
-  const whichPercent = sampleData
-    ? `${(kubernetesPercent * 100).toFixed(1)}%`
-    : calculatePercent();
-  return (
-    <TableRow onClick={() => drilldown(row)}>
-      <TableCell
-        align={"left"}
-        style={{ cursor: "pointer", color: "#346ef2", padding: "1rem" }}
-      >
-        {name}
-      </TableCell>
-      <TableCell align={"right"}>{whichPercent}</TableCell>
-      {/* total cost */}
-      <TableCell align={"right"} style={{ paddingRight: "2em" }}>
-        {`${displayCurrencyAsLessThanPenny(cost, currency)}${costSuffix}`}
-      </TableCell>
-    </TableRow>
-  );
-};
-
-export { CloudCostRow };

+ 0 - 91
ui/src/cloudCost/controls/cloudCostEditControls.js

@@ -1,91 +0,0 @@
-import { makeStyles } from "@material-ui/styles";
-import FormControl from "@material-ui/core/FormControl";
-import InputLabel from "@material-ui/core/InputLabel";
-import MenuItem from "@material-ui/core/MenuItem";
-import Select from "@material-ui/core/Select";
-
-import * as React from "react";
-
-import SelectWindow from "../../components/SelectWindow";
-
-const useStyles = makeStyles({
-  wrapper: {
-    display: "inline-flex",
-  },
-  formControl: {
-    margin: 8,
-    minWidth: 120,
-  },
-});
-
-function EditCloudCostControls({
-  windowOptions,
-  window,
-  setWindow,
-  aggregationOptions,
-  aggregateBy,
-  setAggregateBy,
-  costMetricOptions,
-  costMetric,
-  setCostMetric,
-  currencyOptions,
-  currency,
-  setCurrency,
-}) {
-  const classes = useStyles();
-  return (
-    <div className={classes.wrapper}>
-      <SelectWindow
-        windowOptions={windowOptions}
-        window={window}
-        setWindow={setWindow}
-      />
-      <FormControl className={classes.formControl}>
-        <InputLabel id="aggregation-select-label">Breakdown</InputLabel>
-        <Select
-          id="aggregation-select"
-          value={aggregateBy}
-          onChange={(e) => {
-            setAggregateBy(e.target.value);
-          }}
-        >
-          {aggregationOptions.map((opt) => (
-            <MenuItem key={opt.value} value={opt.value}>
-              {opt.name}
-            </MenuItem>
-          ))}
-        </Select>
-      </FormControl>
-      <FormControl className={classes.formControl}>
-        <InputLabel id="costMetric-label">Cost Metric</InputLabel>
-        <Select
-          id="costMetric"
-          value={costMetric}
-          onChange={(e) => setCostMetric(e.target.value)}
-        >
-          {costMetricOptions.map((opt) => (
-            <MenuItem key={opt.value} value={opt.value}>
-              {opt.name}
-            </MenuItem>
-          ))}
-        </Select>
-      </FormControl>
-      <FormControl className={classes.formControl}>
-        <InputLabel id="currency-label">Currency</InputLabel>
-        <Select
-          id="currency"
-          value={currency}
-          onChange={(e) => setCurrency(e.target.value)}
-        >
-          {currencyOptions?.map((currency) => (
-            <MenuItem key={currency} value={currency}>
-              {currency}
-            </MenuItem>
-          ))}
-        </Select>
-      </FormControl>
-    </div>
-  );
-}
-
-export default React.memo(EditCloudCostControls);

+ 0 - 49
ui/src/cloudCost/tokens.js

@@ -1,49 +0,0 @@
-const windowOptions = [
-  { name: "Today", value: "today" },
-  { name: "Yesterday", value: "yesterday" },
-  { name: "Last 24h", value: "24h" },
-  { name: "Last 48h", value: "48h" },
-  { name: "Week-to-date", value: "week" },
-  { name: "Last week", value: "lastweek" },
-  { name: "Last 7 days", value: "7d" },
-  { name: "Last 14 days", value: "14d" },
-];
-
-const aggregationOptions = [
-  { name: "Account", value: "accountID" },
-  { name: "Invoice Entity", value: "invoiceEntityID" },
-  { name: "Provider", value: "provider" },
-  { name: "Service ", value: "service" },
-  { name: "Category", value: "category" },
-  { name: "Item", value: "item" },
-];
-
-const costMetricOptions = [
-  { name: "Amortized Net Cost", value: "AmortizedNetCost" },
-  { name: "List Cost", value: "ListCost" },
-  { name: "Invoiced Cost", value: "InvoicedCost" },
-  { name: "Amortized Cost", value: "AmortizedCost" },
-];
-
-const aggMap = {
-  invoiceEntityID: "Invoice Entity",
-  provider: "Provider",
-  service: "Service",
-  accountID: "Account",
-};
-
-const costMetricToPropName = {
-  AmortizedNetCost: "amortizedNetCost",
-  AmortizedCost: "amortizedCost",
-  ListCost: "listCost",
-  NetCost: "netCost",
-  InvoicedCost: "invoicedCost",
-};
-
-export {
-  windowOptions,
-  aggregationOptions,
-  costMetricOptions,
-  aggMap,
-  costMetricToPropName,
-};

+ 0 - 337
ui/src/cloudCostReports.js

@@ -1,337 +0,0 @@
-import * as React from "react";
-import Page from "./components/Page";
-import Header from "./components/Header";
-import Footer from "./components/Footer";
-import IconButton from "@material-ui/core/IconButton";
-import RefreshIcon from "@material-ui/icons/Refresh";
-import { makeStyles } from "@material-ui/styles";
-import { Box, Link, Paper, Typography } from "@material-ui/core";
-import CircularProgress from "@material-ui/core/CircularProgress";
-import { get, find } from "lodash";
-import { useLocation, useHistory } from "react-router";
-
-import { checkCustomWindow, toVerboseTimeRange } from "./util";
-import CloudCostEditControls from "./cloudCost/controls/cloudCostEditControls";
-import Subtitle from "./components/Subtitle";
-import Warnings from "./components/Warnings";
-import CloudCostTopService from "./services/cloudCostTop";
-
-import {
-  windowOptions,
-  costMetricOptions,
-  aggregationOptions,
-  aggMap,
-} from "./cloudCost/tokens";
-import { currencyCodes } from "./constants/currencyCodes";
-import CloudCost from "./cloudCost/cloudCost";
-import { CloudCostDetails } from "./cloudCost/cloudCostDetails";
-
-const CloudCostReports = () => {
-  const useStyles = makeStyles({
-    reportHeader: {
-      display: "flex",
-      flexFlow: "row",
-      padding: 24,
-    },
-    titles: {
-      flexGrow: 1,
-    },
-  });
-  const classes = useStyles();
-
-  // Form state, which controls form elements, but not the report itself. On
-  // certain actions, the form state may flow into the report state.
-  const [title, setTitle] = React.useState(
-    "Cumulative cost for last 7 days by account"
-  );
-  const [window, setWindow] = React.useState(windowOptions[0].value);
-  const [aggregateBy, setAggregateBy] = React.useState(
-    aggregationOptions[0].value
-  );
-  const [costMetric, setCostMetric] = React.useState(
-    costMetricOptions[0].value
-  );
-  const [filters, setFilters] = React.useState([]);
-  const [currency, setCurrency] = React.useState("USD");
-  const [selectedProviderId, setSelectedProviderId] = React.useState("");
-  const [selectedItemName, setselectedItemName] = React.useState("");
-  const sampleData = aggregateBy.includes("item");
-  // page and settings state
-  const [init, setInit] = React.useState(false);
-  const [fetch, setFetch] = React.useState(false);
-  const [loading, setLoading] = React.useState(true);
-  const [errors, setErrors] = React.useState([]);
-
-  // data
-  const [cloudCostData, setCloudCostData] = React.useState([]);
-
-  function generateTitle({ window, aggregateBy, costMetric }) {
-    let windowName = get(find(windowOptions, { value: window }), "name", "");
-    if (windowName === "") {
-      if (checkCustomWindow(window)) {
-        windowName = toVerboseTimeRange(window);
-      } else {
-        console.warn(`unknown window: ${window}`);
-      }
-    }
-
-    let aggregationName = get(
-      find(aggregationOptions, { value: aggregateBy }),
-      "name",
-      ""
-    ).toLowerCase();
-    if (aggregationName === "") {
-      console.warn(`unknown aggregation: ${aggregateBy}`);
-    }
-
-    let str = `Cumulative cost for ${windowName} by ${aggregationName}`;
-
-    if (!costMetric) {
-      str = `${str} amoritizedNetCost`;
-    }
-
-    return str;
-  }
-
-  // parse any context information from the URL
-  const routerLocation = useLocation();
-  const searchParams = new URLSearchParams(routerLocation.search);
-  const routerHistory = useHistory();
-
-  async function initialize() {
-    setInit(true);
-  }
-
-  async function fetchData() {
-    setLoading(true);
-    setErrors([]);
-    try {
-      const resp = await CloudCostTopService.fetchCloudCostData(
-        window,
-        aggregateBy,
-        costMetric,
-        filters
-      );
-      if (resp) {
-        setCloudCostData(resp);
-      } else {
-        if (resp.message && resp.message.indexOf("boundary error") >= 0) {
-          let match = resp.message.match(/(ETL is \d+\.\d+% complete)/);
-          let secondary = "Try again after ETL build is complete";
-          if (match.length > 0) {
-            secondary = `${match[1]}. ${secondary}`;
-          }
-          setErrors([
-            {
-              primary: "Data unavailable while ETL is building",
-              secondary: secondary,
-            },
-          ]);
-        }
-        setCloudCostData([]);
-      }
-    } catch (err) {
-      if (err.message.indexOf("404") === 0) {
-        setErrors([
-          {
-            primary: "Failed to load report data",
-            secondary:
-            "Please update OpenCost to the latest version, and open an Issue if problems persist.",
-          },
-        ]);
-      } else {
-        let secondary =
-          "Please open an Issue with OpenCost if problems persist.";
-        if (err.message.length > 0) {
-          secondary = err.message;
-        }
-        setErrors([
-          {
-            primary: "Failed to load report data",
-            secondary: secondary,
-          },
-        ]);
-      }
-      setCloudCostData([]);
-    }
-    setLoading(false);
-  }
-
-  function drilldown(row) {
-    if (aggregateBy.includes("item")) {
-      try {
-        setSelectedProviderId(row.providerID);
-        setselectedItemName(row.labelName ?? row.name);
-      } catch (e) {
-        logger.error(e);
-      }
-
-      return;
-    }
-    const nameParts = row.name.split("/");
-    const nextAgg = aggregateBy.includes("service") ? "item" : "service";
-    const aggToString = [aggregateBy];
-    const newFilters = aggToString.map((property, i) => {
-      const value = nameParts[i];
-      return {
-        property,
-        value,
-      };
-    });
-    setFilters(newFilters);
-    setAggregateBy(nextAgg);
-  }
-
-  React.useEffect(() => {
-    setWindow(searchParams.get("window") || "7d");
-    setAggregateBy(searchParams.get("agg") || "provider");
-    setCostMetric(searchParams.get("costMetric") || "AmortizedNetCost");
-    setCurrency(searchParams.get("currency") || "USD");
-  }, [routerLocation]);
-
-  // Initialize once, then fetch report each time setFetch(true) is called
-  React.useEffect(() => {
-    if (!init) {
-      initialize();
-    }
-    if (init || fetch) {
-      fetchData();
-    }
-  }, [init, fetch]);
-
-  React.useEffect(() => {
-    setFetch(!fetch);
-    setTitle(generateTitle({ window, aggregateBy, costMetric }));
-  }, [window, aggregateBy, costMetric, filters]);
-
-  const hasCloudCostEnabled = aggregateBy.includes("item")
-    ? true // this is kind of hacky but something weird is happening
-    : // when drilling down will address in a later PR - @jjarrett21
-      !!cloudCostData.cloudCostStatus?.length;
-
-  const enabledWarnings = [
-    {
-      primary: "There are no Cloud Cost integrations currently configured.",
-      secondary: (
-        <>
-          Learn more about setting up Cloud Costs{" "}
-          <Link
-            href={
-              "https://www.opencost.io/docs/configuration/#cloud-costs"
-            }
-            target="_blank"
-          >
-            here
-          </Link>
-        </>
-      ),
-    },
-  ];
-
-  return (
-    <Page active="cloud.html">
-      <Header>
-        <IconButton aria-label="refresh" onClick={() => setFetch(true)}>
-          <RefreshIcon />
-        </IconButton>
-      </Header>
-
-      {!loading && !hasCloudCostEnabled && (
-        <div style={{ marginBottom: 20 }}>
-          <Warnings warnings={enabledWarnings} />
-        </div>
-      )}
-
-      {!loading && errors.length > 0 && hasCloudCostEnabled && (
-        <div style={{ marginBottom: 20 }}>
-          <Warnings warnings={errors} />
-        </div>
-      )}
-
-      {init && hasCloudCostEnabled && (
-        <Paper id="cloud-cost">
-          <div className={classes.reportHeader}>
-            <div className={classes.titles}>
-              <Typography variant="h5">{title}</Typography>
-              <Subtitle report={{ window, aggregateBy }} />
-            </div>
-            <CloudCostEditControls
-              windowOptions={windowOptions}
-              window={window}
-              setWindow={(win) => {
-                searchParams.set("window", win);
-                routerHistory.push({
-                  search: `?${searchParams.toString()}`,
-                });
-              }}
-              aggregationOptions={aggregationOptions}
-              aggregateBy={aggregateBy}
-              setAggregateBy={(agg) => {
-                setFilters([])
-                searchParams.set("agg", agg);
-                routerHistory.push({
-                  search: `?${searchParams.toString()}`,
-                });
-              }}
-              costMetricOptions={costMetricOptions}
-              costMetric={costMetric}
-              setCostMetric={(c) => {
-                searchParams.set("costMetric", c);
-                routerHistory.push({
-                  search: `?${searchParams.toString()}`,
-                });
-              }}
-              title={title}
-              // cumulativeData={cumulativeData}
-              currency={currency}
-              currencyOptions={currencyCodes}
-              setCurrency={(curr) => {
-                searchParams.set("currency", curr);
-                routerHistory.push({
-                  search: `?${searchParams.toString()}`,
-                });
-              }}
-            />
-          </div>
-
-          {loading && (
-            <div style={{ display: "flex", justifyContent: "center" }}>
-              <div style={{ paddingTop: 100, paddingBottom: 100 }}>
-                <CircularProgress />
-              </div>
-            </div>
-          )}
-
-          {!loading && (
-            <CloudCost
-              cumulativeData={cloudCostData.tableRows}
-              currency={currency}
-              graphData={cloudCostData.graphData}
-              totalData={cloudCostData.tableTotal}
-              drilldown={drilldown}
-              sampleData={sampleData}
-            />
-          )}
-          {selectedProviderId && selectedItemName && (
-            <CloudCostDetails
-              onClose={() => {
-                setSelectedProviderId("");
-                setselectedItemName("");
-              }}
-              selectedProviderId={selectedProviderId}
-              selectedItem={selectedItemName}
-              agg={aggregateBy}
-              filters={filters}
-              costMetric={costMetric}
-              window={window}
-              currency={currency}
-            />
-          )}
-        </Paper>
-      )}
-      <Footer/>
-    </Page>
-  );
-};
-
-export default React.memo(CloudCostReports);

+ 0 - 149
ui/src/components/AllocationChart/RangeChart.js

@@ -1,149 +0,0 @@
-import React from 'react'
-import { reverse } from 'lodash'
-import { makeStyles } from '@material-ui/styles'
-import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'
-import { primary, greyscale, browns } from '../../constants/colors';
-import { toCurrency } from '../../util';
-
-const useStyles = makeStyles({
-  tooltip: {
-    borderRadius: 2,
-    background: 'rgba(255, 255, 255, 0.95)',
-    padding: 12,
-  },
-  tooltipLineItem: {
-    fontSize: '1rem',
-    margin: 0,
-    marginBottom: 4,
-    padding: 0,
-  },
-})
-
-function toBarLabels(allocationRange) {
-  let keyToFill = {}
-  let p = 0
-  let g = 0
-  let b = 0
-
-  for (const { idle } of allocationRange) {
-    for (const allocation of idle) {
-      const key = allocation.name
-      if (keyToFill[key] === undefined) {
-        // idle allocations are assigned grey
-        keyToFill[key] = greyscale[g]
-        g = (g+1) % greyscale.length
-      }
-    }
-  }
-
-  for (const { top } of allocationRange) {
-    for (const allocation of top) {
-      const key = allocation.name
-      if (keyToFill[key] === undefined) {
-        if (key === "__unallocated__") {
-          // unallocated gets black (clean up)
-          keyToFill[key] = "#212121"
-        } else {
-          // non-idle allocations get the next available color
-          keyToFill[key] = primary[p]
-          p = (p+1) % primary.length
-        }
-      }
-    }
-  }
-
-  for (const { other } of allocationRange) {
-    for (const allocation of other) {
-      const key = allocation.name
-      if (keyToFill[key] === undefined) {
-        // idle allocations are assigned grey
-        keyToFill[key] = browns[b]
-        b = (b+1) % browns.length
-      }
-    }
-  }
-
-  let labels = []
-  for (const key in keyToFill) {
-    labels.push({
-      dataKey: key,
-      fill: keyToFill[key],
-    })
-  }
-
-  return reverse(labels)
-}
-
-function toBar(datum) {
-  const { top, other, idle } = datum
-  const bar = {}
-
-  for (const key in top) {
-    const allocation = top[key]
-    const start = new Date(allocation.start)
-    bar.start = `${start.getUTCFullYear()}-${start.getUTCMonth()+1}-${start.getUTCDate()}`
-    bar[allocation.name] = allocation.totalCost
-  }
-
-  for (const key in other) {
-    const allocation = other[key]
-    const start = new Date(allocation.start)
-    bar.start = `${start.getUTCFullYear()}-${start.getUTCMonth()+1}-${start.getUTCDate()}`
-    bar[allocation.name] = allocation.totalCost
-  }
-
-  for (const key in idle) {
-    const allocation = idle[key]
-    const start = new Date(allocation.start)
-    bar.start = `${start.getUTCFullYear()}-${start.getUTCMonth()+1}-${start.getUTCDate()}`
-    bar[allocation.name] = allocation.totalCost
-  }
-
-  return bar
-}
-
-const RangeChart = ({ data, currency, height }) => {
-  const classes = useStyles()
-
-  const barData = data.map(toBar)
-  const barLabels = toBarLabels(data)
-
-  const CustomTooltip = (params) => {
-    const { active, payload } = params
-
-    if (!payload || payload.length == 0) {
-      return null
-    }
-
-    const total = payload.reduce((sum, item) => sum + item.value, 0.0)
-    if (active) {
-      return (
-        <div className={classes.tooltip}>
-          <p className={classes.tooltipLineItem} style={{ color: '#000000' }}>{`Total: ${toCurrency(total, currency)}`}</p>
-          {reverse(payload).map((item, i) => (
-            <p key={i} className={classes.tooltipLineItem} style={{ color: item.fill }}>{`${item.name}: ${toCurrency(item.value, currency)}`}</p>
-          ))}
-        </div>
-      )
-    }
-
-    return null
-  }
-
-  return (
-    <ResponsiveContainer width="100%" height={height}>
-      <BarChart
-        data={barData}
-        margin={{ top: 30, right: 30, left: 30, bottom: 12 }}
-      >
-        <CartesianGrid strokeDasharray="3 3" />
-        <XAxis dataKey="start" />
-        <YAxis />
-        <Tooltip content={<CustomTooltip />} />
-        {barLabels.map((barLabel, i) => <Bar key={i} dataKey={barLabel.dataKey} stackId="a" fill={barLabel.fill} />)}
-      </BarChart>
-    </ResponsiveContainer>
-  )
-}
-
-export default RangeChart

+ 0 - 96
ui/src/components/AllocationChart/SummaryChart.js

@@ -1,96 +0,0 @@
-import React from 'react'
-import { ResponsiveContainer, PieChart, Pie, Cell } from 'recharts'
-import { primary, greyscale, browns } from '../../constants/colors';
-import { toCurrency } from '../../util';
-
-function toPieData(top, other, idle) {
-  let slices = []
-
-  for (const i in top) {
-    const allocation = top[i]
-    const fill = allocation.name === "__unallocated__"
-      ? "#212121"
-      : primary[i % primary.length]
-
-    slices.push({
-      name: allocation.name,
-      value: allocation.totalCost,
-      fill: fill,
-    })
-  }
-
-  for (const i in other) {
-    const allocation = other[i]
-    const fill = browns[i % browns.length]
-    slices.push({
-      name: allocation.name,
-      value: allocation.totalCost,
-      fill: fill,
-    })
-  }
-
-  for (const i in idle) {
-    const allocation = idle[i]
-    const fill = greyscale[i % greyscale.length]
-    slices.push({
-      name: allocation.name,
-      value: allocation.totalCost,
-      fill: fill,
-    })
-  }
-
-  return slices
-}
-
-const SummaryChart = ({ top, other, idle, currency, height }) => {
-  const pieData = toPieData(top, other, idle)
-
-  const renderLabel = (params) => {
-    const {
-      cx, cy, midAngle, outerRadius, percent, name, fill, value
-    } = params
-
-    const RADIAN = Math.PI / 180
-    const radius = outerRadius * 1.1
-    let x = cx + radius * Math.cos(-midAngle * RADIAN)
-    x += x > cx ? 2 : -2
-    let y = cy + radius * Math.sin(-midAngle * RADIAN)
-    // y -= Math.min(Math.abs(2 / Math.cos(-midAngle * RADIAN)), 8)
-
-    if (percent < 0.02) {
-      return
-    }
-
-    return (
-      <text x={x} y={y} fill={fill} textAnchor={x > cx ? 'start' : 'end'} dominantBaseline="central">
-        {`${name}: ${toCurrency(value, currency)} (${(percent * 100).toFixed(1)}%)`}
-      </text>
-    )
-  }
-
-  return (
-    <ResponsiveContainer width="100%" height={height}>
-      <PieChart>
-        <Pie
-          data={pieData}
-          dataKey="value"
-          nameKey="name"
-          label={renderLabel}
-          labelLine
-          // niko: if tooltips error, try disabling animation
-          // isAnimationActive={false}
-          animationDuration={400}
-          cy="90%"
-          outerRadius="140%"
-          innerRadius="60%"
-          startAngle={180}
-          endAngle={0}
-        >
-          {pieData.map((datum, i) => <Cell key={i} fill={datum.fill} />)}
-        </Pie>
-      </PieChart>
-    </ResponsiveContainer>
-  )
-}
-
-export default SummaryChart

+ 0 - 81
ui/src/components/AllocationChart/index.js

@@ -1,81 +0,0 @@
-import React from 'react'
-import { isArray, filter, map, reduce, reverse, sortBy } from 'lodash'
-
-import Typography from '@material-ui/core/Typography'
-
-import RangeChart from './RangeChart'
-import SummaryChart from './SummaryChart'
-
-// TODO niko/etl
-// sum allocationSet to single allocation
-function agg(allocationSet, name) {
-  if (allocationSet.length === 0) {
-    return null
-  }
-
-  return reduce(allocationSet, (agg, cur) => ({
-    name: agg.name,
-    aggregatedBy: cur.aggregatedBy,
-    properties: agg.properties,
-    start: cur.start,
-    end: cur.end,
-    cpuCost: agg.cpuCost + cur.cpuCost,
-    gpuCost: agg.gpuCost + cur.gpuCost,
-    ramCost: agg.ramCost + cur.ramCost,
-    pvCost: agg.pvCost + cur.pvCost,
-    totalCost: agg.totalCost + cur.totalCost,
-    count: agg.count + 1
-  }), {
-    name: name,
-    properties: null,
-    cpuCost: 0.0,
-    gpuCost: 0.0,
-    ramCost: 0.0,
-    pvCost: 0.0,
-    totalCost: 0.0,
-    count: 0,
-  })
-}
-
-function isIdle(allocation) {
-  return allocation.name.indexOf('__idle__') >= 0
-}
-
-function top(n, by) {
-  return (allocations) => {
-    if (isArray(allocations[0])) {
-      return map(allocations, top(n, by))
-    }
-
-    const sorted = reverse(sortBy(allocations, by))
-    const active = filter(sorted, a => !isIdle(a))
-    const idle = filter(sorted, a => isIdle(a))
-    const topn = active.slice(0, n)
-    const other = []
-    if (active.length > n) {
-      other.push(agg(active.slice(n), 'other'))
-    }
-
-    return {
-      top: topn,
-      other: other,
-      idle: idle,
-    }
-  }
-}
-
-const AllocationChart = ({ allocationRange, currency, n, height }) => {
-  if (allocationRange.length === 0) {
-    return <Typography variant="body2">No data</Typography>
-  }
-
-  if (allocationRange.length === 1) {
-    const datum = top(n, alloc => alloc.totalCost)(allocationRange[0])
-    return <SummaryChart top={datum.top} other={datum.other} idle={datum.idle} currency={currency} height={height} />
-  }
-
-  const data = top(n, alloc => alloc.totalCost)(allocationRange)
-  return <RangeChart data={data} currency={currency} height={height} />
-}
-
-export default React.memo(AllocationChart)

+ 0 - 89
ui/src/components/Controls/Download.js

@@ -1,89 +0,0 @@
-import React from 'react'
-import { get, forEach, reverse, round, sortBy } from 'lodash'
-import ExportIcon from '@material-ui/icons/GetApp'
-import IconButton from '@material-ui/core/IconButton'
-import Tooltip from '@material-ui/core/Tooltip'
-
-const columns = [
-  {
-    head: "Name",
-    prop: "name",
-    currency: false,
-  }, {
-    head: "CPU",
-    prop: "cpuCost",
-    currency: true,
-  }, {
-    head: "GPU",
-    prop: "gpuCost",
-    currency: true,
-  }, {
-    head: "RAM",
-    prop: "ramCost",
-    currency: true,
-  }, {
-    head: "PV",
-    prop: "pvCost",
-    currency: true,
-  }, {
-    head: "Network",
-    prop: "networkCost",
-    currency: true,
-  }, {
-    head: "Shared",
-    prop: "sharedCost",
-    currency: true,
-  }, {
-    head: "Total",
-    prop: "totalCost",
-    currency: true,
-  }
-]
-
-const toCSVLine = (datum) => {
-  let cols = []
-
-  forEach(columns, c => {
-    if (c.currency) {
-      cols.push(round(get(datum, c.prop, 0.0), 2))
-    } else {
-      cols.push(`"${get(datum, c.prop, "")}"`)
-    }
-  })
-
-  return cols.join(',')
-}
-
-const DownloadControl = ({
-  cumulativeData,
-  title,
-}) => {
-  // downloadReport downloads a CSV of the cumulative allocation data
-  function downloadReport() {
-    // Build CSV
-    const head = columns.map(c => c.head).join(',')
-    const body = reverse(sortBy(cumulativeData, 'totalCost')).map(toCSVLine).join('\r\n')
-    const csv = `${head}\r\n${body}`
-
-    // Create download link
-    const a = document.createElement("a")
-    a.href = URL.createObjectURL(new Blob([csv], { type: "text/csv" }))
-    const filename = title.toLowerCase().replace(/\s/gi, '-')
-    a.setAttribute("download", `${filename}-${Date.now()}.csv`)
-
-    // Click the link
-    document.body.appendChild(a)
-    a.click()
-    document.body.removeChild(a)
-  }
-
-  return (
-    <Tooltip title="Download CSV">
-      <IconButton onClick={downloadReport}>
-        <ExportIcon />
-      </IconButton>
-    </Tooltip>
-  )
-}
-
-export default React.memo(DownloadControl)

+ 0 - 74
ui/src/components/Controls/Edit.js

@@ -1,74 +0,0 @@
-import { makeStyles } from '@material-ui/styles';
-import FormControl from '@material-ui/core/FormControl'
-import InputLabel from '@material-ui/core/InputLabel'
-import MenuItem from '@material-ui/core/MenuItem'
-import Select from '@material-ui/core/Select'
-
-import React from 'react';
-
-import SelectWindow from '../SelectWindow';
-
-const useStyles = makeStyles({
-  wrapper: {
-    display: 'inline-flex',
-  },
-  formControl: {
-    margin: 8,
-    minWidth: 120,
-  },
-});
-
-function EditControl({
-  windowOptions, window, setWindow,
-  aggregationOptions, aggregateBy, setAggregateBy,
-  accumulateOptions, accumulate, setAccumulate,
-  currencyOptions, currency, setCurrency,
-}) {
-  const classes = useStyles();
-  return (
-    <div className={classes.wrapper}>
-      <SelectWindow
-        windowOptions={windowOptions}
-        window={window}
-        setWindow={setWindow} />
-      <FormControl className={classes.formControl}>
-        <InputLabel id="aggregation-select-label">Breakdown</InputLabel>
-        <Select
-          id="aggregation-select"
-          value={aggregateBy}
-          onChange={e => {
-            setAggregateBy(e.target.value)
-          }}
-        >
-          {aggregationOptions.map((opt) => <MenuItem key={opt.value} value={opt.value}>{opt.name}</MenuItem>)}
-        </Select>
-      </FormControl>
-      <FormControl className={classes.formControl}>
-        <InputLabel id="accumulate-label">Resolution</InputLabel>
-        <Select
-          id="accumulate"
-          value={accumulate}
-          onChange={e => setAccumulate(e.target.value)}
-        >
-          {accumulateOptions.map((opt) => <MenuItem key={opt.value} value={opt.value}>{opt.name}</MenuItem>)}
-        </Select>
-      </FormControl>
-      <FormControl className={classes.formControl}>
-        <InputLabel id="currency-label">Currency</InputLabel>
-        <Select
-          id="currency"
-          value={currency}
-          onChange={e => setCurrency(e.target.value)}
-        >
-          {currencyOptions?.map((currency) => (
-            <MenuItem key={currency} value={currency}>
-              {currency}
-            </MenuItem>
-          ))}
-        </Select>
-      </FormControl>
-    </div>
-  );
-}
-
-export default React.memo(EditControl);

+ 0 - 48
ui/src/components/Controls/index.js

@@ -1,48 +0,0 @@
-import React from 'react'
-import { makeStyles } from '@material-ui/styles'
-import EditControl from './Edit'
-import DownloadControl from './Download'
-
-const Controls = ({
-  windowOptions,
-  window,
-  setWindow,
-  aggregationOptions,
-  aggregateBy,
-  setAggregateBy,
-  accumulateOptions,
-  accumulate,
-  setAccumulate,
-  title,
-  cumulativeData,
-  currency,
-  currencyOptions,
-  setCurrency,
-}) => {
-
-  return (
-    <div>
-      <EditControl
-        windowOptions={windowOptions}
-        window={window}
-        setWindow={setWindow}
-        aggregationOptions={aggregationOptions}
-        aggregateBy={aggregateBy}
-        setAggregateBy={setAggregateBy}
-        accumulateOptions={accumulateOptions}
-        accumulate={accumulate}
-        setAccumulate={setAccumulate}
-        currency={currency}
-        currencyOptions={currencyOptions}
-        setCurrency={setCurrency}
-      />
-
-      <DownloadControl
-        cumulativeData={cumulativeData}
-        title={title}
-      />
-    </div>
-  )
-}
-
-export default React.memo(Controls)

+ 0 - 227
ui/src/components/Details.js

@@ -1,227 +0,0 @@
-import React, { memo, useEffect, useState } from 'react';
-import { forEach, get, reverse, round, sortBy } from 'lodash';
-import CircularProgress from '@material-ui/core/CircularProgress';
-import ClusterIcon from '@material-ui/icons/GroupWork';
-import NodeIcon from '@material-ui/icons/Memory';
-import List from '@material-ui/core/List';
-import ListItem from '@material-ui/core/ListItem';
-import ListItemIcon from '@material-ui/core/ListItemIcon';
-import ListItemText from '@material-ui/core/ListItemText';
-import Table from '@material-ui/core/Table';
-import TableBody from '@material-ui/core/TableBody';
-import TableCell from '@material-ui/core/TableCell';
-import TableContainer from '@material-ui/core/TableContainer';
-import TableHead from '@material-ui/core/TableHead';
-import TableRow from '@material-ui/core/TableRow';
-import Warnings from './Warnings';
-import AllocationService from '../services/allocation';
-import { bytesToString, toCurrency } from '../util';
-
-const Details = ({
-  window,
-  namespace,
-  controllerKind,
-  controller,
-  pod,
-  currency,
-}) => {
-  const [cluster, setCluster] = useState('')
-  const [node, setNode] = useState('')
-
-  const [fetch, setFetch] = useState(true)
-  const [loading, setLoading] = useState(false)
-  const [errors, setErrors] = useState([])
-  const [rows, setRows] = useState([])
-
-  useEffect(() => {
-    if (fetch) {
-      setCluster('')
-      setNode('')
-      fetchData()
-    }
-  }, [fetch])
-
-  async function fetchData() {
-    setLoading(true)
-    setErrors([])
-
-    try {
-      const filters = []
-
-      if (cluster) {
-        filters.push({
-          property: "cluster",
-          value: cluster,
-        })
-      }
-
-      if (node) {
-        filters.push({
-          property: "node",
-          value: node,
-        })
-      }
-
-      if (namespace) {
-        filters.push({
-          property: "namespace",
-          value: namespace,
-        })
-      }
-
-      if (controllerKind) {
-        filters.push({
-          property: "controllerKind",
-          value: controllerKind,
-        })
-      }
-
-      if (controller) {
-        filters.push({
-          property: "controller",
-          value: controller,
-        })
-      }
-
-      if (pod) {
-        filters.push({
-          property: "pod",
-          value: pod,
-        })
-      }
-
-      const resp = await AllocationService.fetchAllocation(window, '', { accumulate: true })
-
-      let data = []
-      forEach(resp.data[0], (datum) => {
-        if (datum.name === "__idle__") {
-          return
-        }
-
-        if (!cluster) {
-          setCluster(get(datum, 'properties.cluster', ''))
-        }
-
-        if (!node) {
-          setNode(get(datum, 'properties.node', ''))
-        }
-
-        // TODO can we get pod, container back in properties?
-        const names = datum.name.split("/")
-        datum.pod = names[names.length-2]
-        datum.container = names[names.length-1]
-
-        datum.hours = round(get(datum, 'minutes', 0.0) / 60.0, 2)
-
-        if (datum.hours > 0) {
-          datum.cpu = round(get(datum, 'cpuCoreHours', 0.0) / datum.hours, 2)
-          datum.cpuCostPerCoreHr = datum.cpuCost / (datum.cpu * datum.hours)
-          if (datum.cpu === 0) {
-            datum.cpuCostPerCoreHr = 0.0
-          }
-
-          datum.ram = round(get(datum, 'ramByteHours', 0.0) / datum.hours, 2)
-          const ramGiB = datum.ram / 1024 / 1024 / 1024
-          datum.ramCostPerGiBHr = datum.ramCost / (ramGiB * datum.hours)
-          if (ramGiB === 0) {
-            datum.ramCostPerGiBHr = 0.0
-          }
-        } else {
-          datum.cpu = 0.0
-          datum.cpuCostPerCoreHr = 0.0
-          datum.ram = 0.0
-          datum.ramCostPerGiBHr = 0.0
-        }
-
-        data.push(datum)
-      })
-
-      data = reverse(sortBy(data, 'totalCost'))
-
-      setRows(data)
-    } catch (e) {
-      console.warn(`Error fetching details for (${controllerKind}, ${controller}):`, e)
-      setErrors([{
-        primary: "Error fetching details",
-        secondary: `Tried fetching details for: ${namespace}, ${controllerKind}, ${controller}, ${pod}`,
-      }])
-    }
-
-    setLoading(false)
-    setFetch(false)
-  }
-
-  if (loading) {
-    return (
-      <div style={{ display: 'flex', justifyContent: 'center' }}>
-        <div style={{ paddingTop: 100, paddingBottom: 100 }}>
-          <CircularProgress />
-        </div>
-      </div>
-    )
-  }
-
-  return (
-    <div>
-
-      {!loading && errors.length > 0 && (
-        <div style={{ marginBottom: 20 }}>
-          <Warnings warnings={errors} />
-        </div>
-      )}
-
-      <List>
-        {cluster && (
-          <ListItem>
-            <ListItemIcon>
-              <ClusterIcon />
-            </ListItemIcon>
-            <ListItemText primary={cluster} />
-          </ListItem>
-        )}
-        {node && (
-        <ListItem>
-          <ListItemIcon>
-            <NodeIcon />
-          </ListItemIcon>
-          <ListItemText primary={node} />
-        </ListItem>
-        )}
-      </List>
-      <TableContainer>
-        <Table>
-          <TableHead>
-            <TableRow>
-              <TableCell align="left" component="th" scope="row" width={200}>Container</TableCell>
-              <TableCell align="right" component="th" scope="row">Hours</TableCell>
-              <TableCell align="right" component="th" scope="row">CPU</TableCell>
-              <TableCell align="right" component="th" scope="row">$/(CPU*Hr)</TableCell>
-              <TableCell align="right" component="th" scope="row">CPU cost</TableCell>
-              <TableCell align="right" component="th" scope="row">RAM</TableCell>
-              <TableCell align="right" component="th" scope="row">$/(GiB*Hr)</TableCell>
-              <TableCell align="right" component="th" scope="row">RAM cost</TableCell>
-              <TableCell align="right" component="th" scope="row">Total cost</TableCell>
-            </TableRow>
-          </TableHead>
-          <TableBody>
-            {rows.map((row, i) => (
-              <TableRow key={i} hover>
-                <TableCell align="left" component="th" scope="row" width={200}>{row.container}</TableCell>
-                <TableCell align="right" component="th" scope="row">{row.hours}</TableCell>
-                <TableCell align="right" component="th" scope="row">{row.cpu}</TableCell>
-                <TableCell align="right" component="th" scope="row">{toCurrency(row.cpuCostPerCoreHr, currency, 5)}</TableCell>
-                <TableCell align="right" component="th" scope="row">{toCurrency(row.cpuCost, currency, 3)}</TableCell>
-                <TableCell align="right" component="th" scope="row">{bytesToString(row.ram)}</TableCell>
-                <TableCell align="right" component="th" scope="row">{toCurrency(row.ramCostPerGiBHr, currency, 5)}</TableCell>
-                <TableCell align="right" component="th" scope="row">{toCurrency(row.ramCost, currency, 3)}</TableCell>
-                <TableCell align="right" component="th" scope="row">{toCurrency(row.totalCost, currency, 3)}</TableCell>
-              </TableRow>
-            ))}
-          </TableBody>
-        </Table>
-      </TableContainer>
-    </div>
-  )
-}
-
-export default memo(Details)

+ 0 - 13
ui/src/components/Footer.js

@@ -1,13 +0,0 @@
-import {Parser as HtmlToReactParser} from 'html-to-react'
-
-// Footer could be HTML, so we need to parse it.
-const Footer = () => {
-  const content = '<div align="right"><br/>PLACEHOLDER_FOOTER_CONTENT</div>';
-  const htmlToReactParser = new HtmlToReactParser();
-  const parsedContent = htmlToReactParser.parse(content);
-  return (
-    parsedContent
-    )
-  }
-
-export default Footer;

+ 0 - 60
ui/src/components/Header.js

@@ -1,60 +0,0 @@
-import * as React from "react";
-import { makeStyles } from "@material-ui/styles";
-import Breadcrumbs from "@material-ui/core/Breadcrumbs";
-import Link from "@material-ui/core/Link";
-import Typography from "@material-ui/core/Typography";
-import { useLocation } from "react-router-dom";
-
-const useStyles = makeStyles({
-  root: {
-    alignItems: "center",
-    display: "flex",
-    flexFlow: "row",
-    width: "100%",
-    marginTop: "10px",
-  },
-  context: {
-    flex: "1 0 auto",
-  },
-  actions: {
-    flex: "0 0 auto",
-  },
-});
-
-const Header = (props) => {
-  const classes = useStyles();
-  const { title, breadcrumbs } = props;
-  const { pathname } = useLocation();
-
-  const headerTitle = pathname === "/cloud" ? "Cloud Costs" : "Cost Allocation";
-
-  return (
-    <div className={classes.root}>
-      <Typography variant="h3" style={{ marginBottom: "10px" }}>
-        {headerTitle}
-      </Typography>
-      <div className={classes.context}>
-        {title && (
-          <Typography variant="h4" className={classes.title}>
-            {props.title}
-          </Typography>
-        )}
-        {breadcrumbs && breadcrumbs.length > 0 && (
-          <Breadcrumbs aria-label="breadcrumb">
-            {breadcrumbs.slice(0, breadcrumbs.length - 1).map((b) => (
-              <Link color="inherit" href={b.href} key={b.name}>
-                {b.name}
-              </Link>
-            ))}
-            <Typography color="textPrimary">
-              {breadcrumbs[breadcrumbs.length - 1].name}
-            </Typography>
-          </Breadcrumbs>
-        )}
-      </div>
-      <div className={classes.actions}>{props.children}</div>
-    </div>
-  );
-};
-
-export default Header;

+ 0 - 78
ui/src/components/Nav/NavItem.js

@@ -1,78 +0,0 @@
-import * as React from "react";
-import { ListItem, ListItemIcon, ListItemText } from "@material-ui/core";
-import { Link } from "react-router-dom";
-import { makeStyles } from "@material-ui/styles";
-
-const NavItem = ({ active, href, name, onClick, secondary, title, icon }) => {
-  const useStyles = makeStyles({
-    root: {
-      cursor: "pointer",
-      "&:hover": {
-        backgroundColor: "#ebebeb",
-      },
-      "&:selected": {
-        backgroundColor: "#e1e1e1",
-      },
-    },
-    text: {
-      maxWidth: 200,
-      overflow: "hidden",
-      textOverflow: "ellipsis",
-      whiteSpace: "nowrap",
-    },
-    activeIcon: {
-      color: "#346ef2",
-      minWidth: 36,
-    },
-    activeText: {
-      color: "#346ef2",
-    },
-    icon: {
-      color: "#4e4e4e",
-      minWidth: 36,
-    },
-  });
-  const classes = useStyles();
-
-  const listItemIconClasses = { root: classes.icon };
-  const listItemTextClasses = {
-    secondary: classes.text,
-  };
-
-  if (active) {
-    listItemIconClasses.root = classes.activeIcon;
-    listItemTextClasses.primary = classes.activeText;
-  }
-
-  const renderListItemCore = () => (
-    <ListItem
-      className={active ? "active" : ""}
-      classes={{ root: classes.root }}
-      onClick={(e) => {
-        if (onClick) {
-          onClick();
-          e.stopPropagation();
-        }
-      }}
-      selected={active}
-      title={title}
-    >
-      <ListItemIcon classes={listItemIconClasses}>{icon}</ListItemIcon>
-      <ListItemText
-        classes={listItemTextClasses}
-        primary={name}
-        secondary={secondary}
-      />
-    </ListItem>
-  );
-
-  return href && !active ? (
-    <Link style={{ textDecoration: "none", color: "inherit" }} to={`${href}`}>
-      {renderListItemCore()}
-    </Link>
-  ) : (
-    renderListItemCore()
-  );
-};
-
-export { NavItem };

+ 0 - 70
ui/src/components/Nav/SidebarNav.js

@@ -1,70 +0,0 @@
-import * as React from "react";
-import { Drawer, List } from "@material-ui/core";
-
-import { NavItem } from "./NavItem";
-import { BarChart } from "@material-ui/icons";
-import { Cloud } from "@material-ui/icons";
-import { makeStyles } from "@material-ui/styles";
-
-const DRAWER_WIDTH = 200;
-
-const SidebarNav = ({ active }) => {
-  const useStyles = makeStyles({
-    drawer: {
-      width: DRAWER_WIDTH,
-      flexShrink: 0,
-    },
-    drawerPaper: {
-      backgroundColor: "inherit",
-      border: 0,
-      width: DRAWER_WIDTH,
-      paddingTop: "2.5rem",
-    },
-    text: {
-      overflow: "hidden",
-      textOverflow: "ellipsis",
-      whiteSpace: "nowrap",
-    },
-  });
-
-  const classes = useStyles();
-
-  const [init, setInit] = React.useState(false);
-
-  React.useEffect(() => {
-    if (!init) {
-      setInit(true);
-    }
-  }, [init]);
-
-  const top = [
-    {
-      name: "Cost Allocation",
-      href: "allocation",
-      icon: <BarChart />,
-    },
-    { name: "Cloud Costs", href: "cloud", icon: <Cloud /> },
-  ];
-
-  return (
-    <Drawer
-      anchor={"left"}
-      className={classes.drawer}
-      classes={{ paper: classes.drawerPaper }}
-      variant={"permanent"}
-    >
-      <img
-        src={require("../../images/logo.png")}
-        alt="OpenCost"
-        style={{ flexShrink: 1, padding: "1rem" }}
-      />
-      <List style={{ flexGrow: 1 }}>
-        {top.map((l) => (
-          <NavItem active={active === `/${l.href}`} key={l.name} {...l} />
-        ))}
-      </List>
-    </Drawer>
-  );
-};
-
-export { SidebarNav };

+ 0 - 3
ui/src/components/Nav/index.js

@@ -1,3 +0,0 @@
-import { SidebarNav } from "./SidebarNav";
-
-export default SidebarNav;

+ 0 - 46
ui/src/components/Page.js

@@ -1,46 +0,0 @@
-import { makeStyles } from "@material-ui/styles";
-import * as React from "react";
-import { useLocation } from "react-router-dom";
-import { SidebarNav } from "./Nav/SidebarNav";
-
-const useStyles = makeStyles({
-  wrapper: {
-    position: "relative",
-    height: "100vh",
-    flexGrow: 1,
-    overflowX: "auto",
-    paddingLeft: "2rem",
-    paddingRight: "rem",
-    paddingTop: "2.5rem",
-  },
-  flexGrow: {
-    display: "flex",
-    flexFlow: "column",
-    flexGrow: 1,
-  },
-  body: {
-    display: "flex",
-    overflowY: "scroll",
-    margin: "0px",
-    backgroundColor: "f3f3f3",
-  },
-});
-
-const Page = (props) => {
-  const classes = useStyles();
-
-  const { pathname } = useLocation();
-
-  return (
-    <div className={classes.body}>
-      <SidebarNav active={pathname} />
-      <div className={classes.flexGrow}>
-        <div className={classes.wrapper}>
-          <div className={classes.flexGrow}>{props.children}</div>
-        </div>
-      </div>
-    </div>
-  );
-};
-
-export default Page;

+ 0 - 190
ui/src/components/SelectWindow.js

@@ -1,190 +0,0 @@
-import React, { useEffect, useState } from 'react'
-import { makeStyles } from '@material-ui/styles'
-import { endOfDay, startOfDay } from 'date-fns'
-import { MuiPickersUtilsProvider, KeyboardDatePicker } from '@material-ui/pickers'
-import Button from '@material-ui/core/Button'
-import DateFnsUtils from '@date-io/date-fns'
-import FormControl from '@material-ui/core/FormControl'
-import Link from '@material-ui/core/Link'
-import Popover from '@material-ui/core/Popover'
-import TextField from '@material-ui/core/TextField'
-import Typography from '@material-ui/core/Typography'
-import { isValid } from 'date-fns'
-import { find, get } from 'lodash'
-
-const useStyles = makeStyles({
-  dateContainer: {
-    paddingLeft: 18,
-    paddingRight: 18,
-    paddingTop: 6,
-    paddingBottom: 18,
-    display: 'flex',
-    flexFlow: 'row',
-  },
-  dateContainerColumn: {
-    display: 'flex',
-    flexFlow: 'column',
-  },
-  formControl: {
-    margin: 8,
-    width: 120,
-  },
-})
-
-const SelectWindow = ({ windowOptions, window, setWindow }) => {
-    const classes = useStyles()
-    const [anchorEl, setAnchorEl] = useState(null)
-  
-    const [startDate, setStartDate] = useState(null)
-    const [endDate, setEndDate] = useState(null)
-    const [intervalString, setIntervalString] = useState(null)
-  
-    const handleClick = (event) => {
-      setAnchorEl(event.currentTarget)
-    }
-  
-    const handleClose = () => {
-      setAnchorEl(null)
-    }
-  
-    const handleStartDateChange = (date) => {
-      if (isValid(date)) {
-        setStartDate(startOfDay(date))
-      }
-    }
-  
-    const handleEndDateChange = (date) => {
-      if (isValid(date)) {
-        setEndDate(endOfDay(date))
-      }
-    }
-  
-    const handleSubmitPresetDates = (dateString) => {
-      setWindow(dateString)
-      setStartDate(null)
-      setEndDate(null)
-      handleClose()
-    }
-  
-    const handleSubmitCustomDates = () => {
-      if (intervalString !== null) {
-        setWindow(intervalString)
-        handleClose()
-      }
-    }
-  
-    useEffect(() => {
-      if (startDate !== null && endDate !== null) {
-        // Note: getTimezoneOffset() is calculated based on current system locale, NOT date object
-        let adjustedStartDate = new Date(startDate - startDate.getTimezoneOffset() * 60000)
-        let adjustedEndDate = new Date(endDate - endDate.getTimezoneOffset() * 60000)
-        setIntervalString(
-          adjustedStartDate.toISOString().split('.')[0] + "Z" 
-          + "," 
-          + adjustedEndDate.toISOString().split('.')[0] + "Z"
-        )
-      }
-    }, [startDate, endDate])
-  
-    const open = Boolean(anchorEl)
-    const id = open ? 'date-range-popover' : undefined
-  
-    return (
-      <>
-        <FormControl className={classes.formControl}>
-          <TextField
-          id="filled-read-only-input"
-          label="Date Range"
-          value={get(find(windowOptions, { value: window }), "name", "Custom")}
-          onClick={e => handleClick(e)}
-          inputProps={{
-            readOnly: true,
-            style: { cursor: 'pointer' },
-          }}
-          />
-        </FormControl>
-        <Popover
-          id={id}
-          open={open}
-          anchorEl={anchorEl}
-          onClose={handleClose}
-          anchorOrigin={{
-            vertical: 'bottom',
-            horizontal: 'left',
-          }}
-          transformOrigin={{
-            vertical: 'top',
-            horizontal: 'center',
-          }}
-        >
-          <div className={classes.dateContainer}>
-            <div className={classes.dateContainerColumn}>
-            <MuiPickersUtilsProvider utils={DateFnsUtils}>
-              <KeyboardDatePicker
-                style={{ width: '144px' }}
-                autoOk={true}
-                disableToolbar
-                variant="inline"
-                format="MM/dd/yyyy"
-                margin="normal"
-                id="date-picker-start"
-                label="Start Date"
-                value={startDate}
-                maxDate={new Date()}
-                maxDateMessage="Date should not be after today."
-                onChange={handleStartDateChange}
-                KeyboardButtonProps={{
-                  'aria-label': 'change date',
-                }}
-              />
-              <KeyboardDatePicker
-                style={{ width: '144px' }}
-                autoOk={true}
-                disableToolbar
-                variant="inline"
-                format="MM/dd/yyyy"
-                margin="normal"
-                id="date-picker-end"
-                label="End Date"
-                value={endDate}
-                maxDate={new Date()}
-                maxDateMessage="Date should not be after today."
-                onChange={handleEndDateChange}
-                KeyboardButtonProps={{
-                  'aria-label': 'change date',
-                }}
-              />
-            </MuiPickersUtilsProvider>
-              <div>
-                <Button 
-                  style={{ marginTop: 16 }} 
-                  variant="contained" 
-                  color="default"
-                  onClick={handleSubmitCustomDates}
-                >
-                  Apply
-                </Button>
-              </div>
-            </div>
-            <div className={classes.dateContainerColumn} style={{ paddingTop: 12, marginLeft: 18 }}>
-              {windowOptions.map(opt => 
-              <Typography key={opt.value}
-              >
-                <Link
-                  style={{ cursor: "pointer" }}
-                  key={opt.value}
-                  value={opt.value}
-                  onClick={() => handleSubmitPresetDates(opt.value)}
-                >
-                  {opt.name}
-                </Link>
-              </Typography>
-              )}
-            </div>
-          </div>
-        </Popover>
-      </>
-    )
-  }
-
-  export default React.memo(SelectWindow)

+ 0 - 44
ui/src/components/Subtitle.js

@@ -1,44 +0,0 @@
-import * as React from "react";
-import { makeStyles } from "@material-ui/styles";
-import { upperFirst } from "lodash";
-import Breadcrumbs from "@material-ui/core/Breadcrumbs";
-import NavigateNextIcon from "@material-ui/icons/NavigateNext";
-import Typography from "@material-ui/core/Typography";
-import { toVerboseTimeRange } from "../util";
-
-const useStyles = makeStyles({
-  root: {
-    "& > * + *": {
-      marginTop: 2,
-    },
-  },
-  link: {
-    cursor: "pointer",
-  },
-});
-
-const Subtitle = ({ report, onClick }) => {
-  const classes = useStyles();
-
-  const { aggregateBy, window } = report;
-
-  return (
-    <div className={classes.root}>
-      <Breadcrumbs
-        separator={<NavigateNextIcon fontSize="small" />}
-        aria-label="breadcrumb"
-        onClick={onClick}
-      >
-        {aggregateBy && aggregateBy.length > 0 ? (
-          <Typography>
-            {toVerboseTimeRange(window)} by {upperFirst(aggregateBy)}
-          </Typography>
-        ) : (
-          <Typography>{toVerboseTimeRange(window)}</Typography>
-        )}
-      </Breadcrumbs>
-    </div>
-  );
-};
-
-export default React.memo(Subtitle);

+ 0 - 37
ui/src/components/Warnings.js

@@ -1,37 +0,0 @@
-import React from "react";
-import { makeStyles } from "@material-ui/styles";
-import List from "@material-ui/core/List";
-import ListItem from "@material-ui/core/ListItem";
-import ListItemIcon from "@material-ui/core/ListItemIcon";
-import ListItemText from "@material-ui/core/ListItemText";
-import Paper from "@material-ui/core/Paper";
-import WarningIcon from "@material-ui/icons/Warning";
-
-const useStyles = makeStyles({
-  root: {},
-});
-
-const Warnings = ({ warnings }) => {
-  const classes = useStyles();
-
-  if (!warnings || warnings.length === 0) {
-    return null;
-  }
-
-  return (
-    <Paper className={classes.root}>
-      <List>
-        {warnings.map((warn, i) => (
-          <ListItem key={i}>
-            <ListItemIcon>
-              <WarningIcon />
-            </ListItemIcon>
-            <ListItemText primary={warn.primary} secondary={warn.secondary} />
-          </ListItem>
-        ))}
-      </List>
-    </Paper>
-  );
-};
-
-export default Warnings;

+ 0 - 241
ui/src/components/allocationReport.js

@@ -1,241 +0,0 @@
-import React, { useEffect, useState } from "react";
-import { get, round } from "lodash";
-import { makeStyles } from "@material-ui/styles";
-import Table from "@material-ui/core/Table";
-import TableBody from "@material-ui/core/TableBody";
-import TableCell from "@material-ui/core/TableCell";
-import TableContainer from "@material-ui/core/TableContainer";
-import TableHead from "@material-ui/core/TableHead";
-import TablePagination from "@material-ui/core/TablePagination";
-import TableRow from "@material-ui/core/TableRow";
-import TableSortLabel from "@material-ui/core/TableSortLabel";
-import Typography from "@material-ui/core/Typography";
-import AllocationChart from "./AllocationChart";
-import { toCurrency } from "../util";
-
-const useStyles = makeStyles({
-  noResults: {
-    padding: 24,
-  },
-});
-
-function descendingComparator(a, b, orderBy) {
-  if (get(b, orderBy) < get(a, orderBy)) {
-    return -1;
-  }
-  if (get(b, orderBy) > get(a, orderBy)) {
-    return 1;
-  }
-  return 0;
-}
-
-function getComparator(order, orderBy) {
-  return order === "desc"
-    ? (a, b) => descendingComparator(a, b, orderBy)
-    : (a, b) => -descendingComparator(a, b, orderBy);
-}
-
-function stableSort(array, comparator) {
-  const stabilizedThis = array.map((el, index) => [el, index]);
-  stabilizedThis.sort((a, b) => {
-    const order = comparator(a[0], b[0]);
-    if (order !== 0) return order;
-    return a[1] - b[1];
-  });
-  return stabilizedThis.map((el) => el[0]);
-}
-
-const headCells = [
-  { id: "name", numeric: false, label: "Name", width: "auto" },
-  { id: "cpuCost", numeric: true, label: "CPU", width: 90 },
-  { id: "ramCost", numeric: true, label: "RAM", width: 90 },
-  { id: "pvCost", numeric: true, label: "PV", width: 90 },
-  { id: "totalEfficiency", numeric: true, label: "Efficiency", width: 90 },
-  { id: "totalCost", numeric: true, label: "Total cost", width: 90 },
-];
-
-const AllocationReport = ({
-  allocationData,
-  cumulativeData,
-  totalData,
-  currency,
-}) => {
-  const classes = useStyles();
-
-  if (allocationData.length === 0) {
-    return (
-      <Typography variant="body2" className={classes.noResults}>
-        No results
-      </Typography>
-    );
-  }
-
-  const [order, setOrder] = React.useState("desc");
-  const [orderBy, setOrderBy] = React.useState("totalCost");
-  const [page, setPage] = useState(0);
-  const [rowsPerPage, setRowsPerPage] = useState(25);
-  const numData = cumulativeData.length;
-
-  useEffect(() => {
-    setPage(0);
-  }, [numData]);
-
-  const lastPage = Math.floor(numData / rowsPerPage);
-
-  const handleChangePage = (event, newPage) => setPage(newPage);
-
-  const handleChangeRowsPerPage = (event) => {
-    setRowsPerPage(parseInt(event.target.value, 10));
-    setPage(0);
-  };
-
-  const createSortHandler = (property) => (event) =>
-    handleRequestSort(event, property);
-
-  const handleRequestSort = (event, property) => {
-    const isDesc = orderBy === property && order === "desc";
-    setOrder(isDesc ? "asc" : "desc");
-    setOrderBy(property);
-  };
-
-  const orderedRows = stableSort(cumulativeData, getComparator(order, orderBy));
-  const pageRows = orderedRows.slice(
-    page * rowsPerPage,
-    page * rowsPerPage + rowsPerPage
-  );
-
-  return (
-    <div id="report">
-      <AllocationChart
-        allocationRange={allocationData}
-        currency={currency}
-        n={10}
-        height={300}
-      />
-      <TableContainer>
-        <Table>
-          <TableHead>
-            <TableRow>
-              {headCells.map((cell) => (
-                <TableCell
-                  key={cell.id}
-                  colSpan={cell.colspan}
-                  align={cell.numeric ? "right" : "left"}
-                  sortDirection={orderBy === cell.id ? order : false}
-                  style={{ width: cell.width }}
-                >
-                  <TableSortLabel
-                    active={orderBy === cell.id}
-                    direction={orderBy === cell.id ? order : "asc"}
-                    onClick={createSortHandler(cell.id)}
-                  >
-                    {cell.label}
-                  </TableSortLabel>
-                </TableCell>
-              ))}
-            </TableRow>
-          </TableHead>
-          <TableBody>
-            <TableRow>
-              {headCells.map((cell) => {
-                return (
-                  <TableCell
-                    key={cell.id}
-                    colSpan={cell.colspan}
-                    align={cell.numeric ? "right" : "left"}
-                    style={{ fontWeight: 500 }}
-                  >
-                    {cell.numeric
-                      ? cell.label === "Efficiency"
-                        ? totalData.totalEfficiency == 1.0 &&
-                          totalData.cpuReqCoreHrs == 0 &&
-                          totalData.ramReqByteHrs == 0
-                          ? "Inf%"
-                          : `${round(totalData.totalEfficiency * 100, 1)}%`
-                        : toCurrency(totalData[cell.id], currency)
-                      : totalData[cell.id]}
-                  </TableCell>
-                );
-              })}
-            </TableRow>
-            {pageRows.map((row, key) => {
-              if (row.name === "__unmounted__") {
-                row.name = "Unmounted PVs";
-              }
-
-              let isIdle = row.name.indexOf("__idle__") >= 0;
-              let isUnallocated = row.name.indexOf("__unallocated__") >= 0;
-              let isUnmounted = row.name.indexOf("Unmounted PVs") >= 0;
-
-              // Replace "efficiency" with Inf if there is usage w/o request
-              let efficiency = round(row.totalEfficiency * 100, 1);
-              if (
-                row.totalEfficiency == 1.0 &&
-                row.cpuReqCoreHrs == 0 &&
-                row.ramReqByteHrs == 0
-              ) {
-                efficiency = "Inf";
-              }
-
-              // Do not allow drill-down for idle and unallocated rows
-              if (isIdle || isUnallocated || isUnmounted) {
-                return (
-                  <TableRow key={key}>
-                    <TableCell align="left">{row.name}</TableCell>
-                    <TableCell align="right">
-                      {toCurrency(row.cpuCost, currency)}
-                    </TableCell>
-                    <TableCell align="right">
-                      {toCurrency(row.ramCost, currency)}
-                    </TableCell>
-                    <TableCell align="right">
-                      {toCurrency(row.pvCost, currency)}
-                    </TableCell>
-                    {isIdle ? (
-                      <TableCell align="right">&mdash;</TableCell>
-                    ) : (
-                      <TableCell align="right">{efficiency}%</TableCell>
-                    )}
-                    <TableCell align="right">
-                      {toCurrency(row.totalCost, currency)}
-                    </TableCell>
-                  </TableRow>
-                );
-              }
-
-              return (
-                <TableRow key={key}>
-                  <TableCell align="left">{row.name}</TableCell>
-                  <TableCell align="right">
-                    {toCurrency(row.cpuCost, currency)}
-                  </TableCell>
-                  <TableCell align="right">
-                    {toCurrency(row.ramCost, currency)}
-                  </TableCell>
-                  <TableCell align="right">
-                    {toCurrency(row.pvCost, currency)}
-                  </TableCell>
-                  <TableCell align="right">{efficiency}%</TableCell>
-                  <TableCell align="right">
-                    {toCurrency(row.totalCost, currency)}
-                  </TableCell>
-                </TableRow>
-              );
-            })}
-          </TableBody>
-        </Table>
-      </TableContainer>
-      <TablePagination
-        component="div"
-        count={numData}
-        rowsPerPage={rowsPerPage}
-        rowsPerPageOptions={[10, 25, 50]}
-        page={Math.min(page, lastPage)}
-        onChangePage={handleChangePage}
-        onChangeRowsPerPage={handleChangeRowsPerPage}
-      />
-    </div>
-  );
-};
-
-export default React.memo(AllocationReport);

+ 0 - 38
ui/src/constants/colors.js

@@ -1,38 +0,0 @@
-import blue from '@material-ui/core/colors/blue'
-import brown from '@material-ui/core/colors/brown'
-import cyan from '@material-ui/core/colors/cyan'
-import deepOrange from '@material-ui/core/colors/deepOrange'
-import deepPurple from '@material-ui/core/colors/deepPurple'
-import green from '@material-ui/core/colors/green'
-import grey from '@material-ui/core/colors/grey'
-import indigo from '@material-ui/core/colors/indigo'
-import orange from '@material-ui/core/colors/orange'
-import red from '@material-ui/core/colors/red'
-import teal from '@material-ui/core/colors/teal'
-import yellow from '@material-ui/core/colors/yellow'
-
-export const primary = [
-  blue[500],
-  red[500],
-  green[500],
-  yellow[500],
-  cyan[500],
-  orange[500],
-  teal[500],
-  indigo[500],
-  deepOrange[500],
-  deepPurple[500],
-]
-
-export const greyscale = [
-  grey[300],
-  grey[400],
-  grey[200],
-  grey[500],
-  grey[100],
-  grey[600],
-]
-
-export const browns = [
-  brown[500],
-]

+ 0 - 1
ui/src/constants/currencyCodes.js

@@ -1 +0,0 @@
-export const currencyCodes = ["AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN", "BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BOV", "BRL", "BSD", "BTN", "BWP", "BYR", "BZD", "CAD", "CDF", "CHE", "CHF", "CHW", "CLF", "CLP", "CNY", "COP", "COU", "CRC", "CUC", "CUP", "CVE", "CZK", "DJF", "DKK", "DOP", "DZD", "EGP", "ERN", "ETB", "EUR", "FJD", "FKP", "GBP", "GEL", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", "HKD", "HNL", "HRK", "HTG", "HUF", "IDR", "ILS", "INR", "IQD", "IRR", "ISK", "JMD", "JOD", "JPY", "KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "LTL", "LVL", "LYD", "MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRO", "MUR", "MVR", "MWK", "MXN", "MXV", "MYR", "MZN", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "OMR", "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", "QAR", "RON", "RSD", "RUB", "RWF", "SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLL", "SOS", "SRD", "SSP", "STD", "SYP", "SZL", "THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TWD", "TZS", "UAH", "UGX", "USD", "USN", "USS", "UYI", "UYU", "UZS", "VEF", "VND", "VUV", "WST", "XAF", "XAG", "XAU", "XBA", "XBB", "XBC", "XBD", "XCD", "XDR", "XFU", "XOF", "XPD", "XPF", "XPT", "XTS", "XXX", "YER", "ZAR", "ZMW"];

+ 0 - 20
ui/src/css/index.css

@@ -1,20 +0,0 @@
-@import '../../node_modules/material-design-icons-iconfont/dist/material-design-icons.css';
-
-body {
-    background-color: #F3F3F3;
-    display: flex;
-    flex-flow: column;
-    font-family: 'Roboto', sans-serif;
-    margin: 0px;
-    overflow-y: scroll;
-}
-
-body .page-container {
-    display: flex;
-    flex-flow: column;
-    flex-grow: 1;
-}
-
-.recharts-tooltip-wrapper {
-    z-index: 1000;
-}

BIN
ui/src/images/favicon.ico


BIN
ui/src/images/logo.png


+ 0 - 16
ui/src/index.html

@@ -1,16 +0,0 @@
-<!DOCTYPE html>
-<html>
-
-<head>
-	<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
-	<meta content="utf-8" http-equiv="encoding" />
-	<link rel="icon" href="./images/favicon.ico" />
-	<link rel="stylesheet" href="./css/index.css" />
-</head>
-
-<body>
-	<div id="app" class="page-container"></div>
-	<script src="./app.js" type="module"></script>
-</body>
-
-</html>

BIN
ui/src/opencost-ui.png


+ 0 - 25
ui/src/route.js

@@ -1,25 +0,0 @@
-import * as React from "react";
-import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
-
-import Reports from "./Reports.js";
-import CloudCostReports from "./cloudCostReports.js";
-
-const Routes = () => {
-  return (
-    <Router>
-      <Switch>
-        <Route exact path="/">
-          <Reports />
-        </Route>
-        <Route exact path="/allocation">
-          <Reports />
-        </Route>
-        <Route exact path="/cloud">
-          <CloudCostReports />
-        </Route>
-      </Switch>
-    </Router>
-  );
-};
-
-export default Routes;

+ 0 - 28
ui/src/services/allocation.js

@@ -1,28 +0,0 @@
-import axios from "axios";
-
-class AllocationService {
-  BASE_URL = process.env.BASE_URL || "{PLACEHOLDER_BASE_URL}";
-
-  async fetchAllocation(win, aggregate, options) {
-    if (this.BASE_URL.includes("PLACEHOLDER_BASE_URL")) {
-      this.BASE_URL = `http://localhost:9090/model`;
-    }
-
-    const { accumulate, filters } = options;
-    const params = {
-      window: win,
-      aggregate: aggregate,
-      includeIdle: true,
-      step: "1d",
-    };
-    if (typeof accumulate === "boolean") {
-      params.accumulate = accumulate;
-    }
-    const result = await axios.get(`${this.BASE_URL}/allocation/compute`, {
-      params,
-    });
-    return result.data;
-  }
-}
-
-export default new AllocationService();

+ 0 - 42
ui/src/services/cloudCostDayTotals.js

@@ -1,42 +0,0 @@
-import axios from "axios";
-import { parseFilters } from "../util";
-import { costMetricToPropName } from "../cloudCost/tokens";
-
-function formatItemsForCost({ data, costType }) {
-  return data.sets.map(({ cloudCosts, window }) => {
-    return {
-      date: window.start,
-      cost: Object.values(cloudCosts).reduce(
-        (acc, costs) => acc + costs[costType || "amortizedNetCost"].cost,
-        0
-      ),
-    };
-  });
-}
-
-class CloudCostDayTotalsService {
-  BASE_URL = process.env.BASE_URL || "{PLACEHOLDER_BASE_URL}";
-
-  async fetchCloudCostData(window, aggregate, costMetric, filters) {
-    if (this.BASE_URL.includes("PLACEHOLDER_BASE_URL")) {
-      this.BASE_URL = `http://localhost:9090/model`;
-    }
-    if (aggregate.includes("item")) {
-      const resp = await axios.get(
-        `${
-          this.BASE_URL
-        }/cloudCost?window=${window}&costMetric=${costMetric}&filter=${parseFilters(
-          filters
-        )}`
-      );
-      const costMetricProp = costMetricToPropName[costMetric];
-
-      const result_2 = await resp.data;
-      return { data: formatItemsForCost(result_2, costMetricProp) };
-    }
-
-    return [];
-  }
-}
-
-export default new CloudCostDayTotalsService();

+ 0 - 57
ui/src/services/cloudCostTop.js

@@ -1,57 +0,0 @@
-import axios from "axios";
-import { formatSampleItemsForGraph, parseFilters } from "../util";
-
-class CloudCostTopService {
-  BASE_URL = process.env.BASE_URL || "{PLACEHOLDER_BASE_URL}";
-
-  async fetchCloudCostData(window, aggregate, costMetric, filters) {
-    if (this.BASE_URL.includes("PLACEHOLDER_BASE_URL")) {
-      this.BASE_URL = `http://localhost:9090/model`;
-    }
-
-    const params = {
-      window,
-      aggregate,
-      costMetric,
-      filter: parseFilters(filters ?? []),
-      limit: 1000,
-    };
-
-    if (aggregate.includes("item")) {
-      const resp = await axios.get(
-        `${
-          this.BASE_URL
-        }/cloudCost?window=${window}&costMetric=${costMetric}&filter=${parseFilters(
-          filters
-        )}`
-      );
-      const result_2 = await resp.data;
-
-      return formatSampleItemsForGraph(result_2, costMetric);
-    }
-
-    const tableView = await axios.get(`${this.BASE_URL}/cloudCost/view/table`, {
-      params,
-    });
-    const totalsView = await axios.get(
-      `${this.BASE_URL}/cloudCost/view/totals`,
-      {
-        params,
-      }
-    );
-    const graphView = await axios.get(`${this.BASE_URL}/cloudCost/view/graph`, {
-      params,
-    });
-
-    const status = await axios.get(`${this.BASE_URL}/cloudCost/status`);
-
-    return {
-      tableRows: tableView.data.data,
-      graphData: graphView.data.data,
-      tableTotal: totalsView.data.data.combined,
-      cloudCostStatus: status.data.data,
-    };
-  }
-}
-
-export default new CloudCostTopService();

BIN
ui/src/thumbnail.png


+ 0 - 452
ui/src/util.js

@@ -1,452 +0,0 @@
-import { forEach, get, round } from "lodash";
-import { costMetricToPropName } from "./cloudCost/tokens";
-
-// rangeToCumulative takes an AllocationSetRange (type: array[AllocationSet])
-// and accumulates the values into a single AllocationSet (type: object)
-export function rangeToCumulative(allocationSetRange, aggregateBy) {
-  if (allocationSetRange.length === 0) {
-    return null;
-  }
-
-  const result = {};
-
-  forEach(allocationSetRange, (allocSet) => {
-    forEach(allocSet, (alloc) => {
-      if (result[alloc.name] === undefined) {
-        const hrs = get(alloc, "minutes", 0) / 60.0;
-
-        result[alloc.name] = {
-          name: alloc.name,
-          [aggregateBy]: alloc.name,
-          cpuCost: get(alloc, "cpuCost", 0),
-          gpuCost: get(alloc, "gpuCost", 0),
-          ramCost: get(alloc, "ramCost", 0),
-          pvCost: get(alloc, "pvCost", 0),
-          networkCost: get(alloc, "networkCost", 0),
-          sharedCost: get(alloc, "sharedCost", 0),
-          externalCost: get(alloc, "externalCost", 0),
-          totalCost: get(alloc, "totalCost", 0),
-          cpuUseCoreHrs: get(alloc, "cpuCoreUsageAverage", 0) * hrs,
-          cpuReqCoreHrs: get(alloc, "cpuCoreRequestAverage", 0) * hrs,
-          ramUseByteHrs: get(alloc, "ramByteUsageAverage", 0) * hrs,
-          ramReqByteHrs: get(alloc, "ramByteRequestAverage", 0) * hrs,
-          cpuEfficiency: get(alloc, "cpuEfficiency", 0),
-          ramEfficiency: get(alloc, "ramEfficiency", 0),
-          totalEfficiency: get(alloc, "totalEfficiency", 0),
-        };
-      } else {
-        const hrs = get(alloc, "minutes", 0) / 60.0;
-
-        result[alloc.name].cpuCost += get(alloc, "cpuCost", 0);
-        result[alloc.name].gpuCost += get(alloc, "gpuCost", 0);
-        result[alloc.name].ramCost += get(alloc, "ramCost", 0);
-        result[alloc.name].pvCost += get(alloc, "pvCost", 0);
-        result[alloc.name].networkCost += get(alloc, "networkCost", 0);
-        result[alloc.name].sharedCost += get(alloc, "sharedCost", 0);
-        result[alloc.name].externalCost += get(alloc, "externalCost", 0);
-        result[alloc.name].totalCost += get(alloc, "totalCost", 0);
-        result[alloc.name].cpuUseCoreHrs +=
-          get(alloc, "cpuCoreUsageAverage", 0) * hrs;
-        result[alloc.name].cpuReqCoreHrs +=
-          get(alloc, "cpuCoreRequestAverage", 0) * hrs;
-        result[alloc.name].ramUseByteHrs +=
-          get(alloc, "ramByteUsageAverage", 0) * hrs;
-        result[alloc.name].ramReqByteHrs +=
-          get(alloc, "ramByteRequestAverage", 0) * hrs;
-      }
-    });
-  });
-
-  // If the range is of length > 1 (i.e. it is not just a single set) then
-  // compute efficiency for each result after accumulating.
-  if (allocationSetRange.length > 1) {
-    forEach(result, (alloc, name) => {
-      // If we can't compute total efficiency, it defaults to 0.0
-      let totalEfficiency = 0.0;
-
-      // CPU efficiency is defined as (usage/request). If request == 0.0 but
-      // usage > 0, then efficiency gets set to 1.0.
-      let cpuEfficiency = 0.0;
-      if (alloc.cpuReqCoreHrs > 0) {
-        cpuEfficiency = alloc.cpuUseCoreHrs / alloc.cpuReqCoreHrs;
-      } else if (alloc.cpuUseCoreHrs > 0) {
-        cpuEfficiency = 1.0;
-      }
-
-      // RAM efficiency is defined as (usage/request). If request == 0.0 but
-      // usage > 0, then efficiency gets set to 1.0.
-      let ramEfficiency = 0.0;
-      if (alloc.ramReqByteHrs > 0) {
-        ramEfficiency = alloc.ramUseByteHrs / alloc.ramReqByteHrs;
-      } else if (alloc.ramUseByteHrs > 0) {
-        ramEfficiency = 1.0;
-      }
-
-      // Compute efficiency as the cost-weighted average of CPU and RAM
-      // efficiency
-      if (alloc.cpuCost + alloc.ramCost > 0.0) {
-        totalEfficiency =
-          (alloc.cpuCost * cpuEfficiency + alloc.ramCost * ramEfficiency) /
-          (alloc.cpuCost + alloc.ramCost);
-      }
-
-      result[name].cpuEfficiency = cpuEfficiency;
-      result[name].ramEfficiency = ramEfficiency;
-      result[name].totalEfficiency = totalEfficiency;
-    });
-  }
-
-  return result;
-}
-
-// cumulativeToTotals adds each entry in the given AllocationSet (type: object)
-// and returns a single Allocation (type: object) representing the totals
-export function cumulativeToTotals(allocationSet) {
-  let totals = {
-    name: "Totals",
-    cpuCost: 0,
-    gpuCost: 0,
-    ramCost: 0,
-    pvCost: 0,
-    networkCost: 0,
-    sharedCost: 0,
-    externalCost: 0,
-    totalCost: 0,
-    cpuEfficiency: 0,
-    ramEfficiency: 0,
-    totalEfficiency: 0,
-  };
-
-  // Use these for computing efficiency. As such, idle will not factor into
-  // these numbers, including CPU and RAM cost.
-  let cpuReqCoreHrs = 0;
-  let cpuUseCoreHrs = 0;
-  let ramReqByteHrs = 0;
-  let ramUseByteHrs = 0;
-  let cpuCost = 0;
-  let ramCost = 0;
-
-  forEach(allocationSet, (alloc, name) => {
-    // Accumulate efficiency-related fields
-    if (name !== "__idle__") {
-      cpuReqCoreHrs += get(alloc, "cpuReqCoreHrs", 0.0);
-      cpuUseCoreHrs += get(alloc, "cpuUseCoreHrs", 0.0);
-      ramReqByteHrs += get(alloc, "ramReqByteHrs", 0.0);
-      ramUseByteHrs += get(alloc, "ramUseByteHrs", 0.0);
-      cpuCost += get(alloc, "cpuCost", 0.0);
-      ramCost += get(alloc, "ramCost", 0.0);
-    }
-
-    // Sum cumulative fields
-    totals.cpuCost += get(alloc, "cpuCost", 0);
-    totals.gpuCost += get(alloc, "gpuCost", 0);
-    totals.ramCost += get(alloc, "ramCost", 0);
-    totals.pvCost += get(alloc, "pvCost", 0);
-    totals.networkCost += get(alloc, "networkCost", 0);
-    totals.sharedCost += get(alloc, "sharedCost", 0);
-    totals.externalCost += get(alloc, "externalCost", 0);
-    totals.totalCost += get(alloc, "totalCost", 0);
-  });
-
-  // Compute efficiency
-  if (cpuReqCoreHrs > 0) {
-    totals.cpuEfficiency = cpuUseCoreHrs / cpuReqCoreHrs;
-  } else if (cpuUseCoreHrs > 0) {
-    totals.cpuEfficiency = 1.0;
-  }
-
-  if (ramReqByteHrs > 0) {
-    totals.ramEfficiency = ramUseByteHrs / ramReqByteHrs;
-  } else if (ramUseByteHrs > 0) {
-    totals.ramEfficiency = 1.0;
-  }
-
-  if (cpuCost + ramCost > 0) {
-    totals.totalEfficiency =
-      (cpuCost * totals.cpuEfficiency + ramCost * totals.ramEfficiency) /
-      (cpuCost + ramCost);
-  }
-
-  totals.cpuReqCoreHrs = cpuReqCoreHrs;
-  totals.cpuUseCoreHrs = cpuUseCoreHrs;
-  totals.ramReqByteHrs = ramReqByteHrs;
-  totals.ramUseByteHrs = ramUseByteHrs;
-
-  return totals;
-}
-
-export function toVerboseTimeRange(window) {
-  const months = [
-    "January",
-    "February",
-    "March",
-    "April",
-    "May",
-    "June",
-    "July",
-    "August",
-    "September",
-    "October",
-    "November",
-    "December",
-  ];
-
-  const start = new Date();
-  start.setUTCHours(0, 0, 0, 0);
-
-  const end = new Date();
-  end.setUTCHours(0, 0, 0, 0);
-
-  switch (window) {
-    case "today":
-      return `${start.getUTCDate()} ${
-        months[start.getUTCMonth()]
-      } ${start.getUTCFullYear()}`;
-    case "yesterday":
-      start.setUTCDate(start.getUTCDate() - 1);
-      return `${start.getUTCDate()} ${
-        months[start.getUTCMonth()]
-      } ${start.getUTCFullYear()}`;
-    case "week":
-      start.setUTCDate(start.getUTCDate() - start.getUTCDay());
-      return `${start.getUTCDate()} ${
-        months[start.getUTCMonth()]
-      } ${start.getUTCFullYear()} until now`;
-    case "month":
-      start.setUTCDate(1);
-      return `${start.getUTCDate()} ${
-        months[start.getUTCMonth()]
-      } ${start.getUTCFullYear()} until now`;
-    case "lastweek":
-      start.setUTCDate(start.getUTCDate() - (start.getUTCDay() + 7));
-      end.setUTCDate(end.getUTCDate() - (end.getUTCDay() + 1));
-      return `${start.getUTCDate()} ${
-        months[start.getUTCMonth()]
-      } ${start.getUTCFullYear()} through ${end.getUTCDate()} ${
-        months[end.getUTCMonth()]
-      } ${end.getUTCFullYear()}`;
-    case "lastmonth":
-      end.setUTCDate(1);
-      end.setUTCDate(end.getUTCDate() - 1);
-      start.setUTCDate(1);
-      start.setUTCDate(start.getUTCDate() - 1);
-      start.setUTCDate(1);
-      return `${start.getUTCDate()} ${
-        months[start.getUTCMonth()]
-      } ${start.getUTCFullYear()} through ${end.getUTCDate()} ${
-        months[end.getUTCMonth()]
-      } ${end.getUTCFullYear()}`;
-    case "6d":
-      start.setUTCDate(start.getUTCDate() - 6);
-      return `${start.getUTCDate()} ${
-        months[start.getUTCMonth()]
-      } ${start.getUTCFullYear()} through now`;
-    case "29d":
-      start.setUTCDate(start.getUTCDate() - 29);
-      return `${start.getUTCDate()} ${
-        months[start.getUTCMonth()]
-      } ${start.getUTCFullYear()} through now`;
-    case "59d":
-      start.setUTCDate(start.getUTCDate() - 59);
-      return `${start.getUTCDate()} ${
-        months[start.getUTCMonth()]
-      } ${start.getUTCFullYear()} through now`;
-    case "89d":
-      start.setUTCDate(start.getUTCDate() - 89);
-      return `${start.getUTCDate()} ${
-        months[start.getUTCMonth()]
-      } ${start.getUTCFullYear()} through now`;
-  }
-
-  const splitDates = window.split(",");
-  if (checkCustomWindow(window) && splitDates.length > 1) {
-    let s = splitDates[0].split(/\D+/).slice(0, 3);
-    let e = splitDates[1].split(/\D+/).slice(0, 3);
-    if (s.length === 3 && e.length === 3) {
-      start.setUTCFullYear(s[0], s[1] - 1, s[2]);
-      end.setUTCFullYear(e[0], e[1] - 1, e[2]);
-      if (start === end) {
-        return `${start.getUTCDate()} ${
-          months[start.getUTCMonth()]
-        } ${start.getUTCFullYear()}`;
-      } else {
-        return `${start.getUTCDate()} ${
-          months[start.getUTCMonth()]
-        } ${start.getUTCFullYear()} through ${end.getUTCDate()} ${
-          months[end.getUTCMonth()]
-        } ${end.getUTCFullYear()}`;
-      }
-    }
-  }
-  return null;
-}
-
-export function bytesToString(bytes) {
-  const ei = Math.pow(1024, 6);
-  if (bytes >= ei) {
-    return `${round(bytes / ei, 1)} EiB`;
-  }
-  const pi = Math.pow(1024, 5);
-  if (bytes >= pi) {
-    return `${round(bytes / pi, 1)} PiB`;
-  }
-  const ti = Math.pow(1024, 4);
-  if (bytes >= ti) {
-    return `${round(bytes / ti, 1)} TiB`;
-  }
-  const gi = Math.pow(1024, 3);
-  if (bytes >= gi) {
-    return `${round(bytes / gi, 1)} GiB`;
-  }
-  const mi = Math.pow(1024, 2);
-  if (bytes >= mi) {
-    return `${round(bytes / mi, 1)} MiB`;
-  }
-  const ki = Math.pow(1024, 1);
-  if (bytes >= ki) {
-    return `${round(bytes / ki, 1)} KiB`;
-  }
-
-  return `${round(bytes, 1)} B`;
-}
-
-const currencyLocale = "en-US";
-
-export function toCurrency(amount, currency, precision) {
-  if (typeof amount !== "number") {
-    console.warn(
-      `Tried to convert "${amount}" to currency, but it is not a number`
-    );
-    return "";
-  }
-
-  if (currency === undefined || currency === "") {
-    currency = "USD";
-  }
-
-  const opts = {
-    style: "currency",
-    currency: currency,
-  };
-
-  if (typeof precision === "number") {
-    opts.minimumFractionDigits = precision;
-    opts.maximumFractionDigits = precision;
-  }
-
-  return amount.toLocaleString(currencyLocale, opts);
-}
-
-export function checkCustomWindow(window) {
-  // Example ISO interval string: 2020-12-02T00:00:00Z,2020-12-03T23:59:59Z
-  const customDateRegex =
-    /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z,\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/;
-  return customDateRegex.test(window);
-}
-
-export function formatSampleItemsForGraph({ data, costMetric }) {
-  const costMetricPropName = costMetric
-    ? costMetricToPropName[costMetric]
-    : "amortizedNetCost";
-  const graphData = data.sets.map(({ cloudCosts, window: { end, start } }) => {
-    return {
-      end,
-      items: Object.entries(cloudCosts).map(([name, item]) => ({
-        name,
-        value: item.netCost.cost,
-      })),
-      start,
-    };
-  });
-  const accumulator = {};
-  data.sets.forEach(({ cloudCosts, window }) => {
-    Object.entries(cloudCosts).forEach(([name, cloudCostItem]) => {
-      const { properties } = cloudCostItem;
-      accumulator[name] ||= {
-        cost: 0,
-        start: "",
-        end: "",
-        providerID: "",
-        labelName: "",
-        kubernetesCost: 0,
-        kubernetesPercent: 0,
-      };
-      accumulator[name].cost += cloudCostItem[costMetricPropName].cost;
-      accumulator[name].kubernetesCost +=
-        cloudCostItem[costMetricPropName].cost *
-        cloudCostItem[costMetricPropName].kubernetesPercent;
-      accumulator[name].start = window.start;
-      accumulator[name].end = window.end;
-      accumulator[name].providerID = properties.providerID;
-      accumulator[name].labelName = properties.labels?.name;
-      accumulator[name].kubernetesPercent =
-        cloudCostItem[costMetricPropName].kubernetesPercent;
-    });
-  });
-  const tableRows = Object.entries(accumulator)
-    .map(
-      ([
-        name,
-        {
-          cost,
-          start,
-          end,
-          providerID,
-          kubernetesCost,
-          kubernetesPercent,
-          labelName,
-        },
-      ]) => ({
-        cost,
-        name,
-        kubernetesCost,
-        kubernetesPercent,
-        start,
-        end,
-        providerID,
-        labelName,
-      })
-    )
-    .sort((a, b) => (a.cost > b.cost ? -1 : 1));
-
-  const tableTotal = tableRows.reduce(
-    (tr1, tr2) => ({
-      ...tr1,
-      cost: tr1.cost + tr2.cost,
-      kubernetesCost: tr1.kubernetesCost + tr2.kubernetesCost,
-    }),
-    {
-      cost: 0,
-      name: "",
-      kubernetesCost: 0,
-      kubernetesPercent: 0,
-      end: "",
-      start: "",
-      labelName: "",
-      providerID: "",
-    }
-  );
-
-  return { graphData, tableRows, tableTotal };
-}
-
-export function parseFilters(filters) {
-  if (typeof filters === "string") {
-    return filters;
-  }
-  // remove dups (via context ) and format
-  return (
-    [...new Set(filters.map((f) => `${f.property}:"${f.value}"`))].join(
-      encodeURIComponent("+")
-    ) || ""
-  );
-}
-
-export default {
-  rangeToCumulative,
-  cumulativeToTotals,
-  toVerboseTimeRange,
-  bytesToString,
-  toCurrency,
-  checkCustomWindow,
-};