From bad8527bcf56b8f3525e91f0df78e76f9b4101c2 Mon Sep 17 00:00:00 2001 From: Luc Belliveau Date: Fri, 1 Mar 2024 21:01:13 +0000 Subject: [PATCH 01/13] first pass at ogma --- frontend/.env.example | 5 + frontend/.vscode/extensions.json | 3 +- frontend/.vscode/settings.json | 3 + frontend/README.md | 33 +- frontend/package-lock.json | 425 ++ frontend/package.json | 14 +- frontend/src/app/[locale]/[day]/page.tsx | 24 +- frontend/src/app/_components/AppWrapper.tsx | 26 +- frontend/src/app/_components/Map.tsx | 109 - frontend/src/app/_components/WithWet.tsx | 5 + .../src/app/_components/graph/DataLoader.tsx | 79 + frontend/src/app/_components/graph/Layout.tsx | 30 + .../src/app/_components/graph/ProgressBar.tsx | 192 + frontend/src/app/_components/graph/index.tsx | 176 + frontend/src/app/_hooks/useDateToStr.tsx | 16 +- .../app/_hooks/useDelayedResizeObserver.tsx | 42 + frontend/src/app/_utils/dateToStr.tsx | 20 + frontend/src/app/layout.tsx | 14 +- frontend/src/countries.json | 1472 +++++ frontend/src/env.js | 6 + frontend/src/server/api/routers/post.ts | 23 + frontend/src/styles/globals.css | 204 +- frontend/src/supply-chain-neo4j.json | 5376 +++++++++++++++++ 23 files changed, 8133 insertions(+), 164 deletions(-) create mode 100644 frontend/.vscode/settings.json delete mode 100644 frontend/src/app/_components/Map.tsx create mode 100644 frontend/src/app/_components/graph/DataLoader.tsx create mode 100644 frontend/src/app/_components/graph/Layout.tsx create mode 100644 frontend/src/app/_components/graph/ProgressBar.tsx create mode 100644 frontend/src/app/_components/graph/index.tsx create mode 100644 frontend/src/app/_hooks/useDelayedResizeObserver.tsx create mode 100644 frontend/src/app/_utils/dateToStr.tsx create mode 100644 frontend/src/countries.json create mode 100644 frontend/src/supply-chain-neo4j.json diff --git a/frontend/.env.example b/frontend/.env.example index 7a2d041..2ee9542 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -23,3 +23,8 @@ NEXTAUTH_URL="http://localhost:3000" # Next Auth Discord Provider DISCORD_CLIENT_ID="example" DISCORD_CLIENT_SECRET="example" + +# Neo4j credentials +NEO4J_USERNAME="example" +NEO4J_PASSWORD="example" +NEO4J_URL="bolt://example:7687" diff --git a/frontend/.vscode/extensions.json b/frontend/.vscode/extensions.json index cf1f602..89b9bb6 100644 --- a/frontend/.vscode/extensions.json +++ b/frontend/.vscode/extensions.json @@ -6,6 +6,7 @@ "esbenp.prettier-vscode", "rvest.vs-code-prettier-eslint", "bradlc.vscode-tailwindcss", - "github.vscode-github-actions" + "github.vscode-github-actions", + "bierner.markdown-preview-github-styles" ] } \ No newline at end of file diff --git a/frontend/.vscode/settings.json b/frontend/.vscode/settings.json new file mode 100644 index 0000000..665aa50 --- /dev/null +++ b/frontend/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.rulers": [80,] +} \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md index fba19ed..f39bccb 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,28 +1,33 @@ -# Create T3 App +# Foresight Proof of Concept -This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3-app`. +This proof of concept is a showcase of tools to aid humans detect potential health +threats by leveraging the data innovation team's proposed architecture. + + +## Getting started + +First thing to do is to create the set of required environment variables by copying +the examples from [.env.example](./.env.example) to `./env`. -## What's next? How do I make an app with this? +```bash +cp .env.example .env +``` -We try to keep this project as simple as possible, so you can start with just the scaffolding we set up for you, and add additional things later when they become necessary. +Then start the development server: -If you are not familiar with the different technologies used in this project, please refer to the respective docs. If you still are in the wind, please join our [Discord](https://t3.gg/discord) and ask for help. +```bash +npm run dev +``` -- [Next.js](https://nextjs.org) -- [NextAuth.js](https://next-auth.js.org) -- [Prisma](https://prisma.io) -- [Tailwind CSS](https://tailwindcss.com) -- [tRPC](https://trpc.io) +Finally use your browser to visit [http://localhost:3000](http://localhost:3000). ## Learn More +This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3-app`. + To learn more about the [T3 Stack](https://create.t3.gg/), take a look at the following resources: - [Documentation](https://create.t3.gg/) - [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) — Check out these awesome tutorials You can check out the [create-t3-app GitHub repository](https://github.com/t3-oss/create-t3-app) — your feedback and contributions are welcome! - -## How do I deploy this? - -Follow our deployment guides for [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) and [Docker](https://create.t3.gg/en/deployment/docker) for more information. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 864c9ba..c797e6d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,6 +10,8 @@ "hasInstallScript": true, "dependencies": { "@arcnovus/wet-boew-react": "^2.0.0-beta.9", + "@linkurious/ogma": "https://get.linkurio.us/api/get/npm/ogma/5.0.2/?secret=lk-dls-c6212e2b03b09522c8ac2357791eef0a2f5fa97f", + "@linkurious/ogma-react": "^5.0.2", "@next-auth/prisma-adapter": "^1.0.7", "@prisma/client": "^5.6.0", "@t3-oss/env-nextjs": "^0.7.1", @@ -18,11 +20,14 @@ "@trpc/next": "^10.43.6", "@trpc/react-query": "^10.43.6", "@trpc/server": "^10.43.6", + "leaflet": "^1.9.4", + "neo4j-driver": "^5.18.0", "next": "^14.0.4", "next-auth": "^4.24.5", "next-intl": "^3.9.0", "plotly.js": "^2.29.1", "react": "18.2.0", + "react-datepicker": "^6.2.0", "react-dom": "18.2.0", "react-error-boundary": "^4.0.12", "react-plotly.js": "^2.6.0", @@ -38,6 +43,7 @@ "@types/jest": "^29.5.12", "@types/node": "^18.17.0", "@types/react": "^18.2.37", + "@types/react-datepicker": "^6.0.2", "@types/react-dom": "^18.2.15", "@types/react-plotly.js": "^2.6.3", "@typescript-eslint/eslint-plugin": "^6.11.0", @@ -55,6 +61,10 @@ "tailwindcss": "^3.3.5", "ts-node": "^10.9.2", "typescript": "^5.1.6" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -928,6 +938,54 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", + "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "dependencies": { + "@floating-ui/utils": "^0.2.1" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", + "dependencies": { + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.9.tgz", + "integrity": "sha512-p86wynZJVEkEq2BBjY/8p2g3biQ6TlgT4o/3KgFKyTWoJLU1GZ8wpctwRqtkEl2tseYA+kw7dBAIDFcednfI5w==", + "dependencies": { + "@floating-ui/react-dom": "^2.0.8", + "@floating-ui/utils": "^0.2.1", + "tabbable": "^6.0.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", + "dependencies": { + "@floating-ui/dom": "^1.6.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + }, "node_modules/@formatjs/ecma402-abstract": { "version": "1.18.2", "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.2.tgz", @@ -1621,6 +1679,29 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@linkurious/ogma": { + "version": "5.0.2", + "resolved": "https://get.linkurio.us/api/get/npm/ogma/5.0.2/?secret=lk-dls-c6212e2b03b09522c8ac2357791eef0a2f5fa97f", + "optionalDependencies": { + "@mapbox/mapbox-gl-rtl-text": "^0.2.0", + "@types/leaflet": ">=1.x", + "leaflet": ">=1.x", + "xlsx": "0.17.0" + } + }, + "node_modules/@linkurious/ogma-react": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@linkurious/ogma-react/-/ogma-react-5.0.2.tgz", + "integrity": "sha512-cbS+Xf7jUxOvyKoRaK/KvIYpluU42HDuW/80e5U95GAx5LqeGRVpd6t5DElAOfcq2WTPFSlZVjL5HeSge5R9zQ==", + "dependencies": { + "lodash.throttle": "^4.1.1" + }, + "peerDependencies": { + "@linkurious/ogma": "^5.0.2", + "react": "^18.0.8", + "react-dom": "^18.0.8" + } + }, "node_modules/@mapbox/geojson-rewind": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", @@ -1646,6 +1727,15 @@ "node": ">= 0.6" } }, + "node_modules/@mapbox/mapbox-gl-rtl-text": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-rtl-text/-/mapbox-gl-rtl-text-0.2.3.tgz", + "integrity": "sha512-RaCYfnxULUUUxNwcUimV9C/o2295ktTyLEUzD/+VWkqXqvaVfFcZ5slytGzb2Sd/Jj4MlbxD0DCZbfa6CzcmMw==", + "optional": true, + "peerDependencies": { + "mapbox-gl": ">=0.32.1 <2.0.0" + } + }, "node_modules/@mapbox/mapbox-gl-supported": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.5.0.tgz", @@ -2489,6 +2579,12 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/geojson": { + "version": "7946.0.14", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", + "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==", + "optional": true + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -2595,6 +2691,15 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/leaflet": { + "version": "1.9.8", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.8.tgz", + "integrity": "sha512-EXdsL4EhoUtGm2GC2ZYtXn+Fzc6pluVgagvo2VC1RHWToLGlTRwVYoDpqS/7QXa01rmDyBjJk3Catpf60VMkwg==", + "optional": true, + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/node": { "version": "18.19.17", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.17.tgz", @@ -2627,6 +2732,17 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-datepicker": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-6.0.2.tgz", + "integrity": "sha512-RnBAD9hO9GgSNZ2WjUZKltP3OkLxHNxmvFHyp8SC5A5qItPH20VWj/4krJ3iqGUiH1pqV/vRTOQKeJfEOqcXSw==", + "dev": true, + "dependencies": { + "@floating-ui/react": "^0.26.2", + "@types/react": "*", + "date-fns": "^3.3.1" + } + }, "node_modules/@types/react-dom": { "version": "18.2.19", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.19.tgz", @@ -2938,6 +3054,22 @@ "node": ">=0.4.0" } }, + "node_modules/adler-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.2.0.tgz", + "integrity": "sha512-/vUqU/UY4MVeFsg+SsK6c+/05RZXIHZMGJA+PX5JyWI0ZRcBpupnRuPLU/NXXoFwMYCPCoxIfElM2eS+DUXCqQ==", + "optional": true, + "dependencies": { + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + }, + "bin": { + "adler32": "bin/adler32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -3467,6 +3599,25 @@ "node": ">= 0.6.0" } }, + "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==", + "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/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -3562,6 +3713,29 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -3651,6 +3825,28 @@ "element-size": "^1.1.1" } }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "optional": true, + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/cfb/node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3738,6 +3934,11 @@ "resolved": "https://registry.npmjs.org/clamp/-/clamp-1.0.1.tgz", "integrity": "sha512-kgMuFyE78OC6Dyu3Dy7vcx4uy97EIbVxJB/B0eJ3bUNAkwdNcxYzgKltnyADiYwsR7SEqkkUPsEUT//OVS6XMA==" }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -3804,6 +4005,28 @@ "node": ">= 0.12.0" } }, + "node_modules/codepage": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.14.0.tgz", + "integrity": "sha512-iz3zJLhlrg37/gYRWgEPkaFTtzmnEv1h+r7NgZum2lFElYQPi0/5bnmuDfODHxfp0INEfnRqyfyeIJDbb7ahRw==", + "optional": true, + "dependencies": { + "commander": "~2.14.1", + "exit-on-epipe": "~1.0.1" + }, + "bin": { + "codepage": "bin/codepage.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/codepage/node_modules/commander": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", + "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==", + "optional": true + }, "node_modules/collect-v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", @@ -3961,6 +4184,18 @@ "resolved": "https://registry.npmjs.org/country-regex/-/country-regex-1.1.0.tgz", "integrity": "sha512-iSPlClZP8vX7MC3/u6s3lrDuoQyhQukh5LyABJ3hvfzbQ3Yyayd4fp04zjLnfi267B/B2FkumcWWgrbban7sSA==" }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "optional": true, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -4252,6 +4487,15 @@ "node": ">=12" } }, + "node_modules/date-fns": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz", + "integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -5441,6 +5685,15 @@ "node": ">= 0.8.0" } }, + "node_modules/exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, "node_modules/expect": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", @@ -5565,6 +5818,12 @@ "bser": "2.1.1" } }, + "node_modules/fflate": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.3.11.tgz", + "integrity": "sha512-Rr5QlUeGN1mbOHlaqcSYMKVpPbgLy0AWT/W0EHxA6NGI12yO1jpoui2zBBvU2G824ltM6Ut8BFgfHSBGfkmS0A==", + "optional": true + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -5688,6 +5947,15 @@ "node": ">= 6" } }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -8223,6 +8491,11 @@ "node": ">=0.10" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==" + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -8291,6 +8564,11 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -8637,6 +8915,39 @@ "node": ">= 0.6" } }, + "node_modules/neo4j-driver": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/neo4j-driver/-/neo4j-driver-5.18.0.tgz", + "integrity": "sha512-lDDoj45SN/FNZIYa9cTesHTAVMrcPeWmgkrYiYGTVBfJ3yKk2m2G7+lpmfip6NN8H+A0C4NIqfmMHtcC4eflyw==", + "dependencies": { + "neo4j-driver-bolt-connection": "5.18.0", + "neo4j-driver-core": "5.18.0", + "rxjs": "^7.8.1" + } + }, + "node_modules/neo4j-driver-bolt-connection": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/neo4j-driver-bolt-connection/-/neo4j-driver-bolt-connection-5.18.0.tgz", + "integrity": "sha512-Oc0w4V1sFzy6b1Mvsojz2mcDI0Caqv/jsb61DY7s9i0BHMglkNfMR1GJHjRzC6HaeDU85EFw1RLNF0NJmCSzpg==", + "dependencies": { + "buffer": "^6.0.3", + "neo4j-driver-core": "5.18.0", + "string_decoder": "^1.3.0" + } + }, + "node_modules/neo4j-driver-bolt-connection/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/neo4j-driver-core": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/neo4j-driver-core/-/neo4j-driver-core-5.18.0.tgz", + "integrity": "sha512-naq5zT5tYazh81CO28L5YrcL/Wy4NoppqawkE5zyfFFVEs3bhmGn7G170FL0Fs8h7Dab1aZ5hlOBwlXXVWSDng==" + }, "node_modules/next": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/next/-/next-14.1.0.tgz", @@ -9673,6 +9984,18 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" }, + "node_modules/printj": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", + "optional": true, + "bin": { + "printj": "bin/printj.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/prisma": { "version": "5.10.1", "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.10.1.tgz", @@ -9813,6 +10136,22 @@ "node": ">=0.10.0" } }, + "node_modules/react-datepicker": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-6.2.0.tgz", + "integrity": "sha512-GzEOiE6yLfp9P6XNkOhXuYtZHzoAx3tirbi7/dj2WHlGM+NGE1lefceqGR0ZrYsYaqsNJhIJFTgwUpzVzA+mjw==", + "dependencies": { + "@floating-ui/react": "^0.26.2", + "classnames": "^2.2.6", + "date-fns": "^3.3.1", + "prop-types": "^15.7.2", + "react-onclickoutside": "^6.13.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17 || ^18", + "react-dom": "^16.9.0 || ^17 || ^18" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -9841,6 +10180,19 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-onclickoutside": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz", + "integrity": "sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==", + "funding": { + "type": "individual", + "url": "https://github.com/Pomax/react-onclickoutside/blob/master/FUNDING.md" + }, + "peerDependencies": { + "react": "^15.5.x || ^16.x || ^17.x || ^18.x", + "react-dom": "^15.5.x || ^16.x || ^17.x || ^18.x" + } + }, "node_modules/react-plotly.js": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/react-plotly.js/-/react-plotly.js-2.6.0.tgz", @@ -10217,6 +10569,14 @@ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-array-concat": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", @@ -10462,6 +10822,18 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "optional": true, + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/stack-trace": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", @@ -10914,6 +11286,11 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" + }, "node_modules/tailwindcss": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", @@ -11712,6 +12089,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, "node_modules/world-calendars": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/world-calendars/-/world-calendars-1.0.3.tgz", @@ -11859,6 +12254,36 @@ } } }, + "node_modules/xlsx": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.17.0.tgz", + "integrity": "sha512-bZ36FSACiAyjoldey1+7it50PMlDp1pcAJrZKcVZHzKd8BC/z6TQ/QAN8onuqcepifqSznR6uKnjPhaGt6ig9A==", + "optional": true, + "dependencies": { + "adler-32": "~1.2.0", + "cfb": "^1.1.4", + "codepage": "~1.14.0", + "commander": "~2.17.1", + "crc-32": "~1.2.0", + "exit-on-epipe": "~1.0.1", + "fflate": "^0.3.8", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx/node_modules/commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "optional": true + }, "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index aa81e40..c855f9c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,10 +14,10 @@ "test": "jest", "test:watch": "jest --watch" }, - "engines" : { - "npm" : ">=9.0.0", - "node" : ">=18.0.0" - }, + "engines": { + "npm": ">=9.0.0", + "node": ">=18.0.0" + }, "overrides": { "react-lazyload": { "react": "^18.2.0", @@ -30,6 +30,8 @@ }, "dependencies": { "@arcnovus/wet-boew-react": "^2.0.0-beta.9", + "@linkurious/ogma": "https://get.linkurio.us/api/get/npm/ogma/5.0.2/?secret=lk-dls-c6212e2b03b09522c8ac2357791eef0a2f5fa97f", + "@linkurious/ogma-react": "^5.0.2", "@next-auth/prisma-adapter": "^1.0.7", "@prisma/client": "^5.6.0", "@t3-oss/env-nextjs": "^0.7.1", @@ -38,11 +40,14 @@ "@trpc/next": "^10.43.6", "@trpc/react-query": "^10.43.6", "@trpc/server": "^10.43.6", + "leaflet": "^1.9.4", + "neo4j-driver": "^5.18.0", "next": "^14.0.4", "next-auth": "^4.24.5", "next-intl": "^3.9.0", "plotly.js": "^2.29.1", "react": "18.2.0", + "react-datepicker": "^6.2.0", "react-dom": "18.2.0", "react-error-boundary": "^4.0.12", "react-plotly.js": "^2.6.0", @@ -58,6 +63,7 @@ "@types/jest": "^29.5.12", "@types/node": "^18.17.0", "@types/react": "^18.2.37", + "@types/react-datepicker": "^6.0.2", "@types/react-dom": "^18.2.15", "@types/react-plotly.js": "^2.6.3", "@typescript-eslint/eslint-plugin": "^6.11.0", diff --git a/frontend/src/app/[locale]/[day]/page.tsx b/frontend/src/app/[locale]/[day]/page.tsx index 3f26d05..caeb1ae 100644 --- a/frontend/src/app/[locale]/[day]/page.tsx +++ b/frontend/src/app/[locale]/[day]/page.tsx @@ -1,21 +1,18 @@ -import { useTranslations } from "next-intl"; +import { getTranslations } from "next-intl/server"; -import Image from "next/image"; import AppWrapper from "~/app/_components/AppWrapper"; +import Graph from "~/app/_components/graph"; import TimeTravel from "~/app/_components/TimeTravel"; -import useDateToStr from "~/app/_hooks/useDateToStr"; -// import Map from "~/app/_components/Map"; -// import geojson from "~/custom.geo.json"; +import countries from "~/countries.json"; -export default function Index({ +export default async function Index({ params, }: { params: { day: string; locale: string }; }) { - const t = useTranslations("App"); - const timeTravelMsg = useTranslations("TimeTravel"); - const dateToStr = useDateToStr(params.locale); + const t = await getTranslations("App"); + const timeTravelMsg = await getTranslations("TimeTravel"); const startDate = new Date(2019, 11, 1, 12); const endDate = new Date(2020, 0, 31, 12); @@ -38,7 +35,10 @@ export default function Index({ previousButton: timeTravelMsg("previousButton"), }} /> -
+
+ +
+ {/*
{Array.from({ length: 3 }).map((_, i) => (
@@ -83,14 +83,14 @@ export default function Index({

))}
-
+ */} ); } diff --git a/frontend/src/app/_components/AppWrapper.tsx b/frontend/src/app/_components/AppWrapper.tsx index a0196f4..6703dd2 100644 --- a/frontend/src/app/_components/AppWrapper.tsx +++ b/frontend/src/app/_components/AppWrapper.tsx @@ -1,10 +1,12 @@ "use client"; import { useParams } from "next/navigation"; -import React from "react"; +import React, { useRef } from "react"; import { AppTemplate, useLngLinks } from "@arcnovus/wet-boew-react"; +import { useResizeObserver } from "usehooks-ts"; import useWetLang from "~/app/_hooks/useWetLang"; +import useDelayedResizeObserver from "~/app/_hooks/useDelayedResizeObserver"; export default function AppWrapper({ children, @@ -19,16 +21,34 @@ export default function AppWrapper({ currentLanguage: lang, translatedPage: locale === "en-CA" ? "/fr-CA/1" : "/en-CA/1", }); + const ref = useRef(null); + + const { height = 0 } = useResizeObserver({ + ref, + box: "border-box", + }); + + const headerHeight = useDelayedResizeObserver("def-appTop"); + const preFooterHeight = useDelayedResizeObserver("def-preFooter"); + const footerHeight = useDelayedResizeObserver("wb-info"); return ( -
+
-
{children}
+
+ {children} +
); diff --git a/frontend/src/app/_components/Map.tsx b/frontend/src/app/_components/Map.tsx deleted file mode 100644 index 2bafb37..0000000 --- a/frontend/src/app/_components/Map.tsx +++ /dev/null @@ -1,109 +0,0 @@ -"use client"; - -import { useEffect, useRef } from "react"; -import Plot from "react-plotly.js"; - -const test = - `code,state,category,total exports,beef,pork,poultry,dairy,fruits fresh,fruits proc,total fruits,veggies fresh,veggies proc,total veggies,corn,wheat,cotton -CAN,Canada,state,1794.57,58.80,1.40,14.20,63.66,100.70,214.40,315.04,48.20,78.30,126.50,11.70,320.30,0.00 -AUS,Australia,state,10969.87,50.90,91.30,169.80,280.87,28.60,60.90,89.48,14.60,23.70,38.26,112.10,41.00,0.00 -EGY,Egypt,state,180.14,6.20,0.20,0.90,65.98,2.60,5.40,8.01,1.50,2.50,4.05,0.00,0.00,0.00 -CHN,China,state,17000.81,59.20,0.00,35.60,154.18,555.60,1183.00,1738.57,138.70,225.10,363.79,29.50,786.30,0.00 -`.split("\n"); - -const keys = test[0]?.split(","); -const rows: object[] = []; -if (keys) { - for (let x = 1; x < test.length; x += 1) { - const data = test[x]?.split(","); - if (data && data.length === keys.length) { - rows.push(Object.fromEntries(keys.map((k, i) => [k, data[i] ?? ""]))); - } - } -} - -// @ts-expect-error just testing -const unpack = (key: string) => rows.map((r) => r[key] as string); - -export default function Map({ geojson }: { geojson: object }) { - const container = useRef(null); - // const [width, setWidth] = useState(0); - // const [height, setHeight] = useState(0); - useEffect(() => { - if (container.current) { - // setWidth(container.current.clientWidth); - // setHeight(container.current.clientHeight); - } - }, [container]); - return ( -
- -
- ); -} diff --git a/frontend/src/app/_components/WithWet.tsx b/frontend/src/app/_components/WithWet.tsx index 1075189..cb23f8f 100644 --- a/frontend/src/app/_components/WithWet.tsx +++ b/frontend/src/app/_components/WithWet.tsx @@ -15,5 +15,10 @@ export default function WithWet({ children }: { children: React.ReactNode }) { window.wet = window.wet || {}; }, []); const lang = useWetLang(); + useEffect(() => { + console.log("-- load with wet --"); + }, []); + + return {children}; } diff --git a/frontend/src/app/_components/graph/DataLoader.tsx b/frontend/src/app/_components/graph/DataLoader.tsx new file mode 100644 index 0000000..a52572e --- /dev/null +++ b/frontend/src/app/_components/graph/DataLoader.tsx @@ -0,0 +1,79 @@ +import { useOgma } from "@linkurious/ogma-react"; +import OgmaLib from "@linkurious/ogma"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { api } from "~/trpc/react"; +import ProgressBar from "./ProgressBar"; +import { type Country } from "."; + +export default function DataLoader({ countries }: { countries: Country[] }) { + const ogma = useOgma(); + const [totalSize, setTotalSize] = useState(0); + const [progress, setProgress] = useState(0); + + const progressTimer = useRef(null); + const progressTick = useRef(0); + + const { isLoading, data: rawGraph } = api.post.articles.useQuery(undefined, { + refetchOnWindowFocus: false, + }); + + const tick = useCallback(() => { + progressTimer.current = setTimeout(() => { + progressTick.current += 3; + if (progressTick.current >= 100) progressTick.current = 0; + setProgress(progressTick.current); + tick(); + }, 200); + }, []); + + useEffect(() => { + if (isLoading && progressTimer.current === null) { + tick(); + } else if (!isLoading && progressTimer.current !== null) { + clearTimeout(progressTimer.current); + } + }, [isLoading, tick]); + + useEffect(() => { + if (!rawGraph) return; + const parse = async () => { + const g = await OgmaLib.parse.neo4j(rawGraph); + const graph = { + nodes: g.nodes.map((n) => { + if (n.data?.neo4jLabels.includes("Topic")) return n; + const c = n.data?.neo4jProperties?.countries; + if (c && Array.isArray(c) && c.length > 0) { + for (const country of countries) { + if (c.includes(country.country)) { + return { + ...n, + data: { + ...n.data, + geo: country, + }, + }; + } + } + } + return n; + }), + edges: g.edges, + }; + setTotalSize(graph.nodes.length + graph.edges.length); + await ogma.view.locateRawGraph(graph); + await ogma.setGraph(graph, { batchSize: 500 }); + setTotalSize(0); + }; + void parse(); + }, [rawGraph, isLoading, countries, ogma]); + + if (isLoading) + return ( + + ); + + if (totalSize > 0) + return ; + + return <>; +} diff --git a/frontend/src/app/_components/graph/Layout.tsx b/frontend/src/app/_components/graph/Layout.tsx new file mode 100644 index 0000000..606ba46 --- /dev/null +++ b/frontend/src/app/_components/graph/Layout.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { useOgma } from "@linkurious/ogma-react"; +import { useEffect } from "react"; + +export default function LayoutService() { + const ogma = useOgma(); + + useEffect(() => { + // register listener + const onNodesAdded = () => { + ogma.events.once("idle", () => + ogma.layouts.force({ locate: true, gpu: true }), + ); + }; + + ogma.events.on( + ["addNodes", "transformationEnabled", "transformationDisabled"], + onNodesAdded, + ); + + // cleanup + return () => { + ogma.events.off(onNodesAdded); + }; + }, [ogma]); + + return null; + } + \ No newline at end of file diff --git a/frontend/src/app/_components/graph/ProgressBar.tsx b/frontend/src/app/_components/graph/ProgressBar.tsx new file mode 100644 index 0000000..b58d440 --- /dev/null +++ b/frontend/src/app/_components/graph/ProgressBar.tsx @@ -0,0 +1,192 @@ +import { useOgma } from "@linkurious/ogma-react"; +import { useEffect, useMemo, useState } from "react"; + +interface ProgressbarOptions { + /** Gauge radius */ + radius?: number; + /** Text to display. Default "progress" */ + text?: string; + /** CSS class name. Default "progressbar" */ + className?: string; + /** Total amount of nodes and edges */ + totalSize?: number; + /** Number from 0 to 100 */ + percent?: number; + /** show percentage */ + showPercent?: boolean; +} + +export default function ProgressBar({ + radius = 70, + text = "Progress", + className = "ogmaprogressbar", + totalSize = 100, + percent: overridePercent, + showPercent = true, +}: ProgressbarOptions = {}) { + const ogma = useOgma(); + const [percent, setPercent] = useState(0); + + useEffect(() => { + if (typeof overridePercent === "number") setPercent(overridePercent); + }, [overridePercent]); + + useEffect(() => { + const onProgress = () => { + const currentSize = ogma.getNodes().size + ogma.getEdges().size; + setPercent((currentSize / totalSize) * 100); + }; + + ogma.events.on(["addNodes", "addEdges"], onProgress); + + return () => { + ogma.events.off(onProgress); + }; + }, [ogma, totalSize]); + + const strokeDashoffset = useMemo(() => { + if (percent >= 0 && percent <= 100) { + const c = radius * 2 * Math.PI; + return c - (c * percent) / 100; + } + }, [radius, percent]); + + return ( +
+
+
+
+ + + + +
+ {showPercent && ( +

+ {percent.toFixed(2)} + % +

+ )} +
+
+

{text}

+
+
+
+ ); +} + +export class Progressbar { + private _container: HTMLElement; + private _text: HTMLElement; + private _num: HTMLElement; + private _progress: SVGCircleElement; + private _circumference: number; + + /** + * @param {object} options + * @param {number} options.radius=70 Gauge radius + * @param {string} options.text="Progress" Text to display + * @param {string} className="progressbar" + */ + constructor({ + radius = 70, + text = "Progress", + className = "progressbar", + }: ProgressbarOptions = {}) { + this._container = this._renderTemplate({ text, radius, name: className }); + this._text = this._container.querySelector(`.${className}--text`)!; + this._num = this._container.querySelector(`.${className}--num`)!; + this._progress = this._container.querySelector(`.${className}--progress`)!; + this._circumference = radius * 2 * Math.PI; + } + + /** + * Show the progressbar above all other UI + */ + show() { + document.body.appendChild(this._container); + return this; + } + + /** + * Remove the progressbar from the DOM + */ + hide() { + try { + document.body.removeChild(this._container); + } catch {} + return this; + } + + /** + * @param percent Number between 0 and 100 + */ + setValue(percent: number) { + if (percent >= 0 && percent <= 100) { + const c = this._circumference; + const value = c - (c * percent) / 100; + this._progress.style.strokeDashoffset = value.toString(); + this._num.innerHTML = `

${percent.toFixed(2)}%

`; + } + return this; + } + + /** + * @param text + * @returns + */ + setText(text: string) { + this._text.innerText = text; + return this; + } + + private _renderTemplate({ + text, + radius, + name, + }: { + text: string; + radius: number; + name: string; + }) { + const container = document.createElement("div"); + container.className = name; + container.innerHTML = ` +
+
+
+ + + + +
+

0%

+
+
+

${text}

+
+
+ `; + return container; + } +} diff --git a/frontend/src/app/_components/graph/index.tsx b/frontend/src/app/_components/graph/index.tsx new file mode 100644 index 0000000..b0514ed --- /dev/null +++ b/frontend/src/app/_components/graph/index.tsx @@ -0,0 +1,176 @@ +"use client"; + +import { useCallback, useEffect, useRef, useState } from "react"; +import L from "leaflet"; + +import { + EdgeStyleRule, + Geo, + NodeStyleRule, + Ogma, + Tooltip, +} from "@linkurious/ogma-react"; +import OgmaLib, { + type Edge, + type Node as OgmaNode, + type Point, +} from "@linkurious/ogma"; + +import "leaflet/dist/leaflet.css"; +import { useResizeObserver } from "usehooks-ts"; +import LayoutService from "./Layout"; + +import DataLoader from "./DataLoader"; + +OgmaLib.libraries.leaflet = L; + +// Helper function to get the type of a Node +function getNodeType(node: OgmaNode): string { + const labels = node.getData("neo4jLabels") as string[]; + return labels[0] ?? "unknown"; +} + +export interface Country { + country: string; + latitude: string; + longitude: string; + name: string; +} + +export default function Graph({ countries }: { countries: Country[] }) { + // const [popupOpen, setPopupOpen] = useState(false); + // const [clickedNode, setClickedNode] = useState(); + const ref = useRef(null); + const ogmaRef = useRef(null); + const { height, width } = useResizeObserver({ ref, box: "border-box" }); + const [tooltipPositon, setTooltipPosition] = useState({ + x: 0, + y: 0, + }); + const [target, setTarget] = useState(); + + // Controls + + const [geoMode, setGeoMode] = useState(false); + + const requestSetTooltipPosition = useCallback((pos: Point) => { + requestAnimationFrame(() => setTooltipPosition(pos)); + }, []); + + const handleGeoBtnClick = useCallback(() => { + setGeoMode(!geoMode); + }, [geoMode]); + + useEffect(() => { + if (ogmaRef.current && width && height) { + void ogmaRef.current.view.setSize({ width, height }); + } + }, [height, width]); + + return ( +
+
+ { + ogma.events + // .on("click", ({ target }) => { + // if (target && target.isNode) { + // setClickedNode(target); + // setPopupOpen(true); + // } + // }) + .on("mousemove", () => { + const ptr = ogma.getPointerInformation(); + requestSetTooltipPosition( + ogma.view.screenToGraphCoordinates({ x: ptr.x, y: ptr.y }), + ); + setTarget(ptr.target); + }) + // locate graph when the nodes are added + .on("addNodes", () => + ogma.view.locateGraph({ duration: 250, padding: 50 }), + ); + }} + > + + + getNodeType(n) === "Topic" ? "#76378A" : "#9AD0D0", + radius: (n) => 10 + n.getAdjacentNodes().size / 10, + text: (n) => getNodeType(n), + // pulse: (n) => ({ + // enabled: + // getNodeType(n) === "Topic" && n.getAdjacentNodes().size > 500, + // endRatio: 1.5, + // width: 4, + // interval: 600, + // startRatio: 1.0, + // endColor: "red", + // startColor: "orange", + // }), + }} + /> + + {/* (clickedNode ? clickedNode.getPosition() : null)} + onClose={() => setPopupOpen(false)} + isOpen={clickedNode && popupOpen} + > + {clickedNode && ( +
{`Node ${clickedNode.getId()}:`}
+ )} +
*/} + +
+ {target && + target.isNode && + (getNodeType(target) === "Topic" + ? target.getData("neo4jProperties.text") + : target.getData("neo4jProperties.title"))} + {target && !target.isNode && `Edge ${target.getId()}`} +
+
+ + + <> +
+ +
+ +
+
+
+ ); +} diff --git a/frontend/src/app/_hooks/useDateToStr.tsx b/frontend/src/app/_hooks/useDateToStr.tsx index 657ad99..2ebf242 100644 --- a/frontend/src/app/_hooks/useDateToStr.tsx +++ b/frontend/src/app/_hooks/useDateToStr.tsx @@ -1,22 +1,10 @@ import { useCallback } from "react"; +import dateToStr from "~/app/_utils/dateToStr"; export default function useDateToStr(locale: string | string[] | undefined) { return useCallback( (d: Date, long?: boolean) => { - return d.toLocaleDateString( - locale, - !long - ? { - day: "numeric", - month: "short", - year: "numeric", - } - : { - day: "2-digit", - month: "long", - year: "numeric", - }, - ); + return dateToStr(locale, d, long); }, [locale], ); diff --git a/frontend/src/app/_hooks/useDelayedResizeObserver.tsx b/frontend/src/app/_hooks/useDelayedResizeObserver.tsx new file mode 100644 index 0000000..51e6769 --- /dev/null +++ b/frontend/src/app/_hooks/useDelayedResizeObserver.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { useEffect, useState } from "react"; + +const TICK_SPEED = 100; + +export default function useDelayedResizeObserver( + elementId: string, + timeout = 5, +) { + const [height, setHeight] = useState(0); + + useEffect(() => { + console.log("-- load delayed resize observer --"); + const observer = new ResizeObserver(([entry]) => { + const newHeight = entry?.borderBoxSize[0]?.blockSize; + if (newHeight) setHeight(newHeight); + }); + let count = 0; + const findElement = () => { + setTimeout(() => { + count += TICK_SPEED; + const element = document.getElementById(elementId); + if (!element) { + console.log(`${elementId} - ${count}`); + if (timeout > 0 && count > timeout * 1000) { + throw new Error(`Unable to locate element with id ${elementId}.`); + } + findElement(); + } else { + observer.observe(element, { box: "border-box" }); + } + }, TICK_SPEED); + }; + findElement(); + return () => { + observer.disconnect(); + }; + }, [elementId, timeout]); + + return height; +} diff --git a/frontend/src/app/_utils/dateToStr.tsx b/frontend/src/app/_utils/dateToStr.tsx new file mode 100644 index 0000000..db77ce4 --- /dev/null +++ b/frontend/src/app/_utils/dateToStr.tsx @@ -0,0 +1,20 @@ +export default function dateToStr( + locale: string | string[] | undefined, + d: Date, + long?: boolean, +) { + return d.toLocaleDateString( + locale, + !long + ? { + day: "numeric", + month: "short", + year: "numeric", + } + : { + day: "2-digit", + month: "long", + year: "numeric", + }, + ); +} diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index c5b6359..05d5f76 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,5 +1,6 @@ import "~/styles/globals.css"; +import { TRPCReactProvider } from "~/trpc/react"; import WithWet from "./_components/WithWet"; export default function RootLayout({ @@ -10,11 +11,14 @@ export default function RootLayout({ params: { locale: string }; }) { const lang = locale || "en-CA"; + return ( - - - {children} - - + + + + {children} + + + ); } diff --git a/frontend/src/countries.json b/frontend/src/countries.json new file mode 100644 index 0000000..409185e --- /dev/null +++ b/frontend/src/countries.json @@ -0,0 +1,1472 @@ +[ + { + "country": "AD", + "latitude": "42.546245", + "longitude": "1.601554", + "name": "Andorra" + }, + { + "country": "AE", + "latitude": "23.424076", + "longitude": "53.847818", + "name": "United Arab Emirates" + }, + { + "country": "AF", + "latitude": "33.93911", + "longitude": "67.709953", + "name": "Afghanistan" + }, + { + "country": "AG", + "latitude": "17.060816", + "longitude": "-61.796428", + "name": "Antigua and Barbuda" + }, + { + "country": "AI", + "latitude": "18.220554", + "longitude": "-63.068615", + "name": "Anguilla" + }, + { + "country": "AL", + "latitude": "41.153332", + "longitude": "20.168331", + "name": "Albania" + }, + { + "country": "AM", + "latitude": "40.069099", + "longitude": "45.038189", + "name": "Armenia" + }, + { + "country": "AN", + "latitude": "12.226079", + "longitude": "-69.060087", + "name": "Netherlands Antilles" + }, + { + "country": "AO", + "latitude": "-11.202692", + "longitude": "17.873887", + "name": "Angola" + }, + { + "country": "AQ", + "latitude": "-75.250973", + "longitude": "-0.071389", + "name": "Antarctica" + }, + { + "country": "AR", + "latitude": "-38.416097", + "longitude": "-63.616672", + "name": "Argentina" + }, + { + "country": "AS", + "latitude": "-14.270972", + "longitude": "-170.132217", + "name": "American Samoa" + }, + { + "country": "AT", + "latitude": "47.516231", + "longitude": "14.550072", + "name": "Austria" + }, + { + "country": "AU", + "latitude": "-25.274398", + "longitude": "133.775136", + "name": "Australia" + }, + { + "country": "AW", + "latitude": "12.52111", + "longitude": "-69.968338", + "name": "Aruba" + }, + { + "country": "AZ", + "latitude": "40.143105", + "longitude": "47.576927", + "name": "Azerbaijan" + }, + { + "country": "BA", + "latitude": "43.915886", + "longitude": "17.679076", + "name": "Bosnia and Herzegovina" + }, + { + "country": "BB", + "latitude": "13.193887", + "longitude": "-59.543198", + "name": "Barbados" + }, + { + "country": "BD", + "latitude": "23.684994", + "longitude": "90.356331", + "name": "Bangladesh" + }, + { + "country": "BE", + "latitude": "50.503887", + "longitude": "4.469936", + "name": "Belgium" + }, + { + "country": "BF", + "latitude": "12.238333", + "longitude": "-1.561593", + "name": "Burkina Faso" + }, + { + "country": "BG", + "latitude": "42.733883", + "longitude": "25.48583", + "name": "Bulgaria" + }, + { + "country": "BH", + "latitude": "25.930414", + "longitude": "50.637772", + "name": "Bahrain" + }, + { + "country": "BI", + "latitude": "-3.373056", + "longitude": "29.918886", + "name": "Burundi" + }, + { + "country": "BJ", + "latitude": "9.30769", + "longitude": "2.315834", + "name": "Benin" + }, + { + "country": "BM", + "latitude": "32.321384", + "longitude": "-64.75737", + "name": "Bermuda" + }, + { + "country": "BN", + "latitude": "4.535277", + "longitude": "114.727669", + "name": "Brunei" + }, + { + "country": "BO", + "latitude": "-16.290154", + "longitude": "-63.588653", + "name": "Bolivia" + }, + { + "country": "BR", + "latitude": "-14.235004", + "longitude": "-51.92528", + "name": "Brazil" + }, + { + "country": "BS", + "latitude": "25.03428", + "longitude": "-77.39628", + "name": "Bahamas" + }, + { + "country": "BT", + "latitude": "27.514162", + "longitude": "90.433601", + "name": "Bhutan" + }, + { + "country": "BV", + "latitude": "-54.423199", + "longitude": "3.413194", + "name": "Bouvet Island" + }, + { + "country": "BW", + "latitude": "-22.328474", + "longitude": "24.684866", + "name": "Botswana" + }, + { + "country": "BY", + "latitude": "53.709807", + "longitude": "27.953389", + "name": "Belarus" + }, + { + "country": "BZ", + "latitude": "17.189877", + "longitude": "-88.49765", + "name": "Belize" + }, + { + "country": "CA", + "latitude": "56.130366", + "longitude": "-106.346771", + "name": "Canada" + }, + { + "country": "CC", + "latitude": "-12.164165", + "longitude": "96.870956", + "name": "Cocos [Keeling] Islands" + }, + { + "country": "CD", + "latitude": "-4.038333", + "longitude": "21.758664", + "name": "Congo [DRC]" + }, + { + "country": "CF", + "latitude": "6.611111", + "longitude": "20.939444", + "name": "Central African Republic" + }, + { + "country": "CG", + "latitude": "-0.228021", + "longitude": "15.827659", + "name": "Congo [Republic]" + }, + { + "country": "CH", + "latitude": "46.818188", + "longitude": "8.227512", + "name": "Switzerland" + }, + { + "country": "CI", + "latitude": "7.539989", + "longitude": "-5.54708", + "name": "Côte d'Ivoire" + }, + { + "country": "CK", + "latitude": "-21.236736", + "longitude": "-159.777671", + "name": "Cook Islands" + }, + { + "country": "CL", + "latitude": "-35.675147", + "longitude": "-71.542969", + "name": "Chile" + }, + { + "country": "CM", + "latitude": "7.369722", + "longitude": "12.354722", + "name": "Cameroon" + }, + { + "country": "CN", + "latitude": "35.86166", + "longitude": "104.195397", + "name": "China" + }, + { + "country": "CO", + "latitude": "4.570868", + "longitude": "-74.297333", + "name": "Colombia" + }, + { + "country": "CR", + "latitude": "9.748917", + "longitude": "-83.753428", + "name": "Costa Rica" + }, + { + "country": "CU", + "latitude": "21.521757", + "longitude": "-77.781167", + "name": "Cuba" + }, + { + "country": "CV", + "latitude": "16.002082", + "longitude": "-24.013197", + "name": "Cape Verde" + }, + { + "country": "CX", + "latitude": "-10.447525", + "longitude": "105.690449", + "name": "Christmas Island" + }, + { + "country": "CY", + "latitude": "35.126413", + "longitude": "33.429859", + "name": "Cyprus" + }, + { + "country": "CZ", + "latitude": "49.817492", + "longitude": "15.472962", + "name": "Czech Republic" + }, + { + "country": "DE", + "latitude": "51.165691", + "longitude": "10.451526", + "name": "Germany" + }, + { + "country": "DJ", + "latitude": "11.825138", + "longitude": "42.590275", + "name": "Djibouti" + }, + { + "country": "DK", + "latitude": "56.26392", + "longitude": "9.501785", + "name": "Denmark" + }, + { + "country": "DM", + "latitude": "15.414999", + "longitude": "-61.370976", + "name": "Dominica" + }, + { + "country": "DO", + "latitude": "18.735693", + "longitude": "-70.162651", + "name": "Dominican Republic" + }, + { + "country": "DZ", + "latitude": "28.033886", + "longitude": "1.659626", + "name": "Algeria" + }, + { + "country": "EC", + "latitude": "-1.831239", + "longitude": "-78.183406", + "name": "Ecuador" + }, + { + "country": "EE", + "latitude": "58.595272", + "longitude": "25.013607", + "name": "Estonia" + }, + { + "country": "EG", + "latitude": "26.820553", + "longitude": "30.802498", + "name": "Egypt" + }, + { + "country": "EH", + "latitude": "24.215527", + "longitude": "-12.885834", + "name": "Western Sahara" + }, + { + "country": "ER", + "latitude": "15.179384", + "longitude": "39.782334", + "name": "Eritrea" + }, + { + "country": "ES", + "latitude": "40.463667", + "longitude": "-3.74922", + "name": "Spain" + }, + { + "country": "ET", + "latitude": "9.145", + "longitude": "40.489673", + "name": "Ethiopia" + }, + { + "country": "FI", + "latitude": "61.92411", + "longitude": "25.748151", + "name": "Finland" + }, + { + "country": "FJ", + "latitude": "-16.578193", + "longitude": "179.414413", + "name": "Fiji" + }, + { + "country": "FK", + "latitude": "-51.796253", + "longitude": "-59.523613", + "name": "Falkland Islands [Islas Malvinas]" + }, + { + "country": "FM", + "latitude": "7.425554", + "longitude": "150.550812", + "name": "Micronesia" + }, + { + "country": "FO", + "latitude": "61.892635", + "longitude": "-6.911806", + "name": "Faroe Islands" + }, + { + "country": "FR", + "latitude": "46.227638", + "longitude": "2.213749", + "name": "France" + }, + { + "country": "GA", + "latitude": "-0.803689", + "longitude": "11.609444", + "name": "Gabon" + }, + { + "country": "GB", + "latitude": "55.378051", + "longitude": "-3.435973", + "name": "United Kingdom" + }, + { + "country": "GD", + "latitude": "12.262776", + "longitude": "-61.604171", + "name": "Grenada" + }, + { + "country": "GE", + "latitude": "42.315407", + "longitude": "43.356892", + "name": "Georgia" + }, + { + "country": "GF", + "latitude": "3.933889", + "longitude": "-53.125782", + "name": "French Guiana" + }, + { + "country": "GG", + "latitude": "49.465691", + "longitude": "-2.585278", + "name": "Guernsey" + }, + { + "country": "GH", + "latitude": "7.946527", + "longitude": "-1.023194", + "name": "Ghana" + }, + { + "country": "GI", + "latitude": "36.137741", + "longitude": "-5.345374", + "name": "Gibraltar" + }, + { + "country": "GL", + "latitude": "71.706936", + "longitude": "-42.604303", + "name": "Greenland" + }, + { + "country": "GM", + "latitude": "13.443182", + "longitude": "-15.310139", + "name": "Gambia" + }, + { + "country": "GN", + "latitude": "9.945587", + "longitude": "-9.696645", + "name": "Guinea" + }, + { + "country": "GP", + "latitude": "16.995971", + "longitude": "-62.067641", + "name": "Guadeloupe" + }, + { + "country": "GQ", + "latitude": "1.650801", + "longitude": "10.267895", + "name": "Equatorial Guinea" + }, + { + "country": "GR", + "latitude": "39.074208", + "longitude": "21.824312", + "name": "Greece" + }, + { + "country": "GS", + "latitude": "-54.429579", + "longitude": "-36.587909", + "name": "South Georgia and the South Sandwich Islands" + }, + { + "country": "GT", + "latitude": "15.783471", + "longitude": "-90.230759", + "name": "Guatemala" + }, + { + "country": "GU", + "latitude": "13.444304", + "longitude": "144.793731", + "name": "Guam" + }, + { + "country": "GW", + "latitude": "11.803749", + "longitude": "-15.180413", + "name": "Guinea-Bissau" + }, + { + "country": "GY", + "latitude": "4.860416", + "longitude": "-58.93018", + "name": "Guyana" + }, + { + "country": "GZ", + "latitude": "31.354676", + "longitude": "34.308825", + "name": "Gaza Strip" + }, + { + "country": "HK", + "latitude": "22.396428", + "longitude": "114.109497", + "name": "Hong Kong" + }, + { + "country": "HM", + "latitude": "-53.08181", + "longitude": "73.504158", + "name": "Heard Island and McDonald Islands" + }, + { + "country": "HN", + "latitude": "15.199999", + "longitude": "-86.241905", + "name": "Honduras" + }, + { + "country": "HR", + "latitude": "45.1", + "longitude": "15.2", + "name": "Croatia" + }, + { + "country": "HT", + "latitude": "18.971187", + "longitude": "-72.285215", + "name": "Haiti" + }, + { + "country": "HU", + "latitude": "47.162494", + "longitude": "19.503304", + "name": "Hungary" + }, + { + "country": "ID", + "latitude": "-0.789275", + "longitude": "113.921327", + "name": "Indonesia" + }, + { + "country": "IE", + "latitude": "53.41291", + "longitude": "-8.24389", + "name": "Ireland" + }, + { + "country": "IL", + "latitude": "31.046051", + "longitude": "34.851612", + "name": "Israel" + }, + { + "country": "IM", + "latitude": "54.236107", + "longitude": "-4.548056", + "name": "Isle of Man" + }, + { + "country": "IN", + "latitude": "20.593684", + "longitude": "78.96288", + "name": "India" + }, + { + "country": "IO", + "latitude": "-6.343194", + "longitude": "71.876519", + "name": "British Indian Ocean Territory" + }, + { + "country": "IQ", + "latitude": "33.223191", + "longitude": "43.679291", + "name": "Iraq" + }, + { + "country": "IR", + "latitude": "32.427908", + "longitude": "53.688046", + "name": "Iran" + }, + { + "country": "IS", + "latitude": "64.963051", + "longitude": "-19.020835", + "name": "Iceland" + }, + { + "country": "IT", + "latitude": "41.87194", + "longitude": "12.56738", + "name": "Italy" + }, + { + "country": "JE", + "latitude": "49.214439", + "longitude": "-2.13125", + "name": "Jersey" + }, + { + "country": "JM", + "latitude": "18.109581", + "longitude": "-77.297508", + "name": "Jamaica" + }, + { + "country": "JO", + "latitude": "30.585164", + "longitude": "36.238414", + "name": "Jordan" + }, + { + "country": "JP", + "latitude": "36.204824", + "longitude": "138.252924", + "name": "Japan" + }, + { + "country": "KE", + "latitude": "-0.023559", + "longitude": "37.906193", + "name": "Kenya" + }, + { + "country": "KG", + "latitude": "41.20438", + "longitude": "74.766098", + "name": "Kyrgyzstan" + }, + { + "country": "KH", + "latitude": "12.565679", + "longitude": "104.990963", + "name": "Cambodia" + }, + { + "country": "KI", + "latitude": "-3.370417", + "longitude": "-168.734039", + "name": "Kiribati" + }, + { + "country": "KM", + "latitude": "-11.875001", + "longitude": "43.872219", + "name": "Comoros" + }, + { + "country": "KN", + "latitude": "17.357822", + "longitude": "-62.782998", + "name": "Saint Kitts and Nevis" + }, + { + "country": "KP", + "latitude": "40.339852", + "longitude": "127.510093", + "name": "North Korea" + }, + { + "country": "KR", + "latitude": "35.907757", + "longitude": "127.766922", + "name": "South Korea" + }, + { + "country": "KW", + "latitude": "29.31166", + "longitude": "47.481766", + "name": "Kuwait" + }, + { + "country": "KY", + "latitude": "19.513469", + "longitude": "-80.566956", + "name": "Cayman Islands" + }, + { + "country": "KZ", + "latitude": "48.019573", + "longitude": "66.923684", + "name": "Kazakhstan" + }, + { + "country": "LA", + "latitude": "19.85627", + "longitude": "102.495496", + "name": "Laos" + }, + { + "country": "LB", + "latitude": "33.854721", + "longitude": "35.862285", + "name": "Lebanon" + }, + { + "country": "LC", + "latitude": "13.909444", + "longitude": "-60.978893", + "name": "Saint Lucia" + }, + { + "country": "LI", + "latitude": "47.166", + "longitude": "9.555373", + "name": "Liechtenstein" + }, + { + "country": "LK", + "latitude": "7.873054", + "longitude": "80.771797", + "name": "Sri Lanka" + }, + { + "country": "LR", + "latitude": "6.428055", + "longitude": "-9.429499", + "name": "Liberia" + }, + { + "country": "LS", + "latitude": "-29.609988", + "longitude": "28.233608", + "name": "Lesotho" + }, + { + "country": "LT", + "latitude": "55.169438", + "longitude": "23.881275", + "name": "Lithuania" + }, + { + "country": "LU", + "latitude": "49.815273", + "longitude": "6.129583", + "name": "Luxembourg" + }, + { + "country": "LV", + "latitude": "56.879635", + "longitude": "24.603189", + "name": "Latvia" + }, + { + "country": "LY", + "latitude": "26.3351", + "longitude": "17.228331", + "name": "Libya" + }, + { + "country": "MA", + "latitude": "31.791702", + "longitude": "-7.09262", + "name": "Morocco" + }, + { + "country": "MC", + "latitude": "43.750298", + "longitude": "7.412841", + "name": "Monaco" + }, + { + "country": "MD", + "latitude": "47.411631", + "longitude": "28.369885", + "name": "Moldova" + }, + { + "country": "ME", + "latitude": "42.708678", + "longitude": "19.37439", + "name": "Montenegro" + }, + { + "country": "MG", + "latitude": "-18.766947", + "longitude": "46.869107", + "name": "Madagascar" + }, + { + "country": "MH", + "latitude": "7.131474", + "longitude": "171.184478", + "name": "Marshall Islands" + }, + { + "country": "MK", + "latitude": "41.608635", + "longitude": "21.745275", + "name": "Macedonia [FYROM]" + }, + { + "country": "ML", + "latitude": "17.570692", + "longitude": "-3.996166", + "name": "Mali" + }, + { + "country": "MM", + "latitude": "21.913965", + "longitude": "95.956223", + "name": "Myanmar [Burma]" + }, + { + "country": "MN", + "latitude": "46.862496", + "longitude": "103.846656", + "name": "Mongolia" + }, + { + "country": "MO", + "latitude": "22.198745", + "longitude": "113.543873", + "name": "Macau" + }, + { + "country": "MP", + "latitude": "17.33083", + "longitude": "145.38469", + "name": "Northern Mariana Islands" + }, + { + "country": "MQ", + "latitude": "14.641528", + "longitude": "-61.024174", + "name": "Martinique" + }, + { + "country": "MR", + "latitude": "21.00789", + "longitude": "-10.940835", + "name": "Mauritania" + }, + { + "country": "MS", + "latitude": "16.742498", + "longitude": "-62.187366", + "name": "Montserrat" + }, + { + "country": "MT", + "latitude": "35.937496", + "longitude": "14.375416", + "name": "Malta" + }, + { + "country": "MU", + "latitude": "-20.348404", + "longitude": "57.552152", + "name": "Mauritius" + }, + { + "country": "MV", + "latitude": "3.202778", + "longitude": "73.22068", + "name": "Maldives" + }, + { + "country": "MW", + "latitude": "-13.254308", + "longitude": "34.301525", + "name": "Malawi" + }, + { + "country": "MX", + "latitude": "23.634501", + "longitude": "-102.552784", + "name": "Mexico" + }, + { + "country": "MY", + "latitude": "4.210484", + "longitude": "101.975766", + "name": "Malaysia" + }, + { + "country": "MZ", + "latitude": "-18.665695", + "longitude": "35.529562", + "name": "Mozambique" + }, + { + "country": "NA", + "latitude": "-22.95764", + "longitude": "18.49041", + "name": "Namibia" + }, + { + "country": "NC", + "latitude": "-20.904305", + "longitude": "165.618042", + "name": "New Caledonia" + }, + { + "country": "NE", + "latitude": "17.607789", + "longitude": "8.081666", + "name": "Niger" + }, + { + "country": "NF", + "latitude": "-29.040835", + "longitude": "167.954712", + "name": "Norfolk Island" + }, + { + "country": "NG", + "latitude": "9.081999", + "longitude": "8.675277", + "name": "Nigeria" + }, + { + "country": "NI", + "latitude": "12.865416", + "longitude": "-85.207229", + "name": "Nicaragua" + }, + { + "country": "NL", + "latitude": "52.132633", + "longitude": "5.291266", + "name": "Netherlands" + }, + { + "country": "NO", + "latitude": "60.472024", + "longitude": "8.468946", + "name": "Norway" + }, + { + "country": "NP", + "latitude": "28.394857", + "longitude": "84.124008", + "name": "Nepal" + }, + { + "country": "NR", + "latitude": "-0.522778", + "longitude": "166.931503", + "name": "Nauru" + }, + { + "country": "NU", + "latitude": "-19.054445", + "longitude": "-169.867233", + "name": "Niue" + }, + { + "country": "NZ", + "latitude": "-40.900557", + "longitude": "174.885971", + "name": "New Zealand" + }, + { + "country": "OM", + "latitude": "21.512583", + "longitude": "55.923255", + "name": "Oman" + }, + { + "country": "PA", + "latitude": "8.537981", + "longitude": "-80.782127", + "name": "Panama" + }, + { + "country": "PE", + "latitude": "-9.189967", + "longitude": "-75.015152", + "name": "Peru" + }, + { + "country": "PF", + "latitude": "-17.679742", + "longitude": "-149.406843", + "name": "French Polynesia" + }, + { + "country": "PG", + "latitude": "-6.314993", + "longitude": "143.95555", + "name": "Papua New Guinea" + }, + { + "country": "PH", + "latitude": "12.879721", + "longitude": "121.774017", + "name": "Philippines" + }, + { + "country": "PK", + "latitude": "30.375321", + "longitude": "69.345116", + "name": "Pakistan" + }, + { + "country": "PL", + "latitude": "51.919438", + "longitude": "19.145136", + "name": "Poland" + }, + { + "country": "PM", + "latitude": "46.941936", + "longitude": "-56.27111", + "name": "Saint Pierre and Miquelon" + }, + { + "country": "PN", + "latitude": "-24.703615", + "longitude": "-127.439308", + "name": "Pitcairn Islands" + }, + { + "country": "PR", + "latitude": "18.220833", + "longitude": "-66.590149", + "name": "Puerto Rico" + }, + { + "country": "PS", + "latitude": "31.952162", + "longitude": "35.233154", + "name": "Palestinian Territories" + }, + { + "country": "PT", + "latitude": "39.399872", + "longitude": "-8.224454", + "name": "Portugal" + }, + { + "country": "PW", + "latitude": "7.51498", + "longitude": "134.58252", + "name": "Palau" + }, + { + "country": "PY", + "latitude": "-23.442503", + "longitude": "-58.443832", + "name": "Paraguay" + }, + { + "country": "QA", + "latitude": "25.354826", + "longitude": "51.183884", + "name": "Qatar" + }, + { + "country": "RE", + "latitude": "-21.115141", + "longitude": "55.536384", + "name": "Réunion" + }, + { + "country": "RO", + "latitude": "45.943161", + "longitude": "24.96676", + "name": "Romania" + }, + { + "country": "RS", + "latitude": "44.016521", + "longitude": "21.005859", + "name": "Serbia" + }, + { + "country": "RU", + "latitude": "61.52401", + "longitude": "105.318756", + "name": "Russia" + }, + { + "country": "RW", + "latitude": "-1.940278", + "longitude": "29.873888", + "name": "Rwanda" + }, + { + "country": "SA", + "latitude": "23.885942", + "longitude": "45.079162", + "name": "Saudi Arabia" + }, + { + "country": "SB", + "latitude": "-9.64571", + "longitude": "160.156194", + "name": "Solomon Islands" + }, + { + "country": "SC", + "latitude": "-4.679574", + "longitude": "55.491977", + "name": "Seychelles" + }, + { + "country": "SD", + "latitude": "12.862807", + "longitude": "30.217636", + "name": "Sudan" + }, + { + "country": "SE", + "latitude": "60.128161", + "longitude": "18.643501", + "name": "Sweden" + }, + { + "country": "SG", + "latitude": "1.352083", + "longitude": "103.819836", + "name": "Singapore" + }, + { + "country": "SH", + "latitude": "-24.143474", + "longitude": "-10.030696", + "name": "Saint Helena" + }, + { + "country": "SI", + "latitude": "46.151241", + "longitude": "14.995463", + "name": "Slovenia" + }, + { + "country": "SJ", + "latitude": "77.553604", + "longitude": "23.670272", + "name": "Svalbard and Jan Mayen" + }, + { + "country": "SK", + "latitude": "48.669026", + "longitude": "19.699024", + "name": "Slovakia" + }, + { + "country": "SL", + "latitude": "8.460555", + "longitude": "-11.779889", + "name": "Sierra Leone" + }, + { + "country": "SM", + "latitude": "43.94236", + "longitude": "12.457777", + "name": "San Marino" + }, + { + "country": "SN", + "latitude": "14.497401", + "longitude": "-14.452362", + "name": "Senegal" + }, + { + "country": "SO", + "latitude": "5.152149", + "longitude": "46.199616", + "name": "Somalia" + }, + { + "country": "SR", + "latitude": "3.919305", + "longitude": "-56.027783", + "name": "Suriname" + }, + { + "country": "ST", + "latitude": "0.18636", + "longitude": "6.613081", + "name": "São Tomé and Príncipe" + }, + { + "country": "SV", + "latitude": "13.794185", + "longitude": "-88.89653", + "name": "El Salvador" + }, + { + "country": "SY", + "latitude": "34.802075", + "longitude": "38.996815", + "name": "Syria" + }, + { + "country": "SZ", + "latitude": "-26.522503", + "longitude": "31.465866", + "name": "Swaziland" + }, + { + "country": "TC", + "latitude": "21.694025", + "longitude": "-71.797928", + "name": "Turks and Caicos Islands" + }, + { + "country": "TD", + "latitude": "15.454166", + "longitude": "18.732207", + "name": "Chad" + }, + { + "country": "TF", + "latitude": "-49.280366", + "longitude": "69.348557", + "name": "French Southern Territories" + }, + { + "country": "TG", + "latitude": "8.619543", + "longitude": "0.824782", + "name": "Togo" + }, + { + "country": "TH", + "latitude": "15.870032", + "longitude": "100.992541", + "name": "Thailand" + }, + { + "country": "TJ", + "latitude": "38.861034", + "longitude": "71.276093", + "name": "Tajikistan" + }, + { + "country": "TK", + "latitude": "-8.967363", + "longitude": "-171.855881", + "name": "Tokelau" + }, + { + "country": "TL", + "latitude": "-8.874217", + "longitude": "125.727539", + "name": "Timor-Leste" + }, + { + "country": "TM", + "latitude": "38.969719", + "longitude": "59.556278", + "name": "Turkmenistan" + }, + { + "country": "TN", + "latitude": "33.886917", + "longitude": "9.537499", + "name": "Tunisia" + }, + { + "country": "TO", + "latitude": "-21.178986", + "longitude": "-175.198242", + "name": "Tonga" + }, + { + "country": "TR", + "latitude": "38.963745", + "longitude": "35.243322", + "name": "Turkey" + }, + { + "country": "TT", + "latitude": "10.691803", + "longitude": "-61.222503", + "name": "Trinidad and Tobago" + }, + { + "country": "TV", + "latitude": "-7.109535", + "longitude": "177.64933", + "name": "Tuvalu" + }, + { + "country": "TW", + "latitude": "23.69781", + "longitude": "120.960515", + "name": "Taiwan" + }, + { + "country": "TZ", + "latitude": "-6.369028", + "longitude": "34.888822", + "name": "Tanzania" + }, + { + "country": "UA", + "latitude": "48.379433", + "longitude": "31.16558", + "name": "Ukraine" + }, + { + "country": "UG", + "latitude": "1.373333", + "longitude": "32.290275", + "name": "Uganda" + }, + { + "country": "UM", + "latitude": "", + "longitude": "", + "name": "U.S. Minor Outlying Islands" + }, + { + "country": "US", + "latitude": "37.09024", + "longitude": "-95.712891", + "name": "United States" + }, + { + "country": "UY", + "latitude": "-32.522779", + "longitude": "-55.765835", + "name": "Uruguay" + }, + { + "country": "UZ", + "latitude": "41.377491", + "longitude": "64.585262", + "name": "Uzbekistan" + }, + { + "country": "VA", + "latitude": "41.902916", + "longitude": "12.453389", + "name": "Vatican City" + }, + { + "country": "VC", + "latitude": "12.984305", + "longitude": "-61.287228", + "name": "Saint Vincent and the Grenadines" + }, + { + "country": "VE", + "latitude": "6.42375", + "longitude": "-66.58973", + "name": "Venezuela" + }, + { + "country": "VG", + "latitude": "18.420695", + "longitude": "-64.639968", + "name": "British Virgin Islands" + }, + { + "country": "VI", + "latitude": "18.335765", + "longitude": "-64.896335", + "name": "U.S. Virgin Islands" + }, + { + "country": "VN", + "latitude": "14.058324", + "longitude": "108.277199", + "name": "Vietnam" + }, + { + "country": "VU", + "latitude": "-15.376706", + "longitude": "166.959158", + "name": "Vanuatu" + }, + { + "country": "WF", + "latitude": "-13.768752", + "longitude": "-177.156097", + "name": "Wallis and Futuna" + }, + { + "country": "WS", + "latitude": "-13.759029", + "longitude": "-172.104629", + "name": "Samoa" + }, + { + "country": "XK", + "latitude": "42.602636", + "longitude": "20.902977", + "name": "Kosovo" + }, + { + "country": "YE", + "latitude": "15.552727", + "longitude": "48.516388", + "name": "Yemen" + }, + { + "country": "YT", + "latitude": "-12.8275", + "longitude": "45.166244", + "name": "Mayotte" + }, + { + "country": "ZA", + "latitude": "-30.559482", + "longitude": "22.937506", + "name": "South Africa" + }, + { + "country": "ZM", + "latitude": "-13.133897", + "longitude": "27.849332", + "name": "Zambia" + }, + { + "country": "ZW", + "latitude": "-19.015438", + "longitude": "29.154857", + "name": "Zimbabwe" + } +] \ No newline at end of file diff --git a/frontend/src/env.js b/frontend/src/env.js index b190b4b..10c0980 100644 --- a/frontend/src/env.js +++ b/frontend/src/env.js @@ -29,6 +29,9 @@ export const env = createEnv({ ), DISCORD_CLIENT_ID: z.string(), DISCORD_CLIENT_SECRET: z.string(), + NEO4J_USERNAME: z.string(), + NEO4J_PASSWORD: z.string(), + NEO4J_URL: z.string(), }, /** @@ -51,6 +54,9 @@ export const env = createEnv({ NEXTAUTH_URL: process.env.NEXTAUTH_URL, DISCORD_CLIENT_ID: process.env.DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET: process.env.DISCORD_CLIENT_SECRET, + NEO4J_USERNAME: process.env.NEO4J_USERNAME, + NEO4J_PASSWORD: process.env.NEO4J_PASSWORD, + NEO4J_URL: process.env.NEO4J_URL, }, /** * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially diff --git a/frontend/src/server/api/routers/post.ts b/frontend/src/server/api/routers/post.ts index 3994691..a763b5e 100644 --- a/frontend/src/server/api/routers/post.ts +++ b/frontend/src/server/api/routers/post.ts @@ -1,4 +1,7 @@ +import { type Neo4JNodeData } from "@linkurious/ogma"; +import neo4j from "neo4j-driver"; import { z } from "zod"; +import { env } from "~/env"; import { createTRPCRouter, @@ -6,6 +9,11 @@ import { publicProcedure, } from "~/server/api/trpc"; +const driver = neo4j.driver( + env.NEO4J_URL, + neo4j.auth.basic(env.NEO4J_USERNAME, env.NEO4J_PASSWORD), +); + export const postRouter = createTRPCRouter({ hello: publicProcedure .input(z.object({ text: z.string() })) @@ -15,6 +23,21 @@ export const postRouter = createTRPCRouter({ }; }), + articles: publicProcedure.query(async () => { + const session = driver.session(); + try { + const result = await session.run( + "MATCH (t:Topic)-[r]-(a:Article) return t,r,a limit 100", + ); + return JSON.parse(JSON.stringify(result)) as Neo4JNodeData< + Record + >; + } finally { + await session.close(); + } + return null; + }), + create: protectedProcedure .input(z.object({ name: z.string().min(1) })) .mutation(async ({ ctx, input }) => { diff --git a/frontend/src/styles/globals.css b/frontend/src/styles/globals.css index fadc247..734a48b 100644 --- a/frontend/src/styles/globals.css +++ b/frontend/src/styles/globals.css @@ -2,8 +2,208 @@ @tailwind components; @tailwind utilities; -/* @media (min-width: 1900px) { +@media (min-width: 1900px) { .container { width: 1870px !important; } -} */ +} + +.ogma-tooltip, +.ogma-popup { + z-index: 401; + box-sizing: border-box; +} + +.ogma-tooltip--content, +.ogma-popup--body { + transform: translate(-50%, 0); + background-color: var(--overlay-background-color); + color: var(--overlay-text-color); + border-radius: 5px; + padding: 5px; + box-sizing: border-box; + box-shadow: 0 8px 30px rgb(0 0 0 / 12%); + width: auto; + height: auto; + position: relative; +} + +.ogma-tooltip { + /* transition: linear; + transition-property: transform; + transition-duration: 50ms; */ + pointer-events: none; +} + +.ogma-popup--body { + transform: translate(-50%, -100%); +} + +.ogma-tooltip--content:after, +.ogma-popup--body:after { + content: ""; + width: 0; + height: 0; + border-style: solid; + border-width: 6px 7px 6px 0; + border-color: transparent var(--overlay-background-color) transparent transparent; + position: absolute; + left: 50%; + top: auto; + bottom: 3px; + right: auto; + transform: translate(-50%, 100%) rotate(270deg); +} + +.ogma-popup--close { + position: absolute; + top: 0px; + right: 5px; + cursor: pointer; +} + +.ogma-popup--top .ogma-popup--body, +.ogma-tooltip--top .ogma-tooltip--content { + bottom: 6px; + transform: translate(-50%, -100%); +} + +.ogma-popup--bottom .ogma-popup--body, +.ogma-tooltip--bottom .ogma-tooltip--content { + transform: translate(-50%, 0%); + top: 3px; +} + +.ogma-popup--bottom .ogma-popup--body:after, +.ogma-tooltip--bottom .ogma-tooltip--content:after { + top: 3px; + bottom: auto; + transform: translate(-50%, -100%) rotate(90deg); +} + +.ogma-popup--right .ogma-popup--body, +.ogma-tooltip--right .ogma-tooltip--content { + transform: translate(0, -50%); + left: 6px; +} + +.ogma-popup--right .ogma-popup--body:after, +.ogma-tooltip--right .ogma-tooltip--content:after { + left: 0%; + top: 50%; + transform: translate(-100%, -50%) rotate(0deg); +} + +.ogma-popup--left .ogma-popup--body, +.ogma-tooltip--left .ogma-tooltip--content { + transform: translate(-100%, -50%); + right: 6px; +} + +.ogma-popup--left .ogma-popup--body:after, +.ogma-tooltip--left .ogma-tooltip--content:after { + right: 0%; + left: auto; + top: 50%; + transform: translate(100%, -50%) rotate(180deg); +} + +.ogma-popup--content { + padding: 10px; +} + +.control-buttons { + position: absolute; + z-index: 401; + right: 20px; + top: 20px; +} + +.ogmaprogressbar { + position: absolute; + top: 50%; + left: 50%; + width: 200px; + height: 200px; + margin-top: -100px; + margin-left: -100px; +} +.ogmaprogressbar--card { + position: relative; + background: rgba(255, 255, 255, 0.5); + border-radius: 10px; + padding: 10px; + text-align: center; + overflow: hidden; +} + +.ogmaprogressbar--box { + display: inline-block; +} + +.ogmaprogressbar--percent { + position: relative; + width: 150px; + height: 150px; + border-radius: 50%; + box-shadow: inset 0 0 50px #000; + background: #222; + z-index: 1000; +} + +.ogmaprogressbar--num { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + border-radius: 50%; +} +.ogmaprogressbar--num h2 { + color: #777; + font-weight: 700; + font-size: 30px; + transition: 0.5s; +} + +.ogmaprogressbar--num h2 span { + color: #777; + font-size: 24px; + transition: 0.5s; +} + +.ogmaprogressbar--text { + position: relative; + color: #777; + margin-top: 20px; + font-weight: 700; + font-size: 18px; + letter-spacing: 1px; + text-transform: uppercase; + transition: 0.5s; +} + +.ogmaprogressbar--indicator { + position: relative; + width: 150px; + height: 150px; + z-index: 1000; +} +.ogmaprogressbar--circle { + width: 100%; + height: 100%; + fill: none; + stroke: #191919; + stroke-width: 10; + stroke-linecap: round; + transform: translate(5px, 145px) rotate(-90deg); +} + +.ogmaprogressbar--progress { + stroke-dasharray: 440; + stroke-dashoffset: 440; + stroke: #ea5565; +} diff --git a/frontend/src/supply-chain-neo4j.json b/frontend/src/supply-chain-neo4j.json new file mode 100644 index 0000000..0f02f23 --- /dev/null +++ b/frontend/src/supply-chain-neo4j.json @@ -0,0 +1,5376 @@ +{ + "records": [ + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "14", + "labels": ["SupplierA"], + "properties": { + "cost": 33, + "co2": 254, + "name": "SupplierA0", + "lon": 92.70139901973018, + "time": 4, + "lat": 11.13204871524422 + } + }, + { + "identity": "69", + "start": "14", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 537, + "quantity": 289 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "14", + "labels": ["SupplierA"], + "properties": { + "cost": 33, + "co2": 254, + "name": "SupplierA0", + "lon": 92.70139901973018, + "time": 4, + "lat": 12.13204871524422 + } + }, + { + "identity": "60", + "start": "14", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 537, + "quantity": 205 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "14", + "labels": ["SupplierA"], + "properties": { + "cost": 33, + "co2": 254, + "name": "SupplierA0", + "lon": 92.70139901973018, + "time": 4, + "lat": 55.13204871524422 + } + }, + { + "identity": "45", + "start": "14", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 537, + "quantity": 281 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "18", + "labels": ["SupplierB"], + "properties": { + "cost": 26, + "co2": 255, + "name": "SupplierB1", + "lon": 6.338227685970327, + "time": 5, + "lat": 59.06009250248424 + } + }, + { + "identity": "73", + "start": "18", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 3143, + "quantity": 188 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "52", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 160 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "58", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 122 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "1", + "labels": ["Wholesaler"], + "properties": { + "cost": 25, + "co2": 454, + "name": "Wholesaler0", + "lon": 21.323179132582617, + "time": 2, + "stock": 1028, + "lat": 53.493312000154994 + } + }, + { + "identity": "34", + "start": "0", + "end": "1", + "type": "DELIVER", + "properties": { + "km": 3737, + "quantity": 175 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "1", + "labels": ["Wholesaler"], + "properties": { + "cost": 25, + "co2": 454, + "name": "Wholesaler0", + "lon": 21.323179132582617, + "time": 2, + "stock": 1028, + "lat": 53.493312000154994 + } + }, + { + "identity": "37", + "start": "0", + "end": "1", + "type": "DELIVER", + "properties": { + "km": 3737, + "quantity": 146 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "14", + "labels": ["SupplierA"], + "properties": { + "cost": 33, + "co2": 254, + "name": "SupplierA0", + "lon": 92.70139901973018, + "time": 4, + "lat": 33.13204871524422 + } + }, + { + "identity": "54", + "start": "14", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 537, + "quantity": 185 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "70", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 95 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "64", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 81 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "17", + "labels": ["SupplierB"], + "properties": { + "cost": 22, + "co2": 503, + "name": "SupplierB0", + "lon": 25.072991281086093, + "time": 4, + "lat": 14.017349778915698 + } + }, + { + "identity": "72", + "start": "17", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 8056, + "quantity": 165 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "15", + "labels": ["SupplierA"], + "properties": { + "cost": 30, + "co2": 1251, + "name": "SupplierA1", + "lon": 86.52601778754027, + "time": 0, + "lat": 27.895522473272333 + } + }, + { + "identity": "33", + "start": "15", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 6619, + "quantity": 212 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "67", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 220 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "61", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 222 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "16", + "labels": ["SupplierA"], + "properties": { + "cost": 24, + "co2": 1339, + "name": "SupplierA2", + "lon": 82.77858106851605, + "time": 3, + "lat": 47.17026780787867 + } + }, + { + "identity": "36", + "start": "16", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 4385, + "quantity": 201 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "14", + "labels": ["SupplierA"], + "properties": { + "cost": 33, + "co2": 254, + "name": "SupplierA0", + "lon": 92.70139901973018, + "time": 4, + "lat": 22.13204871524422 + } + }, + { + "identity": "66", + "start": "14", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 537, + "quantity": 211 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "14", + "labels": ["SupplierA"], + "properties": { + "cost": 33, + "co2": 254, + "name": "SupplierA0", + "lon": 92.70139901973018, + "time": 4, + "lat": 22.13204871524422 + } + }, + { + "identity": "42", + "start": "14", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 537, + "quantity": 287 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "49", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 79 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.1406389456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "55", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 112 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "14", + "labels": ["SupplierA"], + "properties": { + "cost": 33, + "co2": 254, + "name": "SupplierA0", + "lon": 92.70139901973018, + "time": 4, + "lat": 51.13204871524422 + } + }, + { + "identity": "48", + "start": "14", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 537, + "quantity": 63 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "46", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 130 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "40", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 93 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "43", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 264 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "1", + "labels": ["Wholesaler"], + "properties": { + "cost": 25, + "co2": 454, + "name": "Wholesaler0", + "lon": 21.323179132582617, + "time": 2, + "stock": 1028, + "lat": 53.493312000154994 + } + }, + { + "identity": "31", + "start": "0", + "end": "1", + "type": "DELIVER", + "properties": { + "km": 3737, + "quantity": 178 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "1", + "labels": ["Wholesaler"], + "properties": { + "cost": 25, + "co2": 454, + "name": "Wholesaler0", + "lon": 21.323179132582617, + "time": 2, + "stock": 1028, + "lat": 53.493312000154994 + } + }, + { + "identity": "28", + "start": "0", + "end": "1", + "type": "DELIVER", + "properties": { + "km": 3737, + "quantity": 46 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "1", + "labels": ["Wholesaler"], + "properties": { + "cost": 25, + "co2": 454, + "name": "Wholesaler0", + "lon": 21.323179132582617, + "time": 2, + "stock": 1028, + "lat": 53.493312000154994 + } + }, + { + "identity": "25", + "start": "0", + "end": "1", + "type": "DELIVER", + "properties": { + "km": 3737, + "quantity": 138 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "1", + "labels": ["Wholesaler"], + "properties": { + "cost": 25, + "co2": 454, + "name": "Wholesaler0", + "lon": 21.323179132582617, + "time": 2, + "stock": 1028, + "lat": 53.493312000154994 + } + }, + { + "identity": "22", + "start": "0", + "end": "1", + "type": "DELIVER", + "properties": { + "km": 3737, + "quantity": 256 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "34", + "start": "0", + "end": "1", + "type": "DELIVER", + "properties": { + "km": 3737, + "quantity": 175 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "37", + "start": "0", + "end": "1", + "type": "DELIVER", + "properties": { + "km": 3737, + "quantity": 146 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "3", + "labels": ["Retailer"], + "properties": { + "cost": 36, + "co2": 287, + "name": "Retailer0", + "lon": 120.3350059243061, + "time": "1", + "stock": 537, + "lat": 61.433685818190334 + } + }, + { + "identity": "35", + "start": "1", + "end": "3", + "type": "DELIVER", + "properties": { + "km": 5405, + "quantity": 98 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "13", + "labels": ["Retailer"], + "properties": { + "cost": 23, + "co2": 313, + "name": "Retailer10", + "lon": 144.48225548698983, + "time": "1", + "stock": 345, + "lat": 16.946629825629103 + } + }, + { + "identity": "32", + "start": "1", + "end": "13", + "type": "DELIVER", + "properties": { + "km": 10529, + "quantity": 197 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "3", + "labels": ["Retailer"], + "properties": { + "cost": 36, + "co2": 287, + "name": "Retailer0", + "lon": 120.3350059243061, + "time": "1", + "stock": 537, + "lat": 61.433685818190334 + } + }, + { + "identity": "38", + "start": "1", + "end": "3", + "type": "DELIVER", + "properties": { + "km": 5405, + "quantity": 295 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "31", + "start": "0", + "end": "1", + "type": "DELIVER", + "properties": { + "km": 3737, + "quantity": 178 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "12", + "labels": ["Retailer"], + "properties": { + "cost": 38, + "co2": 265, + "name": "Retailer9", + "lon": 16.725796442007002, + "time": "1", + "stock": 248, + "lat": 18.631603499868035 + } + }, + { + "identity": "29", + "start": "1", + "end": "12", + "type": "DELIVER", + "properties": { + "km": 3912, + "quantity": 112 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "28", + "start": "0", + "end": "1", + "type": "DELIVER", + "properties": { + "km": 3737, + "quantity": 46 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "11", + "labels": ["Retailer"], + "properties": { + "cost": 25, + "co2": 2093, + "name": "Retailer8", + "lon": 8.69147208910817, + "time": "1", + "stock": 1009, + "lat": 45.023413967516916 + } + }, + { + "identity": "26", + "start": "1", + "end": "11", + "type": "DELIVER", + "properties": { + "km": 1273, + "quantity": 66 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "25", + "start": "0", + "end": "1", + "type": "DELIVER", + "properties": { + "km": 3737, + "quantity": 138 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "10", + "labels": ["Retailer"], + "properties": { + "cost": 24, + "co2": 256, + "name": "Retailer7", + "lon": 34.365343995377025, + "time": "1", + "stock": 192, + "lat": 36.68179327242285 + } + }, + { + "identity": "23", + "start": "1", + "end": "10", + "type": "DELIVER", + "properties": { + "km": 3680, + "quantity": 69 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "22", + "start": "0", + "end": "1", + "type": "DELIVER", + "properties": { + "km": 3737, + "quantity": 256 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "9", + "labels": ["Retailer"], + "properties": { + "cost": 22, + "co2": 469, + "name": "Retailer6", + "lon": 54.99018630047464, + "time": "1", + "stock": 885, + "lat": 35.99284008687277 + } + }, + { + "identity": "20", + "start": "1", + "end": "9", + "type": "DELIVER", + "properties": { + "km": 8653, + "quantity": 234 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "8", + "labels": ["Retailer"], + "properties": { + "cost": 32, + "co2": 618, + "name": "Retailer5", + "lon": 0.8503195683839501, + "time": "1", + "stock": 429, + "lat": 41.53999730092966 + } + }, + { + "identity": "17", + "start": "1", + "end": "8", + "type": "DELIVER", + "properties": { + "km": 2023, + "quantity": 250 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "7", + "labels": ["Retailer"], + "properties": { + "cost": 23, + "co2": 2879, + "name": "Retailer4", + "lon": 101.71903330577375, + "time": "1", + "stock": 266, + "lat": 14.142633349703582 + } + }, + { + "identity": "14", + "start": "1", + "end": "7", + "type": "DELIVER", + "properties": { + "km": 8090, + "quantity": 74 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "6", + "labels": ["Retailer"], + "properties": { + "cost": 29, + "co2": 507, + "name": "Retailer3", + "lon": 103.33586459345403, + "time": "1", + "stock": 980, + "lat": 22.2197472039159 + } + }, + { + "identity": "11", + "start": "1", + "end": "6", + "type": "DELIVER", + "properties": { + "km": 4132, + "quantity": 67 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "5", + "labels": ["Retailer"], + "properties": { + "cost": 35, + "co2": 300, + "name": "Retailer2", + "lon": 23.24720461522803, + "time": "1", + "stock": 375, + "lat": 0.6469642657212675 + } + }, + { + "identity": "8", + "start": "1", + "end": "5", + "type": "DELIVER", + "properties": { + "km": 5896, + "quantity": 172 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "4", + "labels": ["Retailer"], + "properties": { + "cost": 22, + "co2": 299, + "name": "Retailer1", + "lon": 69.21941781190561, + "time": "1", + "stock": 620, + "lat": 53.27157446084193 + } + }, + { + "identity": "5", + "start": "1", + "end": "4", + "type": "DELIVER", + "properties": { + "km": 3116, + "quantity": 308 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "3", + "labels": ["Retailer"], + "properties": { + "cost": 36, + "co2": 287, + "name": "Retailer0", + "lon": 120.3350059243061, + "time": "1", + "stock": 537, + "lat": 61.433685818190334 + } + }, + { + "identity": "2", + "start": "1", + "end": "3", + "type": "DELIVER", + "properties": { + "km": 5405, + "quantity": 208 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "6", + "labels": ["Retailer"], + "properties": { + "cost": 29, + "co2": 507, + "name": "Retailer3", + "lon": 103.33586459345403, + "time": "1", + "stock": 980, + "lat": 32.2197472039159 + } + }, + { + "identity": "50", + "start": "2", + "end": "6", + "type": "DELIVER", + "properties": { + "km": 6342, + "quantity": 20 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "11", + "labels": ["Retailer"], + "properties": { + "cost": 25, + "co2": 2093, + "name": "Retailer8", + "lon": 8.69147208910817, + "time": "1", + "stock": 1009, + "lat": 45.023413967516916 + } + }, + { + "identity": "65", + "start": "2", + "end": "11", + "type": "DELIVER", + "properties": { + "km": 10651, + "quantity": 168 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "52", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 160 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "58", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 122 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "70", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 95 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "12", + "labels": ["Retailer"], + "properties": { + "cost": 38, + "co2": 265, + "name": "Retailer9", + "lon": 16.725796442007002, + "time": "1", + "stock": 248, + "lat": 18.631603499868035 + } + }, + { + "identity": "68", + "start": "2", + "end": "12", + "type": "DELIVER", + "properties": { + "km": 13632, + "quantity": 239 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "64", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 81 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "10", + "labels": ["Retailer"], + "properties": { + "cost": 24, + "co2": 256, + "name": "Retailer7", + "lon": 34.365343995377025, + "time": "1", + "stock": 192, + "lat": 36.68179327242285 + } + }, + { + "identity": "62", + "start": "2", + "end": "10", + "type": "DELIVER", + "properties": { + "km": 6887, + "quantity": 299 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "7", + "labels": ["Retailer"], + "properties": { + "cost": 23, + "co2": 2879, + "name": "Retailer4", + "lon": 101.71903330577375, + "time": "1", + "stock": 266, + "lat": 14.142633349703582 + } + }, + { + "identity": "53", + "start": "2", + "end": "7", + "type": "DELIVER", + "properties": { + "km": 12188, + "quantity": 171 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "67", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 220 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "61", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 222 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "4", + "labels": ["Retailer"], + "properties": { + "cost": 22, + "co2": 299, + "name": "Retailer1", + "lon": 69.21941781190561, + "time": "1", + "stock": 620, + "lat": 53.27157446084193 + } + }, + { + "identity": "44", + "start": "2", + "end": "4", + "type": "DELIVER", + "properties": { + "km": 10303, + "quantity": 233 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "5", + "labels": ["Retailer"], + "properties": { + "cost": 35, + "co2": 300, + "name": "Retailer2", + "lon": 23.24720461522803, + "time": "1", + "stock": 375, + "lat": 0.6469642657212675 + } + }, + { + "identity": "47", + "start": "2", + "end": "5", + "type": "DELIVER", + "properties": { + "km": 15717, + "quantity": 43 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "9", + "labels": ["Retailer"], + "properties": { + "cost": 22, + "co2": 469, + "name": "Retailer6", + "lon": 54.99018630047464, + "time": "1", + "stock": 885, + "lat": 35.99284008687277 + } + }, + { + "identity": "59", + "start": "2", + "end": "9", + "type": "DELIVER", + "properties": { + "km": 1639, + "quantity": 80 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "49", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 79 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "3", + "labels": ["Retailer"], + "properties": { + "cost": 36, + "co2": 287, + "name": "Retailer0", + "lon": 120.3350059243061, + "time": "1", + "stock": 537, + "lat": 61.433685818190334 + } + }, + { + "identity": "41", + "start": "2", + "end": "3", + "type": "DELIVER", + "properties": { + "km": 7738, + "quantity": 62 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "55", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 112 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "13", + "labels": ["Retailer"], + "properties": { + "cost": 23, + "co2": 313, + "name": "Retailer10", + "lon": 144.48225548698983, + "time": "1", + "stock": 345, + "lat": 16.946629825629103 + } + }, + { + "identity": "71", + "start": "2", + "end": "13", + "type": "DELIVER", + "properties": { + "km": 8306, + "quantity": 309 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "46", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 130 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "8", + "labels": ["Retailer"], + "properties": { + "cost": 32, + "co2": 618, + "name": "Retailer5", + "lon": 0.8503195683839501, + "time": "1", + "stock": 429, + "lat": 41.53999730092966 + } + }, + { + "identity": "56", + "start": "2", + "end": "8", + "type": "DELIVER", + "properties": { + "km": 10667, + "quantity": 280 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "40", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 93 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "43", + "start": "0", + "end": "2", + "type": "DELIVER", + "properties": { + "km": 6890, + "quantity": 264 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "1", + "labels": ["Wholesaler"], + "properties": { + "cost": 25, + "co2": 454, + "name": "Wholesaler0", + "lon": 21.323179132582617, + "time": 2, + "stock": 1028, + "lat": 53.493312000154994 + } + }, + { + "identity": "35", + "start": "1", + "end": "3", + "type": "DELIVER", + "properties": { + "km": 5405, + "quantity": 98 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "41", + "start": "2", + "end": "3", + "type": "DELIVER", + "properties": { + "km": 7738, + "quantity": 62 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "1", + "labels": ["Wholesaler"], + "properties": { + "cost": 25, + "co2": 454, + "name": "Wholesaler0", + "lon": 21.323179132582617, + "time": 2, + "stock": 1028, + "lat": 53.493312000154994 + } + }, + { + "identity": "38", + "start": "1", + "end": "3", + "type": "DELIVER", + "properties": { + "km": 5405, + "quantity": 295 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "1", + "labels": ["Wholesaler"], + "properties": { + "cost": 25, + "co2": 454, + "name": "Wholesaler0", + "lon": 21.323179132582617, + "time": 2, + "stock": 1028, + "lat": 53.493312000154994 + } + }, + { + "identity": "2", + "start": "1", + "end": "3", + "type": "DELIVER", + "properties": { + "km": 5405, + "quantity": 208 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "44", + "start": "2", + "end": "4", + "type": "DELIVER", + "properties": { + "km": 10303, + "quantity": 233 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "1", + "labels": ["Wholesaler"], + "properties": { + "cost": 25, + "co2": 454, + "name": "Wholesaler0", + "lon": 21.323179132582617, + "time": 2, + "stock": 1028, + "lat": 53.493312000154994 + } + }, + { + "identity": "5", + "start": "1", + "end": "4", + "type": "DELIVER", + "properties": { + "km": 3116, + "quantity": 308 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "47", + "start": "2", + "end": "5", + "type": "DELIVER", + "properties": { + "km": 15717, + "quantity": 43 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "1", + "labels": ["Wholesaler"], + "properties": { + "cost": 25, + "co2": 454, + "name": "Wholesaler0", + "lon": 21.323179132582617, + "time": 2, + "stock": 1028, + "lat": 53.493312000154994 + } + }, + { + "identity": "8", + "start": "1", + "end": "5", + "type": "DELIVER", + "properties": { + "km": 5896, + "quantity": 172 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "50", + "start": "2", + "end": "6", + "type": "DELIVER", + "properties": { + "km": 6342, + "quantity": 20 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "1", + "labels": ["Wholesaler"], + "properties": { + "cost": 25, + "co2": 454, + "name": "Wholesaler0", + "lon": 21.323179132582617, + "time": 2, + "stock": 1028, + "lat": 53.493312000154994 + } + }, + { + "identity": "11", + "start": "1", + "end": "6", + "type": "DELIVER", + "properties": { + "km": 4132, + "quantity": 67 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "53", + "start": "2", + "end": "7", + "type": "DELIVER", + "properties": { + "km": 12188, + "quantity": 171 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "1", + "labels": ["Wholesaler"], + "properties": { + "cost": 25, + "co2": 454, + "name": "Wholesaler0", + "lon": 21.323179132582617, + "time": 2, + "stock": 1028, + "lat": 53.493312000154994 + } + }, + { + "identity": "14", + "start": "1", + "end": "7", + "type": "DELIVER", + "properties": { + "km": 8090, + "quantity": 74 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "56", + "start": "2", + "end": "8", + "type": "DELIVER", + "properties": { + "km": 10667, + "quantity": 280 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "1", + "labels": ["Wholesaler"], + "properties": { + "cost": 25, + "co2": 454, + "name": "Wholesaler0", + "lon": 21.323179132582617, + "time": 2, + "stock": 1028, + "lat": 53.493312000154994 + } + }, + { + "identity": "17", + "start": "1", + "end": "8", + "type": "DELIVER", + "properties": { + "km": 2023, + "quantity": 250 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "59", + "start": "2", + "end": "9", + "type": "DELIVER", + "properties": { + "km": 1639, + "quantity": 80 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "1", + "labels": ["Wholesaler"], + "properties": { + "cost": 25, + "co2": 454, + "name": "Wholesaler0", + "lon": 21.323179132582617, + "time": 2, + "stock": 1028, + "lat": 53.493312000154994 + } + }, + { + "identity": "20", + "start": "1", + "end": "9", + "type": "DELIVER", + "properties": { + "km": 8653, + "quantity": 234 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "62", + "start": "2", + "end": "10", + "type": "DELIVER", + "properties": { + "km": 6887, + "quantity": 299 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "1", + "labels": ["Wholesaler"], + "properties": { + "cost": 25, + "co2": 454, + "name": "Wholesaler0", + "lon": 21.323179132582617, + "time": 2, + "stock": 1028, + "lat": 53.493312000154994 + } + }, + { + "identity": "23", + "start": "1", + "end": "10", + "type": "DELIVER", + "properties": { + "km": 3680, + "quantity": 69 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "65", + "start": "2", + "end": "11", + "type": "DELIVER", + "properties": { + "km": 10651, + "quantity": 168 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "1", + "labels": ["Wholesaler"], + "properties": { + "cost": 25, + "co2": 454, + "name": "Wholesaler0", + "lon": 21.323179132582617, + "time": 2, + "stock": 1028, + "lat": 53.493312000154994 + } + }, + { + "identity": "26", + "start": "1", + "end": "11", + "type": "DELIVER", + "properties": { + "km": 1273, + "quantity": 66 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "68", + "start": "2", + "end": "12", + "type": "DELIVER", + "properties": { + "km": 13632, + "quantity": 239 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "1", + "labels": ["Wholesaler"], + "properties": { + "cost": 25, + "co2": 454, + "name": "Wholesaler0", + "lon": 21.323179132582617, + "time": 2, + "stock": 1028, + "lat": 53.493312000154994 + } + }, + { + "identity": "29", + "start": "1", + "end": "12", + "type": "DELIVER", + "properties": { + "km": 3912, + "quantity": 112 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "1", + "labels": ["Wholesaler"], + "properties": { + "cost": 25, + "co2": 454, + "name": "Wholesaler0", + "lon": 21.323179132582617, + "time": 2, + "stock": 1028, + "lat": 53.493312000154994 + } + }, + { + "identity": "32", + "start": "1", + "end": "13", + "type": "DELIVER", + "properties": { + "km": 10529, + "quantity": 197 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "2", + "labels": ["Wholesaler"], + "properties": { + "cost": 21, + "co2": 353, + "name": "Wholesaler1", + "lon": 46.140638991456186, + "time": 3, + "stock": "3000", + "lat": 48.415497461193 + } + }, + { + "identity": "71", + "start": "2", + "end": "13", + "type": "DELIVER", + "properties": { + "km": 8306, + "quantity": 309 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "23", + "labels": ["RawSupplierA"], + "properties": { + "cost": 35, + "co2": 310, + "name": "RawSupplierA4", + "lon": 138.9329147762149, + "time": 1, + "lat": 38.0983887083636 + } + }, + { + "identity": "78", + "start": "23", + "end": "14", + "type": "DELIVER", + "properties": { + "km": 5266, + "quantity": 261 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "69", + "start": "14", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 537, + "quantity": 289 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "60", + "start": "14", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 537, + "quantity": 205 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "45", + "start": "14", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 537, + "quantity": 281 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "54", + "start": "14", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 537, + "quantity": 185 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "19", + "labels": ["RawSupplierA"], + "properties": { + "cost": 39, + "co2": 363, + "name": "RawSupplierA0", + "lon": 62.268410580129554, + "time": 2, + "lat": 7.0069988838318 + } + }, + { + "identity": "74", + "start": "19", + "end": "14", + "type": "DELIVER", + "properties": { + "km": 9323, + "quantity": 15 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "22", + "labels": ["RawSupplierA"], + "properties": { + "cost": 27, + "co2": 260, + "name": "RawSupplierA3", + "lon": 119.32972337699064, + "time": 5, + "lat": 21.149583347120085 + } + }, + { + "identity": "77", + "start": "22", + "end": "14", + "type": "DELIVER", + "properties": { + "km": 7770, + "quantity": 169 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "66", + "start": "14", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 537, + "quantity": 211 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "20", + "labels": ["RawSupplierA"], + "properties": { + "cost": 22, + "co2": 2878, + "name": "RawSupplierA1", + "lon": 20.102761906460703, + "time": 3, + "lat": 5.427426441547278 + } + }, + { + "identity": "75", + "start": "20", + "end": "14", + "type": "DELIVER", + "properties": { + "km": 9475, + "quantity": 175 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "42", + "start": "14", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 537, + "quantity": 287 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "48", + "start": "14", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 537, + "quantity": 63 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "21", + "labels": ["RawSupplierA"], + "properties": { + "cost": 22, + "co2": 251, + "name": "RawSupplierA2", + "lon": 93.7629078718814, + "time": 4, + "lat": 32.0005263954051 + } + }, + { + "identity": "76", + "start": "21", + "end": "14", + "type": "DELIVER", + "properties": { + "km": 111, + "quantity": 18 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "24", + "labels": ["RawSupplierA"], + "properties": { + "cost": 23, + "co2": 252, + "name": "RawSupplierA5", + "lon": 4.069407789863825, + "time": 3, + "lat": 21.289651228239002 + } + }, + { + "identity": "79", + "start": "24", + "end": "14", + "type": "DELIVER", + "properties": { + "km": 7661, + "quantity": 302 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "24", + "labels": ["RawSupplierA"], + "properties": { + "cost": 23, + "co2": 252, + "name": "RawSupplierA5", + "lon": 4.069407789863825, + "time": 3, + "lat": 21.289651228239002 + } + }, + { + "identity": "85", + "start": "24", + "end": "15", + "type": "DELIVER", + "properties": { + "km": 8205, + "quantity": 95 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "23", + "labels": ["RawSupplierA"], + "properties": { + "cost": 35, + "co2": 310, + "name": "RawSupplierA4", + "lon": 138.9329147762149, + "time": 1, + "lat": 138.0983887083636 + } + }, + { + "identity": "84", + "start": "23", + "end": "15", + "type": "DELIVER", + "properties": { + "km": 10658, + "quantity": 155 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "22", + "labels": ["RawSupplierA"], + "properties": { + "cost": 27, + "co2": 260, + "name": "RawSupplierA3", + "lon": 119.32972337699064, + "time": 5, + "lat": 21.149583347120085 + } + }, + { + "identity": "83", + "start": "22", + "end": "15", + "type": "DELIVER", + "properties": { + "km": 3373, + "quantity": 76 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "19", + "labels": ["RawSupplierA"], + "properties": { + "cost": 39, + "co2": 363, + "name": "RawSupplierA0", + "lon": 62.268410580129554, + "time": 2, + "lat": 7.0069988838318 + } + }, + { + "identity": "80", + "start": "19", + "end": "15", + "type": "DELIVER", + "properties": { + "km": 3384, + "quantity": 64 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "33", + "start": "15", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 6619, + "quantity": 212 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "20", + "labels": ["RawSupplierA"], + "properties": { + "cost": 22, + "co2": 2878, + "name": "RawSupplierA1", + "lon": 20.102761906460703, + "time": 3, + "lat": 5.427426441547278 + } + }, + { + "identity": "81", + "start": "20", + "end": "15", + "type": "DELIVER", + "properties": { + "km": 7391, + "quantity": 107 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "21", + "labels": ["RawSupplierA"], + "properties": { + "cost": 22, + "co2": 251, + "name": "RawSupplierA2", + "lon": 93.7629078718814, + "time": 4, + "lat": 32.0005263954051 + } + }, + { + "identity": "82", + "start": "21", + "end": "15", + "type": "DELIVER", + "properties": { + "km": 7226, + "quantity": 208 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "23", + "labels": ["RawSupplierA"], + "properties": { + "cost": 35, + "co2": 310, + "name": "RawSupplierA4", + "lon": 138.9329147762149, + "time": 1, + "lat": 138.0983887083636 + } + }, + { + "identity": "90", + "start": "23", + "end": "16", + "type": "DELIVER", + "properties": { + "km": 8681, + "quantity": 157 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "22", + "labels": ["RawSupplierA"], + "properties": { + "cost": 27, + "co2": 260, + "name": "RawSupplierA3", + "lon": 119.32972337699064, + "time": 5, + "lat": 21.149583347120085 + } + }, + { + "identity": "89", + "start": "22", + "end": "16", + "type": "DELIVER", + "properties": { + "km": 4367, + "quantity": 251 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "36", + "start": "16", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 4385, + "quantity": 201 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "21", + "labels": ["RawSupplierA"], + "properties": { + "cost": 22, + "co2": 251, + "name": "RawSupplierA2", + "lon": 93.7629078718814, + "time": 4, + "lat": 32.0005263954051 + } + }, + { + "identity": "88", + "start": "21", + "end": "16", + "type": "DELIVER", + "properties": { + "km": 5000, + "quantity": 25 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "20", + "labels": ["RawSupplierA"], + "properties": { + "cost": 22, + "co2": 2878, + "name": "RawSupplierA1", + "lon": 20.102761906460703, + "time": 3, + "lat": 5.427426441547278 + } + }, + { + "identity": "87", + "start": "20", + "end": "16", + "type": "DELIVER", + "properties": { + "km": 7545, + "quantity": 173 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "19", + "labels": ["RawSupplierA"], + "properties": { + "cost": 39, + "co2": 363, + "name": "RawSupplierA0", + "lon": 62.268410580129554, + "time": 2, + "lat": 7.0069988838318 + } + }, + { + "identity": "86", + "start": "19", + "end": "16", + "type": "DELIVER", + "properties": { + "km": 4856, + "quantity": 294 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "24", + "labels": ["RawSupplierA"], + "properties": { + "cost": 23, + "co2": 252, + "name": "RawSupplierA5", + "lon": 4.069407789863825, + "time": 3, + "lat": 21.289651228239002 + } + }, + { + "identity": "91", + "start": "24", + "end": "16", + "type": "DELIVER", + "properties": { + "km": 7459, + "quantity": 76 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "27", + "labels": ["RawSupplierB"], + "properties": { + "cost": 34, + "co2": 254, + "name": "RawSuppplierB2", + "lon": 79.57252591432555, + "time": 3, + "lat": 47.6145843929958 + } + }, + { + "identity": "94", + "start": "27", + "end": "17", + "type": "DELIVER", + "properties": { + "km": 6187, + "quantity": 109 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "26", + "labels": ["RawSupplierB"], + "properties": { + "cost": 32, + "co2": 257, + "name": "RawSuppplierB1", + "lon": 58.38519853750158, + "time": 4, + "lat": 47.2955996520498 + } + }, + { + "identity": "93", + "start": "26", + "end": "17", + "type": "DELIVER", + "properties": { + "km": 13701, + "quantity": 242 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "30", + "labels": ["RawSupplierB"], + "properties": { + "cost": 23, + "co2": 269, + "name": "RawSuppplierB5", + "lon": 37.56651595879072, + "time": 3, + "lat": 41.9299390619598 + } + }, + { + "identity": "97", + "start": "30", + "end": "17", + "type": "DELIVER", + "properties": { + "km": 7472, + "quantity": 273 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "72", + "start": "17", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 8056, + "quantity": 165 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "25", + "labels": ["RawSupplierB"], + "properties": { + "cost": 29, + "co2": 284, + "name": "RawSuppplierB0", + "lon": 9.610399399009628, + "time": 3, + "lat": 22.041859980700057 + } + }, + { + "identity": "92", + "start": "25", + "end": "17", + "type": "DELIVER", + "properties": { + "km": 1859, + "quantity": 220 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "28", + "labels": ["RawSupplierB"], + "properties": { + "cost": 23, + "co2": 271, + "name": "RawSuppplierB3", + "lon": 14.130579474346439, + "time": 5, + "lat": 21.101411735930455 + } + }, + { + "identity": "95", + "start": "28", + "end": "17", + "type": "DELIVER", + "properties": { + "km": 1396, + "quantity": 286 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "29", + "labels": ["RawSupplierB"], + "properties": { + "cost": 25, + "co2": 263, + "name": "RawSuppplierB4", + "lon": 68.08419980419322, + "time": 2, + "lat": 17.41999876663947 + } + }, + { + "identity": "96", + "start": "29", + "end": "17", + "type": "DELIVER", + "properties": { + "km": 10677, + "quantity": 108 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "0", + "labels": ["Product"], + "properties": { + "cost": "100", + "co2": "200", + "name": "Product", + "lon": 54.66461174410734, + "time": "0", + "lat": 36.5889069519533 + } + }, + { + "identity": "73", + "start": "18", + "end": "0", + "type": "DELIVER", + "properties": { + "km": 3143, + "quantity": 188 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "28", + "labels": ["RawSupplierB"], + "properties": { + "cost": 23, + "co2": 271, + "name": "RawSuppplierB3", + "lon": 14.130579474346439, + "time": 5, + "lat": 21.101411735930455 + } + }, + { + "identity": "101", + "start": "28", + "end": "18", + "type": "DELIVER", + "properties": { + "km": 4271, + "quantity": 86 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "25", + "labels": ["RawSupplierB"], + "properties": { + "cost": 29, + "co2": 284, + "name": "RawSuppplierB0", + "lon": 9.610399399009628, + "time": 3, + "lat": 22.041859980700057 + } + }, + { + "identity": "98", + "start": "25", + "end": "18", + "type": "DELIVER", + "properties": { + "km": 4122, + "quantity": 254 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "27", + "labels": ["RawSupplierB"], + "properties": { + "cost": 34, + "co2": 254, + "name": "RawSuppplierB2", + "lon": 79.57252591432555, + "time": 3, + "lat": 47.6145843929958 + } + }, + { + "identity": "100", + "start": "27", + "end": "18", + "type": "DELIVER", + "properties": { + "km": 4780, + "quantity": 123 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "30", + "labels": ["RawSupplierB"], + "properties": { + "cost": 23, + "co2": 269, + "name": "RawSuppplierB5", + "lon": 37.56651595879072, + "time": 3, + "lat": 41.9299390619598 + } + }, + { + "identity": "103", + "start": "30", + "end": "18", + "type": "DELIVER", + "properties": { + "km": 2618, + "quantity": 94 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "29", + "labels": ["RawSupplierB"], + "properties": { + "cost": 25, + "co2": 263, + "name": "RawSuppplierB4", + "lon": 68.08419980419322, + "time": 2, + "lat": 117.41999876663947 + } + }, + { + "identity": "102", + "start": "29", + "end": "18", + "type": "DELIVER", + "properties": { + "km": 5461, + "quantity": 199 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "26", + "labels": ["RawSupplierB"], + "properties": { + "cost": 32, + "co2": 257, + "name": "RawSuppplierB1", + "lon": 58.38519853750158, + "time": 4, + "lat": 147.2955996520498 + } + }, + { + "identity": "99", + "start": "26", + "end": "18", + "type": "DELIVER", + "properties": { + "km": 8715, + "quantity": 276 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "14", + "labels": ["SupplierA"], + "properties": { + "cost": 33, + "co2": 254, + "name": "SupplierA0", + "lon": 92.70139901973018, + "time": 4, + "lat": 51.13204871524422 + } + }, + { + "identity": "74", + "start": "19", + "end": "14", + "type": "DELIVER", + "properties": { + "km": 9323, + "quantity": 15 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "15", + "labels": ["SupplierA"], + "properties": { + "cost": 30, + "co2": 1251, + "name": "SupplierA1", + "lon": 86.52601778754027, + "time": 0, + "lat": 27.895522473272333 + } + }, + { + "identity": "80", + "start": "19", + "end": "15", + "type": "DELIVER", + "properties": { + "km": 3384, + "quantity": 64 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "16", + "labels": ["SupplierA"], + "properties": { + "cost": 24, + "co2": 1339, + "name": "SupplierA2", + "lon": 82.77858106851605, + "time": 3, + "lat": 47.17026780787867 + } + }, + { + "identity": "86", + "start": "19", + "end": "16", + "type": "DELIVER", + "properties": { + "km": 4856, + "quantity": 294 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "14", + "labels": ["SupplierA"], + "properties": { + "cost": 33, + "co2": 254, + "name": "SupplierA0", + "lon": 92.70139901973018, + "time": 4, + "lat": 51.13204871524422 + } + }, + { + "identity": "75", + "start": "20", + "end": "14", + "type": "DELIVER", + "properties": { + "km": 9475, + "quantity": 175 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "15", + "labels": ["SupplierA"], + "properties": { + "cost": 30, + "co2": 1251, + "name": "SupplierA1", + "lon": 86.52601778754027, + "time": 0, + "lat": 27.895522473272333 + } + }, + { + "identity": "81", + "start": "20", + "end": "15", + "type": "DELIVER", + "properties": { + "km": 7391, + "quantity": 107 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "16", + "labels": ["SupplierA"], + "properties": { + "cost": 24, + "co2": 1339, + "name": "SupplierA2", + "lon": 82.77858106851605, + "time": 3, + "lat": 47.17026780787867 + } + }, + { + "identity": "87", + "start": "20", + "end": "16", + "type": "DELIVER", + "properties": { + "km": 7545, + "quantity": 173 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "16", + "labels": ["SupplierA"], + "properties": { + "cost": 24, + "co2": 1339, + "name": "SupplierA2", + "lon": 82.77858106851605, + "time": 3, + "lat": 47.17026780787867 + } + }, + { + "identity": "88", + "start": "21", + "end": "16", + "type": "DELIVER", + "properties": { + "km": 5000, + "quantity": 25 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "14", + "labels": ["SupplierA"], + "properties": { + "cost": 33, + "co2": 254, + "name": "SupplierA0", + "lon": 92.70139901973018, + "time": 4, + "lat": 51.13204871524422 + } + }, + { + "identity": "76", + "start": "21", + "end": "14", + "type": "DELIVER", + "properties": { + "km": 111, + "quantity": 18 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "15", + "labels": ["SupplierA"], + "properties": { + "cost": 30, + "co2": 1251, + "name": "SupplierA1", + "lon": 86.52601778754027, + "time": 0, + "lat": 27.895522473272333 + } + }, + { + "identity": "82", + "start": "21", + "end": "15", + "type": "DELIVER", + "properties": { + "km": 7226, + "quantity": 208 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "15", + "labels": ["SupplierA"], + "properties": { + "cost": 30, + "co2": 1251, + "name": "SupplierA1", + "lon": 86.52601778754027, + "time": 0, + "lat": 27.895522473272333 + } + }, + { + "identity": "83", + "start": "22", + "end": "15", + "type": "DELIVER", + "properties": { + "km": 3373, + "quantity": 76 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "16", + "labels": ["SupplierA"], + "properties": { + "cost": 24, + "co2": 1339, + "name": "SupplierA2", + "lon": 82.77858106851605, + "time": 3, + "lat": 47.17026780787867 + } + }, + { + "identity": "89", + "start": "22", + "end": "16", + "type": "DELIVER", + "properties": { + "km": 4367, + "quantity": 251 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "14", + "labels": ["SupplierA"], + "properties": { + "cost": 33, + "co2": 254, + "name": "SupplierA0", + "lon": 92.70139901973018, + "time": 4, + "lat": 51.13204871524422 + } + }, + { + "identity": "77", + "start": "22", + "end": "14", + "type": "DELIVER", + "properties": { + "km": 7770, + "quantity": 169 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "15", + "labels": ["SupplierA"], + "properties": { + "cost": 30, + "co2": 1251, + "name": "SupplierA1", + "lon": 86.52601778754027, + "time": 0, + "lat": 27.895522473272333 + } + }, + { + "identity": "84", + "start": "23", + "end": "15", + "type": "DELIVER", + "properties": { + "km": 10658, + "quantity": 155 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "14", + "labels": ["SupplierA"], + "properties": { + "cost": 33, + "co2": 254, + "name": "SupplierA0", + "lon": 92.70139901973018, + "time": 4, + "lat": 51.13204871524422 + } + }, + { + "identity": "78", + "start": "23", + "end": "14", + "type": "DELIVER", + "properties": { + "km": 5266, + "quantity": 261 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "16", + "labels": ["SupplierA"], + "properties": { + "cost": 24, + "co2": 1339, + "name": "SupplierA2", + "lon": 82.77858106851605, + "time": 3, + "lat": 47.17026780787867 + } + }, + { + "identity": "90", + "start": "23", + "end": "16", + "type": "DELIVER", + "properties": { + "km": 8681, + "quantity": 157 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "15", + "labels": ["SupplierA"], + "properties": { + "cost": 30, + "co2": 1251, + "name": "SupplierA1", + "lon": 86.52601778754027, + "time": 0, + "lat": 27.895522473272333 + } + }, + { + "identity": "85", + "start": "24", + "end": "15", + "type": "DELIVER", + "properties": { + "km": 8205, + "quantity": 95 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "14", + "labels": ["SupplierA"], + "properties": { + "cost": 33, + "co2": 254, + "name": "SupplierA0", + "lon": 92.70139901973018, + "time": 4, + "lat": 51.13204871524422 + } + }, + { + "identity": "79", + "start": "24", + "end": "14", + "type": "DELIVER", + "properties": { + "km": 7661, + "quantity": 302 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "16", + "labels": ["SupplierA"], + "properties": { + "cost": 24, + "co2": 1339, + "name": "SupplierA2", + "lon": 82.77858106851605, + "time": 3, + "lat": 47.17026780787867 + } + }, + { + "identity": "91", + "start": "24", + "end": "16", + "type": "DELIVER", + "properties": { + "km": 7459, + "quantity": 76 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "18", + "labels": ["SupplierB"], + "properties": { + "cost": 26, + "co2": 255, + "name": "SupplierB1", + "lon": 6.338227685970327, + "time": 5, + "lat": 59.06009250248424 + } + }, + { + "identity": "98", + "start": "25", + "end": "18", + "type": "DELIVER", + "properties": { + "km": 4122, + "quantity": 254 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "17", + "labels": ["SupplierB"], + "properties": { + "cost": 22, + "co2": 503, + "name": "SupplierB0", + "lon": 25.072991281086093, + "time": 4, + "lat": 14.017349778915698 + } + }, + { + "identity": "92", + "start": "25", + "end": "17", + "type": "DELIVER", + "properties": { + "km": 1859, + "quantity": 220 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "17", + "labels": ["SupplierB"], + "properties": { + "cost": 22, + "co2": 503, + "name": "SupplierB0", + "lon": 25.072991281086093, + "time": 4, + "lat": 14.017349778915698 + } + }, + { + "identity": "93", + "start": "26", + "end": "17", + "type": "DELIVER", + "properties": { + "km": 13701, + "quantity": 242 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "18", + "labels": ["SupplierB"], + "properties": { + "cost": 26, + "co2": 255, + "name": "SupplierB1", + "lon": 6.338227685970327, + "time": 5, + "lat": 59.06009250248424 + } + }, + { + "identity": "99", + "start": "26", + "end": "18", + "type": "DELIVER", + "properties": { + "km": 8715, + "quantity": 276 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "18", + "labels": ["SupplierB"], + "properties": { + "cost": 26, + "co2": 255, + "name": "SupplierB1", + "lon": 6.338227685970327, + "time": 5, + "lat": 59.06009250248424 + } + }, + { + "identity": "100", + "start": "27", + "end": "18", + "type": "DELIVER", + "properties": { + "km": 4780, + "quantity": 123 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "17", + "labels": ["SupplierB"], + "properties": { + "cost": 22, + "co2": 503, + "name": "SupplierB0", + "lon": 25.072991281086093, + "time": 4, + "lat": 14.017349778915698 + } + }, + { + "identity": "94", + "start": "27", + "end": "17", + "type": "DELIVER", + "properties": { + "km": 6187, + "quantity": 109 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "18", + "labels": ["SupplierB"], + "properties": { + "cost": 26, + "co2": 255, + "name": "SupplierB1", + "lon": 6.338227685970327, + "time": 5, + "lat": 59.06009250248424 + } + }, + { + "identity": "101", + "start": "28", + "end": "18", + "type": "DELIVER", + "properties": { + "km": 4271, + "quantity": 86 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "17", + "labels": ["SupplierB"], + "properties": { + "cost": 22, + "co2": 503, + "name": "SupplierB0", + "lon": 25.072991281086093, + "time": 4, + "lat": 14.017349778915698 + } + }, + { + "identity": "95", + "start": "28", + "end": "17", + "type": "DELIVER", + "properties": { + "km": 1396, + "quantity": 286 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "18", + "labels": ["SupplierB"], + "properties": { + "cost": 26, + "co2": 255, + "name": "SupplierB1", + "lon": 6.338227685970327, + "time": 5, + "lat": 59.06009250248424 + } + }, + { + "identity": "102", + "start": "29", + "end": "18", + "type": "DELIVER", + "properties": { + "km": 5461, + "quantity": 199 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "17", + "labels": ["SupplierB"], + "properties": { + "cost": 22, + "co2": 503, + "name": "SupplierB0", + "lon": 25.072991281086093, + "time": 4, + "lat": 14.017349778915698 + } + }, + { + "identity": "96", + "start": "29", + "end": "17", + "type": "DELIVER", + "properties": { + "km": 10677, + "quantity": 108 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "18", + "labels": ["SupplierB"], + "properties": { + "cost": 26, + "co2": 255, + "name": "SupplierB1", + "lon": 6.338227685970327, + "time": 5, + "lat": 59.06009250248424 + } + }, + { + "identity": "103", + "start": "30", + "end": "18", + "type": "DELIVER", + "properties": { + "km": 2618, + "quantity": 94 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + }, + { + "keys": ["a", "r"], + "length": 2, + "_fields": [ + { + "identity": "17", + "labels": ["SupplierB"], + "properties": { + "cost": 22, + "co2": 503, + "name": "SupplierB0", + "lon": 25.072991281086093, + "time": 4, + "lat": 14.017349778915698 + } + }, + { + "identity": "97", + "start": "30", + "end": "17", + "type": "DELIVER", + "properties": { + "km": 7472, + "quantity": 273 + } + } + ], + "_fieldLookup": { + "a": 0, + "r": 1 + } + } + ], + "summary": { + "query": { + "text": "MATCH (a)-[r]-() RETURN a, r", + "parameters": {} + }, + "queryType": "r", + "counters": { + "_stats": { + "nodesCreated": 0, + "nodesDeleted": 0, + "relationshipsCreated": 0, + "relationshipsDeleted": 0, + "propertiesSet": 0, + "labelsAdded": 0, + "labelsRemoved": 0, + "indexesAdded": 0, + "indexesRemoved": 0, + "constraintsAdded": 0, + "constraintsRemoved": 0 + }, + "_systemUpdates": 0 + }, + "updateStatistics": { + "_stats": { + "nodesCreated": 0, + "nodesDeleted": 0, + "relationshipsCreated": 0, + "relationshipsDeleted": 0, + "propertiesSet": 0, + "labelsAdded": 0, + "labelsRemoved": 0, + "indexesAdded": 0, + "indexesRemoved": 0, + "constraintsAdded": 0, + "constraintsRemoved": 0 + }, + "_systemUpdates": 0 + }, + "plan": false, + "profile": false, + "notifications": [], + "server": { + "address": "db-xk5qmxsgakadwhvrtomo.graphenedb.com:24786", + "version": "Neo4j/4.1.1", + "protocolVersion": 4.1 + }, + "resultConsumedAfter": { + "low": 2, + "high": 0 + }, + "resultAvailableAfter": { + "low": 1, + "high": 0 + }, + "database": { + "name": "neo4j" + } + } + } + \ No newline at end of file From f42c09adfc314fc9a74b4c07d4dbcb1ea0661f71 Mon Sep 17 00:00:00 2001 From: Luc Belliveau Date: Mon, 4 Mar 2024 18:23:33 +0000 Subject: [PATCH 02/13] added ogma secret and neo4j connection --- frontend/env-dependencies.js | 37 ++++ frontend/jest.setup.ts | 6 + frontend/messages/en-CA.json | 2 +- frontend/messages/fr-CA.json | 4 +- frontend/package-lock.json | 46 ++++- frontend/package.json | 8 +- .../app/_components/TimeTravel.test.tsx | 164 ++--------------- frontend/src/app/[locale]/[day]/page.tsx | 5 - frontend/src/app/_components/TimeTravel.tsx | 166 ++++-------------- frontend/src/app/_utils/dateToStr.tsx | 8 +- frontend/src/styles/globals.css | 4 + 11 files changed, 142 insertions(+), 308 deletions(-) create mode 100644 frontend/env-dependencies.js diff --git a/frontend/env-dependencies.js b/frontend/env-dependencies.js new file mode 100644 index 0000000..8d25051 --- /dev/null +++ b/frontend/env-dependencies.js @@ -0,0 +1,37 @@ +import * as dotenv from "dotenv"; +import pkg from "./package.json" assert { type: "json" }; +import { execSync } from "child_process"; + +dotenv.config(); + +if (!pkg.envDependencies) { + process.exit(0); +} + +const env = Object.assign({}, process.env); + +const deps = Object.entries(pkg.envDependencies); + +if (!Array.isArray(deps) || !deps.every(([_, v]) => typeof v === "string")) { + console.log("ERROR"); + throw new Error(`pkg.envDependencies should have a signature of String[]`); +} + +const parsed = deps.map(([_k, v]) => { + return v.replace(/\${([0-9a-zA-Z_]*)}/g, (_, varName) => { + if (varName in env) { + if (typeof varName === "string") { + const v = env[varName]; + if (typeof v === "string") return v; + } + } + return ""; + }); +}).join(' '); + +try { + execSync('npm install --no-save ' + parsed, { stdio: [0, 1, 2] }) + process.exit(0) +} catch (err) { + throw new Error('Could not install pkg.envDependencies.'); +} diff --git a/frontend/jest.setup.ts b/frontend/jest.setup.ts index 5999ba6..9e26cc5 100644 --- a/frontend/jest.setup.ts +++ b/frontend/jest.setup.ts @@ -4,4 +4,10 @@ const navActual: object = jest.requireActual("next/navigation"); jest.mock("next/navigation", () => ({ ...navActual, useParams: jest.fn(), + useRouter() { + return { + prefetch: jest.fn(), + push: jest.fn(), + } + }, })); diff --git a/frontend/messages/en-CA.json b/frontend/messages/en-CA.json index 701a1f3..74e25dd 100644 --- a/frontend/messages/en-CA.json +++ b/frontend/messages/en-CA.json @@ -5,7 +5,7 @@ "TimeTravel": { "chooseDate": "Today is", "currentDate": "current", - "travelText": "Time travel to", + "travelText": "Time travel", "startButton": "Start", "nextButton": "Next", "lastButton": "Last", diff --git a/frontend/messages/fr-CA.json b/frontend/messages/fr-CA.json index 11cff58..ab1d5d6 100644 --- a/frontend/messages/fr-CA.json +++ b/frontend/messages/fr-CA.json @@ -3,9 +3,9 @@ "title": "Sérum" }, "TimeTravel": { - "chooseDate": "aujourd'hui c'est le", + "chooseDate": "Aujourd'hui c'est le", "currentDate": "actuelle", - "travelText": "Voyager dans le temps vers le", + "travelText": "Voyager dans le temps", "startButton": "Premier", "nextButton": "Prochain", "lastButton": "Dernier", diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c797e6d..21a8c30 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,7 +10,6 @@ "hasInstallScript": true, "dependencies": { "@arcnovus/wet-boew-react": "^2.0.0-beta.9", - "@linkurious/ogma": "https://get.linkurio.us/api/get/npm/ogma/5.0.2/?secret=lk-dls-c6212e2b03b09522c8ac2357791eef0a2f5fa97f", "@linkurious/ogma-react": "^5.0.2", "@next-auth/prisma-adapter": "^1.0.7", "@prisma/client": "^5.6.0", @@ -20,6 +19,7 @@ "@trpc/next": "^10.43.6", "@trpc/react-query": "^10.43.6", "@trpc/server": "^10.43.6", + "date-fns": "^3.3.1", "leaflet": "^1.9.4", "neo4j-driver": "^5.18.0", "next": "^14.0.4", @@ -49,6 +49,7 @@ "@typescript-eslint/eslint-plugin": "^6.11.0", "@typescript-eslint/parser": "^6.11.0", "autoprefixer": "^10.4.14", + "dotenv": "^16.4.5", "eslint": "^8.54.0", "eslint-config-next": "^14.0.4", "eslint-plugin-import": "^2.29.1", @@ -1680,8 +1681,9 @@ } }, "node_modules/@linkurious/ogma": { - "version": "5.0.2", - "resolved": "https://get.linkurio.us/api/get/npm/ogma/5.0.2/?secret=lk-dls-c6212e2b03b09522c8ac2357791eef0a2f5fa97f", + "version": "5.0.3", + "resolved": "https://get.linkurio.us/api/get/npm/ogma/5.0.3/?secret=lk-dls-c6212e2b03b09522c8ac2357791eef0a2f5fa97f", + "peer": true, "optionalDependencies": { "@mapbox/mapbox-gl-rtl-text": "^0.2.0", "@types/leaflet": ">=1.x", @@ -1732,6 +1734,7 @@ "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-rtl-text/-/mapbox-gl-rtl-text-0.2.3.tgz", "integrity": "sha512-RaCYfnxULUUUxNwcUimV9C/o2295ktTyLEUzD/+VWkqXqvaVfFcZ5slytGzb2Sd/Jj4MlbxD0DCZbfa6CzcmMw==", "optional": true, + "peer": true, "peerDependencies": { "mapbox-gl": ">=0.32.1 <2.0.0" } @@ -2583,7 +2586,8 @@ "version": "7946.0.14", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@types/graceful-fs": { "version": "4.1.9", @@ -2696,6 +2700,7 @@ "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.8.tgz", "integrity": "sha512-EXdsL4EhoUtGm2GC2ZYtXn+Fzc6pluVgagvo2VC1RHWToLGlTRwVYoDpqS/7QXa01rmDyBjJk3Catpf60VMkwg==", "optional": true, + "peer": true, "dependencies": { "@types/geojson": "*" } @@ -3059,6 +3064,7 @@ "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.2.0.tgz", "integrity": "sha512-/vUqU/UY4MVeFsg+SsK6c+/05RZXIHZMGJA+PX5JyWI0ZRcBpupnRuPLU/NXXoFwMYCPCoxIfElM2eS+DUXCqQ==", "optional": true, + "peer": true, "dependencies": { "exit-on-epipe": "~1.0.1", "printj": "~1.1.0" @@ -3830,6 +3836,7 @@ "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", "optional": true, + "peer": true, "dependencies": { "adler-32": "~1.3.0", "crc-32": "~1.2.0" @@ -3843,6 +3850,7 @@ "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", "optional": true, + "peer": true, "engines": { "node": ">=0.8" } @@ -4010,6 +4018,7 @@ "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.14.0.tgz", "integrity": "sha512-iz3zJLhlrg37/gYRWgEPkaFTtzmnEv1h+r7NgZum2lFElYQPi0/5bnmuDfODHxfp0INEfnRqyfyeIJDbb7ahRw==", "optional": true, + "peer": true, "dependencies": { "commander": "~2.14.1", "exit-on-epipe": "~1.0.1" @@ -4025,7 +4034,8 @@ "version": "2.14.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==", - "optional": true + "optional": true, + "peer": true }, "node_modules/collect-v8-coverage": { "version": "1.0.2", @@ -4189,6 +4199,7 @@ "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "optional": true, + "peer": true, "bin": { "crc32": "bin/crc32.njs" }, @@ -4726,6 +4737,18 @@ "node": ">=12" } }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/draw-svg-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/draw-svg-path/-/draw-svg-path-1.0.0.tgz", @@ -5690,6 +5713,7 @@ "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", "optional": true, + "peer": true, "engines": { "node": ">=0.8" } @@ -5822,7 +5846,8 @@ "version": "0.3.11", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.3.11.tgz", "integrity": "sha512-Rr5QlUeGN1mbOHlaqcSYMKVpPbgLy0AWT/W0EHxA6NGI12yO1jpoui2zBBvU2G824ltM6Ut8BFgfHSBGfkmS0A==", - "optional": true + "optional": true, + "peer": true }, "node_modules/file-entry-cache": { "version": "6.0.1", @@ -5952,6 +5977,7 @@ "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", "optional": true, + "peer": true, "engines": { "node": ">=0.8" } @@ -9989,6 +10015,7 @@ "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", "optional": true, + "peer": true, "bin": { "printj": "bin/printj.njs" }, @@ -10827,6 +10854,7 @@ "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", "optional": true, + "peer": true, "dependencies": { "frac": "~1.1.2" }, @@ -12094,6 +12122,7 @@ "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", "optional": true, + "peer": true, "engines": { "node": ">=0.8" } @@ -12103,6 +12132,7 @@ "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", "optional": true, + "peer": true, "engines": { "node": ">=0.8" } @@ -12259,6 +12289,7 @@ "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.17.0.tgz", "integrity": "sha512-bZ36FSACiAyjoldey1+7it50PMlDp1pcAJrZKcVZHzKd8BC/z6TQ/QAN8onuqcepifqSznR6uKnjPhaGt6ig9A==", "optional": true, + "peer": true, "dependencies": { "adler-32": "~1.2.0", "cfb": "^1.1.4", @@ -12282,7 +12313,8 @@ "version": "2.17.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "optional": true + "optional": true, + "peer": true }, "node_modules/xml-name-validator": { "version": "4.0.0", diff --git a/frontend/package.json b/frontend/package.json index c855f9c..91a66d2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,7 +8,7 @@ "db:push": "prisma db push", "db:studio": "prisma studio", "dev": "next dev", - "postinstall": "prisma generate", + "postinstall": "node env-dependencies.js && prisma generate", "lint": "next lint", "start": "next start", "test": "jest", @@ -30,7 +30,6 @@ }, "dependencies": { "@arcnovus/wet-boew-react": "^2.0.0-beta.9", - "@linkurious/ogma": "https://get.linkurio.us/api/get/npm/ogma/5.0.2/?secret=lk-dls-c6212e2b03b09522c8ac2357791eef0a2f5fa97f", "@linkurious/ogma-react": "^5.0.2", "@next-auth/prisma-adapter": "^1.0.7", "@prisma/client": "^5.6.0", @@ -40,6 +39,7 @@ "@trpc/next": "^10.43.6", "@trpc/react-query": "^10.43.6", "@trpc/server": "^10.43.6", + "date-fns": "^3.3.1", "leaflet": "^1.9.4", "neo4j-driver": "^5.18.0", "next": "^14.0.4", @@ -56,6 +56,9 @@ "usehooks-ts": "^2.15.1", "zod": "^3.22.4" }, + "envDependencies": { + "@linkurious/ogma": "https://get.linkurio.us/api/get/npm/ogma/5.0.3/?secret=${OGMA_KEY}" + }, "devDependencies": { "@testing-library/jest-dom": "^6.4.2", "@testing-library/react": "^14.2.1", @@ -69,6 +72,7 @@ "@typescript-eslint/eslint-plugin": "^6.11.0", "@typescript-eslint/parser": "^6.11.0", "autoprefixer": "^10.4.14", + "dotenv": "^16.4.5", "eslint": "^8.54.0", "eslint-config-next": "^14.0.4", "eslint-plugin-import": "^2.29.1", diff --git a/frontend/src/__tests__/app/_components/TimeTravel.test.tsx b/frontend/src/__tests__/app/_components/TimeTravel.test.tsx index fac3738..b8b3e71 100644 --- a/frontend/src/__tests__/app/_components/TimeTravel.test.tsx +++ b/frontend/src/__tests__/app/_components/TimeTravel.test.tsx @@ -1,6 +1,5 @@ import { cleanup, - fireEvent, render, screen, within, @@ -30,11 +29,9 @@ describe("TimeTravel [component]", () => { // endDate = new Date(2020, 0, 31, 12), const { container } = render(); - - const anchors = container.getElementsByTagName("A"); - expect(anchors.length).toBe(66); - expect(anchors[2]?.textContent).toContain("Dec 1, 2019"); - expect(anchors[anchors.length - 3]?.textContent).toContain("Jan 31, 2020"); + const navs = container.getElementsByTagName("nav"); + expect(navs.length).toBe(1); + expect(navs[0]?.textContent).toContain("December 1, 2019"); }); it("doesn't allow invalid dates", () => { @@ -65,7 +62,7 @@ describe("TimeTravel [component]", () => { const e = new Date(2020, 3, 3); mockUseParams.mockReturnValue({ locale: "en-CA" }); render(); - expect(await screen.findByText("Apr 1, 2020")).toBeVisible(); + expect(screen.getByText("April 1, 2020", { exact: false })).toBeVisible(); }); it("can use the fr-CA locale", async () => { @@ -73,163 +70,22 @@ describe("TimeTravel [component]", () => { const e = new Date(2020, 3, 3); mockUseParams.mockReturnValue({ locale: "fr-CA" }); render(); - expect(await screen.findByText("1 avr. 2020")).toBeVisible(); + expect(screen.getByText("1 avril 2020", { exact: false })).toBeVisible(); }); it("displays translated messages", async () => { mockUseParams.mockReturnValue({ locale: "en-CA" }); const chooseDate = "478cb3ih3c8y3gf38gf3"; - const currentDate = "321erwdfkjwfjhn3"; const travelText = "dasdasdsad"; const { container } = render( - , + , ); expect( - await within(screen.getByRole("heading", { level: 4 })).findByText( - chooseDate, - ), + within(screen.getByRole("heading", { level: 4 })).getByText(chooseDate, { + exact: false, + }), ).toBeVisible(); - const active = container.getElementsByClassName("active"); - expect(active[0]?.textContent).toContain(currentDate); + const active = container.getElementsByTagName("button"); expect(active[0]?.textContent).toContain(travelText); }); - - it("adds the active class to the specified date", async () => { - mockUseParams.mockReturnValue({ locale: "en-CA" }); - const s = new Date(2020, 1, 1); - const e = new Date(2020, 2, 1); - const d = new Date(2020, 1, 5); - - const { container } = render( - , - ); - const active = container.getElementsByClassName("active"); - expect(active.length).toEqual(1); - expect(active[0]?.tagName).toEqual("LI"); - expect(active[0]?.textContent).toContain("Feb 5, 2020"); - }); - - it("can trigger the onChange handler", async () => { - mockUseParams.mockReturnValue({ locale: "en-CA" }); - const onChange = jest.fn(); - const s = new Date(2020, 1, 1); - const e = new Date(2020, 2, 1); - const d = new Date(2020, 1, 5); - - const { container } = render( - , - ); - const active = container.getElementsByTagName("A"); - expect(active.length).toBeGreaterThan(1); - const elem = active[2]; - expect(elem?.textContent).toContain("Feb 1, 2020"); - elem && fireEvent.click(elem); - expect(onChange).toHaveBeenCalledWith(s, expect.anything()); - }); - - it("can go to start using start button", async () => { - mockUseParams.mockReturnValue({ locale: "en-CA" }); - const onChange = jest.fn(); - const s = new Date(2020, 1, 1); - const e = new Date(2020, 2, 1); - const d = new Date(2020, 1, 5); - - const { container } = render( - , - ); - const active = container.getElementsByTagName("A"); - expect(active.length).toBeGreaterThan(1); - const elem = active[0]; - expect(elem?.textContent).toContain("Start"); - elem && fireEvent.click(elem); - expect(onChange).toHaveBeenCalledWith(s, expect.anything()); - }); - it("can go to previous day using prev button", async () => { - mockUseParams.mockReturnValue({ locale: "en-CA" }); - const onChange = jest.fn(); - const s = new Date(2020, 1, 1); - const e = new Date(2020, 2, 1); - const d = new Date(2020, 1, 5); - - const { container } = render( - , - ); - const active = container.getElementsByTagName("A"); - expect(active.length).toBeGreaterThan(1); - const elem = active[1]; - expect(elem?.textContent).toContain("Prev"); - elem && fireEvent.click(elem); - expect(onChange).toHaveBeenCalledWith( - new Date(d.setDate(d.getDate() - 1)), - expect.anything(), - ); - }); - - it("start and prev buttons are disabled when start date is active", async () => { - const s = new Date(2020, 1, 1); - const e = new Date(2020, 2, 1); - - const { container } = render( - , - ); - const active = container.getElementsByTagName("A"); - expect(active.length).toBeGreaterThan(4); - const elem1 = active[0]; - const elem2 = active[1]; - expect(elem1?.getAttribute("aria-disabled")).toBe("true"); - expect(elem2?.getAttribute("aria-disabled")).toBe("true"); - }); - - it("next and last buttons are disabled when end date is active", async () => { - const s = new Date(2020, 1, 1); - const e = new Date(2020, 2, 1); - - const { container } = render( - , - ); - const active = container.getElementsByTagName("A"); - expect(active.length).toBeGreaterThan(4); - const elem1 = active[active.length - 1]; - const elem2 = active[active.length - 2]; - expect(elem1?.getAttribute("aria-disabled")).toBe("true"); - expect(elem2?.getAttribute("aria-disabled")).toBe("true"); - }); - - it("can go to last using end button", async () => { - mockUseParams.mockReturnValue({ locale: "en-CA" }); - const onChange = jest.fn(); - const s = new Date(2020, 1, 1); - const e = new Date(2020, 2, 1); - const d = new Date(2020, 1, 5); - - const { container } = render( - , - ); - const active = container.getElementsByTagName("A"); - expect(active.length).toBeGreaterThan(1); - const elem = active[active.length - 1]; - expect(elem?.textContent).toContain("End"); - elem && fireEvent.click(elem); - expect(onChange).toHaveBeenCalledWith(e, expect.anything()); - }); - it("can go to next day using next button", async () => { - mockUseParams.mockReturnValue({ locale: "en-CA" }); - const onChange = jest.fn(); - const s = new Date(2020, 1, 1); - const e = new Date(2020, 2, 1); - const d = new Date(2020, 1, 5); - - const { container } = render( - , - ); - const active = container.getElementsByTagName("A"); - expect(active.length).toBeGreaterThan(1); - const elem = active[active.length - 2]; - expect(elem?.textContent).toContain("Next"); - elem && fireEvent.click(elem); - expect(onChange).toHaveBeenCalledWith( - new Date(d.setDate(d.getDate() + 1)), - expect.anything(), - ); - }); }); diff --git a/frontend/src/app/[locale]/[day]/page.tsx b/frontend/src/app/[locale]/[day]/page.tsx index caeb1ae..30662bf 100644 --- a/frontend/src/app/[locale]/[day]/page.tsx +++ b/frontend/src/app/[locale]/[day]/page.tsx @@ -27,12 +27,7 @@ export default async function Index({ date={date} messages={{ chooseDate: timeTravelMsg("chooseDate"), - currentDate: timeTravelMsg("currentDate"), travelText: timeTravelMsg("travelText"), - startButton: timeTravelMsg("startButton"), - nextButton: timeTravelMsg("nextButton"), - lastButton: timeTravelMsg("lastButton"), - previousButton: timeTravelMsg("previousButton"), }} />
diff --git a/frontend/src/app/_components/TimeTravel.tsx b/frontend/src/app/_components/TimeTravel.tsx index 14db827..723d6f5 100644 --- a/frontend/src/app/_components/TimeTravel.tsx +++ b/frontend/src/app/_components/TimeTravel.tsx @@ -5,12 +5,15 @@ * the user to select. */ -import Link from "next/link"; -import { useParams } from "next/navigation"; +import { useParams, useRouter } from "next/navigation"; +import DatePicker from "react-datepicker"; import React, { useCallback, useEffect, useMemo, useRef } from "react"; import { ErrorBoundary, type FallbackProps } from "react-error-boundary"; +import { enCA, frCA } from "date-fns/locale"; import useDateToStr from "~/app/_hooks/useDateToStr"; +import "react-datepicker/dist/react-datepicker.css"; + type TimeTravelProps = { date?: Date; startDate?: Date; @@ -18,11 +21,6 @@ type TimeTravelProps = { messages?: { chooseDate?: string; travelText?: string; - currentDate?: string; - startButton?: string; - previousButton?: string; - nextButton?: string; - lastButton?: string; }; onChange?: ( date: Date, @@ -48,6 +46,8 @@ function TimeTravelComponent({ const { locale } = useParams(); const selectedDate = useRef(null); + const router = useRouter(); + const dateToStr = useDateToStr(locale); useEffect(() => { @@ -56,145 +56,49 @@ function TimeTravelComponent({ c.scrollIntoView({ behavior: "auto", inline: "center", - block: "end" + block: "end", }); } }, [selectedDate]); - const dayMap = useMemo( - () => - Array.from({ length: days }, (_, i) => { - const r = new Date(startDate); - r.setDate(r.getDate() + i); - return r; - }), - [days, startDate], - ); - const handleDateChange = useCallback( - (p: Date) => (evt: React.MouseEvent) => { + (p: Date, evt: React.MouseEvent) => { if (onChange) { onChange(p, evt); } + if (!evt.defaultPrevented) { + const d = (p.getTime() - startDate.getTime()) / 86400000; + router.push(`${d + 1}`); + } }, - [onChange], + [onChange, router, startDate], ); - const currentIndex = useMemo(() => { - return ( - dayMap - .map((d, i) => [d, i] as [Date, number]) - .filter(([d]) => d.getTime() === date.getTime()) - .reduce((p, c) => c[1], -1) + 1 - ); - }, [dayMap, date]); - - const prevDisabled = useMemo( - () => date.getTime() === startDate.getTime(), - [date, startDate], - ); - - const nextDisabled = useMemo( - () => date.getTime() === endDate.getTime(), + const showPreviousMonths = useMemo( + () => date.getMonth() === endDate.getMonth(), [date, endDate], ); return ( -